43 # cwd related to reporoot |
44 # cwd related to reporoot |
44 reporoot = os.path.dirname(repo.path) |
45 reporoot = os.path.dirname(repo.path) |
45 reldir = os.path.relpath(encoding.getcwd(), reporoot) |
46 reldir = os.path.relpath(encoding.getcwd(), reporoot) |
46 if reldir == '.': |
47 if reldir == '.': |
47 reldir = '' |
48 reldir = '' |
48 if any(opts.get(o[1]) for o in commands.walkopts): # a) |
49 if any(opts.get(o[1]) for o in commands.walkopts): # a) |
49 perfhack = False |
50 perfhack = False |
50 else: # b) |
51 else: # b) |
51 relpats = [os.path.relpath(p, reporoot) if os.path.isabs(p) else p |
52 relpats = [ |
52 for p in pats] |
53 os.path.relpath(p, reporoot) if os.path.isabs(p) else p |
|
54 for p in pats |
|
55 ] |
53 # disable perfhack on '..' since it allows escaping from the repo |
56 # disable perfhack on '..' since it allows escaping from the repo |
54 if any(('..' in f or |
57 if any( |
55 not os.path.isfile( |
58 ( |
56 facontext.pathhelper(repo, f, aopts).linelogpath)) |
59 '..' in f |
57 for f in relpats): |
60 or not os.path.isfile( |
|
61 facontext.pathhelper(repo, f, aopts).linelogpath |
|
62 ) |
|
63 ) |
|
64 for f in relpats |
|
65 ): |
58 perfhack = False |
66 perfhack = False |
59 |
67 |
60 # perfhack: emit paths directory without checking with manifest |
68 # perfhack: emit paths directory without checking with manifest |
61 # this can be incorrect if the rev dos not have file. |
69 # this can be incorrect if the rev dos not have file. |
62 if perfhack: |
70 if perfhack: |
63 for p in relpats: |
71 for p in relpats: |
64 yield os.path.join(reldir, p) |
72 yield os.path.join(reldir, p) |
65 else: |
73 else: |
|
74 |
66 def bad(x, y): |
75 def bad(x, y): |
67 raise error.Abort("%s: %s" % (x, y)) |
76 raise error.Abort("%s: %s" % (x, y)) |
|
77 |
68 ctx = scmutil.revsingle(repo, rev) |
78 ctx = scmutil.revsingle(repo, rev) |
69 m = scmutil.match(ctx, pats, opts, badfn=bad) |
79 m = scmutil.match(ctx, pats, opts, badfn=bad) |
70 for p in ctx.walk(m): |
80 for p in ctx.walk(m): |
71 yield p |
81 yield p |
|
82 |
72 |
83 |
73 fastannotatecommandargs = { |
84 fastannotatecommandargs = { |
74 r'options': [ |
85 r'options': [ |
75 ('r', 'rev', '.', _('annotate the specified revision'), _('REV')), |
86 ('r', 'rev', '.', _('annotate the specified revision'), _('REV')), |
76 ('u', 'user', None, _('list the author (long with -v)')), |
87 ('u', 'user', None, _('list the author (long with -v)')), |
77 ('f', 'file', None, _('list the filename')), |
88 ('f', 'file', None, _('list the filename')), |
78 ('d', 'date', None, _('list the date (short with -q)')), |
89 ('d', 'date', None, _('list the date (short with -q)')), |
79 ('n', 'number', None, _('list the revision number (default)')), |
90 ('n', 'number', None, _('list the revision number (default)')), |
80 ('c', 'changeset', None, _('list the changeset')), |
91 ('c', 'changeset', None, _('list the changeset')), |
81 ('l', 'line-number', None, _('show line number at the first ' |
92 ( |
82 'appearance')), |
93 'l', |
|
94 'line-number', |
|
95 None, |
|
96 _('show line number at the first ' 'appearance'), |
|
97 ), |
83 ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')), |
98 ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')), |
84 ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')), |
99 ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')), |
85 ('', 'no-follow', None, _("don't follow copies and renames")), |
100 ('', 'no-follow', None, _("don't follow copies and renames")), |
86 ('', 'linear', None, _('enforce linear history, ignore second parent ' |
101 ( |
87 'of merges (EXPERIMENTAL)')), |
102 '', |
|
103 'linear', |
|
104 None, |
|
105 _( |
|
106 'enforce linear history, ignore second parent ' |
|
107 'of merges (EXPERIMENTAL)' |
|
108 ), |
|
109 ), |
88 ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')), |
110 ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')), |
89 ('', 'rebuild', None, _('rebuild cache even if it exists ' |
111 ( |
90 '(EXPERIMENTAL)')), |
112 '', |
91 ] + commands.diffwsopts + commands.walkopts + commands.formatteropts, |
113 'rebuild', |
|
114 None, |
|
115 _('rebuild cache even if it exists ' '(EXPERIMENTAL)'), |
|
116 ), |
|
117 ] |
|
118 + commands.diffwsopts |
|
119 + commands.walkopts |
|
120 + commands.formatteropts, |
92 r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'), |
121 r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'), |
93 r'inferrepo': True, |
122 r'inferrepo': True, |
94 } |
123 } |
|
124 |
95 |
125 |
96 def fastannotate(ui, repo, *pats, **opts): |
126 def fastannotate(ui, repo, *pats, **opts): |
97 """show changeset information by line for each file |
127 """show changeset information by line for each file |
98 |
128 |
99 List changes in files, showing the revision id responsible for each line. |
129 List changes in files, showing the revision id responsible for each line. |
134 opts = pycompat.byteskwargs(opts) |
164 opts = pycompat.byteskwargs(opts) |
135 |
165 |
136 rev = opts.get('rev', '.') |
166 rev = opts.get('rev', '.') |
137 rebuild = opts.get('rebuild', False) |
167 rebuild = opts.get('rebuild', False) |
138 |
168 |
139 diffopts = patch.difffeatureopts(ui, opts, section='annotate', |
169 diffopts = patch.difffeatureopts( |
140 whitespace=True) |
170 ui, opts, section='annotate', whitespace=True |
|
171 ) |
141 aopts = facontext.annotateopts( |
172 aopts = facontext.annotateopts( |
142 diffopts=diffopts, |
173 diffopts=diffopts, |
143 followmerge=not opts.get('linear', False), |
174 followmerge=not opts.get('linear', False), |
144 followrename=not opts.get('no_follow', False)) |
175 followrename=not opts.get('no_follow', False), |
145 |
176 ) |
146 if not any(opts.get(s) |
177 |
147 for s in ['user', 'date', 'file', 'number', 'changeset']): |
178 if not any( |
|
179 opts.get(s) for s in ['user', 'date', 'file', 'number', 'changeset'] |
|
180 ): |
148 # default 'number' for compatibility. but fastannotate is more |
181 # default 'number' for compatibility. but fastannotate is more |
149 # efficient with "changeset", "line-number" and "no-content". |
182 # efficient with "changeset", "line-number" and "no-content". |
150 for name in ui.configlist('fastannotate', 'defaultformat', ['number']): |
183 for name in ui.configlist('fastannotate', 'defaultformat', ['number']): |
151 opts[name] = True |
184 opts[name] = True |
152 |
185 |
173 for path in paths: |
206 for path in paths: |
174 result = lines = existinglines = None |
207 result = lines = existinglines = None |
175 while True: |
208 while True: |
176 try: |
209 try: |
177 with facontext.annotatecontext(repo, path, aopts, rebuild) as a: |
210 with facontext.annotatecontext(repo, path, aopts, rebuild) as a: |
178 result = a.annotate(rev, master=master, showpath=showpath, |
211 result = a.annotate( |
179 showlines=(showlines and |
212 rev, |
180 not showdeleted)) |
213 master=master, |
|
214 showpath=showpath, |
|
215 showlines=(showlines and not showdeleted), |
|
216 ) |
181 if showdeleted: |
217 if showdeleted: |
182 existinglines = set((l[0], l[1]) for l in result) |
218 existinglines = set((l[0], l[1]) for l in result) |
183 result = a.annotatealllines( |
219 result = a.annotatealllines( |
184 rev, showpath=showpath, showlines=showlines) |
220 rev, showpath=showpath, showlines=showlines |
|
221 ) |
185 break |
222 break |
186 except (faerror.CannotReuseError, faerror.CorruptedFileError): |
223 except (faerror.CannotReuseError, faerror.CorruptedFileError): |
187 # happens if master moves backwards, or the file was deleted |
224 # happens if master moves backwards, or the file was deleted |
188 # and readded, or renamed to an existing name, or corrupted. |
225 # and readded, or renamed to an existing name, or corrupted. |
189 if rebuild: # give up since we have tried rebuild already |
226 if rebuild: # give up since we have tried rebuild already |
190 raise |
227 raise |
191 else: # try a second time rebuilding the cache (slow) |
228 else: # try a second time rebuilding the cache (slow) |
192 rebuild = True |
229 rebuild = True |
193 continue |
230 continue |
194 |
231 |
195 if showlines: |
232 if showlines: |
196 result, lines = result |
233 result, lines = result |
197 |
234 |
198 formatter.write(result, lines, existinglines=existinglines) |
235 formatter.write(result, lines, existinglines=existinglines) |
199 formatter.end() |
236 formatter.end() |
200 |
237 |
|
238 |
201 _newopts = set() |
239 _newopts = set() |
202 _knownopts = {opt[1].replace('-', '_') for opt in |
240 _knownopts = { |
203 (fastannotatecommandargs[r'options'] + commands.globalopts)} |
241 opt[1].replace('-', '_') |
|
242 for opt in (fastannotatecommandargs[r'options'] + commands.globalopts) |
|
243 } |
|
244 |
204 |
245 |
205 def _annotatewrapper(orig, ui, repo, *pats, **opts): |
246 def _annotatewrapper(orig, ui, repo, *pats, **opts): |
206 """used by wrapdefault""" |
247 """used by wrapdefault""" |
207 # we need this hack until the obsstore has 0.0 seconds perf impact |
248 # we need this hack until the obsstore has 0.0 seconds perf impact |
208 if ui.configbool('fastannotate', 'unfilteredrepo'): |
249 if ui.configbool('fastannotate', 'unfilteredrepo'): |
218 paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts))) |
259 paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts))) |
219 repo.prefetchfastannotate(paths) |
260 repo.prefetchfastannotate(paths) |
220 |
261 |
221 return orig(ui, repo, *pats, **opts) |
262 return orig(ui, repo, *pats, **opts) |
222 |
263 |
|
264 |
223 def registercommand(): |
265 def registercommand(): |
224 """register the fastannotate command""" |
266 """register the fastannotate command""" |
225 name = 'fastannotate|fastblame|fa' |
267 name = 'fastannotate|fastblame|fa' |
226 command(name, helpbasic=True, **fastannotatecommandargs)(fastannotate) |
268 command(name, helpbasic=True, **fastannotatecommandargs)(fastannotate) |
227 |
269 |
|
270 |
228 def wrapdefault(): |
271 def wrapdefault(): |
229 """wrap the default annotate command, to be aware of the protocol""" |
272 """wrap the default annotate command, to be aware of the protocol""" |
230 extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper) |
273 extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper) |
231 |
274 |
232 @command('debugbuildannotatecache', |
275 |
233 [('r', 'rev', '', _('build up to the specific revision'), _('REV')) |
276 @command( |
234 ] + commands.walkopts, |
277 'debugbuildannotatecache', |
235 _('[-r REV] FILE...')) |
278 [('r', 'rev', '', _('build up to the specific revision'), _('REV'))] |
|
279 + commands.walkopts, |
|
280 _('[-r REV] FILE...'), |
|
281 ) |
236 def debugbuildannotatecache(ui, repo, *pats, **opts): |
282 def debugbuildannotatecache(ui, repo, *pats, **opts): |
237 """incrementally build fastannotate cache up to REV for specified files |
283 """incrementally build fastannotate cache up to REV for specified files |
238 |
284 |
239 If REV is not specified, use the config 'fastannotate.mainbranch'. |
285 If REV is not specified, use the config 'fastannotate.mainbranch'. |
240 |
286 |
245 options and lives in '.hg/fastannotate/default'. |
291 options and lives in '.hg/fastannotate/default'. |
246 """ |
292 """ |
247 opts = pycompat.byteskwargs(opts) |
293 opts = pycompat.byteskwargs(opts) |
248 rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch') |
294 rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch') |
249 if not rev: |
295 if not rev: |
250 raise error.Abort(_('you need to provide a revision'), |
296 raise error.Abort( |
251 hint=_('set fastannotate.mainbranch or use --rev')) |
297 _('you need to provide a revision'), |
|
298 hint=_('set fastannotate.mainbranch or use --rev'), |
|
299 ) |
252 if ui.configbool('fastannotate', 'unfilteredrepo'): |
300 if ui.configbool('fastannotate', 'unfilteredrepo'): |
253 repo = repo.unfiltered() |
301 repo = repo.unfiltered() |
254 ctx = scmutil.revsingle(repo, rev) |
302 ctx = scmutil.revsingle(repo, rev) |
255 m = scmutil.match(ctx, pats, opts) |
303 m = scmutil.match(ctx, pats, opts) |
256 paths = list(ctx.walk(m)) |
304 paths = list(ctx.walk(m)) |