Version 21 (modified by mkae (Marko Käning), 8 years ago) (diff) |
---|
Working with Git
This document contains information about working with the Git version control system, tailored to developers familiar with Subversion.
Initial setup
Start with some basic setup (based on KDE's configuration hints):
$ git config --global user.name "Your Name" $ git config --global user.email YOUR_MACPORTS_HANDLE@macports.org # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # if not registered with a MacPorts handle # simply use another email address
Additionally one should define a few (not necessarily global) presets for working with your clone of the MacPorts repository:
$ git config --global push.default nothing $ git config --global branch.autosetuprebase always $ git config --global core.excludesfile ~/.gitignore_global $ git config --global commit.template ~/.git-commit-template $ git config --global color.ui true
which avoids that you accidentally push changes into a wrong branch at origin
, makes sure that your current changes get always auto-rebased ontop of the pulled changes from origin
, adds an excludes file as well as a commit template and enables colorizing git
's console output.
T.B.D.: A MacPorts'ish ignore file .gitignore_global
should be supplied here, perhaps based on this?!!
T.B.D.: A git commit template file .git-commit-template
should be supplied here, perhaps based on this?!!
T.B.D.: To make life easier when working with a couple of remotes one should define also some URL renaming for the MacPorts repo as it gets done e.g. for KDE like this:
[url "git://anongit.kde.org/"] insteadOf = kde: [url "git@git.kde.org:"] pushInsteadOf = kde:
which needs an update once it's clear which URL the MacPorts git
repo is going to have.
Common git
tasks while working with ports
Then
svn checkout https://svn.macports.org/repository/macports/trunk/dports
becomes
git clone git@github.com:macports/ports.git
When you clone you will get the entire history of the ports tree, with the latest version being checked out in the filesystem.
After you make a change, you can run git status
and get something like this.
On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: aqua/iTerm2/Portfile no changes added to commit (use "git add" and/or "git commit -a")
What this tells me, is that I've changed a Portfile, but not done anything.
After that, you can add the files that you want to add to your commit using git add aqua/iTerm2/Portfile
.
Now, git status
will look like:
On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: aqua/iTerm2/Portfile
Then run git commit
and everything is set. On your machine. To push to github you then have to run git push
.
Common git
tasks while working with MacPorts base
Checking out a working copy
The source code of MacPorts itself is no longer managed in the same repository as all ports. Contrary to Subversion, checking out a sub-directory of a repository is not possible with Git. In order to avoid that all port maintainers have to clone the complete history of MacPorts base as well, the Subversion repository has been split into multiple separate repositories. MacPorts base is now available using
git clone git@github.com:macports/base.git # or git clone https://github.com/macports/base.git # if SSH does not work on your network
See the section on repository splitting during the export to get an overview of where a path in the old Subversion history is now available in Git.
Committing changes in your working copy
A fundamental difference between Subversion and Git working copies is that svn commit
by default commits all changes in your working copy, but git commit
by default commits none. Git uses a staging area called "index" that allows you to mark changes for inclusion in the next commit. To add changes to the next commit, use
git add <filename>...
git status
gives you an overview of the current index and your working copy. Additionally, it lists the commands to revert local uncommitted modifications (git checkout -- <filename>
) and to remove files from the next commit, but preserve the modifications in your working copy (git reset HEAD <filename>
).
Once you have chosen which files to include in your next commit using git add
, it is a good practice to review this list using
git status
and show the diff to be committed using
git diff --cached
If you are not satisfied with your changes, you can keep changing your files. Note that you will have to add any new modifications to the index using git add
again. Once you are satisfied with your change run
git commit
which prompts you for the commit message. See the section on commit messages in git for more information on git conventions and expectations in commit messages.
Because of Git's distributed nature, a commit on your local machine is not immediately available on the central server, like it was the case with Subversion. This means that you can continue to prepare further changes in additional commits before you publish your changes as a set. In fact, it is a very common practice in Git to do many small changes that are logically consistent in themselves and then publish them in one step.
If you have commit access, you can publish your commits using git push <remote-name> <branch-name>
. <remote-name>
is the name of the repository to which you want to push. The most common push target is the location you initially cloned, which is automatically named origin
. <branch-name>
is the name of the branch you want to push. The Git equivalent to Subversion's trunk
is called master
. In most cases you do not need to specify <remote-name>
or <branch-name>
:
git push
Note that the push will fail if the remote repository has new changes. Contrary to Subversion, it does not matter whether your changes conflict with the remote ones. If this happens, you must update your local working copy as described in the section on fetch the latest changes and re-try the push.
T.B.D.: See config setting for push.default
above, which requires the user to explicitly state the target branch, i.e. it should now be e.g. git push origin master
.
Merge a single change from master into a release branch
The equivalent to Subversion's svn merge -c <revision> .
is git cherry-pick
. Use git cherry-pick
to apply a single change from master to a release branch. To do this, look up the commit ID of the commit you want to pick:
git log # copy the commit ID
Switch to the target branch of the cherry pick:
git checkout release_2_3
Cherry-pick the commit. It is good practice to pass -x
to git cherry-pick
, which will automatically add a "Cherry picked from commit <commmitID>" line to the commit message of your cherry pick. You will have the option to modify the commit message, e.g. to describe why the backport was necessary.
git cherry-pick -x <commitID>
Finally, push the new commit using
git push [origin <branchname>]
T.B.D.: Here as well see config setting for push.default
above requiring a target branch, i.e. it should now be e.g. git push origin master
.
Common git
tasks & notes about MacPorts' Subversion export
Fetching the latest changes
Git's equivalent to svn update
is a little more complicated due to Git's distributed nature. Most of the complexity is not visible if you do not have commits in your working copy that have not been pushed yet. If both the local and the remote repository have changes (git calls them "diverged"), you will run into one of Git's core principles: Every commit has (at least) one parent commit, i.e. the commit history forms a directed acyclic graph.
Background knowledge
A picture is worth a thousand words:
A --- B --- C ---- R1 ---- R2 ---- R3 <= origin/master \ +--- L1 ---- L2 <= master
A, B and C are commits that are both in your local and in the remote repository. R1-3 are commits that have been pushed into the remote repository "origin"'s master branch while you were working. L1 and L2 are commits you prepared locally on your master branch. Git offers two different ways to bring R1-3 into your local branch:
Merging
A merge commit, created by git merge
, is a commit that has multiple parents. If no conflict occurs, merge commits do not usually have a diff attached (i.e. they do not modify files). On conflict, merge commits contain the diff that resolves the conflict. In pictures:
A --- B --- C ---- R1 ---- R2 ---- R3 <= origin/master \ \ +--- L1 ---- L2 ------ M <= master
The new commit M is the merge commit and can be pushed back to origin. This preserves the information that work was done in parallel, but unfortunately tends to mess up the history graph. See the attached screenshot of a commit history that always merges. To avoid this, you can instead rebase your changes.
Rebasing
Rebasing commits rewrites their parent commit IDs and avoids the need for a merge commit. Running git rebase origin/master
will take all commits in your local working copy that are not yet pushed and attach them after the end of origin/master
, which yields this picture:
A --- B --- C ---- R1 ---- R2 ---- R3 <= origin/master \ L1' ---- L2' <= master
Note that L1 and L2 have been modified by this operation; their commit IDs changed because of that. This new state can be pushed back to origin without the need for a merge commit, and the history graph will stay linear. We recommend that all developers rebase their changes rather than merge when conflicts occur during pushing.
Putting the background knowledge into production
First, get all new commits from the remote repository using git fetch <remote-name>
, where <remote-name>
identifies the repository from which you want to fetch and defaults to "origin":
git fetch
Then, rebase your local changes (if any) on top of any new changes in the remote repository and fix any conflicts that occur:
git rebase origin/master
Because these two operations are very common, Git offers a shorthand for them:
git pull --rebase
Note: git rebase
requires that you do not have uncommitted modifications in your working copy. If you have modifications, you can temporarily save them using git stash
and restore them after the rebase using git stash pop
.
Warning: git pull
without the --rebase
flag is a shorthand for git fetch && git merge origin/master
, which will automatically create a merge commit if it thinks that's necessary.
T.B.D.: See config setting for branch.autosetuprebase
above.
Commit messages
There are a number of conventions to writing Git commit messages. For a detailed explanation, see http://chris.beams.io/posts/git-commit/. As a tl;dr, here are seven short rules:
- Separate subject from body with a blank line
- Limit the subject line to 50 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line
- Wrap the body at 72 characters
- Use the body to explain what and why vs. how
T.B.D.: See todo on ~/.git-commit-template
above.
Reverting changes
Subversion has two methods for reverting changes: svn revert
, which drops uncommitted local modifications and restores the committed state and svn merge -c -12345
to undo committed changes.
Due to Git's distributed nature, there are three stages that can be reverted:
- To drop uncommitted modifications, use
git checkout -- <filename>
. If you had already added the file to the index usinggit add
, you have to unstage it first usinggit reset HEAD <filename>
.git status
prints these commands, so you don't have to remember them. - To undo a change that has already been committed and pushed, use
git revert <commitID>
. This will create a new commit that applies the inverse diff. Note that you still have to push this commit to publish it. - To throw away all changes that you have locally committed but not yet pushed, use
git reset --hard origin/master
. You will loose all your uncommitted and committed modifications. If that is not what you want, Git provides a variety of tools that allow you to change commits that you have not pushed yet (and theoretically also commits that have already been pushed, which will prevent you from pushing any changes again). Since this is an advanced topic it will not be covered here. As a pointer for further research, look forgit commit --amend
to change the topmost commit andgit rebase --interactive
, the so-called "interactive rebase", to change older commits.
Repository split
While converting to Git, several subfolders of the Subversion repository have been mapped to different Git repositories. Since Git does not allow to clone sub-directories of a repository, this reduces the size of the repositories that developers must check out to work on different parts of MacPorts. Here's a short non-exhaustive list of mappings:
Subversion (Mac OS Forge) | Git (GitHub) |
---|---|
trunk/base | macports/base.git |
trunk/dports | macports/ports.git |
trunk/dports/PortIndex,PortIndex.quick | deleted |
trunk/doc-new | macports/guide.git |
trunk/doc | macports/guide.git, branch "doc-old" |
trunk/www | macports/www.git |
trunk/base/portmgr/jobs | macports/infrastructure.git, subfolder "jobs" |
contrib | macports/contrib.git |
contrib/MacPorts_Framework,Pallet | macports/pallet.git |
contrib/mpab | macports/mp-buildbot.git, branch "mpab" |
contrib/mp-buildbot | macports/mp-buildbot.git |
contrib/buildbot | macports/infrastructure.git, subfolder "buildbot-old" |
contrib/buildbot-test | macports/infrastructure.git, subfolder "buildbot" |
users/dluke | deleted, exceeds GitHub size limit, only contains old files |
users/jberry | deleted, exceeds GitHub size limit, only contains old files |
users/pipping | deleted, exceeds GitHub size limit, only contains old files |
users/* | separate repository in macports/users/* |
distfiles | deleted |
downloads | deleted |
branches | branches of the appropriate repository/-ies |
tags | annotated tags in the appropriate repository/-ies |
For the full set of conversion rules, see https://github.com/neverpanic/macports-svn2git-rules/blob/master/gitconversion.rules.
Attachments (1)
-
commit-history-with-excessive-merging.png (345.7 KB) - added by neverpanic (Clemens Lang) 8 years ago.
Attach picture that shows the result of excessive merging
Download all attachments as: .zip