def revsetmatch(ui, pattern): try: # hg >= 1.9 return revset.match(ui, pattern) except TypeError: # hg <= 1.8 return revset.match(pattern)
def getlogrevs(repo, pats, opts): """Return (revs, expr, filematcher) where revs is a list of revision numbers, expr is a revset string built from log options and file patterns or None, and used to filter 'revs'. If --stat or --patch are not passed filematcher is None. Otherwise it is a callable taking a revision number and returning a match objects filtering the files to be detailed when displaying the revision. """ if not len(repo): return [], None, None # Default --rev value depends on --follow but --follow behaviour # depends on revisions resolved from --rev... follow = opts.get('follow') or opts.get('follow_first') if opts.get('rev'): revs = scmutil.revrange(repo, opts['rev']) else: if follow and len(repo) > 0: revs = scmutil.revrange(repo, ['.:0']) else: revs = range(len(repo) - 1, -1, -1) if not revs: return [], None, None expr, filematcher = _makelogrevset(repo, pats, opts, revs) if expr: # Evaluate revisions in changelog order for performance # reasons but preserve the original sequence order in the # filtered result. matched = set(revset.match(repo.ui, expr)(repo, sorted(revs))) revs = [r for r in revs if r in matched] if not opts.get('hidden'): # --hidden is still experimental and not worth a dedicated revset # yet. Fortunately, filtering revision number is fast. revs = [r for r in revs if r not in repo.changelog.hiddenrevs] return revs, expr, filematcher
def run(self): if '(' not in self.text: try: ctx = self.repo[self.text] self.showMessage.emit(_('found revision')) self.queryIssued.emit(self.query, [ctx.rev()]) return except (error.RepoError, error.LookupError, error.Abort): self.text = 'keyword("%s")' % self.text cwd = os.getcwd() try: os.chdir(self.repo.root) func = revset.match(self.repo.ui, self.text) l = [] for c in func(self.repo, range(len(self.repo))): l.append(c) if len(l): self.showMessage.emit(_('%d matches found') % len(l)) else: self.showMessage.emit(_('No matches found')) self.queryIssued.emit(self.query, l) except error.ParseError, e: if len(e.args) == 2: msg, pos = e.args self.setCursorPosition.emit(0, pos) else: msg = e.args[0] self.showMessage.emit(_('Parse Error: ') + hglib.tounicode(msg))
def update_info(self, *args): def set_csinfo_mode(mode): """Show the csinfo widget or the info text label""" # hide first, then show if mode: self.rev_to_match_info_text.setShown(False) self.rev_to_match_info.setShown(True) else: self.rev_to_match_info.setShown(False) self.rev_to_match_info_text.setShown(True) def csinfo_update(ctx): self.rev_to_match_info.update(ctx) set_csinfo_mode(True) def csinfo_set_text(text): self.rev_to_match_info_text.setText(text) set_csinfo_mode(False) self.rev_to_match_info_lbl.setText(_('Revision to Match:')) new_rev = hglib.fromunicode(self.rev_combo.currentText()) if new_rev.lower() == 'null': self.match_btn.setEnabled(True) return try: csinfo_update(self.repo[new_rev]) return except (error.LookupError, error.RepoLookupError, error.RepoError): pass # If we get this far, assume we are matching a revision set validrevset = False try: func = revset.match(self.repo.ui, new_rev) rset = [c for c in func(self.repo, range(len(self.repo)))] if len(rset) > 1: self.rev_to_match_info_lbl.setText(_('Revisions to Match:')) csinfo_set_text(_('Match any of <b><i>%d</i></b> revisions') \ % len(rset)) else: self.rev_to_match_info_lbl.setText(_('Revision to Match:')) csinfo_update(rset[0]) validrevset = True except (error.LookupError, error.RepoLookupError): csinfo_set_text(_('<b>Unknown revision!</b>')) except (error.ParseError): csinfo_set_text(_('<b>Parse Error!</b>')) if validrevset: self.match_btn.setEnabled(True) else: self.match_btn.setDisabled(True)
def commit(self): tip, base = self.revs func = revset.match(self.repo.ui, '%s::%s' % (base, tip)) revcount = len(self.repo) revs = [c for c in func(self.repo, range(revcount)) if c != base] descs = [self.repo[c].description() for c in revs] self.repo.opener('cur-message.txt', 'w').write('\n* * *\n'.join(descs)) dlg = commit.CommitDialog(self.repo, [], {}, self) dlg.finished.connect(dlg.deleteLater) dlg.exec_() self.showMessage.emit(_('Compress is complete, old history untouched')) self.compressbtn.setText(_('Close')) self.compressbtn.clicked.disconnect(self.commit) self.compressbtn.clicked.connect(self.accept)
def run(self): cwd = os.getcwd() try: os.chdir(self.repo.root) func = revset.match(self.repo.ui, self.text) l = list(func(self.repo, list(self.repo))) if len(l): self.showMessage.emit(_('%d matches found') % len(l)) else: self.showMessage.emit(_('No matches found')) self.queryIssued.emit(self.query, l) except error.ParseError, e: if len(e.args) == 2: msg, pos = e.args self.setCursorPosition.emit(0, pos) else: msg = e.args[0] self.showMessage.emit(_('Parse Error: ') + hglib.tounicode(msg))
def getsearchmode(query): try: ctx = web.repo[query] except (error.RepoError, error.LookupError): # query is not an exact revision pointer, need to # decide if it's a revset expression or keywords pass else: return MODE_REVISION, ctx revdef = 'reverse(%s)' % query try: tree = revset.parse(revdef) except ParseError: # can't parse to a revset tree return MODE_KEYWORD, query if revset.depth(tree) <= 2: # no revset syntax used return MODE_KEYWORD, query if any((token, (value or '')[:3]) == ('string', 're:') for token, value, pos in revset.tokenize(revdef)): return MODE_KEYWORD, query funcsused = revset.funcsused(tree) if not funcsused.issubset(revset.safesymbols): return MODE_KEYWORD, query mfunc = revset.match(web.repo.ui, revdef) try: revs = mfunc(web.repo) return MODE_REVSET, revs # ParseError: wrongly placed tokens, wrongs arguments, etc # RepoLookupError: no such revision, e.g. in 'revision:' # Abort: bookmark/tag not exists # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo except (ParseError, RepoLookupError, Abort, LookupError): return MODE_KEYWORD, query
def getsearchmode(query): try: ctx = web.repo[query] except (error.RepoError, error.LookupError): # query is not an exact revision pointer, need to # decide if it's a revset expression or keywords pass else: return MODE_REVISION, ctx revdef = 'reverse(%s)' % query try: tree, pos = revset.parse(revdef) except ParseError: # can't parse to a revset tree return MODE_KEYWORD, query if revset.depth(tree) <= 2: # no revset syntax used return MODE_KEYWORD, query if util.any((token, (value or '')[:3]) == ('string', 're:') for token, value, pos in revset.tokenize(revdef)): return MODE_KEYWORD, query funcsused = revset.funcsused(tree) if not funcsused.issubset(revset.safesymbols): return MODE_KEYWORD, query mfunc = revset.match(web.repo.ui, revdef) try: revs = mfunc(web.repo, list(web.repo)) return MODE_REVSET, revs # ParseError: wrongly placed tokens, wrongs arguments, etc # RepoLookupError: no such revision, e.g. in 'revision:' # Abort: bookmark/tag not exists # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo except (ParseError, RepoLookupError, Abort, LookupError): return MODE_KEYWORD, query
def revs(self, expr, *args): expr = revsetlang.formatspec(expr, *args) m = revset.match(None, expr) return m(self)
def getfastlogrevs(orig, repo, pats, opts): blacklist = ['all', 'branch', 'rev', 'sparse'] if any(opts.get(opt) for opt in blacklist) or not opts.get('follow'): return orig(repo, pats, opts) reponame = repo.ui.config('fbconduit', 'reponame') if reponame and repo.ui.configbool('fastlog', 'enabled'): wctx = repo[None] match, pats = scmutil.matchandpats(wctx, pats, opts) files = match.files() if not files or '.' in files: # Walking the whole repo - bail on fastlog return orig(repo, pats, opts) dirs = set() wvfs = repo.wvfs for path in files: if wvfs.isdir(path) and not wvfs.islink(path): 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 return orig(repo, pats, opts) rev = repo['.'].rev() 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() # Complex match - use a revset. complex = ['date', 'exclude', 'include', 'keyword', 'no_merges', 'only_merges', 'prune', 'user'] if match.anypats() or any(opts.get(opt) for opt in complex): f = fastlog(repo, rev, dirs, None) revs = smartset.generatorset(f, iterasc=False) revs.reverse() if not revs: return smartset.baseset([]), None, None expr, filematcher = cmdutil._makelogrevset(repo, pats, opts, revs) matcher = revset.match(repo.ui, expr) matched = matcher(repo, revs) return matched, expr, filematcher else: # Simple match without revset shaves ~0.5 seconds off # hg log -l 100 -T ' ' on common directories. expr = 'fastlog(%s)' % ','.join(dirs) return fastlog(repo, rev, dirs, dirmatches), expr, None return orig(repo, pats, opts)