Git, the saga continues ..
Introduction to more advanced workflows with multiple committers and branches.
Following on from the first post a couple of months back I wanted to add a few more advanced workflows to my “how to git” series. In the previous article I talked about git from the perspective of a single developer executing simple changes to a repo and wanting to record that change at github.com. In this article I want to discuss multiple committers on a single repository and the use of branches.
Multiple Users
Below is an example of multiple users working on the same repository on the same branch (we will talk about branches later). For simplicity I have continued using the same repository as the initial example but added a second user.
User 2
First this will clone the repository and then makes some additions and deletions
git clone
...
git add users2-folder/
git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: users2-folder/more-junk.sh
Next we need to make a commit
git commit -m "added a folder and a script"
[master 9643d19] added a folder and a script
1 file changed, 5 insertions(+)
create mode 100644 users2-folder/more-junk.sh
Finally push the changes that User 2 has made to the remote repository.
git push origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (4/4), 412 bytes | 68.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To https://github.com/gregory-smith/test-branching
44089aa..9643d19 master -> master
Now the interesting thing is that the first user User 1 who we used to demonstrate the original workflow does not have these changes that User 2 has just made. So what do they see when they start performing edits?
User 1
git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README.md
modified: users1-folder/junk.sh
no changes added to commit (use "git add" and/or "git commit -a")
Lets add this users most recent change to a commit and try and push it to the remote repository
git add README.md
git add users1-folder/junk.sh
git commit -m "more edits"
git push origin master
To https://github.com/gregory-smith/test-branching
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'https://github.com/gregory-smith/test-branching'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
User 1 cannot push these changes to the remote repository at github.com because they do not have the latest commits that User 2 has just committed to this branch. User 1 need to synchronise their local copy with the latest changes made by the other committers by performing a pull.
git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), done.
From https://github.com/gregory-smith/test-branching
44089aa..9643d19 master -> origin/master
Merge made by the 'recursive' strategy.
users2-folder/more-junk.sh | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 users2-folder/more-junk.sh
This pull needs to merge the commit made by User 1 to the commit already pushed to github.com by User 2. So User 1 needs to put some entries in a text file explain why I need to merge.
Merge branch 'master' of https://github.com/gregory-smith/test-branching
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
Now User 1 can push the changes they have made.
git push origin master
Enumerating objects: 15, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (7/7), 762 bytes | 95.00 KiB/s, done.
Total 7 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To https://github.com/gregory-smith/test-branching
9643d19..97f310a master -> master
But now User 2 is out of synch with the changes made by User 1, the last thing User 2 wants to do is start doing merges, how can they avoid it? Rather than merging lets get the latest copy of the master branch before we start making any edits.
git pull
remote: Enumerating objects: 15, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 7 (delta 2), reused 7 (delta 2), pack-reused 0
Unpacking objects: 100% (7/7), done.
From https://github.com/gregory-smith/test-branching
9643d19..97f310a master -> origin/master
Updating 9643d19..97f310a
Fast-forward
README.md | 1 +
users1-folder/junk.sh | 2 ++
2 files changed, 3 insertions(+)
So if you have multiplee committers, or just generally for good hygeine, to the Basic workflow commands listed in the first article add the the following rule:
Always perform a git pull before you start making any edits to minimise the chances of merges
Branching
Below is an example of branching using 3 users. The original User 1 and User 2 who now are up to date on the default “master” branch and a third user we will introduce later.
First let’s list the branches that we have.
User 1
git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/master
Let’s create a new branch called “customer1”
git checkout -b customer1
Switched to a new branch 'customer1'
That was easy, now we are in the customer1 branch.
git branch -a
* customer1
master
remotes/origin/HEAD -> origin/master
remotes/origin/master
Let’s make some edits for “customer1”
git status
On branch customer1
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: users1-folder/junk.sh
deleted: users2-folder/more-junk.sh
Untracked files:
(use "git add <file>..." to include in what will be committed)
customer1-specific/
no changes added to commit (use "git add" and/or "git commit -a")
Hopefully the next steps are getting familiar
git add users1-folder/junk.sh
git rm users2-folder/more-junk.sh
git add customer1-specific/config.json
git status
On branch customer1
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: customer1-specific/config.json
modified: users1-folder/junk.sh
deleted: users2-folder/more-junk.sh
Finally let’s push the new branch to the remote repository.
git push origin customer1
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (6/6), 545 bytes | 77.00 KiB/s, done.
Total 6 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'customer1' on GitHub by visiting:
remote: https://github.com/gregory-smith/test-branching/pull/new/customer1
remote:
To https://github.com/gregory-smith/test-branching
* [new branch] customer1 -> customer1
User 2
Now User2 is still working on the Master branch
git pull
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 1), reused 6 (delta 1), pack-reused 0
Unpacking objects: 100% (6/6), done.
From https://github.com/gregory-smith/test-branching
* [new branch] customer1 -> origin/customer1
Already up to date.
so when they do a pull, they discover that a new branch exists, but it does not pull from that branch.
git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/customer1
remotes/origin/master
If I want to work on that branch I must explicitly pull it.
git pull origin customer1
From https://github.com/gregory-smith/test-branching
* branch customer1 -> FETCH_HEAD
Updating 97f310a..0a95ec8
Fast-forward
customer1-specific/config.json | 2 ++
users1-folder/junk.sh | 1 +
users2-folder/more-junk.sh | 5 -----
3 files changed, 3 insertions(+), 5 deletions(-)
create mode 100644 customer1-specific/config.json
delete mode 100644 users2-folder/more-junk.sh
Now I have both branches I should be able to switch between them. Note when you switch between branches how the contents of your local repository change to reflect the files and directories that exist in that branch.
git checkout customer1
Switched to branch 'customer1'
gregs-mac:test-branching gregorysmith$ ls
README.md customer1-specific users1-folder
git checkout master
Switched to branch 'master'
gregs-mac:test-branching gregorysmith$ ls
README.md users1-folder users2-folder
let’s make some changes in the master branch that User 1 has been working on.
git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
git add README.md
git commit -m "added some edits to the README"
[master ef7ed2d] added some edits to the README
1 file changed, 1 insertion(+)
User 1
Lets switch back to User1 who is still working on the customer1 branch
git branch -a
* customer1
master
remotes/origin/HEAD -> origin/master
remotes/origin/customer1
remotes/origin/master
User1 wants to pull the latest commits so issues a pull command
git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/gregory-smith/test-branching
97f310a..ef7ed2d master -> origin/master
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> customer1
Notice that git pull will not work when we have more than one branch, we must specify the branch you want to pull from.
git pull origin customer1
From https://github.com/gregory-smith/test-branching
* branch customer1 -> FETCH_HEAD
Already up to date.
Let’s make some changes and push them to the remote repository in the customer1 branch
git add README.md
git status
On branch customer1
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
git commit -m "edits to README.md for customer1 by user1"
[customer1 6f911d9] edits to README.md for customer1 by user1
1 file changed, 2 insertions(+)
And finally lets push the commit to the remote repository.
git push origin customer1
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 430 bytes | 143.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/gregory-smith/test-branching
0a95ec8..6f911d9 customer1 -> customer1
It is worth noting that we can push changes to this branch without needing to pull for new changes in the other branches. In other words branches are independent of one another.
Finally and importantly, I have a third user who only wants to clone a single branch for a customer.
git clone --single-branch --branch customer1 https://github.com/gregory-smith/test-branching
Cloning into 'test-branching'...
remote: Enumerating objects: 28, done.
remote: Counting objects: 100% (28/28), done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 28 (delta 7), reused 21 (delta 3), pack-reused 0
Unpacking objects: 100% (28/28), done.
This third user, can then only see the files and directories in the customer1 branch and nothing else.
gregs-mac:user3 gregorysmith$ ls
test-branching
gregs-mac:user3 gregorysmith$ cd test-branching/
gregs-mac:test-branching gregorysmith$ ls
README.md customer1-specific users1-folder
Summarising the commands that we used for branching
git branch -a
git pull <branch>
git checkout
git push origin <branch>
Creating branches is definitely more complex, but git does a fantastic job of keeping those branches independent. Just remember to:
- check what branch you are currently working on.
- Verify that you are up to date prior to making changes by performing a pull.
