Useful Git Techniques

Xiao-Hu Tai
2016-09-10T12:36:59Z

git patterns.md

History

Show file at certain commit

git show <hash>:<file>

Show history of a file

git log -p <filename>

Find commits containing a given string in all Git history

git log -S<string>

Find a deleted file in your git history

git log --all -- **/thefile.*

Restore a delete file from your git history

git checkout <deletion-SHA>^ -- <path-to-file>

Staging

Add only untracked files

Two ways to do this (from this StackOverflow answer):

git add $(git ls-files -o --exclude-standard) - set an alias for this if you care to

git add -i

1. a

2. *

3. q

Diffing

Useful options:

--cached - diffs against files that are currently staged (ready to commit)

Useful tips:

You can pipe the result of your diffs to a visual diff tool like p4merge or just to an editor like Sublime Text. I frequently run git diff [options] | subl to manage various diffs when comparing history.

Compare two points in history

git diff <earlier commit hash>..<later commit hash>

See the diff of certain file types

git diff [<earlier hash>[..<later hash>]] -- '*.ext'

See the number of lines changed

git diff --stat

See a diff of all files, including untracked

This one's a little janky. Not useful if you already have stuff staged.

1. git add -A

2. git diff --cached

3. (optional) git diff reset HEAD if you want to unstage

See a diff of only untracked files

1. git add -N .

2. git diff --cached

3. (optional) git diff reset HEAD if you want to unstage

Compare completely unrelated files in the same branch and commit

git diff --no-index <file1> <file2>

Trashing

Throw away all current changes

This totally annihilates everything.

1. git reset --hard HEAD to delete all unstaged changes

2. git clean -fd to delete all untracked files

Messing with history

All of these patterns will rewrite your history. If you force push them to a branch or repo with other contributors, they will be very unhappy with you. Make sure you know what you're doing.

Squash your commits

You might do this to clean up your work before merging to master.

1. git rebase -i HEAD~<number of commits>

2. Change every commit below the top one to have f (fixup) instead of pick, which will combine them with the top one

pick 849c994 Do the thing
f b02404e Oops, a typo
f 107184a I know how to spell

3. If necessary, reword your resulting single commit with git commit --amend or replace the top commit's pick with r (reword) to amend it during the rebase step.

Remove some changes from a commit you just made

1. git reset HEAD~1 to go back one commit and have all files unstaged

2. Make edits

3. Stage your files:

I want to keep most of my changes: git add -A, git reset HEAD <individual files>

I want to keep only some of my changes: git add <individual files>

4. git commit your staged changes

5. git stash leftover changes if you want to keep them around, or git reset --hard HEAD to get rid of them

Split your work in a commit in the past into extra commits

You might do this in order to better compartmentalize your work, split one task into two, or make it easier to code review. This is extremely similar to the above step.

1. Rebase one past your target commit (pick one of these):

git rebase -i HEAD~<how many commits back target commit is, +1> (so if your commit is 2 back, you would want HEAD~3)

git rebase -i <hash of commit before target commit>

2. git reset HEAD~1 to remove your commit but keep the changes in working index

3. git add your files for your first commit

4. git commit your files for your first commit

5. Repeat steps 3 and 4 until satisfied

6. git rebase --continue to finish

Move work from a newer commit to an older one

We can move work from a newer commit to an older one with git rebase. Since it's not actually possible to see the changes of the newer commit when you're editing the older one, it's recommended that you output the diff of changes made in the new commit so you can reference what files were changed.

1. git rebase -i <hash of commit before older commit>

2. Take note of the hash of the commit you want to get changes from

3. Change the older commit you want to move work into from pick to e (edit)

4. Get the changes from the newer commit, multiple ways to do this:

git checkout <hash of newer commit you want changes from> -- <file>

Copy the changes from your diff manually

5. git commit --amend

6. git rebase --continue

Move work from an older commit to a newer one

1. git rebase -i <hash of commit before older commit>

2. Change both the older commit and new commit from pick to e (edit)

3. Copy the old commit's message (if you want to keep it the same)

4. git reset HEAD~1

5. Stage the changes you want to keep

6. git commit -m "<your (copied) message>"

7. git rebase --continue - you'll now be at the edit step of the newer commit

8. git add -A - add the changes that are leftover

9. git commit --amend

10. git rebase --continue

Remove a file from git history you accidentally merged (but didn't subsequently edit).

This is good for if you accidentally merge a credentials file. Sourced from StackOverflow: http://stackoverflow.com/questions/307828/completely-remove-file-from-all-git-repository-commit-history

# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>

# remove the incorrectly added file
git rm somefile.orig

# commit the amended merge
git commit --amend

# go back to the master branch
git checkout master

# replant the master branch onto the corrected merge
git rebase tmpfix

# delete the temporary branch
git branch -d tmpfix

Take credit for a commit during a squash

Note: this is probably an easier way to do this.

Maybe you have a ticket you did most of the work on and you need to squash, but someone else started the work with a commit.

1. git rebase -i <hash of commit before target commit>

2. Like squashing, change every commit you want to squash from pick to f. Change your target commit from pick to e to edit it:

e 9beea7f The commit you want to claim
pick 849c994 Some changes
pick b02404e More changes
pick 107184a The above two commits messages are terrible commit messages, fyi

3. git reset HEAD~1 - pop off the commit

4. git add -A - stage the changes

5. git commit -m "All your commits are belong to us" - commit it again

6. git rebase --continue - all the changes should be squashed, with you credit as the owner.

Distributed Development

Rebasing against a branch that has had its history changed

If someone has pushed force work to a branch feature-a, but you began work locally from a point in the past, you may still be at a point where your changes can successfully apply to that branch.

When you branched, the history looked like:

abc Commit 1
def Commit 2
ghi Commit 3 <- you started here

Your local work looks like:

abc Commit 1
def Commit 2
ghi Commit 3
jkl Commit 4 <- you are here

Now the remote looks like:

abc Commit 1
def Commit 2
mno Commit 3-oops <- updated

To catch your new commit jkl up to the origin:

1. git fetch origin

2. git rebase -i origin/<branch name>: - delete the no-longer relevant ghi Commit 3

# pick ghi Commit 3
pick jkl Commit 4

3. Resolve merge conflicts (if any arise).

4. git rebase --continue

5. Result:

abc Commit 1
def Commit 2
mno Commit 3-oops
jkl Commit 4

Miscellaneous

Weird stuff you can do and might want to do for some weird reason.

Get contents of stash without popping the entry

Maybe you want to stash a changeset, and pop it out to play with it, without losing the set of stashed changes you know are good. You can preserve what's in your stash by forcing a merge conflict.

1. git stash save your changes

2. Edit one of the files you changed in your stash in such a way that it will cause a merge conflict - you can just throw some nonsense into a line you know you changed.

3. git commit [--allow-empty-message -m ""] - commit it so that stash will allow you to pop (make it empty if you want since it's a trash commit)

4. git stash pop [stash@{#}]

5. Your stashed changes will conflict with your trash commit, keeping the original set of changes in your stash while putting a (conflicted) copy into your working index. Simply resolve the conflict you created and continue on your experiments.