1. 程式人生 > >How to collaborate on code in git

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

branches. Ideally, each 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

branch. The 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:

Example of how feature branches start and end back into 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:

  1. Developer A (Mickey), Developer B (Minnie), and remote (GitHub) will start off at the same commit on master branch
  2. Developer A (Mickey) and Developer B (Minnie) will branch off of master to create their own feature branch
  3. Developer A (Mickey) and Developer B (Minnie) finishes their features
  4. Developer A (Mickey) fetches from remote (GitHub)
  5. Developer A (Mickey) merges his feature branch in to master branch
  6. Developer A (Mickey) pushes his updated master branch to remote (GitHub)
  7. Developer B (Minnie) fetches from remote (GitHub)
  8. Developer B (Minnie) merges her feature branch in to master branch
  9. Developer B (Minnie) pushes her updated master branch to remote (GitHub)
  10. Developer A (Mickey) fetches from remote (Github)
The entire feature branch workflow

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.

All master branches are up to date

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
Feature branches are created only on respective developer’s machine

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
Creating commits/snapshots on individual branches

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
No new changes were fetched from 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.

merge feature branch back into 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.

push latest master to origin

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.

fetch and update from origin

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.

merge feature branch 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.

push latest master to origin

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

References