Git

Setup - Download, Install and Config
git init - Create a new repo
git add - Adding and editing files
git commit - Committing your changes
git rm - Removing files
git mv - Moving and renaming files
git reset - Reverting changes
How Git views the world
git branch
git checkout
git diff
git merge
Resolving Conflicts
git rebase
git stash
git tag (show graph!)
Developer Workflow
git clone
git remote
git fetch
git pull
Team Workflow
git init --bare
git push
Organizational Workflow
git bisect
git blame
git fsck
git gc

GitHub

Pull Request
Fork
Organizational Workflow with GitHub

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:

Windows http://msysgit.github.com
Linux apt-get install git (or whichever package manager you use)
Mac OS X brew install git (assuming you already have Homebrew)

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:

> vi .gitignore

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??):

> git gc --prune
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.