mercurial/logcmdutil.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43100 90b9a7e06c2c
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    42 )
    42 )
    43 
    43 
    44 
    44 
    45 def getlimit(opts):
    45 def getlimit(opts):
    46     """get the log limit according to option -l/--limit"""
    46     """get the log limit according to option -l/--limit"""
    47     limit = opts.get('limit')
    47     limit = opts.get(b'limit')
    48     if limit:
    48     if limit:
    49         try:
    49         try:
    50             limit = int(limit)
    50             limit = int(limit)
    51         except ValueError:
    51         except ValueError:
    52             raise error.Abort(_('limit must be a positive integer'))
    52             raise error.Abort(_(b'limit must be a positive integer'))
    53         if limit <= 0:
    53         if limit <= 0:
    54             raise error.Abort(_('limit must be positive'))
    54             raise error.Abort(_(b'limit must be positive'))
    55     else:
    55     else:
    56         limit = None
    56         limit = None
    57     return limit
    57     return limit
    58 
    58 
    59 
    59 
    66     match,
    66     match,
    67     changes=None,
    67     changes=None,
    68     stat=False,
    68     stat=False,
    69     fp=None,
    69     fp=None,
    70     graphwidth=0,
    70     graphwidth=0,
    71     prefix='',
    71     prefix=b'',
    72     root='',
    72     root=b'',
    73     listsubrepos=False,
    73     listsubrepos=False,
    74     hunksfilterfn=None,
    74     hunksfilterfn=None,
    75 ):
    75 ):
    76     '''show diff or diffstat.'''
    76     '''show diff or diffstat.'''
    77     ctx1 = repo[node1]
    77     ctx1 = repo[node1]
    78     ctx2 = repo[node2]
    78     ctx2 = repo[node2]
    79     if root:
    79     if root:
    80         relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
    80         relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
    81     else:
    81     else:
    82         relroot = ''
    82         relroot = b''
    83     copysourcematch = None
    83     copysourcematch = None
    84 
    84 
    85     def compose(f, g):
    85     def compose(f, g):
    86         return lambda x: f(g(x))
    86         return lambda x: f(g(x))
    87 
    87 
    88     def pathfn(f):
    88     def pathfn(f):
    89         return posixpath.join(prefix, f)
    89         return posixpath.join(prefix, f)
    90 
    90 
    91     if relroot != '':
    91     if relroot != b'':
    92         # XXX relative roots currently don't work if the root is within a
    92         # XXX relative roots currently don't work if the root is within a
    93         # subrepo
    93         # subrepo
    94         uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
    94         uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
    95         uirelroot = uipathfn(pathfn(relroot))
    95         uirelroot = uipathfn(pathfn(relroot))
    96         relroot += '/'
    96         relroot += b'/'
    97         for matchroot in match.files():
    97         for matchroot in match.files():
    98             if not matchroot.startswith(relroot):
    98             if not matchroot.startswith(relroot):
    99                 ui.warn(
    99                 ui.warn(
   100                     _('warning: %s not inside relative root %s\n')
   100                     _(b'warning: %s not inside relative root %s\n')
   101                     % (uipathfn(pathfn(matchroot)), uirelroot)
   101                     % (uipathfn(pathfn(matchroot)), uirelroot)
   102                 )
   102                 )
   103 
   103 
   104         relrootmatch = scmutil.match(ctx2, pats=[relroot], default='path')
   104         relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
   105         match = matchmod.intersectmatchers(match, relrootmatch)
   105         match = matchmod.intersectmatchers(match, relrootmatch)
   106         copysourcematch = relrootmatch
   106         copysourcematch = relrootmatch
   107 
   107 
   108         checkroot = repo.ui.configbool(
   108         checkroot = repo.ui.configbool(
   109             'devel', 'all-warnings'
   109             b'devel', b'all-warnings'
   110         ) or repo.ui.configbool('devel', 'check-relroot')
   110         ) or repo.ui.configbool(b'devel', b'check-relroot')
   111 
   111 
   112         def relrootpathfn(f):
   112         def relrootpathfn(f):
   113             if checkroot and not f.startswith(relroot):
   113             if checkroot and not f.startswith(relroot):
   114                 raise AssertionError(
   114                 raise AssertionError(
   115                     "file %s doesn't start with relroot %s" % (f, relroot)
   115                     b"file %s doesn't start with relroot %s" % (f, relroot)
   116                 )
   116                 )
   117             return f[len(relroot) :]
   117             return f[len(relroot) :]
   118 
   118 
   119         pathfn = compose(relrootpathfn, pathfn)
   119         pathfn = compose(relrootpathfn, pathfn)
   120 
   120 
   212             hunksfilterfn=self._makehunksfilter(ctx),
   212             hunksfilterfn=self._makehunksfilter(ctx),
   213         )
   213         )
   214 
   214 
   215 
   215 
   216 def changesetlabels(ctx):
   216 def changesetlabels(ctx):
   217     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
   217     labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
   218     if ctx.obsolete():
   218     if ctx.obsolete():
   219         labels.append('changeset.obsolete')
   219         labels.append(b'changeset.obsolete')
   220     if ctx.isunstable():
   220     if ctx.isunstable():
   221         labels.append('changeset.unstable')
   221         labels.append(b'changeset.unstable')
   222         for instability in ctx.instabilities():
   222         for instability in ctx.instabilities():
   223             labels.append('instability.%s' % instability)
   223             labels.append(b'instability.%s' % instability)
   224     return ' '.join(labels)
   224     return b' '.join(labels)
   225 
   225 
   226 
   226 
   227 class changesetprinter(object):
   227 class changesetprinter(object):
   228     '''show changeset information when templating not requested.'''
   228     '''show changeset information when templating not requested.'''
   229 
   229 
   231         self.ui = ui
   231         self.ui = ui
   232         self.repo = repo
   232         self.repo = repo
   233         self.buffered = buffered
   233         self.buffered = buffered
   234         self._differ = differ or changesetdiffer()
   234         self._differ = differ or changesetdiffer()
   235         self._diffopts = patch.diffallopts(ui, diffopts)
   235         self._diffopts = patch.diffallopts(ui, diffopts)
   236         self._includestat = diffopts and diffopts.get('stat')
   236         self._includestat = diffopts and diffopts.get(b'stat')
   237         self._includediff = diffopts and diffopts.get('patch')
   237         self._includediff = diffopts and diffopts.get(b'patch')
   238         self.header = {}
   238         self.header = {}
   239         self.hunk = {}
   239         self.hunk = {}
   240         self.lastheader = None
   240         self.lastheader = None
   241         self.footer = None
   241         self.footer = None
   242         self._columns = templatekw.getlogcolumns()
   242         self._columns = templatekw.getlogcolumns()
   267             self._show(ctx, copies, props)
   267             self._show(ctx, copies, props)
   268 
   268 
   269     def _show(self, ctx, copies, props):
   269     def _show(self, ctx, copies, props):
   270         '''show a single changeset or file revision'''
   270         '''show a single changeset or file revision'''
   271         changenode = ctx.node()
   271         changenode = ctx.node()
   272         graphwidth = props.get('graphwidth', 0)
   272         graphwidth = props.get(b'graphwidth', 0)
   273 
   273 
   274         if self.ui.quiet:
   274         if self.ui.quiet:
   275             self.ui.write(
   275             self.ui.write(
   276                 "%s\n" % scmutil.formatchangeid(ctx), label='log.node'
   276                 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
   277             )
   277             )
   278             return
   278             return
   279 
   279 
   280         columns = self._columns
   280         columns = self._columns
   281         self.ui.write(
   281         self.ui.write(
   282             columns['changeset'] % scmutil.formatchangeid(ctx),
   282             columns[b'changeset'] % scmutil.formatchangeid(ctx),
   283             label=changesetlabels(ctx),
   283             label=changesetlabels(ctx),
   284         )
   284         )
   285 
   285 
   286         # branches are shown first before any other names due to backwards
   286         # branches are shown first before any other names due to backwards
   287         # compatibility
   287         # compatibility
   288         branch = ctx.branch()
   288         branch = ctx.branch()
   289         # don't show the default branch name
   289         # don't show the default branch name
   290         if branch != 'default':
   290         if branch != b'default':
   291             self.ui.write(columns['branch'] % branch, label='log.branch')
   291             self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
   292 
   292 
   293         for nsname, ns in self.repo.names.iteritems():
   293         for nsname, ns in self.repo.names.iteritems():
   294             # branches has special logic already handled above, so here we just
   294             # branches has special logic already handled above, so here we just
   295             # skip it
   295             # skip it
   296             if nsname == 'branches':
   296             if nsname == b'branches':
   297                 continue
   297                 continue
   298             # we will use the templatename as the color name since those two
   298             # we will use the templatename as the color name since those two
   299             # should be the same
   299             # should be the same
   300             for name in ns.names(self.repo, changenode):
   300             for name in ns.names(self.repo, changenode):
   301                 self.ui.write(ns.logfmt % name, label='log.%s' % ns.colorname)
   301                 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
   302         if self.ui.debugflag:
   302         if self.ui.debugflag:
   303             self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
   303             self.ui.write(
       
   304                 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
       
   305             )
   304         for pctx in scmutil.meaningfulparents(self.repo, ctx):
   306         for pctx in scmutil.meaningfulparents(self.repo, ctx):
   305             label = 'log.parent changeset.%s' % pctx.phasestr()
   307             label = b'log.parent changeset.%s' % pctx.phasestr()
   306             self.ui.write(
   308             self.ui.write(
   307                 columns['parent'] % scmutil.formatchangeid(pctx), label=label
   309                 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
   308             )
   310             )
   309 
   311 
   310         if self.ui.debugflag:
   312         if self.ui.debugflag:
   311             mnode = ctx.manifestnode()
   313             mnode = ctx.manifestnode()
   312             if mnode is None:
   314             if mnode is None:
   313                 mnode = wdirid
   315                 mnode = wdirid
   314                 mrev = wdirrev
   316                 mrev = wdirrev
   315             else:
   317             else:
   316                 mrev = self.repo.manifestlog.rev(mnode)
   318                 mrev = self.repo.manifestlog.rev(mnode)
   317             self.ui.write(
   319             self.ui.write(
   318                 columns['manifest']
   320                 columns[b'manifest']
   319                 % scmutil.formatrevnode(self.ui, mrev, mnode),
   321                 % scmutil.formatrevnode(self.ui, mrev, mnode),
   320                 label='ui.debug log.manifest',
   322                 label=b'ui.debug log.manifest',
   321             )
   323             )
   322         self.ui.write(columns['user'] % ctx.user(), label='log.user')
   324         self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
   323         self.ui.write(
   325         self.ui.write(
   324             columns['date'] % dateutil.datestr(ctx.date()), label='log.date'
   326             columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
   325         )
   327         )
   326 
   328 
   327         if ctx.isunstable():
   329         if ctx.isunstable():
   328             instabilities = ctx.instabilities()
   330             instabilities = ctx.instabilities()
   329             self.ui.write(
   331             self.ui.write(
   330                 columns['instability'] % ', '.join(instabilities),
   332                 columns[b'instability'] % b', '.join(instabilities),
   331                 label='log.instability',
   333                 label=b'log.instability',
   332             )
   334             )
   333 
   335 
   334         elif ctx.obsolete():
   336         elif ctx.obsolete():
   335             self._showobsfate(ctx)
   337             self._showobsfate(ctx)
   336 
   338 
   337         self._exthook(ctx)
   339         self._exthook(ctx)
   338 
   340 
   339         if self.ui.debugflag:
   341         if self.ui.debugflag:
   340             files = ctx.p1().status(ctx)[:3]
   342             files = ctx.p1().status(ctx)[:3]
   341             for key, value in zip(['files', 'files+', 'files-'], files):
   343             for key, value in zip([b'files', b'files+', b'files-'], files):
   342                 if value:
   344                 if value:
   343                     self.ui.write(
   345                     self.ui.write(
   344                         columns[key] % " ".join(value),
   346                         columns[key] % b" ".join(value),
   345                         label='ui.debug log.files',
   347                         label=b'ui.debug log.files',
   346                     )
   348                     )
   347         elif ctx.files() and self.ui.verbose:
   349         elif ctx.files() and self.ui.verbose:
   348             self.ui.write(
   350             self.ui.write(
   349                 columns['files'] % " ".join(ctx.files()),
   351                 columns[b'files'] % b" ".join(ctx.files()),
   350                 label='ui.note log.files',
   352                 label=b'ui.note log.files',
   351             )
   353             )
   352         if copies and self.ui.verbose:
   354         if copies and self.ui.verbose:
   353             copies = ['%s (%s)' % c for c in copies]
   355             copies = [b'%s (%s)' % c for c in copies]
   354             self.ui.write(
   356             self.ui.write(
   355                 columns['copies'] % ' '.join(copies), label='ui.note log.copies'
   357                 columns[b'copies'] % b' '.join(copies),
       
   358                 label=b'ui.note log.copies',
   356             )
   359             )
   357 
   360 
   358         extra = ctx.extra()
   361         extra = ctx.extra()
   359         if extra and self.ui.debugflag:
   362         if extra and self.ui.debugflag:
   360             for key, value in sorted(extra.items()):
   363             for key, value in sorted(extra.items()):
   361                 self.ui.write(
   364                 self.ui.write(
   362                     columns['extra'] % (key, stringutil.escapestr(value)),
   365                     columns[b'extra'] % (key, stringutil.escapestr(value)),
   363                     label='ui.debug log.extra',
   366                     label=b'ui.debug log.extra',
   364                 )
   367                 )
   365 
   368 
   366         description = ctx.description().strip()
   369         description = ctx.description().strip()
   367         if description:
   370         if description:
   368             if self.ui.verbose:
   371             if self.ui.verbose:
   369                 self.ui.write(
   372                 self.ui.write(
   370                     _("description:\n"), label='ui.note log.description'
   373                     _(b"description:\n"), label=b'ui.note log.description'
   371                 )
   374                 )
   372                 self.ui.write(description, label='ui.note log.description')
   375                 self.ui.write(description, label=b'ui.note log.description')
   373                 self.ui.write("\n\n")
   376                 self.ui.write(b"\n\n")
   374             else:
   377             else:
   375                 self.ui.write(
   378                 self.ui.write(
   376                     columns['summary'] % description.splitlines()[0],
   379                     columns[b'summary'] % description.splitlines()[0],
   377                     label='log.summary',
   380                     label=b'log.summary',
   378                 )
   381                 )
   379         self.ui.write("\n")
   382         self.ui.write(b"\n")
   380 
   383 
   381         self._showpatch(ctx, graphwidth)
   384         self._showpatch(ctx, graphwidth)
   382 
   385 
   383     def _showobsfate(self, ctx):
   386     def _showobsfate(self, ctx):
   384         # TODO: do not depend on templater
   387         # TODO: do not depend on templater
   385         tres = formatter.templateresources(self.repo.ui, self.repo)
   388         tres = formatter.templateresources(self.repo.ui, self.repo)
   386         t = formatter.maketemplater(
   389         t = formatter.maketemplater(
   387             self.repo.ui,
   390             self.repo.ui,
   388             '{join(obsfate, "\n")}',
   391             b'{join(obsfate, "\n")}',
   389             defaults=templatekw.keywords,
   392             defaults=templatekw.keywords,
   390             resources=tres,
   393             resources=tres,
   391         )
   394         )
   392         obsfate = t.renderdefault({'ctx': ctx}).splitlines()
   395         obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
   393 
   396 
   394         if obsfate:
   397         if obsfate:
   395             for obsfateline in obsfate:
   398             for obsfateline in obsfate:
   396                 self.ui.write(
   399                 self.ui.write(
   397                     self._columns['obsolete'] % obsfateline, label='log.obsfate'
   400                     self._columns[b'obsolete'] % obsfateline,
       
   401                     label=b'log.obsfate',
   398                 )
   402                 )
   399 
   403 
   400     def _exthook(self, ctx):
   404     def _exthook(self, ctx):
   401         '''empty method used by extension as a hook point
   405         '''empty method used by extension as a hook point
   402         '''
   406         '''
   405         if self._includestat:
   409         if self._includestat:
   406             self._differ.showdiff(
   410             self._differ.showdiff(
   407                 self.ui, ctx, self._diffopts, graphwidth, stat=True
   411                 self.ui, ctx, self._diffopts, graphwidth, stat=True
   408             )
   412             )
   409         if self._includestat and self._includediff:
   413         if self._includestat and self._includediff:
   410             self.ui.write("\n")
   414             self.ui.write(b"\n")
   411         if self._includediff:
   415         if self._includediff:
   412             self._differ.showdiff(
   416             self._differ.showdiff(
   413                 self.ui, ctx, self._diffopts, graphwidth, stat=False
   417                 self.ui, ctx, self._diffopts, graphwidth, stat=False
   414             )
   418             )
   415         if self._includestat or self._includediff:
   419         if self._includestat or self._includediff:
   416             self.ui.write("\n")
   420             self.ui.write(b"\n")
   417 
   421 
   418 
   422 
   419 class changesetformatter(changesetprinter):
   423 class changesetformatter(changesetprinter):
   420     """Format changeset information by generic formatter"""
   424     """Format changeset information by generic formatter"""
   421 
   425 
   443             branch=ctx.branch(),
   447             branch=ctx.branch(),
   444             phase=ctx.phasestr(),
   448             phase=ctx.phasestr(),
   445             user=ctx.user(),
   449             user=ctx.user(),
   446             date=fm.formatdate(ctx.date()),
   450             date=fm.formatdate(ctx.date()),
   447             desc=ctx.description(),
   451             desc=ctx.description(),
   448             bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'),
   452             bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
   449             tags=fm.formatlist(ctx.tags(), name='tag'),
   453             tags=fm.formatlist(ctx.tags(), name=b'tag'),
   450             parents=fm.formatlist(
   454             parents=fm.formatlist(
   451                 [fm.hexfunc(c.node()) for c in ctx.parents()], name='node'
   455                 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
   452             ),
   456             ),
   453         )
   457         )
   454 
   458 
   455         if self.ui.debugflag:
   459         if self.ui.debugflag:
   456             fm.data(
   460             fm.data(
   458                 extra=fm.formatdict(ctx.extra()),
   462                 extra=fm.formatdict(ctx.extra()),
   459             )
   463             )
   460 
   464 
   461             files = ctx.p1().status(ctx)
   465             files = ctx.p1().status(ctx)
   462             fm.data(
   466             fm.data(
   463                 modified=fm.formatlist(files[0], name='file'),
   467                 modified=fm.formatlist(files[0], name=b'file'),
   464                 added=fm.formatlist(files[1], name='file'),
   468                 added=fm.formatlist(files[1], name=b'file'),
   465                 removed=fm.formatlist(files[2], name='file'),
   469                 removed=fm.formatlist(files[2], name=b'file'),
   466             )
   470             )
   467 
   471 
   468         elif self.ui.verbose:
   472         elif self.ui.verbose:
   469             fm.data(files=fm.formatlist(ctx.files(), name='file'))
   473             fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
   470             if copies:
   474             if copies:
   471                 fm.data(
   475                 fm.data(
   472                     copies=fm.formatdict(copies, key='name', value='source')
   476                     copies=fm.formatdict(copies, key=b'name', value=b'source')
   473                 )
   477                 )
   474 
   478 
   475         if self._includestat:
   479         if self._includestat:
   476             self.ui.pushbuffer()
   480             self.ui.pushbuffer()
   477             self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
   481             self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
   508         )
   512         )
   509         self._counter = itertools.count()
   513         self._counter = itertools.count()
   510 
   514 
   511         self._tref = tmplspec.ref
   515         self._tref = tmplspec.ref
   512         self._parts = {
   516         self._parts = {
   513             'header': '',
   517             b'header': b'',
   514             'footer': '',
   518             b'footer': b'',
   515             tmplspec.ref: tmplspec.ref,
   519             tmplspec.ref: tmplspec.ref,
   516             'docheader': '',
   520             b'docheader': b'',
   517             'docfooter': '',
   521             b'docfooter': b'',
   518             'separator': '',
   522             b'separator': b'',
   519         }
   523         }
   520         if tmplspec.mapfile:
   524         if tmplspec.mapfile:
   521             # find correct templates for current mode, for backward
   525             # find correct templates for current mode, for backward
   522             # compatibility with 'log -v/-q/--debug' using a mapfile
   526             # compatibility with 'log -v/-q/--debug' using a mapfile
   523             tmplmodes = [
   527             tmplmodes = [
   524                 (True, ''),
   528                 (True, b''),
   525                 (self.ui.verbose, '_verbose'),
   529                 (self.ui.verbose, b'_verbose'),
   526                 (self.ui.quiet, '_quiet'),
   530                 (self.ui.quiet, b'_quiet'),
   527                 (self.ui.debugflag, '_debug'),
   531                 (self.ui.debugflag, b'_debug'),
   528             ]
   532             ]
   529             for mode, postfix in tmplmodes:
   533             for mode, postfix in tmplmodes:
   530                 for t in self._parts:
   534                 for t in self._parts:
   531                     cur = t + postfix
   535                     cur = t + postfix
   532                     if mode and cur in self.t:
   536                     if mode and cur in self.t:
   534         else:
   538         else:
   535             partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
   539             partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
   536             m = formatter.templatepartsmap(tmplspec, self.t, partnames)
   540             m = formatter.templatepartsmap(tmplspec, self.t, partnames)
   537             self._parts.update(m)
   541             self._parts.update(m)
   538 
   542 
   539         if self._parts['docheader']:
   543         if self._parts[b'docheader']:
   540             self.ui.write(self.t.render(self._parts['docheader'], {}))
   544             self.ui.write(self.t.render(self._parts[b'docheader'], {}))
   541 
   545 
   542     def close(self):
   546     def close(self):
   543         if self._parts['docfooter']:
   547         if self._parts[b'docfooter']:
   544             if not self.footer:
   548             if not self.footer:
   545                 self.footer = ""
   549                 self.footer = b""
   546             self.footer += self.t.render(self._parts['docfooter'], {})
   550             self.footer += self.t.render(self._parts[b'docfooter'], {})
   547         return super(changesettemplater, self).close()
   551         return super(changesettemplater, self).close()
   548 
   552 
   549     def _show(self, ctx, copies, props):
   553     def _show(self, ctx, copies, props):
   550         '''show a single changeset or file revision'''
   554         '''show a single changeset or file revision'''
   551         props = props.copy()
   555         props = props.copy()
   552         props['ctx'] = ctx
   556         props[b'ctx'] = ctx
   553         props['index'] = index = next(self._counter)
   557         props[b'index'] = index = next(self._counter)
   554         props['revcache'] = {'copies': copies}
   558         props[b'revcache'] = {b'copies': copies}
   555         graphwidth = props.get('graphwidth', 0)
   559         graphwidth = props.get(b'graphwidth', 0)
   556 
   560 
   557         # write separator, which wouldn't work well with the header part below
   561         # write separator, which wouldn't work well with the header part below
   558         # since there's inherently a conflict between header (across items) and
   562         # since there's inherently a conflict between header (across items) and
   559         # separator (per item)
   563         # separator (per item)
   560         if self._parts['separator'] and index > 0:
   564         if self._parts[b'separator'] and index > 0:
   561             self.ui.write(self.t.render(self._parts['separator'], {}))
   565             self.ui.write(self.t.render(self._parts[b'separator'], {}))
   562 
   566 
   563         # write header
   567         # write header
   564         if self._parts['header']:
   568         if self._parts[b'header']:
   565             h = self.t.render(self._parts['header'], props)
   569             h = self.t.render(self._parts[b'header'], props)
   566             if self.buffered:
   570             if self.buffered:
   567                 self.header[ctx.rev()] = h
   571                 self.header[ctx.rev()] = h
   568             else:
   572             else:
   569                 if self.lastheader != h:
   573                 if self.lastheader != h:
   570                     self.lastheader = h
   574                     self.lastheader = h
   573         # write changeset metadata, then patch if requested
   577         # write changeset metadata, then patch if requested
   574         key = self._parts[self._tref]
   578         key = self._parts[self._tref]
   575         self.ui.write(self.t.render(key, props))
   579         self.ui.write(self.t.render(key, props))
   576         self._showpatch(ctx, graphwidth)
   580         self._showpatch(ctx, graphwidth)
   577 
   581 
   578         if self._parts['footer']:
   582         if self._parts[b'footer']:
   579             if not self.footer:
   583             if not self.footer:
   580                 self.footer = self.t.render(self._parts['footer'], props)
   584                 self.footer = self.t.render(self._parts[b'footer'], props)
   581 
   585 
   582 
   586 
   583 def templatespec(tmpl, mapfile):
   587 def templatespec(tmpl, mapfile):
   584     if pycompat.ispy3:
   588     if pycompat.ispy3:
   585         assert not isinstance(tmpl, str), 'tmpl must not be a str'
   589         assert not isinstance(tmpl, str), b'tmpl must not be a str'
   586     if mapfile:
   590     if mapfile:
   587         return formatter.templatespec('changeset', tmpl, mapfile)
   591         return formatter.templatespec(b'changeset', tmpl, mapfile)
   588     else:
   592     else:
   589         return formatter.templatespec('', tmpl, None)
   593         return formatter.templatespec(b'', tmpl, None)
   590 
   594 
   591 
   595 
   592 def _lookuptemplate(ui, tmpl, style):
   596 def _lookuptemplate(ui, tmpl, style):
   593     """Find the template matching the given template spec or style
   597     """Find the template matching the given template spec or style
   594 
   598 
   595     See formatter.lookuptemplate() for details.
   599     See formatter.lookuptemplate() for details.
   596     """
   600     """
   597 
   601 
   598     # ui settings
   602     # ui settings
   599     if not tmpl and not style:  # template are stronger than style
   603     if not tmpl and not style:  # template are stronger than style
   600         tmpl = ui.config('ui', 'logtemplate')
   604         tmpl = ui.config(b'ui', b'logtemplate')
   601         if tmpl:
   605         if tmpl:
   602             return templatespec(templater.unquotestring(tmpl), None)
   606             return templatespec(templater.unquotestring(tmpl), None)
   603         else:
   607         else:
   604             style = util.expandpath(ui.config('ui', 'style'))
   608             style = util.expandpath(ui.config(b'ui', b'style'))
   605 
   609 
   606     if not tmpl and style:
   610     if not tmpl and style:
   607         mapfile = style
   611         mapfile = style
   608         if not os.path.split(mapfile)[0]:
   612         if not os.path.split(mapfile)[0]:
   609             mapname = templater.templatepath(
   613             mapname = templater.templatepath(
   610                 'map-cmdline.' + mapfile
   614                 b'map-cmdline.' + mapfile
   611             ) or templater.templatepath(mapfile)
   615             ) or templater.templatepath(mapfile)
   612             if mapname:
   616             if mapname:
   613                 mapfile = mapname
   617                 mapfile = mapname
   614         return templatespec(None, mapfile)
   618         return templatespec(None, mapfile)
   615 
   619 
   616     if not tmpl:
   620     if not tmpl:
   617         return templatespec(None, None)
   621         return templatespec(None, None)
   618 
   622 
   619     return formatter.lookuptemplate(ui, 'changeset', tmpl)
   623     return formatter.lookuptemplate(ui, b'changeset', tmpl)
   620 
   624 
   621 
   625 
   622 def maketemplater(ui, repo, tmpl, buffered=False):
   626 def maketemplater(ui, repo, tmpl, buffered=False):
   623     """Create a changesettemplater from a literal template 'tmpl'
   627     """Create a changesettemplater from a literal template 'tmpl'
   624     byte-string."""
   628     byte-string."""
   636     4. [ui] setting 'style'
   640     4. [ui] setting 'style'
   637     If all of these values are either the unset or the empty string,
   641     If all of these values are either the unset or the empty string,
   638     regular display via changesetprinter() is done.
   642     regular display via changesetprinter() is done.
   639     """
   643     """
   640     postargs = (differ, opts, buffered)
   644     postargs = (differ, opts, buffered)
   641     if opts.get('template') in {'cbor', 'json'}:
   645     if opts.get(b'template') in {b'cbor', b'json'}:
   642         fm = ui.formatter('log', opts)
   646         fm = ui.formatter(b'log', opts)
   643         return changesetformatter(ui, repo, fm, *postargs)
   647         return changesetformatter(ui, repo, fm, *postargs)
   644 
   648 
   645     spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
   649     spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
   646 
   650 
   647     if not spec.ref and not spec.tmpl and not spec.mapfile:
   651     if not spec.ref and not spec.tmpl and not spec.mapfile:
   648         return changesetprinter(ui, repo, *postargs)
   652         return changesetprinter(ui, repo, *postargs)
   649 
   653 
   650     return changesettemplater(ui, repo, spec, *postargs)
   654     return changesettemplater(ui, repo, spec, *postargs)
   664     # _matchfiles() revset but walkchangerevs() builds its matcher with
   668     # _matchfiles() revset but walkchangerevs() builds its matcher with
   665     # scmutil.match(). The difference is input pats are globbed on
   669     # scmutil.match(). The difference is input pats are globbed on
   666     # platforms without shell expansion (windows).
   670     # platforms without shell expansion (windows).
   667     wctx = repo[None]
   671     wctx = repo[None]
   668     match, pats = scmutil.matchandpats(wctx, pats, opts)
   672     match, pats = scmutil.matchandpats(wctx, pats, opts)
   669     slowpath = match.anypats() or (not match.always() and opts.get('removed'))
   673     slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
   670     if not slowpath:
   674     if not slowpath:
   671         follow = opts.get('follow') or opts.get('follow_first')
   675         follow = opts.get(b'follow') or opts.get(b'follow_first')
   672         startctxs = []
   676         startctxs = []
   673         if follow and opts.get('rev'):
   677         if follow and opts.get(b'rev'):
   674             startctxs = [repo[r] for r in revs]
   678             startctxs = [repo[r] for r in revs]
   675         for f in match.files():
   679         for f in match.files():
   676             if follow and startctxs:
   680             if follow and startctxs:
   677                 # No idea if the path was a directory at that revision, so
   681                 # No idea if the path was a directory at that revision, so
   678                 # take the slow path.
   682                 # take the slow path.
   685                 if os.path.exists(repo.wjoin(f)):
   689                 if os.path.exists(repo.wjoin(f)):
   686                     slowpath = True
   690                     slowpath = True
   687                     continue
   691                     continue
   688                 else:
   692                 else:
   689                     raise error.Abort(
   693                     raise error.Abort(
   690                         _('cannot follow file not in parent ' 'revision: "%s"')
   694                         _(
       
   695                             b'cannot follow file not in parent '
       
   696                             b'revision: "%s"'
       
   697                         )
   691                         % f
   698                         % f
   692                     )
   699                     )
   693             filelog = repo.file(f)
   700             filelog = repo.file(f)
   694             if not filelog:
   701             if not filelog:
   695                 # A zero count may be a directory or deleted file, so
   702                 # A zero count may be a directory or deleted file, so
   696                 # try to find matching entries on the slow path.
   703                 # try to find matching entries on the slow path.
   697                 if follow:
   704                 if follow:
   698                     raise error.Abort(
   705                     raise error.Abort(
   699                         _('cannot follow nonexistent file: "%s"') % f
   706                         _(b'cannot follow nonexistent file: "%s"') % f
   700                     )
   707                     )
   701                 slowpath = True
   708                 slowpath = True
   702 
   709 
   703         # We decided to fall back to the slowpath because at least one
   710         # We decided to fall back to the slowpath because at least one
   704         # of the paths was not a file. Check to see if at least one of them
   711         # of the paths was not a file. Check to see if at least one of them
   705         # existed in history - in that case, we'll continue down the
   712         # existed in history - in that case, we'll continue down the
   706         # slowpath; otherwise, we can turn off the slowpath
   713         # slowpath; otherwise, we can turn off the slowpath
   707         if slowpath:
   714         if slowpath:
   708             for path in match.files():
   715             for path in match.files():
   709                 if path == '.' or path in repo.store:
   716                 if path == b'.' or path in repo.store:
   710                     break
   717                     break
   711             else:
   718             else:
   712                 slowpath = False
   719                 slowpath = False
   713 
   720 
   714     return match, pats, slowpath
   721     return match, pats, slowpath
   742     '''hook for extensions to override the filematcher for non-follow cases'''
   749     '''hook for extensions to override the filematcher for non-follow cases'''
   743     return None
   750     return None
   744 
   751 
   745 
   752 
   746 _opt2logrevset = {
   753 _opt2logrevset = {
   747     'no_merges': ('not merge()', None),
   754     b'no_merges': (b'not merge()', None),
   748     'only_merges': ('merge()', None),
   755     b'only_merges': (b'merge()', None),
   749     '_matchfiles': (None, '_matchfiles(%ps)'),
   756     b'_matchfiles': (None, b'_matchfiles(%ps)'),
   750     'date': ('date(%s)', None),
   757     b'date': (b'date(%s)', None),
   751     'branch': ('branch(%s)', '%lr'),
   758     b'branch': (b'branch(%s)', b'%lr'),
   752     '_patslog': ('filelog(%s)', '%lr'),
   759     b'_patslog': (b'filelog(%s)', b'%lr'),
   753     'keyword': ('keyword(%s)', '%lr'),
   760     b'keyword': (b'keyword(%s)', b'%lr'),
   754     'prune': ('ancestors(%s)', 'not %lr'),
   761     b'prune': (b'ancestors(%s)', b'not %lr'),
   755     'user': ('user(%s)', '%lr'),
   762     b'user': (b'user(%s)', b'%lr'),
   756 }
   763 }
   757 
   764 
   758 
   765 
   759 def _makerevset(repo, match, pats, slowpath, opts):
   766 def _makerevset(repo, match, pats, slowpath, opts):
   760     """Return a revset string built from log options and file patterns"""
   767     """Return a revset string built from log options and file patterns"""
   761     opts = dict(opts)
   768     opts = dict(opts)
   762     # follow or not follow?
   769     # follow or not follow?
   763     follow = opts.get('follow') or opts.get('follow_first')
   770     follow = opts.get(b'follow') or opts.get(b'follow_first')
   764 
   771 
   765     # branch and only_branch are really aliases and must be handled at
   772     # branch and only_branch are really aliases and must be handled at
   766     # the same time
   773     # the same time
   767     opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
   774     opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
   768     opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
   775     opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
   769 
   776 
   770     if slowpath:
   777     if slowpath:
   771         # See walkchangerevs() slow path.
   778         # See walkchangerevs() slow path.
   772         #
   779         #
   773         # pats/include/exclude cannot be represented as separate
   780         # pats/include/exclude cannot be represented as separate
   774         # revset expressions as their filtering logic applies at file
   781         # revset expressions as their filtering logic applies at file
   775         # level. For instance "-I a -X b" matches a revision touching
   782         # level. For instance "-I a -X b" matches a revision touching
   776         # "a" and "b" while "file(a) and not file(b)" does
   783         # "a" and "b" while "file(a) and not file(b)" does
   777         # not. Besides, filesets are evaluated against the working
   784         # not. Besides, filesets are evaluated against the working
   778         # directory.
   785         # directory.
   779         matchargs = ['r:', 'd:relpath']
   786         matchargs = [b'r:', b'd:relpath']
   780         for p in pats:
   787         for p in pats:
   781             matchargs.append('p:' + p)
   788             matchargs.append(b'p:' + p)
   782         for p in opts.get('include', []):
   789         for p in opts.get(b'include', []):
   783             matchargs.append('i:' + p)
   790             matchargs.append(b'i:' + p)
   784         for p in opts.get('exclude', []):
   791         for p in opts.get(b'exclude', []):
   785             matchargs.append('x:' + p)
   792             matchargs.append(b'x:' + p)
   786         opts['_matchfiles'] = matchargs
   793         opts[b'_matchfiles'] = matchargs
   787     elif not follow:
   794     elif not follow:
   788         opts['_patslog'] = list(pats)
   795         opts[b'_patslog'] = list(pats)
   789 
   796 
   790     expr = []
   797     expr = []
   791     for op, val in sorted(opts.iteritems()):
   798     for op, val in sorted(opts.iteritems()):
   792         if not val:
   799         if not val:
   793             continue
   800             continue
   794         if op not in _opt2logrevset:
   801         if op not in _opt2logrevset:
   795             continue
   802             continue
   796         revop, listop = _opt2logrevset[op]
   803         revop, listop = _opt2logrevset[op]
   797         if revop and '%' not in revop:
   804         if revop and b'%' not in revop:
   798             expr.append(revop)
   805             expr.append(revop)
   799         elif not listop:
   806         elif not listop:
   800             expr.append(revsetlang.formatspec(revop, val))
   807             expr.append(revsetlang.formatspec(revop, val))
   801         else:
   808         else:
   802             if revop:
   809             if revop:
   803                 val = [revsetlang.formatspec(revop, v) for v in val]
   810                 val = [revsetlang.formatspec(revop, v) for v in val]
   804             expr.append(revsetlang.formatspec(listop, val))
   811             expr.append(revsetlang.formatspec(listop, val))
   805 
   812 
   806     if expr:
   813     if expr:
   807         expr = '(' + ' and '.join(expr) + ')'
   814         expr = b'(' + b' and '.join(expr) + b')'
   808     else:
   815     else:
   809         expr = None
   816         expr = None
   810     return expr
   817     return expr
   811 
   818 
   812 
   819 
   813 def _initialrevs(repo, opts):
   820 def _initialrevs(repo, opts):
   814     """Return the initial set of revisions to be filtered or followed"""
   821     """Return the initial set of revisions to be filtered or followed"""
   815     follow = opts.get('follow') or opts.get('follow_first')
   822     follow = opts.get(b'follow') or opts.get(b'follow_first')
   816     if opts.get('rev'):
   823     if opts.get(b'rev'):
   817         revs = scmutil.revrange(repo, opts['rev'])
   824         revs = scmutil.revrange(repo, opts[b'rev'])
   818     elif follow and repo.dirstate.p1() == nullid:
   825     elif follow and repo.dirstate.p1() == nullid:
   819         revs = smartset.baseset()
   826         revs = smartset.baseset()
   820     elif follow:
   827     elif follow:
   821         revs = repo.revs('.')
   828         revs = repo.revs(b'.')
   822     else:
   829     else:
   823         revs = smartset.spanset(repo)
   830         revs = smartset.spanset(repo)
   824         revs.reverse()
   831         revs.reverse()
   825     return revs
   832     return revs
   826 
   833 
   828 def getrevs(repo, pats, opts):
   835 def getrevs(repo, pats, opts):
   829     """Return (revs, differ) where revs is a smartset
   836     """Return (revs, differ) where revs is a smartset
   830 
   837 
   831     differ is a changesetdiffer with pre-configured file matcher.
   838     differ is a changesetdiffer with pre-configured file matcher.
   832     """
   839     """
   833     follow = opts.get('follow') or opts.get('follow_first')
   840     follow = opts.get(b'follow') or opts.get(b'follow_first')
   834     followfirst = opts.get('follow_first')
   841     followfirst = opts.get(b'follow_first')
   835     limit = getlimit(opts)
   842     limit = getlimit(opts)
   836     revs = _initialrevs(repo, opts)
   843     revs = _initialrevs(repo, opts)
   837     if not revs:
   844     if not revs:
   838         return smartset.baseset(), None
   845         return smartset.baseset(), None
   839     match, pats, slowpath = _makematcher(repo, revs, pats, opts)
   846     match, pats, slowpath = _makematcher(repo, revs, pats, opts)
   850 
   857 
   851         def filematcher(ctx):
   858         def filematcher(ctx):
   852             return match
   859             return match
   853 
   860 
   854     expr = _makerevset(repo, match, pats, slowpath, opts)
   861     expr = _makerevset(repo, match, pats, slowpath, opts)
   855     if opts.get('graph'):
   862     if opts.get(b'graph'):
   856         # User-specified revs might be unsorted, but don't sort before
   863         # User-specified revs might be unsorted, but don't sort before
   857         # _makerevset because it might depend on the order of revs
   864         # _makerevset because it might depend on the order of revs
   858         if repo.ui.configbool('experimental', 'log.topo'):
   865         if repo.ui.configbool(b'experimental', b'log.topo'):
   859             if not revs.istopo():
   866             if not revs.istopo():
   860                 revs = dagop.toposort(revs, repo.changelog.parentrevs)
   867                 revs = dagop.toposort(revs, repo.changelog.parentrevs)
   861                 # TODO: try to iterate the set lazily
   868                 # TODO: try to iterate the set lazily
   862                 revs = revset.baseset(list(revs), istopo=True)
   869                 revs = revset.baseset(list(revs), istopo=True)
   863         elif not (revs.isdescending() or revs.istopo()):
   870         elif not (revs.isdescending() or revs.istopo()):
   876 def _parselinerangeopt(repo, opts):
   883 def _parselinerangeopt(repo, opts):
   877     """Parse --line-range log option and return a list of tuples (filename,
   884     """Parse --line-range log option and return a list of tuples (filename,
   878     (fromline, toline)).
   885     (fromline, toline)).
   879     """
   886     """
   880     linerangebyfname = []
   887     linerangebyfname = []
   881     for pat in opts.get('line_range', []):
   888     for pat in opts.get(b'line_range', []):
   882         try:
   889         try:
   883             pat, linerange = pat.rsplit(',', 1)
   890             pat, linerange = pat.rsplit(b',', 1)
   884         except ValueError:
   891         except ValueError:
   885             raise error.Abort(_('malformatted line-range pattern %s') % pat)
   892             raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
   886         try:
   893         try:
   887             fromline, toline = map(int, linerange.split(':'))
   894             fromline, toline = map(int, linerange.split(b':'))
   888         except ValueError:
   895         except ValueError:
   889             raise error.Abort(_("invalid line range for %s") % pat)
   896             raise error.Abort(_(b"invalid line range for %s") % pat)
   890         msg = _("line range pattern '%s' must match exactly one file") % pat
   897         msg = _(b"line range pattern '%s' must match exactly one file") % pat
   891         fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
   898         fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
   892         linerangebyfname.append(
   899         linerangebyfname.append(
   893             (fname, util.processlinerange(fromline, toline))
   900             (fname, util.processlinerange(fromline, toline))
   894         )
   901         )
   895     return linerangebyfname
   902     return linerangebyfname
   909     # Two-levels map of "rev -> file ctx -> [line range]".
   916     # Two-levels map of "rev -> file ctx -> [line range]".
   910     linerangesbyrev = {}
   917     linerangesbyrev = {}
   911     for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
   918     for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
   912         if fname not in wctx:
   919         if fname not in wctx:
   913             raise error.Abort(
   920             raise error.Abort(
   914                 _('cannot follow file not in parent ' 'revision: "%s"') % fname
   921                 _(b'cannot follow file not in parent ' b'revision: "%s"')
       
   922                 % fname
   915             )
   923             )
   916         fctx = wctx.filectx(fname)
   924         fctx = wctx.filectx(fname)
   917         for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
   925         for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
   918             rev = fctx.introrev()
   926             rev = fctx.introrev()
   919             if rev not in userrevs:
   927             if rev not in userrevs:
   956     differ._makehunksfilter = hunksfilter
   964     differ._makehunksfilter = hunksfilter
   957     return revs, differ
   965     return revs, differ
   958 
   966 
   959 
   967 
   960 def _graphnodeformatter(ui, displayer):
   968 def _graphnodeformatter(ui, displayer):
   961     spec = ui.config('ui', 'graphnodetemplate')
   969     spec = ui.config(b'ui', b'graphnodetemplate')
   962     if not spec:
   970     if not spec:
   963         return templatekw.getgraphnode  # fast path for "{graphnode}"
   971         return templatekw.getgraphnode  # fast path for "{graphnode}"
   964 
   972 
   965     spec = templater.unquotestring(spec)
   973     spec = templater.unquotestring(spec)
   966     if isinstance(displayer, changesettemplater):
   974     if isinstance(displayer, changesettemplater):
   971     templ = formatter.maketemplater(
   979     templ = formatter.maketemplater(
   972         ui, spec, defaults=templatekw.keywords, resources=tres
   980         ui, spec, defaults=templatekw.keywords, resources=tres
   973     )
   981     )
   974 
   982 
   975     def formatnode(repo, ctx):
   983     def formatnode(repo, ctx):
   976         props = {'ctx': ctx, 'repo': repo}
   984         props = {b'ctx': ctx, b'repo': repo}
   977         return templ.renderdefault(props)
   985         return templ.renderdefault(props)
   978 
   986 
   979     return formatnode
   987     return formatnode
   980 
   988 
   981 
   989 
   982 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
   990 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
   983     props = props or {}
   991     props = props or {}
   984     formatnode = _graphnodeformatter(ui, displayer)
   992     formatnode = _graphnodeformatter(ui, displayer)
   985     state = graphmod.asciistate()
   993     state = graphmod.asciistate()
   986     styles = state['styles']
   994     styles = state[b'styles']
   987 
   995 
   988     # only set graph styling if HGPLAIN is not set.
   996     # only set graph styling if HGPLAIN is not set.
   989     if ui.plain('graph'):
   997     if ui.plain(b'graph'):
   990         # set all edge styles to |, the default pre-3.8 behaviour
   998         # set all edge styles to |, the default pre-3.8 behaviour
   991         styles.update(dict.fromkeys(styles, '|'))
   999         styles.update(dict.fromkeys(styles, b'|'))
   992     else:
  1000     else:
   993         edgetypes = {
  1001         edgetypes = {
   994             'parent': graphmod.PARENT,
  1002             b'parent': graphmod.PARENT,
   995             'grandparent': graphmod.GRANDPARENT,
  1003             b'grandparent': graphmod.GRANDPARENT,
   996             'missing': graphmod.MISSINGPARENT,
  1004             b'missing': graphmod.MISSINGPARENT,
   997         }
  1005         }
   998         for name, key in edgetypes.items():
  1006         for name, key in edgetypes.items():
   999             # experimental config: experimental.graphstyle.*
  1007             # experimental config: experimental.graphstyle.*
  1000             styles[key] = ui.config(
  1008             styles[key] = ui.config(
  1001                 'experimental', 'graphstyle.%s' % name, styles[key]
  1009                 b'experimental', b'graphstyle.%s' % name, styles[key]
  1002             )
  1010             )
  1003             if not styles[key]:
  1011             if not styles[key]:
  1004                 styles[key] = None
  1012                 styles[key] = None
  1005 
  1013 
  1006         # experimental config: experimental.graphshorten
  1014         # experimental config: experimental.graphshorten
  1007         state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
  1015         state[b'graphshorten'] = ui.configbool(b'experimental', b'graphshorten')
  1008 
  1016 
  1009     for rev, type, ctx, parents in dag:
  1017     for rev, type, ctx, parents in dag:
  1010         char = formatnode(repo, ctx)
  1018         char = formatnode(repo, ctx)
  1011         copies = getcopies(ctx) if getcopies else None
  1019         copies = getcopies(ctx) if getcopies else None
  1012         edges = edgefn(type, char, state, rev, parents)
  1020         edges = edgefn(type, char, state, rev, parents)
  1013         firstedge = next(edges)
  1021         firstedge = next(edges)
  1014         width = firstedge[2]
  1022         width = firstedge[2]
  1015         displayer.show(
  1023         displayer.show(
  1016             ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
  1024             ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
  1017         )
  1025         )
  1018         lines = displayer.hunk.pop(rev).split('\n')
  1026         lines = displayer.hunk.pop(rev).split(b'\n')
  1019         if not lines[-1]:
  1027         if not lines[-1]:
  1020             del lines[-1]
  1028             del lines[-1]
  1021         displayer.flush(ctx)
  1029         displayer.flush(ctx)
  1022         for type, char, width, coldata in itertools.chain([firstedge], edges):
  1030         for type, char, width, coldata in itertools.chain([firstedge], edges):
  1023             graphmod.ascii(ui, state, type, char, lines, coldata)
  1031             graphmod.ascii(ui, state, type, char, lines, coldata)
  1038         displayer.flush(ctx)
  1046         displayer.flush(ctx)
  1039     displayer.close()
  1047     displayer.close()
  1040 
  1048 
  1041 
  1049 
  1042 def checkunsupportedgraphflags(pats, opts):
  1050 def checkunsupportedgraphflags(pats, opts):
  1043     for op in ["newest_first"]:
  1051     for op in [b"newest_first"]:
  1044         if op in opts and opts[op]:
  1052         if op in opts and opts[op]:
  1045             raise error.Abort(
  1053             raise error.Abort(
  1046                 _("-G/--graph option is incompatible with --%s")
  1054                 _(b"-G/--graph option is incompatible with --%s")
  1047                 % op.replace("_", "-")
  1055                 % op.replace(b"_", b"-")
  1048             )
  1056             )
  1049 
  1057 
  1050 
  1058 
  1051 def graphrevs(repo, nodes, opts):
  1059 def graphrevs(repo, nodes, opts):
  1052     limit = getlimit(opts)
  1060     limit = getlimit(opts)