Git Best Practices and Tips by Toptal Developers

Share

This resource contains a collection of Git best practices and Git tips provided by our Toptal network members.

This resource contains a collection of Git best practices and Git tips provided by our Toptal network members. As such, this page will be updated on a regular basis to include additional information and cover emerging Git techniques. This is a community driven project, so you are encouraged to contribute as well, and we are counting on your feedback.

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. That’s why we want our top Git developers and engineers to share their knowledge and offer their best tips.

How to Check Which Files an Unknown Command Will Modify

Let’s say you are assigned to a project that is using a small legacy command for manipulating some files. Unfortunately, though, that command is not documented. So, how could you figure out which files the command accesses and what changes is it making to those files?

One of the easiest ways to find this out is by using Git. First, copy the whole project to a safe test environment, and use the git init command to initialize a new Git repository. Next, execute the legacy command in question and, after it finishes, check what has changed by using the git diff command. When you’re done, just delete the repository by running the rm -rf .git command.

Contributors

Juan Eugenio Abadie

Full Stack Developer (Ingenea SRL)

Professional C/C++/Web developer who loves Open Source Software and learning new technologies.

Get the latest developer updates from Toptal.

Subscription implies consent to our privacy policy

Is It Possible to Use Multiple GitHub, GitLab, and Bitbucket Accounts at the Same Time?

What should you do when your client asks you to use their email address to create a GitHub account that should be entirely separated from your private account? Or, let’s say that you have a client that doesn’t have much available time (as always), and they give you their GitHub account credentials. Or maybe you need to access some private repository for which you cannot change permissions and invite more users. Or maybe even your client sent you the invitation using a different email then you were expecting, and then the client went on vacation.

In all those cases, you need to manage multiple GitHub accounts from the same development machine. If you are using SSH keys, there will be a problem since your keys are already connected to your personal account. You can clone the Git repository using https URL and type the password each time you need to deploy, but this could be a problem as you need to remember all the passwords, and keep track of the changed ones.

However, there is a way to continue working with multiple accounts using multiple SSH keys.

First, you need to generate keys with the following command:

ssh-keygen -f ~/.ssh/id_rsa_my_company_account
# press enter for empty passphrase

You can find private and public keys by running the following command:

ls ~/.ssh/id_rsa_my_company_account*

Then, you need to add the public key to the GitHub/GitLab/Bitbucket settings. You can copy and paste the output of the following command:

cat ~/.ssh/id_rsa_my_company_account.pub

There is very useful command ssh-add to help you manage your SSH keys. To list all current saved SSH keys in every X-session, or a login session ssh-agent is started, you can execute the following command:

ssh-add -L # list all current keys

You can start a new agent by simply calling it:

ssh-agent bash -c 'ssh-add -L' # The agent has no identities.

So, you can use these two commands (ssh-add and ssh-agent) to set the keys to another account.

ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa_my_company_account; git clone git@github.com:company/repo.git'

Another way to use those specific keys for some repositories is to configure the SSH. Let’s name that repository my_company_account_github.com (no worry, it will be translated to github.com domain). Just create a file ~/.ssh/config with the following content:

Host my_company_account_github.com
  Hostname github.com
  IdentityFile ~/.ssh/id_rsa_my_company_account

When you want to download the repository, just use that new fake repository domain name.

git clone git@my_company_account_github.com:company/repo.git
# this will use ~/.ssh/id_rsa_my_company_account key

If you decide to change which key you are using for an already cloned repo, you can edit .git/config or use git remote set-url origin git@my_company_account_github.com:company/repo.git.

To see which username (-T) and which key (-v) SSH is using:

ssh -T git@my_company_account_github.com
ssh -v git@my_company_account_github.com

If you want, you can generate your SSH key with the password (passphrase). I do not suggest that since it will ask for the key passphrase each time you use git command.

Please note that for some deployment systems, like Heroku, you also need to log in before pushing to their repository. Heroku stores keys in ~/.netrc, which is created when you type heroku login. Keep in mind, once it’s set up, it stays forever. So remember that you need to sign in with heroku login with the same account that is connected to your SSH keys to make it work.

Contributors

Dusan Orlovic

Freelance Git Developer
Serbia

Dusan Orlovic (Duke) is an engineer with Ruby on Rails experience. He believes that web and mobile applications need to be connected to other applications by sharing data, visitors, and outcomes in order to survive on the market. That's why Duke writes modern, well-tested, and maintainable code that connects various services. Open source software makes him a better programmer and gives him the tools he needs to implement web ideas.

Show More

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.

Contributors

Dmitry Lashkov

Freelance Git Developer
Lithuania

Dmitry is a software engineer with over seven years of experience. He has worked with existing products, built complex ones from scratch, and has the ability to focus, dive in, and gain a complete understanding of the problem he's solving. He loves automated tests, reviewed pull requests, and agile methodologies because they allow developers to build quality products and grow professionally. Ruby and Ember are his technologies of choice.

Show More

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.

Contributors

Dmitry Lashkov

Freelance Git Developer
Lithuania

Dmitry is a software engineer with over seven years of experience. He has worked with existing products, built complex ones from scratch, and has the ability to focus, dive in, and gain a complete understanding of the problem he's solving. He loves automated tests, reviewed pull requests, and agile methodologies because they allow developers to build quality products and grow professionally. Ruby and Ember are his technologies of choice.

Show More

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.

There is a switch -u which tells git stash to stash untracked files too, but I would suggest avoiding git stash altogether in cases when there is a branch switching involved. It is much easier to put a temporary WIP commit simply on top of your branch before switching away with the usual git add --all && git commit -m ‘WIP’, and then when you have switched back, do git reset HEAD^.

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.

Contributors

Dmitry Lashkov

Freelance Git Developer
Lithuania

Dmitry is a software engineer with over seven years of experience. He has worked with existing products, built complex ones from scratch, and has the ability to focus, dive in, and gain a complete understanding of the problem he's solving. He loves automated tests, reviewed pull requests, and agile methodologies because they allow developers to build quality products and grow professionally. Ruby and Ember are his technologies of choice.

Show More

Git Shortcuts Can Make You More Productive. Here's How.

There are some interesting Git shortcuts that can be added to the local .gitconfig file. By adding these shortcuts, you should be able to run a Git command by entering just two or three characters, and perform common Git actions much faster instead of typing long commands of 10 characters or more.

The first thing we need to do is to look at our user .gitconfig file. For Linux and Mac it is located at the home directory: /home/<username>/.gitconfig for Linux, /Users/<username>/.gitconfig for Mac, or simply ~/.gitconfig for both. For Windows it is located at C:\Users\<Username>\.gitconfig. If the .gitconfig file does not exist, we need to create an empty one.

Everything added to the .gitconfig file becomes globally available, and will work in all our Git repositories. To create new shortcuts, we need to edit (or create if it doesn’t exist) an alias section in the file.

As an example, we can abbreviate the git add command to just git a by adding the following line under alias section:

[alias]
    a = add

To become more productive, here is our suggested list of most commonly used Git commands and their proposed aliases:

[alias]
    a = add
    cka = add --patch     # stage commits chunk by chunk
    ci = commit -m        # commit with message
    ca = commit -am       # commit all with message
  
    co = checkout         
    nb = checkout -b      # nb stands for new branch               

    cp = cherry-pick -x

    d = diff              # diff unstaged changes
    dc = diff --cached    # diff staged changes
    last = diff HEAD^     # diff last committed change

    pl = pull
    ps = push            

    rc = rebase --continue
    rs = rebase --skip

    ss = stash
    sl = stash list
    sa = stash apply
    sd = stash drop

How are shortcuts used now? Let’s pick a long command as an example. We are in the middle of a rebase and after fixing the conflicts in our files we need to tell Git to continue the rebase. Without the shortcut we would type:

$ git rebase --continue

With the shortcut we just need to type:

$ git rc

Some other interesting shortcut commands that we can add to our ~/.gitconfig are commands that remove files from index, rewind our state to before the last commit, and delete all branches already pulled to master:

[alias]
    # ...
    unstage = reset HEAD
    uncommit = reset --soft HEAD^
    
    delete-merged-branches = "!f() { git checkout --quiet master && git branch --merged | grep --invert-match '\\*' | xargs -n 1 git branch --delete; git checkout --quiet @{-1}; }; f"

We would now do this, respectively:

$ git unstage
$ git uncommit
$ git delete-merged-branches

The last one is long, but you can map it to something like dmb or use your shell autocomplete by pressing tab.

Doing Git shortcuts, or aliases, is a good way to increase productivity when using Git from the command line. It’s worth it to keep track of the commands you use more and add short aliases.

Contributors

Ivan Neto

Freelance Git Developer
Brazil

Ivan is an engineer with eight years of experience in software and web development. Primarily focused on the web, he has solved code optimization problems arising from growing systems, as well as the migrated production apps and services on-the-fly with zero downtime. While a major part of his experience has been on the back-end, he can also work with the front-end, databases, networking, and infrastructure.

Show More

In A Git Repository, How Do We List Files Have Been Changed After A Specific Date?

Git has some powerful subcommands. With a couple of these, in combination with a little Bash scripting, you can produce a list of all the files that have been changed in the repository after a specific date.

for h in `git log --pretty=format:"%h" --since="2015-05-26"`; do
	git show --pretty="format:" --name-only  $h
done | sort | uniq

This command may seem overwhelming at first, but we can break it down for an easier understanding of how it works:

for h in {list of commits since given date}; do
    {list files in commit h}
done | {remove all duplicates}

We begin by looping over all the commits that have been made since the specified date. The command that produces this list is git log --pretty=format:"%h" --since="2015-05-26". This particular command lists all commits made since 2015-05-26. Next, in every iteration, we list all the files that have changed in that specific commit using git show --pretty="format:" --name-only $h, where $h is the commit hash of the current iteration. Since this loop produces a list of files where duplicates may appear, we pass the output through sort and uniq. sort sorts the entire list of filenames, and uniq removes all adjacent duplicates.

Alternative, and little bit shorter version of the same command:

git log --since=2015-05-26 --pretty=format:%h | xargs git show --pretty=format: --name-only | sort -u | grep -v ^$

Contributors

Kleber Virgilio Correia

Freelance Git Developer
Brazil

Kleber is a software developer with ten years of experience working professionally in IT. He enjoys sharing and acquiring knowledge in a broad range of topics, including Unix, Agile software development, functional and object-oriented languages, design patterns, RESTful architecture, distributed applications, and cloud computing.

Show More

Alexey Shein

Freelance Git Developer
Germany

Alexey is a talented software engineer with more than eight years of web development experience including Ruby on Rails, PHP, and JavaScript. He enjoys solving difficult technical problems and learning new technologies. He is a good mentor and likes helping teammates.

Show More

When Force Pushing Is the Only Way

It is possible to rewrite the history of remote branches with git push origin HEAD --force, but 99 percent of the time, it is a dangerous thing to do.

First of all, this is because other people’s fetches (pulls) will be broken and they will have to force fetch (pull) too. Then, unless you use a --force-with-lease, instead of the traditional --force you can accidentally delete useful commits which were pushed by others after you pulled.

So why does this --force switch even exist? It is for the one percent of cases when you need to remove certain commits from the history for good. One example is when you want to reuse the name of some experimental branch and start afresh discarding all the hackish commits. Another case may be when you accidentally pushed some sensitive information and needed to remove commits with it.

Contributors

Dmitry Lashkov

Freelance Git Developer
Lithuania

Dmitry is a software engineer with over seven years of experience. He has worked with existing products, built complex ones from scratch, and has the ability to focus, dive in, and gain a complete understanding of the problem he's solving. He loves automated tests, reviewed pull requests, and agile methodologies because they allow developers to build quality products and grow professionally. Ruby and Ember are his technologies of choice.

Show More

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.

Contributors

Alexey Shein

Freelance Git Developer
Germany

Alexey is a talented software engineer with more than eight years of web development experience including Ruby on Rails, PHP, and JavaScript. He enjoys solving difficult technical problems and learning new technologies. He is a good mentor and likes helping teammates.

Show More

Submit a tip

Submitted questions and answers are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.

* All fields are required

Toptal Connects the Top 3% of Freelance Talent All Over The World.

Join the Toptal community.