Git tips

From ScummVM :: Wiki
Revision as of 15:30, 30 May 2011 by Digitall (talk | contribs) (Add Fingolfin's Github Fork Workflow Notes.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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