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