Mercurial > hg
changeset 21553:bee0e1cffdd3
import: add --partial flag to create a changeset despite failed hunks
The `hg import` command gains a `--partial` flag. When specified, a commit will
always be created from a patch import. Any hunk that fails to apply will
create .rej file, same as what `hg qimport` would do. This change is mainly
aimed at preserving changeset metadata when applying a patch, something very
important for reviewers.
In case of failure with `--partial`, `hg import` returns 1 and the following
message is displayed:
patch applied partially
(fix the .rej files and run `hg commit --amend`)
When multiple patches are imported, we stop at the first one with failed hunks.
In the future, someone may feel brave enough to tackle a --continue flag to
import.
author | Pierre-Yves David <pierre-yves.david@fb.com> |
---|---|
date | Thu, 08 May 2014 17:08:17 -0700 |
parents | 61151f429a5f |
children | 7bcf4adadd2d |
files | mercurial/cmdutil.py mercurial/commands.py mercurial/patch.py tests/test-completion.t tests/test-import.t |
diffstat | 5 files changed, 313 insertions(+), 18 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/cmdutil.py Mon May 19 01:53:34 2014 +0200 +++ b/mercurial/cmdutil.py Thu May 08 17:08:17 2014 -0700 @@ -591,9 +591,11 @@ strip = opts["strip"] sim = float(opts.get('similarity') or 0) if not tmpname: - return (None, None) + return (None, None, False) msg = _('applied to working directory') + rejects = False + try: cmdline_message = logmessage(ui, opts) if cmdline_message: @@ -639,9 +641,17 @@ if opts.get('exact') or opts.get('import_branch'): repo.dirstate.setbranch(branch or 'default') + partial = opts.get('partial', False) files = set() - patch.patch(ui, repo, tmpname, strip=strip, files=files, - eolmode=None, similarity=sim / 100.0) + try: + patch.patch(ui, repo, tmpname, strip=strip, files=files, + eolmode=None, similarity=sim / 100.0) + except patch.PatchError, e: + if not partial: + raise util.Abort(str(e)) + if partial: + rejects = True + files = list(files) if opts.get('no_commit'): if message: @@ -656,7 +666,7 @@ m = scmutil.matchfiles(repo, files or []) n = repo.commit(message, opts.get('user') or user, opts.get('date') or date, match=m, - editor=editor) + editor=editor, force=partial) else: if opts.get('exact') or opts.get('import_branch'): branch = branch or 'default' @@ -684,7 +694,7 @@ if n: # i18n: refers to a short changeset id msg = _('created %s') % short(n) - return (msg, n) + return (msg, n, rejects) finally: os.unlink(tmpname)
--- a/mercurial/commands.py Mon May 19 01:53:34 2014 +0200 +++ b/mercurial/commands.py Thu May 08 17:08:17 2014 -0700 @@ -3685,6 +3685,8 @@ _("don't commit, just update the working directory")), ('', 'bypass', None, _("apply patch without touching the working directory")), + ('', 'partial', None, + _('commit even if some hunks fail')), ('', 'exact', None, _('apply patch to the nodes from which it was generated')), ('', 'import-branch', None, @@ -3726,6 +3728,16 @@ With -s/--similarity, hg will attempt to discover renames and copies in the patch in the same way as :hg:`addremove`. + Use --partial to ensure a changeset will be created from the patch + even if some hunks fail to apply. Hunks that fail to apply will be + written to a <target-file>.rej file. Conflicts can then be resolved + by hand before :hg:`commit --amend` is run to update the created + changeset. This flag exists to let people import patches that + partially apply without losing the associated metadata (author, + date, description, ...), Note that when none of the hunk applies + cleanly, :hg:`import --partial` will create an empty changeset, + importing only the patch metadata. + To read a patch from standard input, use "-" as the patch name. If a URL is specified, the patch will be downloaded from it. See :hg:`help dates` for a list of formats valid for -d/--date. @@ -3751,7 +3763,7 @@ hg import --exact proposed-fix.patch - Returns 0 on success. + Returns 0 on success, 1 on partial success (see --partial). """ if not patch1: @@ -3783,6 +3795,7 @@ base = opts["base"] wlock = lock = tr = None msgs = [] + ret = 0 try: @@ -3804,8 +3817,9 @@ haspatch = False for hunk in patch.split(patchfile): - (msg, node) = cmdutil.tryimportone(ui, repo, hunk, parents, - opts, msgs, hg.clean) + (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk, + parents, opts, + msgs, hg.clean) if msg: haspatch = True ui.note(msg + '\n') @@ -3813,6 +3827,12 @@ parents = repo.parents() else: parents = [repo[node]] + if rej: + ui.write_err(_("patch applied partially\n")) + ui.write_err(("(fix the .rej files and run " + "`hg commit --amend`)\n")) + ret = 1 + break if not haspatch: raise util.Abort(_('%s: no diffs found') % patchurl) @@ -3821,6 +3841,7 @@ tr.close() if msgs: repo.savecommitmessage('\n* * *\n'.join(msgs)) + return ret except: # re-raises # wlock.release() indirectly calls dirstate.write(): since # we're crashing, we do not want to change the working dir
--- a/mercurial/patch.py Mon May 19 01:53:34 2014 +0200 +++ b/mercurial/patch.py Thu May 08 17:08:17 2014 -0700 @@ -1521,14 +1521,11 @@ patcher = ui.config('ui', 'patch') if files is None: files = set() - try: - if patcher: - return _externalpatch(ui, repo, patcher, patchname, strip, - files, similarity) - return internalpatch(ui, repo, patchname, strip, files, eolmode, - similarity) - except PatchError, err: - raise util.Abort(str(err)) + if patcher: + return _externalpatch(ui, repo, patcher, patchname, strip, + files, similarity) + return internalpatch(ui, repo, patchname, strip, files, eolmode, + similarity) def changedfiles(ui, repo, patchpath, strip=1): backend = fsbackend(ui, repo.root)
--- a/tests/test-completion.t Mon May 19 01:53:34 2014 +0200 +++ b/tests/test-completion.t Thu May 08 17:08:17 2014 -0700 @@ -262,7 +262,7 @@ heads: rev, topo, active, closed, style, template help: extension, command, keyword identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure - import: strip, base, edit, force, no-commit, bypass, exact, import-branch, message, logfile, date, user, similarity + import: strip, base, edit, force, no-commit, bypass, partial, exact, import-branch, message, logfile, date, user, similarity incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos locate: rev, print0, fullpath, include, exclude manifest: rev, all
--- a/tests/test-import.t Mon May 19 01:53:34 2014 +0200 +++ b/tests/test-import.t Thu May 08 17:08:17 2014 -0700 @@ -1181,5 +1181,272 @@ 3 4 line + $ cd .. - $ cd .. +Test partial application +------------------------ + +prepare a stack of patches depending on each other + + $ hg init partial + $ cd partial + $ cat << EOF > a + > one + > two + > three + > four + > five + > six + > seven + > EOF + $ hg add a + $ echo 'b' > b + $ hg add b + $ hg commit -m 'initial' -u Babar + $ cat << EOF > a + > one + > two + > 3 + > four + > five + > six + > seven + > EOF + $ hg commit -m 'three' -u Celeste + $ cat << EOF > a + > one + > two + > 3 + > 4 + > five + > six + > seven + > EOF + $ hg commit -m 'four' -u Rataxes + $ cat << EOF > a + > one + > two + > 3 + > 4 + > 5 + > six + > seven + > EOF + $ echo bb >> b + $ hg commit -m 'five' -u Arthur + $ echo 'Babar' > jungle + $ hg add jungle + $ hg ci -m 'jungle' -u Zephir + $ echo 'Celeste' >> jungle + $ hg ci -m 'extended jungle' -u Cornelius + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ extended jungle [Cornelius] 1: +1/-0 + | + o jungle [Zephir] 1: +1/-0 + | + o five [Arthur] 2: +2/-1 + | + o four [Rataxes] 1: +1/-1 + | + o three [Celeste] 1: +1/-1 + | + o initial [Babar] 2: +8/-0 + + +Importing with some success and some errors: + + $ hg update --rev 'desc(initial)' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg export --rev 'desc(five)' | hg import --partial - + applying patch from stdin + patching file a + Hunk #1 FAILED at 1 + 1 out of 1 hunks FAILED -- saving rejects to file a.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ five [Arthur] 1: +1/-0 + | + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Arthur + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509 + # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e + five + + diff -r 8e4f0351909e -r 26e6446bb252 b + --- a/b Thu Jan 01 00:00:00 1970 +0000 + +++ b/b Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,2 @@ + b + +bb + $ hg status -c . + C a + C b + $ ls + a + a.rej + b + +Importing with zero success: + + $ hg update --rev 'desc(initial)' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg export --rev 'desc(four)' | hg import --partial - + applying patch from stdin + patching file a + Hunk #1 FAILED at 0 + 1 out of 1 hunks FAILED -- saving rejects to file a.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ four [Rataxes] 0: +0/-0 + | + | o five [Arthur] 1: +1/-0 + |/ + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Rataxes + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e + # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e + four + + $ hg status -c . + C a + C b + $ ls + a + a.rej + b + +Importing with unknown file: + + $ hg update --rev 'desc(initial)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg export --rev 'desc("extended jungle")' | hg import --partial - + applying patch from stdin + unable to find 'jungle' for patching + 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ extended jungle [Cornelius] 0: +0/-0 + | + | o four [Rataxes] 0: +0/-0 + |/ + | o five [Arthur] 1: +1/-0 + |/ + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Cornelius + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d + # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e + extended jungle + + $ hg status -c . + C a + C b + $ ls + a + a.rej + b + jungle.rej + +Importing multiple failing patches: + + $ hg update --rev 'desc(initial)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 'B' > b # just to make another commit + $ hg commit -m "a new base" + created new head + $ hg export --rev 'desc("extended jungle") + desc("four")' | hg import --partial - + applying patch from stdin + patching file a + Hunk #1 FAILED at 0 + 1 out of 1 hunks FAILED -- saving rejects to file a.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ four [Rataxes] 0: +0/-0 + | + o a new base [test] 1: +1/-1 + | + | o extended jungle [Cornelius] 0: +0/-0 + |/ + | o four [Rataxes] 0: +0/-0 + |/ + | o five [Arthur] 1: +1/-0 + |/ + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Rataxes + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d + # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd + four + + $ hg status -c . + C a + C b