Wednesday, March 6, 2013

Git Course Part II: Some useful commands and tools.

Since we need to illustrate things that require a bunch of commits here, I'll not use the repo I created yesterday for examples, but instead I'll use one of my personal repos: intern-objc https://github.com/rafaeladson/intern-objc
git gui:
If you don't like using the  command line so much, and want something more visual, there are a couple of tools that may or may not help you.
You can check them out here: http://git-scm.com/downloads/guis
Referencing Commits
As far as I know, there are three ways of referencing a commit.
The first way is to get the SHA-1 code that identifies the commit and use that. So for example if you run git log you will probably see this (the contents may change if I ever update this repo):

commit 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd
Author: rafaeladson 
Date: Fri Nov 2 19:51:10 2012 -0200
 
    Updating build to ios6.0
    
    Previous ios5.1
 
commit 30e0256470b56f3c746510e60a416f12c0eede59
Author: rafael.adson 
Date: Wed Mar 14 23:17:36 2012 -0300
 
    Support for deleting items from BaseTableViewcontrolller
    
    Deleting items will be enabled by default on BaseTableViewController.
    
    If you don't want that behavior, you should override the method on your own implementation.
 
commit 3911e80ca914831afa8c02f218a9b2eabac08e52
Author: rafael.adson 
Date: Wed Mar 14 22:27:13 2012 -0300
 
    Creating a script to deploy/update intern in projects.
    
    Also updated the README file.
 
commit 6c81f997a428b9abdfd6522856c0e8d298da0c28
Author: rafael.adson 
Date: Wed Mar 14 21:24:31 2012 -0300
 
    Shell script to package intern.
    
    Created a shell script to automatically package intern.
 
commit 6367d0a913bdb80c444c33a9a98e3afb77089bd4
Author: rafael.adson 
Date: Wed Mar 14 08:47:38 2012 -0300
 
    Documentation for DataManagerDelegate
    
    Now it is commented.
 
commit 87c04c079e3942ac97abf0dced24feaccb215db1
Author: rafael.adson 
Date: Wed Mar 14 01:47:07 2012 -0300
 
    Changed implementation of DataManager to use a delegate instead of using Notification.
    
    Using notification was causing some initialization problems on iPhone applications.
    I found out that using delegate is both more stable and more elegant, so now I migrated the application to use delegates instead.
 
commit cf5adfa3b60853ebc42a2b361ac96e29435e2e90
Author: rafael.adson 
Date: Sat Mar 10 21:49:46 2012 -0300
 
    Taking DataManager out of InternIOSTest target
    
    If DataManager is present on InternIOSTest, I won't be able to import both InternIOS and InternIOSTest on the same project because b
    
    So far, I couldn't solve this problem.
 
commit 4beb746f41f62d80d1c29eadd4fcc97d46c885a5
Author: rafael.adson 
Date: Sat Mar 10 21:30:31 2012 -0300
 
    Added some utilities methods on DataManagerBaseTest
    
    - Added one method to get all instances of an object in the database if you give the entity name.
    - Added one method to delete all instances of a given entity.
    - Updated project configuration for iOS 5.1
Git log lists all commits in sequence. Each commit is shown like this:
commit 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd
Author: rafaeladson 
Date: Fri Nov 2 19:51:10 2012 -0200
 
    Updating build to ios6.0
    
    Previous ios5.1
The first line contains the SHA-1 code, the second the author, the third the date when the commit was made.
Then comes the message telling the description the author did to this commit.
So, If I want to reference this commit, for example, I would use the code 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd.
The second way to reference commit is by using HEAD. Head always points to the topmost commit of your branch (If you don't know  a branch is, I will explain in part III). So, in this case, HEAD is equal to 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd.
However, although HEAD by itself is not very useful, HEAD can be used to reference other commits. There are two ways of doing that. In other two explain that, I will tell you how to reference the commit 3911e80ca914831afa8c02f218a9b2eabac08e52. There are two ways to do that:
1. use HEAD~2. When you use this syntax, you're referencing two commits below HEAD. HEAD~1 or HEAD~ can be used to reference the first commit below HEAD.
2. use HEAD^^, which is equal to HEAD~2. HEAD^^^ == HEAD~3 and HEAD^ == HEAD~ and so on.
The third way to reference a commit is to use the name of the branch or tag that points to that commit. If you don't know what a branch or tag is, don't worry, I'll explain them in my next article.

git reset
Git reset is a extremely useful command that lets you go to any point in history, keeping or discarding your state.
For example, let's say I don't want to support iOS 6.0 anymore, because Apple Maps sucks. So, I want to go back to HEAD^.
In its default mode, git reset will keep your changes, but they will not be in the index anymore, and it will rollback to the desire commit. So, for example:
weasley:intern-objc rafael$ git status
# On branch master
nothing to commit (working directory clean)
(geekie)weasley:intern-objc rafael$ git log -n 1
commit 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd
Author: rafaeladson 
Date: Fri Nov 2 19:51:10 2012 -0200
 
    Updating build to ios6.0
    
    Previous ios5.1
weasley:intern-objc rafael$ git reset HEAD^
Unstaged changes after reset:
MMakefile
Mprepare_for_intern.sh
(geekie)weasley:intern-objc rafael$ git status
# On branch master
# Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
#
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
#modified: Makefile
#modified: prepare_for_intern.sh
#
no changes added to commit (use "git add" and/or "git commit -a")
weasley:intern-objc rafael$ git log -n 1
commit 30e0256470b56f3c746510e60a416f12c0eede59
Author: rafael.adson 
Date: Wed Mar 14 23:17:36 2012 -0300
 
    Support for deleting items from BaseTableViewcontrolller
    
    Deleting items will be enabled by default on BaseTableViewController.
    
    If you don't want that behavior, you should override the method on your own implementation.
 

Note that after git reset HEAD^ my HEAD is now 30e0256470b56f3c746510e60a416f12c0eede59, and the changes that were made by the previous HEAD are now untracked, but still in my working tree.
In soft mode, git reset will rollback to the desired commit, but will keep changes in the index. So, in this case, this is what happens.

 
weasley:intern-objc rafael$ git status
# On branch master
nothing to commit (working directory clean)
(geekie)weasley:intern-objc rafael$ git log -n 1
commit 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd
Author: rafaeladson 
Date: Fri Nov 2 19:51:10 2012 -0200
 
    Updating build to ios6.0
    
    Previous ios5.1
weasley:intern-objc rafael$ git reset --soft HEAD~
weasley:intern-objc rafael$ git status
# On branch master
# Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
#
# Changes to be committed:
# (use "git reset HEAD ..." to upstage)
#
#modified: Makefile
#modified: prepare_for_intern.sh
#
weasley:intern-objc rafael$ git log -n 1
commit 30e0256470b56f3c746510e60a416f12c0eede59
Author: rafael.adson 
Date: Wed Mar 14 23:17:36 2012 -0300
 
    Support for deleting items from BaseTableViewcontrolller
    
    Deleting items will be enabled by default on BaseTableViewController.
    
    If you don't want that behavior, you should override the method on your own implementation.

In this case, note that it did the same thing as before, but this time it kept the changes in the index.
In hard mode, git reset will not keep changes at all. So for our example:
weasley:intern-objc rafael$ git status
# On branch master
nothing to commit (working directory clean)
weasley:intern-objc rafael$ git log -n 1
commit 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd
Author: rafaeladson 
Date: Fri Nov 2 19:51:10 2012 -0200
 
    Updating build to ios6.0
    
    Previous ios5.1
weasley:intern-objc rafael$ git reset --hard HEAD~
HEAD is now at 30e0256 Support for deleting items from BaseTableViewcontrolller
weasley:intern-objc rafael$ git status
# On branch master
# Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
#
nothing to commit (working directory clean)
weasley:intern-objc rafael$ git log -n 1
commit 30e0256470b56f3c746510e60a416f12c0eede59
Author: rafael.adson 
Date: Wed Mar 14 23:17:36 2012 -0300
 
    Support for deleting items from BaseTableViewcontrolller
    
    Deleting items will be enabled by default on BaseTableViewController.
    
    If you don't want that behavior, you should override the method on your own implementation.
 
Note that this time, the changes are gone forever (not really, I still have them, just not in this branch).  Bye bye ie6.
git reset --hard is extremely powerful, but also dangerous. Be careful when using it.
You can also do git reset --hard to erase my current data. So for example, suppose I code a little and them I don't like what I did anymore. I can reset to HEAD by doing:
weasley:intern-objc rafael$ echo "Ooops, just erased my makefile" > Makefile
weasley:intern-objc rafael$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
#modified: Makefile
#
no changes added to commit (use "git add" and/or "git commit -a")
weasley:intern-objc rafael$ git reset --hard
HEAD is now at 4250aef Updating build to ios6.0
weasley:intern-objc rafael$ git status
# On branch master
nothing to commit (working directory clean)
 

The stash
The stash is a FIFO data-structure that will keep your alterations if you don't want them right now, but will need them later.
So suppose for example you did some alteration to the Makefile, and want to keep them, but you need to do another thing and will come back to solve this later.
This is how you would do it:

weasley:intern-objc rafael$ echo "Some alterations I want to keep" >> Makefile
weasley:intern-objc rafael$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
#modified: Makefile
#
no changes added to commit (use "git add" and/or "git commit -a")
weasley:intern-objc rafael$ git stash save "changes to makefile"
Saved working directory and index state On master: changes to makefile
HEAD is now at 4250aef Updating build to ios6.0
weasley:intern-objc rafael$ git status
# On branch master
nothing to commit (working directory clean)
weasley:intern-objc rafael$ git stash list
stash@{0}: On master: changes to makefile
Now the changes you made are in the position 0 of the stash. Suppose I want to make another change:
weasley:intern-objc rafael$ echo "Some more changes to Makefile" >> Makefile
weasley:intern-objc rafael$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
#modified: Makefile
#
no changes added to commit (use "git add" and/or "git commit -a")
weasley:intern-objc rafael$ git stash save "Some more changes"
Saved working directory and index state On master: Some more changes
HEAD is now at 4250aef Updating build to ios6.0
weasley:intern-objc rafael$ git stash list
stash@{0}: On master: Some more changes
stash@{1}: On master: changes to makefile

Now, the newer changes are in position 0 of the stash, the older ones are in position 1.
Then I go, do other things, eventually I come back and want my changes back.
This is how I apply the changes in position 0 to my working space:

weasley:intern-objc rafael$ git stash apply 
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
#modified: Makefile
#
no changes added to commit (use "git add" and/or "git commit -a")
weasley:intern-objc rafael$ git stash list
stash@{0}: On master: Some more changes
stash@{1}: On master: changes to makefile

apply puts the changes back in my workspace, but don't remove them from the stash. If I want to remove them, I could just use pop instead of apply.

Bisect
Bisect is a command that is useful when something went wrong somewhere, but I don't know which commit broke things.
In order to bisect to work, you need to identify a good and bad commit. It will then make a binary search to find out which commit is the culprit.
So, in our example, see that a file exists with the name README.pod. Since I don't like perl, I don't want my README in this format anymore.
So, I want to undo the commit that created the README.pod, so I can create another README in a format that does not come from perl.
The way to start a bisect is by using a git bisect start. I need to inform a good and a bad commit, like this:

git bisect start  

I know in this case that my HEAD is bad, since README.pod exists, and I know that my initial commit (c842b16fb354c3ff3da2ae45fdecf80c5e677f14) is good, since I remember creating README.pod afterwards.
So I run:
weasley:intern-objc rafael$ git bisect start HEAD c842b16fb354c3ff3da2ae45fdecf80c5e677f14
Bisecting: 17 revisions left to test after this (roughly 4 steps)
[b60de31dd42828132e8714c1de0b81dbb46b8b3d] Consertando alguns bugs no TableViewController

The bisect process has started, and git has taken me to the commit b60de31dd42828132e8714c1de0b81dbb46b8b3d, and it wants to know if it is good or bad.
If it's good, I tell git so by doing git bisect good. If it's bad, I do git bisect bad.
In this case, when I run ls I see that README.pod doesn't exist in this commit, so it's a good one. Following up the process:
weasley:intern-objc rafael$ ls
Intern InternIOSTest RunTests.sh intern-ios lib
InternIOS Makefile UnitTests intern-ios.xcodeproj
weasley:intern-objc rafael$ git bisect good
Bisecting: 8 revisions left to test after this (roughly 3 steps)
[492228c62385e97431ae2769fdc8bf656b04f0a1] Changing the appearance of the README file.
weasley:intern-objc rafael$ ls
Intern InternIOSTest Makefile RunTests.sh intern-ios lib
InternIOS LICENSE.txt README.pod UnitTests intern-ios.xcodeproj
weasley:intern-objc rafael$ git bisect bad
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[cd4abe52cb20c90b1969a87a551f3b0873162d95] Changing all strings to Intern table.
weasley:intern-objc rafael$ ls
Intern InternIOSTest RunTests.sh intern-ios lib
InternIOS Makefile UnitTests intern-ios.xcodeproj
weasley:intern-objc rafael$ git bisect good
Bisecting: 2 revisions left to test after this (roughly 1 step)
[2a44f78507a753a16758f9b3d0dfdbb8bbbebc5c] Fixing tests on hudson
weasley:intern-objc rafael$ ls
Intern InternIOSTest RunTests.sh intern-ios lib
InternIOS Makefile UnitTests intern-ios.xcodeproj
weasley:intern-objc rafael$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[2554a24272a6ab63b999f612303c3e86b4e5aa1c] README and LICENSE
weasley:intern-objc rafael$ ls
Intern InternIOSTest Makefile RunTests.sh intern-ios lib
InternIOS LICENSE.txt README.pod UnitTests intern-ios.xcodeproj
weasley:intern-objc rafael$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[90b6a085d048360f0e3906d8065c2a4b8e89dd27] Migrating to ios5.1
weasley:intern-objc rafael$ ls
Intern InternIOSTest RunTests.sh intern-ios lib
InternIOS Makefile UnitTests intern-ios.xcodeproj
weasley:intern-objc rafael$ git bisect good
2554a24272a6ab63b999f612303c3e86b4e5aa1c is the first bad commit
commit 2554a24272a6ab63b999f612303c3e86b4e5aa1c
Author: rafael.adson 
Date: Fri Mar 9 21:57:09 2012 -0300
 
    README and LICENSE
 
:000000 100644 0000000000000000000000000000000000000000 f25e17ec178047de8e901b4678bb7c7c6dff0202 ALICENSE.txt
:000000 100644 0000000000000000000000000000000000000000 5ad516520895e9443f77d62028d03cfdd48722db AREADME.pod

After the whole process, git is then able to tell me that the commit 2554a24272a6ab63b999f612303c3e86b4e5aa1c created the README file.
At any time, if I want to abort the  bisect, I can do:
git bisect reset
Now that I found my commit, how do I undo the changes made to it? Read the next section to find out!

Revert

So let's say I want to undo the alterations done by HEAD, since it puts support in iOS 6 that I don't like.
I don't want to modify my history using reset because there are other people with that commit, and I don't want to upset them. How do I do it?
git reverts help. To revert the changes in HEAD, do:
weasley:intern-objc rafael$ git revert HEAD
[master 9ef89b5] Revert "Updating build to ios6.0"
 1 file changed, 4 insertions(+), 4 deletions(-)
 mode change 100755 => 100644 prepare_for_intern.sh
weasley:intern-objc rafael$ git log -n 2
commit 9ef89b5c26acb907a6fe1218312a1b41847c6deb
Author: Rafael Adson 
Date: Wed Mar 6 00:54:01 2013 -0300
 
    Revert "Updating build to ios6.0"
    
    This reverts commit 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd.
 
commit 4250aef93bfb207dd6f8a4fbd16f7b667c0408fd
Author: rafaeladson 
Date: Fri Nov 2 19:51:10 2012 -0200
 
    Updating build to ios6.0
    
    Previous ios5.1
 

It reverts everything that was done in HEAD, creating another commit that undo the alterations.
You don't need to revert only the last commit. For example, now that I found out which commit created the dreadful README.pod, I can revert it too.
weasley:intern-objc rafael$ git revert 2554a24272a6ab63b999f612303c3e86b4e5aa1c
error: could not revert 2554a24... README and LICENSE
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add ' or 'git rm '
hint: and commit the result with 'git commit'
weasley:intern-objc rafael$ 

Ooops! What happened? In this case, there were conflicts because a commit after 2554a24272a6ab63b999f612303c3e86b4e5aa1c changed the README.pod file.
What you needed to do here is to resolve the conflicts, then commit your alterations.
Since I haven't show you how to resolve conflicts yet, let's just move on.

git help
If you don't know what a command does, you can do git help .
For example: git help bisect will open a manual page that will explain to you how to do a bisect.
If you want a quick help, you can do:
weasley:intern-objc rafael$ git bisect help
Usage: git bisect [help|start|bad|good|skip|next|reset|visualize|replay|log|run]
 
git bisect help
 print this long help message.
git bisect start [--no-checkout] [ [...]] [--] […]
 reset bisect state and start bisection.
git bisect bad []
 mark  a known-bad revision.
git bisect good […]
 mark ... known-good revisions.
git bisect skip [(|)…]
 mark ... untestable revisions.
git bisect next
 find next bisection to test and check it out.
git bisect reset []
 finish bisection search and go back to commit.
git bisect visualize
 show bisect status in gitk.
git bisect replay 
 replay bisection log.
git bisect log
 show bisect log.
git bisect run …
 use ... to automatically bisect.
 
Please use "git help bisect" to get the full man page.
 You can also type git help if you don't know which commands git has.
Next Steps
Next we will work with branches, and we will learn how to join our work with the work of others, and how to resolve conflicts.

1 comment:

  1. "Next we will [...] learn how to [...] resolve conflicts."

    Looking forward to that, as this blog is what I found when googling for resolving git conflicts that prevent a revert.

    As this blog ended four years ago, the hope is slim.

    ReplyDelete