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