diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -13,6 +13,7 @@ import hg, util, revlog, bundlerepo, ext import difflib, patch, time, help, mdiff, tempfile import version, socket import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect +import merge as merge_ # Commands start here, listed alphabetically @@ -2236,6 +2237,35 @@ def rename(ui, repo, *pats, **opts): finally: del wlock +def resolve(ui, repo, *pats, **opts): + """resolve file merges from a branch merge or update + + This command will attempt to resolve unresolved merges from the + last update or merge command. This will use the local file + revision preserved at the last update or merge to cleanly retry + the file merge attempt. With no file or options specified, this + command will attempt to resolve all unresolved files. + """ + + if len([x for x in opts if opts[x]]) > 1: + raise util.Abort(_("too many options specified")) + + ms = merge_.mergestate(repo) + mf = util.matcher(repo.root, "", pats, [], [])[1] + + for f in ms: + if mf(f): + if opts.get("list"): + ui.write("%s %s\n" % (ms[f].upper(), f)) + elif opts.get("mark"): + ms.mark(f, "r") + elif opts.get("unmark"): + ms.mark(f, "u") + else: + wctx = repo.workingctx() + mctx = wctx.parents()[-1] + ms.resolve(f, wctx, mctx) + def revert(ui, repo, *pats, **opts): """restore individual files or dirs to an earlier state @@ -3196,6 +3226,12 @@ table = { _('forcibly copy over an existing managed file')), ] + walkopts + dryrunopts, _('hg rename [OPTION]... SOURCE... DEST')), + "resolve": + (resolve, + [('l', 'list', None, _('list state of files needing merge')), + ('m', 'mark', None, _('mark files as resolved')), + ('u', 'unmark', None, _('unmark files as resolved'))], + ('hg resolve [OPTION] [FILES...]')), "revert": (revert, [('a', 'all', None, _('revert all changes when no arguments given')), diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -271,15 +271,7 @@ def update(repo, node): stats = _merge.update(repo, node, False, False, None) _showstats(repo, stats) if stats[3]: - repo.ui.status(_("There are unresolved merges with" - " locally modified files.\n")) - if stats[1]: - repo.ui.status(_("You can finish the partial merge using:\n")) - else: - repo.ui.status(_("You can redo the full merge using:\n")) - # len(pl)==1, otherwise _merge.update() would have raised util.Abort: - repo.ui.status(_(" hg update %s\n hg update %s\n") - % (pl[0].rev(), repo.changectx(node).rev())) + repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n")) return stats[3] > 0 def clean(repo, node, show_stats=True): @@ -294,11 +286,7 @@ def merge(repo, node, force=None, remind _showstats(repo, stats) if stats[3]: pl = repo.parents() - repo.ui.status(_("There are unresolved merges," - " you can redo the full merge using:\n" - " hg update -C %s\n" - " hg merge %s\n") - % (pl[0].rev(), pl[1].rev())) + repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n")) elif remind: repo.ui.status(_("(branch merge, don't forget to commit)\n")) return stats[3] > 0 diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from node import nullid, nullrev, hex +from node import nullid, nullrev, hex, bin from i18n import _ import errno, util, os, filemerge, copies, shutil @@ -13,27 +13,49 @@ class mergestate(object): '''track 3-way merge state of individual files''' def __init__(self, repo): self._repo = repo + self._read() + def reset(self, node): self._state = {} - self._data = {} - def reset(self, node): self._local = node shutil.rmtree(self._repo.join("merge"), True) + def _read(self): + self._state = {} + try: + f = self._repo.opener("merge/state") + self._local = bin(f.readline()[:-1]) + for l in f: + bits = l[:-1].split("\0") + self._state[bits[0]] = bits[1:] + except IOError, err: + if err.errno != errno.ENOENT: + raise + def _write(self): + f = self._repo.opener("merge/state", "w") + f.write(hex(self._local) + "\n") + for d, v in self._state.items(): + f.write("\0".join([d] + v) + "\n") def add(self, fcl, fco, fca, fd, flags): hash = util.sha1(fcl.path()).hexdigest() self._repo.opener("merge/" + hash, "w").write(fcl.data()) - self._state[fd] = 'u' - self._data[fd] = (hash, fcl.path(), fca.path(), hex(fca.filenode()), - fco.path(), flags) + self._state[fd] = ['u', hash, fcl.path(), fca.path(), + hex(fca.filenode()), fco.path(), flags] + self._write() def __contains__(self, dfile): return dfile in self._state def __getitem__(self, dfile): - return self._state[dfile] + return self._state[dfile][0] + def __iter__(self): + l = self._state.keys() + l.sort() + for f in l: + yield f def mark(self, dfile, state): - self._state[dfile] = state + self._state[dfile][0] = state + self._write() def resolve(self, dfile, wctx, octx): if self[dfile] == 'r': return 0 - hash, lfile, afile, anode, ofile, flags = self._data[dfile] + state, hash, lfile, afile, anode, ofile, flags = self._state[dfile] f = self._repo.opener("merge/" + hash) self._repo.wwrite(dfile, f.read(), flags) fcd = wctx[dfile] @@ -41,7 +63,6 @@ class mergestate(object): fca = self._repo.filectx(afile, fileid=anode) r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca) if not r: - util.set_flags(self._repo.wjoin(dfile), flags) self.mark(dfile, 'r') return r diff --git a/tests/test-add.out b/tests/test-add.out --- a/tests/test-add.out +++ b/tests/test-add.out @@ -18,9 +18,7 @@ merging a warning: conflicts during merge. merging a failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 1 +use 'hg resolve' to retry unresolved file merges M a ? a.orig % should fail diff --git a/tests/test-conflict.out b/tests/test-conflict.out --- a/tests/test-conflict.out +++ b/tests/test-conflict.out @@ -4,9 +4,7 @@ merging a warning: conflicts during merge. merging a failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 1 +use 'hg resolve' to retry unresolved file merges e7fe8eb3e180+0d24b7662d3e+ tip <<<<<<< local something else diff --git a/tests/test-convert-svn-sink.out b/tests/test-convert-svn-sink.out --- a/tests/test-convert-svn-sink.out +++ b/tests/test-convert-svn-sink.out @@ -265,9 +265,7 @@ merging b warning: conflicts during merge. merging b failed! 2 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 4 +use 'hg resolve' to retry unresolved file merges assuming destination b-hg initializing svn repo 'b-hg' initializing svn wc 'b-hg-wc' diff --git a/tests/test-debugcomplete.out b/tests/test-debugcomplete.out --- a/tests/test-debugcomplete.out +++ b/tests/test-debugcomplete.out @@ -33,6 +33,7 @@ push recover remove rename +resolve revert rollback root @@ -79,6 +80,7 @@ debugwalk recover remove rename +resolve revert rollback root diff --git a/tests/test-globalopts.out b/tests/test-globalopts.out --- a/tests/test-globalopts.out +++ b/tests/test-globalopts.out @@ -183,6 +183,7 @@ list of commands: recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir @@ -236,6 +237,7 @@ list of commands: recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir diff --git a/tests/test-help.out b/tests/test-help.out --- a/tests/test-help.out +++ b/tests/test-help.out @@ -74,6 +74,7 @@ list of commands: recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir @@ -123,6 +124,7 @@ use "hg -v help" to show aliases and glo recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir diff --git a/tests/test-merge-local.out b/tests/test-merge-local.out --- a/tests/test-merge-local.out +++ b/tests/test-merge-local.out @@ -21,10 +21,7 @@ merging zzz1_merge_ok merging zzz2_merge_bad merging zzz2_merge_bad failed! 3 files updated, 1 files merged, 2 files removed, 1 files unresolved -There are unresolved merges with locally modified files. -You can finish the partial merge using: - hg update 0 - hg update 1 +use 'hg resolve' to retry unresolved file merges 2 files updated, 0 files merged, 3 files removed, 0 files unresolved --- a/zzz1_merge_ok +++ b/zzz1_merge_ok @@ -42,10 +39,7 @@ merging zzz2_merge_bad warning: conflicts during merge. merging zzz2_merge_bad failed! 3 files updated, 1 files merged, 2 files removed, 1 files unresolved -There are unresolved merges with locally modified files. -You can finish the partial merge using: - hg update 0 - hg update 1 +use 'hg resolve' to retry unresolved file merges 2 files updated, 0 files merged, 3 files removed, 0 files unresolved --- a/zzz1_merge_ok +++ b/zzz1_merge_ok diff --git a/tests/test-merge-revert2.out b/tests/test-merge-revert2.out --- a/tests/test-merge-revert2.out +++ b/tests/test-merge-revert2.out @@ -13,10 +13,7 @@ merging file1 warning: conflicts during merge. merging file1 failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges with locally modified files. -You can redo the full merge using: - hg update 0 - hg update 1 +use 'hg resolve' to retry unresolved file merges diff -r f248da0d4c3e file1 --- a/file1 +++ b/file1 diff --git a/tests/test-merge7.out b/tests/test-merge7.out --- a/tests/test-merge7.out +++ b/tests/test-merge7.out @@ -11,9 +11,7 @@ merging test.txt warning: conflicts during merge. merging test.txt failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 1 - hg merge 2 +use 'hg resolve' to retry unresolved file merges pulling from ../test-a searching for changes adding changesets @@ -33,9 +31,7 @@ my test.txt@451c744aabcc+ other test.txt warning: conflicts during merge. merging test.txt failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 3 - hg merge 4 +use 'hg resolve' to retry unresolved file merges one <<<<<<< local two-point-five diff --git a/tests/test-merge9 b/tests/test-merge9 --- a/tests/test-merge9 +++ b/tests/test-merge9 @@ -23,9 +23,31 @@ hg ci -Am 'change foo' -d '0 0' # test with the rename on the remote side HGMERGE=false hg merge +hg resolve -l # test with the rename on the local side hg up -C 1 HGMERGE=false hg merge +echo % show unresolved +hg resolve -l + +echo % unmark baz +hg resolve -u baz + +echo % show +hg resolve -l + +echo % re-resolve baz +hg resolve baz + +echo % after +hg resolve -l + +echo % resolve all +hg resolve + +echo % after +hg resolve -l + true diff --git a/tests/test-merge9.out b/tests/test-merge9.out --- a/tests/test-merge9.out +++ b/tests/test-merge9.out @@ -7,14 +7,31 @@ merging bar merging bar failed! merging foo and baz to baz 1 files updated, 1 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 1 +use 'hg resolve' to retry unresolved file merges +U bar +R baz 3 files updated, 0 files merged, 1 files removed, 0 files unresolved merging bar merging bar failed! merging baz and foo to baz 1 files updated, 1 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 1 - hg merge 2 +use 'hg resolve' to retry unresolved file merges +% show unresolved +U bar +R baz +% unmark baz +% show +U bar +U baz +% re-resolve baz +merging baz and foo to baz +% after +U bar +R baz +% resolve all +merging bar +warning: conflicts during merge. +merging bar failed! +% after +U bar +R baz