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