def _widen(ui, repo, remote, commoninc, newincludes, newexcludes): newmatch = narrowspec.match(repo.root, newincludes, newexcludes) # TODO(martinvonz): Get expansion working with widening/narrowing. if narrowspec.needsexpansion(newincludes): raise error.Abort('Expansion not yet supported on pull') def pullbundle2extraprepare_widen(orig, pullop, kwargs): orig(pullop, kwargs) # The old{in,ex}cludepats have already been set by orig() kwargs['includepats'] = newincludes kwargs['excludepats'] = newexcludes wrappedextraprepare = extensions.wrappedfunction( exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen) # define a function that narrowbundle2 can call after creating the # backup bundle, but before applying the bundle from the server def setnewnarrowpats(): repo.setnarrowpats(newincludes, newexcludes) repo.setnewnarrowpats = setnewnarrowpats ds = repo.dirstate p1, p2 = ds.p1(), ds.p2() with ds.parentchange(): ds.setparents(node.nullid, node.nullid) common = commoninc[0] with wrappedextraprepare: exchange.pull(repo, remote, heads=common) with ds.parentchange(): ds.setparents(p1, p2) actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()} addgaction = actions['g'].append mf = repo['.'].manifest().matches(newmatch) for f, fn in mf.iteritems(): if f not in repo.dirstate: addgaction( (f, (mf.flags(f), False), "add from widened narrow clone")) merge.applyupdates(repo, actions, wctx=repo[None], mctx=repo['.'], overwrite=False) merge.recordupdates(repo, actions, branchmerge=False)
def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes, newincludes, newexcludes): newmatch = narrowspec.match(repo.root, newincludes, newexcludes) # for now we assume that if a server has ellipses enabled, we will be # exchanging ellipses nodes. In future we should add ellipses as a client # side requirement (maybe) to distinguish a client is shallow or not and # then send that information to server whether we want ellipses or not. # Theoretically a non-ellipses repo should be able to use narrow # functionality from an ellipses enabled server ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities() def pullbundle2extraprepare_widen(orig, pullop, kwargs): orig(pullop, kwargs) # The old{in,ex}cludepats have already been set by orig() kwargs['includepats'] = newincludes kwargs['excludepats'] = newexcludes wrappedextraprepare = extensions.wrappedfunction(exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen) # define a function that narrowbundle2 can call after creating the # backup bundle, but before applying the bundle from the server def setnewnarrowpats(): repo.setnarrowpats(newincludes, newexcludes) repo.setnewnarrowpats = setnewnarrowpats # silence the devel-warning of applying an empty changegroup overrides = {('devel', 'all-warnings'): False} with ui.uninterruptable(): common = commoninc[0] if ellipsesremote: ds = repo.dirstate p1, p2 = ds.p1(), ds.p2() with ds.parentchange(): ds.setparents(node.nullid, node.nullid) with wrappedextraprepare,\ repo.ui.configoverride(overrides, 'widen'): exchange.pull(repo, remote, heads=common) with ds.parentchange(): ds.setparents(p1, p2) else: with remote.commandexecutor() as e: bundle = e.callcommand('narrow_widen', { 'oldincludes': oldincludes, 'oldexcludes': oldexcludes, 'newincludes': newincludes, 'newexcludes': newexcludes, 'cgversion': '03', 'commonheads': common, 'known': [], 'ellipses': False, }).result() with repo.transaction('widening') as tr,\ repo.ui.configoverride(overrides, 'widen'): tgetter = lambda: tr bundle2.processbundle(repo, bundle, transactiongetter=tgetter) repo.setnewnarrowpats() actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()} addgaction = actions['g'].append mf = repo['.'].manifest().matches(newmatch) for f, fn in mf.iteritems(): if f not in repo.dirstate: addgaction((f, (mf.flags(f), False), "add from widened narrow clone")) merge.applyupdates(repo, actions, wctx=repo[None], mctx=repo['.'], overwrite=False) merge.recordupdates(repo, actions, branchmerge=False)
def hgupdate(repo, node, branchmerge, force, partial): """Slightly hacked mercurial.merge.update() (To see changes, diff it with version 1.1.2. Please keep this version number up-to-date if you change the derived version.) Perform a merge between the working directory and the given node node = the node to update to, or None if unspecified branchmerge = whether to merge between branches force = whether to force branch merging or file overwriting partial = a function to filter file lists (dirstate not updated) The table below shows all the behaviors of the update command given the -c and -C or no options, whether the working directory is dirty, whether a revision is specified, and the relationship of the parent rev to the target rev (linear, on the same named branch, or on another named branch). This logic is tested by test-update-branches. -c -C dirty rev | linear same cross n n n n | ok (1) x n n n y | ok ok ok n n y * | merge (2) (2) n y * * | --- discard --- y n y * | --- (3) --- y n n * | --- ok --- y y * * | --- (4) --- x = can't happen * = don't-care 1 = abort: crosses branches (use 'hg merge' or 'hg update -c') 2 = abort: crosses branches (use 'hg merge' to merge or use 'hg update -C' to discard changes) 3 = abort: uncommitted local changes 4 = incompatible options (checked in commands.py) """ wlock = repo.wlock() try: wc = repo[None] ###working copy change context if node is None: ###see nothing passing it node # tip of current branch try: node = repo.branchtags()[wc.branch()] except KeyError: if wc.branch() == "default": # no default branch! node = repo.lookup("tip") # update to tip else: raise util.Abort(_("branch %s not found") % wc.branch()) overwrite = force and not branchmerge pl = wc.parents() ###ParentList of working copy p1, p2 = pl[0], repo[node] ### MyChangeContext, MergeChangeContet pa = p1.ancestor(p2) ### CommonAncestorChangeContext fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str( p2) ##changeset hashes & short hashes fastforward = False ### check phase if not overwrite and len(pl) > 1: ### wc has multiple parents raise util.Abort(_("outstanding uncommitted merges")) if branchmerge: if pa == p2: ###she is the common ancestor raise util.Abort(_("can't merge with ancestor")) elif pa == p1: ###me is the common ancestor if p1.branch() != p2.branch( ): ###...but we are different named branches (usually 'default') fastforward = True ### Merging a branch back into common ancestor else: raise util.Abort( _("nothing to merge (use 'hg update'" " or check 'hg heads')")) if not force and (wc.files() or wc.deleted()): ### edits, adds, or mods in wc raise util.Abort(_("outstanding uncommitted changes")) elif not overwrite: if pa == p1 or pa == p2: # linear pass # all good elif p1.branch() == p2.branch(): if wc.files() or wc.deleted(): raise util.Abort( _("crosses branches (use 'hg merge' or " "'hg update -C' to discard changes)")) raise util.Abort( _("crosses branches (use 'hg merge' " "or 'hg update -C')")) elif wc.files() or wc.deleted(): raise util.Abort( _("crosses named branches (use " "'hg update -C' to discard changes)")) else: # Allow jumping branches if there are no changes overwrite = True ### calculate phase action = [] if not force: merge._checkunknown(wc, p2) #~ if not util.checkcase(repo.path): #~ _checkcollision(p2) action += merge._forgetremoved(wc, p2, branchmerge) action += merge.manifestmerge(repo, wc, p2, pa, overwrite, partial) ### apply phase if not branchmerge: # just jump to the new rev fp1, fp2, xp1, xp2 = fp2, mercurial.node.nullid, xp2, '' if not partial: repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) stats = merge.applyupdates(repo, action, wc, p2, pa) #added: I want to record updates even if partial merge #merge.recordupdates(repo, action, branchmerge) if not partial: repo.dirstate.setparents(fp1, fp2) merge.recordupdates(repo, action, branchmerge) if not branchmerge and not fastforward: repo.dirstate.setbranch(p2.branch()) finally: wlock.release() if not partial: repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3]) return stats
def merge_update( orig, repo, node, branchmerge, force, ancestor=None, mergeancestor=False, labels=None, matcher=None, mergeforce=False, updatecheck=None, wc=None, ): """Apparently node can be a 20-byte hash or an integer referencing a revision number. """ assert node is not None if not util.safehasattr(repo.dirstate, "eden_client"): why_not_eden = "This is not an eden repository." elif matcher is not None and not matcher.always(): why_not_eden = "We don't support doing a partial update through " "eden yet." elif branchmerge: # TODO: We potentially should support handling this scenario ourself in # the future. For now we simply haven't investigated what the correct # semantics are in this case. why_not_eden = 'branchmerge is "truthy:" %s.' % branchmerge elif ancestor is not None: # TODO: We potentially should support handling this scenario ourself in # the future. For now we simply haven't investigated what the correct # semantics are in this case. why_not_eden = "ancestor is not None: %s." % ancestor elif wc is not None and wc.isinmemory(): # In memory merges do not operate on the working directory, # so we don't need to ask eden to change the working directory state # at all, and can use the vanilla merge logic in this case. why_not_eden = "merge is in-memory" else: # TODO: We probably also need to set why_not_eden if there are # subrepositories. (Personally I might vote for just not supporting # subrepos in eden.) why_not_eden = None if why_not_eden: repo.ui.debug("falling back to non-eden update code path: %s\n" % why_not_eden) return orig( repo, node, branchmerge, force, ancestor=ancestor, mergeancestor=mergeancestor, labels=labels, matcher=matcher, mergeforce=mergeforce, updatecheck=updatecheck, wc=wc, ) else: repo.ui.debug("using eden update code path\n") with repo.wlock(): wctx = repo[None] parents = wctx.parents() p1ctx = parents[0] destctx = repo[node] deststr = str(destctx) if not force: # Make sure there isn't an outstanding merge or unresolved files. if len(parents) > 1: raise error.Abort(_("outstanding uncommitted merge")) ms = mergemod.mergestate.read(repo) if list(ms.unresolved()): raise error.Abort(_("outstanding merge conflicts")) # The vanilla merge code disallows updating between two unrelated # branches if the working directory is dirty. I don't really see a # good reason to disallow this; it should be treated the same as if # we committed the changes, checked out the other branch then tried # to graft the changes here. if p1ctx == destctx: # No update to perform. # Just invoke the hooks and return. repo.hook("preupdate", throw=True, parent1=deststr, parent2="") repo.hook("update", parent1=deststr, parent2="", error=0) return 0, 0, 0, 0 # If we are in noconflict mode, then we must do a DRY_RUN first to # see if there are any conflicts that should prevent us from # attempting the update. if updatecheck == "noconflict": conflicts = repo.dirstate.eden_client.checkout( destctx.node(), CheckoutMode.DRY_RUN) if conflicts: actions = _determine_actions_for_conflicts( repo, p1ctx, conflicts) _check_actions_and_raise_if_there_are_conflicts(actions) # Invoke the preupdate hook repo.hook("preupdate", throw=True, parent1=deststr, parent2="") # Record that we're in the middle of an update try: vfs = repo.localvfs except AttributeError: vfs = repo.vfs vfs.write("updatestate", destctx.hex()) # Ask eden to perform the checkout if force: # eden_client.checkout() returns the list of conflicts here, # but since this is a force update it will have already replaced # the conflicts with the destination file state, so we don't have # to do anything with them here. conflicts = repo.dirstate.eden_client.checkout( destctx.node(), CheckoutMode.FORCE) # We do still need to make sure to update the merge state though. # In the non-force code path the merge state is updated in # _handle_update_conflicts(). ms = mergemod.mergestate.clean(repo, p1ctx.node(), destctx.node(), labels) ms.commit() stats = 0, 0, 0, 0 actions = {} else: conflicts = repo.dirstate.eden_client.checkout( destctx.node(), CheckoutMode.NORMAL) # TODO(mbolin): Add a warning if we did a DRY_RUN and the conflicts # we get here do not match. Only in the event of a race would we # expect them to differ from when the DRY_RUN was done (or if we # decide that DIRECTORY_NOT_EMPTY conflicts do not need to be # reported during a DRY_RUN). stats, actions = _handle_update_conflicts(repo, wctx, p1ctx, destctx, labels, conflicts, force) with repo.dirstate.parentchange(): if force: # If the user has done an `update --clean`, then we should # remove all entries from the dirstate. Note this call to # clear() will also remove the parents, but we set them on the # next line, so we'll be OK. repo.dirstate.clear() # TODO(mbolin): Set the second parent, if appropriate. repo.setparents(destctx.node()) mergemod.recordupdates(repo, actions, branchmerge) # Clear the update state util.unlink(vfs.join("updatestate")) # Invoke the update hook repo.hook("update", parent1=deststr, parent2="", error=stats[3]) return stats
def hgupdate(repo, node, branchmerge, force, partial): """Slightly hacked mercurial.merge.update() (To see changes, diff it with version 1.1.2. Please keep this version number up-to-date if you change the derived version.) Perform a merge between the working directory and the given node node = the node to update to, or None if unspecified branchmerge = whether to merge between branches force = whether to force branch merging or file overwriting partial = a function to filter file lists (dirstate not updated) The table below shows all the behaviors of the update command given the -c and -C or no options, whether the working directory is dirty, whether a revision is specified, and the relationship of the parent rev to the target rev (linear, on the same named branch, or on another named branch). This logic is tested by test-update-branches. -c -C dirty rev | linear same cross n n n n | ok (1) x n n n y | ok ok ok n n y * | merge (2) (2) n y * * | --- discard --- y n y * | --- (3) --- y n n * | --- ok --- y y * * | --- (4) --- x = can't happen * = don't-care 1 = abort: crosses branches (use 'hg merge' or 'hg update -c') 2 = abort: crosses branches (use 'hg merge' to merge or use 'hg update -C' to discard changes) 3 = abort: uncommitted local changes 4 = incompatible options (checked in commands.py) """ wlock = repo.wlock() try: wc = repo[None] ###working copy change context if node is None: ###see nothing passing it node # tip of current branch try: node = repo.branchtags()[wc.branch()] except KeyError: if wc.branch() == "default": # no default branch! node = repo.lookup("tip") # update to tip else: raise util.Abort(_("branch %s not found") % wc.branch()) overwrite = force and not branchmerge pl = wc.parents() ###ParentList of working copy p1, p2 = pl[0], repo[node] ### MyChangeContext, MergeChangeContet pa = p1.ancestor(p2) ### CommonAncestorChangeContext fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) ##changeset hashes & short hashes fastforward = False ### check phase if not overwrite and len(pl) > 1: ### wc has multiple parents raise util.Abort(_("outstanding uncommitted merges")) if branchmerge: if pa == p2: ###she is the common ancestor raise util.Abort(_("can't merge with ancestor")) elif pa == p1: ###me is the common ancestor if p1.branch() != p2.branch(): ###...but we are different named branches (usually 'default') fastforward = True ### Merging a branch back into common ancestor else: raise util.Abort(_("nothing to merge (use 'hg update'" " or check 'hg heads')")) if not force and (wc.files() or wc.deleted()): ### edits, adds, or mods in wc raise util.Abort(_("outstanding uncommitted changes")) elif not overwrite: if pa == p1 or pa == p2: # linear pass # all good elif p1.branch() == p2.branch(): if wc.files() or wc.deleted(): raise util.Abort(_("crosses branches (use 'hg merge' or " "'hg update -C' to discard changes)")) raise util.Abort(_("crosses branches (use 'hg merge' " "or 'hg update -C')")) elif wc.files() or wc.deleted(): raise util.Abort(_("crosses named branches (use " "'hg update -C' to discard changes)")) else: # Allow jumping branches if there are no changes overwrite = True ### calculate phase action = [] if not force: merge._checkunknown(wc, p2) #~ if not util.checkcase(repo.path): #~ _checkcollision(p2) action += merge._forgetremoved(wc, p2, branchmerge) action += merge.manifestmerge(repo, wc, p2, pa, overwrite, partial) ### apply phase if not branchmerge: # just jump to the new rev fp1, fp2, xp1, xp2 = fp2, mercurial.node.nullid, xp2, '' if not partial: repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) stats = merge.applyupdates(repo, action, wc, p2, pa) #added: I want to record updates even if partial merge #merge.recordupdates(repo, action, branchmerge) if not partial: repo.dirstate.setparents(fp1, fp2) merge.recordupdates(repo, action, branchmerge) if not branchmerge and not fastforward: repo.dirstate.setbranch(p2.branch()) finally: wlock.release() if not partial: repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3]) return stats