Chapter 6. Git Tools
$ git rev-parse topic1
Ca82a6dff817ec66f44342007202690a93763949
$ git show HEAD@{5}
You can also use this syntax to seewhere a branch was some specific amount of time ago:
$ git show master@{yesterday}
master..experiment means "allcommits reachable by experiment that aren't reachable by master?:
$ git log master..experiemnt
D
C
$ git log experiment..master
F
E
You can also leave off one side of the syntax to have Gitassume HEAD. Git substitutes HEAD if one side is missing.
$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA
If you want to see all commits that are reachable from refAor refBbut not from refC, you can type one of these:
$ git log refA refB ^refC
$ git log refA refB --not refC
?
$ git log master...experiment
F
E
D
C
The result appears in the traditional commit date ordering.A common switch to use with the log command in this case is --left-right?, which shows you which side of the range each commit isin:
$ git log --left-right master...experiment
< F
< E
> D
> C
$ git add ?i
?????????? staged?????? unstaged path
? 1:??? unchanged????????? +0/?1 T0D0
? 2:??? unchanged????????? +1/?1 index.html
? 3:??? unchanged????????? +5/?1 lib/simplegit.rb
?
*** Commands ***
? 1: status???? 2: update??????? 3: revert???? 4: add untracked
? 5:? patch????6: diff????????? 7: quit?????? 8: help
What now>
It lists the changes you've staged on the left and unstagedchanges on the right. After this comes a Commandssection. Here you can do a number of things, including staging files, unstagingfiles, staging parts of files, adding untracked files, and seeing diffs of whathas been staged. Type “2” or “u” to stage a file. Type “5” or “p” to stage part ofthe file, then, for each section of the selected files, it will display hunksof the file diff and ask if you would like to stage them, one by one. To do thepartial-file staging—you can also use gitadd -p or git add --patch on the command line.
$ git commit -amend
You need to be careful with this technique because amendingchanges the SHA-1 of the commit. It's like a very small rebase—don't amend yourlast commit if you've already pushed it.
$ git rebase -i HEAD?3
Every commit included in the range HEAD?3..HEAD?will be rewritten, whether you change the message ornot. Running this command gives you a list of commits in your text editor thatlooks something like this:
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
…
The interactive rebase gives you a script that it's goingto run. It will start at the commit you specify on the command line (HEAD?3?) and replay the changes introduced in each of thesecommits from top to bottom. It lists the oldest at the top, rather than thenewest, because that's the first one it will replay. You need to edit thescript so that it stops at the commit you want to edit. To do so, change theword pickto the word edit?for each of the commits you wantthe script to stop after. When you save and exit the editor, Git replays thecommit and stop after the commit you want to edit(rewrite) and drops you on thecommand line. You can add or change any files and stage them, then you can run gitcommit –-amend to rewrite that commit. And then you can run gitrebase –continue to continue the rebase(replay the follow up commit andstop at the commit you want to edit).
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4aOd added cat-file
to this:
pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit
When you save and exit theeditor, Git rewinds your branch to the parent of these commits, applies 310154eand then f7f3f6d?, and then stops.
pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file
When you save and exit the editor, Git applies all threechanges and then puts you back into the editor to merge the three commitmessages. When you save that, you have a single commit that introduces thechanges of all three previous commits.
pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
Then, when the script drops you to the command line, youreset that commit, take the changes that have been reset, and create multiplecommits out of them:
$ git reset HEAD^
$ git add README
$ git commit -m 'updated README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'added blame'
$ git rebase -continue
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
The --tree-filter?option runs the specified command after each checkout of the project and thenrecommits the results. In this case, you remove a file called passwords.txt?from every snapshot, whether it exists or not. To run filter-branch?on all your branches, you can pass --all to the command.
$ git filter-branch --subdirectory-filter trunk HEAD
Now your new project root is what was in the trunk subdirectory each time. Git will alsoautomatically remove commits that didn't affect the subdirectory.
$ git filter-branch --commit-filter '
??????? if ["$GIT_AUTHOR_EMAIL" = "schaconglocalhost" ];
??????? then
????????????????GIT_AUTHOR_NAME="Scott Chacon";
???????????????GIT_AUTHOR_EMAIL="schacon@example.com";
??????????????? git commit-tree "$@"
??????? else
??????????????? git commit-tree"$@"
??????? fi' HEAD
Because commits contain the SHA-1 values of their parents,this command changes every commit SHA in your history, not just those that havethe matching e-mail address.
$ git blame -L 12,22 simplegit.rb
^4832fe2 (Scott Chacon?2008-03-15 10:31:28 ?0700 12) def show(tree = 'master')
^4832fe2 (Scott Chacon?2008-03-15 10:31:28 ?0700 13)? command("gitshow #{tree}")
^4832fe2 (Scott Chacon ?2008-03-15 10:31:28 ?0700 14) end
^4832fe2 (Scott Chacon?2008-03-15 10:31:28 ?0700 15)
9f6560e4 (Scott Chacon?2008-03-17 21:52:20 ?0700 16) def log(tree = 'master')
79eaf55d (Scott Chacon?2008-04-06 10:15:08 ?0700 17)? command("gitlog #{tree}")
9f6560e4 (Scott Chacon?2008-03-17 21:52:20 ?0700 18) end
9f6560e4 (Scott Chacon?2008-03-17 21:52:20 ?0700 19)
42cf286l (Magnus Chacon 2008-04-13 10:45:01 ?0700 20) defblame(path)
42cf286l (Magnus Chacon 2008-04-13 10:45:01 ?070021)? command("git blame #{path}")
42cf286l (Magnus Chacon 2008-04-13 10:45:01 ?0700 22) end
The first field is the partial SHA-1 of the commit thatlast modified that line. The next two fields are the author name and theauthored date of that commit. After that come the line number and the contentof the file. The “^” before the partial SHA-1 designate that commit is whenthis file was first added to this project, and those lines have been unchangedsince.
$ git bisect start
$ git bisect bad
$ git bisect good v1.0
Bisecting: 6 revisions left to test after this
[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] error handling on repo
Git figured out that about 12 commits came between thecommit you marked as the last good commit (v1.0) and the current bad version,and it checked out the middle one for you. At this point, you can run your testto see if the issue exists as of this commit. Suppose it turns out there is noissue here, and you tell Git that by typing git bisect good andcontinue your journey:
$ git bisect good
Bisecting: 3 revisions left to test after this
[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] secure this thing
Now you're on another commit, halfway between the one youjust tested and your bad commit. You run your test again and find that thiscommit is broken, so you tell Git that with git bisect bad:
$ git bisect bad
Bisecting: 1 revisions left to test after this
[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] drop exceptions table
Suppose this commit is fine, and now Git has all theinformation it needs to determine where the issue was introduced. It tells youthe SHA-1 of the first bad commit and shows some of the commit information andwhich files were modified in that commit so you can figure out what happenedthat may have introduced this bug.
When you're finished, you should run git bisect reset to resetyour HEAD to where you were before you started, or you'll end up in aweird state.
$ git bisect start HEAD v1.O
$ git bisect run test-error.sh
$ git submodule add git://github.com/chneukirchen/rack.git rack
If you run git status?right after you add the submodule, you see two things in the staging area: .gitmodules?file and rackfile. .gitmodules is a configuration file that stores the mappingbetween the project's URL and the local subdirectory you've pulled it into:
$ cat .gitmodules
[submodule "rack"]
????? path = rack
????? url =git://github.com/chneukirchen/rack.git
This file is version-controlled with your other files, likeyour .gitignore file. It's pushed and pulled with the rest of yourproject. This is how other people who clone this project know where to get thesubmodule projects from.
Although rack is a subdirectory in yourworking directory, Git sees it as a submodule and doesn't track its contentswhen you're not in that directory. Instead, Git records it as a particularcommit from that repository. When you make changes and commit in thatsubdirectory, the superproject notices that the HEAD?there has changed and records the exact commit you're currently working off of;that way, when others clone this project, they can re-create the environmentexactly.
$ git read-tree --prefix=rack/ -u rack_branch
It reads the root tree of rack_branch branch intoyour current index and working directory. You pull the rack_branch branch intothe racksubdirectory of your master branch main project. If the Rackproject updates, you can pull in upstream changes by switching to that branchand pulling. Then, you can merge those changes back into your masterbranch. You can use git merge -s subtree and it will work fine; but Git will alsomerge the histories together, which you probably don't want. To pull in thechanges and prepopulate the commit message, use the --squash and --no-commitoptions as well as the -s subtree strategy option:
$ git checkout master
$ git merge --squash -s subtree --no-commit rack_branch
All the changes from your Rack project are merged in andready to be committed locally. You can also do the opposite—make changes in theracksubdirectory of your master branch and then merge theminto your rack-branch?branch.
To get a diff between what you have in your racksubdirectory and the code in your rack_branch branch—to see if youneed to merge them—you must run git diff-tree with the branch youwant to compare to:
$ git diff-tree -p rack_branch