##// END OF EJS Templates
fastannotate: address pyflakes concerns...
Augie Fackler -
r39248:eaa14dff default
parent child Browse files
Show More
@@ -1,193 +1,192
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 `flock` based locking to a common area
103 # * move `flock` based locking to a common area
104 #
104 #
105 # * revise wireprotocol for sharing annotate files
105 # * revise wireprotocol for sharing annotate files
106 #
106 #
107 # * figure out a sensible default for `mainbranch` (with the caveat
107 # * figure out a sensible default for `mainbranch` (with the caveat
108 # 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
109 # `branch`, see above)
109 # `branch`, see above)
110 #
110 #
111 # * format changes to the revmap file (maybe use length-encoding
111 # * format changes to the revmap file (maybe use length-encoding
112 # instead of null-terminated file paths at least?)
112 # instead of null-terminated file paths at least?)
113 from __future__ import absolute_import
113 from __future__ import absolute_import
114
114
115 from mercurial.i18n import _
115 from mercurial.i18n import _
116 from mercurial import (
116 from mercurial import (
117 error as hgerror,
117 error as hgerror,
118 localrepo,
118 localrepo,
119 registrar,
119 registrar,
120 util,
121 )
120 )
122
121
123 from . import (
122 from . import (
124 commands,
123 commands,
125 context,
124 context,
126 protocol,
125 protocol,
127 )
126 )
128
127
129 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
128 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
130 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
129 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
131 # be specifying the version(s) of Mercurial they are tested with, or
130 # be specifying the version(s) of Mercurial they are tested with, or
132 # leave the attribute unspecified.
131 # leave the attribute unspecified.
133 testedwith = 'ships-with-hg-core'
132 testedwith = 'ships-with-hg-core'
134
133
135 cmdtable = commands.cmdtable
134 cmdtable = commands.cmdtable
136
135
137 configtable = {}
136 configtable = {}
138 configitem = registrar.configitem(configtable)
137 configitem = registrar.configitem(configtable)
139
138
140 configitem('fastannotate', 'modes', default=['fastannotate'])
139 configitem('fastannotate', 'modes', default=['fastannotate'])
141 configitem('fastannotate', 'server', default=False)
140 configitem('fastannotate', 'server', default=False)
142 configitem('fastannotate', 'useflock', default=True)
141 configitem('fastannotate', 'useflock', default=True)
143 configitem('fastannotate', 'client', default=False)
142 configitem('fastannotate', 'client', default=False)
144 configitem('fastannotate', 'unfilteredrepo', default=True)
143 configitem('fastannotate', 'unfilteredrepo', default=True)
145 configitem('fastannotate', 'defaultformat', default=['number'])
144 configitem('fastannotate', 'defaultformat', default=['number'])
146 configitem('fastannotate', 'perfhack', default=False)
145 configitem('fastannotate', 'perfhack', default=False)
147 configitem('fastannotate', 'mainbranch')
146 configitem('fastannotate', 'mainbranch')
148 configitem('fastannotate', 'forcetext', default=True)
147 configitem('fastannotate', 'forcetext', default=True)
149 configitem('fastannotate', 'forcefollow', default=True)
148 configitem('fastannotate', 'forcefollow', default=True)
150 configitem('fastannotate', 'clientfetchthreshold', default=10)
149 configitem('fastannotate', 'clientfetchthreshold', default=10)
151 configitem('fastannotate', 'serverbuildondemand', default=True)
150 configitem('fastannotate', 'serverbuildondemand', default=True)
152 configitem('fastannotate', 'remotepath', default='default')
151 configitem('fastannotate', 'remotepath', default='default')
153
152
154 def _flockavailable():
153 def _flockavailable():
155 try:
154 try:
156 import fcntl
155 import fcntl
157 fcntl.flock
156 fcntl.flock
158 except StandardError:
157 except StandardError:
159 return False
158 return False
160 else:
159 else:
161 return True
160 return True
162
161
163 def uisetup(ui):
162 def uisetup(ui):
164 modes = set(ui.configlist('fastannotate', 'modes'))
163 modes = set(ui.configlist('fastannotate', 'modes'))
165 if 'fctx' in modes:
164 if 'fctx' in modes:
166 modes.discard('hgweb')
165 modes.discard('hgweb')
167 for name in modes:
166 for name in modes:
168 if name == 'fastannotate':
167 if name == 'fastannotate':
169 commands.registercommand()
168 commands.registercommand()
170 elif name == 'hgweb':
169 elif name == 'hgweb':
171 from . import support
170 from . import support
172 support.replacehgwebannotate()
171 support.replacehgwebannotate()
173 elif name == 'fctx':
172 elif name == 'fctx':
174 from . import support
173 from . import support
175 support.replacefctxannotate()
174 support.replacefctxannotate()
176 commands.wrapdefault()
175 commands.wrapdefault()
177 else:
176 else:
178 raise hgerror.Abort(_('fastannotate: invalid mode: %s') % name)
177 raise hgerror.Abort(_('fastannotate: invalid mode: %s') % name)
179
178
180 if ui.configbool('fastannotate', 'server'):
179 if ui.configbool('fastannotate', 'server'):
181 protocol.serveruisetup(ui)
180 protocol.serveruisetup(ui)
182
181
183 if ui.configbool('fastannotate', 'useflock', _flockavailable()):
182 if ui.configbool('fastannotate', 'useflock', _flockavailable()):
184 context.pathhelper.lock = context.pathhelper._lockflock
183 context.pathhelper.lock = context.pathhelper._lockflock
185
184
186 def extsetup(ui):
185 def extsetup(ui):
187 # fastannotate has its own locking, without depending on repo lock
186 # fastannotate has its own locking, without depending on repo lock
188 # TODO: avoid mutating this unless the specific repo has it enabled
187 # TODO: avoid mutating this unless the specific repo has it enabled
189 localrepo.localrepository._wlockfreeprefix.add('fastannotate/')
188 localrepo.localrepository._wlockfreeprefix.add('fastannotate/')
190
189
191 def reposetup(ui, repo):
190 def reposetup(ui, repo):
192 if ui.configbool('fastannotate', 'client'):
191 if ui.configbool('fastannotate', 'client'):
193 protocol.clientreposetup(ui, repo)
192 protocol.clientreposetup(ui, repo)
@@ -1,229 +1,226
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,
19 wireprotov1peer,
18 wireprotov1peer,
20 wireprotov1server,
19 wireprotov1server,
21 )
20 )
22 from . import context
21 from . import context
23
22
24 # common
23 # common
25
24
26 def _getmaster(ui):
25 def _getmaster(ui):
27 """get the mainbranch, and enforce it is set"""
26 """get the mainbranch, and enforce it is set"""
28 master = ui.config('fastannotate', 'mainbranch')
27 master = ui.config('fastannotate', 'mainbranch')
29 if not master:
28 if not master:
30 raise error.Abort(_('fastannotate.mainbranch is required '
29 raise error.Abort(_('fastannotate.mainbranch is required '
31 'for both the client and the server'))
30 'for both the client and the server'))
32 return master
31 return master
33
32
34 # server-side
33 # server-side
35
34
36 def _capabilities(orig, repo, proto):
35 def _capabilities(orig, repo, proto):
37 result = orig(repo, proto)
36 result = orig(repo, proto)
38 result.append('getannotate')
37 result.append('getannotate')
39 return result
38 return result
40
39
41 def _getannotate(repo, proto, path, lastnode):
40 def _getannotate(repo, proto, path, lastnode):
42 # output:
41 # output:
43 # FILE := vfspath + '\0' + str(size) + '\0' + content
42 # FILE := vfspath + '\0' + str(size) + '\0' + content
44 # OUTPUT := '' | FILE + OUTPUT
43 # OUTPUT := '' | FILE + OUTPUT
45 result = ''
44 result = ''
46 buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand',
45 buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand',
47 True)
46 True)
48 with context.annotatecontext(repo, path) as actx:
47 with context.annotatecontext(repo, path) as actx:
49 if buildondemand:
48 if buildondemand:
50 # update before responding to the client
49 # update before responding to the client
51 master = _getmaster(repo.ui)
50 master = _getmaster(repo.ui)
52 try:
51 try:
53 if not actx.isuptodate(master):
52 if not actx.isuptodate(master):
54 actx.annotate(master, master)
53 actx.annotate(master, master)
55 except Exception:
54 except Exception:
56 # non-fast-forward move or corrupted. rebuild automically.
55 # non-fast-forward move or corrupted. rebuild automically.
57 actx.rebuild()
56 actx.rebuild()
58 try:
57 try:
59 actx.annotate(master, master)
58 actx.annotate(master, master)
60 except Exception:
59 except Exception:
61 actx.rebuild() # delete files
60 actx.rebuild() # delete files
62 finally:
61 finally:
63 # although the "with" context will also do a close/flush, we
62 # 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
63 # need to do it early so we can send the correct respond to
65 # client.
64 # client.
66 actx.close()
65 actx.close()
67 # send back the full content of revmap and linelog, in the future we
66 # send back the full content of revmap and linelog, in the future we
68 # may want to do some rsync-like fancy updating.
67 # may want to do some rsync-like fancy updating.
69 # the lastnode check is not necessary if the client and the server
68 # the lastnode check is not necessary if the client and the server
70 # agree where the main branch is.
69 # agree where the main branch is.
71 if actx.lastnode != lastnode:
70 if actx.lastnode != lastnode:
72 for p in [actx.revmappath, actx.linelogpath]:
71 for p in [actx.revmappath, actx.linelogpath]:
73 if not os.path.exists(p):
72 if not os.path.exists(p):
74 continue
73 continue
75 content = ''
74 content = ''
76 with open(p, 'rb') as f:
75 with open(p, 'rb') as f:
77 content = f.read()
76 content = f.read()
78 vfsbaselen = len(repo.vfs.base + '/')
77 vfsbaselen = len(repo.vfs.base + '/')
79 relpath = p[vfsbaselen:]
78 relpath = p[vfsbaselen:]
80 result += '%s\0%s\0%s' % (relpath, len(content), content)
79 result += '%s\0%s\0%s' % (relpath, len(content), content)
81 return result
80 return result
82
81
83 def _registerwireprotocommand():
82 def _registerwireprotocommand():
84 if 'getannotate' in wireprotov1server.commands:
83 if 'getannotate' in wireprotov1server.commands:
85 return
84 return
86 wireprotov1server.wireprotocommand(
85 wireprotov1server.wireprotocommand(
87 'getannotate', 'path lastnode')(_getannotate)
86 'getannotate', 'path lastnode')(_getannotate)
88
87
89 def serveruisetup(ui):
88 def serveruisetup(ui):
90 _registerwireprotocommand()
89 _registerwireprotocommand()
91 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
90 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
92
91
93 # client-side
92 # client-side
94
93
95 def _parseresponse(payload):
94 def _parseresponse(payload):
96 result = {}
95 result = {}
97 i = 0
96 i = 0
98 l = len(payload) - 1
97 l = len(payload) - 1
99 state = 0 # 0: vfspath, 1: size
98 state = 0 # 0: vfspath, 1: size
100 vfspath = size = ''
99 vfspath = size = ''
101 while i < l:
100 while i < l:
102 ch = payload[i]
101 ch = payload[i]
103 if ch == '\0':
102 if ch == '\0':
104 if state == 1:
103 if state == 1:
105 result[vfspath] = buffer(payload, i + 1, int(size))
104 result[vfspath] = buffer(payload, i + 1, int(size))
106 i += int(size)
105 i += int(size)
107 state = 0
106 state = 0
108 vfspath = size = ''
107 vfspath = size = ''
109 elif state == 0:
108 elif state == 0:
110 state = 1
109 state = 1
111 else:
110 else:
112 if state == 1:
111 if state == 1:
113 size += ch
112 size += ch
114 elif state == 0:
113 elif state == 0:
115 vfspath += ch
114 vfspath += ch
116 i += 1
115 i += 1
117 return result
116 return result
118
117
119 def peersetup(ui, peer):
118 def peersetup(ui, peer):
120 class fastannotatepeer(peer.__class__):
119 class fastannotatepeer(peer.__class__):
121 @wireprotov1peer.batchable
120 @wireprotov1peer.batchable
122 def getannotate(self, path, lastnode=None):
121 def getannotate(self, path, lastnode=None):
123 if not self.capable('getannotate'):
122 if not self.capable('getannotate'):
124 ui.warn(_('remote peer cannot provide annotate cache\n'))
123 ui.warn(_('remote peer cannot provide annotate cache\n'))
125 yield None, None
124 yield None, None
126 else:
125 else:
127 args = {'path': path, 'lastnode': lastnode or ''}
126 args = {'path': path, 'lastnode': lastnode or ''}
128 f = wireprotov1peer.future()
127 f = wireprotov1peer.future()
129 yield args, f
128 yield args, f
130 yield _parseresponse(f.value)
129 yield _parseresponse(f.value)
131 peer.__class__ = fastannotatepeer
130 peer.__class__ = fastannotatepeer
132
131
133 @contextlib.contextmanager
132 @contextlib.contextmanager
134 def annotatepeer(repo):
133 def annotatepeer(repo):
135 ui = repo.ui
134 ui = repo.ui
136
135
137 remotepath = ui.expandpath(
136 remotepath = ui.expandpath(
138 ui.config('fastannotate', 'remotepath', 'default'))
137 ui.config('fastannotate', 'remotepath', 'default'))
139 peer = hg.peer(ui, {}, remotepath)
138 peer = hg.peer(ui, {}, remotepath)
140
139
141 try:
140 try:
142 yield peer
141 yield peer
143 finally:
142 finally:
144 peer.close()
143 peer.close()
145
144
146 def clientfetch(repo, paths, lastnodemap=None, peer=None):
145 def clientfetch(repo, paths, lastnodemap=None, peer=None):
147 """download annotate cache from the server for paths"""
146 """download annotate cache from the server for paths"""
148 if not paths:
147 if not paths:
149 return
148 return
150
149
151 if peer is None:
150 if peer is None:
152 with annotatepeer(repo) as peer:
151 with annotatepeer(repo) as peer:
153 return clientfetch(repo, paths, lastnodemap, peer)
152 return clientfetch(repo, paths, lastnodemap, peer)
154
153
155 if lastnodemap is None:
154 if lastnodemap is None:
156 lastnodemap = {}
155 lastnodemap = {}
157
156
158 ui = repo.ui
157 ui = repo.ui
159 results = []
158 results = []
160 with peer.commandexecutor() as batcher:
159 with peer.commandexecutor() as batcher:
161 ui.debug('fastannotate: requesting %d files\n' % len(paths))
160 ui.debug('fastannotate: requesting %d files\n' % len(paths))
162 for p in paths:
161 for p in paths:
163 results.append(batcher.callcommand(
162 results.append(batcher.callcommand(
164 'getannotate',
163 'getannotate',
165 {'path': p, 'lastnode':lastnodemap.get(p)}))
164 {'path': p, 'lastnode':lastnodemap.get(p)}))
166
165
167 ui.debug('fastannotate: server returned\n')
166 ui.debug('fastannotate: server returned\n')
168 for result in results:
167 for result in results:
169 r = result.result()
168 r = result.result()
170 for path in sorted(r):
169 for path in sorted(r):
171 # ignore malicious paths
170 # ignore malicious paths
172 if not path.startswith('fastannotate/') or '/../' in (path + '/'):
171 if not path.startswith('fastannotate/') or '/../' in (path + '/'):
173 ui.debug('fastannotate: ignored malicious path %s\n' % path)
172 ui.debug('fastannotate: ignored malicious path %s\n' % path)
174 continue
173 continue
175 content = r[path]
174 content = r[path]
176 if ui.debugflag:
175 if ui.debugflag:
177 ui.debug('fastannotate: writing %d bytes to %s\n'
176 ui.debug('fastannotate: writing %d bytes to %s\n'
178 % (len(content), path))
177 % (len(content), path))
179 repo.vfs.makedirs(os.path.dirname(path))
178 repo.vfs.makedirs(os.path.dirname(path))
180 with repo.vfs(path, 'wb') as f:
179 with repo.vfs(path, 'wb') as f:
181 f.write(content)
180 f.write(content)
182
181
183 def _filterfetchpaths(repo, paths):
182 def _filterfetchpaths(repo, paths):
184 """return a subset of paths whose history is long and need to fetch linelog
183 """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.
184 from the server. works with remotefilelog and non-remotefilelog repos.
186 """
185 """
187 threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10)
186 threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10)
188 if threshold <= 0:
187 if threshold <= 0:
189 return paths
188 return paths
190
189
191 master = repo.ui.config('fastannotate', 'mainbranch') or 'default'
192
193 result = []
190 result = []
194 for path in paths:
191 for path in paths:
195 try:
192 try:
196 if len(repo.file(path)) >= threshold:
193 if len(repo.file(path)) >= threshold:
197 result.append(path)
194 result.append(path)
198 except Exception: # file not found etc.
195 except Exception: # file not found etc.
199 result.append(path)
196 result.append(path)
200
197
201 return result
198 return result
202
199
203 def localreposetup(ui, repo):
200 def localreposetup(ui, repo):
204 class fastannotaterepo(repo.__class__):
201 class fastannotaterepo(repo.__class__):
205 def prefetchfastannotate(self, paths, peer=None):
202 def prefetchfastannotate(self, paths, peer=None):
206 master = _getmaster(self.ui)
203 master = _getmaster(self.ui)
207 needupdatepaths = []
204 needupdatepaths = []
208 lastnodemap = {}
205 lastnodemap = {}
209 try:
206 try:
210 for path in _filterfetchpaths(self, paths):
207 for path in _filterfetchpaths(self, paths):
211 with context.annotatecontext(self, path) as actx:
208 with context.annotatecontext(self, path) as actx:
212 if not actx.isuptodate(master, strict=False):
209 if not actx.isuptodate(master, strict=False):
213 needupdatepaths.append(path)
210 needupdatepaths.append(path)
214 lastnodemap[path] = actx.lastnode
211 lastnodemap[path] = actx.lastnode
215 if needupdatepaths:
212 if needupdatepaths:
216 clientfetch(self, needupdatepaths, lastnodemap, peer)
213 clientfetch(self, needupdatepaths, lastnodemap, peer)
217 except Exception as ex:
214 except Exception as ex:
218 # could be directory not writable or so, not fatal
215 # could be directory not writable or so, not fatal
219 self.ui.debug('fastannotate: prefetch failed: %r\n' % ex)
216 self.ui.debug('fastannotate: prefetch failed: %r\n' % ex)
220 repo.__class__ = fastannotaterepo
217 repo.__class__ = fastannotaterepo
221
218
222 def clientreposetup(ui, repo):
219 def clientreposetup(ui, repo):
223 _registerwireprotocommand()
220 _registerwireprotocommand()
224 if isinstance(repo, localrepo.localrepository):
221 if isinstance(repo, localrepo.localrepository):
225 localreposetup(ui, repo)
222 localreposetup(ui, repo)
226 # TODO: this mutates global state, but only if at least one repo
223 # TODO: this mutates global state, but only if at least one repo
227 # has the extension enabled. This is probably bad for hgweb.
224 # has the extension enabled. This is probably bad for hgweb.
228 if peersetup not in hg.wirepeersetupfuncs:
225 if peersetup not in hg.wirepeersetupfuncs:
229 hg.wirepeersetupfuncs.append(peersetup)
226 hg.wirepeersetupfuncs.append(peersetup)
General Comments 0
You need to be logged in to leave comments. Login now