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