[COMING SOON] Vendor branches with Mercurial

Vendor branches with Mercurial


Vendor branches with Mercurial

WORK IN PROGRESS -- NOT TESTED OR REVIEWED YET

Create a new vendor branch

You want to import a copy of Acme Widgetmaker. It is Apache2-licensed. It will live in src/external/apache2/widgetmaker, and we will track upstream releases on the vendor branch vendor/widgetmaker.

  1. Create a shared repository clone (requires share extension):
    $ hg share -U src src-vendor
    This is another working tree for the same underlying repository data, so all the same changesets and branches are available. (Not strictly necessary, but it's much faster than waiting for hg update to create or delete gigabytes of data across hundreds of thousands of files as you switch between the vendor branch and trunk, and it takes much less disk space than regular hg clone.) The -U option leaves the working tree empty, checked out at the null revision, to start the vendor branch on its own history; if you forget it, you can do hg update null instead.
  2. Unpack the distribution into the right path in the shared repository:
    $ cd ../src-vendor
    $ mkdir -p external/apache2/widgetmaker/dist
    $ tar -C external/apache2/widgetmaker/dist \
        --strip-components=1 \
        -xvzf /path/to/widgetmaker-1.23.tar.gz
  3. Commit it on the vendor branch:
    $ hg branch vendor/widgetmaker
    $ hg addremove
    $ hg commit -m 'Import widgetmaker-1.23'
  4. Merge the vendor branch into trunk—this will populate external/apache2/widgetmaker/dist/ on trunk with the content of Acme Widgetmaker 1.23 on the vendor branch:
    $ cd ../src
    $ hg update trunk
    $ hg merge vendor/widgetmaker
    $ hg commit -m 'Merge widgetmaker-1.23'
  5. Tag it for reference:
    $ hg tag vendor/widgetmaker-1.23

Find our local patches

Developers can commit on trunk to files under external/apache2/widgetmaker/dist/, creating local patches. Local patches incur a maintenance burden and may create bugs, so we should generally try to keep them to a minimum and/or submit them back upstream if applicable. To find the local patches on trunk:

$ cd src/external/apache2/widgetmaker/dist
$ hg diff --from vendor/widgetmaker

A release branch such as netbsd-11 may not be on the latest version. In that case, you can consult doc/3RDPARTY to find which version it's on, and use the tag to diff:

$ hg diff --from vendor/widgetmaker-1.23

Alternatively, you can diff from the latest revision of the branch that has been merged into the current branch:

$ hg diff --from 'max(ancestors(.) & branch("vendor/widgetmaker"))'

Prepare a legacy vendor branch from CVS

Upstream software that NetBSD ships, such as less(1) and tmux(1), was historically maintained in CVS vendor branches. CVS vendor branches are quirky: cvs import, which imports a new of an upstream directory as a release tag on a vendor branch, doesn't delete files on the vendor branch—even if they are deleted from the release tag. For CVS usage, this didn't matter much: the deletion was recorded from one release tag to the next, and that was enough for cvs checkout -jOLD -jNEW to delete it on trunk too.

Unfortunately, the conversion to Mercurial is missing two parts:

  • The sequence of release tags. Although vendor imports with cvs import external/bsd/... VENDOR release-1-4 happen in some definite sequence of release tags release-1-3, release-1-4, release-2-1, and so on, CVS doesn't record that sequence anywhere. And the release tags themselves don't exactly correspond to states of the vendor branch: cvs checkout -rrelease-1-4 may not match cvs checkout -rVENDOR:DATE for any actual date. So the conversion doesn't have commits representing the release tags as such.
  • The merge points. When merging changes from one release tag to the next with cvs checkout -jrelease-1-4 -jrelease-2-1 and then cvs commit, CVS never recorded any correspondence between the release tags and that commit. So the conversion doesn't have merge history identifying which release tags were merged into trunk.

To work around this, we can create a fictitious merge of the vendor branch from CVS, so that the next time we do a vendor branch update in Mercurial, it will compute the desired three-way merge between the current latest upstream, our local patches, and the next upstream version.

  1. Merge the vendor branch into trunk, but if any files don't match, take the local version (from trunk), not the branch:
    $ hg merge -t :local vendor/widgetmaker
  2. The vendor branch may still have files that were deleted upstream, because cvs import didn't actually delete them on the vendor branch—only on the release tag:
    $ hg revert -r 'p1()' -C --all
  3. Review the diff from trunk to make sure the merge isn't actually changing anything:
    $ hg diff --from 'p1()'
  4. Commit:
    $ hg commit

Import a new version on a vendor branch

When Acme Widgetmaker 1.24 is released, it may be time for us to import a new version—and make sure we don't lose any local patches we made, in case they are still applicable.

  1. Create a shared repository for the vendor work tree if you haven't already:
    $ hg share -U src src-vendor
  2. Switch to the vendor branch and replace its content wholesale (except for .hgtags, which is where the previous tag is stored):
    $ cd src-vendor
    $ hg update vendor/widgetmaker
    $ hg revert -r null -C --all -X .hgtags
    $ mkdir -p external/apache2/widgetmaker/dist
    $ tar -C external/apache2/widgetmaker/dist \
        --strip-components=1 \
        -xvzf /path/to/widgetmaker-1.24.tar.gz
  3. Auto-detect additions, removals, and renames; then commit and tag it for reference:
    $ hg addremove
    $ hg commit -m 'Import widgetmaker-1.24'
    $ hg tag vendor/widgetmaker-1.24
    Note: You can also use, for example, hg addremove -s 80 to auto-detect renames with 80% similarity; the default is to detect renames only if they have 100% similarity, i.e., if they are byte-for-byte identical. If you lower the similarity threshold, you should verify the renames are correct; somteimes automatic rename detection goes awry, especially with long blocks of copying terms copied and pasted into many files.
  4. Merge the vendor branch into trunk:
    $ cd ../src
    $ hg merge vendor/widgetmaker
  5. If there are merge conflicts, resolve them and mark them resolved with hg resolve:
    $ edit sys/kern/sys_descrip.c
    $ hg resolve -m sys/kern/kern_descrip.c
    You can also use it to re-merge with different merge tools, e.g. -t :local to take our local version and discard upstream changes, or -t :other to take the upstream version and discard local changes.
  6. Review the new changes being imported, and review our local changes from upstream:
    $ hg diff
    $ hg diff --from 'p2()'
  7. Commit the merge, and push it:
    $ hg diff
    $ hg commit -m 'Merge widgetmaker-1.24'
    $ hg push
    (You can also push it to a draft topic for review first.)