This presentation is an HTML5 website
Press → key to advance.
Zoom in/out: Ctrl or Command + +/-
“Git is a free & open source, distributed version control system designed to handle everything from small to very large projects with speed and efficiency.”
First initialize the git repository:
git init
Then add files
git add -A
# or
git add .
# or
git add myfile.txt
git add myotherfile.rb
And commit the files
git commit
The index contains the changes to will be added to your next commit. Your commit will /not/ contain the changes in your working directory. Only the changes that were added to the index!
If you add a file, or a part of a file, to the index, a copy is made of that file. When you commit, it is that copy which ends up in the commit.
Important: if you make changes to the file after adding it to the index, those changes will not end up in your commit, unless you add the file again to the index!
`git diff`
# shows the diff of your working copy
`git diff --cached`
# show the diff of your index
If you modify files, you need to ‘stage’ them again, by running git add
.
If you only modified files (not added new ones) you can skip staging with:
git commit -a
You can also add only parts of a modified file:
git add -p myfile.txt
git status # show the status of your repo
git log
git log
has many options. The following:
git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset \
%s %Cgreen(%cr) %C(bold blue)an>%Creset' --abbrev-commit --date=relative
Will give you this, which gives a lot more information
Tip: add this in your global gitconfig as an alias: ~/.gitconfig
[alias]
l = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s
%Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
Checkout a single file. Notice the dashes: git checkout
is also used in other cases, this makes it clear to Git that you are pointing to a single file.
git checkout -- /path/to/file # restore version from index
git checkout HEAD /path/to/file # restore to latest committed version
Reset your branch to a commit and all the changes in your working copy
git reset [--hard]
Write a reversed version of an existing commit. This is very useful if you have already pushed the commit
git revert [commit-sha]
We’ll discuss this later
Knowing how Git stores all your data will give you a better understanding of the fundamentals and Git will become a lot more predictable
The most important point to remember:
Basically a Git repository is a database of objects
The most important types of objects:
Let’s study those in a bit more detail
Git stores the contents of a file in a ‘blob’.
some examples:
$ git hash-object hello.txt
ce6c1fd146f65c899e6b10e46c89097c644e3229
$ git hash-object say-hi.rb
a8784b043f12b4b0c9114c55ebf33f5c9b44ce8f
A tree is a like a directory
If you think about Git, think about commits!
Did I mention that you should think in terms of commits, when working with Git?
If you understand commits, you basically understand Git.
Because a commit hash is very difficult to remember and not really useful to work with, Git uses references to point to specific commits.
One such reference is HEAD
Another important reference is master
And that brings us to…
Branches in Git are basically just references to other commits
Creating a branch and switching to it is easy:
git branch mywork
git checkout mywork
The two previous commands can be combined:
git checkout -b mywork
This will create a branch and immediately check it out.
Once you have merged your work on a specific feature back into your master branch you can delete your feature branch:
git branch -d mywork
When you have been working on a (feature) branch for a while you will probably want to merge those branches back together.
# to merge the origin branch back into your mywork branch (to bring it up to date)
# checkout the target branch
git checkout mywork
# merge the branch into the current branch
git merge origin
Now your repository looks like this: (Notice that commit C7 has two parents)
When merging two or more branches there are two possibilities:
Git automatically proposes a commit message:
Merge branch 'mywork' into master
Merge the ‘mywork’ branch into origin The origin branch is simply fast-forwarded
Instead of merging (with merge commits) you can also rebase (so you can then fast forward)
Some people will tell you that this is very harmful, it can break your repository and destroy the universe.
This is NOT TRUE. (At least if you know what you are doing)
By rebasing your commits you can actually rewrite your history:
Do not rebase commits which have already been pushed to other people
Do not rebase commits which have already been pushed to other people
Do not rebase commits which have already been pushed to other people
(I’l explain how to push later)
Each commit which is rebased will get a new, different, hash. People (and Git) which pull this new hash will get confused.
If you do rebase a commit which was already pushed, Git will refuse the new commit, unless you use the --force
option.
After modifying your index again:
git commit --amend
This is an alternative approach to merging, with a merge commit.
Let’s reuse the example:
Now you want to merge ‘mywork’ into ‘origin’ without creating a merge commit
What we did before:
git checkout origin
git merge mywork
What we will do now:
# on the mywork branch
git rebase origin
# fix any merge conflicts
git checkout origin
git merge mywork
# on the mywork branch
git rebase origin
With interactive rebasing you can really rewrite history the way you want it to be. …And break your repository.
Rebase the commits since
git rebase --interactive <commit-hash>
Suppose we have the following commits
To rebase the most recent 3 commits:
git rebase --interactive 4efd195
To share your work with other people you have a few options. One of these is to work with remotes
Easiest method to set this up is by cloning an existing repository instead of initializing your repo.
git clone http://git.drupal.org/project/drupal.git
This will set up everything for you
Sometimes you will want to add a remote
This is done with the git remote
command
git remote add github git@github.com:teranex/git-talk.git
This will add my Github repository for this presentation as a remote with the name github
When you have cloned the repository:
git pull
This will pull in the changes from the current branch on the origin
git push
Will push your changes to the origin
However: This will only work for ‘tracking’ branches.
It is important to think about remote branches as just branches
By default, Git does not know, nor care, about relationships between branches!
Git just sees two branches
git pull github master
git push github master
You can get a good overview of all your local and remote branches and how they are trakcing with: git branch -avv
To create a local branch based on a remote branch:
git checkout --track -b mywork github/mywork
To link an existing local branch to a remote branch:
git branch --setup-stream github/mywork
You can verify this in the git config file (.git/config) in your repository:
To better understand pulling, let’s see what actually happens. Instead of using git pull
, you also do (while on the master branch):
# pull in the new objects
git fetch github
# merge the remote branch with the local branch
git merge github/master
This works exactly the same as merging two local branches!
Merging a remote branch in your local branch can create a useless merge commit:
You can avoid this by rebasing instead of merging:
git pull --rebase
or, if you want to do it manually:
git fetch github
git rebase github/master
$(__git_ps1 ' %s ')
in your $PS1
Git has plugins available to migrate from and/or integrate into other versioning systems as well. One such plugin is git-svn.
With git-svn you can:
To locally use Git and push to a central Subversion server:
First ‘clone’ the Subversion repository into a local Git repo
git svn clone -s http://svn.example.com/myproject
# the -s means the subversion repo has a standard layout (trunk/ etc)
Now you can work as usual with your Git repository. Except… instead of running git pull
to get the changes from other people, you now do:
git svn rebase
And you don’t do git push
, but:
git svn dcommit
To use the ‘Git Flow’ branching strategy a Git plugin is available: git-flow. This plugin makes it really easy to follow Git Flow:
First initialize it to configure the names. (I recommend to use the default) git flow init
To start a feature branch:
git flow feature start my-exiting-feature
To publish the feature (push it to the remote)
git flow feature publish my-exiting-feature
To finalize the branch:
git flow feature finish my-exiting-feature
In a Git repository you can ‘link’ other repositories to subdirectories. This can be useful, for example when using external libraries or when building your VIM configuration, to pull in all the plugins.
But submodules do not really work the way you would expect them to work, at first:
First you need to add a repository as a submodule
git submodule add git://path/to/repo path/in/repo
This will modify the .gitmodules file, which is part of the parent repository, and record the exact commit which is checked out in the submodule. Now you can commit, push, etc.
When another repository pulls in the change the following steps are required:
git submodule init
# This will update the .git/config file for that repository
# and register the submodule in the repository
git submodule update
# This will check-out all the submodules to the correct commit
To update you can checkout another commit in the submodule (for example by pulling) and commit the reference to this new commit in the parent repository.
To update all the modules, you can use something like:
git submodule foreach git pull origin master
Sometimes somebody will introduce bugs into your software or brake previously working features. Who knows, maybe even you! While trying to find the source of the problem, it can be useful to know which commit exactly introduced the troubles in paradise. That is exactly what Git bisect is for.
Let’s say you know the feature was already broken in the previous commit and 10 commits ago the feature still worked.
Start Git Bisect and inform Git about this:
git bisect start
git bisect bad HEAD^
git bisect good HEAD~10
Git will checkout the commit in the middle, so you can test:
Bisecting: 5 revisions left to test after this (roughly 3 steps)
After testing you inform Git about the result
git bisect good # when the feature worked
git bisect bad # when the feature is b0rken
After you have found the bad commit, reset your repository:
git bisect reset
If you write a script which can verify each commit, you can let Git run it for every commit!
Sometimes you will want to reset your working copy to a clean state, without committing your work, nor deleting it. In this case you can stash your changes:
git stash
git stash save "something fancy i was working on"
This will create something like a commit and hard-reset your working copy.
Later you can retrieve your work:
git stash list # list all your stashes
git stash pop # re-apply the latest stash, this will also delete the stash!
git stash apply # like pop, but do not delete the stash
I often use the stash when I want to git pull --rebase
, while I have uncommitted changes (git will refuse to do it in that case):
git stash
git pull --rebase
git stash pop
This presentation is licensed under the Creative Commons Attribution-Non Commercial-Share Alike 3.0 license
This presentation is available on github: https://github.com/teranex/git-talk
Sources of images and inspiration: