Tuesday, July 26, 2016

Git Tutorial 6—pull request



Continue our theme, dev1 is still the lucky hero, in this tutorial, dev2 is allowed to merge his code directly into master, he has to create pull request to ask dev1 to pull his code into master.  Let us see what kind of troubles he will run into.

Day 1 end

dev1 pushes his code into remote

dev1 is happy with his changes and push to the remote master:

git checkout master
git merge t1
git push -v --tags --set-upstream origin master:master
git branch --delete t1

dev2 creates a pull request

dev2 wants to ask dev1 to pull his code into master, to do so, he does these things:
1)      merge master into his branch, and as we know by now, he runs into a conflict and solves it
git checkout master
git pull origin master

git checkout t2
#runs into a conflict
git merge master

#solves the conflict
git add testf
git commit

2)      push his local branch to remote, in doing so, creates a remote branch with the same name
git push -v --tags --set-upstream origin t2

At this point, dev2’s workspace looks like this:


3)      creates a pull request
hub pull-request -m "p1"

Note, hub is an extension of github commands, and can be downloaded from https://github.com/github/hub, to install it in windows, just add the bin directory to the PATH variable.

Now you can see in Github, an open pull request, which, since dev2 has taken care to solve conflict first, can be auto merged:














What will happen if dev2 doesn’t merge first? In other words, dev2 doesn’t do the first step, instead he just does the second and third step:

git push -v --tags --set-upstream origin t2
hub pull-request -m "p1"
Github shows the result pull request has conflicts, and has to be solved manually:

dev1 merges the pull request

Since there is no conflict on the pull request, dev1 can merge the request on Github by clicking on the merge button.  

In our plot, dev1 doesn’t trust dev2 very much, perhaps because dev2 is an open source contributor who is unknown to dev1, so dev1 decides to merge dev2’s code locally to check out. He can merge the remote t2 into his local master and then push his local master to remote:

git checkout master
git pull origin master

#note, need to fetch origin first, otherwise git will complain origin/t2 is unknown
git fetch origin
git merge --no-ff origin/t2

git push -v --tags --set-upstream origin master:master

Or he can check out the remote t2 into a local branch, and merge the local branch to his local master, then push his local master to remote:

git checkout master
git pull origin master

git fetch origin
git branch t2 origin/t2
git merge --no-ff t2

git push -v --tags --set-upstream origin master:master

Either way, dev1 ends with:


Going to Github, you will see the pull request has been merged:

Day 2

dev1 pushes his code into remote

dev1 works on a local branch t3, and pushes his changes to remote:
git checkout master
git pull origin master
git checkout t3

echo 't3-c1' >> testf; git add testf; git commit -m 't3-c1'
echo 't3-c2' >> testf1; git add testf1; git commit -m 't3-c2'

git checkout master
git pull origin master
git merge t3
git push -v --tags --set-upstream origin master:master

git branch --delete t3

dev2 creates a pull request

git checkout master
git pull origin master

git checkout -b t4
echo 't4-c1' >>  testf; git add testf; git commit -m 't4-c1'

git push -v --tags --set-upstream origin t4

hub pull-request -m "p2"

At this point, the pull request contains no conflict, and can be auto merged. Unfortunately, dev1 feels too tired, and he returns home without noticing the pull request.

Day 3

 

dev1 pushes his code into remote

dev1 doesn’t pay attention to dev2’s pull request, he chooses to work on his stuff first:

git checkout master
git pull origin master
git checkout -b t5
echo 't5-c1'>> testf; git add testf; git commit -m 't5-c1'

Note, at this point, the pull request created in day 2 now contains conflicts!

dev2 creates a pull request

dev2 continues to work on his local branch t4, then tries to merge t4 with remote master, upon which he runs into a conflict which he solves manually:

git checkout t4
echo 't4-c2' >>  testf; git add testf; git commit -m 't4-c2'

git checkout master
git pull origin master

git checkout t4
git merge master

#solve the conflict
git add testf
git commit

Then he pushes his local t4 to remote:
git push -v --tags --set-upstream origin t4

Now, notice the pull request created in day 2 has no conflict again, because the conflict has been solved by dev2!

dev2 tries to create a new pull request:
hub pull-request -m "p3"

This is not necessary and will actually fail with the message:
Error creating pull request: Unprocessable Entity (HTTP 422)
A pull request already exists for t4.

 

dev1 merges the pull request

Finally, dev1 has some free time and is able to merge the pull request:
git checkout master
git pull origin master

git fetch origin
git merge --no-ff origin/t4

git push -v --tags --set-upstream origin master:master

dev1’s workspace now looks like this:


Lessons learned:

  • To make sure a team functions smoothly, pull requests should be merged quickly.
  • Solving conflicts is a chore, and it should be avoided as much as possible. In any day,  well-designed code (loosely-coupling code) is the king.


 

Thursday, July 14, 2016

Git Tutorial 5-- rebase and delete local branches



This tutorial is similar to the previous, except instead of merging to master, dev1 and dev2 use rebase.

Day 1 end

This is the end of day 1, dev1 is ready to call a day, he is happy with this changes, and ready to push his changes to the remote (we will assume dev1 is almost the lucky one, and simplify his actions, in reality, everyone should take similar actions as dev2) . dev1 first rebases t1 onto master, than merge t1 into master and push to the remote master:

git checkout t1
git rebase master

git checkout master
git merge t1
git push -v --tags --set-upstream origin master:master

 
dev1 then deletes branch t1.
git branch --delete t1



dev2 wants to do the same thing, he first syncs up his local master with the remote master:
git checkout master
git pull origin master
 
Bad luck, dev2 finds out his branch t2 has deviated from master. He needs to get his changes into master, so he rebases t2 onto master, and when he does this, he runs into the dreaded conflict:
git checkout t2
git rebase master



Git shows this error is caused by reapplying t2-c1.

Git tries to be smart, and performs an auto-merge, but it fails. Note, upon automerging failure, Git has changed your file in the working directory, here is the content of testf:

dev2 will need to manually resolve this conflict (and delete those markers <<<<<<< HEAD, =======, >>>>>>> t2), he can signal to Git that the conflict has been resolved, and continues rebasing again: 
git add testf
git rebase –continue


Git continues to reapply t2-c2, and again runs into a conflict:

dev2 needs to solve this conflict, and notifies Git to continue:
git add testf
git rebase --continue

Finally, dev2 is able to merge t2 into master and push to remote:
git checkout master
git merge t2
git push -v --tags --set-upstream origin master:master




dev2 then deletes branch t2.

Day 2

dev1 syncs his local branch with the remote master and creates a new branch t3:
git checkout master
git pull origin master
git checkout –b t3

And then he commits 2 changes to t3,
echo 't3-c1' >> testf; git add testf; git commit -m 't3-c1'
echo 't3-c2' >> testf1; git add testf1; git commit -m 't3-c2'

And merges t3 into local master and pushes his changes to the remote master:
git checkout master
git pull origin master
git merge t3
git push -v --tags --set-upstream origin master:master

And then delete t3:
git branch --delete t3

In our script, dev2 is always the unlucky one. He doesn’t realize his local master is behind, and he checks out t4 from the old master:
git checkout master
git checkout -b t4
echo 't4-c1' >>  testf; git add testf; git commit -m 't4-c1'



Now he realizes his local master is behind, so he syncs up his local master with the remote, and then anxious to make sure his changes are based on the latest code, he rebases t4 onto his local master and runs into a conflict:

git checkout master
git pull --rebase origin master
git checkout t4
git rebase master


Running git status shows you:



So again dev2 needs to carefully resolve the conflict (be sure to check out each marked lines). He then continues rebasing again:
git add testf
git rebase –continue


dev2 thinks his work is not finished and is not ready to push to remote master, so he keeps working on t4:
echo 't4-c2' >>  testf; git add testf; git commit -m 't4-c2'

Day 3

The sun always smiles on dev1, and he checks out t5, works on t5, and pushes to the remote master:
git checkout master
git pull origin master
git checkout -b t5
echo 't5-c1'>> testf; git add testf; git commit -m 't5-c1'

git checkout master
git pull origin master
git merge t5
git push -v --tags --set-upstream origin master:master

git branch --delete t5

Back to our unlucky dev2, he is ready to push his changes. First, he syncs his local master to remote, and then rebases t4 onto local master and runs into a conflict:
git checkout master
git pull --rebase origin master

git checkout t4
git rebase master



Poor dev2, he is losing his mind! He has solved the conflict t4-c1 yesterday, and today he has to solve it again. He curses but he still gets the job done and notify Git to continue:
git add testf
git rebase –continue

Just as dev2 thinks his ordeal is over, he is hit again with conflict on t4-c2:

dev2 resolves the conflict and continues:

git add testf
git rebase –continue


Well, you should really admire dev2, after all this torture, he is still calm. Because he has spent so much time on resolving the conflict, before he pushes his local master to remote, he syncs up his local master with the remote just to be sure there are no changes made that overlap his changes (good luck for him this time, no more changes on the remote master, otherwise our poor dev2 may lose his mind):
git checkout master
git pull --rebase origin master
git merge t4
git push -v --tags --set-upstream origin master:master

Resulting diagram looks like this which is exactly the same as in the previous tutorial:



Lessons learned:
  •  Rebase results in a beautiful line, but at a great cost on every day developers! 

  •  It is quite common for a developer to hold on his work until he is sure that his work is at least not going to affect others, just like dev2 in the script in day 2. dev2 works on his branch, keeps an eye on the code in the remote repository, making sure that his code is built on top of the latest. Using rebase, dev2 in day 2 has to resolve the conflict on the same commit twice!  This seems to suggest that after using rebase, you should push your changes, otherwise you will have to go through the same conflict again. 

  • Personally, I do not like to reapply my commits one at a time, that is probably not what I would view my commits. I make 2 commits into my local branch, if these 2 commits are not related, then it is not confusing to recommit them again. However, the 2nd commit might add some new functions and might also refactor the code in the 1st commit. In my mind, 2nd commit is entangled with the 1st commit. If I had to write the 1st, 2nd commit from the scratch, I probably would have written them differently. Using rebasing forces me to go through the history again, which is confusing.