Staging Changes

This page is currently a work in progress. It also does not reflect the contribution practices of the Mercurial project itself.

Although Mercurial encourages the sharing of changes between repositories by default, and in many environments it is natural to clone, pull and push with the result that all work is replicated, some environments attempt to maintain a set of "clean" repositories containing only changes regarded as essential to a particular work, not the changes made by contributors in private within their own repository clones. The practice of communicating changes by exporting them in some way or other is employed by various projects including Mercurial itself, but in some environments contributors may be expected to push "clean" changes directly to a project repository, potentially a central repository in connection with a CVS-like working practice.

In order to support the activity of making and pushing "clean" or "collapsed" changes selectively, it can be useful to maintain a staging area in the form of an additional clone of an upstream repository. Consider a contributor pulling and pushing changes in a centralised model, having cloned the upstream repository:

It may demand a fair amount of care to only push acceptable changes back to the upstream repository, so we might suggest an additional clone as follows:

This process might be done as follows:

hg clone upstream staging
hg clone staging local

This may not be technically necessary, but the idea is to have a local clone into which we will contribute changes acceptable for the upstream repository. If we manage to make a mistake, we will not have polluted the upstream repository, and can recover by, in the worst case, cloning the upstream repository anew.

Preparing clean changes

As a contributor, you can now do work within the local clone, committing freely in order to version your changes. However, as already discussed, the upstream project may not want all your changesets as part of their repository's history. Consider the following situation:

It may be undesirable to replicate each of the committed changes to the upstream repository whose maintainers would prefer a single changeset incorporating the work done. To prepare such a changeset, one can produce a diff summarising the work as follows:

hg diff -r 1

Here, the convenient numeric changeset identifier has been used for simplicity. This diff can be stored in a file as follows:

hg diff -r 1 > changes.diff

Now, in order to communicate this change, we need to pretend that the work was done in a single collapsed changeset starting from the same place in the repository history. We can navigate to that place as follows:

hg update -r 1

The working directory for our local clone will have the following state indicated by the red node in the graph:

We may now apply our collapsed changeset to the working files:

patch -p1 < changes.diff

This changeset may have added or removed files, and we should check the repository status to remind ourselves which files have been affected, using hg add and hg remove to ensure that they are taken into consideration when committing the changes.

Committing the result will cause the local clone to change:

Mercurial will report that it has created a new head in the repository. At this point, we have a changeset in our clone which should be acceptable for contribution.

Pushing changes for staging

Now if we were to merely perform a standard push operation, all changesets from the local clone would be pushed to the staging clone; this is not what we want. Instead, we should merely push the collapsed change; this is done by specifying the revision of the collapsed changeset:

hg push -r 5 ../staging

Here, we assume that the staging clone resides in the same directory as the local clone.

As a result of pushing, the repositories will now look like this:

You can now push freely to the upstream repository from the staging clone:

cd ../staging
hg push # should push to upstream

This should have the following effect:

Dealing with new upstream changes

It may be the case that changes have been made upstream while your own work was being done, and pushing to the upstream repository causes Mercurial to complain about new remote heads:

abort: push creates new remote heads!

This can be visualised as follows:

Such "competing" upstream changes can be pulled and merged in the staging repository. First, update to the point at which your work was done:

hg update tip

This assumes that your work is the most recent revision. If this is not the case for some reason, use the following to find the appropriate revision number:

hg heads

Then pull and merge the competing changes:

hg pull
hg merge

If all went well, the result can be committed and then pushed upstream:

hg commit
hg push

The result should look like this:

Now, the staging repository should reflect the state of the upstream repository.

Returning to work

Updating your local clone should be as easy as performing a pull operation:

hg pull

This will add the details of the merge you performed to the local clone, but your previous work will be unaffected (as nothing gets thrown away):

You should now update to a revision known to the upstream repository, such as the tip which has just been retrieved in the pull operation:

hg update tip

And from this point, it should be possible to repeat the whole contribution process again. As a side-effect of having an unshared "branch" of changes that we didn't push previously, it is now not possible to perform a default push operation without Mercurial complaining:

hg push # causes an "abort: push creates new remote heads!" error

This forces us to consider how we attempt to share our contributions more carefully. However, it is necessary to exercise caution: if you decide to merge from one of your unshared revisions, for whatever reason, you run the risk of pushing your unshared changes by accident. That said, in the event of having to revisit any work previously shared, it is most likely that the starting point of that work would be the shared, collapsed changeset, not any of the component changesets comprising the eventually shared work.


CategoryHowTo