Elevate your Git-fu!

If I had a dollar for every time I looked up how to ... in Git, I would have enough to launch a new cryptocurrency of my own (dibs on the name Gitcoin™️ 😛). As developers, we use Git almost everyday, and most of us find ourselves experiencing déjà vu with some common scenarios when working with Git. This blog discusses how to easily navigate your way through such scenarios along with answers to some frequent Git how-to's.

Turning off git command pagination

Git commands like git diff and git log can print large amounts of text to standard output and are hence paginated by default (using less command under the hood), requiring user input for navigation and termination. To disable pagination and have Git display the entire output, we can just pass the --no-pager flag to Git.

git --no-pager log

If you want to disable it on all shells, you can add the following line to your .bashrc (Linux) or .zshrc (macOS), although I do not recommend doing this.

export GIT_PAGER=""

This technique works best for scripting and automation, where we want to avoid user interaction and maintain control flow without prompts.

Removing files mistakenly added to commit

Many times, we may inadvertently commit the wrong file(s), and checking the git status doesn't help since there are no hint commands displayed as would be before committing. In this situation, we can use

git restore --source=HEAD~ --staged -- <file>

to bring a file back into the staging area.

If you are unsure of the filenames and would prefer to move all files back into the staging area, then

git reset --soft HEAD~

will be your best friend.

Checking the current commit hash

I personally require this all the time to navigate back and forth between commits. Every commit has a SHA-1 hash composed of few of the commit's properties such as the date, author/committer, commit message, etc. You can get the current commit SHA using

git rev-parse HEAD

Git committer statistics

The git shortlog command can be used to aggregate commits by author and title. This is especially useful for release announcements. It even provides a summary of commits by count.

For example, commit count summaries in the aws/eks-distro-prow-jobs repository are as follows:

$ git shortlog -sn
    62  EKS Distro Bot
    31  Abhay Krishna
    29  EKS Distro PR Bot
    27  Abhay Krishna Arunachalam
    23  [REDACTED]
    15  [REDACTED]
              .
              .
              .

Retaining commit message when amending

Sometimes, we may need to make minor changes to files that have already been committed. Once the files have been updated and git added, we need to amend the commit to include the new changes, but we probably want to retain the same commit message.

We can do that with the help of

git commit --amend --no-edit

or

git commit --amend -C HEAD

Note: I find myself using this a lot so I have configured the following Git alias, which is short for commit without amend.

git config alias.cwa 'commit --amend --no-edit'

Reordering unmerged commits

In a software company, more often than not, you may be working on multiple features or modules at the same time. In some cases, you may want a more recent feature change to get reviewed first and merged before other changes, for several reasons like coherence, priority, etc. In such cases, we can re-order commits as follows.

  • Get the list of all commits with their hashes
git log --oneline
  • Identify the depth of the commit range you want to reorder with respect to the HEAD commit.
  • Perform interactive rebase on the branch.
git rebase -i HEAD~n # n is the depth from the previous step

     This will open an editor with the commits in the range specified, along with      prompts on how to edit the history. Besides re-ordering, you can also edit,      pick, drop, reword and squash commits as desired.

  • Once you have saved and exited the editor, you can repeat step 1 to view the re-ordered commit history.

Note: After changing history and before pushing to a remote branch, it's important to rebase on top of the remote head to validate that the re-ordering does not cause conflicts.

git fetch upstream
git rebase upstream/main

Recovering lost changes

If you are in a situation where you wrote hundreds of lines of code and then end up losing them due to some conspiracy of the universe, then this one command can save the day. That command is git reflog (read ref-log and not re-flog, though I get why one might think they can expect Git to co-operate by repeatedly flogging it for all the torture 😤).

git reflog gives you an entire history of all the changes and actions you made across all branches in the local repository. The entries in the log are called reference logs, and they record when the tips of branches and other references were updated in the local Git working tree. Each entry is marked with an index number which can be used to move forward and backward through history.

reflog has several use-cases such as retrieving lost/deleted commits, reverting breaking changes, identifying divergent paths, etc.

Viewing Git help on the browser

The manual pages for Git command help open in the terminal by default. Perusing man-pages can be a cumbersome task and they are also not user-friendly when searching for information (unless your Vim-fu is on point). If you would prefer to look up a command's manual page on the browser, all you need to do is use the -w or --web flag.

For example, the man-page for git branch can be opened on the default browser (configurable) using

git help branch -w

or

git branch --help -w

Getting affected filenames

Commands like git diff and git show in their raw form are great for displaying all the changes that are yet to be and have been committed, respectively. But in some cases, we may only require the names of files affected by a Git operation (for example, for scripting or filtering). We can directly obtain just the names by passing the following flags to the command.

git diff --pretty="format:" --name-only
git show --pretty="format:" --name-only

To see all files added to the staging area, we can use

git diff --staged --pretty="format:" --name-only

Filtering based on file extensions is also supported. For example, to get the list of all Python files in the latest commit, we can use

git show --pretty="format:" --name-only -- "*.py"

That brings us to the end of this blog. Thank you all for giving it a read! As closing notes, I wish to point out that Git is flexible in that it gives you several different techniques to fix a single problem, and the above methods are just from my experience and not advocated as canon. Feel free to leave your comments and corrections, and do reach out to me on LinkedIn and Twitter.

18