def _oldworkingcopyparent(repo, subset, x): """``oldworkingcopyparent([index])`` previous working copy parent 'index' is how many undoable commands you want to look back. See 'hg undo'. """ args = revset.getargsdict(x, "oldoworkingcopyrevset", "reverseindex") reverseindex = revsetlang.getinteger( args.get("reverseindex"), _("index must be a positive interger"), 1) revs = _getoldworkingcopyparent(repo, reverseindex) return subset & smartset.baseset(revs)
def smartlogrevset(repo, subset, x): """``smartlog([heads], [master])`` Changesets relevent to you. 'heads' overrides what feature branches to include. (default: 'interestingbookmarks() + heads(draft()) + .') 'master' is the head of the public branch. (default: 'interestingmaster()') """ args = revset.getargsdict(x, "smartlogrevset", "heads master") if "master" in args: masterset = revset.getset(repo, subset, args["master"]) else: masterset = repo.revs("interestingmaster()") if "heads" in args: heads = set(revset.getset(repo, subset, args["heads"])) else: heads = set(repo.revs("interestingbookmarks() + heads(draft()) + .")) # Remove "null" commit. "::x" does not support it. masterset -= smartset.baseset([nodemod.nullrev]) if nodemod.nullrev in heads: heads.remove(nodemod.nullrev) # Explicitly disable revnum deprecation warnings. with repo.ui.configoverride({("devel", "legacy.revnum:real"): ""}): # Select ancestors that are draft. drafts = repo.revs("draft() & ::%ld", heads) # Include parents of drafts, and public heads. revs = repo.revs( "parents(%ld) + %ld + %ld + %ld", drafts, drafts, heads, masterset ) # Include the ancestor of above commits to make the graph connected. # # When calculating ancestors, filter commits using 'public()' to reduce the # number of commits to calculate. This is sound because the above logic # includes p1 of draft commits, and assume master is public. Practically, # this optimization can make a 3x difference. revs = smartset.baseset(repo.revs("ancestor(%ld & public()) + %ld", revs, revs)) # Collapse long obsoleted stack - only keep their heads and roots. # This is incompatible with automation (namely, nuclide-core) yet. if repo.ui.configbool("smartlog", "collapse-obsolete") and not repo.ui.plain(): obsrevs = smartset.baseset(repo.revs("%ld & obsolete()", revs)) hiderevs = smartset.baseset( repo.revs("%ld - (heads(%ld) + roots(%ld))", obsrevs, obsrevs, obsrevs) ) revs = repo.revs("%ld - %ld", revs, hiderevs) return subset & revs
def _olddraft(repo, subset, x): """``olddraft([index])`` previous draft commits 'index' is how many undoable commands you want to look back an undoable command is one that changed draft heads, bookmarks and or working copy parent. Note that olddraft uses an absolute index and so olddraft(1) represents the state after an hg undo -a and not an hg undo. Note: this revset may include hidden commits """ args = revset.getargsdict(x, "olddraftrevset", "reverseindex") reverseindex = revsetlang.getinteger(args.get("reverseindex"), _("index must be a positive integer"), 1) revs = _getolddrafts(repo, reverseindex) return subset & smartset.baseset(revs)
def _localbranch(repo, subset, x): """``_localbranch(changectx)`` localbranch changesets Returns all commits within the same localbranch as the changeset(s). A local branch is all draft changesets that are connected, uninterupted by public changesets. Any draft commit within a branch, or a public commit at the base of the branch, can be used to identify localbranches. """ # executed on an filtered repo args = revset.getargsdict(x, "branchrevset", "changectx") revstring = revsetlang.getstring( args.get("changectx"), _("localbranch argument must be a changectx")) revs = repo.revs(revstring) # we assume that there is only a single rev if repo[revs.first()].phase() == phases.public: querystring = revsetlang.formatspec("(children(%d) & draft())::", revs.first()) else: querystring = revsetlang.formatspec("((::%ld) & draft())::", revs) return subset & smartset.baseset(repo.revs(querystring))
def fastlogfollow(orig, repo, subset, x, name, followfirst=False): if followfirst: # fastlog does not support followfirst=True repo.ui.debug("fastlog: not used because 'followfirst' is set\n") return orig(repo, subset, x, name, followfirst) args = revset.getargsdict(x, name, "file startrev") if "file" not in args: # Not interesting for fastlog case. repo.ui.debug("fastlog: not used because 'file' is not provided\n") return orig(repo, subset, x, name, followfirst) if "startrev" in args: revs = revset.getset(repo, smartset.fullreposet(repo), args["startrev"]) it = iter(revs) try: startrev = next(it) except StopIteration: startrev = repo["."].rev() try: next(it) # fastlog does not support multiple startrevs repo.ui.debug( "fastlog: not used because multiple revs are provided\n") return orig(repo, subset, x, name, followfirst) except StopIteration: # supported by fastlog: startrev contains a single rev pass else: startrev = repo["."].rev() reponame = repo.ui.config("fbscmquery", "reponame") if not reponame or not repo.ui.configbool("fastlog", "enabled"): repo.ui.debug("fastlog: not used because fastlog is disabled\n") return orig(repo, subset, x, name, followfirst) path = revset.getstring(args["file"], _("%s expected a pattern") % name) if path.startswith("path:"): # strip "path:" prefix path = path[5:] if any( path.startswith("%s:" % prefix) for prefix in matchmod.allpatternkinds): # Patterns other than "path:" are not supported repo.ui.debug( "fastlog: not used because '%s:' patterns are not supported\n" % path.split(":", 1)[0]) return orig(repo, subset, x, name, followfirst) files = [path] if not files or "." in files: # Walking the whole repo - bail on fastlog repo.ui.debug( "fastlog: not used because walking through the entire repo\n") return orig(repo, subset, x, name, followfirst) dirs = set() wvfs = repo.wvfs for path in files: if wvfs.isdir(path) and not wvfs.islink(path): dirs.update([path + "/"]) else: if repo.ui.configbool("fastlog", "files"): dirs.update([path]) else: # bail on symlinks, and also bail on files for now # with follow behavior, for files, we are supposed # to track copies / renames, but it isn't convenient # to do this through scmquery repo.ui.debug( "fastlog: not used because %s is not a directory\n" % path) return orig(repo, subset, x, name, followfirst) rev = startrev parents = repo.changelog.parentrevs public = set() # Our criterion for invoking fastlog is finding a single # common public ancestor from the current head. First we # have to walk back through drafts to find all interesting # public parents. Typically this will just be one, but if # there are merged drafts, we may have multiple parents. if repo[rev].phase() == phases.public: public.add(rev) else: queue = deque() queue.append(rev) seen = set() while queue: cur = queue.popleft() if cur not in seen: seen.add(cur) if repo[cur].mutable(): for p in parents(cur): if p != nullrev: queue.append(p) else: public.add(cur) def fastlog(repo, startrev, dirs, localmatch): filefunc = repo.changelog.readfiles for parent in lazyparents(startrev, public, parents): files = filefunc(parent) if dirmatches(files, dirs): yield parent repo.ui.debug("found common parent at %s\n" % repo[parent].hex()) for rev in combinator(repo, parent, dirs, localmatch): yield rev def combinator(repo, rev, dirs, localmatch): """combinator(repo, rev, dirs, localmatch) Make parallel local and remote queries along ancestors of rev along path and combine results, eliminating duplicates, restricting results to those which match dirs """ LOCAL = "L" REMOTE = "R" queue = util.queue(FASTLOG_QUEUE_SIZE + 100) hash = repo[rev].hex() local = LocalIteratorThread(queue, LOCAL, rev, dirs, localmatch, repo) remote = FastLogThread(queue, REMOTE, reponame, "hg", hash, dirs, repo) # Allow debugging either remote or local path debug = repo.ui.config("fastlog", "debug") if debug != "local": repo.ui.debug("starting fastlog at %s\n" % hash) remote.start() if debug != "remote": local.start() seen = set([rev]) try: while True: try: producer, success, msg = queue.get(True, 3600) except util.empty: raise error.Abort("Timeout reading log data") if not success: if producer == LOCAL: raise error.Abort(msg) elif msg: repo.ui.log("hgfastlog", msg) continue if msg is None: # Empty message means no more results return rev = msg if debug: if producer == LOCAL: repo.ui.debug("LOCAL:: %s\n" % msg) elif producer == REMOTE: repo.ui.debug("REMOTE:: %s\n" % msg) if rev not in seen: seen.add(rev) yield rev finally: local.stop() remote.stop() revgen = fastlog(repo, rev, dirs, dirmatches) fastlogset = smartset.generatorset(revgen, iterasc=False) # Optimization: typically for "reverse(:.) & follow(path)" used by # "hg log". The left side is more expensive, although it has smaller # "weight". Make sure fastlogset is on the left side to avoid slow # walking through ":.". if subset.isdescending(): fastlogset.reverse() return fastlogset & subset return subset & fastlogset