I must admit, I am one of those who had their brains formatted by CVS. It was darn difficult to understand Git and its benefits. I figure there are many like me out there. This is why I decided to write a Git tutorial series from my view point. Also, I apply Git to a smaller scale project than say, Linux kernel with a massive number of developers.
My knowledge of Git is nowhere near being at an expert level. I’m sure I will say something that is stupid. So please bear this in mind in your comments. Please help me make this series better.
I found a few things really helpful when learning about Git:
- I found this book on Git very helpful. It’s completely free.
- Whether you are following this tutorial or reading the book above, always actually do the steps. Just reading about Git will get your brains confused really quickly. Actually doing the steps will help you a lot.
- The best way to learn about Git is to use the command line tools. IDEs like Xcode and Eclipse have some level of Git support. But, as a beginner, they really messed me up.
In Part I, we will focus on code committing. We will get to working with branches in future parts.
Please make sure that you have a working Git command line system before proceeding. The book mentioned above has clear steps on installation in Linux, Windows and Mac (I am using Mac to write these articles).
What is a Repository?
In CVS there is only one repository – the one that sits in the central server. Every developer has a working copy of the files in a local folder.
In Git, typically, each developer has a local repository. This is where code is committed frequently. Since this is a local repo, you don’t have to be super careful that the code works or even compiles:-). Basically, you are backing your work up periodically. In case of a bad mistake, you can always get things back from the local repo.
Once you are happy with the code, you can push the changes to a central repository. When pushing your code to the central repo, you can choose to include the history of every local commits. Or, better yet, you can compress all local commits into a single commit. This is better. Because, other developers don’t need to know about every little local commits you have done. We will get to this later.
Creating a Local Repository
There are two ways you can create a local repository:
- Create a brand new repository for a new project. This is usually done only once in the life of a project.
- For existing projects, create a clone of a remote repository in your local hard drive.
Let’s first learn to create a brand new local repository. Xcode and Eclipse provides ways to create a new local repo for any project. But, as I said, let’s not use the IDEs for this yet.
Create a folder structure for your project. If you are using Xcode or Eclipse, just create the project using the IDE tools.
Open a command prompt window and go to the root folder of the project. (Xcode creates many folders with similar sounding names. The root folder contains the PROJECT.xcodeproj folder. So, yeah, it’s really the root folder.)
Run this command to create a new local repository for the project.
git init
The repository is physically created in the .git folder within the root. If you regularly backup your hard drive, don’t forget to include that folder.
Now, let’s see how to create a local repo by cloning a remote one. This is something you need to do to contribute code to a project that already exists.
Using the command prompt, go to any folder. It should not be initialized using git init (which is only used to create a brand new repo). Then run the git clone command with the URL to the remote repo. For example, try this:
git clone git://github.com/bibhas2/ThreadControl.git
This will create a folder for the project within where you are right now. This folder will have the repository in it (in .git).
Committing Files
Commit is a multi step process. New or modified files are first added to a list of files to be committed. This is called staging. Then you do the commit. Also, as I said, commits are first done to a local repo. Changes are finally pushed to a remote repo.
This type of multi-stage commit process really helps developers experiment freely.
Let’s get started. Create a bunch of files in your project’s folder. If you are using Xcode or Eclipse, this is already done for you.
From the command prompt, use the git add command to add files to the staging area (list of files ready for commit). For example, running this command from the root folder will add all modified files to the staging area:
git add .
Alternatively, you can selectively add files or folders to commit list. If you specify a folder, all subfolders are recursively traversed and all files are added to the staging area.
git add file.c
git add src/
Note: Every time you modify a file, you will need to schedule it for commit using git add. This is nothing like the cvs add command. With git add, you are not adding a file to the repository. You are adding it to a list of files ready for commit.
If you have deleted a file, you can schedule that change for commit using git rm command.
git rm test.c
Before you go ahead and commit, you should probably see what exactly will be committed. This can be done using the git status command. A file can be in any one of these states:
- A file was created, modified or deleted and has been staged for commit. Only these changes will be committed.
- A file was modified or deleted but has not been staged for commit. These changes will not be committed.
- A file has been created but has not been staged for commit. Git is not even tracking this file in anyway.
Let’s see an example git status output. You can run the command from any folder within the project. The output will be the same.
git status
A sample output will be:
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: test.c
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: ../README
# modified: another.c
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# new.c
The file test.c was deleted and was scheduled for commit (using git rm). Only this change will be committed. Files README and another.c have been modified but have not been scheduled for commit. The file new.c was created but never scheduled for commit. Git is not tracking this file in any way.
Finally, to commit the changes, use the git commit command.
git commit
git commit -m "A commit message"
Carefully staging each modified file may be tedious. Especially, if you have made mass changes. In that case, do an auto staging using the -a switch.
git commit -a
This will automatically add all modified files to the staging area and remove deleted files prior to doing a commit.
Things to Consider
What gets staged is not so much a file but changes to a file. Consider the scenario:
- You modify a file.
- Stage the file for commit.
- Make more changes to the file.
- Commit the file. This will only commit the contents of the file as it was when it was staged.
Also, in CVS, a single repository stores many Eclipse or Xcode projects. They are called modules. You checkout individual modules as needed. In Git, each project has its own repository. If you have several projects that are closely tied together, it may be cumbersome to work with multiple different repositories. In that case, you can create a super-repository that will contain individual Xcode or Eclipse projects as subfolders. For example, let’s say that we have a Eclipse workspace with these folders:
.metadata – This is used by Eclipse. There is no need to store it in Git repo.
ProjectA – An Eclipse project
ProjectB – Another Eclipse project
You can create a single Git repo for the whole workspace. From the workspace folder, run:
git init
git add ProjectA
git add ProjectB
In addition, create a .gitignore file in the workspace folder to exclude the .metadata folder.
.metadata/ #Exclude this folder
*.class #Exclude Java classes