def prunetemporaryincludes(self): if repo.vfs.exists('tempsparse'): origstatus = self.status() modified, added, removed, deleted, a, b, c = origstatus if modified or added or removed or deleted: # Still have pending changes. Don't bother trying to prune. return sparsematch = self.sparsematch(includetemp=False) dirstate = self.dirstate actions = [] dropped = [] tempincludes = self.gettemporaryincludes() for file in tempincludes: if file in dirstate and not sparsematch(file): message = 'dropping temporarily included sparse files' actions.append((file, None, message)) dropped.append(file) typeactions = collections.defaultdict(list) typeactions['r'] = actions mergemod.applyupdates(self, typeactions, self[None], self['.'], False) # Fix dirstate for file in dropped: dirstate.drop(file) self.vfs.unlink('tempsparse') self.invalidatesignaturecache() msg = _("cleaned up %d temporarily added file(s) from the " "sparse checkout\n") ui.status(msg % len(tempincludes))
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 _applyupdates(repo, actions, wctx, dest, labels, conflicts): numerrors = sum(1 for c in conflicts if c.type == ConflictType.ERROR) # Call applyupdates # Note that applyupdates may mutate actions. stats = mergemod.applyupdates(repo, actions, wctx, dest, overwrite=False, labels=labels) # Add the error count to the number of unresolved files. # This ensures we exit unsuccessfully if there were any errors return (stats[0], stats[1], stats[2], stats[3] + numerrors), actions
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 _refresh(ui, repo, origstatus, origsparsematch, force): """Refreshes which files are on disk by comparing the old status and sparsematch with the new sparsematch. Will raise an exception if a file with pending changes is being excluded or included (unless force=True). """ modified, added, removed, deleted, unknown, ignored, clean = origstatus # Verify there are no pending changes pending = set() pending.update(modified) pending.update(added) pending.update(removed) sparsematch = repo.sparsematch() abort = False for file in pending: if not sparsematch(file): ui.warn(_("pending changes to '%s'\n") % file) abort = not force if abort: raise error.Abort(_("could not update sparseness due to " + "pending changes")) # Calculate actions dirstate = repo.dirstate ctx = repo['.'] added = [] lookup = [] dropped = [] mf = ctx.manifest() files = set(mf) actions = {} for file in files: old = origsparsematch(file) new = sparsematch(file) # Add files that are newly included, or that don't exist in # the dirstate yet. if (new and not old) or (old and new and not file in dirstate): fl = mf.flags(file) if repo.wvfs.exists(file): actions[file] = ('e', (fl,), '') lookup.append(file) else: actions[file] = ('g', (fl, False), '') added.append(file) # Drop files that are newly excluded, or that still exist in # the dirstate. elif (old and not new) or (not old and not new and file in dirstate): dropped.append(file) if file not in pending: actions[file] = ('r', [], '') # Verify there are no pending changes in newly included files abort = False for file in lookup: ui.warn(_("pending changes to '%s'\n") % file) abort = not force if abort: raise error.Abort(_("cannot change sparseness due to " + "pending changes (delete the files or use --force " + "to bring them back dirty)")) # Check for files that were only in the dirstate. for file, state in dirstate.iteritems(): if not file in files: old = origsparsematch(file) new = sparsematch(file) if old and not new: dropped.append(file) # Apply changes to disk typeactions = dict((m, []) for m in 'a f g am cd dc r dm dg m e k p pr'.split()) for f, (m, args, msg) in actions.iteritems(): if m not in typeactions: typeactions[m] = [] typeactions[m].append((f, args, msg)) mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False) # Fix dirstate for file in added: dirstate.normal(file) for file in dropped: dirstate.drop(file) for file in lookup: # File exists on disk, and we're bringing it back in an unknown state. dirstate.normallookup(file) return added, dropped, lookup
def _calculateupdates(orig, repo, wctx, mctx, ancestors, branchmerge, *arg, **kwargs): """Filter updates to only lay out files that match the sparse rules. """ actions, diverge, renamedelete = orig(repo, wctx, mctx, ancestors, branchmerge, *arg, **kwargs) if not util.safehasattr(repo, 'sparsematch'): return actions, diverge, renamedelete files = set() prunedactions = {} oldrevs = [pctx.rev() for pctx in wctx.parents()] oldsparsematch = repo.sparsematch(*oldrevs) if branchmerge: # If we're merging, use the wctx filter, since we're merging into # the wctx. sparsematch = repo.sparsematch(wctx.parents()[0].rev()) else: # If we're updating, use the target context's filter, since we're # moving to the target context. sparsematch = repo.sparsematch(mctx.rev()) temporaryfiles = [] for file, action in actions.iteritems(): type, args, msg = action files.add(file) if sparsematch(file): prunedactions[file] = action elif type == 'm': temporaryfiles.append(file) prunedactions[file] = action elif branchmerge: if type != 'k': temporaryfiles.append(file) prunedactions[file] = action elif type == 'f': prunedactions[file] = action elif file in wctx: prunedactions[file] = ('r', args, msg) if len(temporaryfiles) > 0: ui.status(_("temporarily included %d file(s) in the sparse checkout" " for merging\n") % len(temporaryfiles)) repo.addtemporaryincludes(temporaryfiles) # Add the new files to the working copy so they can be merged, etc actions = [] message = 'temporarily adding to sparse checkout' wctxmanifest = repo[None].manifest() for file in temporaryfiles: if file in wctxmanifest: fctx = repo[None][file] actions.append((file, (fctx.flags(), False), message)) typeactions = collections.defaultdict(list) typeactions['g'] = actions mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False) dirstate = repo.dirstate for file, flags, msg in actions: dirstate.normal(file) profiles = repo.getactiveprofiles() changedprofiles = profiles & files # If an active profile changed during the update, refresh the checkout. # Don't do this during a branch merge, since all incoming changes should # have been handled by the temporary includes above. if changedprofiles and not branchmerge: mf = mctx.manifest() for file in mf: old = oldsparsematch(file) new = sparsematch(file) if not old and new: flags = mf.flags(file) prunedactions[file] = ('g', (flags, False), '') elif old and not new: prunedactions[file] = ('r', [], '') return prunedactions, diverge, renamedelete
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 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 _handleupdateconflicts(repo, wctx, src, dest, labels, conflicts): # When resolving conflicts during an update operation, the working # directory (wctx) is one side of the merge, the destination commit (dest) # is the other side of the merge, and the source commit (src) is treated as # the common ancestor. # # This is what we want with respect to the graph topology. If we are # updating from commit A (src) to B (dest), and the real ancestor is C, we # effectively treat the update operation as reverting all commits from A to # C, then applying the commits from C to B. We are then trying to re-apply # the local changes in the working directory (against A) to the new # location B. Using A as the common ancestor in this operation is the # desired behavior. # Build a list of actions to pass to mergemod.applyupdates() actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split()) numerrors = 0 for conflict in conflicts: # The action tuple is: # - path_in_1, path_in_2, path_in_ancestor, move, ancestor_node if conflict.type == ConflictType.ERROR: # We don't record this as a conflict for now. # We will report the error, but the file will show modified in # the working directory status after the update returns. repo.ui.write_err( _('error updating %s: %s\n') % (conflict.path, conflict.message)) numerrors += 1 continue elif conflict.type == ConflictType.MODIFIED_REMOVED: action_type = 'cd' action = (conflict.path, None, conflict.path, False, src.node()) prompt = "prompt changed/deleted" elif conflict.type == ConflictType.UNTRACKED_ADDED: action_type = 'c' action = (dest.manifest().flags(conflict.path), ) prompt = "remote created" elif conflict.type == ConflictType.REMOVED_MODIFIED: action_type = 'dc' action = (None, conflict.path, conflict.path, False, src.node()) prompt = "prompt deleted/changed" elif conflict.type == ConflictType.MISSING_REMOVED: # Nothing to do here really. The file was already removed # locally in the working directory before, and it was removed # in the new commit. continue elif conflict.type == ConflictType.MODIFIED: action_type = 'm' action = (conflict.path, conflict.path, conflict.path, False, src.node()) prompt = "versions differ" else: raise Exception('unknown conflict type received from eden: ' '%r, %r, %r' % (conflict.type, conflict.path, conflict.message)) actions[action_type].append((conflict.path, action, prompt)) # Call applyupdates stats = mergemod.applyupdates(repo, actions, wctx, dest, overwrite=False, labels=labels) # Add the error count to the number of unresolved files. # This ensures we exit unsuccessfully if there were any errors return (stats[0], stats[1], stats[2], stats[3] + numerrors)