Git Commands Every Software Engineer Should Know

Version control is crucial in software development, but the vast majority of engineers learn it incorrectly when starting out.

Git Commands Every Software Engineer Should Know

In this article, I’m just going to go over all the Git commands I use on a day to day basis, split up by concept and use-case. I will also cover a lot of pitfalls junior Git users fall into and how to avoid them. I have noticed that the Git education in a lot of universities (my alma mater included) is quite poor, so I have seen a lot of messy Git usage across my career. So let’s jump into how you can use Git more cleanly and keep your team happy.

Pushing Code Changes

Status
Git is constantly tracking what’s going on as you make changes locally. To figure out what it sees, you do:

git status

This command is very important, because what it says will affect what you do with your next 2 commands, add and commit. This status represents the changed files Git detects across your project.

Diff
This tells you exactly what code changes Git is currently aware of and what the commit will look like after you add/commit whatever. Here’s how you do it:

git diff FILE_PATH

This will give you the lines added/deleted for that one file. If the diff doesn’t match your expectations, you’re going to want to hit save in whatever IDE/text editor you’re using. You can also just do a git diff, but that gives you everything you have changed so far which is kind of a mess.

Add
Git status is going to return the files in your local repository in 2 categories, untracked and tracked. Tracked files are those that Git is aware of and has most likely been committed before. Untracked files are new files that you haven’t run add on. What add does is that it lets Git know that this file (should always be new) is something you want to commit and eventually push to remote. A lot of people (including myself as a newgrad) saw the Git push workflow like this:

git add . <-- Try not to do this!
git commit some_message_here
git push

What git add . does is that it adds literally every file Git has detected a change with to be set up for a commit. This is dangerous if a lot of the code you’ve created is actually generated, and your .gitignore doesn’t sufficiently cover them. This will lead to a lot of junk getting pushed to your remote, which will constantly generate noise on pull requests. Only run git add . if everything underneath the untracked files section is stuff that you want on remote. In the end, you can always be safe by just running git add on the individuals files that you’re interested in. In the end, you’re usually just working on already existing files so you should rarely run git add anyways, which I will elaborate on in the next section.

Commit
This is effectively the only git commit command I ever use, and why you never really need to run the “git add .”:

git commit -am ‘YOUR_COMMIT_MESSAGE’

The -am specifies 2 command line parameters:

  • a (add) - What this does is that it grabs all of the changes you’ve made to existing files and packages them into your commit. This is why you should almost never run git add.
  • -m (message) - This allows you to attach the message associated with the commit from the command line. If you don’t use this flag, git is going to open up a Vim prompt for you to type your commit message which I personally find too heavy, especially when I was just starting out. Vim is amazing yes, but it can be very unintuitive to junior developers.

Push
This is what gets your code onto remote (usually GitHub). Usually it’s just this:

git push

However, push is actually structured like this:

git push REMOTE_NAME BRANCH_NAME

By default, Git will push to whatever remote lies at origin. It will also push all local branches that don’t match their counterparts on the remote side, counterparts being determined by a simple name match. Keep these notes in mind for when you’re pushing to non-origin remotes and the much rarer case of your local branch not having the same name as the remote one you’re updating.

If you want to be specific and push a specific branch to a certain remote, you can use the following:

git push remote_name my_local_branch_name:remote_branch_name

Dealing With Other People’s Code

Fetch
What fetch does is it grabs all of the new code on remote so you can play around with it locally (rebase, cherry-pick, check out a new branch, etc). Unlike git pull, it doesn’t do anything with your local code; you are fully in control. This is why git pull is risky. “git pull” is one of those things they teach you in school to get you up and running with git with as few commands memorized as possible (I did git pull all the time back in university). In short, git pull does a fetch and then a merge, which gives you less control over how you want the new code to be integrated with yours. Here is how you do a fetch:

git fetch REMOTE_NAME

By default, a fetch will fetch from whatever the origin remote is in your git repository (so if you don’t specify a remote, git fetch = git fetch origin). I’ll go over what remotes are later.

Cherry Pick
Let’s say that there’s a single commit’s worth of code that you want to test/work with. However, there isn’t a branch you can merge/rebase against in order to only get that commit. This is when you do a cherry pick. Let’s say that you really want the code I wrote in this commit: https://github.com/Gear61/PADFriendFinder-Android/commit/d6ea8da972343c954b2e235f85e000cbda73fcf9

Every commit has a unique ID. You can see from the end of the URL and from the webpage itself at the top right that the commit ID for the linked commit is:
d6ea8da972343c954b2e235f85e000cbda73fcf9

Cherry pick is structured like this:

git cherry-pick COMMIT_ID

So to add my code for those 2 tests to your local environment/branch, you do this:

git cherry-pick 1925560490f66c473cd811e3485594484c6900a2

Stash
The second command I want to go over is stash. Stash is mainly relevant when you’re merging in code. Git gets angry at you when you have uncommitted code changes in this scenario as it doesn't know how to intertwine your changes and the incoming code. What stash does is that it hides away your work in progress code in a safe place and you can bring it back whenever. Here are the 3 main commands for that:

git stash — This “hides away” your code
git stash apply — This brings back your most recently “hidden away” code
git stash list — shows you the list of stashes you have locally (yup, you can have multiple stashes, Git is great)

So a common flow if you’re following a forking model is like this (let’s say the main repo is remote “main”, and you are updating your master against main’s):

git fetch main - Get the latest changes from the main repo
git stash - Put away your local changes temporarily so you can merge
git merge remotes/main/master - Update your local master against that of the main repo
git stash apply - Put your local changes on top of your updated master branch and resolve any merge conflicts

Branches

Figure out which branch you’re on along with all local branches

git branch

Figure out what branches exist (both local and remote)

git branch -av
-a (all)
-v (verbose)

If you’re missing a branch, you either need to convert a remote branch into a local one and/or do a fetch.

Start working on another branch

git checkout BRANCH_NAME

Create a new branch

git checkout -b BRANCH_NAME

The new branch will be a clone of the branch that you are currently on.

Remotes

A remote is a place where code is stored online. For almost everyone, it’s a GitHub repository, which is represented by a URL. Here’s an example remote:
git@github.com:Gear61/PADFriendFinder-Android.git. That link corresponds to the Android app I built to find friends in Puzzle&Dragons.

Adding a Remote
Let’s say that I’m working on PAD Friend Finder, and I want some code changes that my brother Jonathan has written and pushed to his fork which have not been merged into the main repo. In order to do that, I need to make my local Git repository aware that it even exists. To do that, I must add Jonathan’s fork as a remote.

Adding remotes is structured like this:

git remote add NAME_OF_REMOTE REMOTE_URL

So in order to start accessing the code on my brother’s fork by adding it as a remote, I would run this:

git remote add jonathan git@github.com:jcio001/PADFriendFinder-Android.git

And after that, I would run this to get the code changes on it that I don’t currently have locally:

git fetch jonathan

Checking Your Current Remotes
Sometimes you forget what the names of your remotes are. For those situations, you do:

git remote -v

This gives you a list of all the remotes that you have and the URLs they correspond to.