from os.path import join from mercurial import repo, localrepo, util, cmdutil, hg, revlog from mercurial.node import short from hgcomp import hgui from hgcomp.templatetags.hgtags import hgdate from lib.diffstat import diffstat def maybe_int(val): try: return int(val) except ValueError: return val class Changelog(object): def __init__(self, repo, pats=None, revrange=None, reversed=None): self.repo = repo if pats: self.pats = [join(repo.root, pat) for pat in pats] else: self.pats = [] self._range = revrange self._reversed = reversed def __repr__(self): return repr(list(self)) def __len__(self): return len(self.repo) def __nonzero__(self): return bool(len(self.repo)) def __iter__(self): return self.iterator() def __getitem__(self, k): if isinstance(k, slice): start = k.start if k.start is not None else '' stop = k.stop if k.stop is not None else '' _range = '%s:%s' % (start, stop) else: _range = str(k) cl = self._clone() cl._range = _range if cl._reversed: cl._reverse_range() return cl def _reverse_range(self): length = len(self) if ':' not in self._range: try: r = int(self._range) if r <= length: # swap number in other end of changelog self._range = str(length - r) except ValueError: pass return start, stop = self._range.split(':') try: start, stop = int(start), int(stop) if start <= length and stop <= length: self._range = '%s:%s' % (length - start, length - stop) except ValueError: self._range = '%s:%s' % (stop, start) def _fix_range(self, revrange): if not ':' in revrange: return revrange start, stop = revrange.split(':') start = maybe_int(start) stop = maybe_int(stop) if self._reversed: if start: if isinstance(start, int): start = self.repo[start - 1].hex() else: start = self.repo[start].parents()[0].hex() else: start = '' stop = self.repo[stop].hex() if stop else '' else: if stop: if isinstance(stop, int): stop = self.repo[stop + 1].hex() else: stop = self.repo[stop].children()[0].hex() else: stop = '' start = self.repo[start].hex() if start else '' return '%s:%s' % (start, stop) def _clone(self): return self.__class__(self.repo, self.pats, self._range, self._reversed) def reversed(self): cl = self._clone() cl._reversed = True if cl._range: cl._reverse_range() return cl def iterator(self, revrange=None): """ An iterator over the changes from repo. """ if not revrange and self._range: revrange = self._range revrange = self._fix_range(revrange) if not revrange and self._reversed: revrange = '%s:%s' % (len(self), 0) opts = {'rev': revrange and [revrange] or None, 'follow': True} def getrenamed(ctx): for f in ctx.files(): if not f in ctx.manifest(): continue cp = ctx.filectx(f).renamed() if cp: # to, from yield (f, cp[0]) get = util.cachefunc(lambda r: self.repo[r].changeset()) changeiter, matchfn = cmdutil.walkchangerevs(hgui, self.repo, self.pats, get, opts) cache, forward = [], True for st, rev, fns in changeiter: if st == 'window': # "rev" in 'window' iteration determines direction forward = rev elif st == 'iter': # Don't worry, len(iter) == len(add), # so cache will not raise IndexError if not forward: yield cache.pop() elif st == 'add': ctx = self.repo[rev] files, ins, rem = diffstat(ctx) ctx.diffstat = {'files': files, 'inserted': ins, 'deleted': rem} ctx.short = short(ctx.node()) ctx.date = hgdate(ctx.date()) ctx.copies = list(getrenamed(ctx)) if forward: yield ctx else: cache.append(ctx)