##// END OF EJS Templates
py3: add r'' prefix to prevent b'' being prepended...
Pulkit Goyal -
r39443:b3572f73 default
parent child Browse files
Show More
@@ -1,281 +1,281 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 '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 '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 '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 rev = opts.get('rev', '.')
133 rev = opts.get('rev', '.')
134 rebuild = opts.get('rebuild', False)
134 rebuild = opts.get('rebuild', False)
135
135
136 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
136 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
137 whitespace=True)
137 whitespace=True)
138 aopts = facontext.annotateopts(
138 aopts = facontext.annotateopts(
139 diffopts=diffopts,
139 diffopts=diffopts,
140 followmerge=not opts.get('linear', False),
140 followmerge=not opts.get('linear', False),
141 followrename=not opts.get('no_follow', False))
141 followrename=not opts.get('no_follow', False))
142
142
143 if not any(opts.get(s)
143 if not any(opts.get(s)
144 for s in ['user', 'date', 'file', 'number', 'changeset']):
144 for s in ['user', 'date', 'file', 'number', 'changeset']):
145 # default 'number' for compatibility. but fastannotate is more
145 # default 'number' for compatibility. but fastannotate is more
146 # efficient with "changeset", "line-number" and "no-content".
146 # efficient with "changeset", "line-number" and "no-content".
147 for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
147 for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
148 opts[name] = True
148 opts[name] = True
149
149
150 ui.pager('fastannotate')
150 ui.pager('fastannotate')
151 template = opts.get('template')
151 template = opts.get('template')
152 if template == 'json':
152 if template == 'json':
153 formatter = faformatter.jsonformatter(ui, repo, opts)
153 formatter = faformatter.jsonformatter(ui, repo, opts)
154 else:
154 else:
155 formatter = faformatter.defaultformatter(ui, repo, opts)
155 formatter = faformatter.defaultformatter(ui, repo, opts)
156 showdeleted = opts.get('deleted', False)
156 showdeleted = opts.get('deleted', False)
157 showlines = not bool(opts.get('no_content'))
157 showlines = not bool(opts.get('no_content'))
158 showpath = opts.get('file', False)
158 showpath = opts.get('file', False)
159
159
160 # find the head of the main (master) branch
160 # find the head of the main (master) branch
161 master = ui.config('fastannotate', 'mainbranch') or rev
161 master = ui.config('fastannotate', 'mainbranch') or rev
162
162
163 # paths will be used for prefetching and the real annotating
163 # paths will be used for prefetching and the real annotating
164 paths = list(_matchpaths(repo, rev, pats, opts, aopts))
164 paths = list(_matchpaths(repo, rev, pats, opts, aopts))
165
165
166 # for client, prefetch from the server
166 # for client, prefetch from the server
167 if util.safehasattr(repo, 'prefetchfastannotate'):
167 if util.safehasattr(repo, 'prefetchfastannotate'):
168 repo.prefetchfastannotate(paths)
168 repo.prefetchfastannotate(paths)
169
169
170 for path in paths:
170 for path in paths:
171 result = lines = existinglines = None
171 result = lines = existinglines = None
172 while True:
172 while True:
173 try:
173 try:
174 with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
174 with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
175 result = a.annotate(rev, master=master, showpath=showpath,
175 result = a.annotate(rev, master=master, showpath=showpath,
176 showlines=(showlines and
176 showlines=(showlines and
177 not showdeleted))
177 not showdeleted))
178 if showdeleted:
178 if showdeleted:
179 existinglines = set((l[0], l[1]) for l in result)
179 existinglines = set((l[0], l[1]) for l in result)
180 result = a.annotatealllines(
180 result = a.annotatealllines(
181 rev, showpath=showpath, showlines=showlines)
181 rev, showpath=showpath, showlines=showlines)
182 break
182 break
183 except (faerror.CannotReuseError, faerror.CorruptedFileError):
183 except (faerror.CannotReuseError, faerror.CorruptedFileError):
184 # happens if master moves backwards, or the file was deleted
184 # happens if master moves backwards, or the file was deleted
185 # and readded, or renamed to an existing name, or corrupted.
185 # and readded, or renamed to an existing name, or corrupted.
186 if rebuild: # give up since we have tried rebuild already
186 if rebuild: # give up since we have tried rebuild already
187 raise
187 raise
188 else: # try a second time rebuilding the cache (slow)
188 else: # try a second time rebuilding the cache (slow)
189 rebuild = True
189 rebuild = True
190 continue
190 continue
191
191
192 if showlines:
192 if showlines:
193 result, lines = result
193 result, lines = result
194
194
195 formatter.write(result, lines, existinglines=existinglines)
195 formatter.write(result, lines, existinglines=existinglines)
196 formatter.end()
196 formatter.end()
197
197
198 _newopts = set([])
198 _newopts = set([])
199 _knownopts = set([opt[1].replace('-', '_') for opt in
199 _knownopts = set([opt[1].replace('-', '_') for opt in
200 (fastannotatecommandargs['options'] + commands.globalopts)])
200 (fastannotatecommandargs[r'options'] + commands.globalopts)])
201
201
202 def _annotatewrapper(orig, ui, repo, *pats, **opts):
202 def _annotatewrapper(orig, ui, repo, *pats, **opts):
203 """used by wrapdefault"""
203 """used by wrapdefault"""
204 # we need this hack until the obsstore has 0.0 seconds perf impact
204 # we need this hack until the obsstore has 0.0 seconds perf impact
205 if ui.configbool('fastannotate', 'unfilteredrepo'):
205 if ui.configbool('fastannotate', 'unfilteredrepo'):
206 repo = repo.unfiltered()
206 repo = repo.unfiltered()
207
207
208 # treat the file as text (skip the isbinary check)
208 # treat the file as text (skip the isbinary check)
209 if ui.configbool('fastannotate', 'forcetext'):
209 if ui.configbool('fastannotate', 'forcetext'):
210 opts['text'] = True
210 opts['text'] = True
211
211
212 # check if we need to do prefetch (client-side)
212 # check if we need to do prefetch (client-side)
213 rev = opts.get('rev')
213 rev = opts.get('rev')
214 if util.safehasattr(repo, 'prefetchfastannotate') and rev is not None:
214 if util.safehasattr(repo, 'prefetchfastannotate') and rev is not None:
215 paths = list(_matchpaths(repo, rev, pats, opts))
215 paths = list(_matchpaths(repo, rev, pats, opts))
216 repo.prefetchfastannotate(paths)
216 repo.prefetchfastannotate(paths)
217
217
218 return orig(ui, repo, *pats, **opts)
218 return orig(ui, repo, *pats, **opts)
219
219
220 def registercommand():
220 def registercommand():
221 """register the fastannotate command"""
221 """register the fastannotate command"""
222 name = '^fastannotate|fastblame|fa'
222 name = '^fastannotate|fastblame|fa'
223 command(name, **fastannotatecommandargs)(fastannotate)
223 command(name, **fastannotatecommandargs)(fastannotate)
224
224
225 def wrapdefault():
225 def wrapdefault():
226 """wrap the default annotate command, to be aware of the protocol"""
226 """wrap the default annotate command, to be aware of the protocol"""
227 extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
227 extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
228
228
229 @command('debugbuildannotatecache',
229 @command('debugbuildannotatecache',
230 [('r', 'rev', '', _('build up to the specific revision'), _('REV'))
230 [('r', 'rev', '', _('build up to the specific revision'), _('REV'))
231 ] + commands.walkopts,
231 ] + commands.walkopts,
232 _('[-r REV] FILE...'))
232 _('[-r REV] FILE...'))
233 def debugbuildannotatecache(ui, repo, *pats, **opts):
233 def debugbuildannotatecache(ui, repo, *pats, **opts):
234 """incrementally build fastannotate cache up to REV for specified files
234 """incrementally build fastannotate cache up to REV for specified files
235
235
236 If REV is not specified, use the config 'fastannotate.mainbranch'.
236 If REV is not specified, use the config 'fastannotate.mainbranch'.
237
237
238 If fastannotate.client is True, download the annotate cache from the
238 If fastannotate.client is True, download the annotate cache from the
239 server. Otherwise, build the annotate cache locally.
239 server. Otherwise, build the annotate cache locally.
240
240
241 The annotate cache will be built using the default diff and follow
241 The annotate cache will be built using the default diff and follow
242 options and lives in '.hg/fastannotate/default'.
242 options and lives in '.hg/fastannotate/default'.
243 """
243 """
244 rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
244 rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
245 if not rev:
245 if not rev:
246 raise error.Abort(_('you need to provide a revision'),
246 raise error.Abort(_('you need to provide a revision'),
247 hint=_('set fastannotate.mainbranch or use --rev'))
247 hint=_('set fastannotate.mainbranch or use --rev'))
248 if ui.configbool('fastannotate', 'unfilteredrepo'):
248 if ui.configbool('fastannotate', 'unfilteredrepo'):
249 repo = repo.unfiltered()
249 repo = repo.unfiltered()
250 ctx = scmutil.revsingle(repo, rev)
250 ctx = scmutil.revsingle(repo, rev)
251 m = scmutil.match(ctx, pats, opts)
251 m = scmutil.match(ctx, pats, opts)
252 paths = list(ctx.walk(m))
252 paths = list(ctx.walk(m))
253 if util.safehasattr(repo, 'prefetchfastannotate'):
253 if util.safehasattr(repo, 'prefetchfastannotate'):
254 # client
254 # client
255 if opts.get('REV'):
255 if opts.get('REV'):
256 raise error.Abort(_('--rev cannot be used for client'))
256 raise error.Abort(_('--rev cannot be used for client'))
257 repo.prefetchfastannotate(paths)
257 repo.prefetchfastannotate(paths)
258 else:
258 else:
259 # server, or full repo
259 # server, or full repo
260 for i, path in enumerate(paths):
260 for i, path in enumerate(paths):
261 ui.progress(_('building'), i, total=len(paths))
261 ui.progress(_('building'), i, total=len(paths))
262 with facontext.annotatecontext(repo, path) as actx:
262 with facontext.annotatecontext(repo, path) as actx:
263 try:
263 try:
264 if actx.isuptodate(rev):
264 if actx.isuptodate(rev):
265 continue
265 continue
266 actx.annotate(rev, rev)
266 actx.annotate(rev, rev)
267 except (faerror.CannotReuseError, faerror.CorruptedFileError):
267 except (faerror.CannotReuseError, faerror.CorruptedFileError):
268 # the cache is broken (could happen with renaming so the
268 # the cache is broken (could happen with renaming so the
269 # file history gets invalidated). rebuild and try again.
269 # file history gets invalidated). rebuild and try again.
270 ui.debug('fastannotate: %s: rebuilding broken cache\n'
270 ui.debug('fastannotate: %s: rebuilding broken cache\n'
271 % path)
271 % path)
272 actx.rebuild()
272 actx.rebuild()
273 try:
273 try:
274 actx.annotate(rev, rev)
274 actx.annotate(rev, rev)
275 except Exception as ex:
275 except Exception as ex:
276 # possibly a bug, but should not stop us from building
276 # possibly a bug, but should not stop us from building
277 # cache for other files.
277 # cache for other files.
278 ui.warn(_('fastannotate: %s: failed to '
278 ui.warn(_('fastannotate: %s: failed to '
279 'build cache: %r\n') % (path, ex))
279 'build cache: %r\n') % (path, ex))
280 # clear the progress bar
280 # clear the progress bar
281 ui.write()
281 ui.write()
General Comments 0
You need to be logged in to leave comments. Login now