Setting Up a Git Server and Client
Introduction
Git is a distributed version control system. It’s free software created by Linus Torvalds, the creator of the Linux kernel, and distributed under GNU GPL version 2.
Like BitKeeper, Git doesn’t rely on a centralized server. It’s a low-level tool, designed to be simple and highly efficient, whose main task is to manage the evolution of a file tree.
Git indexes files based on their checksum calculated with the SHA-1 function. When a file isn’t modified, the checksum doesn’t change, and the file is stored only once. However, if the file is modified, both versions are stored on the disk.
Git wasn’t initially a version control system in the strict sense. Linus Torvalds explained that “in many ways, you can consider git as a filesystem: it allows associative addressing, and has the notion of versioning, but most importantly, I designed it by solving the problem from a filesystem specialist’s perspective (my job is kernels!), and I had absolutely no interest in creating a traditional version control system.” It has since evolved to incorporate all the features of a version control system.
Git is considered to be highly performant, to the point where some other version control systems (Darcs, Arch) that don’t use databases have shown interest in Git’s file storage system for their own operations. However, they would continue to offer more advanced features.
Installation
First, let’s install Git on the server:
|
|
If you want to make the server accessible via a git://
address, you’ll also need to install:
|
|
Configuration
Server
First, we’ll create the repository:
|
|
Now that our project is created, let’s add a small file to make sure everything is working correctly:
|
|
Then we’ll add it and commit it:
|
|
To check the status at any time, run:
|
|
Next, we clone this repository:
|
|
- clone: creates a copy of the repository locally
- –bare: creates a copy containing only the info from the myproject folder
- myproject.git: folder that git has created for us
The git protocol
If you want to make git accessible via its protocol (git://
), you need to configure the following file like this:
|
|
Then for each project you want to make accessible from outside, you’ll need to place a ‘git-daemon-export-ok’ file:
|
|
Restart the daemon and it will now be accessible on port 9418.
Client
You should first install Git as done on the server.
Then with your current user, inform git about your identity (make sure your user exists on the server):
|
|
This should create the following in your ~/.gitconfig:
|
|
If you want to have different identities for different repositories, simply remove global (by placing yourself in the repository in question):
|
|
Now we will retrieve our project from our git. We’re using the SSH method here, but git can also work with rsync, http, https, and git-daemon.
With SSH
Here’s the SSH method:
|
|
- For repository updates, you’ll need to use the push option. First, changes must be made locally (that’s the advantage of git), such as an add and a commit (we’ll see this just after):
|
|
or
|
|
‘master’ here corresponds to the branch name we’re interested in (see below for using branches).
- To add a file to an SSH repository:
|
|
In the future, to avoid dealing with SSH and its special commands, add this to your ~/.gitconfig file:
|
|
With Git-daemon
Git will refuse to synchronize if the folder in question doesn’t contain a file called ‘git-daemon-export-ok’. Once this file is created, the folder will be accessible to everyone:
|
|
or
|
|
We can use the following options:
- ‘–export-all’: this option no longer requires the ‘git-daemon-export-ok’ file
- ‘–user-path=gitexport’: this option will allow URLs on user home directories. So
git://deimos-laptop/~deimos/myproject.git
will point to /home/deimos/gitexport/myproject.git
On HTTP (with Nginx)
I spent quite a bit of time getting Git over http(s) and Gitweb to coexist, but it’s working now.
Here’s the method I used:
|
|
Here I have my git over https working (http is redirected to https) as well as my gitweb since everything that matches gitweb.cgi is caught. Now, for the git part, we’ll need to authorize the repositories we want. For that, we’ll need to rename a file in our repository and run a command:
|
|
Replace www-data with the user who has rights to the repository. Use www-data so that nginx has the rights. Then you have permissions to clone:
|
|
Configuring a proxy
If you need to go through a proxy with Git, you can do it like this (for its global part):
|
|
To check the current settings:
|
|
If you need to remove this configuration:
|
|
Usage
Updating your local repository
If you want to update your local repository from the server, it’s simple:
|
|
or
|
|
Adding files
To add files in bulk, use this command:
|
|
Committing changes
If you want to both log and commit at the same time (otherwise leave out -m…):
|
|
This will update the repository locally (don’t forget to push afterwards if you’re working with a remote server).
Changing the default editor and viewer
You can force vim and less for example by entering these lines:
|
|
Getting logs
You can check the logs of previous commits at any time with this command:
|
|
If you want to get logs for a specific file:
|
|
If your file has been renamed, use the –follow option to see its new name as well. To see logs from the last 2 days:
|
|
Searching
You can search for content in files like this:
|
|
Ignoring files
You may want to exclude certain files such as vim temporary files. You’ll need to create a .gitignore file at the root of the working copy, which will generally be added to the repository itself, or in the .git/info/exclude file. Here’s an example:
|
|
Making sure there’s nothing to commit
You can ensure there’s nothing to commit like this:
|
|
Configuring aliases
You can configure aliases for longer commands. For example, if you’re used to SVN:
|
|
Here ‘git co’ corresponds to the ‘git checkout’ command. This configuration (whether global or not) can be located in 2 places:
- ~/.gitconfig to benefit from it in all your repositories.
- .git/config of a project to restrict its access to that single project.
Revert: canceling a commit
Git revert allows you to cancel the previous commit by introducing a new one. First, we’ll do a git log to get the commit number:
|
|
Then we’ll do a revert to cancel the old one:
|
|
In case of conflict, simply make manual changes, do another add, and then commit.
Note: you can also use the first few characters of the commit identifier (SHA-1) such as bfbfe. Git will automatically complete it (as long as another commit doesn’t start with the same characters).
Reset: deleting all commits from an old one
Git reset will completely remove all commits made after a certain version:
|
|
Git reset allows you to rewrite history. You can verify this with a ‘git log’. This can have serious consequences if you’re working with others on a project. Not only because the local repositories of other people will need to be resynchronized, but also because there can be many conflicts. It is therefore advised to use this command with caution. Then you must redo a ‘git add’ of your files, then a ‘git commit’.
If you want the local working copy to reflect the repository, add the –hard option during reset:
|
|
In case you’re having a terrible day and deleted months of work and want to go back, git has preserved the previous state, so do:
|
|
HEAD means the last commit by default.
If you get a message like:
! [rejected] master -> master (non-fast forward)
And you really want to force the commit to look exactly like what you have on your machine:
|
|
Restoring a file from an old commit
You can choose to restore only a particular file from an old commit via the checkout command:
|
|
Deleting a file
There are 2 methods; my preferred one, which I find simple:
|
|
The 2nd method consists of doing a standard rm, then at the next git commit -a, it will detect that this file no longer exists and will remove it from the repository.
Moving a file
To move a file within your git repository, use this command:
|
|
Listing the repository contents
You can use this command to list the contents of a repository:
|
|
Note: empty directories will not be displayed
Creating a repository archive
To create an archive in tar gzip format, we’ll use the archive option:
|
|
- –prefix: allows giving a folder that will be created upon decompression.
- HEAD: allows specifying the commit (here the 1st)
Viewing a specific file from a commit
It’s possible to view a file (README) from a commit:
|
|
or see all diffs at once if you don’t specify the file:
|
|
Rewriting history
If you forgot to do something in a previous commit and want to go back, you need to do an interactive rebase by indicating the earliest commit you want to modify:
|
|
In the editor, change ‘pick’ to ’edit’ on the line(s) you want to modify. Save and exit. Make all your changes and then validate the commit:
|
|
Then, to move on to the next commit:
|
|
Until it’s finished.
Making diffs between different versions
It’s very easy with git to make diffs between different versions. For example, from HEAD to 2 versions back:
|
|
Modifying the text of the last commit
To see the state of the last log, we can do:
|
|
To modify the text of this last log, we’ll use the amend option:
|
|
This allows you to change just the text; there won’t be a new commit number. Only the identification number will change.
Modifying the date of the last commit
It’s possible to modify the date of the last commit:
|
|
Or you can specify the date explicitly:
|
|
Modifying the author of commits
When using multiple Git accounts on the same machine, it’s easy to make a mistake when cloning a repository and not setting the right username and email address. When you realize it, it’s too late and you need to rewrite part of the history to correct the information:
|
|
Synchronizing only part of a repository
If you want to have only part of a repository (also called sparse), you’ll need to first retrieve the entire repository, then specify what interests you. Only what interests you will remain:
|
|
Here in the git_deimosfr repository, only the “configs/puppet/” folder interests me. You can add multiple folders to the “.git/info/sparse-checkout” file line by line if you want to keep multiple files.
Using an external git repository within a git
If, for example, you already have a Git in which you want to integrate another Git, but external, you’ll need to use submodules. For my part, I have a puppet folder with lots of modules inside. Some of these modules weren’t created by me and are maintained by other people who have a Git. I want some modules to point to external gits. Here’s how to proceed:
- I add the external source to this git:
|
|
Now you just need to commit and push if you want to make this configuration valid on the server.
- Now let’s consider the case of a client who does a pull. They will get your entire tree structure, but not the external links; for that, they will need to execute the following commands:
|
|
If during a clone you want the submodules to also be taken, you need to add the “–recursive” option. Example:
|
|
Branches
Creating a branch
If, for example, the current version you have on git suits you and you want to make it stable, you need to use branches and create a new branch as a development branch:
|
|
Sending a branch to the server
To send a branch to the server:
|
|
Listing branches
Now, the devel branch is created; to check:
|
|
The ‘*’ indicates the current branch (in use).
When using remote branches, you can list them with this option:
|
|
Changing branches
To change branches, simply use the checkout option:
|
|
Let’s check:
|
|
If we want to go back to the previous branch, we can at any time do:
|
|
The -f option corresponds to the –hard option of reset which allows synchronizing local changes made to the repository.
If you’re working on a remote branch, do this for example:
|
|
Getting a branch from a remote server
Once your “pull” is done, change branches like this (you can name it differently if you wish):
|
|
- new_branch: the name of my new local branch
- new_remote_branch: the name of the new remote branch (server side)
Deleting a branch
To delete a branch, nothing simpler:
|
|
If this works, perfect; if you want to force in case of a problem, use ‘-D’ instead of ‘-d’. To apply the changes to the remote server:
|
|
This will delete the devel branch on the remote server.
Merging branches
The merge option consists of completely merging 2 branches. Like for example merging a devel branch with a stable one so that devel becomes stable:
|
|
Rebase: Applying a patch to multiple branches
Imagine we’ve been working on the devel branch but a bug in master has been discovered. It’s normal that if master gets the fix, the devel branch should get it too. We’ll place ourselves in the master branch, apply the patch, then merge the patch with the devel branch. We place ourselves on the devel branch, then execute these commands:
|
|
It’s possible to do it interactively with the -i argument:
|
|
Creating a branch during a restoration
You can create a branch when restoring a file by first placing yourself in the branch and using the -b option:
|
|
Tags
Tags are very useful for marking a specific version. For example, to release version 1.0, you can create a tag and reuse it later to download this particular version. You can tag anything; it allows for easy referencing.
Creating a Tag
To create a tag, it’s easy:
|
|
Listing tags
To list available tags:
|
|
Deleting a tag
To delete a tag:
|
|
Sending tags to the server
Once the tags are created locally, you can push them to the server:
|
|
Managing conflicts
You can manage conflicts interactively:
|
|
Automatic resolution of already seen conflicts
It’s possible to ask git to automatically resolve problems it has already encountered:
|
|
Using git to find a bug
Imagine a sneaky bug suddenly appears and disrupts your project. After investigations worthy of an English pipe smoker, you discover that the nasty malfunction has only appeared recently. It’s clearly a regression!
Sure, you could waste time fixing this regression the classic way, by diving into dusty code full of virtual cobwebs, but it would be much simpler to discover exactly when it appeared, which commit corresponds to it, to have a very precise idea of its cause.
This is exactly what git-bisect allows you to do, in a particularly intuitive way. Tell it a commit with the malfunction (HEAD), and a past commit, any one, that doesn’t have it.
Git then places you in your development history, right in the middle between the two commits. After checking, you tell git whether the regression appears or not. And we start again. Each time, we divide by 2 the range of commits that potentially introduced the bug, until we catch the culprit. Great, isn’t it?
Let’s put it into practice:
|
|
8 commits potentially introduced the regression. We indicate whether the current commit is correct or not:
|
|
And we continue, again and again, until we find the bad commit that caused this wasted time (but it could have been worse):
37b4745bf75e44638b9fe796c6dc97c1fa349e8e is first bad commit
At any point, you can get a history of your journey:
|
|
If at a specific moment, you don’t want to test a particular commit, for whatever reason, use the command:
|
|
All this is fine and good, but there’s better (no!? yes!). Suppose you’re a fan of TDD. You surely have a script that automatically tests whether the current code is good or not. It’s then possible to automate the search:
|
|
Go get a coffee, git is working for you. Note: the script must return 0 if the code is correct, 1 if it’s incorrect, and 125 if it’s untestable (git skip). During bisection, git creates a special dedicated branch. Don’t forget to switch back to your development branch when you’ve caught the regression. You can do it simply by typing:
|
|
So, with all this, no more tedious regression searches.
Knowing commits line by line
This is the solution for slapping the fingers of someone who did a commit. Among other things, you have the ability to know line by line who wrote what:
|
|
Moving a folder and its history to another repository
Sometimes you may need to recreate another empty repository to contain a folder from another repository. This was my case for the DeleteHistory extension for Mediawiki that I created. At first, I had a repository called ‘mediawiki_extensions’ where I had 2 extensions. And then with time and advancing Mediawiki versions, it was preferable to separate by plugins while keeping the history. So I’ll explain how I did it (I followed this documentation).
We’ll work locally with my old repository which we’ll call oldrepo and my new repository newrepo (how original).
So let’s clone this repo:
|
|
Then we’ll filter for the folder we’re interested in, let’s call it folder2keep:
|
|
Now, all the files and folders from folder2keep are at the root of our repository. If you wish, you can create a folder and place everything inside, but this is not mandatory:
|
|
Replace * with all elements you want to place inside and commit:
|
|
Now, we’ll go into our famous folder and make a local reference between the 2:
|
|
Then we’ll bring in the sources, create the main branch, and merge everything:
|
|
Now we’ll transfer the history and do some cleaning:
|
|
And there you go, the temporary repository (old) can now be deleted.
Hooks
There are hooks in Git allowing pre or post processing. Here’s a use case for performing an action (running a command via ssh) when a tag arrives on the server. The idea is to be able to deploy a new version of software on X servers based on a tag (example: prod or preprod). Here are the options that will be necessary:
- Create a hook in the repository
- Create a dedicated user if you use Gitlab/GitHub/Gitolite
- Generate a private SSH key with the ‘git’ user
- Copy via ssh-copy-id to remote servers the ‘git’ user key (usually www-data)
- Create a deployment script and set the proper permissions
- Clone the repository on all necessary servers
|
|
You need to modify the environments with the ones you want so that when a tag arrives with the name of the tag in question, an action is triggered behind it. For example, to deploy on production servers, you’ll need to put a tag like “prod-v1.0”.
Switch to the git user (or the one who has the rights) and copy the public key to all the servers on which the tags will need to act:
|
|
Then create the deployment script:
|
|
You can add any commands you want. You obviously need to copy this script to all target servers and give it execution rights.
Create the log file and set the right permissions:
|
|
Then clone the repositories with the final users:
|
|
And there you go, all that’s left is to push a tag on a commit:
|
|
Utilities
Gitk
Gitk provides a graphical version of your git status (branches etc.).
FAQ
warning: You did not specify any refspecs to push, and the current remote
If you get this kind of message when you do a push:
warning: You did not specify any refspecs to push, and the current remote
warning: has not configured any push refspecs. The default action in this
warning: case is to push all matching refspecs, that is, all branches
warning: that exist both locally and remotely will be updated. This may
warning: not necessarily be what you want to happen.
warning:
warning: You can specify what action you want to take in this case, and
warning: avoid seeing this message again, by configuring 'push.default' to:
warning: 'nothing' : Do not push anything
warning: 'matching' : Push all matching branches (default)
warning: 'tracking' : Push the current branch to whatever it is tracking
warning: 'current' : Push the current branch
Simply run this command, selecting what corresponds best to you:
|
|
References
http://www.kernel.org/pub/software/scm/git/docs/user-manual.html http://git.wiki.kernel.org/index.php/GitFaq http://alexgirard.com/git-book/index.html How To Install A Public Git Repository On A Debian Server Git it http://www.unixgarden.com/index.php/administration-systeme/git-les-mains-dans-le-cambouis http://stackoverflow.com/questions/1811730/how-do-you-work-with-a-git-repository-within-another-repository http://chrisjean.com/2009/04/20/git-submodules-adding-using-removing-and-updating/
Last updated 02 Feb 2015, 20:33 +0200.