Maintaining Shared Git Repositories on oss.oracle.com
Most oss.oracle.com projects use a shared central repository. That is, multiple maintainers can upload (push) changes. This document is for those maintainers.
All pushes are via SSH. As a maintainer, you have access to push to oss.oracle.com, and you are a member of the appropriate group to write to the repository. The push URL is slightly different than the clone/pull URL, so ask another maintainer if you don't know it. I'll just use ssh://oss.oracle.com/path/project.git, where path stands in for the full path.
No, Really, Work on a Branch!
Don't ever make changes on master. It's not just for convenience. If you accidentally type git push without any arguments, the defaults might very well push your current master changes up to oss.oracle.com. That's probably not what you wanted. Work on a branch, and explicitly tell git push what branch you are pushing from and to.
Fast Forwards Only
The most basic "merge" in Git is the "fast-forward". What this really means is that all of your changes start where the central repository already is. This is just like Subversion. In Subversion, you could not commit if you hadn't yet updated to the latest upstream version. The same with the Git repositories on oss.oracle.com. They are configured to reject all changes that are not fast-forward. The section on git rebase at the bottom has more information on the git equivalent of svn update.
Get a Review Acked-by
While Git doesn't require this, oss.oracle.com Policy does. All commits need two sign-offs. The Signed-off-by from the author and an Acked-by from a reviewer. A regular contributor doesn't have to worry about this. They send their change with their own Signed-off-by as author, and then the maintainer accepting it includes their Signed-off-by as a reviewer. As a maintainer, you need to make sure you get review as well. Do not commit with only your own sign-off.
A Simple Workflow
Here's a simple workflow for a change to a project on oss.oracle.com.
- Clone the repository and set up a working branch
$ git clone git://oss.oracle.com/git/project.git localdir $ cd localdir $ git branch workingbranch master $ git checkout workingbranch
- Make some changes.
$ vi foo.c ...hack hack hack... ...test test test... $ git commit -s -a $ vi bar.c ...hack hack hack... ...test test test... $ git commit -s -a
- Get review from someone. Put their sign-off in a file.
echo "Julie Hacker <email@example.com>" > /tmp/signoff
- Apply the review sign-off.
$ git branch to-push master $ git checkout to-push $ git format-patch -C -k --stdout master..workingbranch > /tmp/changes-to-push $ git applymbox -k /tmp/changes-to-push /tmp/signoff
- Push the changes to oss.oracle.com
$ git branch master workingbranch * to-push $ git push ssh://oss.oracle.com/path/project.git to-push:master
Delete the to-push branch, as you are done with it.
$ git checkout workingbranch $ git branch -D to-push
In the simple workflow above, the git push command was
git push ssh://oss.oracle.com/path/project.git to-push:master
git push <repository-url> <local-branch>:<repository-branch>
The changes from the local branch to-push were uploaded to the central repository at repository-url in the branch master. So your changes are now a part of master. We explicitly specify the local and repository branches, so that Git cannot be confused. This is important!
You can also push to branches on the server. You would just give a different remote branch.
Working on a Public Branch
Local branches are great for making your changes before you push them upstream. But sometimes you want a branch to be visitble to the world. New features often are better tested and discussed this way.
You can see all the public branches via the git branch -r command.
$ git branch master * workingbranch $ git branch -r master * workingbranch origin/HEAD origin/master origin/projects-1.0 origin/new-feature-X
What's going on here? With the git branch command, you only see the branches in your local repository (master and workingbranch). Add the -r option, and you see all of the "tracking branches". These are branches that are on the central repository, but do not exist for you locally. The nice thing about tracking branches is that you can check them out! To work on the new-feature-X branch, do:
$ git branch new-feature-working origin/new-feature-X $ git checkout new-feature-working
When the time has come to push, you create your to-push source just as before, and then push to the feature branch instead of master.
$ git push ssh://oss.oracle.com/path/project.git to-push:new-feature-X
Just like master, all pushes must be fast-forward. Any "major" branch requires a review sign-off as well. A major branch is any branch that becomes released software.
Rebasing for Fast Forwards
What Happens in Subversion
When using a centralized SCM like Subversion, you cannot commit if your working tree isn't up-to-date. This means it must have the most recent changes from the server. This happens in the following scenario:
- You check out revision 100 of a project
- You change some things
- Meanwhile, Sam commits a change.
- You try to commit your changes.
At this point, Subversion gives you an error. Your tree is not up-to-date. You then use the svn update command to pull Sam's changes into your working tree. Subversion tries to merge these changes with yours. Most of the time, it works just fine. You now have a working tree based on the latest revision. You test it again, and then you commit.
Trying the Same Thing in Git
Let's try a very similar process in Git. Assume your changes are already signed off and in the to-push branch.
$ git push ssh://oss.oracle.com/path/project.git to-push:master Error: not fast-forward
Unlike Subversion, Git can merge changes that aren't sequential. However, this requires a working tree and possible human interaction. The central repository has neither, so it rejects any push that requires a merge. A fast-forward, on the other hand, is like a Subversion commit. It is sequential and safe.
What you want is effectively the same behavior that happens in Subversion. You want the underlying software updated to match the central server, and then you want to apply your changes on top of it.
Doing it By Hand
Let's do the work by hand to understand what is happening. Here's what we'll do.
- Generate patches from our changes
Update our local master to match the server
- Create a local branch to merge our changes
- Apply the patches to the merge branch
- Push them to the server
$ git status # On branch to-push nothing to commit (working directory clean) $ git format-patch -k -C --stdout master..to-push >/tmp/to-merge $ git checkout master $ git pull ...pulls updates from central repository... $ git branch merge-push master $ git checkout merge-push $ git applymbox -k /tmp/to-merge ...applies the changes... $ git push ssh://oss.oracle.com/path/project.git merge-push:master
First, note that we do not provide a signoff file to git applymbox. Your signoffs should be correct already in your to-push branch.
In the git applymbox process, Git is applying your changes. Just like a Subversion update, this can go smoothly or explode with conflicts. If there are conflicts, Git will stop at the first problem. You can then fix up the conflict and tell Git to continue. For more information about resolving these merge conflicts, see Git's documentation.
I'm sure you've noticed that this is a pain. For Subversion, it's just
$ svn update
Never fear. Git does have a way to do this in one shot. I just wanted you to understand the mechanism, because it is a much more powerful operation in a distributed SCM.
Using git rebase
The git rebase command performs the process of saving off your changes, updating the start point, and then reapplying your changes. The terminology comes from treating your starting point as the base for your changes. The version of master you started from when you created your working branch is your original base. You now want to change your base to the current version of master. You want to "rebase to master".
$ git status # On branch to-push nothing to commit (working directory clean) $ git checkout master $ git pull ...pulls updates from the central repository... $ git checkout to-push $ git rebase master ...does all the stuff we did by hand above... $ git push ssh://oss.oracle.com/path/project.git to-push:master
Just like above, any merge conflicts will cause the rebase to halt at the point of the problem. You resolve them just as you would when doing this by hand.