Git Guide
If you just want to browse our git repository use [[https://github.com/spacewalkproject/spacewalk]].
A quick start guide to using git, especially if you're used to using svn or cvs.
To install git on Fedora just
# dnf -y install git
For installation on RHEL, you have to add EPEL repository. Then you can install git as usual.
For anonymous access to the Spacewalk source code, feel free to clone the repository:
git clone https://github.com/spacewalkproject/spacewalk.git
For more details check out the DownloadIt page.
If you have been approved to get git commit access to the spacewalk git repository you'll need to do the following:
- Have a github account.
- Upload your public ssh key to your github account (optional, but you won't have to log in all the time)
- Get someone to add you to the commiters list: https://github.com/orgs/spacewalkproject/teams/commiters
After that you should be able to git clone with:
git clone [email protected]:spacewalkproject/spacewalk.git
You must configure your name and email address for git to apply it to your commits. There are several ways to do this but one is to do so in ~/.gitconfig
.
NOTE: git can differentiate between the author of a commit and person with commit access pushing it to the repository. Most of the time these are the same, but in the case of accepting patches from the community the author should be modified (via setting the GIT_AUTHOR_NAME
and GIT_AUTHOR_EMAIL
environment variables) while keeping your commit user as yourself.
A good sample ~/.gitconfig:
[user] name = Bill Smith email = [email protected]
[alias] ci = commit -a co = checkout st = status -a praise = blame
[apply] whitespace = strip
[diff] color = auto rename = copy
[pager] color = true
[status] color = auto
- Try to work in local branches and use master (or other tracking branches) just for pushing/pulling changes.
- Your git commits are local only until you push them to the remote repository. (or submit a patch if you do not have commit access)
- Always run gitk --all (or tig) before you push changes. Examine what will be pushed so you can address anything unexpected before you push. (after which the commit is for all intents and purposes, permanent)
Ready to start hacking on the code? Create yourself a branch to work in.
git checkout -b mybugfix This is actually combining two steps into one, you could also do this the long way with:
git branch mybugfix # create the branch git checkout mybugfix # work on this branch
The branch will be created from your current location, i.e. if you currently have master checked out your branch will point to the same commit as was the HEAD of master at that time.
List your branches anytime and see which you're working on with:
git branch # list all your branches You now have a local branch nobody else can see, they're extremely fast and lightweight, and you can commit as you please. Nothing is pushed to the central repository until you explicitly do so.
Note that the single directory you cloned can be used to work on any branch you like with a simple git checkout command. Switching branches is extremely fast and easy and you can do so at virtually any time. Even if you have changes you don't want to commit (which you often can anyhow as you're working in a private branch), you can use git-stash to stash them away and apply them later, possibly to another branch. (if you found yourself working on the wrong branch)
git stash save "Half finished fixes for epoch bug."
Now that you've branched you can get to work modifying, adding, and deleting files similar to the way you would use svn.
[gituser@git manager]$ echo "blahblahblah" > newfile [gituser@git manager]$ git add newfile
[gituser@git manager]$ git-rm Worker.java rm 'java/code/src/com/redhat/rhn/manager/Worker.java' [gituser@git manager]$ git status
[gituser@git manager]$
Now it's time to commit (and you can and should commit often, as often as you like).
IMPORTANT NOTE: By default, files you have modified will not be included if you run git commit
. Normally you will probably just want to use the -a option to commit all modified files:
git commit -a If you wish to only include some of the modified files in your commit, you must do something like:
git commit modified-file1.txt modified-file3.txt Repeat for as many commits as you like on your branch until you're satisfied with your bugfix, feature, or whatever.
We have a convention for our commits. The simple rule is:
If you are working on a bugzilla or a feature (that should have a bugzilla associated with it) you should prepend your commit message with the bugzilla # followed by your message describing the commit.
BUGZILLA# - comment goes here
For example:
461162 - just add all the networks to the system profile in cobbler
If you are not working on a bugzilla in relation to the change just type your message as normal.
NOTE: These instructions apply to those with commit access.
When the time comes to push the changes in your local branch out to the repository, we need to pull down any changes others have pushed since we created that branch and resolve conflicts if necessary. There are two tools to do this, merge and rebase, but for smaller individual changes we will use rebase.
Git rebase essentially checks what commits are in some destination branch that your local branch does not have, what commits are in your local branch that aren't in the destination, then pulls down the new remote commits and re-applies your commits on top of it. It essentially re-does your work again patch by patch, on top of the latest state of the destination branch. (usually master)
To push your fixes in 'mybugfix' out you would do the following:
git checkout master git merge mybugfix # merge in the commits from your local branch (use --squash if you'd like 1 to fold them into one commit) git pull --rebase # pull latest commits down from master, re-apply ours on top gitk --all # review the status of the repository (can also use "tig" command for this) git push origin master # push commits from local branch master to remote branch with the same name
If you feel you cannot keep track of local branches (or just do not want to use them), you can work directly in your master tracking branch. Please just be sure to use the --rebase option when you have unpushed changes committed and go to pull from master.
git pull --rebase
Not using --rebase means that git pull will merge the remote references in instead, resulting in many of the dreaded "merge commits" in the git history which clutter up the log unnecessarily.
Note that using local branches offers some substantial benefits and is the recommended approach, see Understand Branches section below for more.
For those without commit access, or those who just prefer to submit a patch for review, the steps are as follows:
git checkout master git pull # fetch latest remote changes into master (should apply clean) git checkout mybugfix # return to your branch git rebase master # re-apply you changes on top of current state of master git format-patch master # generate a patch against master
This will generate a number of patch files, one for each commit in your branch.
Applying patches is as simple as:
git am 0001-incoming.patch # apply the patch in your current branch
Note that if you examine the git log, both the git identity of the patch author and the patch committer are tracked.
The use of branches has proven one of the more difficult things to adapt to for those coming from non-distributed scm's like subversion.
- Branches can be local (only in your git clone) or remote (i.e. they exist in the remote repository).
- List local branches with git branch, remote branches with git branch -r.
- When listing remote branches you'll see things like "origin/master". Origin is a default remote reference created when you do a git clone of some remote repository. Because git is designed for highly distributed workflow, it is possible to actually have MANY remote references configured within one git repository and push/pull from them all to collaborate with others. For our situation however all you really need to know is that "origin" is a remote reference to the spacewalk git repository, thus origin/master refers to the remote master branch.
- After doing a git clone, a local "master" branch is configured for you as a tracking branch for origin/master. You can create other tracking branches easily with git checkout --track -b mylocalbranch origin/someremotebranch (commonly used to work release or feature branches)
- If you git checkout origin/somebranch you've checked out a pointer to a remote branch, and thus you cannot commit here. (git will indicate this clearly) If you wish to make some modifications you just need to create a local branch off of that commit with git checkout -b mybranch.
- The main advantage to working in local branches is context switching. If working in a local branch and something comes up, (you need to go to master and look at something, fix a bug in some release branch, change to some other urgent task, etc.) just commit your work (or stash it) and go about your business. Return to your branch when you wish to resume work.
See what remote branches exist:
git branch -r
Create a new remote branch (based on an existing local branch):
git-push origin localbranch:refs/heads/newremotebranch
Checkout a local copy of a remote branch, originally you will need to track it so you can keep up with changes automatically. (consider this like your local "master" branch, except push/pull works against the remote branch instead of the remote master)
git checkout --track -b localbranch origin/remotebranch
Push latest changes from your local branch back to the remote branch:
git-push origin localbranch:remotebranch
If you ever want to use that branch again, you can just do:
git-checkout my-branchname
As with any long lived branch it's important to sync it with master periodically to prevent a merge disaster when you rejoin. In the case of long lived branches, use git merge instead of git rebase.
git checkout master git pull git checkout myremotetrackingbranch git merge master
Deleting a remote branch entirely: For all intents and purposes lets leave remote branches for the time being. Deleting them can be a little dangerous if you were to happen to do it on a RELEASE branch, and send us digging into scm backups to recover it. Check with someone if you have a temporary remote branch that you really want deleted. Otherwise we'll probably clean them up from time to time.
- It is a very good idea to treat your tracking branches as you do master. I.e. do not work directly in them, but rather create local branches off them to do your work and only use the tracking branch for pulling down changes and then merging in your work and immediately pushing it out.
- Running "git push" without specifying the branches as per the above syntax will cause git to attempt to push your local commits from all tracked remote branches back to their respective remote counterparts. This is definitely counterintuitive. However if you only use tracking branches for pushing/pulling changes and instead work in local branches, this will not be an issue for you. Use the recommended git push origin master:master syntax to be specific.
- You will see git pull fetching information about other remote branches, please note this is not applying those commits in your local tracking branch, it's just updating internal info and noticing that something has changed in the remote repo. Only the current branch will have it's changes applied.
Consider the following two branches with some commits:
Branch 1: A -> B -> C -> D -> E
Branch 2: A -> B -> C -> C1 -> C2
Here Branch 2 was created off of Branch 1 at the point in time when commit C was the most recent.
If we were to merge Branch 2 directly into Branch 1 (git checkout branch1 && git merge branch2) we could end up with:
Branch 1: A -> B -> C -> D -> E -> C1 -> C2 -> MC
Where MC is a "merge commit" identifying that a merge took place, what files were involved, and most importantly what files conflicted and had to be resolved.
If however we ran a rebase first (git checkout branch2 && git rebase branch1 && git checkout branch1 && git merge branch2) we would end up with the following:
Branch 1: A -> B -> C -> D -> E -> E1 -> E2
Where E1 and E2 are the commits previously known as C1 and C2, but re-applied on top of commit E instead.
The key difference is that rebase re-applies the new commits on top of the current state of the tree, whereas merge brings them in and adds them to the history, plus a merge commit. Rebase is generally much better (cleaner history) for small individual changes, while we frequently use merge for long lived release/feature branches that are not so simple.
Git will normally merge just about anything that can be safely automatically merged but conflicts can still occur. If you get the dreaded Automatic merge failed; fix conflicts and then commit the result. during a merge, here is how you can go about resolving it.
First list the files that require merging:
git ls-files --unmerged
Choose a file, open it, and search for the conflict markers just as you would when resolving a svn conflict.
If the conflict is not obvious you can use gitk to view ONLY the commits made in each branch that are resulting in your conflict:
gitk --merge path/to/file
If you'd like try a three way merge, try:
git mergetool -t meld
This will fire up a merge tool (in my case "meld") with three columns. On the left will be the file as it was in your current branch, in the middle the state of the file now (after automatic merging including conflict markers), and on the left the state of the file from the branch being merged in.
Once you've resolved your conflict:
git add path/to/resolved/file
And proceed to the next file.
Once you've resolved all conflicts:
git commit -a
Git will pre-populate the commit message with the data for your merge commit including what files conflicted. Add anything you feel is necessary and you're ready to push out your changes.
If you run into a situation where your git tree is seemingly confused and you cannot pull or checkout another branch, here are the steps you can take to correct it. This can sometimes happen when files in your checkout are owned by root (seems to be happening for some users using our devel setup) or when a merge or pull encounters conflicts that are never resolved.
- Make sure you don't actually need any of the files that git shows as modified or untracked, because these steps below will delete them.
- Make sure all files in your git tree are owned by the correct user.
- Get rid of modified files by resetting your tree to the last known commit for the branch you're on: git reset --hard HEAD
- Cleanup files git lists as untracked with: git clean -xfd (be careful here, this will delete any untracked files in your checkout) If you want to keep some of them, back them up or remove untracked files manually.
- Verify your git tree is now clean, then try to git pull or git checkout as you were before.
Revert a file with uncommitted changes you do not wish to keep:
git checkout path/to/file/to/revert.txt Revert all your current uncommitted changes:
git reset --hard HEAD Revert changes from a past commit (generates a new commit):
git revert a31f80910768ba2232c796b814be11d064421f19
View diff of changes in a past commit:
git show a31f80910768ba2232c796b814be11d064421f19
View diff of changes between two past commits:
git diff baf1a1490e205f821fbcc9c4ec2581728afd1c14..a31f80910768ba2232c796b814be11d064421f19
View a part revision of a specific file (feel free to pipe the output somewhere if you like). Make sure to use the full path to the file as it would be from the root of your git repository:
git show SHA1:java/code/src/com/redhat/rhn/common/db/datasource/xml/Channel_queries.xml
Replace file with a past version. Note that after doing this you still need to commit the change:
git checkout SHA1 -- path/to/file
Once you make changes, you can see the differences you've made since last commit:
git diff HEAD
The following commands can help with formatting patches.
git-format-patch git-send-email
Finished with your branch? Want to get rid of it? Then
git branch -d mybranch If you get a warning about unmerged changes, you can force the removal with
git branch -D mybranch
See what you have committed but not pushed:
git log --pretty=oneline origin/master..master
Ever find yourself working on something then have to go fix a bug? You can stash away your files, fix your bug, then go back to what you were doing. Save your work:
git stash save mymessage FIX YOUR BUG, commit and push. See what you have stashed: git stash list git stash apply stash@{0}
-
Install tig for a very handsome mutt-like command line app for browsing git history and diffs.
-
Install gitk for a brutally ugly yet still rather useful graphical tool for doing the same thing.
-
Keep track of what branch you are in by looking at your prompt. Update your prompt as follows:
PS1="[\u@\h \W$(git branch 2> /dev/null | grep -e '* ' | sed 's/^..(.*)/{\1}/')]$ "
Here is what you will see when you cd into a git repository:
[speedy@gonzalez spacewalk{master}]$ If you chdir to a non git repository, your prompt will look like normal:
[speedy@gonzalez ~]$
Here is an example workflow where we are working on a spec file and testing with tito build --test --srpm
.
For readability we will name different SHA1 number as SHA1-A, SHA1-B, ... You can find tito in the rel-eng/bin directory of your git checkout, and it can be called relatively or added to your path.
When we begin, the tree has SHA1-A
$ git commit -a -m 'edit spec to comply with fedora guidelines'
After the commit it now has SHA1-B
$ gitk --all
Review the changes, then do some testing. You find error,for example, rpmlint complains. Edit the spec.
$ git commit -a -m 'shut up rpmlint'
The tree now has SHA1-C
$ tito build --test --srpm
We still find errors, re-edit the spec file.
$ git commit -a -m 'I hate you rpmlint'
The tree now has SHA1-D
$ tito build --test --srpm
Again, still more errors, edit the spec file.
$ git commit -a -m 'say again something rpmlint and I kill you'
The tree now has SHA1-E
$ tito build --test --srpm
Finally, rpmlint
is silent. We will reject all our previous commits,
but they are still in git you may in following steps do in gitk 'Update'
to see what happened
do not do 'Reload' as you will not see dead part of tree.
$ git reset --hard SHA1-A
We now pick up all the previous changes and apply it to our working copy without commits.
{{{ $ git cherry-pick -n SHA1-B $ git cherry-pick -n SHA1-C $ git cherry-pick -n SHA1-D $ git cherry-pick -n SHA1-E }}}
Now we have one commit including all of the changes:
$ git commit -a -m 'edit spec to comply with fedora guidelines'
Confirm this is what we wanted and we are ready to merge and push
$ tito build --test --srpm