diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -5278,12 +5278,13 @@ def unbundle(ui, repo, fname1, *fnames, @command('^update|up|checkout|co', [('C', 'clean', None, _('discard uncommitted changes (no backup)')), ('c', 'check', None, _('require clean working directory')), + ('m', 'merge', None, _('merge uncommitted 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 @@ -5302,8 +5303,8 @@ def update(ui, repo, node=None, rev=None .. 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 @@ -5315,10 +5316,14 @@ def update(ui, repo, node=None, rev=None 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 @@ -5343,8 +5348,15 @@ def update(ui, repo, node=None, rev=None 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) @@ -5358,7 +5370,8 @@ def update(ui, repo, node=None, rev=None 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 --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -691,18 +691,19 @@ def _showstats(repo, stats, quietempty=F 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")) @@ -722,7 +723,7 @@ def clean(repo, node, show_stats=True, q # 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: @@ -734,10 +735,19 @@ def updatetotally(ui, repo, checkout, br :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 @@ -749,9 +759,10 @@ def updatetotally(ui, repo, checkout, br 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 --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -1444,7 +1444,8 @@ def recordupdates(repo, actions, branchm 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 @@ -1468,14 +1469,17 @@ def update(repo, node, branchmerge, forc This logic is tested by test-update-branches.t. - -c -C dirty rev linear | result - y y * * * | (1) - * * * n n | x - * * n * * | ok - n n y * y | merge - n n y y n | (2) - n y y * * | discard - y n y * * | (3) + -c -C -m dirty rev linear | result + y y * * * * | (1) + y * y * * * | (1) + * y y * * * | (1) + * * * * n n | x + * * * n * * | ok + n n n y * y | merge + n n n y y n | (2) + n n y y * * | merge + n y n y * * | discard + y n n y * * | (3) x = can't happen * = don't-care @@ -1486,9 +1490,16 @@ def update(repo, node, branchmerge, forc 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. @@ -1545,7 +1556,8 @@ def update(repo, node, branchmerge, forc 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 --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -228,7 +228,7 @@ Show all commands + options 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 --git a/tests/test-update-branches.t b/tests/test-update-branches.t --- a/tests/test-update-branches.t +++ b/tests/test-update-branches.t @@ -160,6 +160,16 @@ Cases are run as shown in that table, ro 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 @@ Cases are run as shown in that table, ro 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