Created at:
Modified at:
Git notes
First steps with Git
I started with revision control systems some time ago, with CVS. At that time there were already more modern version control software but we just loved the simplicity of CVS. Some time later the company changed the system but still used a client-server approach (centralized): we started using Subversion that I still think it is the best system *for some centralized environments* like some kinds of companies, but that is another history.
When starting to making some development with free software, I was introduced to modern decentralized version control systems. After trying out Mercurial, Bazaar (used by Canonical for the Ubuntu Linux project), Monotone (very interesting and one of the main inspiration when Linus Torvalds built git) and Fossil (another very interesting system that tries to bundle bug tracking together with code revision). I also gave a try out git, which has the enormous advantage of being adopted by a huge developer community, which led to the birth of online services that provided git hosting, like GitHub and others.
This small guide will not tell you the difference of the systems I just mentioned (it is worth to take a look at each of them) but will just help you start with git basics. Before beginning, check if you have git installed in your machine. We are going to use the command line interface. There are graphical front ends to it, as well as plugins for your favorite IDE, but we'll stick to the simple yet powerful command line interface.
Starting with a hosting service
Note: If you are not using GitHub, you may jump directly to section "Standalone mode".
First: git is a *decentralized* version control system, which means that you do not need a server. You can sync repositories you have in different machines, like your desktop, your laptop and your friends' computers. But it allows you to have a *reference remote repository* where everybody will fetch source code from there. You may start using one of the many git hosting services. Let's start with the most common one: GitHub. GitHub requires you to register SSH keys so you can use them to push code into your remote repository. In this article, we aren't going into details on how to do that. You can see link below for more information
Create an account in GitHub, setup your SSH keys and create a testing
repository. GitHub will give you a link to *clone* your repository. So, just
clone it with git clone
!
$ git clone git@github.com:username/project.git
I'm supposing the name of the user is username
and the repository name you
choose is project
.
The command above will create a directory named project
in your machine.
It is an empty directory, because your GitHub repository is empty for now.
Inside it, there is a sole subdirectory named .git
, which has metadata of
your repository and where you shouldn't touch (unless you know what you are
doing).
Standalone mode
If you don't want to use GitHub, you can setup your own repository. First
create the directory of the project, then use git init
:
$ mkdir project
$ cd project
$ git init
It is important to note that, unlike CVS nor Subversion, the repository is not
stored elsewhere, but within the directory of the working copy. After you
make changes to your files in project
directory, you make a
commit
(see section "Playing with git") and changes are stored in the .git
directory.
Playing with git
According to the gittutorial_, after you install git, it is a good idea to
tell who you are, with git config
:
$ git config --global user.name "Your Name Comes Here"
$ git config --global user.email your@email.comes.here.com
It will create a file in your home directory named .gitconfig
with these
and other settings you changed with git config
. It is important to
identify yourself, like above, so commits are assigned to right developers.
Now, inside directory project
, create two files, fileA.txt
and
fileB.txt
. Put any content inside it, it doesn't matter for now. The
structure will be like that:
* project/
* fileA.txt
* fileB.txt
Now add files to your repository with git add
:
$ git add *
They are *marked for adition* but they are not commited to the repository yet.
Actually, git add
adds file to the *staging area* or *index*. It is a
very powerful feature of git, but is beyond the scope of this text. To
know more about that, read about the staging area.
To commit the files in the repository, use git commit
:
$ git commit
Your favorite text editor will show up, where it is a good idea to add a
commit message, brief explaining what you changed in this commit. When it is
the first commit of the repository I like the Initial commit
string.
Now, make a change in one of the files e use git diff
to see the
differences:
$ git diff
Voilà! This command show differences between the last version committed and the files your working copy. To commit the changes, execute::
Don't forget the commit message with a summary of the changes you've made.
The flag -a
is necessary to avoid having to type git add
to add the
changes. Git doesn't commit changes you've made in your files before marking
them to addition (even though changes are in files already added and committed
to the repository before).
You may pass the commit message to git commit
command directly, without
having to open your editor. Use the -m
flag::
$ git commit -a -m 'Commit message comes here'
For new files git commit -a
will not work. You will have to add them
manually with git add
.
Finally, the command git log
will show you the log of your changes. If you
are not convinced of the importance of filling out the with a message, you may
get convinced now. The git log
command shows the changes you've made,
alongside with the author, date, etc.
Take a look at the output of commands above, in a testing repository I've just made:
$ cd /tmp/
$ mkdir project
$ cd project/
$ git init
Initialized empty Git repository in /tmp/project/.git/
$ touch fileA.txt fileB.txt
$ git add *
$ git commit -a -m 'Initial commit'
[master (root-commit) 112f5c1] Initial commit
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 fileA.txt
create mode 100644 fileB.txt
$ echo 'A test line' >> fileA.txt
$ git diff
diff --git a/fileA.txt b/fileA.txt
index e69de29..9efab9b 100644
--- a/fileA.txt
+++ b/fileA.txt
@@ -0,0 +1 @@
+A test line
$ git commit -a -m 'Adding a test line'
[master cc28b9f] Adding a test line
1 file changed, 1 insertion(+)
$ git log
commit cc28b9f73a5d487c9d3166b0a3585926ab7d8961
Author: My Name <my@email.com>
Date: Thu Jan 5 15:39:43 2017 -0200
Adding a test line
commit 112f5c1ec517bb25d1a054c063652c796d918a77
Author: My Name <my@email.com>
Date: Thu Jan 5 15:39:24 2017 -0200
Initial commit
Repository and working copy
In traditional centralized revision control systems like CVS and Subversion, the repository is always separated from the local working copy, where developers work. The repository is normally placed in a remote machine and the user checks out from the repository to the working copy and commits to the repository.
In git, the repository is together with the working copy. See all files of the the your project::
$ ls -A1
.git
fileA.txt
fileB.txt
Alongside with fileA.txt
and fileB.txt
there is the .git
directory. That is your repository! All the log, commits, remotes, etc., is
within this directory and if you delete it you will wipe everything out.
The repository in GitHub is a special kind of repository called *bare repository*.
Bare repositories
Bare repositories are like normal repositories, but without the working copy. They are conceived to store repository information only, not needing to have the working copy. Developers are not meant to have direct access to the bare repository and should access it remotely, via git commands.
To setup a git bare repository, pass the --bare
to git init
. Let's
make it with another example::
$ cd /tmp/
$ mkdir project2.git
$ cd project2.git
$ git init --bare
Initialized empty Git repository in /tmp/project2.git/
You can now clone it, like you would do with any other repository::
$ cd /tmp
$ git clone /tmp/project2.git /tmp/project2-working-copy
Cloning into '/tmp/project2-working-copy'...
warning: You appear to have cloned an empty repository.
done.
Git tell us we have cloned an empty repository. But it doesn't matter, We
can now add files, make commits and so on. But they will be in our clone
(/tmp/project2-working-copy
). To push changes back to the repository we
just cloned, we use the git push
command::
$ git push origin master
origin
is the repository we cloned (this name can be changed with git
remote rename
). master
is the name of the branch we are syncing with
the other repositories. We see about branches in section "Branch, checkout
and merge".
There is a nice thread in the git mailing list about bare repositories that you may want to take a look:
Discussion: how to use two bare repositories?
Remote, push, fetch and pull
Now that you know what repositories are (and bare repositories), let's
discover how to sync them. Come back to either your project
directory
(the one you cloned from GitHub) or
project2
(the one you cloned from your
own bare repository). Use the command git remote
:
$ git remote -v
origin git@github.com:username/project.git (fetch)
origin git@github.com:username/project.git (push)
When we clone a repository, the name of the repository we cloned from is, by
default, origin
(you can change it with git remote mv
). When we
commit to our repository, changes **don't** get synced back to the origin
repository by default. To push that changes back, we need to type the
following command::
$ git push origin master
origin
is the repository we want to push changes to and master
is the
name of the branch we want to sync. Check section "Branch, checkout and
merge" for more information about branches.
You can add different "remotes". Just use command git remote add
:
$ git remote add test mygithost:/foo/bar
$ git remote -v
origin git@github.com:username/project.git (fetch)
origin git@github.com:username/project.git (push)
test mygithost:/foo/bar (fetch)
test mygithost:/foo/bar (push)
Now you can push your changes to the test
remote repository too. Of
course, the git environment need to be installed in the remote machine.
Now, what if other developers have also pushed their changes to the remote
repository? How we pull them back? Using the command git pull
:
$ git pull origin master
It is the opposite of git push
.
There is also git fetch
:
$ git fetch origin
The difference between git fetch
and git pull
is that the fetch
"fetches" differences from the remote repositoy but don't *merge* them
automatically to your local branch, while git pull
does. In practice,
git pull
is equal to git fetch
+ git merge
. For more information
on merging, check the section Branch, checkout and merge
.
Branch, checkout and merge
Branching is normally an advanced feature of revision control systems but in
git it is presented to the developer very early, because in git you work with
branches every time, even though you don't realize that nor you don't use the
git branch
command.
Every repository has at least one branch, normally called master
(you can
change its name with git branch -m
). Let's try in our project
directory:
$ git branch
* master
The star at the beginning of the line shows the current branch. To create a
new branch, just pass a parameter to git branch
:
$ git branch mybranch
And show branches again::
$ git branch
* master
mybranch
mybranch
is already created (it is a exact copy of master
) but it is
not checked out yet. To change to it, use git checkout
::
$ git checkout mybranch
Switched to branch 'mybranch'
And show branches again:
$ git branch
master
* mybranch
Now, every change will be committed in branch mybranch
, not master
.
Note: Command git checkout
has a lot of different uses, not only checking
out a different branch. It can be used, for instance, to retrieve an old
revision of a given file.
Finally, what if I want to *merge* my changes back to the master
branch?
First, we need to change (checkout
) to the branch that is going to receive
the changes::
$ git checkout master
Switched to branch 'master'
And them execute command git merge
:
$ git merge mybranch
Updating cc28b9f..af0381b
Fast-forward
fileA.txt | 1 +
1 file changed, 1 insertion(+)
Note: You see the line Fast-forward
? It is the algorithm used by git
merge
. Git uses different algorithms for merging
, depending on the
case. git merge
is a powerful command with lots of options. It is
worth to study the git merge
documentation.
Other resources
This tutorial is just a starting point for such a powerful tool. I highly recommend you to read the Git oficial documentation that has a lot of information, and also the man pages of the commands you are going to use.
Also, there is the Pro Git book that spans from basic features to advanced ones. It is highly recommended!
Finally, when you have a questions, look for its solutions on the web. Lots of people use git and it is likely that someone already posted something in a forum asking the same question of you.
Good luck!
Random tips
Mirroring bare repositories
Since git is a decentralized system, you can also clone bare repositories and
tracking each other. This is useful way, say, if you have a bare repository
somewhere (e.g. GitHub), but you still need a bare repository locally
for any reason. Supposing a.git
is your primary repository and b.git
is the one you "copy", and both are located in /tmp
(this path can be remote,
too), follow these steps::
$ cd /tmp/
$ git clone --bare /tmp/a.git b.git
$ cd b.git
$ git remote add a.git /tmp/a.git # --mirror flag can appear here
Then, whenever you want to sync b.git
, obtaining modifications made in
a.git
, do:
$ cd /tmp/b.git
$ git fetch a.git
Discussion: how to use two bare repositories?
Branches and refs update in bare repository
I frequently use bare repositories not as and "end point" of commiting, but as a "middle" point. For instance, in my server I have some bare repositories that I clone from free software projects. Why keeping a bare repository that tracks another repository? They are organized this way:
So, in this diagram "upstream" is the oficial repositories hosted in providers like SourceForge or GitHub. "origin" is a bare repository in my personal server that clones "upstream" and fetches new changes from it. It is where I push my private branches to. "work" is my local working copy.
The problem shows up when I fetch changes from "upstream", using git-fetch
:
$ git fetch upstream
It will fetch "objects", but not "refs". Refs (inside the refs
directory under the git control directory) are just text files pointing to
commits of the tree. For instance, if you have a tag named "bla", there will
be a ref in refs/tags/bla
and, if you cat
(the cat
utility) it, you will
see a hash representing a commit. An example of my current refs/heads/master
ref:
$ cat refs/heads/master
a82cd88b10906fae8d5a0f10020eb6d7fb9274a7
So, back to our example, in a bare repository, I can't rebase or merge
remote branches, like I'd do in a working copy repository. And when I use
the git-fetch
without specifying any other argument, it will not update
refs. So, I have to use:
$ git fetch upstream master:master
So it will update refs/heads/master
to point to where the head of
"upstream" points. Sometimes it might not be easy to know the branch name you
are fetching from (in some situations under a shell script, for instance), so
it appears to be a good idea to use git-update-ref
...
$ git update-ref refs/heads/master refs/remotes/upstream/master
... it will update refs/heads/master
to make it to point to the same "object"
as refs/remotes/upstream/master
points to.
Working with bundles
Sometimes you do not have an internet connection to push and pull
repositories. To move your data using other media (email, an HDD, etc.), use
git bundle
. It creates a "bundle" (a file) with the contents of your
repository. This file can be transfered safely.
First, cd into your repository directory:
$ cd myrepo
Then create the bundle with all the branches, tags, (all the refs) etc.::
It will create a bundle with all the branches. Later, to clone it to a to a repository with a working copy, do:
$ git clone myrepo.bundle -b master myrepo
This will create the myrepo
directory with the master branch. Then, you can
fetch other branches:
git fetch ../myrepo.bundle mybranch:mybranch
Deleting a remote branch
If your remote branch is called "foo", faça:
$ git push origin :foo
Check section "remote: *** Deleting a branch is not allowed in this repository" if you get an error like that.
Troubleshooting
This section tries to explain some difficulties I have trying to set up Git, and the solutions I found.
Error "*** Project description file hasn't been set" when git pull origin
When trying to push to a new remote repository: I got the following error message:
*** Project description file hasn't been set
error: hooks/update exited with error code 1
error: hook declined to update refs/heads/master
I had to change the description
file of the remote repository, which means that I
should provide a short description for it. Rememter: it is the remote repository
(not local one) description
file.
"Project description file" error in git?
Push do Git dando erro de Project description
"fatal: git upload-pack: aborting due to possible repository corruption on the"
If you are trying to clone a repository, and get the following error:
Initialized empty Git repository in /tmp/repo/.git/
error: git upload-pack: git-pack-objects died with error.
remote: aborting due to possible repository corruption on the remote side.
fatal: early EOF
fatal: git upload-pack: aborting due to possible repository corruption on the remote side.
fatal: index-pack failed
Before calling git fsck
desperately, make sure the remote user's shell can find
git in the $PATH
. In my problem, they were installed from pkgsrc, which by
default installs stuff in /usr/pkg
.
This was easily solved doing:
# cd /usr/local/bin
# ln -s /usr/pkg/bin/git
# ln -s /usr/pkg/bin/git-receive-pack
# ln -s /usr/pkg/bin/git-upload-archive
# ln -s /usr/pkg/bin/git-upload-pack
git-clone: path or ssh problem with git-upload-pack in 1.6.0?
error: "patch failed .. Applying: .. trailing whitespace when git svn rebase"
Maybe you get this error:
Applying: <Commit messasge>
.../path-to-repository/.git/rebase-apply/patch:21: trailing whitespace.
When git svn rebase
returns this error, it means that someone commited to the
Subversion repository, code that "breaks" git constraints related to source code
style. To allow it be commited in your local git repository, run this command:
git config core.whitespace -trailing-space,-space-before-tab
error: "patch failed .. patch does not apply .. Patch already applied"
After a git svn rebase
, if you see any error like:
Applying: <Commit message>
error: patch failed: <file:line>
error: <file>: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.
For any reason your local repository and the Subversion repository are not in sync and git tries to get changes from SVN that were already taken. Try:
git rebase --skip
It might be a good idea to run git diff
to check if the merge worked fine.
"warning: remote HEAD refers to nonexistent ref, unable to checkout."
This error is caused when cloning from a bundle (check git-bundle
for
details). The clone was made, but git cannot checkout a working tree.
A git checkout
with the right parameters is enough. So:
$ git checkout -b master origin/master
remote: *** Deleting a branch is not allowed in this repository
If you get an error like that when deleting a remote branch, your probably
have a hook enabled in the remote side that is not allowing you to delete it.
This is probably yourrepo.git/hooks/update
.
Login to your remote machine and disable the hook with:
$ git --git-dir /path/to/yourrepo.git config --bool hooks.allowdeletebranch true
Cannot delete remote branch in git
fatal: unable to access '...': SSL certificate
This usually happens for self-signed certificates when cloning a repository. To
make it ignore the certificate check, set the GIT_SSL_NO_VERIFY
variable::
$ GIT_SSL_NO_VERIFY=true git ...