Books / Introduction to C Programming Language / Chapter 5
Introduction to Git
When you are programming, you will make mistakes. If you program long enough, these will eventually include true acts of boneheadedness like accidentally deleting all of your source files. You are also likely to spend some of your time trying out things that don’t work, at the end of which you’d like to go back to the last version of your program that did work. All these problems can be solved by using a version control system.
There are many version control systems to choose from: rcs
, cvs
, svn
, hg
, and git
. git
is the most popular and we’ll cover that in this course. A brief summary of git
is given below.
What is Git?
Git is a software for tracking changes in any set of files, usually used for coordinating work among programmers collaboratively developing source code during software development. It was originally authored by Linus Torvalds in 2005 for development of the Linux kernel.
(Optional) Register for GitHub
GitHub provides hosting for your code and version control using Git. It offers the distributed version control and source code management (SCM) functionality of Git, plus its own features. It provides access control and several collaboration features such as bug tracking, feature requests, task management, continuous integration, and wikis for every project. You don’t need a GitHub account to follow the examples in this chapter because we are going to work with the local repo and not going to push any changes to a GitHub remote repo.
Setting up Git on your machine
Typically you run git
inside a directory that holds some project you are working on (say, hw1
). Before you can do anything with git
, you will need to create the repository, which is a hidden directory .git
that records changes to your files:
$ mkdir git-demo
$ cd git-demo
$ git init
Initialized empty Git repository in /home/classes/cs223/class/aspnes.james.ja54/git-demo/.git/
Now let’s create a file and add it to the repository:
$ echo 'int main(int argc, char **argv) { return 0; }' > tiny.c
$ git add tiny.c
The git status
command will tell us that Git knows about tiny.c
, but hasn’t committed the changes to the repository yet:
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: tiny.c
#
The git commit
command will commit the actual changes, along with a message saying what you did. For short messages, the easiest way to do this is to include the message on the command line:
$ git commit -a -m "add very short C program"
[master (root-commit) 5393616] add very short C program
Committer: James Aspnes <[email protected]>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:
git config --global user.name "Your Name"
git config --global user.email [email protected]
If the identity used for this commit is wrong, you can fix it with:
git commit --amend --author='Your Name <[email protected]>'
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 tiny.c
The -a
argument tells Git to include any changes I made to files it already knows about. The -m
argument sets the commit message.
Because this is the first time I ever did a commit, and because I didn’t tell Git who I was before, it complains that its guess for my name and email address may be wrong. It also tells me what to do to get it to shut up about this next time:
$ git config --global user.name "James Aspnes"
$ git config --global user.email "[email protected]"
$ git commit --amend --author="James Aspnes <[email protected]>" -m "add a very short C program"
[master a44e1e1] add a very short C program
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 tiny.c
Note that I repeated the -m
business to git commit --amend
; if I hadn’t, it would have run the default editor (vim
) to let me edit my commit message. If I don’t like vim
, I can change the default using git config --global core.editor
, e.g.:
$ git config --global core.editor "emacs -nw"
I can see what commits I’ve done so far using git log
:
$ git log
commit a44e1e195de4ce785cd95cae3b93c817d598a9ee
Author: James Aspnes <[email protected]>
Date: Thu Dec 29 20:21:21 2011 -0500
add a very short C program
Editing files
Suppose I edit tiny.c
using my favorite editor to turn it into the classic hello-world program:
#include <stdio.h>
int
main(int argc, char **argv)
{
puts("hello, world");
return 0;
}
I can see what files have changed using git status
:
$ 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: tiny.c
#
no changes added to commit (use "git add" and/or "git commit -a")
Notice how Git reminds me to use git commit -a
to include these changes in my next commit. I can also do git add tiny.c
if I just want include the changes to tiny.c
(maybe I made changes to a different file that I want to commit separately), but usually that’s too much work.
If I want to know the details of the changes since my last commit, I can do git diff
:
$ git diff
diff --git a/tiny.c b/tiny.c
index 0314ff1..f8d9dcd 100644
--- a/tiny.c
+++ b/tiny.c
@@ -1 +1,8 @@
-int main(int argc, char **argv) { return 0; }
+#include <stdio.h>
+
+int
+main(int argc, char **argv)
+{
+ puts("hello, world");
+ return 0;
+}
Since I like these changes, I do a commit:
$ git commit -a -m "expand previous program to hello world"
[master 13a73be] expand previous program to hello world
1 files changed, 8 insertions(+), 1 deletions(-)
Now there are two commits in my log:
$ git log | tee /dev/null
commit 13a73bedd3a48c173898d1afec05bd6fa0d7079a
Author: James Aspnes <[email protected]>
Date: Thu Dec 29 20:34:06 2011 -0500
expand previous program to hello world
commit a44e1e195de4ce785cd95cae3b93c817d598a9ee
Author: James Aspnes <[email protected]>
Date: Thu Dec 29 20:21:21 2011 -0500
add a very short C program
Renaming files
You can rename a file with git mv
. This is just like regular mv
, except that it tells Git what you are doing.
$ git mv tiny.c hello.c
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# renamed: tiny.c -> hello.c
#
These changes don’t get written to the repository unless you do another git commit
:
$ git commit -a -m "give better name to hello program"
[master 6d2116c] give better name to hello program
1 files changed, 0 insertions(+), 0 deletions(-)
rename tiny.c => hello.c (100%)
Adding and removing files
To add a file, create it and then call git add
:
$ cp hello.c goodbye.c
$ git status
# On branch master
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# goodbye.c
nothing added to commit but untracked files present (use "git add" to track)
$ git add goodbye.c
$ git commit -a -m "we need a second program to say goodbye"
[master 454b24c] we need a second program to say goodbye
1 files changed, 8 insertions(+), 0 deletions(-)
create mode 100644 goodbye.c
To remove a file, use git rm
:
$ git rm goodbye.c
$ git status
# On branch master
# 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: goodbye.c
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m "no, goodbye.c was a bad idea"
[master defa0e0] no, goodbye.c was a bad idea
1 files changed, 0 insertions(+), 8 deletions(-)
delete mode 100644 goodbye.c
Recovering files from the repository
If you make a mistake, you can back out using the repository. Here I will delete my hello.c
file and then get it back using git checkout -- hello.c
:
$ rm hello.c
$ ls
$ git checkout -- hello.c
$ ls
hello.c
I can also get back old versions of files by putting the commit id before the --
:
$ git checkout a44e1 -- tiny.c
$ ls
hello.c tiny.c
The commit id can be any unique prefix of the ridiculously long hex name shown by git log
.
Having recovered tiny.c
, I will keep it around by adding it to a new commit:
$ git commit -a -m "keep tiny.c around"
[master 23d6219] keep tiny.c around
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 tiny.c
Undoing bad commits
Suppose I commit a change that I didn’t want to make. For example, let’s suppose I decide to add some punctuation to the greeting in hello.c
but botch my edit:
$ vim hello.c
$ git commit -a -m "add exclamation point"
[master f40d8d3] add exclamation point
1 files changed, 1 insertions(+), 1 deletions(-)
Only now does it occur to me to test my program:
$ gcc -o hello hello.c
$ ./hello
hello, wolrd!
Disaster!
I can use git diff
to see what went wrong. The command below compares the current working directory to HEAD^
, the commit before the most recent commit:
$ git diff HEAD^ | tee /dev/null
diff --git a/hello.c b/hello.c
index f8d9dcd..dc227a8 100644
--- a/hello.c
+++ b/hello.c
@@ -3,6 +3,6 @@
int
main(int argc, char **argv)
{
- puts("hello, world");
+ puts("hello, wolrd!");
return 0;
}
And I see my mistake leaping out at me on the new line I added (which git diff
puts a +
in front of). But now what do I do? I already commited the change, which means that I can’t get it out of the history.
Instead, I use git revert
on HEAD
, the most recent commit:
$ git revert HEAD
[master fca3166] Revert "add exclamation point"
1 files changed, 1 insertions(+), 1 deletions(-)
(Not shown here is where it popped up a vim
session to let me edit the commit message; I just hit :x
Now everything is back to the way it was before the bad commit:
$ ./hello
hello, world
Looking at old versions
Running git log
will now show me the entire history of my project, newest commits first:
fca3166a697c6d72fb9e8aec913bb8e36fb5fe4e Revert "add exclamation point"
f40d8d386890103abacd0bf4142ecad62eed5aeb add exclamation point
23d6219c9380ba03d9be0672f0a7b25d18417731 keep tiny.c around
defa0e0430293ca910f077d5dd19fccc47ab0521 no, goodbye.c was a bad idea
454b24c307121b5a597375a99a37a825b0dc7e81 we need a second program to say goodbye
6d2116c4c72a6ff92b8b276eb88ddb556d1b8fdd give better name to hello program
13a73bedd3a48c173898d1afec05bd6fa0d7079a expand previous program to hello world
a44e1e195de4ce785cd95cae3b93c817d598a9ee add a very short C program
If I want to look at an old version (say, after I created goodbye.c
), I can go back to it using git checkout
:
$ git checkout 454b2
Note: checking out '454b2'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at 454b24c... we need a second program to say goodbye
$ ls
goodbye.c hello hello.c
Now I have both goodbye.c
and hello.c
, as well as my compiled program hello
, since I didn’t tell Git about it. Note that I also got lots of horrendous warnings about the fact that I am living in the past and shouldn’t expect to make any permanent changes here.
To go back to the last commit, use git checkout master
:
$ git checkout master
Previous HEAD position was 454b24c... we need a second program to say goodbye
Switched to branch 'master'
$ ls
hello hello.c tiny.c
More information about Git
For a visual walkthrough of Git, please read Git tutorial.
All Git commands take a --help
argument that brings up their manual page. There is also extensive documentation at http://git-scm.com.