Git good — advance the knowledge

Author
Damian
Terlecki
38 minutes read

There are at least three very important things, almost every software developer should have a good knowledge of. That is a good familiarity of the code editor, an even better grasp of the version control system and some scripting proficiency. Nowadays our IDEs are becoming the swiss knives for everything, sometimes to the point of blatant Wenger 16999. I, however, prefer sticking to basic tools, which often prove to be more powerful at specific things than a "jack of all trades, master of none" solution.

So, by knowing well your version control system, you will be able to do things that are not possible from the IDE interface and you will retain this knowledge even after switching the editor. As for scripting, it comes handy in many cases, mostly during maintenance (searching through log files), bug fixing (creating data fixing scripts) and development (parsing and creating input/output data files).

Today I will refer to the second point and I will show you the most useful Git commands that I use in my daily work, which greatly improve my productivity. For a more accurate (but without a context) description, you can refer to the explainshell site, which explains commands bit by bit. Git documentation can be found here.

CommandDescriptionWarning
git log --pretty=format:"%h | A: %aN/%ad | C: %cN/%cd | %s" --date=format:"%y-%m-%d %H:%M:%S" Shows formatted commit logs:
  • commit hash;
  • author with date;
  • committer with date;
  • commit message;
  • add --author="t3rmian" to see what the author been up to;
  • add --all to log from all branches;
  • precede with git fetch --all for up to date logs.
git log --all --graph --decorate --oneline Shows a nicely formatted commit history in oneline graph-text format.
git log [a]..[b] Shows commit logs between two tags/commits/branches:
  • useful for release notes;
  • can be formatted;
  • a is exclusive;
  • b is inclusive;
  • appending ~ refers to a previous commit.
git reflog Displays reference logs of the local repository and branch tip updates. Useful for:
  • checking what commands were run recently;
  • reverting hard resets and other fubar situations.
git diff [a]..[b] --name-only Displays names o files which have been changed between a and b. Useful for:
  • comparing changes to the previous release;
  • assuring reverts have been applied for whole features.
git pull --rebase origin master Pulls master from origin and rebases changes on top of that.
  • Rewrites history.
  • Can cause merge conflicts.
git merge -X [theirs|ours] feature Merges feature branch into the current branch using default recursive strategy and in case of conflicts, takes ours/theirs (feature) changes instead of ours/theirs.Conflicts are helpful to properly maintain and merge crossing features.
git checkout feature && git merge -s ours master && git checkout master && git merge feature Overwrites master with feature branch without rewriting history:
  • useful for superseding old protected branches;
  • when git push --force is not possible.
git commit --date=relative.1.day.ago Commit with author date pushed back by one day.
git commit --date "$(date -d 24hours)" Commit with author date in future:
  • utilizes Linux date command.
git commit --amend Stage changes (git add) before running this command to reuse the most recent commit (e.g. if you forgot to add/remove something):
  • add --no-edit to reuse the commit message.
Rewrites history.
git rebase --committer-date-is-author-date HEAD~1 Runs non-interactive rebase to change committer date to the author date for the HEAD commit.Rewrites history.
git reset --hard [commit] Resets index and working tree to a specific point. Use:
  • HEAD@{2} for resetting using reflog;
  • HEAD~1 for resetting using log.
Can rewrite history.
git reset --soft HEAD~ Discards last commit and retains staged files.Rewrites history.
git revert [commit] Reverts specific commit introducing an additional one. Use:
  • oldestCommit..newestCommit for reverting multiple commits (append ^ to include the oldest commit);
  • -m 1 to select first the parent as a mainline (usually master) for merge commits.
Can cause conflicts.
git checkout different-branch -- path/to/a/file Copies file from different-branch from different-branch into the current working tree.
git stash [|apply|pop|list|drop] A convenient way to temporarily store unfinished (staged) work when switching branches.
git checkout . Undoes unstaged changes.
git clean -df Removes untracked files and directories ( -x also removes ignored files). Can remove files not intended for removal. Consider using:
  • git clean -dfn for a dry run;
  • git clean -dif for an interactive run.
git rebase -i [commit] Run an interactive rebase up to defined commit or branch. What's more:
  • append ~ to also include the commit;
  • rebase and squash is an alternative to a git merge --squash feature.
Rewrites history.
git cherry-pick -x [commit] Cherry-pick a commit with a referenced commit message. Use:
  • oldestHash..newestHash for cherry-picking multiple commits (append ^ to the include oldest commit);
  • git rebase --onto target from to is an alternative for cherry-picking multiple commits;
  • git rebase -i branch is an alternative for interactively cherry-picking multiple commits;
  • -m 1 to select the first parent as a mainline (usually master) for merge commits.
Can cause conflicts.
git filter-branch --env-filter 'export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"' Rewrites whole branch resetting committer date of each commit to author date.Rewrites history.
git bisect start
git bisect bad [|bad_commit]
git bisect good [|good_commit]
git bisect reset
A bisect method which helps determine the source of regression bug using divide and conquer principle:
  1. Find good commit and bad commit, start the bisect and mark the commits.
  2. Git will checkout in the middle. Test the revision and mark it as good or bad with git bisect good/git bisect bad.
  3. After finding the bug source commit hit the reset command and revert/fix the code.
gitk --follow [filename] Displays commits for a given path/file visualizing commits graph and following renamed paths. Useful when:
  • tracking changes;
  • analyzing bug reports;
  • finding undocumented features by a message id from an issue tracker.

The difference between ^ and ~ when combined with commits is slight but perceptible:

  • ref~ is a reference to commit's first parent;
  • ref~2 is a reference to commit's first parent's first parent;
  • ref^ is a reference to commit's first parent;
  • ref^2 is a reference to commit's second parent.

In case you get an error when using HEAD^1, your shell might be interpreting ^ as line continuation symbol, use "HEAD^" instead.

Here is an illustration, by Jon Loeliger. Both commit nodes B and C are parents of commit node A. Parent commits are ordered left-to-right.

G   H   I   J
 \ /     \ /
  D   E   F
   \  |  / \
    \ | /   |
     \|/    |
      B     C
       \   /
        \ /
         A
A =      = A^0
B = A^   = A^1     = A~1
C = A^2  = A^2
D = A^^  = A^1^1   = A~2
E = B^2  = A^^2
F = B^3  = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2  = B^^2    = A^^^2  = A~2^2
I = F^   = B^3^    = A^^3^
J = F^2  = B^3^2   = A^^3^2

There are also other ways to reference the revisions like master@{yesterday}, @{push}, /"fix nasty bug". You can read more about them in the git documentation about git rev-parse. One last thing which I would like to point out is that there is a difference between double-dot and triple-dot commit ranges. Check out the graphs on Chuck Lu's blog for a better insight on that.