How to rename remote branch in Git

Git is known for its confusing and difficult commands. If you want to rename a remote branch, say a branch that is on Github, from the comfort of your shell then you are in luck for one such experience! 🙂

This is the invocation to rename a branch old_branch on a remote named foo_remote to a new branch name new_branch:

$ git push foo_remote foo_remote/old_branch:refs/heads/new_branch :old_branch

If you find yourself renaming remote branches a lot, then it is better to add this as a Git command. To do that, save this piece of shell script code as a file named git-rename-remote-branch:

The file can be named anything as long as it begins with a git- prefix. Make it executable and place it in any directory that is in your PATH.

Now, you can rename a remote branch with a sane command:

$ git rename-remote-branch foo_remote old_branch new_branch

Tried with: Git 2.7.4 and Ubuntu 14.04

Advertisements

How to use tags in Git

A tag in Git is a user-friendly label attached to a particular commit. It is commonly used to mark a particular commit, for example to mark as a release version. Using tags in Git is quite easy, once you notice how they are pushed to a remote repository.

  • To list tags in your repository:
$ git tag
  • To mark the current commit with a tag named FOOBAR_TAG:
$ git tag FOOBAR_TAG
  • If you know the hash of a particular commit, say abcd1234, and want to attach a tag to it:
$ git tag FOOBAR_TAG abcd1234
  • If you want to label the head commit of a branch with a tag:
$ git tag FOOBAR_TAG XYZ_BRANCH
  • There are times when you want to do a reverse lookup, to lookup the commit associated with a tag. There are a couple of ways to do this:
$ git log -n 1 FOOBAR_TAG
$ git rev-list -n 1 FOOBAR_TAG
  • When you push your new commits to a remote, the tags you created locally are not pushed. This command pushes only the tags (not any new commits) to a remote:
$ git push FOOBAR_REMOTE --tags
  • There are times when you want to move a tag to a different commit. This operation essentially removes the old tag and writes a tag with the same name at the new commit. This can be done if you create the new tag (with the same name) with force:
$ git tag -f FOOBAR_TAG
  • If you try to push a moved tag (like the above example) to a remote, Git will complain that the remote already has a tag of the same name pointing at a different commit. Again, you use force to coerce Git to do this:
$ git push -f FOOBAR_REMOTE --tags

Tried with: Git 2.7.4 and Ubuntu 16.04

How to cherry pick in Git

Cherry picking in Git allows you to pick a specific commit and replay the changes done in that single commit on your current commit. This is different from a merge, where Git tries to replay the changes of all the commits from the other branch. Even if you specified a commit for a merge, Git will try to fuse the branch of which that commit is head to your current branch. After a merge, the two branches are fused in the DAG.

In contrast, a cherry pick replays changes on your current workspace. After you cherry pick a commit and replay it on your current commit, you will see that there is no branch being merged to your commit in the DAG. There will just be one or more new commits.

  • To cherry pick a specific commit and replay it on your current commit, use the hash of that commit:
$ git cherry-pick 123456
  • You can use a branch name too, in which case only topmost commit of the branch is replayed:
$ git cherry-pick some_branch
  • You can specify multiple commits to cherry pick, in that order:
$ git cherry-pick 123456 some_branch some_tag

The above command will result in 3 new commits on your current branch.

Cherry-picking changes all the files that were changed in a commit. If you need to pick only the changes from a specific file in a specific commit, we need to be a bit more clever.

First, we get the diff of the changes on a specific file in a specific commit:

$ git show 123456 -- foo/bar.cpp

We can then pipe this diff to be applied as a patch on our current workspace:

$ git show 123456 -- foo/bar.cpp | git apply -

Tried with: Git 2.7.4 and Ubuntu 16.04

How to install and use Git LFS

Large File Storage (LFS) is an extension to Git to handle large files. This is usually used to store binaries or graphics or video game assets.

  • Install: The easiest way is to install a package from the LFS repository:
$ curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
$ sudo apt install git-lfs
  • Use: To fetch the files from a remote repository to local repository and checkout a large file:
$ git lfs fetch
$ git lfs checkout foobar.bin

This replaces the foobar.bin which was a placeholder with the actual large file fetched from the remote repository (which should also have LFS support, like Github for example).

Tried with: Git 2.9 and Ubuntu 14.04

Visual Studio Code extensions that I use

  • CPP Tools: The official extension for working with C++ code. Automatically indexes all code in the currently open directory, offers auto-completion and syntax highlighting.

  • Python by Don Jayamanne: There are many Python extensions, but this seems to be the most popular one. Syntax highlighting, indexing and code completion.

  • Vim: There are many Vim extensions, but this seems to be the most popular one. It has entire universes to traverse before it can be as good as Vrapper, the Vim extension for Eclipse. This VSCode extension offers very basic navigation and editing commands.

  • Git Blame: This extension does one little thing that I need everyday to work with code from other people: know who modified a line of code. This extension shows that for the current line in the status bar.

  • Matlab: I need to regularly browse through some MATLAB files. This extension offers syntax highlighting of Matlab files.

Tried with: Visual Studio Code 1.4 and Ubuntu 16.04

Merge with squash in Git

Merge is a common operation in Git to merge the changes in another branch to the current branch.

Here is an example, where we checkout the master branch and merge a feature_branch to it:

$ git checkout master
$ git merge feature_branch
$ git commit
Before merge of feature_branch to master
Before merge of feature_branch to master
After merge of feature_branch to master
After merge of feature_branch to master

In the pictorial depictions above we can see that the two branches are actually merged together in the directed acyclic graph (DAG) with an edge. After this, the git log for master will show all the commits that are in feature_branch too in addition to the commits in master.

There might be cases where you do not want to actually merge the two branches. Maybe you just want a single commit on master that has all the changes that would have been merged from feature_branch. Note that this is different from git rebase since that replays all the multiple commits of feature_branch on master.

The answer to this is git merge --squash. This command effectively changes the files such that they would be after a git merge. However, there would be no link between master and feature_branch after this commit is committed.

To merge with squash in the above scenario:

$ git checkout master
$ git merge --squash feature_branch
$ git commit

When you commit you will see that Git inserts a default commit message that says Squashed commit of the following and lists all the commits from feature_branch that are squashed into this commit. You can delete this commit message and create your own of course.

After merge --squash of feature_branch to master
After merge –squash of feature_branch to master

The pictorial depiction of this operation above shows that there is no link between the two branches after this merge. When you do git log no one can see the multiple commits of feature_branch. You can even safely delete feature_branch if you want to after this operation.

Tried with: Git 2.9.0 and Ubuntu 16.04

How to install Git

Installing Git is easy in Ubuntu:

$ sudo apt install git

However, Ubuntu ships with old versions of Git and development on Git is progressing rapidly. New stable releases of Git drop once every 6 months or less.

The latest Git stable releases can be installed using their PPA:

$ sudo add-apt-repository ppa:git-core/ppa
$ sudo apt update
$ sudo apt install git

Tried with: Ubuntu 15.10 and Git 2.9

How to stash in Git

Stashing in Git is a very useful technique that eases daily work with code. Many a time, you are in the middle of writing or changing some code when you might be required to quickly checkout a different branch. For example, another team member wants you to quickly run a test in a different branch or fix a bug appearing in a different branch. It is pretty easy to clone a new repository to do these things if the repository is small and building files is quick. If your repository or build setup is large, complicated or time consuming, then stashing is a great addition to your workflow.

Stashing just saves all your staged or unstaged files that are changed and saves them up in your local repository. The stashed commits are actually stored locally in a reflog for a branch named stash. So, the git stash commands are just simple wrappers over standard git commands.

The command to stash up all your changed files and have a clean slate:

$ git stash

By default this stash commit is saved with a generic commit message that includes the branch, the commit hash and the log of the commit upon which the stash was created.

It is very easy to forget what your changes your stash contained. So, I like to stash with my own commit message:

$ git stash save "WIP on enabling write to disk"

You can stash many times in a local repository. The stashes are saved in a LIFO stack. You can list and view all the stashes in the stack:

$ git stash list

You can see that this is just showing the items from the stash reflog:

$ git reflog stash

To unstash and apply the top-most item in the stack:

$ git stash apply

Since the top-most stash is applied, the above command is the same as:

$ git stash apply stash@{0}

To unstash a different item from the stack:

$ git stash apply stash@{99}

To checkout a particular file from a particular stash:

$ git checkout stash@{13} -- foobar/joe.cpp

That is all there is to it! This is one of the easier and straightforward commands in Git 🙂

Tried with: Git 2.8.3

How to git fetch everything

git fetch is a common command used to fetch and update the local repository with commits and branches from one or more remote repositories.

  • The simplest version of the command is:
$ git fetch

This fetches only from the origin remote. Also, note that it does not fetch for any of the submodules inside the current repository.

  • To fetch from a particular remote:
$ git fetch some_remote

This can be useful when there are many remotes and you want to fetch from just one to save time.

  • To fetch from all the remotes you have associated with the current repository:
$ git fetch --all

Note again, that this does nothing for the submodules.

  • To fetch from the origin remote of the main repository and the origin remote of all submodule repositories:
$ git fetch --recurse-submodules
  • What if we want to fetch from all remotes for the main repository and also from all remotes for all the submodules? We might think that this does that:
$ git fetch --all --recurse-submodules

And here you land into a Git trap! Strangely, the above command only fetches from all remotes for the main repository. For the submodules, it only fetches from their origin remote!

So, what if I do want to fetch from all remotes for all submodules? That can be achieved by using the very useful submodule foreach which loops over all submodules (but not the main repository!) and executes the git command you specify. Knowing this, we can do this:

$ git submodule foreach --recursive git fetch --all

We are almost there! Can we create one mega command to fetch from all remotes for both the main repository and all the submodules? We can do it at the shell by combining two commands:

$ git fetch --all && git submodule foreach --recursive git fetch --all
  • If you deal with submodules all the time, you can turn the above command into a Git alias by adding this to your .gitconfig:
[alias]
fetch-all-recur = !git fetch --all && git submodule foreach --recursive git fetch --all

With this alias added, you can sit back and type:

$ git fetch-all-recur

Enjoy! 🙂

Tried with: Git 2.8.2 and Ubuntu 14.04

How to get autocomplete for command options in Fish

Fish has support to autocomplete the options of any command at the shell. (This is sometimes called tab completions.) For example, when I type ls - and TAB, Fish shows me the options available for the ls command. This works even when you’ve typed out part of an option. Fish generates this information by parsing all the man pages installed on your system.

If you find that Fish autocomplete is not working for the options of a command, that can be fixed easily by asking Fish to reindex from man pages. The Fish function that you need to run for this is: fish_update_completions.

I recently installed Git. Since Git is infamous for its myriad command-line options I missed having autocomplete at the shell for Git command options. After running fish_update_completions, I could get autocomplete for options of all Git commands! The icing on top was to discover that Fish could autocomplete options even for my Git aliases!

Tried with: Fish 2.2.0