Git is scary
An gentle introduction to the basic git commands
Coming from the Windows world it is quite an adjustment to dealing with git
's command line first attitude. Some of the commands have intimidating sounding names, and even more intimidating arguments.
rebase
and reset --hard HEAD
might be two of the worst offenders. Then there are the commands that sound like they do similar jobs (hello fetch
and pull
).
It can be tempting to simply find a decent GUI for your OS of choice (and there are plenty of them) and call it a day, but you won't be truly comfortable using git until you've come to terms with the basics.
The intention of this post is be a quick introduction to some of the words you're going to see when you're using git. All the commands should be prefixed with git
when used.
Repository (aka: repo)
This is the name of a particular set of files and/or directories that you want git to monitor - usually one specific project. In reality, at the top of the repo's directory structure git will create a .git
folder to store it's own information. You should never need to modify this directory directly.
A local repository is one that is on your file system, whereas a remote repo is one that you have to access via http or ssh. Git can handle these directly, and you talk to a remote repo through your local one (or via a something like github)
init
and clone
One of these will likely be the first command that you use.
> git init
Initialised empty repository in /home/rob/example/.git/
init
simply creates you a new git repo in the current directiory.
clone
will create a new dir with the contents of the git repo at the address given, with the remote origin
that points to where you cloned from.
add
, rm
and mv
These commands are use to push your changes to the staging area. This is a kind of pre-commit area where you tell git what changes you want to be commited when you next run commit
.
They work similar to their *nix namesakes, so add
for a new or modified file, rm
to remove a file and mv
to move a file. For an added bonus, you can use these to begin with (rather than using the normal *nix commands first) so you don't need to do both.
You can always see the current status of the staging area, as well as unstaged files with git status
.
commit
This command will "save" the current staging area, and assign it an SHA hash. Git is a distributed version control system
or dvcs
for short, which means that it doesn't need to talk to a server to commit things. Your git repo is it's own server.
This is a fundamental difference from the historically popular version control systems, and can be a bit confusing initially.
Historically you would do something like this.
- Make a change locally.
- Submit that change to the source control server.
With git, there's now an extra step.
- Make a change locally.
commit
your change.- You can repeat steps 1 & 2 multiple times before moving to...
push
that change to a remote location
This allows you a lot more flexibility in the way you work.
log
This will show you a list of recent commits
.
> git log
commit 73dde4fe8219e543153da73405734c44f312fd9b
Merge: bf8f61e 4659e2d
Author: Rob Gough <rob@example.com>
Date: Mon Sep 30 12:16:05 2013 +0100
Merge branch 'add-author'
Conflicts:
README.markdown
commit bf8f61ee86189b936742a33c2d2e4b85b99ee72c
Author: Rob Gough <rob@example.com>
Date: Mon Sep 30 12:13:05 2013 +0100
added the description
There are a large number of arguments that can be passed to log
that will allow you to do funky thinks like show the branching information. It is definitely worth investigating further and will make it a lot easier to stick with the command line. Below is an example of the settings that I'm currently using.
* 73dde4f - (HEAD, master) Merge branch 'add-author' (2 weeks ago) <Rob Gough>
|\
| * 4659e2d - added author (2 weeks ago) <Rob Gough>
* | bf8f61e - (my-example-tag) added the description (2 weeks ago) <Rob Gough>
|/
* b6e2f2d - readme (2 weeks ago) <Rob Gough>
branch
and checkout
Branches are named after the branches of a tree. They all start at the same point (the trunk) and diverge from there.
Unlike most version control systems branching with git is painless and quick, and it is heavily encouraged to use branches for absolutely everything you work on. This can take a bit of adjustment if you're used to the old world.
git branch
will list out all the current branches, putting a *
next to the one you are currently on.
git branch my-branch
will create you a new branch called my-branch. Note that you will not switch to it automatically.
If you want to switch to a branch then you need to use git checkout
. This will update the files in the working directory to match the version stored in that branch, and will record any future commits (until your next checkout) in that branch.
Rather than having to type both branch and checkout if you want to create a new branch, there is a shortcut: git checkout -b new-branch-name
.
The advantage of branching is that it provides a safe place for you to do all your experimentation without the fear of damaging your existing code and allows you to work on multiple features independently and simultaneously.
remote
This command is used for setting up remote repositories. These are repositories that your repo can talk to. You can list the current remotes with
> git remote
origin
If you've created your repo with init
then you've probably not got any repo's, but if you have used clone
then the repo you have cloned from will be there under the name origin
. Because git is decentralised you can add any of your colleague's repo's in there too using
> git remote add <name> <url>
This allows you to sync your changes to other repo's, or bring all the latest changes back to your code. We'll cover the commands for this shortly.
Two of the most common url's for remote repo's are https
and ssh
both of which you can see in the example below.
> git remote add robgough https://github.com/robgough/myrepo.git
> git remote add robgough git@github.com:robgough/myrepo.git
fetch
This is used to import commits from a remote repository into your local repo. They are stored as a remote branch as opposed to a local branch, allowing you to review them putting them into your local code.
Note that if you omit the branch name it will assume you want master
.
> git fetch <remote-name>
> git fetch <remote-name> <branch-name>
This allows you to see what is happening on that remote branch, what people are working on. It has no effect on your local code (which is nice).
A remote branch is read-only, and you can view your current remote branches with git branch -r
. You can checkout
these remote branches and view their history with log
. If you want to accept the changes you will need to perform a merge
.
merge
So you've completed your work on a branch, and now you need to get it back into master (or indeed some-other-branch), that's where the merge
command comes in.
This allows you to bring the changes in one branch over to another, and provides some help to manage when conflicts arise.
> git checkout master
> git merge my-feature-branch
You always need to checkout the branch you want to merge into, and then run the merge
command passing in the branch you want to merge from.
Once you have fixed any conflicts then you will need to commit to complete the merge.
If there were no conflicts at all and it was a straight forward merge then git will do something called a fast forward. The big difference here is that you won't need to commit to complete the merge.
If you want to ensure that all merge's are recorded you can run with the --no-ff
switch.
It is worth checking out the links in the further reading section for more detail on what git is doing under-the-hood here.
pull
If you need to pull down changes from a remote repo we can do a fetch
and then a merge
. You can think of pull
as a shortcut to this, rolling up both those commands into one.
> git pull <remote>
You can also ask pull
to use rebase
rather than merge
using
> git pull --rebase <remote>
push
push
is how you get your local changes up to a remote repository. This is closer to the traditional idea of a submit, except you've already filled in all the details of the change within the commits.
You can use this command to push particular branches, and to create branches on the remote repo too.
git push <remote> <branch>
Note that git push
will default to a remote called origin
and a branch called master
.
If the resulting push will cause a non-fast-forward merge in the remote repo, then the push will fail. You will need to pull down the changes locally, merge them, then push the result of that merge back up. This can be overridden with the --force
switch, but this should only be used if you really know what you're doing.
--all
will push all your local branches up to the remote.
revert
This is used on an individual commit, and will create a NEW commit which will undo the changes made by the specified commit.
This is useful for fixing a small mistake which was put into a single commit.
Say for example we have 4 commits.
Git Log: A => B => C => D
Value of MY_CONST: 2 4 4 4
and in B
we changed the value of MY_CONST
to equal 4
, from the earlier value of 2
. If we run...
> git revert B
The the git history will look like
Git Log: A => B => C => D => E
Value of MY_CONST: 2 4 4 4 2
where E
returns the value of MY_CONST
to 2
reset
Unlike revert
, which keeps a history of the undone changes, reset
will destroy the history of anything you reset. Consider that a warning up front.
It has a lot of options, so we'll cover a few of the key ones here.
> git reset <file>
Removed the specified file from the staging area, but leave the working dir unchanged.
> git reset
Similar to reset file
except this will remove all files from the staging area, but will leave them in the working dir. Using the --hard
flag will also overwrite all the changes in the working directory. So this brings you back to where you were before you started making any changes.
> git reset <commit>
Move the current branch back to the specified commit, leaving the changes since that commit sat in the working directory.
> git reset --hard <commit>
Move the current branch back to the specified commit, and empties out the working directory. This will destroy everything not-commited, and all commits after the specified commit.
This should only be used on local code, and is considered bad form on public code. That is where revert
should be used instead.
rebase
This is hard to explain in a few paragaphs, but makes complete sense when you understand how git works under the hood. In essence it is a way to move a branch from one base commit to another.
C > D (branch)
/
A > B > E > F (master)
>git checkout branch
>git rebase F
G > H (branch)
/
A > B > E > F (master)
Notice that C and D have become G and H respectively. That's because git has actually created new commits for them under the hood. The advantage of doing this is that you can now simply do a fast-forward merge onto master, as it will look as if all those changes in branch were made after E and F.
stash
Assume you're working on branch-a, and an urgent request comes in that requires working on another branch. stash
let's you save your working state without commit-ing, and then switch to another branch. When you come back later you can apply
your stash again and continue working. You can also move stashes onto other branches, useful when you realise you've been doing your work on the wrong branch!
The useful commands here are
> git stash
Creates as stash with the current working dir.
> git stash list
Lists your current stashes
> git stash apply
Brings that stash back into your working dir. Note that the stash will still exist, and you will need to run git stash drop <name>
to remove it.
> git stash pop
This can be used to both apply
and then drop
in one go.
> git stash apply --index
This is like apply, but will will also try and replicate the staging area to how it was when you stashed too.
tags
These are a bit like labels, and can be used to identify code at a particular point of time. If you do versioned releases, you could use tags to identify a particular release. This avoids having to keep branches around once a release is created. If a hotfix was required, you could re-branch at a later date from a tag.
Further Reading (and Watching)
https://www.atlassian.com/git/tutorial/git-basics
http://think-like-a-git.net/
Git for Ages 4 and Up [video]
Git Immersion
Update 1
Added git stash pop
as suggested by @SFrost007