mercurial/logcmdutil.py
changeset 36007 29b83c08afe0
parent 36005 dd77e36eabb6
child 36008 006ff7268c5c
equal deleted inserted replaced
36006:f113ac0750f3 36007:29b83c08afe0
   120                 tempnode2 = None
   120                 tempnode2 = None
   121             submatch = matchmod.subdirmatcher(subpath, match)
   121             submatch = matchmod.subdirmatcher(subpath, match)
   122             sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
   122             sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
   123                      stat=stat, fp=fp, prefix=prefix)
   123                      stat=stat, fp=fp, prefix=prefix)
   124 
   124 
       
   125 class changesetdiffer(object):
       
   126     """Generate diff of changeset with pre-configured filtering functions"""
       
   127 
       
   128     def _makefilematcher(self, ctx):
       
   129         return scmutil.matchall(ctx.repo())
       
   130 
       
   131     def _makehunksfilter(self, ctx):
       
   132         return None
       
   133 
       
   134     def showdiff(self, ui, ctx, diffopts, stat=False):
       
   135         repo = ctx.repo()
       
   136         node = ctx.node()
       
   137         prev = ctx.p1().node()
       
   138         diffordiffstat(ui, repo, diffopts, prev, node,
       
   139                        match=self._makefilematcher(ctx), stat=stat,
       
   140                        hunksfilterfn=self._makehunksfilter(ctx))
       
   141 
   125 def changesetlabels(ctx):
   142 def changesetlabels(ctx):
   126     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
   143     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
   127     if ctx.obsolete():
   144     if ctx.obsolete():
   128         labels.append('changeset.obsolete')
   145         labels.append('changeset.obsolete')
   129     if ctx.isunstable():
   146     if ctx.isunstable():
   133     return ' '.join(labels)
   150     return ' '.join(labels)
   134 
   151 
   135 class changesetprinter(object):
   152 class changesetprinter(object):
   136     '''show changeset information when templating not requested.'''
   153     '''show changeset information when templating not requested.'''
   137 
   154 
   138     def __init__(self, ui, repo, makefilematcher=None, makehunksfilter=None,
   155     def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
   139                  diffopts=None, buffered=False):
       
   140         self.ui = ui
   156         self.ui = ui
   141         self.repo = repo
   157         self.repo = repo
   142         self.buffered = buffered
   158         self.buffered = buffered
   143         self._makefilematcher = makefilematcher or (lambda ctx: None)
   159         self._differ = differ or changesetdiffer()
   144         self._makehunksfilter = makehunksfilter or (lambda ctx: None)
       
   145         self.diffopts = diffopts or {}
   160         self.diffopts = diffopts or {}
   146         self.header = {}
   161         self.header = {}
   147         self.hunk = {}
   162         self.hunk = {}
   148         self.lastheader = None
   163         self.lastheader = None
   149         self.footer = None
   164         self.footer = None
   278     def _exthook(self, ctx):
   293     def _exthook(self, ctx):
   279         '''empty method used by extension as a hook point
   294         '''empty method used by extension as a hook point
   280         '''
   295         '''
   281 
   296 
   282     def _showpatch(self, ctx):
   297     def _showpatch(self, ctx):
   283         matchfn = self._makefilematcher(ctx)
       
   284         hunksfilterfn = self._makehunksfilter(ctx)
       
   285         if not matchfn:
       
   286             return
       
   287         stat = self.diffopts.get('stat')
   298         stat = self.diffopts.get('stat')
   288         diff = self.diffopts.get('patch')
   299         diff = self.diffopts.get('patch')
   289         diffopts = patch.diffallopts(self.ui, self.diffopts)
   300         diffopts = patch.diffallopts(self.ui, self.diffopts)
   290         node = ctx.node()
       
   291         prev = ctx.p1().node()
       
   292         if stat:
   301         if stat:
   293             diffordiffstat(self.ui, self.repo, diffopts, prev, node,
   302             self._differ.showdiff(self.ui, ctx, diffopts, stat=True)
   294                            match=matchfn, stat=True,
       
   295                            hunksfilterfn=hunksfilterfn)
       
   296         if stat and diff:
   303         if stat and diff:
   297             self.ui.write("\n")
   304             self.ui.write("\n")
   298         if diff:
   305         if diff:
   299             diffordiffstat(self.ui, self.repo, diffopts, prev, node,
   306             self._differ.showdiff(self.ui, ctx, diffopts, stat=False)
   300                            match=matchfn, stat=False,
       
   301                            hunksfilterfn=hunksfilterfn)
       
   302         if stat or diff:
   307         if stat or diff:
   303             self.ui.write("\n")
   308             self.ui.write("\n")
   304 
   309 
   305 class jsonchangeset(changesetprinter):
   310 class jsonchangeset(changesetprinter):
   306     '''format changeset information.'''
   311     '''format changeset information.'''
   307 
   312 
   308     def __init__(self, ui, repo, makefilematcher=None, makehunksfilter=None,
   313     def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
   309                  diffopts=None, buffered=False):
   314         changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
   310         changesetprinter.__init__(self, ui, repo, makefilematcher,
       
   311                                   makehunksfilter, diffopts, buffered)
       
   312         self.cache = {}
   315         self.cache = {}
   313         self._first = True
   316         self._first = True
   314 
   317 
   315     def close(self):
   318     def close(self):
   316         if not self._first:
   319         if not self._first:
   381             if copies:
   384             if copies:
   382                 self.ui.write((',\n  "copies": {%s}') %
   385                 self.ui.write((',\n  "copies": {%s}') %
   383                               ", ".join('"%s": "%s"' % (j(k), j(v))
   386                               ", ".join('"%s": "%s"' % (j(k), j(v))
   384                                                         for k, v in copies))
   387                                                         for k, v in copies))
   385 
   388 
   386         matchfn = self._makefilematcher(ctx)
       
   387         stat = self.diffopts.get('stat')
   389         stat = self.diffopts.get('stat')
   388         diff = self.diffopts.get('patch')
   390         diff = self.diffopts.get('patch')
   389         diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
   391         diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
   390         node, prev = ctx.node(), ctx.p1().node()
   392         if stat:
   391         if matchfn and stat:
       
   392             self.ui.pushbuffer()
   393             self.ui.pushbuffer()
   393             diffordiffstat(self.ui, self.repo, diffopts, prev, node,
   394             self._differ.showdiff(self.ui, ctx, diffopts, stat=True)
   394                            match=matchfn, stat=True)
       
   395             self.ui.write((',\n  "diffstat": "%s"')
   395             self.ui.write((',\n  "diffstat": "%s"')
   396                           % j(self.ui.popbuffer()))
   396                           % j(self.ui.popbuffer()))
   397         if matchfn and diff:
   397         if diff:
   398             self.ui.pushbuffer()
   398             self.ui.pushbuffer()
   399             diffordiffstat(self.ui, self.repo, diffopts, prev, node,
   399             self._differ.showdiff(self.ui, ctx, diffopts, stat=False)
   400                            match=matchfn, stat=False)
       
   401             self.ui.write((',\n  "diff": "%s"') % j(self.ui.popbuffer()))
   400             self.ui.write((',\n  "diff": "%s"') % j(self.ui.popbuffer()))
   402 
   401 
   403         self.ui.write("\n }")
   402         self.ui.write("\n }")
   404 
   403 
   405 class changesettemplater(changesetprinter):
   404 class changesettemplater(changesetprinter):
   411     functions that use changesest_templater.
   410     functions that use changesest_templater.
   412     '''
   411     '''
   413 
   412 
   414     # Arguments before "buffered" used to be positional. Consider not
   413     # Arguments before "buffered" used to be positional. Consider not
   415     # adding/removing arguments before "buffered" to not break callers.
   414     # adding/removing arguments before "buffered" to not break callers.
   416     def __init__(self, ui, repo, tmplspec, makefilematcher=None,
   415     def __init__(self, ui, repo, tmplspec, differ=None, diffopts=None,
   417                  makehunksfilter=None, diffopts=None, buffered=False):
   416                  buffered=False):
   418         changesetprinter.__init__(self, ui, repo, makefilematcher,
   417         changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
   419                                   makehunksfilter, diffopts, buffered)
       
   420         tres = formatter.templateresources(ui, repo)
   418         tres = formatter.templateresources(ui, repo)
   421         self.t = formatter.loadtemplater(ui, tmplspec,
   419         self.t = formatter.loadtemplater(ui, tmplspec,
   422                                          defaults=templatekw.keywords,
   420                                          defaults=templatekw.keywords,
   423                                          resources=tres,
   421                                          resources=tres,
   424                                          cache=templatekw.defaulttempl)
   422                                          cache=templatekw.defaulttempl)
   531     """Create a changesettemplater from a literal template 'tmpl'
   529     """Create a changesettemplater from a literal template 'tmpl'
   532     byte-string."""
   530     byte-string."""
   533     spec = templatespec(tmpl, None)
   531     spec = templatespec(tmpl, None)
   534     return changesettemplater(ui, repo, spec, buffered=buffered)
   532     return changesettemplater(ui, repo, spec, buffered=buffered)
   535 
   533 
   536 def changesetdisplayer(ui, repo, opts, makefilematcher=None,
   534 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
   537                        makehunksfilter=None, buffered=False):
       
   538     """show one changeset using template or regular display.
   535     """show one changeset using template or regular display.
   539 
   536 
   540     Display format will be the first non-empty hit of:
   537     Display format will be the first non-empty hit of:
   541     1. option 'template'
   538     1. option 'template'
   542     2. option 'style'
   539     2. option 'style'
   543     3. [ui] setting 'logtemplate'
   540     3. [ui] setting 'logtemplate'
   544     4. [ui] setting 'style'
   541     4. [ui] setting 'style'
   545     If all of these values are either the unset or the empty string,
   542     If all of these values are either the unset or the empty string,
   546     regular display via changesetprinter() is done.
   543     regular display via changesetprinter() is done.
   547     """
   544     """
   548     # options
   545     postargs = (differ, opts, buffered)
   549     if not makefilematcher and (opts.get('patch') or opts.get('stat')):
       
   550         def makefilematcher(ctx):
       
   551             return scmutil.matchall(repo)
       
   552 
       
   553     postargs = (makefilematcher, makehunksfilter, opts, buffered)
       
   554     if opts.get('template') == 'json':
   546     if opts.get('template') == 'json':
   555         return jsonchangeset(ui, repo, *postargs)
   547         return jsonchangeset(ui, repo, *postargs)
   556 
   548 
   557     spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
   549     spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
   558 
   550 
   724         revs = smartset.spanset(repo)
   716         revs = smartset.spanset(repo)
   725         revs.reverse()
   717         revs.reverse()
   726     return revs
   718     return revs
   727 
   719 
   728 def getrevs(repo, pats, opts):
   720 def getrevs(repo, pats, opts):
   729     """Return (revs, filematcher) where revs is a smartset
   721     """Return (revs, differ) where revs is a smartset
   730 
   722 
   731     filematcher is a callable taking a changectx and returning a match
   723     differ is a changesetdiffer with pre-configured file matcher.
   732     objects filtering the files to be detailed when displaying the revision.
       
   733     """
   724     """
   734     follow = opts.get('follow') or opts.get('follow_first')
   725     follow = opts.get('follow') or opts.get('follow_first')
   735     followfirst = opts.get('follow_first')
   726     followfirst = opts.get('follow_first')
   736     limit = getlimit(opts)
   727     limit = getlimit(opts)
   737     revs = _initialrevs(repo, opts)
   728     revs = _initialrevs(repo, opts)
   760     if expr:
   751     if expr:
   761         matcher = revset.match(None, expr)
   752         matcher = revset.match(None, expr)
   762         revs = matcher(repo, revs)
   753         revs = matcher(repo, revs)
   763     if limit is not None:
   754     if limit is not None:
   764         revs = revs.slice(0, limit)
   755         revs = revs.slice(0, limit)
   765     return revs, filematcher
   756 
       
   757     differ = changesetdiffer()
       
   758     differ._makefilematcher = filematcher
       
   759     return revs, differ
   766 
   760 
   767 def _parselinerangeopt(repo, opts):
   761 def _parselinerangeopt(repo, opts):
   768     """Parse --line-range log option and return a list of tuples (filename,
   762     """Parse --line-range log option and return a list of tuples (filename,
   769     (fromline, toline)).
   763     (fromline, toline)).
   770     """
   764     """
   783         linerangebyfname.append(
   777         linerangebyfname.append(
   784             (fname, util.processlinerange(fromline, toline)))
   778             (fname, util.processlinerange(fromline, toline)))
   785     return linerangebyfname
   779     return linerangebyfname
   786 
   780 
   787 def getlinerangerevs(repo, userrevs, opts):
   781 def getlinerangerevs(repo, userrevs, opts):
   788     """Return (revs, filematcher, hunksfilter).
   782     """Return (revs, differ).
   789 
   783 
   790     "revs" are revisions obtained by processing "line-range" log options and
   784     "revs" are revisions obtained by processing "line-range" log options and
   791     walking block ancestors of each specified file/line-range.
   785     walking block ancestors of each specified file/line-range.
   792 
   786 
   793     "filematcher(ctx) -> match" is a factory function returning a match object
   787     "differ" is a changesetdiffer with pre-configured file matcher and hunks
   794     for a given revision for file patterns specified in --line-range option.
   788     filter.
   795 
       
   796     "hunksfilter(ctx) -> filterfn(fctx, hunks)" is a factory function
       
   797     returning a hunks filtering function.
       
   798     """
   789     """
   799     wctx = repo[None]
   790     wctx = repo[None]
   800 
   791 
   801     # Two-levels map of "rev -> file ctx -> [line range]".
   792     # Two-levels map of "rev -> file ctx -> [line range]".
   802     linerangesbyrev = {}
   793     linerangesbyrev = {}
   841         files = list(linerangesbyrev.get(ctx.rev(), []))
   832         files = list(linerangesbyrev.get(ctx.rev(), []))
   842         return scmutil.matchfiles(repo, files)
   833         return scmutil.matchfiles(repo, files)
   843 
   834 
   844     revs = sorted(linerangesbyrev, reverse=True)
   835     revs = sorted(linerangesbyrev, reverse=True)
   845 
   836 
   846     return revs, filematcher, hunksfilter
   837     differ = changesetdiffer()
       
   838     differ._makefilematcher = filematcher
       
   839     differ._makehunksfilter = hunksfilter
       
   840     return revs, differ
   847 
   841 
   848 def _graphnodeformatter(ui, displayer):
   842 def _graphnodeformatter(ui, displayer):
   849     spec = ui.config('ui', 'graphnodetemplate')
   843     spec = ui.config('ui', 'graphnodetemplate')
   850     if not spec:
   844     if not spec:
   851         return templatekw.showgraphnode  # fast path for "{graphnode}"
   845         return templatekw.showgraphnode  # fast path for "{graphnode}"
   908         for type, char, width, coldata in itertools.chain([firstedge], edges):
   902         for type, char, width, coldata in itertools.chain([firstedge], edges):
   909             graphmod.ascii(ui, state, type, char, lines, coldata)
   903             graphmod.ascii(ui, state, type, char, lines, coldata)
   910             lines = []
   904             lines = []
   911     displayer.close()
   905     displayer.close()
   912 
   906 
   913 def graphlog(ui, repo, revs, filematcher, opts):
   907 def graphlog(ui, repo, revs, differ, opts):
   914     # Parameters are identical to log command ones
   908     # Parameters are identical to log command ones
   915     revdag = graphmod.dagwalker(repo, revs)
   909     revdag = graphmod.dagwalker(repo, revs)
   916 
   910 
   917     getrenamed = None
   911     getrenamed = None
   918     if opts.get('copies'):
   912     if opts.get('copies'):
   920         if opts.get('rev'):
   914         if opts.get('rev'):
   921             endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
   915             endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
   922         getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
   916         getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
   923 
   917 
   924     ui.pager('log')
   918     ui.pager('log')
   925     displayer = changesetdisplayer(ui, repo, opts, makefilematcher=filematcher,
   919     displayer = changesetdisplayer(ui, repo, opts, differ, buffered=True)
   926                                    buffered=True)
       
   927     displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
   920     displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
   928 
   921 
   929 def checkunsupportedgraphflags(pats, opts):
   922 def checkunsupportedgraphflags(pats, opts):
   930     for op in ["newest_first"]:
   923     for op in ["newest_first"]:
   931         if op in opts and opts[op]:
   924         if op in opts and opts[op]: