Elevate your Git-fu!
Tags: git, devops, beginners, tutorial
Published: 2021-07-11 09:08:36

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.
```bash
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.
```bash
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
```bash
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
```bash
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
```bash
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](https://github.com/aws/eks-distro-prow-jobs) repository are as follows:
```bash
$ 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 add`ed, 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
```bash
git commit --amend --no-edit
```
or
```bash
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 **c**ommit **w**ithout **a**mend.
```bash
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
```bash
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.
```bash
git rebase -i HEAD~n # n is the depth from the previous step
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;This will open an editor with the commits in the range specified, along with &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prompts on how to edit the history. Besides re-ordering, you can also edit, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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.
```bash
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
```bash
git help branch -w
```
or
```bash
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.
```bash
git diff --pretty="format:" --name-only
git show --pretty="format:" --name-only
```
To see all files added to the staging area, we can use
```bash
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
```bash
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](https://www.linkedin.com/in/abhayk96/) and [Twitter](https://twitter.com/abhay_krishna96).