def run(self): # type: () -> None revs = None paths = self.paths self._paths_to_fetch = len(paths) for path in paths: g = self.generate(path) gen = smartset.generatorset(g, iterasc=False) gen.reverse() if revs: revs = smartset.addset(revs, gen, ascending=False) else: revs = gen if revs: for rev in revs: if self.stopped(): break self.queue.put((self.id, True, rev)) # The end marker (self.id, True, None) indicates that the thread # completed successfully. Don't send it if the thread is stopped. # The thread can be stopped for one of two reasons: # 1. The fastlog service failed - in this case, flagging a successful # finish is harmful, because it will stop us continuing with local # results, truncating output. # 2. The caller is going to ignore all future results from us. In this # case, it'll ignore the end marker anyway - it's discarding the # entire queue. if not self.stopped(): self.queue.put((self.id, True, None))
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