Data Smith

The Data Blog

Musings and ramblings on a variety of topics.

Git, the saga continues ..

Introduction to more advanced workflows with multiple committers and branches.

Greg Smith

10 minute read

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.

Recent posts

About

This is my personal blog space that I use to write about the many different things that interest me. To find out a little more about me click on the link