How to collaborate on code in git
How to collaborate on code in git
Problem
After you have shared the code, you are going to want to collaborate on the code with others. But how?
Solution
There are a few popular workflows using git:
This tutorial will walk through collaborating using the feature branch workflow:
Main Idea
Every developer will contribute code using feature
feature
branch will contain all of the code changes needed to add a single feature to the project. A feature can be new functionality, code cleanup (or refactors), bug fixes, etc.All feature
branches will branch from the master
branch and will eventually get merged back in to the master
master
branch is the only branch that lives forever and feature
branches should be short lived because they should go away after the feature is implemented and integrated into the rest of the application as soon as possible to avoid merge conflicts. Merging long living branches usually increases the chances of merge conflicts.Typically, this is how feature
branches should look relative to the master
branch:
For the feature/a
, feature/b
, and feature/c
branches above, they can consist of commits from a single or multiple developers. Another way of saying it is that each feature can be built by either a single developer or multiple developers.
Just a reminder, every commit is a snapshot in time of the code. Each commit on a feature
branch is a change to the code that supports that branch’s feature. Each commit on the master
branch is a snapshot of the application code that has all previous features integrated.
Example
In this example, we will go through how the feature branch workflow would work in a team with 2 people that uses GitHub. The same would be true for 3, 4, or more people in a team.
In this example, these are the events that will happen:
- Developer A (Mickey), Developer B (Minnie), and remote (GitHub) will start off at the same commit on
master
branch - Developer A (Mickey) and Developer B (Minnie) will branch off of
master
to create their ownfeature
branch - Developer A (Mickey) and Developer B (Minnie) finishes their features
- Developer A (Mickey) fetches from remote (GitHub)
- Developer A (Mickey) merges his
feature
branch in tomaster
branch - Developer A (Mickey) pushes his updated
master
branch to remote (GitHub) - Developer B (Minnie) fetches from remote (GitHub)
- Developer B (Minnie) merges her
feature
branch in tomaster
branch - Developer B (Minnie) pushes her updated
master
branch to remote (GitHub) - Developer A (Mickey) fetches from remote (Github)
Developer A (Mickey), Developer B (Minnie), and GitHub will start off at the same commit on master branch
We will start off assuming all of the developers have the latest version of master
. That means the Developer A’s (Mickey’s) local master
branch is identical to the remote’s (GitHub’s) master
branch. From Developer A’s (Mickey’s) perspective, the remote’s (GitHub’s) master
branch is known as his remote master
branch.
The same is true for Developer B (Minnie). Developer B’s (Minnie’s) local master
branch is identical to the remote’s (GitHub’s) master
branch. From Developer B’s (Minnie’s) perspective, the remote’s (GitHub’s) master
branch is known as her remote master
branch.
Developer A (Mickey) and Developer B (Minnie) will branch off of master to create their own feature branch
Each developer will create a new branch to start their individual feature. You can use to create a new branch and then to switch to the new branch. Alternatively, you can use to create and switch to a new branch with one command.
developerA(mickey)$ git branch feature/a # create a new branch named feature/a from the current branch/commitdeveloperA(mickey)$ git branch # list branches to see newly created branch feature/a* masterdeveloperA(mickey)$ git checkout feature/a # switch to newly created branch feature/aSwitched to branch 'feature/a'developerA(mickey)$ git branch # verify we have switched branches* feature/a master
developerB(minnie)$ git checkout -b feature/b # create feature/b branch and switch to it in a single command instead of 2 separate commands like aboveSwitched to a new branch 'feature/b'developerB(minnie)$ git branch # verify we are on the correct branch* feature/b master
Developer A (Mickey) and Developer B (Minnie) finishes their features
Before committing any changes make sure you are on the branch that you want to commit to using , , and/or .
Developer A (Mickey) makes changes and commits/saves them.
# make changes and commit/save changesdeveloperA(mickey)$ git checkout feature/a # make sure you are working on the correct branchSwitched to branch 'feature/a'developerA(mickey)$ touch a1.txtdeveloperA(mickey)$ git add a1.txtdeveloperA(mickey)$ git commit -m 'add a1.txt' # aka commit a1[feature/a 3e789b5] add a1.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 a1.txtdeveloperA(mickey)$ touch a2.txtdeveloperA(mickey)$ git add a2.txtdeveloperA(mickey)$ git commit -m 'add a2.txt' # aka commit a2[feature/a 04d2987] add a2.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 a2.txtdeveloperA(mickey)$ touch a3.txtdeveloperA(mickey)$ git add a3.txtdeveloperA(mickey)$ git commit -m 'add a3.txt' # aka commit a3[feature/a e52dcbc] add a3.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 a3.txt
# view new commitsdeveloperA(mickey)$ git log --oneline --decorate --all --graph* e52dcbc (HEAD -> feature/a) add a3.txt* 04d2987 add a2.txt* 3e789b5 add a1.txt* 5300667 (origin/master, master) add application2.txt* 888c144 add application1.txt
Developer B (Minnie) makes changes and commits/saves them.
# make changes and commit/save changesdeveloperB(minnie)$ git checkout feature/b # make sure you are working on the correct branchSwitched to branch 'feature/b'developerB(minnie)$ touch b1.txtdeveloperB(minnie)$ git add b1.txtdeveloperB(minnie)$ git commit -m 'created b1.txt' # aka commit b1[feature/b 77d32f6] created b1.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 b1.txtdeveloperB(minnie)$ touch b2.txtdeveloperB(minnie)$ git add b2.txtdeveloperB(minnie)$ git commit -m 'created b2.txt' # aka commit b2[feature/b d680cf6] created b2.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 b2.txt
# view new commitsdeveloperB(minnie)$ git log --all --decorate --oneline --graph* d680cf6 (HEAD -> feature/b) created b2.txt* 77d32f6 created b1.txt* 5300667 (origin/master, master) add application2.txt* 888c144 add application1.txt
Developer A (Mickey) fetches from remote (GitHub)
Because we are never sure if someone else has pushed code up to the remote (GitHub), it is always safe practice to fetch from the remote (GitHub) before updating any shared branches, such as the master
branch. This ensures we are manipulating the latest version of the shared (master
) branch. To fetch from the remote (GitHub), we use the command.
developerA(mickey)$ git fetch origin# no new changes were fetched from origin/remote/GitHub
Developer A (Mickey) merges his feature branch in to master branch
To merge the feature
branch in to the master
branch, make sure you are on the master
branch before issuing the command. Remember to always be on the branch that is getting merged in to.
The option is used to help preserve the appearance that a branch was merged in.
developerA(mickey)$ git checkout masterSwitched to branch 'master'Your branch is up to date with 'origin/master'.developerA(mickey)$ git merge feature/a --no-ff -m 'merge feature/a branch into master branch'Merge made by the 'recursive' strategy. a1.txt | 0 a2.txt | 0 a3.txt | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a1.txt create mode 100644 a2.txt create mode 100644 a3.txt
developerA(mickey)$ git log --graph --all --oneline --decorate* b3df9a3 (HEAD -> master) merge feature/a branch into master branch|\ | * c3a9a88 (feature/a) created a3.txt| * 64191e8 created a2.txt| * e1a8e1c created a1.txt|/ * 5300667 (origin/master) add application2.txt* 888c144 add application1.txt
Notice that the master
and origin/master
branches are no longer on the same commit because the local master
branch is now ‘ahead of the’ (i.e. more up to date than) remote master
branch.
Developer A (Mickey) pushes his updated master branch to remote (GitHub)
Now that Developer A’s (Mickey’s) added functionality is integrated into the application, he needs to push his changes to remote (GitHub) so that others can fetch his changes. We can use command to do that.
developerA(mickey)$ git push originEnumerating objects: 8, done.Counting objects: 100% (8/8), done.Delta compression using up to 4 threads.Compressing objects: 100% (7/7), done.Writing objects: 100% (7/7), 729 bytes | 729.00 KiB/s, done.Total 7 (delta 3), reused 0 (delta 0)remote: Resolving deltas: 100% (3/3), done.To https://github.com/zhao-lin-li/disney 5300667..95bcc26 master -> master
developerA(mickey)$ git log --graph --all --oneline --decorate* 95bcc26 (HEAD -> master, origin/master) merge feature/a branch into master branch|\ | * c3a9a88 (feature/a) created a3.txt| * 64191e8 created a2.txt| * e1a8e1c created a1.txt|/ * 5300667 add application2.txt* 888c144 add application1.txt
Notice that the master
and origin/master
branches are now on the same commit.
Developer B (Minnie) fetches from remote (GitHub)
Again it is good practice to always fetch before operating on a shared branch, such as master
.
In this particular scenario, Developer B (Minnie) did indeed needed to bring her local master
branch up to date.
If Developer B (Minnie) did not fetch before merging, her merge would have made her local master
branch divergent from the remote (GitHub) master
branch.
developerB(minnie)$ git fetch originremote: Counting objects: 7, done.remote: Compressing objects: 100% (4/4), done.remote: Total 7 (delta 3), reused 7 (delta 3), pack-reused 0Unpacking objects: 100% (7/7), done.From https://github.com/zhao-lin-li/disney 5300667..95bcc26 master -> origin/master
developerB(minnie)$ git log --all --decorate --oneline --graph* 95bcc26 (origin/master) merge feature/a branch into master branch|\ | * c3a9a88 created a3.txt| * 64191e8 created a2.txt| * e1a8e1c created a1.txt|/ | * d680cf6 (HEAD -> feature/b) created b2.txt| * 77d32f6 created b1.txt|/ * 5300667 (master) add application2.txt* 888c144 add application1.txt
Notice that the master
and origin/master
branches are no longer on the same commit because the local master
branch is ‘behind the’ (i.e. more out of date than) remote master
branch.
Developer B (Minnie) needs to bring her local master
branch up to date with the remote (GitHub) master
branch. We can do this using the git rebase
command.
developerB(minnie)$ git checkout master # make sure we are operating on the correct branchSwitched to branch 'master'Your branch is behind 'origin/master' by 4 commits, and can be fast-forwarded. (use "git pull" to update your local branch)
# using git pull would work too
developerB(minnie)$ git rebase origin/masterFirst, rewinding head to replay your work on top of it...Fast-forwarded master to origin/master.developerB(minnie)$ git log --all --decorate --oneline --graph* 95bcc26 (HEAD -> master, origin/master) merge feature/a branch into master branch|\ | * c3a9a88 created a3.txt| * 64191e8 created a2.txt| * e1a8e1c created a1.txt|/ | * d680cf6 (feature/b) created b2.txt| * 77d32f6 created b1.txt|/ * 5300667 add application2.txt* 888c144 add application1.txt
Notice that the master
and origin/master
branches are now on the same commit because the local master
branch been updated to match the remote master
branch.
Also note the feature/b
branch is shown relative to the new commits by Developer A (Mickey). Commit 77d32f6
and e1a8e1c
are both created from commit 5300667
. That’s the commit that both branches were created from.
Developer B (Minnie) merges her feature branch in to master branch
Developer B (Minnie) will now have to merge her feature
branch into the master
branch using just as Developer A (Mickey) had done.
Again, remember to always be on the branch that is getting merged in to, which in this case is the master
branch. And we will use the --no-ff
option again to preserve the appearance that a branch was merged in. Though this time git would not be able to fast-forward and lose that appearance, because of it needing to account for commit C.
developerB(minnie)$ git merge feature/b --no-ff -m 'merge feature/b branch into master branch'Merge made by the 'recursive' strategy. b1.txt | 0 b2.txt | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 b1.txt create mode 100644 b2.txt
developerB(minnie)$ git log --all --decorate --oneline --graph* d444aae (HEAD -> master) merge feature/b branch into master branch|\ | * d680cf6 (feature/b) created b2.txt| * 77d32f6 created b1.txt* | 95bcc26 (origin/master) merge feature/a branch into master branch|\ \ | |/ |/| | * c3a9a88 created a3.txt| * 64191e8 created a2.txt| * e1a8e1c created a1.txt|/ * 5300667 add application2.txt* 888c144 add application1.txt
Notice that the master
and origin/master
branches are no longer on the same commit because the local master
branch is now ‘ahead of the’ (i.e. more up to date than) remote master
branch.
It is a bit hard to tell, but the graph does show two branches branching off of master
branch and then getting merged back into master
branch.
Developer B (Minnie) pushes her updated master branch to remote (GitHub)
Now that Developer B’s (Minnie’s) added functionality is integrated into the application along with Developer A’s (Mickey’s) feature, she needs to push her changes to remote (GitHub) using the command just like before.
developerB(minnie)$ git push originEnumerating objects: 8, done.Counting objects: 100% (8/8), done.Delta compression using up to 4 threads.Compressing objects: 100% (6/6), done.Writing objects: 100% (6/6), 650 bytes | 325.00 KiB/s, done.Total 6 (delta 3), reused 0 (delta 0)remote: Resolving deltas: 100% (3/3), completed with 1 local object.To https://github.com/zhao-lin-li/disney 95bcc26..d444aae master -> master
developerB(minnie)$ git log --all --decorate --oneline --graph* d444aae (HEAD -> master, origin/master) merge feature/b branch into master branch|\ | * d680cf6 (feature/b) created b2.txt| * 77d32f6 created b1.txt* | 95bcc26 merge feature/a branch into master branch|\ \ | |/ |/| | * c3a9a88 created a3.txt| * 64191e8 created a2.txt| * e1a8e1c created a1.txt|/ * 5300667 add application2.txt* 888c144 add application1.txt
Notice that the master
and origin/master
branches are now on the same commit.
Developer A (Mickey) fetches from remote (Github)
I personally always try to fetch from remote to ensure my local master
branch is always up to date with the remote master
branch, even if I do not plan on modifying the master
branch.
If Developer A (Mickey) follows this philosophy, then he will be fetching and updating his local master
branch to be up-to-date with the changes to master
that Developer B (Minnie) just pushed up.
developerA(mickey)$ git fetch originremote: Counting objects: 6, done.remote: Compressing objects: 100% (3/3), done.remote: Total 6 (delta 3), reused 6 (delta 3), pack-reused 0Unpacking objects: 100% (6/6), done.From https://github.com/zhao-lin-li/disney 95bcc26..d444aae master -> origin/master
developerA(mickey)$ git checkout master # be sure to be on the correct branchAlready on 'master'Your branch is behind 'origin/master' by 3 commits, and can be fast-forwarded. (use "git pull" to update your local branch)
developerA(mickey)$ git rebase origin/masterFirst, rewinding head to replay your work on top of it...Fast-forwarded master to origin/master.
developerA(mickey)$ git log --graph --all --oneline --decorate* d444aae (HEAD -> master, origin/master) merge feature/b branch into master branch|\ | * d680cf6 created b2.txt| * 77d32f6 created b1.txt* | 95bcc26 merge feature/a branch into master branch|\ \ | |/ |/| | * c3a9a88 (feature/a) created a3.txt| * 64191e8 created a2.txt| * e1a8e1c created a1.txt|/ * 5300667 add application2.txt* 888c144 add application1.txt
After this last step, all of the developers and the remote are all working to the same commit on the master
branch.
Notes
This tutorial did not go through removing branches, but it is always good to remove any branches you no longer need using . In this scenario, with feature/a
and feature/b
merged into master
, I would just delete feature/a
and feature/b
because the code that they capture is already part of master
.
It is always good to spot check what you are doing (e.g. ensure you are on the correct branch, commit, etc.), but using the command:
$ git statusOn branch masterYour branch is up to date with 'origin/master'.nothing to commit, working tree clean