Using Git
Git is a distributed version control system (DVCS), providing speed and power for individual
users while providing flexibility for groups to choose how they want to collaborate.
Setup
The first step is to download and install Git:
You can also visit the official download page at http://git-scm.com/download
for additional options and platforms. Once we've confirmed that Git has been installed, we'll let it know who we
are, and confirm that our settings have been saved:
> git --version
git version 1.8.3.1
> git config --global user.name "Kurt Christensen"
> git config --global user.email kurtc@bitbakery.com
> git config -l
user.name=Kurt Christensen
user.email=kurtc@bitbakery.com
There are additional Git environment variables we could set, but this will be sufficient. These settings are stored
in ~/.gitconfig, which you can edit manually.
Clone an Existing Repository
Git is a distributed version control system, so to start working with an existing repository, we must actually clone that repository into our local development environment.
> git clone https://github.com/projectileboy/Using.git
Cloning into 'Using'...
remote: Counting objects: 94, done.
remote: Compressing objects: 100% (72/72), done.
remote: Total 94 (delta 17), reused 89 (delta 12)
Unpacking objects: 100% (94/94), done.
Here we cloned using an HTTPS connection, but other protocols can be used. Git has its own protocol (communicating on
port 9418), but probably the most common method is to use SSH - using SSH will save you from entering your username
and password in certain circumstances.
Create a Repository
We can also simply create a local repository from scratch:
> mkdir -p ~/alpha/git-repo
> cd alpha/git-repo/
> git init
Initialized empty Git repository in /Users/kurtc/alpha/git-repo/.git/
Note that we could have created our new repo in a directory that already contained files; we simply chose to start
with an empty directory. It bears repeating that we're not somehow missing something just because we created our
repository locally - we have the full power of Git available to us, right on our own machine. For people
unaccustomed to working with distributed version control systems, this can take a while to absorb: for a given
codebase, no Git repo is inherently more or less important than any other repo (in fact, the clone command is simply a combination of init and fetch).
Adding Files
We'll start by creating a file and adding it to our repo, and see what happens:
> echo "a" > a.txt
> git add .
> git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: a.txt
#
Notice that a.txt is no longer untracked - is has been added to the staging
area (also called the index). Whenever we do add, edit, move or remove files in Git,
these changes are not immediately committed to the repo, but exist only in the staging area, which enables us to
pick and choose what exactly gets committed to our local Git repo, and when. The staging area also lets us easily
revert any changes without ever having touched our repo.
We'll learn more about the staging area as we go, but for now, note that git status indicated that it hasn't yet been
saved within our repository, which we can confirm by checking the commit log:
> git log
fatal: bad default revision 'HEAD'
That looks scary, but it isn't surprising. As with other version control systems, we must explicitly commit
any changes we wish to save:
> git commit -m "Added a.txt to master branch in alpha repo"
[master (root-commit) 00d110d] Added a.txt to master branch in alpha repo
1 file changed, 1 insertion(+)
create mode 100644 a.txt
At this point we can verify thet nothing is lingeirng in the staging area, and that a.txt has
actually been committed:
> git status
# On branch master
nothing to commit, working directory clean
> git log
commit 00d110dfb187371c60bcd485c124162361098a3b
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 15:52:09 2013 -0500
Added a.txt to master branch in alpha repo
git status told us what branch we're looking at, what the most recent commit was, and what files
it sees that are 'untracked' - files and or directories that our Git repo is not yet aware of. Let's add a.txt (we'll actually use a shorthand that adds all new or modified files in the current
directory (including subdirectories)):
> echo "b" > b.txt
> git add .
> git commit -m "Added b.txt to master branch in alpha repo"
[master e29b824] Added b.txt to master branch in alpha repo
1 file changed, 1 insertion(+)
create mode 100644 b.txt
> git rm b.txt
rm 'b.txt'
> git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# deleted: b.txt
#
> git commit -m "Removed b.txt from master branch in alpha repo"
[master a880e1a] Removed b.txt from master branch in alpha repo
1 file changed, 1 deletion(-)
delete mode 100644 b.txt
> git log
commit a880e1a62effabc8d2c9f8fcffcdaa6ac9827d84
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 15:54:02 2013 -0500
Removed b.txt from master branch in alpha repo
commit e29b824e8fb2049d76c7c67c159f1dfcd8c9e834
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 15:53:20 2013 -0500
Added b.txt to master branch in alpha repo
commit 00d110dfb187371c60bcd485c124162361098a3b
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 15:52:09 2013 -0500
Added a.txt to master branch in alpha repo
> git reset --hard HEAD~2
HEAD is now at 00d110d Added a.txt to master branch in alpha repo
Valhalla:git-repo kurtc$ git log
commit 00d110dfb187371c60bcd485c124162361098a3b
Author: Kurt Christensen
Date: Thu Sep 5 15:52:09 2013 -0500
Added a.txt to master branch in alpha repo
> git branch new-stuff
> git checkout new-stuff
Switched to branch 'new-stuff'
> git branch
master
* new-stuff
> echo "b" > b.txt
> git add .
> git commit -m "Added b.txt to new-stuff branch in alpha repo"
[new-stuff feeb9fe] Added b.txt to new-stuff branch in alpha repo
1 file changed, 1 insertion(+)
create mode 100644 b.txt
Valhalla:git-repo kurtc$ git checkout master
Switched to branch 'master'
> echo "c" > c.txt
> git add .
> git commit -m "Added c.txt to master branch in alpha repo"
[master 4b93612] Added c.txt to master branch in alpha repo
1 file changed, 1 insertion(+)
create mode 100644 c.txt
> git merge new-stuff -m "Merged new-stuff branch into master"
Merge made by the 'recursive' strategy.
b.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 b.txt
Valhalla:git-repo kurtc$ git log
commit d5834b1e7710eac2779b49ea99ceed1591c6b8e9
Merge: 4b93612 feeb9fe
Author: Kurt Christensen
Date: Thu Sep 5 16:07:20 2013 -0500
Merged new-stuff branch into master
commit 4b93612fafd61298e88e051f540f2c0b6100c01a
Author: Kurt Christensen
Date: Thu Sep 5 16:01:34 2013 -0500
Added c.txt to master branch in alpha repo
commit feeb9fef2fcab00667d97fd35ed4223883fca6cd
Author: Kurt Christensen
Date: Thu Sep 5 15:59:48 2013 -0500
Added b.txt to new-stuff branch in alpha repo
commit 00d110dfb187371c60bcd485c124162361098a3b
Author: Kurt Christensen
Date: Thu Sep 5 15:52:09 2013 -0500
Added a.txt to master branch in alpha repo
> git checkout -b conflicting-changes
Switched to a new branch 'conflicting-changes'
> echo "extra line added to a.txt on conflicting-changes" > a.txt
> git commit -a -m "Modified a.txt on conflicting-changes in alpha repo"
[conflicting-changes ce9f94c] Modified a.txt on conflicting-changes in alpha repo
1 file changed, 1 insertion(+), 1 deletion(-)
> git checkout master
Switched to branch 'master'
> echo "extra line added to a.txt on master" > a.txt
> git commit -a -m "Modified a.txt on master in alpha repo"
[master 18a48d9] Modified a.txt on master in alpha repo
1 file changed, 1 insertion(+), 1 deletion(-)
> git merge conflicting-changes -m "Merged conflicting-changes into master in alpha repo"
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
Automatic merge failed; fix conflicts and then commit the result.
> cat a.txt
<<<<<<< HEAD
extra line added to a.txt on master
=======
extra line added to a.txt on conflicting-changes
>>>>>>> conflicting-changes
> vi a.txt
> cat a.txt
We added this line on master
And we merged in this line from conflicting-changes
> git status
# On branch master
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add ..." to mark resolution)
#
# both modified: a.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
> git commit -a -m "Merged conflicting-changes into master in alpha repo"
[master 668cb00] Merged conflicting-changes into master in alpha repo
> git branch -d conflicting-changes
Deleted branch conflicting-changes (was ce9f94c).
> git branch
* master
> git log
commit 668cb00ded51ea8d764bc5ecc0d777d9a6e68877
Merge: 18a48d9 ce9f94c
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 16:26:33 2013 -0500
Merged conflicting-changes into master in alpha repo
commit 18a48d9f33dfec6c5bf911ca390b3bb9e56f875b
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 16:20:26 2013 -0500
Modified a.txt on master in alpha repo
commit ce9f94ceb39d2d3e1954c3d0511f2137a0392aa7
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 16:19:39 2013 -0500
Modified a.txt on conflicting-changes in alpha repo
commit d5834b1e7710eac2779b49ea99ceed1591c6b8e9
Merge: 4b93612 feeb9fe
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 16:07:20 2013 -0500
Merged new-stuff branch into master
commit 4b93612fafd61298e88e051f540f2c0b6100c01a
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 16:01:34 2013 -0500
Added c.txt to master branch in alpha repo
commit feeb9fef2fcab00667d97fd35ed4223883fca6cd
Author: Kurt Christensen <kurtc@bitbakery.com>
Date: Thu Sep 5 15:59:48 2013 -0500
Valhalla:git-repo kurtc$
Valhalla:~ kurtc$ mkdir -p ~/beta
Valhalla:~ kurtc$ cd beta
Valhalla:beta kurtc$ git clone ~/alpha/git-repo
Cloning into 'git-repo'...
done.
Valhalla:beta kurtc$ cd git-repo/
Valhalla:git-repo kurtc$ git remote
origin
Valhalla:git-repo kurtc$ git remote -v
origin /Users/kurtc/alpha/git-repo (fetch)
origin /Users/kurtc/alpha/git-repo (push)
Valhalla:git-repo kurtc$ ll
total 24
drwxr-xr-x 12 kurtc staff 408 Sep 6 00:34 .git/
-rw-r--r-- 1 kurtc staff 81 Sep 6 00:34 a.txt
-rw-r--r-- 1 kurtc staff 2 Sep 6 00:34 b.txt
-rw-r--r-- 1 kurtc staff 2 Sep 6 00:34 c.txt
Note that our entire commit has been marked with a globally unique identifier. We'll see later that this enables us
to share our work with others, but for now we should note that this reflects a certain philosophy of version control
that differs from Subversion and other file-based systems - that the state of any given file is unimportant; rather
what matters is the state of the entire project at a particular point in time.
Let's review what has happened thus far, because if you don't understand how Git works, it will be hard for you
to ever really use it effectively. When we first created a.txt, it was not part of our Git repo. Running git status
told us this explicitly - Git saw it as an "untracked file". In order to add a.txt to out repo, we added it with git add.
Running git status
Sharing Changes
Any version control system that only lets us work with our local repository will be of limited utility - what we'd
really like is to be able to share our changes with other developers. In order for another developer to contribute
to our project, the first thing they'll need to do is clone our repo. Ordinarily this would be done over a network,
but for now we'll simply clone our repo into a different spot in our filesystem:
> mkdir ~/BETA/
> cd ~/BETA/
> git clone ~/playground/
Cloning into 'playground'...
done.
> ll
total 0
drwxr-xr-x 4 kurtc staff 136 Aug 7 09:31 playground/
When we say that we clone the repository, we mean that quite literally - our new BETA clone of the original
playground repo is (for the moment), identical to the original repo, with all of the same commit history.
(The actual picture is a bit more complicated, in that we cloned a particular branch of the repo (the master, in
this case), ignoring any other branches that might have existed in the original playground repo. We'll look into
this more later, but for now we simplify things, and in any case, for the master branch, we have created a perfect
clone.)
Because we cloned our new repo from the original repo at ~/playground, Git assumes that in the future we will want
to fetch updates from that repo. Consequently, when we clone a repo, Git specifies the source as a remote repository
with which we're connected - more specifically, Git marks it as the origin:
> git remote -v
origin /Users/kurtc/playground/ (fetch)
origin /Users/kurtc/playground/ (push)
The -v flag we passed to the git remote command gives us a little more information (namely, the URL of the remote
repo). An interesting thing to note here is that we can specify additional remote repos. This might seem strange,
but in fact is quite common. For example, developer A and developer B may both be working with clones from a repository
which everyone defines as the central team repo. There may be occasions where developer A and developer B would like
to push and pull changes from one another, before sharing their collective changes with the rest of the team.
To collaborate, we can pull (that is, fetch and merge) each others' changes as they become available. To illustrate,
let's start by adding a new file (b.txt) in the BETA/playground repo:
> vi b.txt
> git add .
> git commit -m "Added all the great stuff in b.txt"
[master ef651bf] Added all the great stuff in b.txt
1 file changed, 2 insertions(+)
create mode 100644 b.txt
And now let's add another file (c.txt) in our original playground repo:
> cd ~/playground
> vi c.txt
> git add .
> git commit -m "Added c.txt from ~/playground"
[master c1d2c78] Added c.txt from ~/playground
1 file changed, 2 insertions(+)
create mode 100644 c.txt
At this point, in order to establish a truly peer-to-peer relationship between our BETA/playground repo and the
original playground repo from which BETA was cloned, we'll explicitly add BETA as a remote repo for the original
playground repo:
> git remote add BETA ~/BETA/playground
> git remote -v
BETA /Users/kurtc/BETA/playground (fetch)
BETA /Users/kurtc/BETA/playground (push)
git pull fetches any changes (since the last time we pulled) from a remote repo, and attempts to automatically
merge those changes into our local repo. By default, git pull will look for a remote named 'origin', and will pull
> git pull BETA master
From /Users/kurtc/BETA/playground
* branch master -> FETCH_HEAD
Merge made by the 'recursive' strategy.
b.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 b.txt
> ll
total 24
drwxr-xr-x 15 kurtc staff 510 Aug 7 14:42 .git/
-rw-r--r-- 1 kurtc staff 29 Aug 6 11:36 a.txt
-rw-r--r-- 1 kurtc staff 38 Aug 7 14:40 b.txt
-rw-r--r-- 1 kurtc staff 32 Aug 7 14:39 c.txt
> git status
# On branch master
nothing to commit, working directory clean
> git log
commit acba5be8ee980d40e5a9b245979630de650e3180
Merge: c1d2c78 ef651bf
Author: Kurt Christensen <kurt.j.christensen@gmail.com>
Date: Wed Aug 7 14:40:33 2013 -0500
Merge branch 'master' of /Users/kurtc/BETA/playground
Merging changes from BETA/playground into the original playground.
At this point, we expect that to be b.txt. At this point, we also
expect to have c.txt in the original playground repo, and NOT in
the BETA/playground repo.
commit c1d2c785b6d2d95d1481ac039092d9d7a7e8dc84
Author: Kurt Christensen <kurt.j.christensen@gmail.com>
Date: Wed Aug 7 14:39:32 2013 -0500
Added c.txt from ~/playground
commit ef651bfd815e80286b4caf1e6546129ba359b954
Author: Kurt Christensen <kurt.j.christensen@gmail.com>
Date: Wed Aug 7 13:20:24 2013 -0500
Added all the great stuff in b.txt
commit 4366c6a8c9d3066b07cddd550bb874d14b959abd
Author: Kurt Christensen <kurt.j.christensen@gmail.com>
Date: Tue Aug 6 11:56:12 2013 -0500
Adding our first file to the repo
> cd ~/BETA/playground/
> git pull
From /Users/kurtc/playground
4366c6a..acba5be master -> origin/master
Updating ef651bf..acba5be
Fast-forward
c.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 c.txt
> vi a.txt
> cd ~/playground/
> git add .
> git commit -m "Modified a.txt in the original playground repo"
[master 9070f9d] Modified a.txt in the original playground repo
1 file changed, 2 insertions(+)
> cd ~/BETA/playground/
> vi a.txt
> git add .
> git commit -m "Modified a.txt in the BETA/playground repo"
[master 2b4635a] Modified a.txt in the BETA/playground repo
1 file changed, 2 insertions(+)
> git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/kurtc/playground
acba5be..9070f9d master -> origin/master
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
Automatic merge failed; fix conflicts and then commit the result.
> git status
# On branch master
# Your branch and 'origin/master' have diverged,
# and have 1 and 1 different commit each, respectively.
# (use "git pull" to merge the remote branch into yours)
#
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
#
# both modified: a.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
> git diff
diff --cc a.txt
index 77df6ba,efcd7b0..0000000
--- a/a.txt
+++ b/a.txt
@@@ -1,4 -1,4 +1,8 @@@
This is a simple text file.
++<<<<<<< HEAD
+This line was added in the BETA/playground repo
++=======
+ This line was added in the original playground repo.
++>>>>>>> 9070f9d8bcfcd2c1ca3041c981104197ac1c62a4
a.txt is now in a conflicted state, and is marked up accordingly (similar to SVN):
> more a.txt
This is a simple text file.
<<<<<<< HEAD
This line was added in the BETA/playground repo
=======
This line was added in the original playground repo.
>>>>>>> 9070f9d8bcfcd2c1ca3041c981104197ac1c62a4
Resolving Conflicts
Defining Workflow
The very nature of a centralized version control system such as SVN or Perforce encourages a certain workflow - how
team members share code in order to create a single coherent release. With Git however, we must consciously choose
our team workflow, because no Git repo is inherently more or less important than any other Git repo. The peer-to-peer
workflow we've been using so far may be fine for a team of two, but will quickly become unwieldy as our team grows.
Thus, a more typical workflow is to set up a hub-and-spoke relationship between developers and teams:
In this workflow, the hubs could be developer repos, but this is probably a bad idea - in order to share changes,
the hub developer would have to pull changes from all of her other team members (and know when to do so), and then
notify all the other developers that they should, in turn, pull her repo to pick up any changes.
Headless repos do not have local copies of the actual files, and therefore do not have a staging area. This enables
developers to push changes from their local repo to the shared repo - changes which in turn can be picked up by
the other team members.
> mkdir ~/headless
> cd headless
> git init --bare
Initialized empty Git repository in /Users/kurtc/headless/
Now we must hydrate our headless hub repo. (Note that we could have also done this with git clone --bare)
> cd ~/playground/
> git remote -v
BETA /Users/kurtc/BETA/playground (fetch)
BETA /Users/kurtc/BETA/playground (push)
> git remote add origin ~/headless
> git push origin master
Counting objects: 14, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (14/14), 1.48 KiB | 0 bytes/s, done.
Total 14 (delta 2), reused 3 (delta 0)
To /Users/kurtc/headless
* [new branch] master -> master
By convention, 'origin' points to the primary source from which we pull others' changes, and to which we push our
changes to be pulled in by others. Thus, let's re-point the BETA repo's 'origin' to point to our new hub:
> cd ~/BETA/playground/
> git remote set-url origin ~/headless
> git remote -v
origin /Users/kurtc/headless (fetch)
origin /Users/kurtc/headless (push)
Removing Files
But back to work. Unfortunately, we accidentally committed our *.iws file, which contains user-specific IntelliJ
project desktop settings, and we'd like to remove it from our Git repo - without deleting the file itself:
> git rm --cached test.iws
rm 'test.iws'
> git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: � �test.iws
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# test.iws
Just as we must explicitly commit any file additions or modifications, so we must commit our removal:
> git commit -m "Removed file which I had accidentally committed"
[master]: created 0df874f: "Removed file which I had accidentally committed"
1 files changed, 0 insertions(+), 525 deletions(-)
delete mode 100644 test.iws
Changing Your Mind
> git reset
src/test.arc: locally modified
src/test.clj: locally modified
> git status
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: src/test.arc
# modified: src/test.clj
#
no changes added to commit (use "git add" and/or "git commit -a")
Just Ignore It
To avoid this problem in the future, we need a way to tell Git to ignore any *.iws files it finds
in this project, which we can do with a .gitignore file:
In vi (or whichever text editor you prefer) we can edit the file to add specific files or else filname patterns)
which we would like our Git repository to ignore:
# gitignore file for the 'test' project
*.iws
~
~
We save the file, and then add and commit it to our Git repo:
> git add .gitignore
> git commit -m "Added .gitignore file for the project"
[master]: created ad9ce7a: "Added .gitignore file for the project"
1 files changed, 2 insertions(+), 0 deletions(-)
create mode 100644 .gitignore
> git checkout test.arc
> git status
# On branch master
# Changed but not updated:
# � (use "git add <file>..." to update what will be committed)
# � (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: � test.clj
#
no changes added to commit (use "git add" and/or "git commit -a")
> git checkout test.clj�
> git status
# On branch master
nothing to commit (working directory clean)
> git checkout -- test.arc
> ls
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 22:37 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 22:37 test.clj
> git mv src/test.clj src/tutorial.clj
> git status
# On branch master
# Changes to be committed:
# � (use "git reset HEAD <file>..." to unstage)
#
# renamed: � �src/test.clj -> src/tutorial.clj
#
> ls src
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 22:57 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 22:37 tutorial.clj
> git checkout -- src/tutorial.clj�
> ls src
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 22:57 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 22:37 tutorial.clj
> git status
# On branch master
# Changes to be committed:
# � (use "git reset HEAD <file>..." to unstage)
#
# renamed: � �src/test.clj -> src/tutorial.clj
#
> git checkout -- src/test.clj
error: pathspec 'src/test.clj' did not match any file(s) known to git.
> git checkout
D src/test.clj
A src/tutorial.clj
> ls
total 104
drwxr-xr-x � 8 kurtc �kurtc � �272 Apr �3 07:53 ./
drwxr-xr-x �76 kurtc �kurtc � 2584 Mar 10 01:04 ../
drwxr-xr-x �13 kurtc �kurtc � �442 Apr �5 22:59 .git/
-rw-r--r-- � 1 kurtc �kurtc � � 46 Apr �1 14:08 .gitignore
drwxr-xr-x � 4 kurtc �kurtc � �136 Apr �5 22:57 src/
-rw-r--r-- � 1 kurtc �kurtc � �446 Feb �4 �2009 test.iml
-rw-r--r-- � 1 kurtc �kurtc �12600 Dec �1 01:09 test.ipr
-rw-r--r-- � 1 kurtc �kurtc �27115 Jan 26 00:25 test.iws
> cd src
> ls
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 22:57 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 22:37 tutorial.clj
> rm tutorial.clj�
> git status
# On branch master
# Changes to be committed:
# � (use "git reset HEAD <file>..." to unstage)
#
# renamed: � �test.clj -> tutorial.clj
#
# Changed but not updated:
# � (use "git add/rm <file>..." to update what will be committed)
# � (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: � �tutorial.clj
#
> git reset -- test.clj
src/test.clj: locally modified
src/tutorial.clj: locally modified
> ls
total 8
drwxr-xr-x �3 kurtc �kurtc �102 Apr �5 23:00 ./
drwxr-xr-x �8 kurtc �kurtc �272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc �425 Apr �5 22:35 test.arc
> git checkout test.clj
> ls
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 23:01 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 23:01 test.clj
> git status
# On branch master
# Changes to be committed:
# � (use "git reset HEAD <file>..." to unstage)
#
# new file: � tutorial.clj
#
# Changed but not updated:
# � (use "git add/rm <file>..." to update what will be committed)
# � (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: � �tutorial.clj
#
> ls
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 23:01 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 23:01 test.clj
> git reset --hard
HEAD is now at ad9ce7a Added .gitignore file for the project
> ls
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 23:01 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 23:01 test.clj
> git status
# On branch master
nothing to commit (working directory clean)
> git branch experimental
> git branch
��experimental
* master
> git checkout experimental
Switched to branch "experimental"
> ls
total 104
drwxr-xr-x � 8 kurtc �kurtc � �272 Apr �3 07:53 ./
drwxr-xr-x �76 kurtc �kurtc � 2584 Mar 10 01:04 ../
drwxr-xr-x �13 kurtc �kurtc � �442 Apr �5 23:24 .git/
-rw-r--r-- � 1 kurtc �kurtc � � 46 Apr �1 14:08 .gitignore
drwxr-xr-x � 4 kurtc �kurtc � �136 Apr �5 23:01 src/
-rw-r--r-- � 1 kurtc �kurtc � �446 Feb �4 �2009 test.iml
-rw-r--r-- � 1 kurtc �kurtc �12600 Dec �1 01:09 test.ipr
-rw-r--r-- � 1 kurtc �kurtc �27115 Jan 26 00:25 test.iws
> cd src
> ls
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 23:01 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 425 Apr �5 22:35 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 23:01 test.clj
> vi test.arc
> git status
# On branch experimental
# Changed but not updated:
# � (use "git add <file>..." to update what will be committed)
# � (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: � test.arc
#
no changes added to commit (use "git add" and/or "git commit -a")
> git checkout master
M src/test.arc
Switched to branch "master"
> vi test.arc
> git checkout experimental
M src/test.arc
Switched to branch "experimental"
> less test.arc�
> git status
# On branch experimental
# Changed but not updated:
# � (use "git add <file>..." to update what will be committed)
# � (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: � test.arc
#
no changes added to commit (use "git add" and/or "git commit -a")
> git add .
> git commit -m "Committed on the experimental branch"
[experimental]: created 730831c: "Committed on the experimental branch"
�1 files changed, 3 insertions(+), 1 deletions(-)
> git branch
* experimental
��master
> git checkout master
Switched to branch "master"
> git commit -am "Committed change on the master branch"
[master]: created 413c0bd: "Committed change on the master branch"
�1 files changed, 3 insertions(+), 1 deletions(-)
> git status
# On branch master
nothing to commit (working directory clean)
> git status
# On branch master
nothing to commit (working directory clean)
> git branch
��experimental
*�master
> git merge experimental
Auto-merging src/test.arc
CONFLICT (content): Merge conflict in src/test.arc
Automatic merge failed; fix conflicts and then commit the result.
> ls
total 16
drwxr-xr-x �4 kurtc �kurtc � 136 Apr �5 23:38 ./
drwxr-xr-x �8 kurtc �kurtc � 272 Apr �3 07:53 ../
-rw-r--r-- �1 kurtc �kurtc � 582 Apr �5 23:38 test.arc
-rw-r--r-- �1 kurtc �kurtc �1921 Apr �5 23:01 test.clj
> vi test.arc
> git merge experimental
fatal: You are in the middle of a conflicted merge.
> git merge -summary
fatal: You are in the middle of a conflicted merge.
> git status
src/test.arc: needs merge
# On branch master
# Changed but not updated:
# � (use "git add <file>..." to update what will be committed)
# � (use "git checkout -- <file>..." to discard changes in working directory)
#
# unmerged: � test.arc
#
no changes added to commit (use "git add" and/or "git commit -a")
> git commit -am "Resolved merge conflict from the 'experimental' branch"
[master]: created 4bbe17d: "Resolved merge conflict from the 'experimental' branch"
> git merge experimental
Already up-to-date.
> git branch -d experimental
Deleted branch experimental (730831c).
git pull
- merging, rebasing, etc.
git push
Simple Collaboration: Pull and Push
Now let's assume we want to push our changes to our team's shared Git repository. We need to get our changes there
because this is the repo against which the continuous integration server runs, and which produces builds that are
actually used by people outside of our dev team.
> git
WTF? Why is Git complaining? Well, the problem is that someone else pushed some changes into the team's shared repo,
and when that happened, a new commit was created in that repo. That commit has its very own SHA-1 identifer
(remember?), and that SHA-1 identifier does NOT exist in your local repo. Consequently, Git doesn't know where to
put your changes. (Possible analogy: Imagine that each commit is a piece of paper in a stack of paper. )
So the *first* thing we'll do to share our stuff with everyone else is we'll first pull the latest stuff from our
team's shared repo, where� pull� is really just a combined� fetch� and� merge:
> git pull
Note that we could have merged in changes from any repo. For example, perhaps I'm working on a feature for which I
need Alyssa's code, but I don't yet want to worry about merging in anyone else's changes. Then I could simply pull
from Alyssa's repo:
> git pull (buhhkjhkjhklhkljhlkjlkjh)
And when I'm finally ready to push my changes and Alyssa's changes to our team's shared repo, everything will "just
work", (assuming no one else has pushed any changes to our team's shared repo since our last pull):
> git push
Again, note that we don't need to specify the team's shared repo, since that's the repo from which we originaly
cloned our own local repo.
So that's the simple case. But what happens when we pull from a repo that contains changes which conflict withour
own local changes?
(TODO)
(TODO - Describe flattening� stuff so that your oddball local branches don't get pushed to the shared repo)
- Commits and SHA-1 identifiers
- Fast forward merges
Reeling In The Years
Git provides a variety of options for searching through our commit log:
> git log --since=yesterday
commit 4366c6a8c9d3066b07cddd550bb874d14b959abd
Author: Kurt Christensen <kurt.j.christensen@gmail.com>
Date: Tue Aug 6 11:56:12 2013 -0500
Adding our first file to the repo
Simple Branching and Merging
We can create branches (for research projects, released versions, etc.) just as we can in any other source control
system. Ordinarily, we are working within the trunk, labeled "master". We can create a new branch labeled
"version-3" like so:
> git branch version-3
> git branch
version-3
* master
Note that git branch listed out all the branches in the repo, with a little asterisk next to the current branch.
Note that creating a branch didn't automatically move us to that branch. We can totally annihilate our new branch
like so:
> git branch -D version-3
> git branch
* master
Go ahead and create a new branch again:
> git branch research-project
> git checkout research-project
> git branch
* research-project
master
We are now working in the branch "version-3". If we make a change to a file, the change will be visible in
"version-3", but not in the trunk, which we can see if we move back to the trunk:
> git checkout master
We can merge changes from the main line into our "version-3" branch (for example, to keep a research branch sync'ed
up with changes in the main line as much as possible):
> git buh
...or we can merge changes from "version-3" down into main line (this is what we normally do if we're fixing bugs on
a released version, and we want to try and apply the same bug fixes back down to the main line):
> git goo
As of right now, no one else yet knows about the existence of our "research-project" branch. But I can pull updates
from our shared team repo, and I'll get any mainline changes merged in. Also, I'll see any branches that anyone else
has pushed into our team's shared repo:
> git goo
Similarly, I can push my changes into the shared repo, and people will now see my "research-branch"
But what if I don't want people to see my "research-branch"? Can I push stuff from a local branch to a remote
branch? Just how fine-grained and crazy can we get?
Simple Maintenance
Validate that our repo isn't messed up:
> git fsck
Checking object directories: 100% (256/256), done.
Clean up the repo:
> git gc
Counting objects: 3, done.
Writing objects: 100% (3/3), done.
Total 3 (delta 0), reused 0 (delta 0)
REALLY clean up the repo (TODO: What's the difference??):
Etc.
D'oh! I messed up the commit message! Let's fix that...
> git commit --amend
This opens up our default text editor (vi, in my case) so that we can edit the previous commit message:
Git Internals
> ll .git
total 40
COMMIT_EDITMSG
HEAD
config
description
hooks/
index
info/
logs/
objects/
refs/
> vi .git/HEAD
> vi .git/COMMIT_EDITMSG
> vi .git/config
> vi .git/description
> vi .git/index
Valhalla:playground kurtc$ ll .git/hooks/
total 80
-rwxr-xr-x 1 kurtc staff 452 Aug 6 11:25 applypatch-msg.sample
-rwxr-xr-x 1 kurtc staff 896 Aug 6 11:25 commit-msg.sample
-rwxr-xr-x 1 kurtc staff 189 Aug 6 11:25 post-update.sample
-rwxr-xr-x 1 kurtc staff 398 Aug 6 11:25 pre-applypatch.sample
-rwxr-xr-x 1 kurtc staff 1704 Aug 6 11:25 pre-commit.sample
-rw-r--r-- 1 kurtc staff 1348 Aug 6 11:25 pre-push.sample
-rwxr-xr-x 1 kurtc staff 4951 Aug 6 11:25 pre-rebase.sample
-rwxr-xr-x 1 kurtc staff 1239 Aug 6 11:25 prepare-commit-msg.sample
-rwxr-xr-x 1 kurtc staff 3611 Aug 6 11:25 update.sample
Valhalla:playground kurtc$ vi .git/hooks/pre-commit.sample
Valhalla:playground kurtc$ ll .git/info/
total 8
-rw-r--r-- 1 kurtc staff 240 Aug 6 11:25 exclude
Valhalla:playground kurtc$ vi .git/info/exclude
Valhalla:playground kurtc$ ll .git/logs/
total 8
-rw-r--r-- 1 kurtc staff 199 Aug 6 11:56 HEAD
drwxr-xr-x 3 kurtc staff 102 Aug 6 11:56 refs/
Valhalla:playground kurtc$ ll .git/logs/refs/
total 0
drwxr-xr-x 3 kurtc staff 102 Aug 6 11:56 heads/
Valhalla:playground kurtc$ ll .git/logs/refs/heads/
total 8
-rw-r--r-- 1 kurtc staff 199 Aug 6 11:56 master
Valhalla:playground kurtc$ vi .git/logs/refs/heads/master
Valhalla:playground kurtc$ ll .git/objects/
total 0
drwxr-xr-x 3 kurtc staff 102 Aug 6 11:56 43/
drwxr-xr-x 4 kurtc staff 136 Aug 6 11:56 57/
drwxr-xr-x 2 kurtc staff 68 Aug 6 11:25 info/
drwxr-xr-x 2 kurtc staff 68 Aug 6 11:25 pack/
Valhalla:playground kurtc$ ll .git/objects/43/
total 8
-r--r--r-- 1 kurtc staff 151 Aug 6 11:56 66c6a8c9d3066b07cddd550bb874d14b959abd
Valhalla:playground kurtc$ ll .git/objects/57
total 16
-r--r--r-- 1 kurtc staff 49 Aug 6 11:56 0a2a2340e8423e39098a9daabaaa173385aa64
-r--r--r-- 1 kurtc staff 43 Aug 6 11:41 b015418629c0453f3810c709994e1b5057c97c
Valhalla:playground kurtc$ ll .git/objects/info/
Valhalla:playground kurtc$ ll .git/objects/pack/
Valhalla:playground kurtc$ ll .git/refs/
total 0
drwxr-xr-x 3 kurtc staff 102 Aug 6 11:56 heads/
drwxr-xr-x 2 kurtc staff 68 Aug 6 11:25 tags/
Valhalla:playground kurtc$ ll .git/refs/heads/
total 8
-rw-r--r-- 1 kurtc staff 41 Aug 6 11:56 master
Valhalla:playground kurtc$ vi .git/refs/heads/master
Valhalla:playground kurtc$ ll .git/refs/tags/
Using GitHub
GitHub (http://github.com) is probably the single largest contributing factor to the widespread adoption
of Git. Beyond hosting repositories
Pull Requests
Forking a GitHub Repo
Using Git is much easier if you understand how Git views the world. Git does not think about files, changes,
branches and merges the same way as many other version control systems.
With Git, when we think of checkouts, updates, adds, removes, and commits, we think of these things relative to our
own personal Git repo (which is often local, but doesn't need to be). And when we want to interact with the rest of
the world, we think in terms of merging repositories. So, to get working on a project for the first time, you'll
clone someone else's repository.
Now, when you're working as part of a team, your team may have some notion of a "central repository" in order to
coordinate your work, but this will only be by convention - there is nothing inherent in Git that makes any
repository more or less important than any other repository. Let that sink in a second, because it's big - in the
world of Git, everyone has a complete repository with a complete history, and no repository is inherently more
important than any other repository.