Bering-uClibc 5.x - Developer Guide - Hints and Tips for using Git SCM
Hints and Tips for using Git SCM | ||
---|---|---|
Prev | Bering-uClibc 5.x - Developer Guide | Next |
Contents
- 1 Background
- 2 Overview of Git
- 3 Git Repositories for LEAF
- 4 Getting Started
- 5 More Advanced Topics
- 6 Branching
- 7 Graphical User Interfaces
- 8 Git Command Reference for CVS Users
- 9 Hints and Tips
- 10 Further Reading
Background
The LEAF project successfully used the CVS (Concurrent Version System) Source Code Management (SCM) tool, hosted by SourceForge, for many years. Various developers suggested switching to an alternative SCM such as Subversion or Git (see in particular the email thread "Talks about versioning system" on the leaf-devel mailing list in late September 2010) but the consensus was that CVS met all the basic requirements and a change of SCM would only divert resources away from other more important tasks.
The consensus view changed in late January / early February 2011, when the SourceForge systems were compromised and CVS proved particularly difficult to restore (see the SourceForge report here). In particular, the indication was that the SourceForge-hosted CVS service would be retired.
The SourceForge-hosted Git SCM service was selected as the new SCM for Bering-uClibc 4.x early in February 2011.
Overview of Git
Git is a distributed Source Code Management tool whereas CVS is a client-server SCM tool. This means that every Git repository is a "peer" (equivalent) of every other repository. One repository, in our case the one hosted by SourceForge, is arbitrarily designated as the "main" or "central" repository, but every developer has their own local, working repository with the same capabilities as the central one.
A developer initiates their own local repository by "cloning" the master one, hosted by SourceForge.
The developer then interacts with their local repository rather than interacting directly with the central repository, and periodically synchronizes the local repository with the central one. Changes made locally are "pushed" to the central repository, whereas changes made by other developers (and "pushed" to the central repository by those other developers) are "pulled" from the central repository so as to update the local repository.
It is tempting to refer to the central repository as the "master" repository, but that would be confusing as will become clear later. The term "central" or "SourceForge-hosted" repository will be used instead.
Git Repositories for LEAF
The LEAF Project currently has three separate Git repositories:
- leaf/bering-uclibc
- This is the main, active repository for the Bering-uClibc Project. Since 2011-06-20 this holds the live source code for Bering-uClibc 4.x and is the only repository that most developers need to use.
- leaf/leaf
- This was initially (until 2011-06-20) used for Bering-uClibc 4.x development but that has now moved to leaf/bering-uclibc. The leaf/leaf repository is now reserved for general purpose LEAF items not specific to any Project,
- leaf/packages
- This separate repository is used to hold the Package
.lrp
files generated for LEAF Project Releases, to keep them out of the other repositories which are reserved for source code.
In the future it is likely that further repositories will be added. The general rule is one Git Repository for each LEAF Project.
Getting Started
Install the Git Client
The next step is to ensure you have the Git client software installed on your build host. On Red Hat-derived Linux distributions this is accomplished with:
yum install git
Clone the SourceForge-hosted Git Repository
Next, you will want to copy ("clone") the SourceForge-hosted Git repository. The naming convention for project repository URLs is documented on the SourceForge Git page. In our case "PROJECTNAME" = "leaf" and "REPONAME" = "bering-uclibc" so the repository is accessed as:
- Read-only
- git://leaf.git.sourceforge.net/p/leaf/bering-uclibc
- Read-write
- ssh://USERNAME@leaf.git.sourceforge.net/p/leaf/bering-uclibc
The examples that follow assume you want read-write access and will therefore use the second (ssh) variant. Obviously you need to replace "USERNAME" with your own SourceForge username. You will be prompted to enter your SourceForge account password when using the "ssh" URL, or you can configure SSH keys as described here.
To actually create a copy of the SourceForge-hosted repository change to a suitable directory on your build host and run the following command:
git clone ssh://USERNAME@leaf.git.sourceforge.net/p/leaf/bering-uclibc
Note: The "git" commands must be entered as e.g. "git clone" (argument "clone" to command "git") but you will also see references to e.g. "git-clone" in the documentation. This is because the man pages use the latter naming style (with the hyphen) and in the very early days of Git there were several independent command executables called e.g. "git-clone" rather than a single command executable called "git".
This creates a new directory "bering-uclibc" under the current directory which contains a full copy of the "bering-uclibc" repository on SourceForge. Change to this new "bering-uclibc" directory for the following steps:
cd bering-uclibc
This directory "bering-uclibc" represents the local repository which is a full Git repository in its own right, although it understands that it was derived from (and is therefore "linked" back to) the SourceForge repository.
(Actually it is the .git
directory under "bering-uclibc" which is the real Git repository.)
Note: The SourceForge repository is known by the alias "origin". That alias will be used in some of the commands.
Any git commands executed in or below the "bering-uclibc" directory operate on this repository.
Configure your User Identity
Any changes you make are identified with your user details as specified in a Git configuration file. To populate these on a "per repository" basis run the following commands:
git config user.name "Your Full Name" git config user.email "USERNAME@users.sourceforge.net"
Tip: Review the contents of file .git/config
to see where these setting are maintained and what else is in this file.
An alternative is to configure these details on a "per user" basis, which means they are instead written to file $HOME/.gitconfig
and apply to all repositories (unless overridden by a "per repository" setting). To do this, add the --global argument as shown below:
git config --global user.name "Your Full Name" git config --global user.email "USERNAME@users.sourceforge.net"
Change an Existing File
That's all of the preparation complete; now we can actually change something.
The following examples show an update to buildtool/conf/sources.cfg
.
Edit the file
First change to the directory containing the file to be modified:
cd buildtool/conf
Modify the file using your favourite text editor:
edit sources.cfg
Tell Git about the changes
When the modifications are complete you need to tell Git about them. This is accomplished using the "git add" command:
git add sources.cfg
Note: With CVS you would only "add" new files or directories. With Git you need to also need to "add" changes to existing files.
Running "git add" has the effect of adding the differences in the specified file(s) to a local "index" of pending changes which is then processed by a subsequent "git commit" transaction.
Commit the changes to the local Git repository
At this point you can review the contents of the "index" of pending changes with the following command:
git diff --cached
It is good practice to always do this before running "git commit".
Note: The "--cached" argument is required once changes have been added to the index. Before running "git add" a simple "git diff filename" will show the deltas.
Another command to show what changes are pending is:
git status
This also shows what files have been modified but not yet added to the list of changes to be processed.
Next, you need to commit the changes to your local Git repository:
git commit -m "Log message for changes"
Note: With CVS this would commit the changes directly to the shared repository on SourceForge however with Git the "commit" command only commits the changes to the local Git repository.
Publish the changes to the SourceForge-hosted Git repository
Publishing the local changes to the shared Git repository hosted on SourceForge needs a further step:
git push
Tip: All of the "git" commands take a "--dry-run" command-line parameter. This processes the command as normal but does not actually update the repository. Use this while you are learning to check what effect a command will have before running it for real.
Add a New Directory
When adding a new Package it is necessary to add a new directory (under repo/
) together with (some of) the files in that directory.
This is easier with Git than with CVS, since adding the directory automatically adds the files as well.
git add packagedir/
Note that any number of related changes can be stacked up with multiple "git add" commands before running "git commit".
This means that adding a new Package entry to sources.cfg
can be done as part of the same commit as adding the source files themselves.
Update the Local Repository with Changes from SourceForge
Other developers will be making their own changes and publishing updates to the SourceForge-hosted Git repository. Update the local repository to re-align it with the master repository using the following command:
git pull
If there are no changes this responds as follows:
Already up-to-date.
More Advanced Topics
Ignoring Generated Files
Any files generated within the Git repository will show up in a "git status" and are at risk of being accidentally added to the repository. To specify that such files should be ignored, Git checks for files called .gitignore
in each directory within the repository.
The contents of a .gitignore
file take effect on files in the same directory and in all sub-directories, and the contents are basically just filename glob patterns which specify the files to be ignored.
For example, there is a buildtool/image/.gitignore
file which specifies that any generated disk Image files (in the various sub-directories) should be excluded. The contents are:
# Exclude any generated image files *.tar.gz *.iso
Moving and Removing Files
When Removing files, or Moving them around within the repository, simply use the "git rm" and "git mv" rather than "rm" and "mv". These "git" commands do what you would expect at the file system level and also keep the Git "index" updated. The changes then need to be committed and pushed.
Tagging Releases
It is customary to "tag" each Bering-uClibc 4.x release (including Betas and Release Candidates) in the SCM so that it is clear which version of the source code was used to build the executables distributed in the released disk images. This is done by running commands like:
git tag -a 4.0-rc1 git push origin 4.0-rc1
The first command creates an unsigned "annotated" tag label for the most recent commit and the second pushes it to the SourceForge-hosted Git repository.
The tags defined for a Git repository can be listed with "git tag -l". For example:
git tag -l 5.0-beta1 5.0-rc1
It is then a simple matter to adjust the working copy of the files to reflect the state of the repository when the tag was applied. Simply use the tag name as an argument to "git checkout". For example:
git checkout 5.0-rc1
If you need to refer to an earlier version of the repository which was not specifically tagged this is a little more complex. The main challenge is that without a tag there is no friendly string which refers to the relevant previous commit. There is a shorthand way to refer to e.g. "8 commits ago" on the branch called "master":
git checkout master~8
In addition, each commit is labelled with a less friendly unique identifier which can be used instead.
Branching
Default Branch
By default, Git works with a single local branch called "master", as can be seen by running the "git branch" command with no arguments:
git branch * master
The asterisk (*) indicates that this is the "current" branch.
This local branch "master" is synchronized with the "master" branch in the "origin" repository (known as "origin/master").
Creating a Branch
A new local branch can be created very easily. For example:
git branch new-branch
creates a new local branch called "new-branch" which is based on the latest commit ("HEAD") on branch "master".
If you want the branch to refer to an older snapshot of the repository, for example the release tagged as "4.0", specify this as the "<start-point>", for example:
git branch new-branch 4.0
Working with a new Branch
When a new branch is created it is not automatically made the default branch. If you run
git branch
you’ll get a list of all existing branches:
new-branch * master
In order to use the new branch you need to switch to it using "git checkout". For example:
git checkout new-branch
If you now run
git branch
you’ll see that new-branch is the active branch:
* new-branch master
So you can check the active branch with
git branch
and switch the current active branch with
git checkout branch
That way one can easily work on different branches on the local host.
Note: Alternatively, a new branch based on a snapshot of the tagged version 5.0, can be created using the "-b" argument to "git checkout". For example:
git checkout -b new-branch 5.0
in which case new-branch is based on version 5.0 and automatically made the default.
Comparing Branches
Can report on the differences between the current branch and another branch using "git diff branchname". For example, assuming that a new branch has been made the current/default branch this can be compared to the master branch:
git diff master
Merging a Branch
To merge the changes made in new-branch into master, run
git merge new-branch
This also works the other way around. Say you are working on a new development in new-branch but there have been changes in master which you want to incorporate:
git merge master
Deleting a Branch
A local branch can be deleted using e.g.
git branch -d new-branch
Remote Branches
In general, all Bering-uClibc 5.x development takes place along a single trunk, with identifying labels being used to signify milestones such as "5.0-beta1" and "5.0". However, in some cases it is necessary to work on multiple developments in parallel.
Imagine that "4.1-beta1" has already been released but a serious bug is found in "5.0". The bug can be fixed in "5.1-beta2" but that might take many weeks to be formally released as "5.1" and will include a large number of other changes. One solution is to fix the bug in a release called "5.0.1" which is based on "5.0" and omits the other changes in "5.1-beta1".
The best way to handle this is to create a new branch (called e.g. "5.0-fixes") which is based on the "5.0" label. In order for this branch to be shared by all of the developers it needs to be present on the main, SourceForge-hosted Git repository. Such a branch is referred to as a remote branch. In general, each developer will create their own local branch which is set up to "track" this remote branch (a tracking branch).
Creating Remote Branches
If you want to share your local branch with other developers, you push it to the remote server.
git push -u origin 5.0-fixes:5.0-fixes
The option '-u' enables tracking of the remote branch for the branch creator.
Listing Remote Branches
Add the "-r" argument to the "git branch" command to have it show remote branches. For example:
git branch -r origin/HEAD -> origin/master origin/master origin/4.0-fixes
Tracking a Remote Branch
If the remote branch already exists, a developer can set up their own local branch to track this with a command like the following:
git branch --track 5.0-fixes origin/5.0-fixes
The "tracking" means that git pull and git push will automatically read from / write to the correct branch in the remote repository.
Confirming Remote Branch Tracking
To be certain which local branches are mapped to which remote branches, run git remote show origin. For example:
git remote show origin * remote origin Fetch URL: ssh://USERNAME@leaf.git.sourceforge.net/gitroot/leaf/bering-uclibc Push URL: ssh://USERNAME@leaf.git.sourceforge.net/gitroot/leaf/bering-uclibc HEAD branch: master Remote branches: 5.0-fixes tracked master tracked Local branches configured for 'git pull': 5.0-fixes merges with remote 5.0-fixes master merges with remote master Local refs configured for 'git push': 5.0-fixes pushes to 5.0-fixes (up to date) master pushes to master (up to date)
Deleting a Remote Branch
Just in case you want to delete the remote branch 5.0-fixes, you push "nothing" to the remote branch 5.0-fixes.
git push origin :5.0-fixes
Graphical User Interfaces
There are some GUI tools which can help manage Git repositories.
git gui - A portable graphical interface to Git
git gui is written in Tcl/Tk. The default display is a GUI equivalent of "git status", showing Staged and Unstaged changes. It has buttons for entering Commit messages, for Pushing changes etc.
TODO - Add screenshot.
gitk - The git repository browser
gitk is written in Tcl/Tk and provides a view of the current Git repository, with an emphasis on showing the history of commits. A particular feature is the "commit graph structure" (shown here for the 4.1 release - the yellow "flag" is a version tag"). Click on the image for a larger view:
Git Command Reference for CVS Users
For developers familiar with CVS, here are the equivalent Git commands for some basic SCM tasks:
Task | CVS Command | Git Command |
---|---|---|
Check-in a modified file | cvs commit -m "Log Message" filename | git add filename; git commit -m "Log Message"; git push |
Update local repository with changes from SourceForge | cvs update | git pull |
Hints and Tips
To use all the tricks below it's better to have a recent version of git: >= 1.7.9.
Compiling your own version of git
- You can compile your own version of git (it's easy). On my fedora 15 i've done:
sudo yum install gcc libcurl-devel openssl-devel expat-devel perl-ExtUtils-MakeMaker tcl gettext asciidoc xmlto git clone git://github.com/git/git.git cd git git checkout v1.7.12 make prefix=/usr all man # You can change the prefix if you don't want to install on /usr # Then remove the old git package sudo yum remove git # Then install git into you system as root: sudo make prefix=/usr install install-man # Change the prefix if you have change it in the first make command # And install the git completion script(s) sudo cp contrib/completion/* /etc/bash_completion.d/
Useful configurations
- Add the refnames and date in local time when using the git log command:
git config --global log.decorate short git config --global log.date local
- Update you PS1 with git informations:
- Add $(__git_ps1) at the end of your PS1 (usually configure in your $HOME/.bashrc). For example:
PS1="[\u@\h \W]$(__git_ps1) \\$ "
- Add some variables in your $HOME/.bashrc:
export GIT_PS1_SHOWDIRTYSTATE=true
export GIT_PS1_SHOWSTASHSTATE=true
export GIT_PS1_SHOWUNTRACKEDFILES=true
export GIT_PS1_SHOWUPSTREAM="verbose"
- Check that the bash-completion package is installed:
sudo yum install bash-completion
- Reload your $HOME/.bashrc:
source $HOME/.bashrc
Add Colors
Git can color its output to your terminal, which can help you visually parse the output quickly and easily. This is the configuration i used:
git config --global color.ui auto git config --global color.branch.current green git config --global color.branch.local yellow git config --global color.branch.remote red git config --global color.status.added yellow git config --global color.status.changed green git config --global color.status.untracked cyan
With this trick you can create multiple worktrees (like BuC-4.x and BuC-5.x) but with only one shared local git repository.
- First of all you need to be sure that there was no files waiting to be commiting. If so, commit all you modifications or stash (git-stash) them. Then move your .git directory to a dedicated directory (this will be your local git repository). For example if you working tree is bering-uClibc:
mv bering-uClibc/.git bering-uClibc.git
- Now get the last version of the git-new-workdir from the git project:
wget --quiet https://raw.github.com/git/git/master/contrib/workdir/git-new-workdir && chmod +rx git-new-workdir
- Then create your new worktrees. Suppose you want to have BuC-4.x pointed to the maint branch and BuC-5.x pointed to the master branch, do:
./git-new-workdir bering-uClibc.git BuC-4.x maint ./git-new-workdir bering-uClibc.git BuC-5.x master
There we go.
We can remove the git-new-workdir script because it is no more useful now.
Further Reading
- Git project Home page and Documentation page
- gittutorial(7) Manual Page
- Git User’s Manual
- Git Best Practices
- SourceForge documentation on their hosted Git service
- Version Control with Git, by Jon Loeliger. Published by O'Reilly Media, Inc. 2009. ISBN 978-0-596-52012-0.
- This is not a reference manual but it describes how Git works "under the covers" which can provide a very useful foundation to understanding the more complex features.
Prev | Up | Next |