##// END OF EJS Templates
py3: fix kwargs handling in hgext/fastannotate.py...
Pulkit Goyal -
r39702:b77f2ea5 default
parent child Browse files
Show More
@@ -1,281 +1,284 b''
1 # Copyright 2016-present Facebook. All Rights Reserved.
1 # Copyright 2016-present Facebook. All Rights Reserved.
2 #
2 #
3 # commands: fastannotate commands
3 # commands: fastannotate commands
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 commands,
14 commands,
15 error,
15 error,
16 extensions,
16 extensions,
17 patch,
17 patch,
18 pycompat,
18 pycompat,
19 registrar,
19 registrar,
20 scmutil,
20 scmutil,
21 util,
21 util,
22 )
22 )
23
23
24 from . import (
24 from . import (
25 context as facontext,
25 context as facontext,
26 error as faerror,
26 error as faerror,
27 formatter as faformatter,
27 formatter as faformatter,
28 )
28 )
29
29
30 cmdtable = {}
30 cmdtable = {}
31 command = registrar.command(cmdtable)
31 command = registrar.command(cmdtable)
32
32
33 def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
33 def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
34 """generate paths matching given patterns"""
34 """generate paths matching given patterns"""
35 perfhack = repo.ui.configbool('fastannotate', 'perfhack')
35 perfhack = repo.ui.configbool('fastannotate', 'perfhack')
36
36
37 # disable perfhack if:
37 # disable perfhack if:
38 # a) any walkopt is used
38 # a) any walkopt is used
39 # b) if we treat pats as plain file names, some of them do not have
39 # b) if we treat pats as plain file names, some of them do not have
40 # corresponding linelog files
40 # corresponding linelog files
41 if perfhack:
41 if perfhack:
42 # cwd related to reporoot
42 # cwd related to reporoot
43 reporoot = os.path.dirname(repo.path)
43 reporoot = os.path.dirname(repo.path)
44 reldir = os.path.relpath(pycompat.getcwd(), reporoot)
44 reldir = os.path.relpath(pycompat.getcwd(), reporoot)
45 if reldir == '.':
45 if reldir == '.':
46 reldir = ''
46 reldir = ''
47 if any(opts.get(o[1]) for o in commands.walkopts): # a)
47 if any(opts.get(o[1]) for o in commands.walkopts): # a)
48 perfhack = False
48 perfhack = False
49 else: # b)
49 else: # b)
50 relpats = [os.path.relpath(p, reporoot) if os.path.isabs(p) else p
50 relpats = [os.path.relpath(p, reporoot) if os.path.isabs(p) else p
51 for p in pats]
51 for p in pats]
52 # disable perfhack on '..' since it allows escaping from the repo
52 # disable perfhack on '..' since it allows escaping from the repo
53 if any(('..' in f or
53 if any(('..' in f or
54 not os.path.isfile(
54 not os.path.isfile(
55 facontext.pathhelper(repo, f, aopts).linelogpath))
55 facontext.pathhelper(repo, f, aopts).linelogpath))
56 for f in relpats):
56 for f in relpats):
57 perfhack = False
57 perfhack = False
58
58
59 # perfhack: emit paths directory without checking with manifest
59 # perfhack: emit paths directory without checking with manifest
60 # this can be incorrect if the rev dos not have file.
60 # this can be incorrect if the rev dos not have file.
61 if perfhack:
61 if perfhack:
62 for p in relpats:
62 for p in relpats:
63 yield os.path.join(reldir, p)
63 yield os.path.join(reldir, p)
64 else:
64 else:
65 def bad(x, y):
65 def bad(x, y):
66 raise error.Abort("%s: %s" % (x, y))
66 raise error.Abort("%s: %s" % (x, y))
67 ctx = scmutil.revsingle(repo, rev)
67 ctx = scmutil.revsingle(repo, rev)
68 m = scmutil.match(ctx, pats, opts, badfn=bad)
68 m = scmutil.match(ctx, pats, opts, badfn=bad)
69 for p in ctx.walk(m):
69 for p in ctx.walk(m):
70 yield p
70 yield p
71
71
72 fastannotatecommandargs = {
72 fastannotatecommandargs = {
73 r'options': [
73 r'options': [
74 ('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
74 ('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
75 ('u', 'user', None, _('list the author (long with -v)')),
75 ('u', 'user', None, _('list the author (long with -v)')),
76 ('f', 'file', None, _('list the filename')),
76 ('f', 'file', None, _('list the filename')),
77 ('d', 'date', None, _('list the date (short with -q)')),
77 ('d', 'date', None, _('list the date (short with -q)')),
78 ('n', 'number', None, _('list the revision number (default)')),
78 ('n', 'number', None, _('list the revision number (default)')),
79 ('c', 'changeset', None, _('list the changeset')),
79 ('c', 'changeset', None, _('list the changeset')),
80 ('l', 'line-number', None, _('show line number at the first '
80 ('l', 'line-number', None, _('show line number at the first '
81 'appearance')),
81 'appearance')),
82 ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')),
82 ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')),
83 ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')),
83 ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')),
84 ('', 'no-follow', None, _("don't follow copies and renames")),
84 ('', 'no-follow', None, _("don't follow copies and renames")),
85 ('', 'linear', None, _('enforce linear history, ignore second parent '
85 ('', 'linear', None, _('enforce linear history, ignore second parent '
86 'of merges (EXPERIMENTAL)')),
86 'of merges (EXPERIMENTAL)')),
87 ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
87 ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
88 ('', 'rebuild', None, _('rebuild cache even if it exists '
88 ('', 'rebuild', None, _('rebuild cache even if it exists '
89 '(EXPERIMENTAL)')),
89 '(EXPERIMENTAL)')),
90 ] + commands.diffwsopts + commands.walkopts + commands.formatteropts,
90 ] + commands.diffwsopts + commands.walkopts + commands.formatteropts,
91 r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
91 r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
92 r'inferrepo': True,
92 r'inferrepo': True,
93 }
93 }
94
94
95 def fastannotate(ui, repo, *pats, **opts):
95 def fastannotate(ui, repo, *pats, **opts):
96 """show changeset information by line for each file
96 """show changeset information by line for each file
97
97
98 List changes in files, showing the revision id responsible for each line.
98 List changes in files, showing the revision id responsible for each line.
99
99
100 This command is useful for discovering when a change was made and by whom.
100 This command is useful for discovering when a change was made and by whom.
101
101
102 By default this command prints revision numbers. If you include --file,
102 By default this command prints revision numbers. If you include --file,
103 --user, or --date, the revision number is suppressed unless you also
103 --user, or --date, the revision number is suppressed unless you also
104 include --number. The default format can also be customized by setting
104 include --number. The default format can also be customized by setting
105 fastannotate.defaultformat.
105 fastannotate.defaultformat.
106
106
107 Returns 0 on success.
107 Returns 0 on success.
108
108
109 .. container:: verbose
109 .. container:: verbose
110
110
111 This command uses an implementation different from the vanilla annotate
111 This command uses an implementation different from the vanilla annotate
112 command, which may produce slightly different (while still reasonable)
112 command, which may produce slightly different (while still reasonable)
113 outputs for some cases.
113 outputs for some cases.
114
114
115 Unlike the vanilla anootate, fastannotate follows rename regardless of
115 Unlike the vanilla anootate, fastannotate follows rename regardless of
116 the existence of --file.
116 the existence of --file.
117
117
118 For the best performance when running on a full repo, use -c, -l,
118 For the best performance when running on a full repo, use -c, -l,
119 avoid -u, -d, -n. Use --linear and --no-content to make it even faster.
119 avoid -u, -d, -n. Use --linear and --no-content to make it even faster.
120
120
121 For the best performance when running on a shallow (remotefilelog)
121 For the best performance when running on a shallow (remotefilelog)
122 repo, avoid --linear, --no-follow, or any diff options. As the server
122 repo, avoid --linear, --no-follow, or any diff options. As the server
123 won't be able to populate annotate cache when non-default options
123 won't be able to populate annotate cache when non-default options
124 affecting results are used.
124 affecting results are used.
125 """
125 """
126 if not pats:
126 if not pats:
127 raise error.Abort(_('at least one filename or pattern is required'))
127 raise error.Abort(_('at least one filename or pattern is required'))
128
128
129 # performance hack: filtered repo can be slow. unfilter by default.
129 # performance hack: filtered repo can be slow. unfilter by default.
130 if ui.configbool('fastannotate', 'unfilteredrepo'):
130 if ui.configbool('fastannotate', 'unfilteredrepo'):
131 repo = repo.unfiltered()
131 repo = repo.unfiltered()
132
132
133 opts = pycompat.byteskwargs(opts)
134
133 rev = opts.get('rev', '.')
135 rev = opts.get('rev', '.')
134 rebuild = opts.get('rebuild', False)
136 rebuild = opts.get('rebuild', False)
135
137
136 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
138 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
137 whitespace=True)
139 whitespace=True)
138 aopts = facontext.annotateopts(
140 aopts = facontext.annotateopts(
139 diffopts=diffopts,
141 diffopts=diffopts,
140 followmerge=not opts.get('linear', False),
142 followmerge=not opts.get('linear', False),
141 followrename=not opts.get('no_follow', False))
143 followrename=not opts.get('no_follow', False))
142
144
143 if not any(opts.get(s)
145 if not any(opts.get(s)
144 for s in ['user', 'date', 'file', 'number', 'changeset']):
146 for s in ['user', 'date', 'file', 'number', 'changeset']):
145 # default 'number' for compatibility. but fastannotate is more
147 # default 'number' for compatibility. but fastannotate is more
146 # efficient with "changeset", "line-number" and "no-content".
148 # efficient with "changeset", "line-number" and "no-content".
147 for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
149 for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
148 opts[name] = True
150 opts[name] = True
149
151
150 ui.pager('fastannotate')
152 ui.pager('fastannotate')
151 template = opts.get('template')
153 template = opts.get('template')
152 if template == 'json':
154 if template == 'json':
153 formatter = faformatter.jsonformatter(ui, repo, opts)
155 formatter = faformatter.jsonformatter(ui, repo, opts)
154 else:
156 else:
155 formatter = faformatter.defaultformatter(ui, repo, opts)
157 formatter = faformatter.defaultformatter(ui, repo, opts)
156 showdeleted = opts.get('deleted', False)
158 showdeleted = opts.get('deleted', False)
157 showlines = not bool(opts.get('no_content'))
159 showlines = not bool(opts.get('no_content'))
158 showpath = opts.get('file', False)
160 showpath = opts.get('file', False)
159
161
160 # find the head of the main (master) branch
162 # find the head of the main (master) branch
161 master = ui.config('fastannotate', 'mainbranch') or rev
163 master = ui.config('fastannotate', 'mainbranch') or rev
162
164
163 # paths will be used for prefetching and the real annotating
165 # paths will be used for prefetching and the real annotating
164 paths = list(_matchpaths(repo, rev, pats, opts, aopts))
166 paths = list(_matchpaths(repo, rev, pats, opts, aopts))
165
167
166 # for client, prefetch from the server
168 # for client, prefetch from the server
167 if util.safehasattr(repo, 'prefetchfastannotate'):
169 if util.safehasattr(repo, 'prefetchfastannotate'):
168 repo.prefetchfastannotate(paths)
170 repo.prefetchfastannotate(paths)
169
171
170 for path in paths:
172 for path in paths:
171 result = lines = existinglines = None
173 result = lines = existinglines = None
172 while True:
174 while True:
173 try:
175 try:
174 with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
176 with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
175 result = a.annotate(rev, master=master, showpath=showpath,
177 result = a.annotate(rev, master=master, showpath=showpath,
176 showlines=(showlines and
178 showlines=(showlines and
177 not showdeleted))
179 not showdeleted))
178 if showdeleted:
180 if showdeleted:
179 existinglines = set((l[0], l[1]) for l in result)
181 existinglines = set((l[0], l[1]) for l in result)
180 result = a.annotatealllines(
182 result = a.annotatealllines(
181 rev, showpath=showpath, showlines=showlines)
183 rev, showpath=showpath, showlines=showlines)
182 break
184 break
183 except (faerror.CannotReuseError, faerror.CorruptedFileError):
185 except (faerror.CannotReuseError, faerror.CorruptedFileError):
184 # happens if master moves backwards, or the file was deleted
186 # happens if master moves backwards, or the file was deleted
185 # and readded, or renamed to an existing name, or corrupted.
187 # and readded, or renamed to an existing name, or corrupted.
186 if rebuild: # give up since we have tried rebuild already
188 if rebuild: # give up since we have tried rebuild already
187 raise
189 raise
188 else: # try a second time rebuilding the cache (slow)
190 else: # try a second time rebuilding the cache (slow)
189 rebuild = True
191 rebuild = True
190 continue
192 continue
191
193
192 if showlines:
194 if showlines:
193 result, lines = result
195 result, lines = result
194
196
195 formatter.write(result, lines, existinglines=existinglines)
197 formatter.write(result, lines, existinglines=existinglines)
196 formatter.end()
198 formatter.end()
197
199
198 _newopts = set([])
200 _newopts = set([])
199 _knownopts = set([opt[1].replace('-', '_') for opt in
201 _knownopts = set([opt[1].replace('-', '_') for opt in
200 (fastannotatecommandargs[r'options'] + commands.globalopts)])
202 (fastannotatecommandargs[r'options'] + commands.globalopts)])
201
203
202 def _annotatewrapper(orig, ui, repo, *pats, **opts):
204 def _annotatewrapper(orig, ui, repo, *pats, **opts):
203 """used by wrapdefault"""
205 """used by wrapdefault"""
204 # we need this hack until the obsstore has 0.0 seconds perf impact
206 # we need this hack until the obsstore has 0.0 seconds perf impact
205 if ui.configbool('fastannotate', 'unfilteredrepo'):
207 if ui.configbool('fastannotate', 'unfilteredrepo'):
206 repo = repo.unfiltered()
208 repo = repo.unfiltered()
207
209
208 # treat the file as text (skip the isbinary check)
210 # treat the file as text (skip the isbinary check)
209 if ui.configbool('fastannotate', 'forcetext'):
211 if ui.configbool('fastannotate', 'forcetext'):
210 opts['text'] = True
212 opts[r'text'] = True
211
213
212 # check if we need to do prefetch (client-side)
214 # check if we need to do prefetch (client-side)
213 rev = opts.get('rev')
215 rev = opts.get(r'rev')
214 if util.safehasattr(repo, 'prefetchfastannotate') and rev is not None:
216 if util.safehasattr(repo, 'prefetchfastannotate') and rev is not None:
215 paths = list(_matchpaths(repo, rev, pats, opts))
217 paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
216 repo.prefetchfastannotate(paths)
218 repo.prefetchfastannotate(paths)
217
219
218 return orig(ui, repo, *pats, **opts)
220 return orig(ui, repo, *pats, **opts)
219
221
220 def registercommand():
222 def registercommand():
221 """register the fastannotate command"""
223 """register the fastannotate command"""
222 name = '^fastannotate|fastblame|fa'
224 name = '^fastannotate|fastblame|fa'
223 command(name, **fastannotatecommandargs)(fastannotate)
225 command(name, **fastannotatecommandargs)(fastannotate)
224
226
225 def wrapdefault():
227 def wrapdefault():
226 """wrap the default annotate command, to be aware of the protocol"""
228 """wrap the default annotate command, to be aware of the protocol"""
227 extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
229 extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
228
230
229 @command('debugbuildannotatecache',
231 @command('debugbuildannotatecache',
230 [('r', 'rev', '', _('build up to the specific revision'), _('REV'))
232 [('r', 'rev', '', _('build up to the specific revision'), _('REV'))
231 ] + commands.walkopts,
233 ] + commands.walkopts,
232 _('[-r REV] FILE...'))
234 _('[-r REV] FILE...'))
233 def debugbuildannotatecache(ui, repo, *pats, **opts):
235 def debugbuildannotatecache(ui, repo, *pats, **opts):
234 """incrementally build fastannotate cache up to REV for specified files
236 """incrementally build fastannotate cache up to REV for specified files
235
237
236 If REV is not specified, use the config 'fastannotate.mainbranch'.
238 If REV is not specified, use the config 'fastannotate.mainbranch'.
237
239
238 If fastannotate.client is True, download the annotate cache from the
240 If fastannotate.client is True, download the annotate cache from the
239 server. Otherwise, build the annotate cache locally.
241 server. Otherwise, build the annotate cache locally.
240
242
241 The annotate cache will be built using the default diff and follow
243 The annotate cache will be built using the default diff and follow
242 options and lives in '.hg/fastannotate/default'.
244 options and lives in '.hg/fastannotate/default'.
243 """
245 """
246 opts = pycompat.byteskwargs(opts)
244 rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
247 rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
245 if not rev:
248 if not rev:
246 raise error.Abort(_('you need to provide a revision'),
249 raise error.Abort(_('you need to provide a revision'),
247 hint=_('set fastannotate.mainbranch or use --rev'))
250 hint=_('set fastannotate.mainbranch or use --rev'))
248 if ui.configbool('fastannotate', 'unfilteredrepo'):
251 if ui.configbool('fastannotate', 'unfilteredrepo'):
249 repo = repo.unfiltered()
252 repo = repo.unfiltered()
250 ctx = scmutil.revsingle(repo, rev)
253 ctx = scmutil.revsingle(repo, rev)
251 m = scmutil.match(ctx, pats, opts)
254 m = scmutil.match(ctx, pats, opts)
252 paths = list(ctx.walk(m))
255 paths = list(ctx.walk(m))
253 if util.safehasattr(repo, 'prefetchfastannotate'):
256 if util.safehasattr(repo, 'prefetchfastannotate'):
254 # client
257 # client
255 if opts.get('REV'):
258 if opts.get('REV'):
256 raise error.Abort(_('--rev cannot be used for client'))
259 raise error.Abort(_('--rev cannot be used for client'))
257 repo.prefetchfastannotate(paths)
260 repo.prefetchfastannotate(paths)
258 else:
261 else:
259 # server, or full repo
262 # server, or full repo
260 for i, path in enumerate(paths):
263 for i, path in enumerate(paths):
261 ui.progress(_('building'), i, total=len(paths))
264 ui.progress(_('building'), i, total=len(paths))
262 with facontext.annotatecontext(repo, path) as actx:
265 with facontext.annotatecontext(repo, path) as actx:
263 try:
266 try:
264 if actx.isuptodate(rev):
267 if actx.isuptodate(rev):
265 continue
268 continue
266 actx.annotate(rev, rev)
269 actx.annotate(rev, rev)
267 except (faerror.CannotReuseError, faerror.CorruptedFileError):
270 except (faerror.CannotReuseError, faerror.CorruptedFileError):
268 # the cache is broken (could happen with renaming so the
271 # the cache is broken (could happen with renaming so the
269 # file history gets invalidated). rebuild and try again.
272 # file history gets invalidated). rebuild and try again.
270 ui.debug('fastannotate: %s: rebuilding broken cache\n'
273 ui.debug('fastannotate: %s: rebuilding broken cache\n'
271 % path)
274 % path)
272 actx.rebuild()
275 actx.rebuild()
273 try:
276 try:
274 actx.annotate(rev, rev)
277 actx.annotate(rev, rev)
275 except Exception as ex:
278 except Exception as ex:
276 # possibly a bug, but should not stop us from building
279 # possibly a bug, but should not stop us from building
277 # cache for other files.
280 # cache for other files.
278 ui.warn(_('fastannotate: %s: failed to '
281 ui.warn(_('fastannotate: %s: failed to '
279 'build cache: %r\n') % (path, ex))
282 'build cache: %r\n') % (path, ex))
280 # clear the progress bar
283 # clear the progress bar
281 ui.write()
284 ui.write()
General Comments 0
You need to be logged in to leave comments. Login now