##// END OF EJS Templates
narrow: drop explicit dirstate write...
Martin von Zweigbergk -
r41212:4475322b default
parent child Browse files
Show More
@@ -1,481 +1,481
1 # narrowcommands.py - command modifications for narrowhg extension
1 # narrowcommands.py - command modifications for narrowhg extension
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
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 itertools
9 import itertools
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 bundle2,
14 bundle2,
15 cmdutil,
15 cmdutil,
16 commands,
16 commands,
17 discovery,
17 discovery,
18 encoding,
18 encoding,
19 error,
19 error,
20 exchange,
20 exchange,
21 extensions,
21 extensions,
22 hg,
22 hg,
23 merge,
23 merge,
24 narrowspec,
24 narrowspec,
25 node,
25 node,
26 pycompat,
26 pycompat,
27 registrar,
27 registrar,
28 repair,
28 repair,
29 repository,
29 repository,
30 repoview,
30 repoview,
31 sparse,
31 sparse,
32 util,
32 util,
33 wireprototypes,
33 wireprototypes,
34 )
34 )
35
35
36 table = {}
36 table = {}
37 command = registrar.command(table)
37 command = registrar.command(table)
38
38
39 def setup():
39 def setup():
40 """Wraps user-facing mercurial commands with narrow-aware versions."""
40 """Wraps user-facing mercurial commands with narrow-aware versions."""
41
41
42 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
42 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
43 entry[1].append(('', 'narrow', None,
43 entry[1].append(('', 'narrow', None,
44 _("create a narrow clone of select files")))
44 _("create a narrow clone of select files")))
45 entry[1].append(('', 'depth', '',
45 entry[1].append(('', 'depth', '',
46 _("limit the history fetched by distance from heads")))
46 _("limit the history fetched by distance from heads")))
47 entry[1].append(('', 'narrowspec', '',
47 entry[1].append(('', 'narrowspec', '',
48 _("read narrowspecs from file")))
48 _("read narrowspecs from file")))
49 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
49 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
50 if 'sparse' not in extensions.enabled():
50 if 'sparse' not in extensions.enabled():
51 entry[1].append(('', 'include', [],
51 entry[1].append(('', 'include', [],
52 _("specifically fetch this file/directory")))
52 _("specifically fetch this file/directory")))
53 entry[1].append(
53 entry[1].append(
54 ('', 'exclude', [],
54 ('', 'exclude', [],
55 _("do not fetch this file/directory, even if included")))
55 _("do not fetch this file/directory, even if included")))
56
56
57 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
57 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
58 entry[1].append(('', 'depth', '',
58 entry[1].append(('', 'depth', '',
59 _("limit the history fetched by distance from heads")))
59 _("limit the history fetched by distance from heads")))
60
60
61 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
61 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
62
62
63 def clonenarrowcmd(orig, ui, repo, *args, **opts):
63 def clonenarrowcmd(orig, ui, repo, *args, **opts):
64 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
64 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
65 opts = pycompat.byteskwargs(opts)
65 opts = pycompat.byteskwargs(opts)
66 wrappedextraprepare = util.nullcontextmanager()
66 wrappedextraprepare = util.nullcontextmanager()
67 narrowspecfile = opts['narrowspec']
67 narrowspecfile = opts['narrowspec']
68
68
69 if narrowspecfile:
69 if narrowspecfile:
70 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
70 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
71 ui.status(_("reading narrowspec from '%s'\n") % filepath)
71 ui.status(_("reading narrowspec from '%s'\n") % filepath)
72 try:
72 try:
73 fdata = util.readfile(filepath)
73 fdata = util.readfile(filepath)
74 except IOError as inst:
74 except IOError as inst:
75 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
75 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
76 (filepath, encoding.strtolocal(inst.strerror)))
76 (filepath, encoding.strtolocal(inst.strerror)))
77
77
78 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
78 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
79 if profiles:
79 if profiles:
80 raise error.Abort(_("cannot specify other files using '%include' in"
80 raise error.Abort(_("cannot specify other files using '%include' in"
81 " narrowspec"))
81 " narrowspec"))
82
82
83 narrowspec.validatepatterns(includes)
83 narrowspec.validatepatterns(includes)
84 narrowspec.validatepatterns(excludes)
84 narrowspec.validatepatterns(excludes)
85
85
86 # narrowspec is passed so we should assume that user wants narrow clone
86 # narrowspec is passed so we should assume that user wants narrow clone
87 opts['narrow'] = True
87 opts['narrow'] = True
88 opts['include'].extend(includes)
88 opts['include'].extend(includes)
89 opts['exclude'].extend(excludes)
89 opts['exclude'].extend(excludes)
90
90
91 if opts['narrow']:
91 if opts['narrow']:
92 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
92 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
93 orig(pullop, kwargs)
93 orig(pullop, kwargs)
94
94
95 if opts.get('depth'):
95 if opts.get('depth'):
96 kwargs['depth'] = opts['depth']
96 kwargs['depth'] = opts['depth']
97 wrappedextraprepare = extensions.wrappedfunction(exchange,
97 wrappedextraprepare = extensions.wrappedfunction(exchange,
98 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
98 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
99
99
100 with wrappedextraprepare:
100 with wrappedextraprepare:
101 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
101 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
102
102
103 def pullnarrowcmd(orig, ui, repo, *args, **opts):
103 def pullnarrowcmd(orig, ui, repo, *args, **opts):
104 """Wraps pull command to allow modifying narrow spec."""
104 """Wraps pull command to allow modifying narrow spec."""
105 wrappedextraprepare = util.nullcontextmanager()
105 wrappedextraprepare = util.nullcontextmanager()
106 if repository.NARROW_REQUIREMENT in repo.requirements:
106 if repository.NARROW_REQUIREMENT in repo.requirements:
107
107
108 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
108 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
109 orig(pullop, kwargs)
109 orig(pullop, kwargs)
110 if opts.get(r'depth'):
110 if opts.get(r'depth'):
111 kwargs['depth'] = opts[r'depth']
111 kwargs['depth'] = opts[r'depth']
112 wrappedextraprepare = extensions.wrappedfunction(exchange,
112 wrappedextraprepare = extensions.wrappedfunction(exchange,
113 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
113 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
114
114
115 with wrappedextraprepare:
115 with wrappedextraprepare:
116 return orig(ui, repo, *args, **opts)
116 return orig(ui, repo, *args, **opts)
117
117
118 def archivenarrowcmd(orig, ui, repo, *args, **opts):
118 def archivenarrowcmd(orig, ui, repo, *args, **opts):
119 """Wraps archive command to narrow the default includes."""
119 """Wraps archive command to narrow the default includes."""
120 if repository.NARROW_REQUIREMENT in repo.requirements:
120 if repository.NARROW_REQUIREMENT in repo.requirements:
121 repo_includes, repo_excludes = repo.narrowpats
121 repo_includes, repo_excludes = repo.narrowpats
122 includes = set(opts.get(r'include', []))
122 includes = set(opts.get(r'include', []))
123 excludes = set(opts.get(r'exclude', []))
123 excludes = set(opts.get(r'exclude', []))
124 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
124 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
125 includes, excludes, repo_includes, repo_excludes)
125 includes, excludes, repo_includes, repo_excludes)
126 if includes:
126 if includes:
127 opts[r'include'] = includes
127 opts[r'include'] = includes
128 if excludes:
128 if excludes:
129 opts[r'exclude'] = excludes
129 opts[r'exclude'] = excludes
130 return orig(ui, repo, *args, **opts)
130 return orig(ui, repo, *args, **opts)
131
131
132 def pullbundle2extraprepare(orig, pullop, kwargs):
132 def pullbundle2extraprepare(orig, pullop, kwargs):
133 repo = pullop.repo
133 repo = pullop.repo
134 if repository.NARROW_REQUIREMENT not in repo.requirements:
134 if repository.NARROW_REQUIREMENT not in repo.requirements:
135 return orig(pullop, kwargs)
135 return orig(pullop, kwargs)
136
136
137 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
137 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
138 raise error.Abort(_("server does not support narrow clones"))
138 raise error.Abort(_("server does not support narrow clones"))
139 orig(pullop, kwargs)
139 orig(pullop, kwargs)
140 kwargs['narrow'] = True
140 kwargs['narrow'] = True
141 include, exclude = repo.narrowpats
141 include, exclude = repo.narrowpats
142 kwargs['oldincludepats'] = include
142 kwargs['oldincludepats'] = include
143 kwargs['oldexcludepats'] = exclude
143 kwargs['oldexcludepats'] = exclude
144 if include:
144 if include:
145 kwargs['includepats'] = include
145 kwargs['includepats'] = include
146 if exclude:
146 if exclude:
147 kwargs['excludepats'] = exclude
147 kwargs['excludepats'] = exclude
148 # calculate known nodes only in ellipses cases because in non-ellipses cases
148 # calculate known nodes only in ellipses cases because in non-ellipses cases
149 # we have all the nodes
149 # we have all the nodes
150 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
150 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
151 kwargs['known'] = [node.hex(ctx.node()) for ctx in
151 kwargs['known'] = [node.hex(ctx.node()) for ctx in
152 repo.set('::%ln', pullop.common)
152 repo.set('::%ln', pullop.common)
153 if ctx.node() != node.nullid]
153 if ctx.node() != node.nullid]
154 if not kwargs['known']:
154 if not kwargs['known']:
155 # Mercurial serializes an empty list as '' and deserializes it as
155 # Mercurial serializes an empty list as '' and deserializes it as
156 # [''], so delete it instead to avoid handling the empty string on
156 # [''], so delete it instead to avoid handling the empty string on
157 # the server.
157 # the server.
158 del kwargs['known']
158 del kwargs['known']
159
159
160 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
160 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
161 pullbundle2extraprepare)
161 pullbundle2extraprepare)
162
162
163 # This is an extension point for filesystems that need to do something other
163 # This is an extension point for filesystems that need to do something other
164 # than just blindly unlink the files. It's not clear what arguments would be
164 # than just blindly unlink the files. It's not clear what arguments would be
165 # useful, so we're passing in a fair number of them, some of them redundant.
165 # useful, so we're passing in a fair number of them, some of them redundant.
166 def _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes, newexcludes,
166 def _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes, newexcludes,
167 oldmatch, newmatch):
167 oldmatch, newmatch):
168 for f in repo.dirstate:
168 for f in repo.dirstate:
169 if not newmatch(f):
169 if not newmatch(f):
170 repo.dirstate.drop(f)
170 repo.dirstate.drop(f)
171 repo.wvfs.unlinkpath(f)
171 repo.wvfs.unlinkpath(f)
172
172
173 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
173 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
174 newincludes, newexcludes, force):
174 newincludes, newexcludes, force):
175 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
175 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
176 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
176 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
177
177
178 # This is essentially doing "hg outgoing" to find all local-only
178 # This is essentially doing "hg outgoing" to find all local-only
179 # commits. We will then check that the local-only commits don't
179 # commits. We will then check that the local-only commits don't
180 # have any changes to files that will be untracked.
180 # have any changes to files that will be untracked.
181 unfi = repo.unfiltered()
181 unfi = repo.unfiltered()
182 outgoing = discovery.findcommonoutgoing(unfi, remote,
182 outgoing = discovery.findcommonoutgoing(unfi, remote,
183 commoninc=commoninc)
183 commoninc=commoninc)
184 ui.status(_('looking for local changes to affected paths\n'))
184 ui.status(_('looking for local changes to affected paths\n'))
185 localnodes = []
185 localnodes = []
186 for n in itertools.chain(outgoing.missing, outgoing.excluded):
186 for n in itertools.chain(outgoing.missing, outgoing.excluded):
187 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
187 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
188 localnodes.append(n)
188 localnodes.append(n)
189 revstostrip = unfi.revs('descendants(%ln)', localnodes)
189 revstostrip = unfi.revs('descendants(%ln)', localnodes)
190 hiddenrevs = repoview.filterrevs(repo, 'visible')
190 hiddenrevs = repoview.filterrevs(repo, 'visible')
191 visibletostrip = list(repo.changelog.node(r)
191 visibletostrip = list(repo.changelog.node(r)
192 for r in (revstostrip - hiddenrevs))
192 for r in (revstostrip - hiddenrevs))
193 if visibletostrip:
193 if visibletostrip:
194 ui.status(_('The following changeset(s) or their ancestors have '
194 ui.status(_('The following changeset(s) or their ancestors have '
195 'local changes not on the remote:\n'))
195 'local changes not on the remote:\n'))
196 maxnodes = 10
196 maxnodes = 10
197 if ui.verbose or len(visibletostrip) <= maxnodes:
197 if ui.verbose or len(visibletostrip) <= maxnodes:
198 for n in visibletostrip:
198 for n in visibletostrip:
199 ui.status('%s\n' % node.short(n))
199 ui.status('%s\n' % node.short(n))
200 else:
200 else:
201 for n in visibletostrip[:maxnodes]:
201 for n in visibletostrip[:maxnodes]:
202 ui.status('%s\n' % node.short(n))
202 ui.status('%s\n' % node.short(n))
203 ui.status(_('...and %d more, use --verbose to list all\n') %
203 ui.status(_('...and %d more, use --verbose to list all\n') %
204 (len(visibletostrip) - maxnodes))
204 (len(visibletostrip) - maxnodes))
205 if not force:
205 if not force:
206 raise error.Abort(_('local changes found'),
206 raise error.Abort(_('local changes found'),
207 hint=_('use --force-delete-local-changes to '
207 hint=_('use --force-delete-local-changes to '
208 'ignore'))
208 'ignore'))
209
209
210 with ui.uninterruptible():
210 with ui.uninterruptible():
211 if revstostrip:
211 if revstostrip:
212 tostrip = [unfi.changelog.node(r) for r in revstostrip]
212 tostrip = [unfi.changelog.node(r) for r in revstostrip]
213 if repo['.'].node() in tostrip:
213 if repo['.'].node() in tostrip:
214 # stripping working copy, so move to a different commit first
214 # stripping working copy, so move to a different commit first
215 urev = max(repo.revs('(::%n) - %ln + null',
215 urev = max(repo.revs('(::%n) - %ln + null',
216 repo['.'].node(), visibletostrip))
216 repo['.'].node(), visibletostrip))
217 hg.clean(repo, urev)
217 hg.clean(repo, urev)
218 overrides = {('devel', 'strip-obsmarkers'): False}
218 overrides = {('devel', 'strip-obsmarkers'): False}
219 with ui.configoverride(overrides, 'narrow'):
219 with ui.configoverride(overrides, 'narrow'):
220 repair.strip(ui, unfi, tostrip, topic='narrow')
220 repair.strip(ui, unfi, tostrip, topic='narrow')
221
221
222 todelete = []
222 todelete = []
223 for f, f2, size in repo.store.datafiles():
223 for f, f2, size in repo.store.datafiles():
224 if f.startswith('data/'):
224 if f.startswith('data/'):
225 file = f[5:-2]
225 file = f[5:-2]
226 if not newmatch(file):
226 if not newmatch(file):
227 todelete.append(f)
227 todelete.append(f)
228 elif f.startswith('meta/'):
228 elif f.startswith('meta/'):
229 dir = f[5:-13]
229 dir = f[5:-13]
230 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
230 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
231 include = True
231 include = True
232 for d in dirs:
232 for d in dirs:
233 visit = newmatch.visitdir(d)
233 visit = newmatch.visitdir(d)
234 if not visit:
234 if not visit:
235 include = False
235 include = False
236 break
236 break
237 if visit == 'all':
237 if visit == 'all':
238 break
238 break
239 if not include:
239 if not include:
240 todelete.append(f)
240 todelete.append(f)
241
241
242 repo.destroying()
242 repo.destroying()
243
243
244 with repo.transaction("narrowing"):
244 with repo.transaction("narrowing"):
245 # Update narrowspec before removing revlogs, so repo won't be
245 # Update narrowspec before removing revlogs, so repo won't be
246 # corrupt in case of crash
246 # corrupt in case of crash
247 repo.setnarrowpats(newincludes, newexcludes)
247 repo.setnarrowpats(newincludes, newexcludes)
248
248
249 for f in todelete:
249 for f in todelete:
250 ui.status(_('deleting %s\n') % f)
250 ui.status(_('deleting %s\n') % f)
251 util.unlinkpath(repo.svfs.join(f))
251 util.unlinkpath(repo.svfs.join(f))
252 repo.store.markremoved(f)
252 repo.store.markremoved(f)
253
253
254 _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes,
254 _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes,
255 newexcludes, oldmatch, newmatch)
255 newexcludes, oldmatch, newmatch)
256
256
257 repo.destroyed()
257 repo.destroyed()
258
258
259 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
259 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
260 newincludes, newexcludes):
260 newincludes, newexcludes):
261 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
261 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
262
262
263 # for now we assume that if a server has ellipses enabled, we will be
263 # for now we assume that if a server has ellipses enabled, we will be
264 # exchanging ellipses nodes. In future we should add ellipses as a client
264 # exchanging ellipses nodes. In future we should add ellipses as a client
265 # side requirement (maybe) to distinguish a client is shallow or not and
265 # side requirement (maybe) to distinguish a client is shallow or not and
266 # then send that information to server whether we want ellipses or not.
266 # then send that information to server whether we want ellipses or not.
267 # Theoretically a non-ellipses repo should be able to use narrow
267 # Theoretically a non-ellipses repo should be able to use narrow
268 # functionality from an ellipses enabled server
268 # functionality from an ellipses enabled server
269 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
269 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
270
270
271 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
271 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
272 orig(pullop, kwargs)
272 orig(pullop, kwargs)
273 # The old{in,ex}cludepats have already been set by orig()
273 # The old{in,ex}cludepats have already been set by orig()
274 kwargs['includepats'] = newincludes
274 kwargs['includepats'] = newincludes
275 kwargs['excludepats'] = newexcludes
275 kwargs['excludepats'] = newexcludes
276 wrappedextraprepare = extensions.wrappedfunction(exchange,
276 wrappedextraprepare = extensions.wrappedfunction(exchange,
277 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
277 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
278
278
279 # define a function that narrowbundle2 can call after creating the
279 # define a function that narrowbundle2 can call after creating the
280 # backup bundle, but before applying the bundle from the server
280 # backup bundle, but before applying the bundle from the server
281 def setnewnarrowpats():
281 def setnewnarrowpats():
282 repo.setnarrowpats(newincludes, newexcludes)
282 repo.setnarrowpats(newincludes, newexcludes)
283 repo.setnewnarrowpats = setnewnarrowpats
283 repo.setnewnarrowpats = setnewnarrowpats
284 # silence the devel-warning of applying an empty changegroup
284 # silence the devel-warning of applying an empty changegroup
285 overrides = {('devel', 'all-warnings'): False}
285 overrides = {('devel', 'all-warnings'): False}
286
286
287 with ui.uninterruptible():
287 with ui.uninterruptible():
288 common = commoninc[0]
288 common = commoninc[0]
289 if ellipsesremote:
289 if ellipsesremote:
290 ds = repo.dirstate
290 ds = repo.dirstate
291 p1, p2 = ds.p1(), ds.p2()
291 p1, p2 = ds.p1(), ds.p2()
292 with ds.parentchange():
292 with ds.parentchange():
293 ds.setparents(node.nullid, node.nullid)
293 ds.setparents(node.nullid, node.nullid)
294 with wrappedextraprepare,\
294 with wrappedextraprepare,\
295 repo.ui.configoverride(overrides, 'widen'):
295 repo.ui.configoverride(overrides, 'widen'):
296 exchange.pull(repo, remote, heads=common)
296 exchange.pull(repo, remote, heads=common)
297 with ds.parentchange():
297 with ds.parentchange():
298 ds.setparents(p1, p2)
298 ds.setparents(p1, p2)
299 else:
299 else:
300 with remote.commandexecutor() as e:
300 with remote.commandexecutor() as e:
301 bundle = e.callcommand('narrow_widen', {
301 bundle = e.callcommand('narrow_widen', {
302 'oldincludes': oldincludes,
302 'oldincludes': oldincludes,
303 'oldexcludes': oldexcludes,
303 'oldexcludes': oldexcludes,
304 'newincludes': newincludes,
304 'newincludes': newincludes,
305 'newexcludes': newexcludes,
305 'newexcludes': newexcludes,
306 'cgversion': '03',
306 'cgversion': '03',
307 'commonheads': common,
307 'commonheads': common,
308 'known': [],
308 'known': [],
309 'ellipses': False,
309 'ellipses': False,
310 }).result()
310 }).result()
311
311
312 with repo.transaction('widening') as tr,\
312 with repo.transaction('widening') as tr,\
313 repo.ui.configoverride(overrides, 'widen'):
313 repo.ui.configoverride(overrides, 'widen'):
314 tgetter = lambda: tr
314 tgetter = lambda: tr
315 bundle2.processbundle(repo, bundle,
315 bundle2.processbundle(repo, bundle,
316 transactiongetter=tgetter)
316 transactiongetter=tgetter)
317
317
318 repo.setnewnarrowpats()
318 repo.setnewnarrowpats()
319 actions = merge.emptyactions()
319 actions = merge.emptyactions()
320 addgaction = actions['g'].append
320 addgaction = actions['g'].append
321
321
322 mf = repo['.'].manifest().matches(newmatch)
322 mf = repo['.'].manifest().matches(newmatch)
323 for f, fn in mf.iteritems():
323 for f, fn in mf.iteritems():
324 if f not in repo.dirstate:
324 if f not in repo.dirstate:
325 addgaction((f, (mf.flags(f), False),
325 addgaction((f, (mf.flags(f), False),
326 "add from widened narrow clone"))
326 "add from widened narrow clone"))
327
327
328 merge.applyupdates(repo, actions, wctx=repo[None],
328 merge.applyupdates(repo, actions, wctx=repo[None],
329 mctx=repo['.'], overwrite=False)
329 mctx=repo['.'], overwrite=False)
330 merge.recordupdates(repo, actions, branchmerge=False)
330 merge.recordupdates(repo, actions, branchmerge=False)
331
331
332 # TODO(rdamazio): Make new matcher format and update description
332 # TODO(rdamazio): Make new matcher format and update description
333 @command('tracked',
333 @command('tracked',
334 [('', 'addinclude', [], _('new paths to include')),
334 [('', 'addinclude', [], _('new paths to include')),
335 ('', 'removeinclude', [], _('old paths to no longer include')),
335 ('', 'removeinclude', [], _('old paths to no longer include')),
336 ('', 'addexclude', [], _('new paths to exclude')),
336 ('', 'addexclude', [], _('new paths to exclude')),
337 ('', 'import-rules', '', _('import narrowspecs from a file')),
337 ('', 'import-rules', '', _('import narrowspecs from a file')),
338 ('', 'removeexclude', [], _('old paths to no longer exclude')),
338 ('', 'removeexclude', [], _('old paths to no longer exclude')),
339 ('', 'clear', False, _('whether to replace the existing narrowspec')),
339 ('', 'clear', False, _('whether to replace the existing narrowspec')),
340 ('', 'force-delete-local-changes', False,
340 ('', 'force-delete-local-changes', False,
341 _('forces deletion of local changes when narrowing')),
341 _('forces deletion of local changes when narrowing')),
342 ('', 'update-working-copy', False,
342 ('', 'update-working-copy', False,
343 _('update working copy when the store has changed')),
343 _('update working copy when the store has changed')),
344 ] + commands.remoteopts,
344 ] + commands.remoteopts,
345 _('[OPTIONS]... [REMOTE]'),
345 _('[OPTIONS]... [REMOTE]'),
346 inferrepo=True)
346 inferrepo=True)
347 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
347 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
348 """show or change the current narrowspec
348 """show or change the current narrowspec
349
349
350 With no argument, shows the current narrowspec entries, one per line. Each
350 With no argument, shows the current narrowspec entries, one per line. Each
351 line will be prefixed with 'I' or 'X' for included or excluded patterns,
351 line will be prefixed with 'I' or 'X' for included or excluded patterns,
352 respectively.
352 respectively.
353
353
354 The narrowspec is comprised of expressions to match remote files and/or
354 The narrowspec is comprised of expressions to match remote files and/or
355 directories that should be pulled into your client.
355 directories that should be pulled into your client.
356 The narrowspec has *include* and *exclude* expressions, with excludes always
356 The narrowspec has *include* and *exclude* expressions, with excludes always
357 trumping includes: that is, if a file matches an exclude expression, it will
357 trumping includes: that is, if a file matches an exclude expression, it will
358 be excluded even if it also matches an include expression.
358 be excluded even if it also matches an include expression.
359 Excluding files that were never included has no effect.
359 Excluding files that were never included has no effect.
360
360
361 Each included or excluded entry is in the format described by
361 Each included or excluded entry is in the format described by
362 'hg help patterns'.
362 'hg help patterns'.
363
363
364 The options allow you to add or remove included and excluded expressions.
364 The options allow you to add or remove included and excluded expressions.
365
365
366 If --clear is specified, then all previous includes and excludes are DROPPED
366 If --clear is specified, then all previous includes and excludes are DROPPED
367 and replaced by the new ones specified to --addinclude and --addexclude.
367 and replaced by the new ones specified to --addinclude and --addexclude.
368 If --clear is specified without any further options, the narrowspec will be
368 If --clear is specified without any further options, the narrowspec will be
369 empty and will not match any files.
369 empty and will not match any files.
370 """
370 """
371 opts = pycompat.byteskwargs(opts)
371 opts = pycompat.byteskwargs(opts)
372 if repository.NARROW_REQUIREMENT not in repo.requirements:
372 if repository.NARROW_REQUIREMENT not in repo.requirements:
373 raise error.Abort(_('the narrow command is only supported on '
373 raise error.Abort(_('the narrow command is only supported on '
374 'respositories cloned with --narrow'))
374 'respositories cloned with --narrow'))
375
375
376 # Before supporting, decide whether it "hg tracked --clear" should mean
376 # Before supporting, decide whether it "hg tracked --clear" should mean
377 # tracking no paths or all paths.
377 # tracking no paths or all paths.
378 if opts['clear']:
378 if opts['clear']:
379 raise error.Abort(_('the --clear option is not yet supported'))
379 raise error.Abort(_('the --clear option is not yet supported'))
380
380
381 # import rules from a file
381 # import rules from a file
382 newrules = opts.get('import_rules')
382 newrules = opts.get('import_rules')
383 if newrules:
383 if newrules:
384 try:
384 try:
385 filepath = os.path.join(encoding.getcwd(), newrules)
385 filepath = os.path.join(encoding.getcwd(), newrules)
386 fdata = util.readfile(filepath)
386 fdata = util.readfile(filepath)
387 except IOError as inst:
387 except IOError as inst:
388 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
388 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
389 (filepath, encoding.strtolocal(inst.strerror)))
389 (filepath, encoding.strtolocal(inst.strerror)))
390 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
390 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
391 'narrow')
391 'narrow')
392 if profiles:
392 if profiles:
393 raise error.Abort(_("including other spec files using '%include' "
393 raise error.Abort(_("including other spec files using '%include' "
394 "is not supported in narrowspec"))
394 "is not supported in narrowspec"))
395 opts['addinclude'].extend(includepats)
395 opts['addinclude'].extend(includepats)
396 opts['addexclude'].extend(excludepats)
396 opts['addexclude'].extend(excludepats)
397
397
398 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
398 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
399 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
399 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
400 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
400 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
401 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
401 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
402
402
403 update_working_copy = opts['update_working_copy']
403 update_working_copy = opts['update_working_copy']
404 only_show = not (addedincludes or removedincludes or addedexcludes or
404 only_show = not (addedincludes or removedincludes or addedexcludes or
405 removedexcludes or newrules or update_working_copy)
405 removedexcludes or newrules or update_working_copy)
406
406
407 oldincludes, oldexcludes = repo.narrowpats
407 oldincludes, oldexcludes = repo.narrowpats
408
408
409 # filter the user passed additions and deletions into actual additions and
409 # filter the user passed additions and deletions into actual additions and
410 # deletions of excludes and includes
410 # deletions of excludes and includes
411 addedincludes -= oldincludes
411 addedincludes -= oldincludes
412 removedincludes &= oldincludes
412 removedincludes &= oldincludes
413 addedexcludes -= oldexcludes
413 addedexcludes -= oldexcludes
414 removedexcludes &= oldexcludes
414 removedexcludes &= oldexcludes
415
415
416 widening = addedincludes or removedexcludes
416 widening = addedincludes or removedexcludes
417 narrowing = removedincludes or addedexcludes
417 narrowing = removedincludes or addedexcludes
418
418
419 # Only print the current narrowspec.
419 # Only print the current narrowspec.
420 if only_show:
420 if only_show:
421 ui.pager('tracked')
421 ui.pager('tracked')
422 fm = ui.formatter('narrow', opts)
422 fm = ui.formatter('narrow', opts)
423 for i in sorted(oldincludes):
423 for i in sorted(oldincludes):
424 fm.startitem()
424 fm.startitem()
425 fm.write('status', '%s ', 'I', label='narrow.included')
425 fm.write('status', '%s ', 'I', label='narrow.included')
426 fm.write('pat', '%s\n', i, label='narrow.included')
426 fm.write('pat', '%s\n', i, label='narrow.included')
427 for i in sorted(oldexcludes):
427 for i in sorted(oldexcludes):
428 fm.startitem()
428 fm.startitem()
429 fm.write('status', '%s ', 'X', label='narrow.excluded')
429 fm.write('status', '%s ', 'X', label='narrow.excluded')
430 fm.write('pat', '%s\n', i, label='narrow.excluded')
430 fm.write('pat', '%s\n', i, label='narrow.excluded')
431 fm.end()
431 fm.end()
432 return 0
432 return 0
433
433
434 if update_working_copy:
434 if update_working_copy:
435 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc') as tr:
435 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc') as tr:
436 narrowspec.updateworkingcopy(repo, tr)
436 narrowspec.updateworkingcopy(repo)
437 narrowspec.copytoworkingcopy(repo, tr)
437 narrowspec.copytoworkingcopy(repo, tr)
438 return 0
438 return 0
439
439
440 if not widening and not narrowing:
440 if not widening and not narrowing:
441 ui.status(_("nothing to widen or narrow\n"))
441 ui.status(_("nothing to widen or narrow\n"))
442 return 0
442 return 0
443
443
444 with repo.wlock(), repo.lock():
444 with repo.wlock(), repo.lock():
445 cmdutil.bailifchanged(repo)
445 cmdutil.bailifchanged(repo)
446
446
447 # Find the revisions we have in common with the remote. These will
447 # Find the revisions we have in common with the remote. These will
448 # be used for finding local-only changes for narrowing. They will
448 # be used for finding local-only changes for narrowing. They will
449 # also define the set of revisions to update for widening.
449 # also define the set of revisions to update for widening.
450 remotepath = ui.expandpath(remotepath or 'default')
450 remotepath = ui.expandpath(remotepath or 'default')
451 url, branches = hg.parseurl(remotepath)
451 url, branches = hg.parseurl(remotepath)
452 ui.status(_('comparing with %s\n') % util.hidepassword(url))
452 ui.status(_('comparing with %s\n') % util.hidepassword(url))
453 remote = hg.peer(repo, opts, url)
453 remote = hg.peer(repo, opts, url)
454
454
455 # check narrow support before doing anything if widening needs to be
455 # check narrow support before doing anything if widening needs to be
456 # performed. In future we should also abort if client is ellipses and
456 # performed. In future we should also abort if client is ellipses and
457 # server does not support ellipses
457 # server does not support ellipses
458 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
458 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
459 raise error.Abort(_("server does not support narrow clones"))
459 raise error.Abort(_("server does not support narrow clones"))
460
460
461 commoninc = discovery.findcommonincoming(repo, remote)
461 commoninc = discovery.findcommonincoming(repo, remote)
462
462
463 if narrowing:
463 if narrowing:
464 newincludes = oldincludes - removedincludes
464 newincludes = oldincludes - removedincludes
465 newexcludes = oldexcludes | addedexcludes
465 newexcludes = oldexcludes | addedexcludes
466 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
466 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
467 newincludes, newexcludes,
467 newincludes, newexcludes,
468 opts['force_delete_local_changes'])
468 opts['force_delete_local_changes'])
469 # _narrow() updated the narrowspec and _widen() below needs to
469 # _narrow() updated the narrowspec and _widen() below needs to
470 # use the updated values as its base (otherwise removed includes
470 # use the updated values as its base (otherwise removed includes
471 # and addedexcludes will be lost in the resulting narrowspec)
471 # and addedexcludes will be lost in the resulting narrowspec)
472 oldincludes = newincludes
472 oldincludes = newincludes
473 oldexcludes = newexcludes
473 oldexcludes = newexcludes
474
474
475 if widening:
475 if widening:
476 newincludes = oldincludes | addedincludes
476 newincludes = oldincludes | addedincludes
477 newexcludes = oldexcludes - removedexcludes
477 newexcludes = oldexcludes - removedexcludes
478 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
478 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
479 newincludes, newexcludes)
479 newincludes, newexcludes)
480
480
481 return 0
481 return 0
@@ -1,298 +1,296
1 # narrowspec.py - methods for working with a narrow view of a repository
1 # narrowspec.py - methods for working with a narrow view of a repository
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
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 errno
10 import errno
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 match as matchmod,
15 match as matchmod,
16 merge,
16 merge,
17 repository,
17 repository,
18 sparse,
18 sparse,
19 util,
19 util,
20 )
20 )
21
21
22 # The file in .hg/store/ that indicates which paths exit in the store
22 # The file in .hg/store/ that indicates which paths exit in the store
23 FILENAME = 'narrowspec'
23 FILENAME = 'narrowspec'
24 # The file in .hg/ that indicates which paths exit in the dirstate
24 # The file in .hg/ that indicates which paths exit in the dirstate
25 DIRSTATE_FILENAME = 'narrowspec.dirstate'
25 DIRSTATE_FILENAME = 'narrowspec.dirstate'
26
26
27 # Pattern prefixes that are allowed in narrow patterns. This list MUST
27 # Pattern prefixes that are allowed in narrow patterns. This list MUST
28 # only contain patterns that are fast and safe to evaluate. Keep in mind
28 # only contain patterns that are fast and safe to evaluate. Keep in mind
29 # that patterns are supplied by clients and executed on remote servers
29 # that patterns are supplied by clients and executed on remote servers
30 # as part of wire protocol commands. That means that changes to this
30 # as part of wire protocol commands. That means that changes to this
31 # data structure influence the wire protocol and should not be taken
31 # data structure influence the wire protocol and should not be taken
32 # lightly - especially removals.
32 # lightly - especially removals.
33 VALID_PREFIXES = (
33 VALID_PREFIXES = (
34 b'path:',
34 b'path:',
35 b'rootfilesin:',
35 b'rootfilesin:',
36 )
36 )
37
37
38 def normalizesplitpattern(kind, pat):
38 def normalizesplitpattern(kind, pat):
39 """Returns the normalized version of a pattern and kind.
39 """Returns the normalized version of a pattern and kind.
40
40
41 Returns a tuple with the normalized kind and normalized pattern.
41 Returns a tuple with the normalized kind and normalized pattern.
42 """
42 """
43 pat = pat.rstrip('/')
43 pat = pat.rstrip('/')
44 _validatepattern(pat)
44 _validatepattern(pat)
45 return kind, pat
45 return kind, pat
46
46
47 def _numlines(s):
47 def _numlines(s):
48 """Returns the number of lines in s, including ending empty lines."""
48 """Returns the number of lines in s, including ending empty lines."""
49 # We use splitlines because it is Unicode-friendly and thus Python 3
49 # We use splitlines because it is Unicode-friendly and thus Python 3
50 # compatible. However, it does not count empty lines at the end, so trick
50 # compatible. However, it does not count empty lines at the end, so trick
51 # it by adding a character at the end.
51 # it by adding a character at the end.
52 return len((s + 'x').splitlines())
52 return len((s + 'x').splitlines())
53
53
54 def _validatepattern(pat):
54 def _validatepattern(pat):
55 """Validates the pattern and aborts if it is invalid.
55 """Validates the pattern and aborts if it is invalid.
56
56
57 Patterns are stored in the narrowspec as newline-separated
57 Patterns are stored in the narrowspec as newline-separated
58 POSIX-style bytestring paths. There's no escaping.
58 POSIX-style bytestring paths. There's no escaping.
59 """
59 """
60
60
61 # We use newlines as separators in the narrowspec file, so don't allow them
61 # We use newlines as separators in the narrowspec file, so don't allow them
62 # in patterns.
62 # in patterns.
63 if _numlines(pat) > 1:
63 if _numlines(pat) > 1:
64 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
64 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
65
65
66 components = pat.split('/')
66 components = pat.split('/')
67 if '.' in components or '..' in components:
67 if '.' in components or '..' in components:
68 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
68 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
69
69
70 def normalizepattern(pattern, defaultkind='path'):
70 def normalizepattern(pattern, defaultkind='path'):
71 """Returns the normalized version of a text-format pattern.
71 """Returns the normalized version of a text-format pattern.
72
72
73 If the pattern has no kind, the default will be added.
73 If the pattern has no kind, the default will be added.
74 """
74 """
75 kind, pat = matchmod._patsplit(pattern, defaultkind)
75 kind, pat = matchmod._patsplit(pattern, defaultkind)
76 return '%s:%s' % normalizesplitpattern(kind, pat)
76 return '%s:%s' % normalizesplitpattern(kind, pat)
77
77
78 def parsepatterns(pats):
78 def parsepatterns(pats):
79 """Parses an iterable of patterns into a typed pattern set.
79 """Parses an iterable of patterns into a typed pattern set.
80
80
81 Patterns are assumed to be ``path:`` if no prefix is present.
81 Patterns are assumed to be ``path:`` if no prefix is present.
82 For safety and performance reasons, only some prefixes are allowed.
82 For safety and performance reasons, only some prefixes are allowed.
83 See ``validatepatterns()``.
83 See ``validatepatterns()``.
84
84
85 This function should be used on patterns that come from the user to
85 This function should be used on patterns that come from the user to
86 normalize and validate them to the internal data structure used for
86 normalize and validate them to the internal data structure used for
87 representing patterns.
87 representing patterns.
88 """
88 """
89 res = {normalizepattern(orig) for orig in pats}
89 res = {normalizepattern(orig) for orig in pats}
90 validatepatterns(res)
90 validatepatterns(res)
91 return res
91 return res
92
92
93 def validatepatterns(pats):
93 def validatepatterns(pats):
94 """Validate that patterns are in the expected data structure and format.
94 """Validate that patterns are in the expected data structure and format.
95
95
96 And that is a set of normalized patterns beginning with ``path:`` or
96 And that is a set of normalized patterns beginning with ``path:`` or
97 ``rootfilesin:``.
97 ``rootfilesin:``.
98
98
99 This function should be used to validate internal data structures
99 This function should be used to validate internal data structures
100 and patterns that are loaded from sources that use the internal,
100 and patterns that are loaded from sources that use the internal,
101 prefixed pattern representation (but can't necessarily be fully trusted).
101 prefixed pattern representation (but can't necessarily be fully trusted).
102 """
102 """
103 if not isinstance(pats, set):
103 if not isinstance(pats, set):
104 raise error.ProgrammingError('narrow patterns should be a set; '
104 raise error.ProgrammingError('narrow patterns should be a set; '
105 'got %r' % pats)
105 'got %r' % pats)
106
106
107 for pat in pats:
107 for pat in pats:
108 if not pat.startswith(VALID_PREFIXES):
108 if not pat.startswith(VALID_PREFIXES):
109 # Use a Mercurial exception because this can happen due to user
109 # Use a Mercurial exception because this can happen due to user
110 # bugs (e.g. manually updating spec file).
110 # bugs (e.g. manually updating spec file).
111 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
111 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
112 hint=_('narrow patterns must begin with one of '
112 hint=_('narrow patterns must begin with one of '
113 'the following: %s') %
113 'the following: %s') %
114 ', '.join(VALID_PREFIXES))
114 ', '.join(VALID_PREFIXES))
115
115
116 def format(includes, excludes):
116 def format(includes, excludes):
117 output = '[include]\n'
117 output = '[include]\n'
118 for i in sorted(includes - excludes):
118 for i in sorted(includes - excludes):
119 output += i + '\n'
119 output += i + '\n'
120 output += '[exclude]\n'
120 output += '[exclude]\n'
121 for e in sorted(excludes):
121 for e in sorted(excludes):
122 output += e + '\n'
122 output += e + '\n'
123 return output
123 return output
124
124
125 def match(root, include=None, exclude=None):
125 def match(root, include=None, exclude=None):
126 if not include:
126 if not include:
127 # Passing empty include and empty exclude to matchmod.match()
127 # Passing empty include and empty exclude to matchmod.match()
128 # gives a matcher that matches everything, so explicitly use
128 # gives a matcher that matches everything, so explicitly use
129 # the nevermatcher.
129 # the nevermatcher.
130 return matchmod.never(root, '')
130 return matchmod.never(root, '')
131 return matchmod.match(root, '', [], include=include or [],
131 return matchmod.match(root, '', [], include=include or [],
132 exclude=exclude or [])
132 exclude=exclude or [])
133
133
134 def parseconfig(ui, spec):
134 def parseconfig(ui, spec):
135 # maybe we should care about the profiles returned too
135 # maybe we should care about the profiles returned too
136 includepats, excludepats, profiles = sparse.parseconfig(ui, spec, 'narrow')
136 includepats, excludepats, profiles = sparse.parseconfig(ui, spec, 'narrow')
137 if profiles:
137 if profiles:
138 raise error.Abort(_("including other spec files using '%include' is not"
138 raise error.Abort(_("including other spec files using '%include' is not"
139 " supported in narrowspec"))
139 " supported in narrowspec"))
140
140
141 validatepatterns(includepats)
141 validatepatterns(includepats)
142 validatepatterns(excludepats)
142 validatepatterns(excludepats)
143
143
144 return includepats, excludepats
144 return includepats, excludepats
145
145
146 def load(repo):
146 def load(repo):
147 try:
147 try:
148 spec = repo.svfs.read(FILENAME)
148 spec = repo.svfs.read(FILENAME)
149 except IOError as e:
149 except IOError as e:
150 # Treat "narrowspec does not exist" the same as "narrowspec file exists
150 # Treat "narrowspec does not exist" the same as "narrowspec file exists
151 # and is empty".
151 # and is empty".
152 if e.errno == errno.ENOENT:
152 if e.errno == errno.ENOENT:
153 return set(), set()
153 return set(), set()
154 raise
154 raise
155
155
156 return parseconfig(repo.ui, spec)
156 return parseconfig(repo.ui, spec)
157
157
158 def save(repo, includepats, excludepats):
158 def save(repo, includepats, excludepats):
159 validatepatterns(includepats)
159 validatepatterns(includepats)
160 validatepatterns(excludepats)
160 validatepatterns(excludepats)
161 spec = format(includepats, excludepats)
161 spec = format(includepats, excludepats)
162 repo.svfs.write(FILENAME, spec)
162 repo.svfs.write(FILENAME, spec)
163
163
164 def copytoworkingcopy(repo, tr):
164 def copytoworkingcopy(repo, tr):
165 if tr:
165 if tr:
166 def write(file):
166 def write(file):
167 spec = repo.svfs.read(FILENAME)
167 spec = repo.svfs.read(FILENAME)
168 file.write(spec)
168 file.write(spec)
169 file.close()
169 file.close()
170 tr.addfilegenerator('narrowspec', (DIRSTATE_FILENAME,), write,
170 tr.addfilegenerator('narrowspec', (DIRSTATE_FILENAME,), write,
171 location='plain')
171 location='plain')
172 else:
172 else:
173 spec = repo.svfs.read(FILENAME)
173 spec = repo.svfs.read(FILENAME)
174 repo.vfs.write(DIRSTATE_FILENAME, spec)
174 repo.vfs.write(DIRSTATE_FILENAME, spec)
175
175
176 def savebackup(repo, backupname):
176 def savebackup(repo, backupname):
177 if repository.NARROW_REQUIREMENT not in repo.requirements:
177 if repository.NARROW_REQUIREMENT not in repo.requirements:
178 return
178 return
179 svfs = repo.svfs
179 svfs = repo.svfs
180 svfs.tryunlink(backupname)
180 svfs.tryunlink(backupname)
181 util.copyfile(svfs.join(FILENAME), svfs.join(backupname), hardlink=True)
181 util.copyfile(svfs.join(FILENAME), svfs.join(backupname), hardlink=True)
182
182
183 def restorebackup(repo, backupname):
183 def restorebackup(repo, backupname):
184 if repository.NARROW_REQUIREMENT not in repo.requirements:
184 if repository.NARROW_REQUIREMENT not in repo.requirements:
185 return
185 return
186 util.rename(repo.svfs.join(backupname), repo.svfs.join(FILENAME))
186 util.rename(repo.svfs.join(backupname), repo.svfs.join(FILENAME))
187
187
188 def clearbackup(repo, backupname):
188 def clearbackup(repo, backupname):
189 if repository.NARROW_REQUIREMENT not in repo.requirements:
189 if repository.NARROW_REQUIREMENT not in repo.requirements:
190 return
190 return
191 repo.svfs.unlink(backupname)
191 repo.svfs.unlink(backupname)
192
192
193 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
193 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
194 r""" Restricts the patterns according to repo settings,
194 r""" Restricts the patterns according to repo settings,
195 results in a logical AND operation
195 results in a logical AND operation
196
196
197 :param req_includes: requested includes
197 :param req_includes: requested includes
198 :param req_excludes: requested excludes
198 :param req_excludes: requested excludes
199 :param repo_includes: repo includes
199 :param repo_includes: repo includes
200 :param repo_excludes: repo excludes
200 :param repo_excludes: repo excludes
201 :return: include patterns, exclude patterns, and invalid include patterns.
201 :return: include patterns, exclude patterns, and invalid include patterns.
202
202
203 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
203 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
204 (set(['f1']), {}, [])
204 (set(['f1']), {}, [])
205 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
205 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
206 (set(['f1']), {}, [])
206 (set(['f1']), {}, [])
207 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
207 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
208 (set(['f1/fc1']), {}, [])
208 (set(['f1/fc1']), {}, [])
209 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
209 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
210 ([], set(['path:.']), [])
210 ([], set(['path:.']), [])
211 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
211 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
212 (set(['f2/fc2']), {}, [])
212 (set(['f2/fc2']), {}, [])
213 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
213 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
214 ([], set(['path:.']), [])
214 ([], set(['path:.']), [])
215 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
215 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
216 (set(['f1/$non_exitent_var']), {}, [])
216 (set(['f1/$non_exitent_var']), {}, [])
217 """
217 """
218 res_excludes = set(req_excludes)
218 res_excludes = set(req_excludes)
219 res_excludes.update(repo_excludes)
219 res_excludes.update(repo_excludes)
220 invalid_includes = []
220 invalid_includes = []
221 if not req_includes:
221 if not req_includes:
222 res_includes = set(repo_includes)
222 res_includes = set(repo_includes)
223 elif 'path:.' not in repo_includes:
223 elif 'path:.' not in repo_includes:
224 res_includes = []
224 res_includes = []
225 for req_include in req_includes:
225 for req_include in req_includes:
226 req_include = util.expandpath(util.normpath(req_include))
226 req_include = util.expandpath(util.normpath(req_include))
227 if req_include in repo_includes:
227 if req_include in repo_includes:
228 res_includes.append(req_include)
228 res_includes.append(req_include)
229 continue
229 continue
230 valid = False
230 valid = False
231 for repo_include in repo_includes:
231 for repo_include in repo_includes:
232 if req_include.startswith(repo_include + '/'):
232 if req_include.startswith(repo_include + '/'):
233 valid = True
233 valid = True
234 res_includes.append(req_include)
234 res_includes.append(req_include)
235 break
235 break
236 if not valid:
236 if not valid:
237 invalid_includes.append(req_include)
237 invalid_includes.append(req_include)
238 if len(res_includes) == 0:
238 if len(res_includes) == 0:
239 res_excludes = {'path:.'}
239 res_excludes = {'path:.'}
240 else:
240 else:
241 res_includes = set(res_includes)
241 res_includes = set(res_includes)
242 else:
242 else:
243 res_includes = set(req_includes)
243 res_includes = set(req_includes)
244 return res_includes, res_excludes, invalid_includes
244 return res_includes, res_excludes, invalid_includes
245
245
246 # These two are extracted for extensions (specifically for Google's CitC file
246 # These two are extracted for extensions (specifically for Google's CitC file
247 # system)
247 # system)
248 def _deletecleanfiles(repo, files):
248 def _deletecleanfiles(repo, files):
249 for f in files:
249 for f in files:
250 repo.wvfs.unlinkpath(f)
250 repo.wvfs.unlinkpath(f)
251
251
252 def _writeaddedfiles(repo, pctx, files):
252 def _writeaddedfiles(repo, pctx, files):
253 actions = merge.emptyactions()
253 actions = merge.emptyactions()
254 addgaction = actions['g'].append
254 addgaction = actions['g'].append
255 mf = repo['.'].manifest()
255 mf = repo['.'].manifest()
256 for f in files:
256 for f in files:
257 if not repo.wvfs.exists(f):
257 if not repo.wvfs.exists(f):
258 addgaction((f, (mf.flags(f), False), "narrowspec updated"))
258 addgaction((f, (mf.flags(f), False), "narrowspec updated"))
259 merge.applyupdates(repo, actions, wctx=repo[None],
259 merge.applyupdates(repo, actions, wctx=repo[None],
260 mctx=repo['.'], overwrite=False)
260 mctx=repo['.'], overwrite=False)
261
261
262 def checkworkingcopynarrowspec(repo):
262 def checkworkingcopynarrowspec(repo):
263 storespec = repo.svfs.tryread(FILENAME)
263 storespec = repo.svfs.tryread(FILENAME)
264 wcspec = repo.vfs.tryread(DIRSTATE_FILENAME)
264 wcspec = repo.vfs.tryread(DIRSTATE_FILENAME)
265 if wcspec != storespec:
265 if wcspec != storespec:
266 raise error.Abort(_("working copy's narrowspec is stale"),
266 raise error.Abort(_("working copy's narrowspec is stale"),
267 hint=_("run 'hg tracked --update-working-copy'"))
267 hint=_("run 'hg tracked --update-working-copy'"))
268
268
269 def updateworkingcopy(repo, tr):
269 def updateworkingcopy(repo):
270 oldspec = repo.vfs.tryread(DIRSTATE_FILENAME)
270 oldspec = repo.vfs.tryread(DIRSTATE_FILENAME)
271 newspec = repo.svfs.tryread(FILENAME)
271 newspec = repo.svfs.tryread(FILENAME)
272
272
273 oldincludes, oldexcludes = parseconfig(repo.ui, oldspec)
273 oldincludes, oldexcludes = parseconfig(repo.ui, oldspec)
274 newincludes, newexcludes = parseconfig(repo.ui, newspec)
274 newincludes, newexcludes = parseconfig(repo.ui, newspec)
275 oldmatch = match(repo.root, include=oldincludes, exclude=oldexcludes)
275 oldmatch = match(repo.root, include=oldincludes, exclude=oldexcludes)
276 newmatch = match(repo.root, include=newincludes, exclude=newexcludes)
276 newmatch = match(repo.root, include=newincludes, exclude=newexcludes)
277 addedmatch = matchmod.differencematcher(newmatch, oldmatch)
277 addedmatch = matchmod.differencematcher(newmatch, oldmatch)
278 removedmatch = matchmod.differencematcher(oldmatch, newmatch)
278 removedmatch = matchmod.differencematcher(oldmatch, newmatch)
279
279
280 ds = repo.dirstate
280 ds = repo.dirstate
281 lookup, status = ds.status(removedmatch, subrepos=[], ignored=False,
281 lookup, status = ds.status(removedmatch, subrepos=[], ignored=False,
282 clean=True, unknown=False)
282 clean=True, unknown=False)
283 _deletecleanfiles(repo, status.clean)
283 _deletecleanfiles(repo, status.clean)
284 trackeddirty = lookup + status.modified + status.added
284 trackeddirty = lookup + status.modified + status.added
285 for f in sorted(trackeddirty):
285 for f in sorted(trackeddirty):
286 repo.ui.status(_('not deleting possibly dirty file %s\n') % f)
286 repo.ui.status(_('not deleting possibly dirty file %s\n') % f)
287 for f in status.clean + trackeddirty:
287 for f in status.clean + trackeddirty:
288 ds.drop(f)
288 ds.drop(f)
289
289
290 repo.narrowpats = newincludes, newexcludes
290 repo.narrowpats = newincludes, newexcludes
291 repo._narrowmatch = newmatch
291 repo._narrowmatch = newmatch
292 pctx = repo['.']
292 pctx = repo['.']
293 newfiles = [f for f in pctx.manifest().walk(addedmatch) if f not in ds]
293 newfiles = [f for f in pctx.manifest().walk(addedmatch) if f not in ds]
294 for f in newfiles:
294 for f in newfiles:
295 ds.normallookup(f)
295 ds.normallookup(f)
296 _writeaddedfiles(repo, pctx, newfiles)
296 _writeaddedfiles(repo, pctx, newfiles)
297
298 ds.write(tr)
General Comments 0
You need to be logged in to leave comments. Login now