Most useful Git commands

Those of us who work in a team often have to use some git-based repository for managing automation source code. In this post I would like to share with you git commands I use most often for my everyday tasks, and hopefully you will find them useful.

Updating local repository

  • git pull – pull down the latest data from the remote repository.
  • git pull --recurse-submodules – same as above, but updates submodules as well.
  • git pull --rebase origin master – update current local branch against the most recent master on remote. You may need to resolve merge conflicts. Note that local master won’t be updated.
  • git fetch origin master:master && git rebase master – same as above
  • git fetch origin master:master && git reset --hard origin/master – force update local branch to the remote one, discarding all local changes. If you find out after doing git pull that there are some merge conflicts, but you don’t actually care about local changes and just want the local branch to be the same as the remote one, you can do just git reset --hard origin/main without doing git fetch first, because you’ve already pulled down the latest data. Be careful with this command, because all you local changes on the current branch will be lost!

Note: git fetch is equivalent to git fetch origin master:master, but I’ve written a full notation in case branch other than master needs to be fetched. By the way, git fetch origin master is equivalent to git fetch origin master:, and therefore it stores fetched value of master branch in FETCH_HEAD, and not in local master (i.e. it doesn’t really fetches master).

Checking out branches

  • git checkout --recurse-submodules <branch> – checkout branch recursively (i.e. with all submodules).
  • git checkout -f <branch> – forced checkout, i.e. it throws away all local changes .It is similar to git reset --hard <commitID>, which throws away all local changes up to <commitID>, but the latter doesn’t checkout a new branch.

Fixing mistakes

Reverting a merge

git revert -m 1 <merge-commit-hash> – revert (undo) a pushed merge (i.e. a merged PR). Also this can be done via Github or Gitlab GUI by pressing button “Revert”, which appears after PR was merged. However, there is one important detail: “…declares that you will never want the tree changes brought in by the merge. As a result, later merges will only bring in tree changes introduced by commits that are not ancestors of the previously reverted merge. This may or may not be what you want.” (git-merge man page). So, if you want to undo a merge because there was a mistake in merged changes, fix them, and merge the branch again, it won’t work as expected. What you need to do in such case is:

  1. Checkout a new branch.
  2. Revert the previous “revert” using git revert -m 1 <revert-commit-hash> 
  3. New branch should now have the changes which were previously reverted.
  4. Make necessary fixes and merge this branch to master.
Fixing up a commit

One of really cool git features is fixing up commits. Let’s imagine you’ve implemented a new cool feature or test case, pushed the changes to the server, created a PR and proudly wait for praise from reviewers. However, shockingly, instead of being awed by your prowess as programmer, they write comments suggesting for you to change this and that. Happens quite often, right?

In such case, instead of creating new commits with messages explaining what you fixed and polluting the master’s history, you can squash original commit and amendment commits in a single commit after you’ve finished working on amendments.

This can be achieved by executing git rebase --interactive --autosquash master from your working branch. Upon executing, a file with list of branches commits will be opened in your default editor, and there you will be able to squash/fixup your commits (as well as delete, modify commit message, reorder etc.) by changing a operation keyword in front of a commit in the list. For example, in the picture below I’m going to fixup “first commit” using two “fixup!” commits, and “third commit” using one “fixup!” commit. Note the word “fixup!” in the commit messages of the amendment commits. If it’s there and --autosquash option is specified, then git will automatically set commit operation to “fixup” (same applies to “squash!”) and move the amendment commit to be right after the original commit.

Output of git rebase --interactive --autosquash master

Actually, if you create fixup commits using Android Studio GUI, it will insert keyword “fixup!” automatically if you chose “Fixup…” in commit’s context menu.

Context menu of commit in Android Studio

Here you can see how commit history looks before and after squashing.

Commit history before fixup
Commit history after fixup

So far I’ve been using words “fixup” and “squash” interchangeably, however there is a difference between squashing (“squash” operation) and fixing up (“fixup” operation). Squash operation will prompt you to combine the messages of the original and the squash commit, while the fixup operation will keep the original message and discard the message from the fixup commit.

There is one problem with git rebase --interactive --autosquash master, though. As you might have guessed, besides squashing, this command will also do the rebase. I often want to do the rebasing only after I finished squashing, because otherwise I might end up wasting a lot of time on merging commits which later were amended in fixup commits. This merge hell can be avoided by rebasing not on master (or other parent branch), but on the current branch root or even specific commit in the current branch. For example, git rebase -i --autosquash <M2>, where <M2> is hash of commit M2, which is root of the feature branch on the scheme below, will squash/fixup commits in current branch without doing actual rebasing.

(Feature)          A --> B --> C --> D --> E --> F --> G
                  /
(Master)  M1 --> M2 --> M3

Also, instead of specifying commit’s hash, you can specify “the last N commits” like this: git rebase -i --autosquash feature~5. This will rebase last five commits on the one before them (actually, ~5 means “fifth parent” of the latest commit, and it would be commit B on the scheme above).

If you use one of Jetbrains IDEs, including Android Studio, squashing can be done from GUI by selecting “Interactively Rebase From Here…” from the context menu of the commit you want to rebase on. Unfortunately, it won’t automatically reorder the commits or set the corresponding operation for fixup commits, so you will have to do that manually.

Context menu of commit in Android Studio

Miscellaneous

  • git remote prune origin [--dry-run] – delete references to remote branches that don’t exist anymore on the remote repository. If option --dry-run is specified, branches which can be deleted will be listed, but won’t be actually deleted. I like to execute this command from time to time in order cleanup branch list which appears in IDE.
  • git submodule update --remote --merge – update the submodule to the latest commit on its remote master.
  • git branch --unset-upstream – stop tracking remote branch. OK, this command might not be the most useful one. I think I used it only once or twice and I don’t remember why.

Please let me know in the comments if you know any other awesome git commands.

Leave a Reply

Your email address will not be published.