hgext/fastannotate/commands.py
changeset 43076 2372284d9457
parent 42057 566daffc607d
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    29 )
    29 )
    30 
    30 
    31 cmdtable = {}
    31 cmdtable = {}
    32 command = registrar.command(cmdtable)
    32 command = registrar.command(cmdtable)
    33 
    33 
       
    34 
    34 def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
    35 def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
    35     """generate paths matching given patterns"""
    36     """generate paths matching given patterns"""
    36     perfhack = repo.ui.configbool('fastannotate', 'perfhack')
    37     perfhack = repo.ui.configbool('fastannotate', 'perfhack')
    37 
    38 
    38     # disable perfhack if:
    39     # disable perfhack if:
    43         # cwd related to reporoot
    44         # cwd related to reporoot
    44         reporoot = os.path.dirname(repo.path)
    45         reporoot = os.path.dirname(repo.path)
    45         reldir = os.path.relpath(encoding.getcwd(), reporoot)
    46         reldir = os.path.relpath(encoding.getcwd(), reporoot)
    46         if reldir == '.':
    47         if reldir == '.':
    47             reldir = ''
    48             reldir = ''
    48         if any(opts.get(o[1]) for o in commands.walkopts): # a)
    49         if any(opts.get(o[1]) for o in commands.walkopts):  # a)
    49             perfhack = False
    50             perfhack = False
    50         else: # b)
    51         else:  # b)
    51             relpats = [os.path.relpath(p, reporoot) if os.path.isabs(p) else p
    52             relpats = [
    52                        for p in pats]
    53                 os.path.relpath(p, reporoot) if os.path.isabs(p) else p
       
    54                 for p in pats
       
    55             ]
    53             # disable perfhack on '..' since it allows escaping from the repo
    56             # disable perfhack on '..' since it allows escaping from the repo
    54             if any(('..' in f or
    57             if any(
    55                     not os.path.isfile(
    58                 (
    56                         facontext.pathhelper(repo, f, aopts).linelogpath))
    59                     '..' in f
    57                    for f in relpats):
    60                     or not os.path.isfile(
       
    61                         facontext.pathhelper(repo, f, aopts).linelogpath
       
    62                     )
       
    63                 )
       
    64                 for f in relpats
       
    65             ):
    58                 perfhack = False
    66                 perfhack = False
    59 
    67 
    60     # perfhack: emit paths directory without checking with manifest
    68     # perfhack: emit paths directory without checking with manifest
    61     # this can be incorrect if the rev dos not have file.
    69     # this can be incorrect if the rev dos not have file.
    62     if perfhack:
    70     if perfhack:
    63         for p in relpats:
    71         for p in relpats:
    64             yield os.path.join(reldir, p)
    72             yield os.path.join(reldir, p)
    65     else:
    73     else:
       
    74 
    66         def bad(x, y):
    75         def bad(x, y):
    67             raise error.Abort("%s: %s" % (x, y))
    76             raise error.Abort("%s: %s" % (x, y))
       
    77 
    68         ctx = scmutil.revsingle(repo, rev)
    78         ctx = scmutil.revsingle(repo, rev)
    69         m = scmutil.match(ctx, pats, opts, badfn=bad)
    79         m = scmutil.match(ctx, pats, opts, badfn=bad)
    70         for p in ctx.walk(m):
    80         for p in ctx.walk(m):
    71             yield p
    81             yield p
       
    82 
    72 
    83 
    73 fastannotatecommandargs = {
    84 fastannotatecommandargs = {
    74     r'options': [
    85     r'options': [
    75         ('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
    86         ('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
    76         ('u', 'user', None, _('list the author (long with -v)')),
    87         ('u', 'user', None, _('list the author (long with -v)')),
    77         ('f', 'file', None, _('list the filename')),
    88         ('f', 'file', None, _('list the filename')),
    78         ('d', 'date', None, _('list the date (short with -q)')),
    89         ('d', 'date', None, _('list the date (short with -q)')),
    79         ('n', 'number', None, _('list the revision number (default)')),
    90         ('n', 'number', None, _('list the revision number (default)')),
    80         ('c', 'changeset', None, _('list the changeset')),
    91         ('c', 'changeset', None, _('list the changeset')),
    81         ('l', 'line-number', None, _('show line number at the first '
    92         (
    82                                      'appearance')),
    93             'l',
       
    94             'line-number',
       
    95             None,
       
    96             _('show line number at the first ' 'appearance'),
       
    97         ),
    83         ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')),
    98         ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')),
    84         ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')),
    99         ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')),
    85         ('', 'no-follow', None, _("don't follow copies and renames")),
   100         ('', 'no-follow', None, _("don't follow copies and renames")),
    86         ('', 'linear', None, _('enforce linear history, ignore second parent '
   101         (
    87                                'of merges (EXPERIMENTAL)')),
   102             '',
       
   103             'linear',
       
   104             None,
       
   105             _(
       
   106                 'enforce linear history, ignore second parent '
       
   107                 'of merges (EXPERIMENTAL)'
       
   108             ),
       
   109         ),
    88         ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
   110         ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
    89         ('', 'rebuild', None, _('rebuild cache even if it exists '
   111         (
    90                                 '(EXPERIMENTAL)')),
   112             '',
    91     ] + commands.diffwsopts + commands.walkopts + commands.formatteropts,
   113             'rebuild',
       
   114             None,
       
   115             _('rebuild cache even if it exists ' '(EXPERIMENTAL)'),
       
   116         ),
       
   117     ]
       
   118     + commands.diffwsopts
       
   119     + commands.walkopts
       
   120     + commands.formatteropts,
    92     r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
   121     r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
    93     r'inferrepo': True,
   122     r'inferrepo': True,
    94 }
   123 }
       
   124 
    95 
   125 
    96 def fastannotate(ui, repo, *pats, **opts):
   126 def fastannotate(ui, repo, *pats, **opts):
    97     """show changeset information by line for each file
   127     """show changeset information by line for each file
    98 
   128 
    99     List changes in files, showing the revision id responsible for each line.
   129     List changes in files, showing the revision id responsible for each line.
   134     opts = pycompat.byteskwargs(opts)
   164     opts = pycompat.byteskwargs(opts)
   135 
   165 
   136     rev = opts.get('rev', '.')
   166     rev = opts.get('rev', '.')
   137     rebuild = opts.get('rebuild', False)
   167     rebuild = opts.get('rebuild', False)
   138 
   168 
   139     diffopts = patch.difffeatureopts(ui, opts, section='annotate',
   169     diffopts = patch.difffeatureopts(
   140                                      whitespace=True)
   170         ui, opts, section='annotate', whitespace=True
       
   171     )
   141     aopts = facontext.annotateopts(
   172     aopts = facontext.annotateopts(
   142         diffopts=diffopts,
   173         diffopts=diffopts,
   143         followmerge=not opts.get('linear', False),
   174         followmerge=not opts.get('linear', False),
   144         followrename=not opts.get('no_follow', False))
   175         followrename=not opts.get('no_follow', False),
   145 
   176     )
   146     if not any(opts.get(s)
   177 
   147                for s in ['user', 'date', 'file', 'number', 'changeset']):
   178     if not any(
       
   179         opts.get(s) for s in ['user', 'date', 'file', 'number', 'changeset']
       
   180     ):
   148         # default 'number' for compatibility. but fastannotate is more
   181         # default 'number' for compatibility. but fastannotate is more
   149         # efficient with "changeset", "line-number" and "no-content".
   182         # efficient with "changeset", "line-number" and "no-content".
   150         for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
   183         for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
   151             opts[name] = True
   184             opts[name] = True
   152 
   185 
   173     for path in paths:
   206     for path in paths:
   174         result = lines = existinglines = None
   207         result = lines = existinglines = None
   175         while True:
   208         while True:
   176             try:
   209             try:
   177                 with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
   210                 with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
   178                     result = a.annotate(rev, master=master, showpath=showpath,
   211                     result = a.annotate(
   179                                         showlines=(showlines and
   212                         rev,
   180                                                    not showdeleted))
   213                         master=master,
       
   214                         showpath=showpath,
       
   215                         showlines=(showlines and not showdeleted),
       
   216                     )
   181                     if showdeleted:
   217                     if showdeleted:
   182                         existinglines = set((l[0], l[1]) for l in result)
   218                         existinglines = set((l[0], l[1]) for l in result)
   183                         result = a.annotatealllines(
   219                         result = a.annotatealllines(
   184                             rev, showpath=showpath, showlines=showlines)
   220                             rev, showpath=showpath, showlines=showlines
       
   221                         )
   185                 break
   222                 break
   186             except (faerror.CannotReuseError, faerror.CorruptedFileError):
   223             except (faerror.CannotReuseError, faerror.CorruptedFileError):
   187                 # happens if master moves backwards, or the file was deleted
   224                 # happens if master moves backwards, or the file was deleted
   188                 # and readded, or renamed to an existing name, or corrupted.
   225                 # and readded, or renamed to an existing name, or corrupted.
   189                 if rebuild: # give up since we have tried rebuild already
   226                 if rebuild:  # give up since we have tried rebuild already
   190                     raise
   227                     raise
   191                 else: # try a second time rebuilding the cache (slow)
   228                 else:  # try a second time rebuilding the cache (slow)
   192                     rebuild = True
   229                     rebuild = True
   193                     continue
   230                     continue
   194 
   231 
   195         if showlines:
   232         if showlines:
   196             result, lines = result
   233             result, lines = result
   197 
   234 
   198         formatter.write(result, lines, existinglines=existinglines)
   235         formatter.write(result, lines, existinglines=existinglines)
   199     formatter.end()
   236     formatter.end()
   200 
   237 
       
   238 
   201 _newopts = set()
   239 _newopts = set()
   202 _knownopts = {opt[1].replace('-', '_') for opt in
   240 _knownopts = {
   203               (fastannotatecommandargs[r'options'] + commands.globalopts)}
   241     opt[1].replace('-', '_')
       
   242     for opt in (fastannotatecommandargs[r'options'] + commands.globalopts)
       
   243 }
       
   244 
   204 
   245 
   205 def _annotatewrapper(orig, ui, repo, *pats, **opts):
   246 def _annotatewrapper(orig, ui, repo, *pats, **opts):
   206     """used by wrapdefault"""
   247     """used by wrapdefault"""
   207     # we need this hack until the obsstore has 0.0 seconds perf impact
   248     # we need this hack until the obsstore has 0.0 seconds perf impact
   208     if ui.configbool('fastannotate', 'unfilteredrepo'):
   249     if ui.configbool('fastannotate', 'unfilteredrepo'):
   218         paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
   259         paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
   219         repo.prefetchfastannotate(paths)
   260         repo.prefetchfastannotate(paths)
   220 
   261 
   221     return orig(ui, repo, *pats, **opts)
   262     return orig(ui, repo, *pats, **opts)
   222 
   263 
       
   264 
   223 def registercommand():
   265 def registercommand():
   224     """register the fastannotate command"""
   266     """register the fastannotate command"""
   225     name = 'fastannotate|fastblame|fa'
   267     name = 'fastannotate|fastblame|fa'
   226     command(name, helpbasic=True, **fastannotatecommandargs)(fastannotate)
   268     command(name, helpbasic=True, **fastannotatecommandargs)(fastannotate)
   227 
   269 
       
   270 
   228 def wrapdefault():
   271 def wrapdefault():
   229     """wrap the default annotate command, to be aware of the protocol"""
   272     """wrap the default annotate command, to be aware of the protocol"""
   230     extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
   273     extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
   231 
   274 
   232 @command('debugbuildannotatecache',
   275 
   233          [('r', 'rev', '', _('build up to the specific revision'), _('REV'))
   276 @command(
   234          ] + commands.walkopts,
   277     'debugbuildannotatecache',
   235          _('[-r REV] FILE...'))
   278     [('r', 'rev', '', _('build up to the specific revision'), _('REV'))]
       
   279     + commands.walkopts,
       
   280     _('[-r REV] FILE...'),
       
   281 )
   236 def debugbuildannotatecache(ui, repo, *pats, **opts):
   282 def debugbuildannotatecache(ui, repo, *pats, **opts):
   237     """incrementally build fastannotate cache up to REV for specified files
   283     """incrementally build fastannotate cache up to REV for specified files
   238 
   284 
   239     If REV is not specified, use the config 'fastannotate.mainbranch'.
   285     If REV is not specified, use the config 'fastannotate.mainbranch'.
   240 
   286 
   245     options and lives in '.hg/fastannotate/default'.
   291     options and lives in '.hg/fastannotate/default'.
   246     """
   292     """
   247     opts = pycompat.byteskwargs(opts)
   293     opts = pycompat.byteskwargs(opts)
   248     rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
   294     rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
   249     if not rev:
   295     if not rev:
   250         raise error.Abort(_('you need to provide a revision'),
   296         raise error.Abort(
   251                           hint=_('set fastannotate.mainbranch or use --rev'))
   297             _('you need to provide a revision'),
       
   298             hint=_('set fastannotate.mainbranch or use --rev'),
       
   299         )
   252     if ui.configbool('fastannotate', 'unfilteredrepo'):
   300     if ui.configbool('fastannotate', 'unfilteredrepo'):
   253         repo = repo.unfiltered()
   301         repo = repo.unfiltered()
   254     ctx = scmutil.revsingle(repo, rev)
   302     ctx = scmutil.revsingle(repo, rev)
   255     m = scmutil.match(ctx, pats, opts)
   303     m = scmutil.match(ctx, pats, opts)
   256     paths = list(ctx.walk(m))
   304     paths = list(ctx.walk(m))
   270                         continue
   318                         continue
   271                     actx.annotate(rev, rev)
   319                     actx.annotate(rev, rev)
   272                 except (faerror.CannotReuseError, faerror.CorruptedFileError):
   320                 except (faerror.CannotReuseError, faerror.CorruptedFileError):
   273                     # the cache is broken (could happen with renaming so the
   321                     # the cache is broken (could happen with renaming so the
   274                     # file history gets invalidated). rebuild and try again.
   322                     # file history gets invalidated). rebuild and try again.
   275                     ui.debug('fastannotate: %s: rebuilding broken cache\n'
   323                     ui.debug(
   276                              % path)
   324                         'fastannotate: %s: rebuilding broken cache\n' % path
       
   325                     )
   277                     actx.rebuild()
   326                     actx.rebuild()
   278                     try:
   327                     try:
   279                         actx.annotate(rev, rev)
   328                         actx.annotate(rev, rev)
   280                     except Exception as ex:
   329                     except Exception as ex:
   281                         # possibly a bug, but should not stop us from building
   330                         # possibly a bug, but should not stop us from building
   282                         # cache for other files.
   331                         # cache for other files.
   283                         ui.warn(_('fastannotate: %s: failed to '
   332                         ui.warn(
   284                                   'build cache: %r\n') % (path, ex))
   333                             _(
       
   334                                 'fastannotate: %s: failed to '
       
   335                                 'build cache: %r\n'
       
   336                             )
       
   337                             % (path, ex)
       
   338                         )
   285         progress.complete()
   339         progress.complete()