Whilst I use GUI tools, such as the Visual Studio GIT tooling, TortoiseGit etc. I really wanted to get a good understanding on using GIT from the command line. I’ve heard it said that this is the best way to use GIT – whilst I’ll leave that to the reader to decide, let’s get to grips with common tasks etc. from the CLI.
Note: This is not meant to be a comprehensive list of all commands or even all options in the commands I’ve listed. It’s more a list of commands I use often use.
Creating our repository
As you probably know, git is a distributed source control system, so we can create a repository locally (as opposed to requiring a server to host our source code). This means we can create a repository for anything we do, allowing us to commit changes/revert etc.
To create a repository, first create a folder for your files (if you’ve not already done so) then run the following command to create the repository (which will add the .git folder)
git init
This can be run against an empty folder or one with files within it. It will not delete any existing files.
If we want to create a repository that can act as a remote or shared repository we’d use
git init --bare
This remote type of repository is useful in team situations and is special in that no code changes will occur against this repository, it just acts as the PUSH location for each user’s repository.
Staging files
After we’ve add our files or folders we’ll want to stage the files/folders. This step doesn’t always exist in the UI tools (or not in a separate step), but allows us to in essence state what files/folders are to be part of the commit at the specific moment in time. Changes are not committed to the repository and hence changes can be lost. To add all files/folders we can execute
git add .
To add individual files we list each one after the other.
Once staged, we could edit our files further and these changes will not be committed until they too are staged. So think of staging as copying the state of the file/folders at a point in time, ready for committing.
We would tend to stage changes to create a change-set of files, i.e. for a feature or functionality as a single commit.
Unstaging files
To unstage, we simply use
git rm --cached -r .
We replace the . with any specific files that we want to unstage, in such situations the -r can be omitted, i.e.
git rm --cached file1.txt
Status
To find the state of the git repository, including which files are tracked, untracked etc. can be found using
git status
Committing files
When we’re ready to commit our staged files/folders, we execute
git commit
The editor associated with git (for example vi) will be displayed if you run this command and we can now enter a message to differentiate our commit. To commit with a message (i.e. without having to open an editor) simply type
git commit -m "Your message goes here"
Obviously replacing what’s in the speech marks with the log message you want for the commit.
If we want to stage & commit (at least stage files that are already added to the repo.) we can use the -a parameter, i.e.
git commit -a -m "Your message goes here"
Commits are not associated with an incremented number, such as SVN but instead using an SHA1 string. This is a bit of a pain in some ways, especially if, like me, you use the SVN commit number as part of your versioning system, i.e. 1.0.1234 would be the commit 1234 in SVN.
Viewing the log
We can look at the log of commits (i.e. the commit it’s SHA1 and the log message) using
git log
When the : or (END) appears, press q to quit, space bar to continue when you see a :
The log command, by default, is quite verbose, we can reduce this verbosity using
git log --oneline
Which, as the parameter suggests, outputs the logs to a single line, this means only the first 7 characters for the SHA1 string and also a single line from the commit.
Branches
To view the current list of branches we use
git branch
Master is the default branch, equivalent to trunk in SVN etc. So if you’ve not yet created any branches you’ll see only master listed.
To create a new branch off of master we use
git branch branch_name
Obviously replacing branch_name with the name of your branch. Whatever branch we’re in when we create a new branch is taken from the current branch. In other words if you’re in master (usually denoted by the name master in the command prompt, if your prompt is setup) then creating a branch from this will branch master.
Checking out a branch
Switching to a branch is known as checking out a branch. So let’s assume we created a branch named rc1.0 from master, we can checkout (or switch) to this branch using
git checkout rc1.0
All of this happens on your local machine (unless you’re also linked to a remote repo.) so you can create branches cheaply and easily to work on locally. When linked to a remote repo. you can push and pull changes to that repo. to add your branches etc.
We can delete a branch using
git checkout -d rc1.0
If there are changes within the branch you’ll need to use the capitalized version of this parameter, i.e.
git checkout -D rc1.0
Tags are branches
A tag is just a branch which has a special meaning in GIT (which, if I recall is the same in SVN). All we’re really doing with a tag is creating a pointer/reference to a commit and in most cases versioning this in some way (at least conceptually).
So using
git tag -a v1.0
will result in a new tag added and named v1.0, we can list the tags by using
git tag
and delete using
git tag -d v1.0
Merging
Whether you pull changes from a remote repo. or you are simply working on multiple branches and wish to merge changes between them, you use
git merge branch_name
If there are conflicts then you’ll need to resolve those. This is where one of the diff tools that come with git GUI’s is useful, or use your preferred editor.
GIT will be in a merging state. Make your changes such as resolving conflicts etc., then stage and commit your changes.
Cherry picking the commits you want
Occasionally you’ll be in a situation where, maybe you’ve been working on a branch and somebody says, “we need feature X in master” but you’ve made other changes which you do not want merged, in such cases we look to cherry pick just the changes we want.
The first thing we’d need to do, is view the logs of what’s been committed to look for the commit SHA1 code as these are what we’ll need to tell the cherry-pick command what commits to “merge” into our branch. We do not need to switch to the branch we can run
git log branch_name
to get the log for the specified branch.
Now we run
git cherry-pick sha1_string
where the sha1_string is the commit SHA1 – you need not list the whole SHA1 but usually 4 characters, if unique, will suffice, in other words we need to use enough characters to be unique. We do not need to specify the branch as git will figure this out. Conflicts will still need to be resolved, like any merge. If no conflicts exist then the changes are auto-committed, otherwise you’ll need to add and then run
git cherry-pick --continue
Cloning a repository
When taking a copy of a repository, whether from a remote location such as GitHub or cloning a local repository we use the clone command, i.e.
git clone /c/Development/myapp
In this example I’m using git bash to clone from my c:\Development\MyApp repository into the currently selected folder.
Fetch and Pull
After we clone a repository, whether remote or local, we can fetch or pull as well as push to the other repository. This is all part of a distributed source control system.
To fetch changes from a repository we can use
git fetch
and to pull from our remote repository we use
git pull
Both fetch and pull update our local repository. However pull, in essence does a fetch then merge. So the main difference between a fetch and a pull is that a fetch will do an update (in SVN terms) but it doesn’t force you to merge the changes, whereas pull will try to merge changes and hence you may get merge conflicts. Obviously if you’re busy on some work but want to see what’s happening on a remote repo. then you’d just do a fetch.
Push
When changes are made to a local repository and we want to push those to a remote we use
git push
This doesn’t work on our local repository unless it’s been created with the init –bare command.
Adding “remote” repositories
A remote repository, in this instance is any other repository (i.e. it doesn’t have to be in a remote location). If, for example, we created a local repository and then a “remote” using init –bare we need to tell git about the existence of this repository. To do this we use
git remote remote_name url
So for example, we might create a remote named main on our local machine if we wanted, something like this
git remote main /c/Development/mainrepo
Now we can push to the remote using it’s name, i.e.
git push main
If you want to make an upstream/remote location the default (i.e. so you no longer type the remote name) then you can set this using
git push -u remote_name
where remote name in my example is main.
If you need to check the remotes, simply use
git remote -v
Stash
Whilst working on a branch, we might need to switch to another branch or maybe we’ve made changes and want to store them for later use, but not commit. In such cases we can stash our code.
git stash push
This command will temporarily store our changes with no name/text associated with them, this is fine if the stash entry is short lived, but it might be better to assign a name or the likes to the stash entry using
git stash push -m "Your name/message"
We can “pop” the changes back into our repo. using
git stash pop
If we have multiple items stashed, we can pop specific one’s using the –index, for example
git stash pop --index 1
We can list everything in the stash using
git stash list
Reverting
There are several ways we might revert something. We might want to revert our repo. to a previous version, in this case we use the checkout command with the
git checkout 39e5eec
This will result in a detached HEAD, to “rettach” just use
git checkout master
Obviously replace master with your branch name.
If we have changes staged and/or unstaged but want to revert/clear all the changes we can use
git reset --hard
We can revert a commit by using
git revert d9b66e0
Where the d9b66e0 is the SHA1 commit you wish to revert.
Switching between branches
When we switch between branches we use checkout command, but what about if we switch to a branch and want to switch back to the previous branch.
Sure it’s easy to use the command completion (i.e. tab key) after the checkout command along with part of the name of your branch, but an alternative is to use the – option, i.e.
git checkout -
so for example, we might use the following to switch to master then back to the previous branch
git checkout master
git checkout -
Rebase instead of merge
In my post Advanced Git CLI I talked about the rebase command. Rebasing can be used for advanced scenarios but also used in place of a merge in far more simple scenarios.
Let’s assume we’ve created a branch from master and we’ve made a bunch of changes but master has changed. If we merge master we’ll get an entry in the log to show a merge, which is fine. An alternative to merge is to rebase which basically stores your branch changes, then gets the updates from master (in this case) and then applies your changes on top of the master changes in your branch. It’s almost like we’ve branch master as it is now then made our changes.
Note that this still may come with merge conflicts which you will still need to resolve, but it’s makes for cleaner logs.
Here’s an example of the command being used from a branch and rebasing against master
git rebase master
The choice between using rebase or merge may be dependent upon what your want from your project history and rebasing can therefore hide collaboration commits etc. which may not be good. Also rebasing upon a merge (i.e. if you merged from master then made more changes) will probably result in conflicts which have no differences.
See also Merging vs. Rebasing for more information of comparisons between the two commands.
Will there be a merge conflict?
Occasionally we’ll be in a situation where we’re working on our branch and need to check whether there’s any merge conflicts awaiting us. Sadly there’s no simply command that says check-for-conflicts as it were, but we can do the following (after ensuring master, for example, is upto date and running from the branch you wish to merge into)
git merge master --no-ff --no-commit
(obviously replace master with the branch you want to potentially merge into your branch)
The option –no-ff, as the name suggests, tells git merge to not fast forward whilst –no-commit, again probably obvious, tells git merge to not auto-commit. In essence these options will merge from master (in this example) but no make any actual commits to your branch. However the merge will show us any conflicts etc.
Instead of committing this merge we simply abort it using
git merge --abort
so now our branch is back to the state pre-merge.