This resource contains a collection of Git best practices and Git tips.

Git is not only a version control system, but also a collaboration and release management system. Using and understanding Git is an essential skill for any development team. At the same time, Git is comprehensive and complex; even simple tasks may need commands that can only be executed from the command line (terminal) without any GUI help.

Why Is Git Stash Dangerous?

Have you ever stashed your changes on your current feature branch, switched to the main branch to make a hotfix, and then went back to the feature branch? If so, you might have been in danger of unintentionally leaking some of the new feature files into the main branch.

Consider the following scenario: On your feature branch, you created a new file new-cool-feature.js. Suddenly, an urgent fix was requested, so you did git stash. You checked out the master branch, created a file controller.js, did git add --all && git commit -m ‘Fix’, and pushed the branch. Unexpectedly, new-cool-feature.js would end up being pushed to your master branch. This is because git stash works just like git commit -a, meaning it ignores new files that are not yet tracked by Git.

This has two advantages:

  1. No need to memorize additional stashing commands, like list all stashes, or stash untracked files. It is the usual Git commit routine with git add, git commit, and git log.
  2. In case you have WIP commits on several branches, there is no need to pick the needed one when you are back, as in the case of a stash. It is always the same git reset HEAD^.

However, I’d say there is one case when git stash is safe and convenient to use, which is when you want to see how your uncommitted code changes the behavior of the software. In this case, you can quickly do git stash, see how it worked before your new changes, and then git stash pop to go back. Not stashing some files accidentally, in this case, has virtually no consequences, as you will unstash on the same branch in a moment anyway. There is also no issue of picking the right stash from a list.

 

How to Create Very Short Shortcuts?

Adding aliases to .gitconfig has the advantage of retaining Git autocomplete, but it also has a disadvantage. These commands are still at least five characters long, like git c, and five characters is still too long for a command that you type dozens of times throughout your day. If you take an alternative path and create a bash alias like gc, you will lose autocomplete. Luckily, there is a way to make autocomplete work with Bash aliases. Bash uses a command __git_complete that you can use to register your aliases. Add the following to one of your RC scripts:

alias gc=’git checkout’
. /usr/share/bash-completion/completions/git
__git_complete gc _git_checkout

The second line eagerly loads Git autocompletion functions into Bash, without it, they are loaded only after you try to autocomplete your first Git command. To see a list of Git commands that __git_complete accepts, type _git_ and trigger autocompletion. This is confirmed to work with Git 2.7.4 and Bash 4.3.12.

 

How to Automatically Track Branch on First Push?

How often do you find yourself pushing a branch, and getting the following error message:

2.3.0 in ~/.dotfiles on test $ git push
fatal: The current branch test has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin test

Oh, yes, I forgot to use the --set-upstream (or its shorter version, -u) flag. Again. This is so frustrating, can we somehow automate it? The fact is, in 90 percent of cases you want to push your current branch to origin with the same name. Fear no more, as we have a solution for that.

Put this in your .bashrc (or .zshrc if you are a Zsh user):

git_branch() {
  git symbolic-ref --short HEAD 2>/dev/null
}

has_tracking_branch() {
 git rev-parse --abbrev-ref @{u} > /dev/null 2>&1
}

alias gp='git push $(has_tracking_branch || echo "-u origin $(git_branch)")'

This code sets a handy gp alias that sets tracking the branch on first push and falls back to usual Git push on subsequent calls:

2.3.0 in ~/.dotfiles on test $ gp
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 290 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
To git@github.com:conf/dotfiles.git
 * [new branch]      test -> test
Branch test setup to track remote branch test from origin by rebasing.

Exactly what we needed.

When Is It 100 Percent Safe to Rebase?

The main goal of rebasing is to get rid of redundant merge commits and utilize the commit-less fast forward merges instead. Rebasing and fast-forward merges work together and are useful in case of a distributed version control system that Git is.

Imagine a case where you did some work on your feature branch and tried to push it into a remote repository, but your push got rejected, because your colleague had just pushed some changes before you. If at this moment you do git pull, then under the hood Git will execute git fetch && git merge origin/new-feature. But instead of the usual fast forward merge that simply adds new commits to your branch, in this case, it will additionally create an actual merge commit since your local new-feature and remote new-feature branches have diverted.

This redundant merge commit can be easily avoided by doing git fetch and git rebase origin/new-feature. Rebase will rewrite the history of your local branch to make it look like you did all your commits later than your colleague.

Rebasing an unmerged local branch new-feature using respective remote-tracking branch origin/new-feature is always 100 percent safe, because you rewrite only the unpushed part of your local commit history. Nobody has seen your new commits, and you can do whatever you want with them. This is the beauty of the distributed version control system.

The only inconvenience is that in the case of a rebasing conflict, Git flips the roles in conflict markers of local new-feature with remote-tracking origin/new-feature and calls the remote HEAD, when in the case of a merge conflict it would call the local HEAD.

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*