Python Tales and Plone Stories

4teamwork

Git - Partially Applying a Patch

We are currently in the process of consolidating many of our packages for a large project into a single project.core package. We do this in order to simplify the dependency graph, testing, GitHub workflow and general maintenance. These are packages that we first decided should be optional, but now have grown to be such integral parts of the system that we have to have a hard dependency on them anyway.

We already merged packages with the structure

project.base/project/base
project.journal/project/journal
project.search/project/search
...

into a single egg project.core that looks like

project.core/project/base
project.core/project/journal
project.core/project/search
...

So the former eggs will now be python subpackages inside the one project.core egg, project being a namespace package.

In a perfect world, that massive subtree merge (kudos to senny and jone!) would be the end of it and we would switch to the new package’s repo immediately and deploy the new codebase at all our clients’ sites at the same time.

But since that’s not possible, we’ll have a short time window where we’ll be working on the new repo, but also have to backport changes that need to be pushed to deployments to the old repositories.

Because the new subpackages still have the same location relative to the containing egg, their dotted names and therefore the imports don’t change. This should make for very easy backporting of those changes by simply applying the changeset’s patch to the respective old repo.

However, a few paths did change, so for atomicity’s sake git fails the whole patch when some of the hunks don’t apply, and doesn’t modify the working tree at all:

1
$ git apply ~/urgent.patch
error: patch failed: docs/HISTORY.txt:4
error: docs/HISTORY.txt: patch does not apply

Yeah. Whereas before each package had its own docs/HISTORY.txt, now there’s only one in the new project.core package, and its structure doesn’t match (git can not locate the couple of lines of context that surround that hunk). Bummer. Especially because I know that 90% of that patch would apply cleanly.

That’s where git apply --reject comes in. Git will apply all the hunks it can, and leave the rejected hunks in corresponding *.rej files:

1
$ git apply --reject ~/urgent.patch
Checking patch docs/HISTORY.txt...
error: while searching for:
3.0 (unreleased)
----------------

- Some changelog context that doesn't exist in the new file...
  [some_guy]

error: patch failed: docs/HISTORY.txt:4
Checking patch project/search/portlets/configure.zcml...
Checking patch project/search/portlets/search_portlet.py...
Applying patch docs/HISTORY.txt with 1 rejects...
Rejected hunk #1.
Applied patch project/search/portlets/configure.zcml cleanly.
Applied patch project/search/portlets/search_portlet.py cleanly.

So the resulting working tree looks like this:

# On branch 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:   project/search/portlets/configure.zcml
#   modified:   project/search/portlets/search_portlet.py
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   docs/HISTORY.txt.rej
no changes added to commit (use "git add" and/or "git commit -a")

All that’s left to do is to manually merge the change from docs/HISTORY.txt.rej into docs/HISTORY.txt and remove the *.rej file.

Comments