Patchwork [1,of,4,V3] update: accept --merge to allow merging across topo branches (issue5125)

login
register
mail settings
Submitter via Mercurial-devel
Date Feb. 15, 2017, 8:56 p.m.
Message ID <aeb2aca1361d20c12985.1487192199@martinvonz.mtv.corp.google.com>
Download mbox | patch
Permalink /patch/18511/
State Superseded
Headers show

Comments

via Mercurial-devel - Feb. 15, 2017, 8:56 p.m.
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1487019517 28800
#      Mon Feb 13 12:58:37 2017 -0800
# Node ID aeb2aca1361d20c12985404b87030abf3ea22f3c
# Parent  afaf3c2b129c8940387fd9928ae4fdc28259d13c
update: accept --merge to allow merging across topo branches (issue5125)

Patch

diff -r afaf3c2b129c -r aeb2aca1361d mercurial/commands.py
--- a/mercurial/commands.py	Mon Feb 13 15:04:46 2017 -0800
+++ b/mercurial/commands.py	Mon Feb 13 12:58:37 2017 -0800
@@ -5286,12 +5286,13 @@ 
 @command('^update|up|checkout|co',
     [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
     ('c', 'check', None, _('require clean working directory')),
+    ('m', 'merge', None, _('merge local changes')),
     ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
     ('r', 'rev', '', _('revision'), _('REV'))
      ] + mergetoolopts,
-    _('[-C|-c] [-d DATE] [[-r] REV]'))
+    _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
-           tool=None):
+           merge=None, tool=None):
     """update working directory (or switch revisions)
 
     Update the repository's working directory to the specified
@@ -5310,8 +5311,8 @@ 
 
     .. container:: verbose
 
-      The -C/--clean and -c/--check options control what happens if the
-      working directory contains uncommitted changes.
+      The -C/--clean, -c/--check, and -m/--merge options control what
+      happens if the working directory contains uncommitted changes.
       At most of one of them can be specified.
 
       1. If no option is specified, and if
@@ -5323,10 +5324,14 @@ 
          branch), the update is aborted and the uncommitted changes
          are preserved.
 
-      2. With the -c/--check option, the update is aborted and the
+      2. With the -m/--merge option, the update is allowed even if the
+         requested changeset is not an ancestor or descendant of
+         the working directory's parent.
+
+      3. With the -c/--check option, the update is aborted and the
          uncommitted changes are preserved.
 
-      3. With the -C/--clean option, uncommitted changes are discarded and
+      4. With the -C/--clean option, uncommitted changes are discarded and
          the working directory is updated to the requested changeset.
 
     To cancel an uncommitted merge (and lose your changes), use
@@ -5351,8 +5356,15 @@ 
     if date and rev is not None:
         raise error.Abort(_("you can't specify a revision and a date"))
 
-    if check and clean:
-        raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
+    if len([x for x in (clean, check, merge) if x]) > 1:
+        raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
+                            "or -m/merge"))
+
+    updatecheck = None
+    if check:
+        updatecheck = 'abort'
+    elif merge:
+        updatecheck = 'none'
 
     with repo.wlock():
         cmdutil.clearunfinished(repo)
@@ -5366,7 +5378,8 @@ 
 
         repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
 
-        return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
+        return hg.updatetotally(ui, repo, rev, brev, clean=clean,
+                                updatecheck=updatecheck)
 
 @command('verify', [])
 def verify(ui, repo):
diff -r afaf3c2b129c -r aeb2aca1361d mercurial/hg.py
--- a/mercurial/hg.py	Mon Feb 13 15:04:46 2017 -0800
+++ b/mercurial/hg.py	Mon Feb 13 12:58:37 2017 -0800
@@ -681,18 +681,19 @@ 
     repo.ui.status(_("%d files updated, %d files merged, "
                      "%d files removed, %d files unresolved\n") % stats)
 
-def updaterepo(repo, node, overwrite):
+def updaterepo(repo, node, overwrite, updatecheck=None):
     """Update the working directory to node.
 
     When overwrite is set, changes are clobbered, merged else
 
     returns stats (see pydoc mercurial.merge.applyupdates)"""
     return mergemod.update(repo, node, False, overwrite,
-                           labels=['working copy', 'destination'])
+                           labels=['working copy', 'destination'],
+                           updatecheck=updatecheck)
 
-def update(repo, node, quietempty=False):
-    """update the working directory to node, merging linear changes"""
-    stats = updaterepo(repo, node, False)
+def update(repo, node, quietempty=False, updatecheck=None):
+    """update the working directory to node"""
+    stats = updaterepo(repo, node, False, updatecheck=updatecheck)
     _showstats(repo, stats, quietempty)
     if stats[3]:
         repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
@@ -712,7 +713,7 @@ 
 # naming conflict in updatetotally()
 _clean = clean
 
-def updatetotally(ui, repo, checkout, brev, clean=False, check=False):
+def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
     """Update the working directory with extra care for non-file components
 
     This takes care of non-file components below:
@@ -724,10 +725,19 @@ 
     :checkout: to which revision the working directory is updated
     :brev: a name, which might be a bookmark to be activated after updating
     :clean: whether changes in the working directory can be discarded
-    :check: whether changes in the working directory should be checked
+    :updatecheck: how to deal with a dirty working directory
+
+    Valid values for updatecheck are (None => linear):
+
+     * abort: abort if the working directory is dirty
+     * none: don't check (merge working directory changes into destination)
+     * linear: check that update is linear before merging working directory
+               changes into destination
 
     This returns whether conflict is detected at updating or not.
     """
+    if updatecheck is None:
+        updatecheck = 'linear'
     with repo.wlock():
         movemarkfrom = None
         warndest = False
@@ -739,9 +749,10 @@ 
         if clean:
             ret = _clean(repo, checkout)
         else:
-            if check:
+            if updatecheck == 'abort':
                 cmdutil.bailifchanged(repo, merge=False)
-            ret = _update(repo, checkout)
+                updatecheck = 'none'
+            ret = _update(repo, checkout, updatecheck=updatecheck)
 
         if not ret and movemarkfrom:
             if movemarkfrom == repo['.'].node():
diff -r afaf3c2b129c -r aeb2aca1361d mercurial/merge.py
--- a/mercurial/merge.py	Mon Feb 13 15:04:46 2017 -0800
+++ b/mercurial/merge.py	Mon Feb 13 12:58:37 2017 -0800
@@ -1444,7 +1444,8 @@ 
             repo.dirstate.normal(f)
 
 def update(repo, node, branchmerge, force, ancestor=None,
-           mergeancestor=False, labels=None, matcher=None, mergeforce=False):
+           mergeancestor=False, labels=None, matcher=None, mergeforce=False,
+           updatecheck=None):
     """
     Perform a merge between the working directory and the given node
 
@@ -1491,9 +1492,16 @@ 
     Return the same tuple as applyupdates().
     """
 
-    # This functon used to find the default destination if node was None, but
+    # This function used to find the default destination if node was None, but
     # that's now in destutil.py.
     assert node is not None
+    if not branchmerge and not force:
+        # TODO: remove the default once all callers that pass branchmerge=False
+        # and force=False pass a value for updatecheck. We may want to allow
+        # updatecheck='abort' to better suppport some of these callers.
+        if updatecheck is None:
+            updatecheck = 'linear'
+        assert updatecheck in ('none', 'linear')
     # If we're doing a partial update, we need to skip updating
     # the dirstate, so make a note of any partial-ness to the
     # update here.
@@ -1550,7 +1558,8 @@ 
                 repo.hook('update', parent1=xp2, parent2='', error=0)
                 return 0, 0, 0, 0
 
-            if pas not in ([p1], [p2]):  # nonlinear
+            if (updatecheck == 'linear' and
+                    pas not in ([p1], [p2])):  # nonlinear
                 dirty = wc.dirty(missing=True)
                 if dirty:
                     # Branching is a bit strange to ensure we do the minimal
diff -r afaf3c2b129c -r aeb2aca1361d tests/test-completion.t
--- a/tests/test-completion.t	Mon Feb 13 15:04:46 2017 -0800
+++ b/tests/test-completion.t	Mon Feb 13 12:58:37 2017 -0800
@@ -223,7 +223,7 @@ 
   serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
   summary: remote
-  update: clean, check, date, rev, tool
+  update: clean, check, merge, date, rev, tool
   addremove: similarity, subrepos, include, exclude, dry-run
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
diff -r afaf3c2b129c -r aeb2aca1361d tests/test-update-branches.t
--- a/tests/test-update-branches.t	Mon Feb 13 15:04:46 2017 -0800
+++ b/tests/test-update-branches.t	Mon Feb 13 12:58:37 2017 -0800
@@ -160,6 +160,16 @@ 
   parent=1
   M foo
 
+  $ revtest '-m dirty linear'   dirty 1 2 -m
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  parent=2
+  M foo
+
+  $ revtest '-m dirty cross'  dirty 3 4 -m
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  parent=4
+  M foo
+
   $ revtest '-c dirtysub linear'   dirtysub 1 2 -c
   abort: uncommitted changes in subrepository 'sub'
   parent=1
@@ -171,7 +181,17 @@ 
   parent=2
 
   $ revtest '-cC dirty linear'  dirty 1 2 -cC
-  abort: cannot specify both -c/--check and -C/--clean
+  abort: can only specify one of -C/--clean, -c/--check, or -m/merge
+  parent=1
+  M foo
+
+  $ revtest '-mc dirty linear'  dirty 1 2 -mc
+  abort: can only specify one of -C/--clean, -c/--check, or -m/merge
+  parent=1
+  M foo
+
+  $ revtest '-mC dirty linear'  dirty 1 2 -mC
+  abort: can only specify one of -C/--clean, -c/--check, or -m/merge
   parent=1
   M foo