Show More
@@ -1,193 +1,193 b'' | |||||
1 | # Copyright 2016-present Facebook. All Rights Reserved. |
|
1 | # Copyright 2016-present Facebook. All Rights Reserved. | |
2 | # |
|
2 | # | |
3 | # fastannotate: faster annotate implementation using linelog |
|
3 | # fastannotate: faster annotate implementation using linelog | |
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 | """yet another annotate implementation that might be faster (EXPERIMENTAL) |
|
7 | """yet another annotate implementation that might be faster (EXPERIMENTAL) | |
8 |
|
8 | |||
9 | The fastannotate extension provides a 'fastannotate' command that makes |
|
9 | The fastannotate extension provides a 'fastannotate' command that makes | |
10 | use of the linelog data structure as a cache layer and is expected to |
|
10 | use of the linelog data structure as a cache layer and is expected to | |
11 | be faster than the vanilla 'annotate' if the cache is present. |
|
11 | be faster than the vanilla 'annotate' if the cache is present. | |
12 |
|
12 | |||
13 | In most cases, fastannotate requires a setup that mainbranch is some pointer |
|
13 | In most cases, fastannotate requires a setup that mainbranch is some pointer | |
14 | that always moves forward, to be most efficient. |
|
14 | that always moves forward, to be most efficient. | |
15 |
|
15 | |||
16 | Using fastannotate together with linkrevcache would speed up building the |
|
16 | Using fastannotate together with linkrevcache would speed up building the | |
17 | annotate cache greatly. Run "debugbuildlinkrevcache" before |
|
17 | annotate cache greatly. Run "debugbuildlinkrevcache" before | |
18 | "debugbuildannotatecache". |
|
18 | "debugbuildannotatecache". | |
19 |
|
19 | |||
20 | :: |
|
20 | :: | |
21 |
|
21 | |||
22 | [fastannotate] |
|
22 | [fastannotate] | |
23 | # specify the main branch head. the internal linelog will only contain |
|
23 | # specify the main branch head. the internal linelog will only contain | |
24 | # the linear (ignoring p2) "mainbranch". since linelog cannot move |
|
24 | # the linear (ignoring p2) "mainbranch". since linelog cannot move | |
25 | # backwards without a rebuild, this should be something that always moves |
|
25 | # backwards without a rebuild, this should be something that always moves | |
26 | # forward, usually it is "master" or "@". |
|
26 | # forward, usually it is "master" or "@". | |
27 | mainbranch = master |
|
27 | mainbranch = master | |
28 |
|
28 | |||
29 | # fastannotate supports different modes to expose its feature. |
|
29 | # fastannotate supports different modes to expose its feature. | |
30 | # a list of combination: |
|
30 | # a list of combination: | |
31 | # - fastannotate: expose the feature via the "fastannotate" command which |
|
31 | # - fastannotate: expose the feature via the "fastannotate" command which | |
32 | # deals with everything in a most efficient way, and provides extra |
|
32 | # deals with everything in a most efficient way, and provides extra | |
33 | # features like --deleted etc. |
|
33 | # features like --deleted etc. | |
34 | # - fctx: replace fctx.annotate implementation. note: |
|
34 | # - fctx: replace fctx.annotate implementation. note: | |
35 | # a. it is less efficient than the "fastannotate" command |
|
35 | # a. it is less efficient than the "fastannotate" command | |
36 | # b. it will make it practically impossible to access the old (disk |
|
36 | # b. it will make it practically impossible to access the old (disk | |
37 | # side-effect free) annotate implementation |
|
37 | # side-effect free) annotate implementation | |
38 | # c. it implies "hgweb". |
|
38 | # c. it implies "hgweb". | |
39 | # - hgweb: replace hgweb's annotate implementation. conflict with "fctx". |
|
39 | # - hgweb: replace hgweb's annotate implementation. conflict with "fctx". | |
40 | # (default: fastannotate) |
|
40 | # (default: fastannotate) | |
41 | modes = fastannotate |
|
41 | modes = fastannotate | |
42 |
|
42 | |||
43 | # default format when no format flags are used (default: number) |
|
43 | # default format when no format flags are used (default: number) | |
44 | defaultformat = changeset, user, date |
|
44 | defaultformat = changeset, user, date | |
45 |
|
45 | |||
46 | # serve the annotate cache via wire protocol (default: False) |
|
46 | # serve the annotate cache via wire protocol (default: False) | |
47 | # tip: the .hg/fastannotate directory is portable - can be rsynced |
|
47 | # tip: the .hg/fastannotate directory is portable - can be rsynced | |
48 | server = True |
|
48 | server = True | |
49 |
|
49 | |||
50 | # build annotate cache on demand for every client request (default: True) |
|
50 | # build annotate cache on demand for every client request (default: True) | |
51 | # disabling it could make server response faster, useful when there is a |
|
51 | # disabling it could make server response faster, useful when there is a | |
52 | # cronjob building the cache. |
|
52 | # cronjob building the cache. | |
53 | serverbuildondemand = True |
|
53 | serverbuildondemand = True | |
54 |
|
54 | |||
55 | # update local annotate cache from remote on demand |
|
55 | # update local annotate cache from remote on demand | |
56 | client = False |
|
56 | client = False | |
57 |
|
57 | |||
58 | # path to use when connecting to the remote server (default: default) |
|
58 | # path to use when connecting to the remote server (default: default) | |
59 | remotepath = default |
|
59 | remotepath = default | |
60 |
|
60 | |||
61 | # minimal length of the history of a file required to fetch linelog from |
|
61 | # minimal length of the history of a file required to fetch linelog from | |
62 | # the server. (default: 10) |
|
62 | # the server. (default: 10) | |
63 | clientfetchthreshold = 10 |
|
63 | clientfetchthreshold = 10 | |
64 |
|
64 | |||
65 | # use flock instead of the file existence lock |
|
65 | # use flock instead of the file existence lock | |
66 | # flock may not work well on some network filesystems, but they avoid |
|
66 | # flock may not work well on some network filesystems, but they avoid | |
67 | # creating and deleting files frequently, which is faster when updating |
|
67 | # creating and deleting files frequently, which is faster when updating | |
68 | # the annotate cache in batch. if you have issues with this option, set it |
|
68 | # the annotate cache in batch. if you have issues with this option, set it | |
69 | # to False. (default: True if flock is supported, False otherwise) |
|
69 | # to False. (default: True if flock is supported, False otherwise) | |
70 | useflock = True |
|
70 | useflock = True | |
71 |
|
71 | |||
72 | # for "fctx" mode, always follow renames regardless of command line option. |
|
72 | # for "fctx" mode, always follow renames regardless of command line option. | |
73 | # this is a BC with the original command but will reduced the space needed |
|
73 | # this is a BC with the original command but will reduced the space needed | |
74 | # for annotate cache, and is useful for client-server setup since the |
|
74 | # for annotate cache, and is useful for client-server setup since the | |
75 | # server will only provide annotate cache with default options (i.e. with |
|
75 | # server will only provide annotate cache with default options (i.e. with | |
76 | # follow). do not affect "fastannotate" mode. (default: True) |
|
76 | # follow). do not affect "fastannotate" mode. (default: True) | |
77 | forcefollow = True |
|
77 | forcefollow = True | |
78 |
|
78 | |||
79 | # for "fctx" mode, always treat file as text files, to skip the "isbinary" |
|
79 | # for "fctx" mode, always treat file as text files, to skip the "isbinary" | |
80 | # check. this is consistent with the "fastannotate" command and could help |
|
80 | # check. this is consistent with the "fastannotate" command and could help | |
81 | # to avoid a file fetch if remotefilelog is used. (default: True) |
|
81 | # to avoid a file fetch if remotefilelog is used. (default: True) | |
82 | forcetext = True |
|
82 | forcetext = True | |
83 |
|
83 | |||
84 | # use unfiltered repo for better performance. |
|
84 | # use unfiltered repo for better performance. | |
85 | unfilteredrepo = True |
|
85 | unfilteredrepo = True | |
86 |
|
86 | |||
87 | # sacrifice correctness in some corner cases for performance. it does not |
|
87 | # sacrifice correctness in some corner cases for performance. it does not | |
88 | # affect the correctness of the annotate cache being built. the option |
|
88 | # affect the correctness of the annotate cache being built. the option | |
89 | # is experimental and may disappear in the future (default: False) |
|
89 | # is experimental and may disappear in the future (default: False) | |
90 | perfhack = True |
|
90 | perfhack = True | |
91 | """ |
|
91 | """ | |
92 |
|
92 | |||
93 | # TODO from import: |
|
93 | # TODO from import: | |
94 | # * `branch` is probably the wrong term, throughout the code. |
|
94 | # * `branch` is probably the wrong term, throughout the code. | |
95 | # |
|
95 | # | |
96 | # * replace the fastannotate `modes` configuration with a collection |
|
96 | # * replace the fastannotate `modes` configuration with a collection | |
97 | # of booleans. |
|
97 | # of booleans. | |
98 | # |
|
98 | # | |
99 | # * Use the templater instead of bespoke formatting |
|
99 | # * Use the templater instead of bespoke formatting | |
100 | # |
|
100 | # | |
101 | # * rename the config knob for updating the local cache from a remote server |
|
101 | # * rename the config knob for updating the local cache from a remote server | |
102 | # |
|
102 | # | |
103 | # * move various global-setup bits to extsetup() or reposetup() |
|
|||
104 | # |
|
|||
105 | # * move `flock` based locking to a common area |
|
103 | # * move `flock` based locking to a common area | |
106 | # |
|
104 | # | |
107 | # * revise wireprotocol for sharing annotate files |
|
105 | # * revise wireprotocol for sharing annotate files | |
108 | # |
|
106 | # | |
109 | # * figure out a sensible default for `mainbranch` (with the caveat |
|
107 | # * figure out a sensible default for `mainbranch` (with the caveat | |
110 | # that we probably also want to figure out a better term than |
|
108 | # that we probably also want to figure out a better term than | |
111 | # `branch`, see above) |
|
109 | # `branch`, see above) | |
112 | # |
|
110 | # | |
113 | # * format changes to the revmap file (maybe use length-encoding |
|
111 | # * format changes to the revmap file (maybe use length-encoding | |
114 | # instead of null-terminated file paths at least?) |
|
112 | # instead of null-terminated file paths at least?) | |
115 | from __future__ import absolute_import |
|
113 | from __future__ import absolute_import | |
116 |
|
114 | |||
117 | from mercurial.i18n import _ |
|
115 | from mercurial.i18n import _ | |
118 | from mercurial import ( |
|
116 | from mercurial import ( | |
119 | error as hgerror, |
|
117 | error as hgerror, | |
120 | localrepo, |
|
118 | localrepo, | |
121 | registrar, |
|
119 | registrar, | |
122 | util, |
|
120 | util, | |
123 | ) |
|
121 | ) | |
124 |
|
122 | |||
125 | from . import ( |
|
123 | from . import ( | |
126 | commands, |
|
124 | commands, | |
127 | context, |
|
125 | context, | |
128 | protocol, |
|
126 | protocol, | |
129 | ) |
|
127 | ) | |
130 |
|
128 | |||
131 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
|
129 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | |
132 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
|
130 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | |
133 | # be specifying the version(s) of Mercurial they are tested with, or |
|
131 | # be specifying the version(s) of Mercurial they are tested with, or | |
134 | # leave the attribute unspecified. |
|
132 | # leave the attribute unspecified. | |
135 | testedwith = 'ships-with-hg-core' |
|
133 | testedwith = 'ships-with-hg-core' | |
136 |
|
134 | |||
137 | cmdtable = commands.cmdtable |
|
135 | cmdtable = commands.cmdtable | |
138 |
|
136 | |||
139 | configtable = {} |
|
137 | configtable = {} | |
140 | configitem = registrar.configitem(configtable) |
|
138 | configitem = registrar.configitem(configtable) | |
141 |
|
139 | |||
142 | configitem('fastannotate', 'modes', default=['fastannotate']) |
|
140 | configitem('fastannotate', 'modes', default=['fastannotate']) | |
143 | configitem('fastannotate', 'server', default=False) |
|
141 | configitem('fastannotate', 'server', default=False) | |
144 | configitem('fastannotate', 'useflock', default=True) |
|
142 | configitem('fastannotate', 'useflock', default=True) | |
145 | configitem('fastannotate', 'client', default=False) |
|
143 | configitem('fastannotate', 'client', default=False) | |
146 | configitem('fastannotate', 'unfilteredrepo', default=True) |
|
144 | configitem('fastannotate', 'unfilteredrepo', default=True) | |
147 | configitem('fastannotate', 'defaultformat', default=['number']) |
|
145 | configitem('fastannotate', 'defaultformat', default=['number']) | |
148 | configitem('fastannotate', 'perfhack', default=False) |
|
146 | configitem('fastannotate', 'perfhack', default=False) | |
149 | configitem('fastannotate', 'mainbranch') |
|
147 | configitem('fastannotate', 'mainbranch') | |
150 | configitem('fastannotate', 'forcetext', default=True) |
|
148 | configitem('fastannotate', 'forcetext', default=True) | |
151 | configitem('fastannotate', 'forcefollow', default=True) |
|
149 | configitem('fastannotate', 'forcefollow', default=True) | |
152 | configitem('fastannotate', 'clientfetchthreshold', default=10) |
|
150 | configitem('fastannotate', 'clientfetchthreshold', default=10) | |
153 | configitem('fastannotate', 'serverbuildondemand', default=True) |
|
151 | configitem('fastannotate', 'serverbuildondemand', default=True) | |
154 | configitem('fastannotate', 'remotepath', default='default') |
|
152 | configitem('fastannotate', 'remotepath', default='default') | |
155 |
|
153 | |||
156 | def _flockavailable(): |
|
154 | def _flockavailable(): | |
157 | try: |
|
155 | try: | |
158 | import fcntl |
|
156 | import fcntl | |
159 | fcntl.flock |
|
157 | fcntl.flock | |
160 | except StandardError: |
|
158 | except StandardError: | |
161 | return False |
|
159 | return False | |
162 | else: |
|
160 | else: | |
163 | return True |
|
161 | return True | |
164 |
|
162 | |||
165 | def uisetup(ui): |
|
163 | def uisetup(ui): | |
166 | modes = set(ui.configlist('fastannotate', 'modes')) |
|
164 | modes = set(ui.configlist('fastannotate', 'modes')) | |
167 | if 'fctx' in modes: |
|
165 | if 'fctx' in modes: | |
168 | modes.discard('hgweb') |
|
166 | modes.discard('hgweb') | |
169 | for name in modes: |
|
167 | for name in modes: | |
170 | if name == 'fastannotate': |
|
168 | if name == 'fastannotate': | |
171 | commands.registercommand() |
|
169 | commands.registercommand() | |
172 | elif name == 'hgweb': |
|
170 | elif name == 'hgweb': | |
173 | from . import support |
|
171 | from . import support | |
174 | support.replacehgwebannotate() |
|
172 | support.replacehgwebannotate() | |
175 | elif name == 'fctx': |
|
173 | elif name == 'fctx': | |
176 | from . import support |
|
174 | from . import support | |
177 | support.replacefctxannotate() |
|
175 | support.replacefctxannotate() | |
178 | commands.wrapdefault() |
|
176 | commands.wrapdefault() | |
179 | else: |
|
177 | else: | |
180 | raise hgerror.Abort(_('fastannotate: invalid mode: %s') % name) |
|
178 | raise hgerror.Abort(_('fastannotate: invalid mode: %s') % name) | |
181 |
|
179 | |||
182 | if ui.configbool('fastannotate', 'server'): |
|
180 | if ui.configbool('fastannotate', 'server'): | |
183 | protocol.serveruisetup(ui) |
|
181 | protocol.serveruisetup(ui) | |
184 |
|
182 | |||
185 | if ui.configbool('fastannotate', 'useflock', _flockavailable()): |
|
183 | if ui.configbool('fastannotate', 'useflock', _flockavailable()): | |
186 | context.pathhelper.lock = context.pathhelper._lockflock |
|
184 | context.pathhelper.lock = context.pathhelper._lockflock | |
187 |
|
185 | |||
|
186 | def extsetup(ui): | |||
188 | # fastannotate has its own locking, without depending on repo lock |
|
187 | # fastannotate has its own locking, without depending on repo lock | |
|
188 | # TODO: avoid mutating this unless the specific repo has it enabled | |||
189 | localrepo.localrepository._wlockfreeprefix.add('fastannotate/') |
|
189 | localrepo.localrepository._wlockfreeprefix.add('fastannotate/') | |
190 |
|
190 | |||
191 | def reposetup(ui, repo): |
|
191 | def reposetup(ui, repo): | |
192 | if ui.configbool('fastannotate', 'client'): |
|
192 | if ui.configbool('fastannotate', 'client'): | |
193 | protocol.clientreposetup(ui, repo) |
|
193 | protocol.clientreposetup(ui, repo) |
@@ -1,227 +1,229 b'' | |||||
1 | # Copyright 2016-present Facebook. All Rights Reserved. |
|
1 | # Copyright 2016-present Facebook. All Rights Reserved. | |
2 | # |
|
2 | # | |
3 | # protocol: logic for a server providing fastannotate support |
|
3 | # protocol: logic for a server providing fastannotate support | |
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 | from __future__ import absolute_import |
|
7 | from __future__ import absolute_import | |
8 |
|
8 | |||
9 | import contextlib |
|
9 | import contextlib | |
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 | error, |
|
14 | error, | |
15 | extensions, |
|
15 | extensions, | |
16 | hg, |
|
16 | hg, | |
17 | localrepo, |
|
17 | localrepo, | |
18 | scmutil, |
|
18 | scmutil, | |
19 | wireprotov1peer, |
|
19 | wireprotov1peer, | |
20 | wireprotov1server, |
|
20 | wireprotov1server, | |
21 | ) |
|
21 | ) | |
22 | from . import context |
|
22 | from . import context | |
23 |
|
23 | |||
24 | # common |
|
24 | # common | |
25 |
|
25 | |||
26 | def _getmaster(ui): |
|
26 | def _getmaster(ui): | |
27 | """get the mainbranch, and enforce it is set""" |
|
27 | """get the mainbranch, and enforce it is set""" | |
28 | master = ui.config('fastannotate', 'mainbranch') |
|
28 | master = ui.config('fastannotate', 'mainbranch') | |
29 | if not master: |
|
29 | if not master: | |
30 | raise error.Abort(_('fastannotate.mainbranch is required ' |
|
30 | raise error.Abort(_('fastannotate.mainbranch is required ' | |
31 | 'for both the client and the server')) |
|
31 | 'for both the client and the server')) | |
32 | return master |
|
32 | return master | |
33 |
|
33 | |||
34 | # server-side |
|
34 | # server-side | |
35 |
|
35 | |||
36 | def _capabilities(orig, repo, proto): |
|
36 | def _capabilities(orig, repo, proto): | |
37 | result = orig(repo, proto) |
|
37 | result = orig(repo, proto) | |
38 | result.append('getannotate') |
|
38 | result.append('getannotate') | |
39 | return result |
|
39 | return result | |
40 |
|
40 | |||
41 | def _getannotate(repo, proto, path, lastnode): |
|
41 | def _getannotate(repo, proto, path, lastnode): | |
42 | # output: |
|
42 | # output: | |
43 | # FILE := vfspath + '\0' + str(size) + '\0' + content |
|
43 | # FILE := vfspath + '\0' + str(size) + '\0' + content | |
44 | # OUTPUT := '' | FILE + OUTPUT |
|
44 | # OUTPUT := '' | FILE + OUTPUT | |
45 | result = '' |
|
45 | result = '' | |
46 | buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand', |
|
46 | buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand', | |
47 | True) |
|
47 | True) | |
48 | with context.annotatecontext(repo, path) as actx: |
|
48 | with context.annotatecontext(repo, path) as actx: | |
49 | if buildondemand: |
|
49 | if buildondemand: | |
50 | # update before responding to the client |
|
50 | # update before responding to the client | |
51 | master = _getmaster(repo.ui) |
|
51 | master = _getmaster(repo.ui) | |
52 | try: |
|
52 | try: | |
53 | if not actx.isuptodate(master): |
|
53 | if not actx.isuptodate(master): | |
54 | actx.annotate(master, master) |
|
54 | actx.annotate(master, master) | |
55 | except Exception: |
|
55 | except Exception: | |
56 | # non-fast-forward move or corrupted. rebuild automically. |
|
56 | # non-fast-forward move or corrupted. rebuild automically. | |
57 | actx.rebuild() |
|
57 | actx.rebuild() | |
58 | try: |
|
58 | try: | |
59 | actx.annotate(master, master) |
|
59 | actx.annotate(master, master) | |
60 | except Exception: |
|
60 | except Exception: | |
61 | actx.rebuild() # delete files |
|
61 | actx.rebuild() # delete files | |
62 | finally: |
|
62 | finally: | |
63 | # although the "with" context will also do a close/flush, we |
|
63 | # although the "with" context will also do a close/flush, we | |
64 | # need to do it early so we can send the correct respond to |
|
64 | # need to do it early so we can send the correct respond to | |
65 | # client. |
|
65 | # client. | |
66 | actx.close() |
|
66 | actx.close() | |
67 | # send back the full content of revmap and linelog, in the future we |
|
67 | # send back the full content of revmap and linelog, in the future we | |
68 | # may want to do some rsync-like fancy updating. |
|
68 | # may want to do some rsync-like fancy updating. | |
69 | # the lastnode check is not necessary if the client and the server |
|
69 | # the lastnode check is not necessary if the client and the server | |
70 | # agree where the main branch is. |
|
70 | # agree where the main branch is. | |
71 | if actx.lastnode != lastnode: |
|
71 | if actx.lastnode != lastnode: | |
72 | for p in [actx.revmappath, actx.linelogpath]: |
|
72 | for p in [actx.revmappath, actx.linelogpath]: | |
73 | if not os.path.exists(p): |
|
73 | if not os.path.exists(p): | |
74 | continue |
|
74 | continue | |
75 | content = '' |
|
75 | content = '' | |
76 | with open(p, 'rb') as f: |
|
76 | with open(p, 'rb') as f: | |
77 | content = f.read() |
|
77 | content = f.read() | |
78 | vfsbaselen = len(repo.vfs.base + '/') |
|
78 | vfsbaselen = len(repo.vfs.base + '/') | |
79 | relpath = p[vfsbaselen:] |
|
79 | relpath = p[vfsbaselen:] | |
80 | result += '%s\0%s\0%s' % (relpath, len(content), content) |
|
80 | result += '%s\0%s\0%s' % (relpath, len(content), content) | |
81 | return result |
|
81 | return result | |
82 |
|
82 | |||
83 | def _registerwireprotocommand(): |
|
83 | def _registerwireprotocommand(): | |
84 | if 'getannotate' in wireprotov1server.commands: |
|
84 | if 'getannotate' in wireprotov1server.commands: | |
85 | return |
|
85 | return | |
86 | wireprotov1server.wireprotocommand( |
|
86 | wireprotov1server.wireprotocommand( | |
87 | 'getannotate', 'path lastnode')(_getannotate) |
|
87 | 'getannotate', 'path lastnode')(_getannotate) | |
88 |
|
88 | |||
89 | def serveruisetup(ui): |
|
89 | def serveruisetup(ui): | |
90 | _registerwireprotocommand() |
|
90 | _registerwireprotocommand() | |
91 | extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities) |
|
91 | extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities) | |
92 |
|
92 | |||
93 | # client-side |
|
93 | # client-side | |
94 |
|
94 | |||
95 | def _parseresponse(payload): |
|
95 | def _parseresponse(payload): | |
96 | result = {} |
|
96 | result = {} | |
97 | i = 0 |
|
97 | i = 0 | |
98 | l = len(payload) - 1 |
|
98 | l = len(payload) - 1 | |
99 | state = 0 # 0: vfspath, 1: size |
|
99 | state = 0 # 0: vfspath, 1: size | |
100 | vfspath = size = '' |
|
100 | vfspath = size = '' | |
101 | while i < l: |
|
101 | while i < l: | |
102 | ch = payload[i] |
|
102 | ch = payload[i] | |
103 | if ch == '\0': |
|
103 | if ch == '\0': | |
104 | if state == 1: |
|
104 | if state == 1: | |
105 | result[vfspath] = buffer(payload, i + 1, int(size)) |
|
105 | result[vfspath] = buffer(payload, i + 1, int(size)) | |
106 | i += int(size) |
|
106 | i += int(size) | |
107 | state = 0 |
|
107 | state = 0 | |
108 | vfspath = size = '' |
|
108 | vfspath = size = '' | |
109 | elif state == 0: |
|
109 | elif state == 0: | |
110 | state = 1 |
|
110 | state = 1 | |
111 | else: |
|
111 | else: | |
112 | if state == 1: |
|
112 | if state == 1: | |
113 | size += ch |
|
113 | size += ch | |
114 | elif state == 0: |
|
114 | elif state == 0: | |
115 | vfspath += ch |
|
115 | vfspath += ch | |
116 | i += 1 |
|
116 | i += 1 | |
117 | return result |
|
117 | return result | |
118 |
|
118 | |||
119 | def peersetup(ui, peer): |
|
119 | def peersetup(ui, peer): | |
120 | class fastannotatepeer(peer.__class__): |
|
120 | class fastannotatepeer(peer.__class__): | |
121 | @wireprotov1peer.batchable |
|
121 | @wireprotov1peer.batchable | |
122 | def getannotate(self, path, lastnode=None): |
|
122 | def getannotate(self, path, lastnode=None): | |
123 | if not self.capable('getannotate'): |
|
123 | if not self.capable('getannotate'): | |
124 | ui.warn(_('remote peer cannot provide annotate cache\n')) |
|
124 | ui.warn(_('remote peer cannot provide annotate cache\n')) | |
125 | yield None, None |
|
125 | yield None, None | |
126 | else: |
|
126 | else: | |
127 | args = {'path': path, 'lastnode': lastnode or ''} |
|
127 | args = {'path': path, 'lastnode': lastnode or ''} | |
128 | f = wireprotov1peer.future() |
|
128 | f = wireprotov1peer.future() | |
129 | yield args, f |
|
129 | yield args, f | |
130 | yield _parseresponse(f.value) |
|
130 | yield _parseresponse(f.value) | |
131 | peer.__class__ = fastannotatepeer |
|
131 | peer.__class__ = fastannotatepeer | |
132 |
|
132 | |||
133 | @contextlib.contextmanager |
|
133 | @contextlib.contextmanager | |
134 | def annotatepeer(repo): |
|
134 | def annotatepeer(repo): | |
135 | ui = repo.ui |
|
135 | ui = repo.ui | |
136 |
|
136 | |||
137 | remotepath = ui.expandpath( |
|
137 | remotepath = ui.expandpath( | |
138 | ui.config('fastannotate', 'remotepath', 'default')) |
|
138 | ui.config('fastannotate', 'remotepath', 'default')) | |
139 | peer = hg.peer(ui, {}, remotepath) |
|
139 | peer = hg.peer(ui, {}, remotepath) | |
140 |
|
140 | |||
141 | try: |
|
141 | try: | |
142 | yield peer |
|
142 | yield peer | |
143 | finally: |
|
143 | finally: | |
144 | peer.close() |
|
144 | peer.close() | |
145 |
|
145 | |||
146 | def clientfetch(repo, paths, lastnodemap=None, peer=None): |
|
146 | def clientfetch(repo, paths, lastnodemap=None, peer=None): | |
147 | """download annotate cache from the server for paths""" |
|
147 | """download annotate cache from the server for paths""" | |
148 | if not paths: |
|
148 | if not paths: | |
149 | return |
|
149 | return | |
150 |
|
150 | |||
151 | if peer is None: |
|
151 | if peer is None: | |
152 | with annotatepeer(repo) as peer: |
|
152 | with annotatepeer(repo) as peer: | |
153 | return clientfetch(repo, paths, lastnodemap, peer) |
|
153 | return clientfetch(repo, paths, lastnodemap, peer) | |
154 |
|
154 | |||
155 | if lastnodemap is None: |
|
155 | if lastnodemap is None: | |
156 | lastnodemap = {} |
|
156 | lastnodemap = {} | |
157 |
|
157 | |||
158 | ui = repo.ui |
|
158 | ui = repo.ui | |
159 | results = [] |
|
159 | results = [] | |
160 | with peer.commandexecutor() as batcher: |
|
160 | with peer.commandexecutor() as batcher: | |
161 | ui.debug('fastannotate: requesting %d files\n' % len(paths)) |
|
161 | ui.debug('fastannotate: requesting %d files\n' % len(paths)) | |
162 | for p in paths: |
|
162 | for p in paths: | |
163 | results.append(batcher.callcommand( |
|
163 | results.append(batcher.callcommand( | |
164 | 'getannotate', |
|
164 | 'getannotate', | |
165 | {'path': p, 'lastnode':lastnodemap.get(p)})) |
|
165 | {'path': p, 'lastnode':lastnodemap.get(p)})) | |
166 |
|
166 | |||
167 | ui.debug('fastannotate: server returned\n') |
|
167 | ui.debug('fastannotate: server returned\n') | |
168 | for result in results: |
|
168 | for result in results: | |
169 | r = result.result() |
|
169 | r = result.result() | |
170 | for path in sorted(r): |
|
170 | for path in sorted(r): | |
171 | # ignore malicious paths |
|
171 | # ignore malicious paths | |
172 | if not path.startswith('fastannotate/') or '/../' in (path + '/'): |
|
172 | if not path.startswith('fastannotate/') or '/../' in (path + '/'): | |
173 | ui.debug('fastannotate: ignored malicious path %s\n' % path) |
|
173 | ui.debug('fastannotate: ignored malicious path %s\n' % path) | |
174 | continue |
|
174 | continue | |
175 | content = r[path] |
|
175 | content = r[path] | |
176 | if ui.debugflag: |
|
176 | if ui.debugflag: | |
177 | ui.debug('fastannotate: writing %d bytes to %s\n' |
|
177 | ui.debug('fastannotate: writing %d bytes to %s\n' | |
178 | % (len(content), path)) |
|
178 | % (len(content), path)) | |
179 | repo.vfs.makedirs(os.path.dirname(path)) |
|
179 | repo.vfs.makedirs(os.path.dirname(path)) | |
180 | with repo.vfs(path, 'wb') as f: |
|
180 | with repo.vfs(path, 'wb') as f: | |
181 | f.write(content) |
|
181 | f.write(content) | |
182 |
|
182 | |||
183 | def _filterfetchpaths(repo, paths): |
|
183 | def _filterfetchpaths(repo, paths): | |
184 | """return a subset of paths whose history is long and need to fetch linelog |
|
184 | """return a subset of paths whose history is long and need to fetch linelog | |
185 | from the server. works with remotefilelog and non-remotefilelog repos. |
|
185 | from the server. works with remotefilelog and non-remotefilelog repos. | |
186 | """ |
|
186 | """ | |
187 | threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10) |
|
187 | threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10) | |
188 | if threshold <= 0: |
|
188 | if threshold <= 0: | |
189 | return paths |
|
189 | return paths | |
190 |
|
190 | |||
191 | master = repo.ui.config('fastannotate', 'mainbranch') or 'default' |
|
191 | master = repo.ui.config('fastannotate', 'mainbranch') or 'default' | |
192 |
|
192 | |||
193 | result = [] |
|
193 | result = [] | |
194 | for path in paths: |
|
194 | for path in paths: | |
195 | try: |
|
195 | try: | |
196 | if len(repo.file(path)) >= threshold: |
|
196 | if len(repo.file(path)) >= threshold: | |
197 | result.append(path) |
|
197 | result.append(path) | |
198 | except Exception: # file not found etc. |
|
198 | except Exception: # file not found etc. | |
199 | result.append(path) |
|
199 | result.append(path) | |
200 |
|
200 | |||
201 | return result |
|
201 | return result | |
202 |
|
202 | |||
203 | def localreposetup(ui, repo): |
|
203 | def localreposetup(ui, repo): | |
204 | class fastannotaterepo(repo.__class__): |
|
204 | class fastannotaterepo(repo.__class__): | |
205 | def prefetchfastannotate(self, paths, peer=None): |
|
205 | def prefetchfastannotate(self, paths, peer=None): | |
206 | master = _getmaster(self.ui) |
|
206 | master = _getmaster(self.ui) | |
207 | needupdatepaths = [] |
|
207 | needupdatepaths = [] | |
208 | lastnodemap = {} |
|
208 | lastnodemap = {} | |
209 | try: |
|
209 | try: | |
210 | for path in _filterfetchpaths(self, paths): |
|
210 | for path in _filterfetchpaths(self, paths): | |
211 | with context.annotatecontext(self, path) as actx: |
|
211 | with context.annotatecontext(self, path) as actx: | |
212 | if not actx.isuptodate(master, strict=False): |
|
212 | if not actx.isuptodate(master, strict=False): | |
213 | needupdatepaths.append(path) |
|
213 | needupdatepaths.append(path) | |
214 | lastnodemap[path] = actx.lastnode |
|
214 | lastnodemap[path] = actx.lastnode | |
215 | if needupdatepaths: |
|
215 | if needupdatepaths: | |
216 | clientfetch(self, needupdatepaths, lastnodemap, peer) |
|
216 | clientfetch(self, needupdatepaths, lastnodemap, peer) | |
217 | except Exception as ex: |
|
217 | except Exception as ex: | |
218 | # could be directory not writable or so, not fatal |
|
218 | # could be directory not writable or so, not fatal | |
219 | self.ui.debug('fastannotate: prefetch failed: %r\n' % ex) |
|
219 | self.ui.debug('fastannotate: prefetch failed: %r\n' % ex) | |
220 | repo.__class__ = fastannotaterepo |
|
220 | repo.__class__ = fastannotaterepo | |
221 |
|
221 | |||
222 | def clientreposetup(ui, repo): |
|
222 | def clientreposetup(ui, repo): | |
223 | _registerwireprotocommand() |
|
223 | _registerwireprotocommand() | |
224 | if isinstance(repo, localrepo.localrepository): |
|
224 | if isinstance(repo, localrepo.localrepository): | |
225 | localreposetup(ui, repo) |
|
225 | localreposetup(ui, repo) | |
|
226 | # TODO: this mutates global state, but only if at least one repo | |||
|
227 | # has the extension enabled. This is probably bad for hgweb. | |||
226 | if peersetup not in hg.wirepeersetupfuncs: |
|
228 | if peersetup not in hg.wirepeersetupfuncs: | |
227 | hg.wirepeersetupfuncs.append(peersetup) |
|
229 | hg.wirepeersetupfuncs.append(peersetup) |
General Comments 0
You need to be logged in to leave comments.
Login now