def _destrestack(repo, subset, x): """restack destination for given single source revision""" unfi = repo.unfiltered() obsoleted = unfi.revs("obsolete()") getparents = unfi.changelog.parentrevs getphase = unfi._phasecache.phase nodemap = unfi.changelog.nodemap src = revset.getset(repo, subset, x).first() # Empty src or already obsoleted - Do not return a destination if not src or src in obsoleted: return smartset.baseset() # Find the obsoleted "base" by checking source's parent recursively base = src while base not in obsoleted: base = getparents(base)[0] # When encountering a public revision which cannot be obsoleted, stop # the search early and return no destination. Do the same for nullrev. if getphase(repo, base) == phases.public or base == nullrev: return smartset.baseset() # Find successors for given base # NOTE: Ideally we can use obsutil.successorssets to detect divergence # case. However it does not support cycles (unamend) well. So we use # allsuccessors and pick non-obsoleted successors manually as a workaround. basenode = repo[base].node() if mutation.enabled(repo): succnodes = mutation.allsuccessors(repo, [basenode]) else: succnodes = obsutil.allsuccessors(repo.obsstore, [basenode]) succnodes = [ n for n in succnodes if (n != basenode and n in nodemap and nodemap[n] not in obsoleted) ] # In case of a split, only keep its heads succrevs = list(unfi.revs("heads(%ln)", succnodes)) if len(succrevs) == 0: # Prune - Find the first non-obsoleted ancestor while base in obsoleted: base = getparents(base)[0] if base == nullrev: # Root node is pruned. The new base (destination) is the # virtual nullrev. return smartset.baseset([nullrev]) return smartset.baseset([base]) elif len(succrevs) == 1: # Unique visible successor case - A valid destination return smartset.baseset([succrevs[0]]) else: # Multiple visible successors - Choose the one with a greater revision # number. This is to be compatible with restack old behavior. We might # want to revisit it when we introduce the divergence concept to users. return smartset.baseset([max(succrevs)])
def smarthide(repo, revhide, revshow, local=False): """hides changecontexts and reveals some commits tries to connect related hides and shows with obs marker when reasonable and correct use local to not hide revhides without corresponding revshows """ hidectxs = repo.set(revhide) showctxs = repo.set(revshow) markers = [] nodes = [] for ctx in hidectxs: unfi = repo.unfiltered() related = set() if mutation.enabled(unfi): related.update(mutation.allpredecessors(unfi, [ctx.node()])) related.update(mutation.allsuccessors(unfi, [ctx.node()])) else: related.update(obsutil.allpredecessors(unfi.obsstore, [ctx.node()])) related.update(obsutil.allsuccessors(unfi.obsstore, [ctx.node()])) related.intersection_update(x.node() for x in showctxs) destinations = [repo[x] for x in related] # two primary objectives: # 1. correct divergence/nondivergence # 2. correct visibility of changesets for the user # secondary objectives: # 3. useful ui message in hg sl: "Undone to" # Design choices: # 1-to-1 correspondence is easy # 1-to-many correspondence is hard: # it's either divergent A to B, A to C # or split A to B,C # because of undo we don't know which # without complex logic # Solution: provide helpful ui message for # common and easy case (1 to 1), use simplest # correct solution for complex edge case if len(destinations) == 1: markers.append((ctx, destinations)) nodes.append(ctx.node()) elif len(destinations) > 1: # split markers.append((ctx, [])) nodes.append(ctx.node()) elif len(destinations) == 0: if not local: markers.append((ctx, [])) nodes.append(ctx.node()) if obsolete.isenabled(repo, obsolete.createmarkersopt): obsolete.createmarkers(repo, markers, operation="undo") visibility.remove(repo, nodes)