##// END OF EJS Templates
fastannotate: move some global state mutation to extsetup()...
Augie Fackler -
r39247:8da20fc9 default
parent child Browse files
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