Difference between revisions of "Git tips"
Salty-horse (talk | contribs) (Start the Git tips page) |
(Add Fingolfin's Github Fork Workflow Notes.) |
||
(7 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
The following is a list of tips, | The following is a list of tips, dos and don'ts for working with the ScummVM Git repositories, compared to working with Subversion. | ||
== Organization == | == Organization == | ||
Development is done on the ''master'' branch, which is equivalent | Development is done on the ''master'' branch, which is equivalent to the old Subversion ''trunk''. Release branches are branched from, and merged to, ''master''. Tags are always annotated (<tt>git tag -a</tt>) in order to include a timestamp. | ||
== | Please keep the "official" branches on the scummvm repository to "master" and release-*. If you want to use long-lived branches for other projects, either create them on your own forked repository, or consult the team to decide if they belong in the main repository. | ||
== Don'ts == | |||
* '''Never ever''' use '''''git push -f''''' without discussing it on the mailing-list. That operation deletes commits from the server, and will cause problems to others working with the repository. | * '''Never ever''' use '''''git push -f''''' without discussing it on the mailing-list. That operation deletes commits from the server, and will cause problems to others working with the repository. | ||
* Try to not create pointless merge commits. This will be | * Try to not create pointless merge commits. This will be explained in the [[#Workflow]] section. If you do make merge commits, the branch you're merging should include commits that are "bunched together" on purpose. This makes them easier to review or revert. | ||
* Do not create additional branches on the main scummvm repository without first consulting with the rest of the team. | |||
== Workflow == | == Workflow == | ||
There are several ways to work with git. Here we'll discuss two: You could make your commits on the master branch, or you could make them on a separate branch, called a ''topic branch'' and then merge them into the master branch, before pushing. We'll cover the basics of pushing first | There are several ways to work with git. Here we'll discuss two: You could make your commits on the master branch, or you could make them on a separate branch, called a ''topic branch'' and then merge them into the master branch, before pushing. We'll cover the basics of pushing first. | ||
=== How to push changes === | === How to push changes === | ||
The important thing to realize is that when pushing changes to the remote server (aka "''origin''"), your changes '''''must''''' be based on the remote | The important thing to realize is that when pushing changes to the remote server (aka "''origin''"), your changes '''''must''''' be based on the remote repository's branch you wish to push to. Subversion allowed you, in some cases, to commit without having the most up-to-date working copy. Git doesn't. | ||
A normal ''git push'' without any errors is one that only adds new commits, that (in their metadata) point to existing commits. | |||
If you try to push local commits when the ''origin'' has commits you do not have, you will get an error similar to this: | |||
To git@github.com:scummvm/scummvm.git | |||
! [rejected] master -> master (non-fast-forward) | |||
error: failed to push some refs to 'git@github.com:scummvm/scummvm.git' | |||
To prevent you from losing history, non-fast-forward updates were rejected | |||
Merge the remote changes before pushing again. See the 'Note about | |||
fast-forwards' section of 'git push --help' for details. | |||
To fix this problem, we need to fetch the remote commits we do not have, and then either "put" our new commits on top of the remote repository's, or create a new "merge commit" that combines the remote repository's commit and yours. | |||
Let's introduce several related commands: | |||
* '''git fetch''' — Retrieves commit objects from the remote repository. You can always run this command without fear. It doesn't modify any of your local changes. | |||
* '''git merge''' — Creates a "merge commit" from 2 or more branches. If only one branch diverges from the root, it will perform a "fast-forward" and no merge commit will be created (this is configurable). | |||
* '''git rebase''' — Moves (i.e. reapplies) a series of commits onto a different base. | |||
* '''git pull''' — Perform a ''fetch'' followed by a ''merge'' (the default behavior) or ''rebase'' (with a flag). | |||
So in our case, if we made a few unrelated commits, we just want to apply them on top of the remote commits, and to do that we can use <tt>git pull --rebase</tt> which will get the remote changes, and reapply our local changes on top of the remote's. From that state, we could run <tt>gitk</tt> or any other visualization tool, see that everything is to our liking, and <tt>git push</tt> our changes up. | |||
==== Configuring <tt>git pull</tt> to rebase automatically ==== | |||
If you like making commits on the master branch, and want <tt>git pull</tt> to always rebase your local changes on top of the fetched commits, run this configuration command to modify the repository's <tt>.git/config</tt> file: | |||
$ git config branch.master.rebase true | |||
=== Branchy development === | === Branchy development === | ||
TODO | TODO | ||
=== Github Fork Workflow === | |||
==== Preliminaries ==== | |||
I assume that you are familiar with the basics of git, have a github account, and have adjusted your local git config to contain info about your github user account, as per <http://help.github.com/git-email-settings/>. | |||
In addition, if you are on a unixy system, I recommend taking a look at the "hub" tool <http://github.com/defunkt/hub> which makes it easier to fork repositories, clone other people's repositories etc. But I will not assume that you are using it. | |||
==== What is a github fork and why use it? ==== | |||
A github fork is a clone of another github hosted repository (say, scummvm/scummvm) which is located under your github user account (say fingolfin/scummvm). It allows you to publish changes you make to ScummVM (preferably in a topic branch), but which you do not want to / cannot push to the regular scummvm repository yet. | |||
It's a good place to stage bigger changes. It's also very easy to ask the ScummVM team to review your changes made in a public github fork, and to optionally merge them into scummvm/scummv, via a so-called "pull request" <http://help.github.com/pull-requests/>. | |||
==== How to create a github fork? ==== | |||
See here <http://help.github.com/fork-a-repo/>. Essentially, you use the "fork" button on <https://github.com/scummvm/scummvm>. Then, clone your new fork. You can now work in this clone as in a clone of scummvm/scummvm, but pulls and pushes now go to / come from your fork. | |||
==== How to use your github fork? ==== | |||
In the following, I will try to describe a workflow that is hopefully effective, safe, and relatively easy to use. Other workflows are possible, and you are welcome to use them; the one here tries to minimize the risk of messing up the scummvm/scummvm repository, and to avoid situations in which you need to do complicated stuff you correct mistakes you made. | |||
Over time, your fork will start to *deviate* from scummvm/scummvm. In particular, it will not automatically receive updates made to scummvm/scummvm. So, you will want to regularly sync your fork and its master branch against the master branch of scummvm/scummvm. This is easiest if your fork's master branch never receives any custom commits; always keep it identical to (a possibly old version of) the scummvm/scummvm master branch. All your own work should go to branches, ideally one branch for every topic you are working on (so-called topic branches). | |||
==== Initial Setup ==== | |||
This is how I did the initial setup for it for my personal fork: | |||
1) Clone the fingolfin/scummvm repository | |||
git clone git@github.com:fingolfin/scummvm.git scummvm-fingolfin | |||
cd scummvm-fingolfin | |||
2) add a "remote" for the official scummvm/scummvm repository (I will use "upstream" as remote name, to match the github docs; personally, I prefer using the remote "username", so in this case I would have used "scummvm" instead of "upstream". Pick whatever you like) | |||
A read-only remote can be added like this: | |||
git remote add upstream git://github.com/scummvm/scummvm.git | |||
If you have write access, you may want to instead add a read+write remote, e.g. using the SSH protocol: | |||
git remote add upstream git@github.com:scummvm/scummvm.git | |||
3) Retrieve the latest data from scummvm/scummvm | |||
git fetch upstream | |||
==== Sync master branches ==== | |||
To sync my master branch with the upstream master branch, I can now do this, assuming that "master" is the currently active branch (if not, use "git co master" to switch to it): | |||
git pull upstream master | |||
git push | |||
==== Working with code ==== | |||
Usually, any code I make goes first into a branch: | |||
git co -b new-cool-feature | |||
Let's edit a file (e.g. modify base/main.cpp to print "hello, world"). Commit this as usual; note that the commit will land on the new-cool-feature branch, not master. | |||
... edit base/main.cpp ... | |||
git add base/main.cpp | |||
git ci -m "BASE: Add new cool feature" | |||
You can check the difference between the two branches: | |||
git diff master..new-cool-feature | |||
Now we want the rest of the world to be able to see our marvelous new code, so let's push it to a new branch on our github fork, with identical name: | |||
git push origin new-cool-feature | |||
With this we have established a connection between our local "new-cool-feature" branch, and the "new-cool-feature" branch on github. From now on we can just do "git push". So let's edit base/main.cpp again, this time maybe by printing a "goodbye, world" at the end. Then, let's commit and push the changes: | |||
... edit base/main.cpp ... | |||
git add base/main.cpp | |||
git ci -m "BASE: Even better new feature" | |||
git push | |||
You can look at the result here: <https://github.com/fingolfin/scummvm/tree/new-cool-feature> | |||
At some point, you may want your work to be integrated into the official ScummVM tree. As mentioned above, the easiest way (if you are not a committer) probably is to submit a pull request <http://help.github.com/pull-requests/>. Even people who do have write access often use this, in order to receive feedback on their code. | |||
If you have write access and just want to integrate your changes, do the following (I'll assume you do a merge here, but you can of course also do a rebase): | |||
git co master | |||
git pull upstream # ensure we are in sync with upstream | |||
git merge new-cool-feature | |||
git push upstream # push to upstream | |||
git push # push to your fork | |||
[question to the experts: maybe we want to suggest use of --no-ff for the merge ?] | |||
Note: In reality, I sometimes make an exception and skip the creation of a (topic) branch. E.g. if I only want to commit a quick fix to some code. In that case, I might commit the fix into my local master branch, then push the result to both my fork and scummvm | |||
git co master | |||
git pull | |||
... edit files, add and commit them ... | |||
git push upstream | |||
git push | |||
==== Cleaning up your existing clones / checkouts ==== | |||
Maybe you were already using a fork, but were following a different model (or no model at all ;) and would like to adapt to this. E.g. maybe you made commits directly to your master branch, then merged changes from scummvm/scummvm's master branch into that, and now have a messy history which intermingles your own work with upstream work. | |||
Cleaning this up is usually quite simple. Let's assume the most simple case, namely that you made no branches at all and did all your work on master. (If you did use branches, you need to adapt this. Feel free to ask here or on our IRC channel if you need help.) | |||
So, let's assume you are in your local clone of your fork, with master as sole active branch. Make sure that the upstream remote has been added, as described above, so | |||
git remote add upstream git://github.com/scummvm/scummvm.git | |||
or | |||
git remote add upstream git@github.com:scummvm/scummvm.git | |||
Now, we fetch the upstream changes, and *rebase* our master branch on that: | |||
git fetch upstream | |||
git rebase upstream/master | |||
If all went fine, then this just completely. If you are in bad luck, this will cause an error, and you will have to resolve conflicts. It prints instructions for how to do that. Feel free to ask here or on IRC for assistance with resolving the issues. In the worst case, you can just use | |||
git rebase --abort | |||
to give up. | |||
But let's assume the rebase worked fine, or that you managed to make it work by resolving any conflicts. Now it's time to move any work you have done on master to a branch, so that we don't need to worry about this again in the future. Also, we will reset your local master branch to match the upstream master branch | |||
git br BRANCH_NAME | |||
git reset --hard upstream/master | |||
Finally, we can push your work back to your fork: | |||
git co master | |||
git push origin master | |||
git co BRANCH_NAME | |||
git push origin BRANCH_NAME |
Latest revision as of 15:30, 30 May 2011
The following is a list of tips, dos and don'ts for working with the ScummVM Git repositories, compared to working with Subversion.
Organization
Development is done on the master branch, which is equivalent to the old Subversion trunk. Release branches are branched from, and merged to, master. Tags are always annotated (git tag -a) in order to include a timestamp.
Please keep the "official" branches on the scummvm repository to "master" and release-*. If you want to use long-lived branches for other projects, either create them on your own forked repository, or consult the team to decide if they belong in the main repository.
Don'ts
- Never ever use git push -f without discussing it on the mailing-list. That operation deletes commits from the server, and will cause problems to others working with the repository.
- Try to not create pointless merge commits. This will be explained in the #Workflow section. If you do make merge commits, the branch you're merging should include commits that are "bunched together" on purpose. This makes them easier to review or revert.
- Do not create additional branches on the main scummvm repository without first consulting with the rest of the team.
Workflow
There are several ways to work with git. Here we'll discuss two: You could make your commits on the master branch, or you could make them on a separate branch, called a topic branch and then merge them into the master branch, before pushing. We'll cover the basics of pushing first.
How to push changes
The important thing to realize is that when pushing changes to the remote server (aka "origin"), your changes must be based on the remote repository's branch you wish to push to. Subversion allowed you, in some cases, to commit without having the most up-to-date working copy. Git doesn't.
A normal git push without any errors is one that only adds new commits, that (in their metadata) point to existing commits.
If you try to push local commits when the origin has commits you do not have, you will get an error similar to this:
To git@github.com:scummvm/scummvm.git ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to 'git@github.com:scummvm/scummvm.git' To prevent you from losing history, non-fast-forward updates were rejected Merge the remote changes before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details.
To fix this problem, we need to fetch the remote commits we do not have, and then either "put" our new commits on top of the remote repository's, or create a new "merge commit" that combines the remote repository's commit and yours.
Let's introduce several related commands:
- git fetch — Retrieves commit objects from the remote repository. You can always run this command without fear. It doesn't modify any of your local changes.
- git merge — Creates a "merge commit" from 2 or more branches. If only one branch diverges from the root, it will perform a "fast-forward" and no merge commit will be created (this is configurable).
- git rebase — Moves (i.e. reapplies) a series of commits onto a different base.
- git pull — Perform a fetch followed by a merge (the default behavior) or rebase (with a flag).
So in our case, if we made a few unrelated commits, we just want to apply them on top of the remote commits, and to do that we can use git pull --rebase which will get the remote changes, and reapply our local changes on top of the remote's. From that state, we could run gitk or any other visualization tool, see that everything is to our liking, and git push our changes up.
Configuring git pull to rebase automatically
If you like making commits on the master branch, and want git pull to always rebase your local changes on top of the fetched commits, run this configuration command to modify the repository's .git/config file:
$ git config branch.master.rebase true
Branchy development
TODO
Github Fork Workflow
Preliminaries
I assume that you are familiar with the basics of git, have a github account, and have adjusted your local git config to contain info about your github user account, as per <http://help.github.com/git-email-settings/>.
In addition, if you are on a unixy system, I recommend taking a look at the "hub" tool <http://github.com/defunkt/hub> which makes it easier to fork repositories, clone other people's repositories etc. But I will not assume that you are using it.
What is a github fork and why use it?
A github fork is a clone of another github hosted repository (say, scummvm/scummvm) which is located under your github user account (say fingolfin/scummvm). It allows you to publish changes you make to ScummVM (preferably in a topic branch), but which you do not want to / cannot push to the regular scummvm repository yet. It's a good place to stage bigger changes. It's also very easy to ask the ScummVM team to review your changes made in a public github fork, and to optionally merge them into scummvm/scummv, via a so-called "pull request" <http://help.github.com/pull-requests/>.
How to create a github fork?
See here <http://help.github.com/fork-a-repo/>. Essentially, you use the "fork" button on <https://github.com/scummvm/scummvm>. Then, clone your new fork. You can now work in this clone as in a clone of scummvm/scummvm, but pulls and pushes now go to / come from your fork.
How to use your github fork?
In the following, I will try to describe a workflow that is hopefully effective, safe, and relatively easy to use. Other workflows are possible, and you are welcome to use them; the one here tries to minimize the risk of messing up the scummvm/scummvm repository, and to avoid situations in which you need to do complicated stuff you correct mistakes you made.
Over time, your fork will start to *deviate* from scummvm/scummvm. In particular, it will not automatically receive updates made to scummvm/scummvm. So, you will want to regularly sync your fork and its master branch against the master branch of scummvm/scummvm. This is easiest if your fork's master branch never receives any custom commits; always keep it identical to (a possibly old version of) the scummvm/scummvm master branch. All your own work should go to branches, ideally one branch for every topic you are working on (so-called topic branches).
Initial Setup
This is how I did the initial setup for it for my personal fork:
1) Clone the fingolfin/scummvm repository
git clone git@github.com:fingolfin/scummvm.git scummvm-fingolfin cd scummvm-fingolfin
2) add a "remote" for the official scummvm/scummvm repository (I will use "upstream" as remote name, to match the github docs; personally, I prefer using the remote "username", so in this case I would have used "scummvm" instead of "upstream". Pick whatever you like)
A read-only remote can be added like this:
git remote add upstream git://github.com/scummvm/scummvm.git
If you have write access, you may want to instead add a read+write remote, e.g. using the SSH protocol:
git remote add upstream git@github.com:scummvm/scummvm.git
3) Retrieve the latest data from scummvm/scummvm
git fetch upstream
Sync master branches
To sync my master branch with the upstream master branch, I can now do this, assuming that "master" is the currently active branch (if not, use "git co master" to switch to it):
git pull upstream master git push
Working with code
Usually, any code I make goes first into a branch:
git co -b new-cool-feature
Let's edit a file (e.g. modify base/main.cpp to print "hello, world"). Commit this as usual; note that the commit will land on the new-cool-feature branch, not master.
... edit base/main.cpp ... git add base/main.cpp git ci -m "BASE: Add new cool feature"
You can check the difference between the two branches:
git diff master..new-cool-feature
Now we want the rest of the world to be able to see our marvelous new code, so let's push it to a new branch on our github fork, with identical name:
git push origin new-cool-feature
With this we have established a connection between our local "new-cool-feature" branch, and the "new-cool-feature" branch on github. From now on we can just do "git push". So let's edit base/main.cpp again, this time maybe by printing a "goodbye, world" at the end. Then, let's commit and push the changes:
... edit base/main.cpp ... git add base/main.cpp git ci -m "BASE: Even better new feature" git push
You can look at the result here: <https://github.com/fingolfin/scummvm/tree/new-cool-feature>
At some point, you may want your work to be integrated into the official ScummVM tree. As mentioned above, the easiest way (if you are not a committer) probably is to submit a pull request <http://help.github.com/pull-requests/>. Even people who do have write access often use this, in order to receive feedback on their code.
If you have write access and just want to integrate your changes, do the following (I'll assume you do a merge here, but you can of course also do a rebase):
git co master git pull upstream # ensure we are in sync with upstream git merge new-cool-feature git push upstream # push to upstream git push # push to your fork
[question to the experts: maybe we want to suggest use of --no-ff for the merge ?]
Note: In reality, I sometimes make an exception and skip the creation of a (topic) branch. E.g. if I only want to commit a quick fix to some code. In that case, I might commit the fix into my local master branch, then push the result to both my fork and scummvm
git co master git pull ... edit files, add and commit them ... git push upstream git push
Cleaning up your existing clones / checkouts
Maybe you were already using a fork, but were following a different model (or no model at all ;) and would like to adapt to this. E.g. maybe you made commits directly to your master branch, then merged changes from scummvm/scummvm's master branch into that, and now have a messy history which intermingles your own work with upstream work.
Cleaning this up is usually quite simple. Let's assume the most simple case, namely that you made no branches at all and did all your work on master. (If you did use branches, you need to adapt this. Feel free to ask here or on our IRC channel if you need help.) So, let's assume you are in your local clone of your fork, with master as sole active branch. Make sure that the upstream remote has been added, as described above, so
git remote add upstream git://github.com/scummvm/scummvm.git
or
git remote add upstream git@github.com:scummvm/scummvm.git
Now, we fetch the upstream changes, and *rebase* our master branch on that:
git fetch upstream git rebase upstream/master
If all went fine, then this just completely. If you are in bad luck, this will cause an error, and you will have to resolve conflicts. It prints instructions for how to do that. Feel free to ask here or on IRC for assistance with resolving the issues. In the worst case, you can just use
git rebase --abort
to give up.
But let's assume the rebase worked fine, or that you managed to make it work by resolving any conflicts. Now it's time to move any work you have done on master to a branch, so that we don't need to worry about this again in the future. Also, we will reset your local master branch to match the upstream master branch
git br BRANCH_NAME git reset --hard upstream/master
Finally, we can push your work back to your fork:
git co master git push origin master git co BRANCH_NAME git push origin BRANCH_NAME