##// END OF EJS Templates
cleanup: prefer nested context managers to \-continuations...
Augie Fackler -
r41926:1eb2fc21 default
parent child Browse files
Show More
@@ -1,1503 +1,1503 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import copy
12 import copy
13 import os
13 import os
14
14
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 from mercurial.hgweb import (
17 from mercurial.hgweb import (
18 webcommands,
18 webcommands,
19 )
19 )
20
20
21 from mercurial import (
21 from mercurial import (
22 archival,
22 archival,
23 cmdutil,
23 cmdutil,
24 copies as copiesmod,
24 copies as copiesmod,
25 error,
25 error,
26 exchange,
26 exchange,
27 extensions,
27 extensions,
28 exthelper,
28 exthelper,
29 filemerge,
29 filemerge,
30 hg,
30 hg,
31 logcmdutil,
31 logcmdutil,
32 match as matchmod,
32 match as matchmod,
33 merge,
33 merge,
34 pathutil,
34 pathutil,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 smartset,
37 smartset,
38 subrepo,
38 subrepo,
39 upgrade,
39 upgrade,
40 url as urlmod,
40 url as urlmod,
41 util,
41 util,
42 )
42 )
43
43
44 from . import (
44 from . import (
45 lfcommands,
45 lfcommands,
46 lfutil,
46 lfutil,
47 storefactory,
47 storefactory,
48 )
48 )
49
49
50 eh = exthelper.exthelper()
50 eh = exthelper.exthelper()
51
51
52 # -- Utility functions: commonly/repeatedly needed functionality ---------------
52 # -- Utility functions: commonly/repeatedly needed functionality ---------------
53
53
54 def composelargefilematcher(match, manifest):
54 def composelargefilematcher(match, manifest):
55 '''create a matcher that matches only the largefiles in the original
55 '''create a matcher that matches only the largefiles in the original
56 matcher'''
56 matcher'''
57 m = copy.copy(match)
57 m = copy.copy(match)
58 lfile = lambda f: lfutil.standin(f) in manifest
58 lfile = lambda f: lfutil.standin(f) in manifest
59 m._files = [lf for lf in m._files if lfile(lf)]
59 m._files = [lf for lf in m._files if lfile(lf)]
60 m._fileset = set(m._files)
60 m._fileset = set(m._files)
61 m.always = lambda: False
61 m.always = lambda: False
62 origmatchfn = m.matchfn
62 origmatchfn = m.matchfn
63 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
63 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
64 return m
64 return m
65
65
66 def composenormalfilematcher(match, manifest, exclude=None):
66 def composenormalfilematcher(match, manifest, exclude=None):
67 excluded = set()
67 excluded = set()
68 if exclude is not None:
68 if exclude is not None:
69 excluded.update(exclude)
69 excluded.update(exclude)
70
70
71 m = copy.copy(match)
71 m = copy.copy(match)
72 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
72 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
73 manifest or f in excluded)
73 manifest or f in excluded)
74 m._files = [lf for lf in m._files if notlfile(lf)]
74 m._files = [lf for lf in m._files if notlfile(lf)]
75 m._fileset = set(m._files)
75 m._fileset = set(m._files)
76 m.always = lambda: False
76 m.always = lambda: False
77 origmatchfn = m.matchfn
77 origmatchfn = m.matchfn
78 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
78 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
79 return m
79 return m
80
80
81 def addlargefiles(ui, repo, isaddremove, matcher, uipathfn, **opts):
81 def addlargefiles(ui, repo, isaddremove, matcher, uipathfn, **opts):
82 large = opts.get(r'large')
82 large = opts.get(r'large')
83 lfsize = lfutil.getminsize(
83 lfsize = lfutil.getminsize(
84 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
84 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
85
85
86 lfmatcher = None
86 lfmatcher = None
87 if lfutil.islfilesrepo(repo):
87 if lfutil.islfilesrepo(repo):
88 lfpats = ui.configlist(lfutil.longname, 'patterns')
88 lfpats = ui.configlist(lfutil.longname, 'patterns')
89 if lfpats:
89 if lfpats:
90 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
90 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
91
91
92 lfnames = []
92 lfnames = []
93 m = matcher
93 m = matcher
94
94
95 wctx = repo[None]
95 wctx = repo[None]
96 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
96 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
97 exact = m.exact(f)
97 exact = m.exact(f)
98 lfile = lfutil.standin(f) in wctx
98 lfile = lfutil.standin(f) in wctx
99 nfile = f in wctx
99 nfile = f in wctx
100 exists = lfile or nfile
100 exists = lfile or nfile
101
101
102 # Don't warn the user when they attempt to add a normal tracked file.
102 # Don't warn the user when they attempt to add a normal tracked file.
103 # The normal add code will do that for us.
103 # The normal add code will do that for us.
104 if exact and exists:
104 if exact and exists:
105 if lfile:
105 if lfile:
106 ui.warn(_('%s already a largefile\n') % uipathfn(f))
106 ui.warn(_('%s already a largefile\n') % uipathfn(f))
107 continue
107 continue
108
108
109 if (exact or not exists) and not lfutil.isstandin(f):
109 if (exact or not exists) and not lfutil.isstandin(f):
110 # In case the file was removed previously, but not committed
110 # In case the file was removed previously, but not committed
111 # (issue3507)
111 # (issue3507)
112 if not repo.wvfs.exists(f):
112 if not repo.wvfs.exists(f):
113 continue
113 continue
114
114
115 abovemin = (lfsize and
115 abovemin = (lfsize and
116 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
116 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
117 if large or abovemin or (lfmatcher and lfmatcher(f)):
117 if large or abovemin or (lfmatcher and lfmatcher(f)):
118 lfnames.append(f)
118 lfnames.append(f)
119 if ui.verbose or not exact:
119 if ui.verbose or not exact:
120 ui.status(_('adding %s as a largefile\n') % uipathfn(f))
120 ui.status(_('adding %s as a largefile\n') % uipathfn(f))
121
121
122 bad = []
122 bad = []
123
123
124 # Need to lock, otherwise there could be a race condition between
124 # Need to lock, otherwise there could be a race condition between
125 # when standins are created and added to the repo.
125 # when standins are created and added to the repo.
126 with repo.wlock():
126 with repo.wlock():
127 if not opts.get(r'dry_run'):
127 if not opts.get(r'dry_run'):
128 standins = []
128 standins = []
129 lfdirstate = lfutil.openlfdirstate(ui, repo)
129 lfdirstate = lfutil.openlfdirstate(ui, repo)
130 for f in lfnames:
130 for f in lfnames:
131 standinname = lfutil.standin(f)
131 standinname = lfutil.standin(f)
132 lfutil.writestandin(repo, standinname, hash='',
132 lfutil.writestandin(repo, standinname, hash='',
133 executable=lfutil.getexecutable(repo.wjoin(f)))
133 executable=lfutil.getexecutable(repo.wjoin(f)))
134 standins.append(standinname)
134 standins.append(standinname)
135 if lfdirstate[f] == 'r':
135 if lfdirstate[f] == 'r':
136 lfdirstate.normallookup(f)
136 lfdirstate.normallookup(f)
137 else:
137 else:
138 lfdirstate.add(f)
138 lfdirstate.add(f)
139 lfdirstate.write()
139 lfdirstate.write()
140 bad += [lfutil.splitstandin(f)
140 bad += [lfutil.splitstandin(f)
141 for f in repo[None].add(standins)
141 for f in repo[None].add(standins)
142 if f in m.files()]
142 if f in m.files()]
143
143
144 added = [f for f in lfnames if f not in bad]
144 added = [f for f in lfnames if f not in bad]
145 return added, bad
145 return added, bad
146
146
147 def removelargefiles(ui, repo, isaddremove, matcher, uipathfn, dryrun, **opts):
147 def removelargefiles(ui, repo, isaddremove, matcher, uipathfn, dryrun, **opts):
148 after = opts.get(r'after')
148 after = opts.get(r'after')
149 m = composelargefilematcher(matcher, repo[None].manifest())
149 m = composelargefilematcher(matcher, repo[None].manifest())
150 try:
150 try:
151 repo.lfstatus = True
151 repo.lfstatus = True
152 s = repo.status(match=m, clean=not isaddremove)
152 s = repo.status(match=m, clean=not isaddremove)
153 finally:
153 finally:
154 repo.lfstatus = False
154 repo.lfstatus = False
155 manifest = repo[None].manifest()
155 manifest = repo[None].manifest()
156 modified, added, deleted, clean = [[f for f in list
156 modified, added, deleted, clean = [[f for f in list
157 if lfutil.standin(f) in manifest]
157 if lfutil.standin(f) in manifest]
158 for list in (s.modified, s.added,
158 for list in (s.modified, s.added,
159 s.deleted, s.clean)]
159 s.deleted, s.clean)]
160
160
161 def warn(files, msg):
161 def warn(files, msg):
162 for f in files:
162 for f in files:
163 ui.warn(msg % uipathfn(f))
163 ui.warn(msg % uipathfn(f))
164 return int(len(files) > 0)
164 return int(len(files) > 0)
165
165
166 if after:
166 if after:
167 remove = deleted
167 remove = deleted
168 result = warn(modified + added + clean,
168 result = warn(modified + added + clean,
169 _('not removing %s: file still exists\n'))
169 _('not removing %s: file still exists\n'))
170 else:
170 else:
171 remove = deleted + clean
171 remove = deleted + clean
172 result = warn(modified, _('not removing %s: file is modified (use -f'
172 result = warn(modified, _('not removing %s: file is modified (use -f'
173 ' to force removal)\n'))
173 ' to force removal)\n'))
174 result = warn(added, _('not removing %s: file has been marked for add'
174 result = warn(added, _('not removing %s: file has been marked for add'
175 ' (use forget to undo)\n')) or result
175 ' (use forget to undo)\n')) or result
176
176
177 # Need to lock because standin files are deleted then removed from the
177 # Need to lock because standin files are deleted then removed from the
178 # repository and we could race in-between.
178 # repository and we could race in-between.
179 with repo.wlock():
179 with repo.wlock():
180 lfdirstate = lfutil.openlfdirstate(ui, repo)
180 lfdirstate = lfutil.openlfdirstate(ui, repo)
181 for f in sorted(remove):
181 for f in sorted(remove):
182 if ui.verbose or not m.exact(f):
182 if ui.verbose or not m.exact(f):
183 ui.status(_('removing %s\n') % uipathfn(f))
183 ui.status(_('removing %s\n') % uipathfn(f))
184
184
185 if not dryrun:
185 if not dryrun:
186 if not after:
186 if not after:
187 repo.wvfs.unlinkpath(f, ignoremissing=True)
187 repo.wvfs.unlinkpath(f, ignoremissing=True)
188
188
189 if dryrun:
189 if dryrun:
190 return result
190 return result
191
191
192 remove = [lfutil.standin(f) for f in remove]
192 remove = [lfutil.standin(f) for f in remove]
193 # If this is being called by addremove, let the original addremove
193 # If this is being called by addremove, let the original addremove
194 # function handle this.
194 # function handle this.
195 if not isaddremove:
195 if not isaddremove:
196 for f in remove:
196 for f in remove:
197 repo.wvfs.unlinkpath(f, ignoremissing=True)
197 repo.wvfs.unlinkpath(f, ignoremissing=True)
198 repo[None].forget(remove)
198 repo[None].forget(remove)
199
199
200 for f in remove:
200 for f in remove:
201 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
201 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
202 False)
202 False)
203
203
204 lfdirstate.write()
204 lfdirstate.write()
205
205
206 return result
206 return result
207
207
208 # For overriding mercurial.hgweb.webcommands so that largefiles will
208 # For overriding mercurial.hgweb.webcommands so that largefiles will
209 # appear at their right place in the manifests.
209 # appear at their right place in the manifests.
210 @eh.wrapfunction(webcommands, 'decodepath')
210 @eh.wrapfunction(webcommands, 'decodepath')
211 def decodepath(orig, path):
211 def decodepath(orig, path):
212 return lfutil.splitstandin(path) or path
212 return lfutil.splitstandin(path) or path
213
213
214 # -- Wrappers: modify existing commands --------------------------------
214 # -- Wrappers: modify existing commands --------------------------------
215
215
216 @eh.wrapcommand('add',
216 @eh.wrapcommand('add',
217 opts=[('', 'large', None, _('add as largefile')),
217 opts=[('', 'large', None, _('add as largefile')),
218 ('', 'normal', None, _('add as normal file')),
218 ('', 'normal', None, _('add as normal file')),
219 ('', 'lfsize', '', _('add all files above this size (in megabytes) '
219 ('', 'lfsize', '', _('add all files above this size (in megabytes) '
220 'as largefiles (default: 10)'))])
220 'as largefiles (default: 10)'))])
221 def overrideadd(orig, ui, repo, *pats, **opts):
221 def overrideadd(orig, ui, repo, *pats, **opts):
222 if opts.get(r'normal') and opts.get(r'large'):
222 if opts.get(r'normal') and opts.get(r'large'):
223 raise error.Abort(_('--normal cannot be used with --large'))
223 raise error.Abort(_('--normal cannot be used with --large'))
224 return orig(ui, repo, *pats, **opts)
224 return orig(ui, repo, *pats, **opts)
225
225
226 @eh.wrapfunction(cmdutil, 'add')
226 @eh.wrapfunction(cmdutil, 'add')
227 def cmdutiladd(orig, ui, repo, matcher, prefix, uipathfn, explicitonly, **opts):
227 def cmdutiladd(orig, ui, repo, matcher, prefix, uipathfn, explicitonly, **opts):
228 # The --normal flag short circuits this override
228 # The --normal flag short circuits this override
229 if opts.get(r'normal'):
229 if opts.get(r'normal'):
230 return orig(ui, repo, matcher, prefix, uipathfn, explicitonly, **opts)
230 return orig(ui, repo, matcher, prefix, uipathfn, explicitonly, **opts)
231
231
232 ladded, lbad = addlargefiles(ui, repo, False, matcher, uipathfn, **opts)
232 ladded, lbad = addlargefiles(ui, repo, False, matcher, uipathfn, **opts)
233 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
233 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
234 ladded)
234 ladded)
235 bad = orig(ui, repo, normalmatcher, prefix, uipathfn, explicitonly, **opts)
235 bad = orig(ui, repo, normalmatcher, prefix, uipathfn, explicitonly, **opts)
236
236
237 bad.extend(f for f in lbad)
237 bad.extend(f for f in lbad)
238 return bad
238 return bad
239
239
240 @eh.wrapfunction(cmdutil, 'remove')
240 @eh.wrapfunction(cmdutil, 'remove')
241 def cmdutilremove(orig, ui, repo, matcher, prefix, uipathfn, after, force,
241 def cmdutilremove(orig, ui, repo, matcher, prefix, uipathfn, after, force,
242 subrepos, dryrun):
242 subrepos, dryrun):
243 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
243 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
244 result = orig(ui, repo, normalmatcher, prefix, uipathfn, after, force,
244 result = orig(ui, repo, normalmatcher, prefix, uipathfn, after, force,
245 subrepos, dryrun)
245 subrepos, dryrun)
246 return removelargefiles(ui, repo, False, matcher, uipathfn, dryrun,
246 return removelargefiles(ui, repo, False, matcher, uipathfn, dryrun,
247 after=after, force=force) or result
247 after=after, force=force) or result
248
248
249 @eh.wrapfunction(subrepo.hgsubrepo, 'status')
249 @eh.wrapfunction(subrepo.hgsubrepo, 'status')
250 def overridestatusfn(orig, repo, rev2, **opts):
250 def overridestatusfn(orig, repo, rev2, **opts):
251 try:
251 try:
252 repo._repo.lfstatus = True
252 repo._repo.lfstatus = True
253 return orig(repo, rev2, **opts)
253 return orig(repo, rev2, **opts)
254 finally:
254 finally:
255 repo._repo.lfstatus = False
255 repo._repo.lfstatus = False
256
256
257 @eh.wrapcommand('status')
257 @eh.wrapcommand('status')
258 def overridestatus(orig, ui, repo, *pats, **opts):
258 def overridestatus(orig, ui, repo, *pats, **opts):
259 try:
259 try:
260 repo.lfstatus = True
260 repo.lfstatus = True
261 return orig(ui, repo, *pats, **opts)
261 return orig(ui, repo, *pats, **opts)
262 finally:
262 finally:
263 repo.lfstatus = False
263 repo.lfstatus = False
264
264
265 @eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
265 @eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
266 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
266 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
267 try:
267 try:
268 repo._repo.lfstatus = True
268 repo._repo.lfstatus = True
269 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
269 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
270 finally:
270 finally:
271 repo._repo.lfstatus = False
271 repo._repo.lfstatus = False
272
272
273 @eh.wrapcommand('log')
273 @eh.wrapcommand('log')
274 def overridelog(orig, ui, repo, *pats, **opts):
274 def overridelog(orig, ui, repo, *pats, **opts):
275 def overridematchandpats(orig, ctx, pats=(), opts=None, globbed=False,
275 def overridematchandpats(orig, ctx, pats=(), opts=None, globbed=False,
276 default='relpath', badfn=None):
276 default='relpath', badfn=None):
277 """Matcher that merges root directory with .hglf, suitable for log.
277 """Matcher that merges root directory with .hglf, suitable for log.
278 It is still possible to match .hglf directly.
278 It is still possible to match .hglf directly.
279 For any listed files run log on the standin too.
279 For any listed files run log on the standin too.
280 matchfn tries both the given filename and with .hglf stripped.
280 matchfn tries both the given filename and with .hglf stripped.
281 """
281 """
282 if opts is None:
282 if opts is None:
283 opts = {}
283 opts = {}
284 matchandpats = orig(ctx, pats, opts, globbed, default, badfn=badfn)
284 matchandpats = orig(ctx, pats, opts, globbed, default, badfn=badfn)
285 m, p = copy.copy(matchandpats)
285 m, p = copy.copy(matchandpats)
286
286
287 if m.always():
287 if m.always():
288 # We want to match everything anyway, so there's no benefit trying
288 # We want to match everything anyway, so there's no benefit trying
289 # to add standins.
289 # to add standins.
290 return matchandpats
290 return matchandpats
291
291
292 pats = set(p)
292 pats = set(p)
293
293
294 def fixpats(pat, tostandin=lfutil.standin):
294 def fixpats(pat, tostandin=lfutil.standin):
295 if pat.startswith('set:'):
295 if pat.startswith('set:'):
296 return pat
296 return pat
297
297
298 kindpat = matchmod._patsplit(pat, None)
298 kindpat = matchmod._patsplit(pat, None)
299
299
300 if kindpat[0] is not None:
300 if kindpat[0] is not None:
301 return kindpat[0] + ':' + tostandin(kindpat[1])
301 return kindpat[0] + ':' + tostandin(kindpat[1])
302 return tostandin(kindpat[1])
302 return tostandin(kindpat[1])
303
303
304 cwd = repo.getcwd()
304 cwd = repo.getcwd()
305 if cwd:
305 if cwd:
306 hglf = lfutil.shortname
306 hglf = lfutil.shortname
307 back = util.pconvert(repo.pathto(hglf)[:-len(hglf)])
307 back = util.pconvert(repo.pathto(hglf)[:-len(hglf)])
308
308
309 def tostandin(f):
309 def tostandin(f):
310 # The file may already be a standin, so truncate the back
310 # The file may already be a standin, so truncate the back
311 # prefix and test before mangling it. This avoids turning
311 # prefix and test before mangling it. This avoids turning
312 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
312 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
313 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
313 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
314 return f
314 return f
315
315
316 # An absolute path is from outside the repo, so truncate the
316 # An absolute path is from outside the repo, so truncate the
317 # path to the root before building the standin. Otherwise cwd
317 # path to the root before building the standin. Otherwise cwd
318 # is somewhere in the repo, relative to root, and needs to be
318 # is somewhere in the repo, relative to root, and needs to be
319 # prepended before building the standin.
319 # prepended before building the standin.
320 if os.path.isabs(cwd):
320 if os.path.isabs(cwd):
321 f = f[len(back):]
321 f = f[len(back):]
322 else:
322 else:
323 f = cwd + '/' + f
323 f = cwd + '/' + f
324 return back + lfutil.standin(f)
324 return back + lfutil.standin(f)
325 else:
325 else:
326 def tostandin(f):
326 def tostandin(f):
327 if lfutil.isstandin(f):
327 if lfutil.isstandin(f):
328 return f
328 return f
329 return lfutil.standin(f)
329 return lfutil.standin(f)
330 pats.update(fixpats(f, tostandin) for f in p)
330 pats.update(fixpats(f, tostandin) for f in p)
331
331
332 for i in range(0, len(m._files)):
332 for i in range(0, len(m._files)):
333 # Don't add '.hglf' to m.files, since that is already covered by '.'
333 # Don't add '.hglf' to m.files, since that is already covered by '.'
334 if m._files[i] == '.':
334 if m._files[i] == '.':
335 continue
335 continue
336 standin = lfutil.standin(m._files[i])
336 standin = lfutil.standin(m._files[i])
337 # If the "standin" is a directory, append instead of replace to
337 # If the "standin" is a directory, append instead of replace to
338 # support naming a directory on the command line with only
338 # support naming a directory on the command line with only
339 # largefiles. The original directory is kept to support normal
339 # largefiles. The original directory is kept to support normal
340 # files.
340 # files.
341 if standin in ctx:
341 if standin in ctx:
342 m._files[i] = standin
342 m._files[i] = standin
343 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
343 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
344 m._files.append(standin)
344 m._files.append(standin)
345
345
346 m._fileset = set(m._files)
346 m._fileset = set(m._files)
347 m.always = lambda: False
347 m.always = lambda: False
348 origmatchfn = m.matchfn
348 origmatchfn = m.matchfn
349 def lfmatchfn(f):
349 def lfmatchfn(f):
350 lf = lfutil.splitstandin(f)
350 lf = lfutil.splitstandin(f)
351 if lf is not None and origmatchfn(lf):
351 if lf is not None and origmatchfn(lf):
352 return True
352 return True
353 r = origmatchfn(f)
353 r = origmatchfn(f)
354 return r
354 return r
355 m.matchfn = lfmatchfn
355 m.matchfn = lfmatchfn
356
356
357 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
357 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
358 return m, pats
358 return m, pats
359
359
360 # For hg log --patch, the match object is used in two different senses:
360 # For hg log --patch, the match object is used in two different senses:
361 # (1) to determine what revisions should be printed out, and
361 # (1) to determine what revisions should be printed out, and
362 # (2) to determine what files to print out diffs for.
362 # (2) to determine what files to print out diffs for.
363 # The magic matchandpats override should be used for case (1) but not for
363 # The magic matchandpats override should be used for case (1) but not for
364 # case (2).
364 # case (2).
365 oldmatchandpats = scmutil.matchandpats
365 oldmatchandpats = scmutil.matchandpats
366 def overridemakefilematcher(orig, repo, pats, opts, badfn=None):
366 def overridemakefilematcher(orig, repo, pats, opts, badfn=None):
367 wctx = repo[None]
367 wctx = repo[None]
368 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
368 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
369 return lambda ctx: match
369 return lambda ctx: match
370
370
371 wrappedmatchandpats = extensions.wrappedfunction(scmutil, 'matchandpats',
371 wrappedmatchandpats = extensions.wrappedfunction(scmutil, 'matchandpats',
372 overridematchandpats)
372 overridematchandpats)
373 wrappedmakefilematcher = extensions.wrappedfunction(
373 wrappedmakefilematcher = extensions.wrappedfunction(
374 logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
374 logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
375 with wrappedmatchandpats, wrappedmakefilematcher:
375 with wrappedmatchandpats, wrappedmakefilematcher:
376 return orig(ui, repo, *pats, **opts)
376 return orig(ui, repo, *pats, **opts)
377
377
378 @eh.wrapcommand('verify',
378 @eh.wrapcommand('verify',
379 opts=[('', 'large', None,
379 opts=[('', 'large', None,
380 _('verify that all largefiles in current revision exists')),
380 _('verify that all largefiles in current revision exists')),
381 ('', 'lfa', None,
381 ('', 'lfa', None,
382 _('verify largefiles in all revisions, not just current')),
382 _('verify largefiles in all revisions, not just current')),
383 ('', 'lfc', None,
383 ('', 'lfc', None,
384 _('verify local largefile contents, not just existence'))])
384 _('verify local largefile contents, not just existence'))])
385 def overrideverify(orig, ui, repo, *pats, **opts):
385 def overrideverify(orig, ui, repo, *pats, **opts):
386 large = opts.pop(r'large', False)
386 large = opts.pop(r'large', False)
387 all = opts.pop(r'lfa', False)
387 all = opts.pop(r'lfa', False)
388 contents = opts.pop(r'lfc', False)
388 contents = opts.pop(r'lfc', False)
389
389
390 result = orig(ui, repo, *pats, **opts)
390 result = orig(ui, repo, *pats, **opts)
391 if large or all or contents:
391 if large or all or contents:
392 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
392 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
393 return result
393 return result
394
394
395 @eh.wrapcommand('debugstate',
395 @eh.wrapcommand('debugstate',
396 opts=[('', 'large', None, _('display largefiles dirstate'))])
396 opts=[('', 'large', None, _('display largefiles dirstate'))])
397 def overridedebugstate(orig, ui, repo, *pats, **opts):
397 def overridedebugstate(orig, ui, repo, *pats, **opts):
398 large = opts.pop(r'large', False)
398 large = opts.pop(r'large', False)
399 if large:
399 if large:
400 class fakerepo(object):
400 class fakerepo(object):
401 dirstate = lfutil.openlfdirstate(ui, repo)
401 dirstate = lfutil.openlfdirstate(ui, repo)
402 orig(ui, fakerepo, *pats, **opts)
402 orig(ui, fakerepo, *pats, **opts)
403 else:
403 else:
404 orig(ui, repo, *pats, **opts)
404 orig(ui, repo, *pats, **opts)
405
405
406 # Before starting the manifest merge, merge.updates will call
406 # Before starting the manifest merge, merge.updates will call
407 # _checkunknownfile to check if there are any files in the merged-in
407 # _checkunknownfile to check if there are any files in the merged-in
408 # changeset that collide with unknown files in the working copy.
408 # changeset that collide with unknown files in the working copy.
409 #
409 #
410 # The largefiles are seen as unknown, so this prevents us from merging
410 # The largefiles are seen as unknown, so this prevents us from merging
411 # in a file 'foo' if we already have a largefile with the same name.
411 # in a file 'foo' if we already have a largefile with the same name.
412 #
412 #
413 # The overridden function filters the unknown files by removing any
413 # The overridden function filters the unknown files by removing any
414 # largefiles. This makes the merge proceed and we can then handle this
414 # largefiles. This makes the merge proceed and we can then handle this
415 # case further in the overridden calculateupdates function below.
415 # case further in the overridden calculateupdates function below.
416 @eh.wrapfunction(merge, '_checkunknownfile')
416 @eh.wrapfunction(merge, '_checkunknownfile')
417 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
417 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
418 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
418 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
419 return False
419 return False
420 return origfn(repo, wctx, mctx, f, f2)
420 return origfn(repo, wctx, mctx, f, f2)
421
421
422 # The manifest merge handles conflicts on the manifest level. We want
422 # The manifest merge handles conflicts on the manifest level. We want
423 # to handle changes in largefile-ness of files at this level too.
423 # to handle changes in largefile-ness of files at this level too.
424 #
424 #
425 # The strategy is to run the original calculateupdates and then process
425 # The strategy is to run the original calculateupdates and then process
426 # the action list it outputs. There are two cases we need to deal with:
426 # the action list it outputs. There are two cases we need to deal with:
427 #
427 #
428 # 1. Normal file in p1, largefile in p2. Here the largefile is
428 # 1. Normal file in p1, largefile in p2. Here the largefile is
429 # detected via its standin file, which will enter the working copy
429 # detected via its standin file, which will enter the working copy
430 # with a "get" action. It is not "merge" since the standin is all
430 # with a "get" action. It is not "merge" since the standin is all
431 # Mercurial is concerned with at this level -- the link to the
431 # Mercurial is concerned with at this level -- the link to the
432 # existing normal file is not relevant here.
432 # existing normal file is not relevant here.
433 #
433 #
434 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
434 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
435 # since the largefile will be present in the working copy and
435 # since the largefile will be present in the working copy and
436 # different from the normal file in p2. Mercurial therefore
436 # different from the normal file in p2. Mercurial therefore
437 # triggers a merge action.
437 # triggers a merge action.
438 #
438 #
439 # In both cases, we prompt the user and emit new actions to either
439 # In both cases, we prompt the user and emit new actions to either
440 # remove the standin (if the normal file was kept) or to remove the
440 # remove the standin (if the normal file was kept) or to remove the
441 # normal file and get the standin (if the largefile was kept). The
441 # normal file and get the standin (if the largefile was kept). The
442 # default prompt answer is to use the largefile version since it was
442 # default prompt answer is to use the largefile version since it was
443 # presumably changed on purpose.
443 # presumably changed on purpose.
444 #
444 #
445 # Finally, the merge.applyupdates function will then take care of
445 # Finally, the merge.applyupdates function will then take care of
446 # writing the files into the working copy and lfcommands.updatelfiles
446 # writing the files into the working copy and lfcommands.updatelfiles
447 # will update the largefiles.
447 # will update the largefiles.
448 @eh.wrapfunction(merge, 'calculateupdates')
448 @eh.wrapfunction(merge, 'calculateupdates')
449 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
449 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
450 acceptremote, *args, **kwargs):
450 acceptremote, *args, **kwargs):
451 overwrite = force and not branchmerge
451 overwrite = force and not branchmerge
452 actions, diverge, renamedelete = origfn(
452 actions, diverge, renamedelete = origfn(
453 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
453 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
454
454
455 if overwrite:
455 if overwrite:
456 return actions, diverge, renamedelete
456 return actions, diverge, renamedelete
457
457
458 # Convert to dictionary with filename as key and action as value.
458 # Convert to dictionary with filename as key and action as value.
459 lfiles = set()
459 lfiles = set()
460 for f in actions:
460 for f in actions:
461 splitstandin = lfutil.splitstandin(f)
461 splitstandin = lfutil.splitstandin(f)
462 if splitstandin in p1:
462 if splitstandin in p1:
463 lfiles.add(splitstandin)
463 lfiles.add(splitstandin)
464 elif lfutil.standin(f) in p1:
464 elif lfutil.standin(f) in p1:
465 lfiles.add(f)
465 lfiles.add(f)
466
466
467 for lfile in sorted(lfiles):
467 for lfile in sorted(lfiles):
468 standin = lfutil.standin(lfile)
468 standin = lfutil.standin(lfile)
469 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
469 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
470 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
470 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
471 if sm in ('g', 'dc') and lm != 'r':
471 if sm in ('g', 'dc') and lm != 'r':
472 if sm == 'dc':
472 if sm == 'dc':
473 f1, f2, fa, move, anc = sargs
473 f1, f2, fa, move, anc = sargs
474 sargs = (p2[f2].flags(), False)
474 sargs = (p2[f2].flags(), False)
475 # Case 1: normal file in the working copy, largefile in
475 # Case 1: normal file in the working copy, largefile in
476 # the second parent
476 # the second parent
477 usermsg = _('remote turned local normal file %s into a largefile\n'
477 usermsg = _('remote turned local normal file %s into a largefile\n'
478 'use (l)argefile or keep (n)ormal file?'
478 'use (l)argefile or keep (n)ormal file?'
479 '$$ &Largefile $$ &Normal file') % lfile
479 '$$ &Largefile $$ &Normal file') % lfile
480 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
480 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
481 actions[lfile] = ('r', None, 'replaced by standin')
481 actions[lfile] = ('r', None, 'replaced by standin')
482 actions[standin] = ('g', sargs, 'replaces standin')
482 actions[standin] = ('g', sargs, 'replaces standin')
483 else: # keep local normal file
483 else: # keep local normal file
484 actions[lfile] = ('k', None, 'replaces standin')
484 actions[lfile] = ('k', None, 'replaces standin')
485 if branchmerge:
485 if branchmerge:
486 actions[standin] = ('k', None, 'replaced by non-standin')
486 actions[standin] = ('k', None, 'replaced by non-standin')
487 else:
487 else:
488 actions[standin] = ('r', None, 'replaced by non-standin')
488 actions[standin] = ('r', None, 'replaced by non-standin')
489 elif lm in ('g', 'dc') and sm != 'r':
489 elif lm in ('g', 'dc') and sm != 'r':
490 if lm == 'dc':
490 if lm == 'dc':
491 f1, f2, fa, move, anc = largs
491 f1, f2, fa, move, anc = largs
492 largs = (p2[f2].flags(), False)
492 largs = (p2[f2].flags(), False)
493 # Case 2: largefile in the working copy, normal file in
493 # Case 2: largefile in the working copy, normal file in
494 # the second parent
494 # the second parent
495 usermsg = _('remote turned local largefile %s into a normal file\n'
495 usermsg = _('remote turned local largefile %s into a normal file\n'
496 'keep (l)argefile or use (n)ormal file?'
496 'keep (l)argefile or use (n)ormal file?'
497 '$$ &Largefile $$ &Normal file') % lfile
497 '$$ &Largefile $$ &Normal file') % lfile
498 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
498 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
499 if branchmerge:
499 if branchmerge:
500 # largefile can be restored from standin safely
500 # largefile can be restored from standin safely
501 actions[lfile] = ('k', None, 'replaced by standin')
501 actions[lfile] = ('k', None, 'replaced by standin')
502 actions[standin] = ('k', None, 'replaces standin')
502 actions[standin] = ('k', None, 'replaces standin')
503 else:
503 else:
504 # "lfile" should be marked as "removed" without
504 # "lfile" should be marked as "removed" without
505 # removal of itself
505 # removal of itself
506 actions[lfile] = ('lfmr', None,
506 actions[lfile] = ('lfmr', None,
507 'forget non-standin largefile')
507 'forget non-standin largefile')
508
508
509 # linear-merge should treat this largefile as 're-added'
509 # linear-merge should treat this largefile as 're-added'
510 actions[standin] = ('a', None, 'keep standin')
510 actions[standin] = ('a', None, 'keep standin')
511 else: # pick remote normal file
511 else: # pick remote normal file
512 actions[lfile] = ('g', largs, 'replaces standin')
512 actions[lfile] = ('g', largs, 'replaces standin')
513 actions[standin] = ('r', None, 'replaced by non-standin')
513 actions[standin] = ('r', None, 'replaced by non-standin')
514
514
515 return actions, diverge, renamedelete
515 return actions, diverge, renamedelete
516
516
517 @eh.wrapfunction(merge, 'recordupdates')
517 @eh.wrapfunction(merge, 'recordupdates')
518 def mergerecordupdates(orig, repo, actions, branchmerge):
518 def mergerecordupdates(orig, repo, actions, branchmerge):
519 if 'lfmr' in actions:
519 if 'lfmr' in actions:
520 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
520 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
521 for lfile, args, msg in actions['lfmr']:
521 for lfile, args, msg in actions['lfmr']:
522 # this should be executed before 'orig', to execute 'remove'
522 # this should be executed before 'orig', to execute 'remove'
523 # before all other actions
523 # before all other actions
524 repo.dirstate.remove(lfile)
524 repo.dirstate.remove(lfile)
525 # make sure lfile doesn't get synclfdirstate'd as normal
525 # make sure lfile doesn't get synclfdirstate'd as normal
526 lfdirstate.add(lfile)
526 lfdirstate.add(lfile)
527 lfdirstate.write()
527 lfdirstate.write()
528
528
529 return orig(repo, actions, branchmerge)
529 return orig(repo, actions, branchmerge)
530
530
531 # Override filemerge to prompt the user about how they wish to merge
531 # Override filemerge to prompt the user about how they wish to merge
532 # largefiles. This will handle identical edits without prompting the user.
532 # largefiles. This will handle identical edits without prompting the user.
533 @eh.wrapfunction(filemerge, '_filemerge')
533 @eh.wrapfunction(filemerge, '_filemerge')
534 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
534 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
535 labels=None):
535 labels=None):
536 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
536 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
537 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
537 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
538 labels=labels)
538 labels=labels)
539
539
540 ahash = lfutil.readasstandin(fca).lower()
540 ahash = lfutil.readasstandin(fca).lower()
541 dhash = lfutil.readasstandin(fcd).lower()
541 dhash = lfutil.readasstandin(fcd).lower()
542 ohash = lfutil.readasstandin(fco).lower()
542 ohash = lfutil.readasstandin(fco).lower()
543 if (ohash != ahash and
543 if (ohash != ahash and
544 ohash != dhash and
544 ohash != dhash and
545 (dhash == ahash or
545 (dhash == ahash or
546 repo.ui.promptchoice(
546 repo.ui.promptchoice(
547 _('largefile %s has a merge conflict\nancestor was %s\n'
547 _('largefile %s has a merge conflict\nancestor was %s\n'
548 'keep (l)ocal %s or\ntake (o)ther %s?'
548 'keep (l)ocal %s or\ntake (o)ther %s?'
549 '$$ &Local $$ &Other') %
549 '$$ &Local $$ &Other') %
550 (lfutil.splitstandin(orig), ahash, dhash, ohash),
550 (lfutil.splitstandin(orig), ahash, dhash, ohash),
551 0) == 1)):
551 0) == 1)):
552 repo.wwrite(fcd.path(), fco.data(), fco.flags())
552 repo.wwrite(fcd.path(), fco.data(), fco.flags())
553 return True, 0, False
553 return True, 0, False
554
554
555 @eh.wrapfunction(copiesmod, 'pathcopies')
555 @eh.wrapfunction(copiesmod, 'pathcopies')
556 def copiespathcopies(orig, ctx1, ctx2, match=None):
556 def copiespathcopies(orig, ctx1, ctx2, match=None):
557 copies = orig(ctx1, ctx2, match=match)
557 copies = orig(ctx1, ctx2, match=match)
558 updated = {}
558 updated = {}
559
559
560 for k, v in copies.iteritems():
560 for k, v in copies.iteritems():
561 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
561 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
562
562
563 return updated
563 return updated
564
564
565 # Copy first changes the matchers to match standins instead of
565 # Copy first changes the matchers to match standins instead of
566 # largefiles. Then it overrides util.copyfile in that function it
566 # largefiles. Then it overrides util.copyfile in that function it
567 # checks if the destination largefile already exists. It also keeps a
567 # checks if the destination largefile already exists. It also keeps a
568 # list of copied files so that the largefiles can be copied and the
568 # list of copied files so that the largefiles can be copied and the
569 # dirstate updated.
569 # dirstate updated.
570 @eh.wrapfunction(cmdutil, 'copy')
570 @eh.wrapfunction(cmdutil, 'copy')
571 def overridecopy(orig, ui, repo, pats, opts, rename=False):
571 def overridecopy(orig, ui, repo, pats, opts, rename=False):
572 # doesn't remove largefile on rename
572 # doesn't remove largefile on rename
573 if len(pats) < 2:
573 if len(pats) < 2:
574 # this isn't legal, let the original function deal with it
574 # this isn't legal, let the original function deal with it
575 return orig(ui, repo, pats, opts, rename)
575 return orig(ui, repo, pats, opts, rename)
576
576
577 # This could copy both lfiles and normal files in one command,
577 # This could copy both lfiles and normal files in one command,
578 # but we don't want to do that. First replace their matcher to
578 # but we don't want to do that. First replace their matcher to
579 # only match normal files and run it, then replace it to just
579 # only match normal files and run it, then replace it to just
580 # match largefiles and run it again.
580 # match largefiles and run it again.
581 nonormalfiles = False
581 nonormalfiles = False
582 nolfiles = False
582 nolfiles = False
583 manifest = repo[None].manifest()
583 manifest = repo[None].manifest()
584 def normalfilesmatchfn(orig, ctx, pats=(), opts=None, globbed=False,
584 def normalfilesmatchfn(orig, ctx, pats=(), opts=None, globbed=False,
585 default='relpath', badfn=None):
585 default='relpath', badfn=None):
586 if opts is None:
586 if opts is None:
587 opts = {}
587 opts = {}
588 match = orig(ctx, pats, opts, globbed, default, badfn=badfn)
588 match = orig(ctx, pats, opts, globbed, default, badfn=badfn)
589 return composenormalfilematcher(match, manifest)
589 return composenormalfilematcher(match, manifest)
590 with extensions.wrappedfunction(scmutil, 'match', normalfilesmatchfn):
590 with extensions.wrappedfunction(scmutil, 'match', normalfilesmatchfn):
591 try:
591 try:
592 result = orig(ui, repo, pats, opts, rename)
592 result = orig(ui, repo, pats, opts, rename)
593 except error.Abort as e:
593 except error.Abort as e:
594 if pycompat.bytestr(e) != _('no files to copy'):
594 if pycompat.bytestr(e) != _('no files to copy'):
595 raise e
595 raise e
596 else:
596 else:
597 nonormalfiles = True
597 nonormalfiles = True
598 result = 0
598 result = 0
599
599
600 # The first rename can cause our current working directory to be removed.
600 # The first rename can cause our current working directory to be removed.
601 # In that case there is nothing left to copy/rename so just quit.
601 # In that case there is nothing left to copy/rename so just quit.
602 try:
602 try:
603 repo.getcwd()
603 repo.getcwd()
604 except OSError:
604 except OSError:
605 return result
605 return result
606
606
607 def makestandin(relpath):
607 def makestandin(relpath):
608 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
608 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
609 return repo.wvfs.join(lfutil.standin(path))
609 return repo.wvfs.join(lfutil.standin(path))
610
610
611 fullpats = scmutil.expandpats(pats)
611 fullpats = scmutil.expandpats(pats)
612 dest = fullpats[-1]
612 dest = fullpats[-1]
613
613
614 if os.path.isdir(dest):
614 if os.path.isdir(dest):
615 if not os.path.isdir(makestandin(dest)):
615 if not os.path.isdir(makestandin(dest)):
616 os.makedirs(makestandin(dest))
616 os.makedirs(makestandin(dest))
617
617
618 try:
618 try:
619 # When we call orig below it creates the standins but we don't add
619 # When we call orig below it creates the standins but we don't add
620 # them to the dir state until later so lock during that time.
620 # them to the dir state until later so lock during that time.
621 wlock = repo.wlock()
621 wlock = repo.wlock()
622
622
623 manifest = repo[None].manifest()
623 manifest = repo[None].manifest()
624 def overridematch(orig, ctx, pats=(), opts=None, globbed=False,
624 def overridematch(orig, ctx, pats=(), opts=None, globbed=False,
625 default='relpath', badfn=None):
625 default='relpath', badfn=None):
626 if opts is None:
626 if opts is None:
627 opts = {}
627 opts = {}
628 newpats = []
628 newpats = []
629 # The patterns were previously mangled to add the standin
629 # The patterns were previously mangled to add the standin
630 # directory; we need to remove that now
630 # directory; we need to remove that now
631 for pat in pats:
631 for pat in pats:
632 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
632 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
633 newpats.append(pat.replace(lfutil.shortname, ''))
633 newpats.append(pat.replace(lfutil.shortname, ''))
634 else:
634 else:
635 newpats.append(pat)
635 newpats.append(pat)
636 match = orig(ctx, newpats, opts, globbed, default, badfn=badfn)
636 match = orig(ctx, newpats, opts, globbed, default, badfn=badfn)
637 m = copy.copy(match)
637 m = copy.copy(match)
638 lfile = lambda f: lfutil.standin(f) in manifest
638 lfile = lambda f: lfutil.standin(f) in manifest
639 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
639 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
640 m._fileset = set(m._files)
640 m._fileset = set(m._files)
641 origmatchfn = m.matchfn
641 origmatchfn = m.matchfn
642 def matchfn(f):
642 def matchfn(f):
643 lfile = lfutil.splitstandin(f)
643 lfile = lfutil.splitstandin(f)
644 return (lfile is not None and
644 return (lfile is not None and
645 (f in manifest) and
645 (f in manifest) and
646 origmatchfn(lfile) or
646 origmatchfn(lfile) or
647 None)
647 None)
648 m.matchfn = matchfn
648 m.matchfn = matchfn
649 return m
649 return m
650 listpats = []
650 listpats = []
651 for pat in pats:
651 for pat in pats:
652 if matchmod.patkind(pat) is not None:
652 if matchmod.patkind(pat) is not None:
653 listpats.append(pat)
653 listpats.append(pat)
654 else:
654 else:
655 listpats.append(makestandin(pat))
655 listpats.append(makestandin(pat))
656
656
657 copiedfiles = []
657 copiedfiles = []
658 def overridecopyfile(orig, src, dest, *args, **kwargs):
658 def overridecopyfile(orig, src, dest, *args, **kwargs):
659 if (lfutil.shortname in src and
659 if (lfutil.shortname in src and
660 dest.startswith(repo.wjoin(lfutil.shortname))):
660 dest.startswith(repo.wjoin(lfutil.shortname))):
661 destlfile = dest.replace(lfutil.shortname, '')
661 destlfile = dest.replace(lfutil.shortname, '')
662 if not opts['force'] and os.path.exists(destlfile):
662 if not opts['force'] and os.path.exists(destlfile):
663 raise IOError('',
663 raise IOError('',
664 _('destination largefile already exists'))
664 _('destination largefile already exists'))
665 copiedfiles.append((src, dest))
665 copiedfiles.append((src, dest))
666 orig(src, dest, *args, **kwargs)
666 orig(src, dest, *args, **kwargs)
667 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile), \
667 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile):
668 extensions.wrappedfunction(scmutil, 'match', overridematch):
668 with extensions.wrappedfunction(scmutil, 'match', overridematch):
669 result += orig(ui, repo, listpats, opts, rename)
669 result += orig(ui, repo, listpats, opts, rename)
670
670
671 lfdirstate = lfutil.openlfdirstate(ui, repo)
671 lfdirstate = lfutil.openlfdirstate(ui, repo)
672 for (src, dest) in copiedfiles:
672 for (src, dest) in copiedfiles:
673 if (lfutil.shortname in src and
673 if (lfutil.shortname in src and
674 dest.startswith(repo.wjoin(lfutil.shortname))):
674 dest.startswith(repo.wjoin(lfutil.shortname))):
675 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
675 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
676 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
676 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
677 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
677 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
678 if not os.path.isdir(destlfiledir):
678 if not os.path.isdir(destlfiledir):
679 os.makedirs(destlfiledir)
679 os.makedirs(destlfiledir)
680 if rename:
680 if rename:
681 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
681 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
682
682
683 # The file is gone, but this deletes any empty parent
683 # The file is gone, but this deletes any empty parent
684 # directories as a side-effect.
684 # directories as a side-effect.
685 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
685 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
686 lfdirstate.remove(srclfile)
686 lfdirstate.remove(srclfile)
687 else:
687 else:
688 util.copyfile(repo.wjoin(srclfile),
688 util.copyfile(repo.wjoin(srclfile),
689 repo.wjoin(destlfile))
689 repo.wjoin(destlfile))
690
690
691 lfdirstate.add(destlfile)
691 lfdirstate.add(destlfile)
692 lfdirstate.write()
692 lfdirstate.write()
693 except error.Abort as e:
693 except error.Abort as e:
694 if pycompat.bytestr(e) != _('no files to copy'):
694 if pycompat.bytestr(e) != _('no files to copy'):
695 raise e
695 raise e
696 else:
696 else:
697 nolfiles = True
697 nolfiles = True
698 finally:
698 finally:
699 wlock.release()
699 wlock.release()
700
700
701 if nolfiles and nonormalfiles:
701 if nolfiles and nonormalfiles:
702 raise error.Abort(_('no files to copy'))
702 raise error.Abort(_('no files to copy'))
703
703
704 return result
704 return result
705
705
706 # When the user calls revert, we have to be careful to not revert any
706 # When the user calls revert, we have to be careful to not revert any
707 # changes to other largefiles accidentally. This means we have to keep
707 # changes to other largefiles accidentally. This means we have to keep
708 # track of the largefiles that are being reverted so we only pull down
708 # track of the largefiles that are being reverted so we only pull down
709 # the necessary largefiles.
709 # the necessary largefiles.
710 #
710 #
711 # Standins are only updated (to match the hash of largefiles) before
711 # Standins are only updated (to match the hash of largefiles) before
712 # commits. Update the standins then run the original revert, changing
712 # commits. Update the standins then run the original revert, changing
713 # the matcher to hit standins instead of largefiles. Based on the
713 # the matcher to hit standins instead of largefiles. Based on the
714 # resulting standins update the largefiles.
714 # resulting standins update the largefiles.
715 @eh.wrapfunction(cmdutil, 'revert')
715 @eh.wrapfunction(cmdutil, 'revert')
716 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
716 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
717 # Because we put the standins in a bad state (by updating them)
717 # Because we put the standins in a bad state (by updating them)
718 # and then return them to a correct state we need to lock to
718 # and then return them to a correct state we need to lock to
719 # prevent others from changing them in their incorrect state.
719 # prevent others from changing them in their incorrect state.
720 with repo.wlock():
720 with repo.wlock():
721 lfdirstate = lfutil.openlfdirstate(ui, repo)
721 lfdirstate = lfutil.openlfdirstate(ui, repo)
722 s = lfutil.lfdirstatestatus(lfdirstate, repo)
722 s = lfutil.lfdirstatestatus(lfdirstate, repo)
723 lfdirstate.write()
723 lfdirstate.write()
724 for lfile in s.modified:
724 for lfile in s.modified:
725 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
725 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
726 for lfile in s.deleted:
726 for lfile in s.deleted:
727 fstandin = lfutil.standin(lfile)
727 fstandin = lfutil.standin(lfile)
728 if (repo.wvfs.exists(fstandin)):
728 if (repo.wvfs.exists(fstandin)):
729 repo.wvfs.unlink(fstandin)
729 repo.wvfs.unlink(fstandin)
730
730
731 oldstandins = lfutil.getstandinsstate(repo)
731 oldstandins = lfutil.getstandinsstate(repo)
732
732
733 def overridematch(orig, mctx, pats=(), opts=None, globbed=False,
733 def overridematch(orig, mctx, pats=(), opts=None, globbed=False,
734 default='relpath', badfn=None):
734 default='relpath', badfn=None):
735 if opts is None:
735 if opts is None:
736 opts = {}
736 opts = {}
737 match = orig(mctx, pats, opts, globbed, default, badfn=badfn)
737 match = orig(mctx, pats, opts, globbed, default, badfn=badfn)
738 m = copy.copy(match)
738 m = copy.copy(match)
739
739
740 # revert supports recursing into subrepos, and though largefiles
740 # revert supports recursing into subrepos, and though largefiles
741 # currently doesn't work correctly in that case, this match is
741 # currently doesn't work correctly in that case, this match is
742 # called, so the lfdirstate above may not be the correct one for
742 # called, so the lfdirstate above may not be the correct one for
743 # this invocation of match.
743 # this invocation of match.
744 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
744 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
745 False)
745 False)
746
746
747 wctx = repo[None]
747 wctx = repo[None]
748 matchfiles = []
748 matchfiles = []
749 for f in m._files:
749 for f in m._files:
750 standin = lfutil.standin(f)
750 standin = lfutil.standin(f)
751 if standin in ctx or standin in mctx:
751 if standin in ctx or standin in mctx:
752 matchfiles.append(standin)
752 matchfiles.append(standin)
753 elif standin in wctx or lfdirstate[f] == 'r':
753 elif standin in wctx or lfdirstate[f] == 'r':
754 continue
754 continue
755 else:
755 else:
756 matchfiles.append(f)
756 matchfiles.append(f)
757 m._files = matchfiles
757 m._files = matchfiles
758 m._fileset = set(m._files)
758 m._fileset = set(m._files)
759 origmatchfn = m.matchfn
759 origmatchfn = m.matchfn
760 def matchfn(f):
760 def matchfn(f):
761 lfile = lfutil.splitstandin(f)
761 lfile = lfutil.splitstandin(f)
762 if lfile is not None:
762 if lfile is not None:
763 return (origmatchfn(lfile) and
763 return (origmatchfn(lfile) and
764 (f in ctx or f in mctx))
764 (f in ctx or f in mctx))
765 return origmatchfn(f)
765 return origmatchfn(f)
766 m.matchfn = matchfn
766 m.matchfn = matchfn
767 return m
767 return m
768 with extensions.wrappedfunction(scmutil, 'match', overridematch):
768 with extensions.wrappedfunction(scmutil, 'match', overridematch):
769 orig(ui, repo, ctx, parents, *pats, **opts)
769 orig(ui, repo, ctx, parents, *pats, **opts)
770
770
771 newstandins = lfutil.getstandinsstate(repo)
771 newstandins = lfutil.getstandinsstate(repo)
772 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
772 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
773 # lfdirstate should be 'normallookup'-ed for updated files,
773 # lfdirstate should be 'normallookup'-ed for updated files,
774 # because reverting doesn't touch dirstate for 'normal' files
774 # because reverting doesn't touch dirstate for 'normal' files
775 # when target revision is explicitly specified: in such case,
775 # when target revision is explicitly specified: in such case,
776 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
776 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
777 # of target (standin) file.
777 # of target (standin) file.
778 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
778 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
779 normallookup=True)
779 normallookup=True)
780
780
781 # after pulling changesets, we need to take some extra care to get
781 # after pulling changesets, we need to take some extra care to get
782 # largefiles updated remotely
782 # largefiles updated remotely
783 @eh.wrapcommand('pull',
783 @eh.wrapcommand('pull',
784 opts=[('', 'all-largefiles', None,
784 opts=[('', 'all-largefiles', None,
785 _('download all pulled versions of largefiles (DEPRECATED)')),
785 _('download all pulled versions of largefiles (DEPRECATED)')),
786 ('', 'lfrev', [],
786 ('', 'lfrev', [],
787 _('download largefiles for these revisions'), _('REV'))])
787 _('download largefiles for these revisions'), _('REV'))])
788 def overridepull(orig, ui, repo, source=None, **opts):
788 def overridepull(orig, ui, repo, source=None, **opts):
789 revsprepull = len(repo)
789 revsprepull = len(repo)
790 if not source:
790 if not source:
791 source = 'default'
791 source = 'default'
792 repo.lfpullsource = source
792 repo.lfpullsource = source
793 result = orig(ui, repo, source, **opts)
793 result = orig(ui, repo, source, **opts)
794 revspostpull = len(repo)
794 revspostpull = len(repo)
795 lfrevs = opts.get(r'lfrev', [])
795 lfrevs = opts.get(r'lfrev', [])
796 if opts.get(r'all_largefiles'):
796 if opts.get(r'all_largefiles'):
797 lfrevs.append('pulled()')
797 lfrevs.append('pulled()')
798 if lfrevs and revspostpull > revsprepull:
798 if lfrevs and revspostpull > revsprepull:
799 numcached = 0
799 numcached = 0
800 repo.firstpulled = revsprepull # for pulled() revset expression
800 repo.firstpulled = revsprepull # for pulled() revset expression
801 try:
801 try:
802 for rev in scmutil.revrange(repo, lfrevs):
802 for rev in scmutil.revrange(repo, lfrevs):
803 ui.note(_('pulling largefiles for revision %d\n') % rev)
803 ui.note(_('pulling largefiles for revision %d\n') % rev)
804 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
804 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
805 numcached += len(cached)
805 numcached += len(cached)
806 finally:
806 finally:
807 del repo.firstpulled
807 del repo.firstpulled
808 ui.status(_("%d largefiles cached\n") % numcached)
808 ui.status(_("%d largefiles cached\n") % numcached)
809 return result
809 return result
810
810
811 @eh.wrapcommand('push',
811 @eh.wrapcommand('push',
812 opts=[('', 'lfrev', [],
812 opts=[('', 'lfrev', [],
813 _('upload largefiles for these revisions'), _('REV'))])
813 _('upload largefiles for these revisions'), _('REV'))])
814 def overridepush(orig, ui, repo, *args, **kwargs):
814 def overridepush(orig, ui, repo, *args, **kwargs):
815 """Override push command and store --lfrev parameters in opargs"""
815 """Override push command and store --lfrev parameters in opargs"""
816 lfrevs = kwargs.pop(r'lfrev', None)
816 lfrevs = kwargs.pop(r'lfrev', None)
817 if lfrevs:
817 if lfrevs:
818 opargs = kwargs.setdefault(r'opargs', {})
818 opargs = kwargs.setdefault(r'opargs', {})
819 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
819 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
820 return orig(ui, repo, *args, **kwargs)
820 return orig(ui, repo, *args, **kwargs)
821
821
822 @eh.wrapfunction(exchange, 'pushoperation')
822 @eh.wrapfunction(exchange, 'pushoperation')
823 def exchangepushoperation(orig, *args, **kwargs):
823 def exchangepushoperation(orig, *args, **kwargs):
824 """Override pushoperation constructor and store lfrevs parameter"""
824 """Override pushoperation constructor and store lfrevs parameter"""
825 lfrevs = kwargs.pop(r'lfrevs', None)
825 lfrevs = kwargs.pop(r'lfrevs', None)
826 pushop = orig(*args, **kwargs)
826 pushop = orig(*args, **kwargs)
827 pushop.lfrevs = lfrevs
827 pushop.lfrevs = lfrevs
828 return pushop
828 return pushop
829
829
830 @eh.revsetpredicate('pulled()')
830 @eh.revsetpredicate('pulled()')
831 def pulledrevsetsymbol(repo, subset, x):
831 def pulledrevsetsymbol(repo, subset, x):
832 """Changesets that just has been pulled.
832 """Changesets that just has been pulled.
833
833
834 Only available with largefiles from pull --lfrev expressions.
834 Only available with largefiles from pull --lfrev expressions.
835
835
836 .. container:: verbose
836 .. container:: verbose
837
837
838 Some examples:
838 Some examples:
839
839
840 - pull largefiles for all new changesets::
840 - pull largefiles for all new changesets::
841
841
842 hg pull -lfrev "pulled()"
842 hg pull -lfrev "pulled()"
843
843
844 - pull largefiles for all new branch heads::
844 - pull largefiles for all new branch heads::
845
845
846 hg pull -lfrev "head(pulled()) and not closed()"
846 hg pull -lfrev "head(pulled()) and not closed()"
847
847
848 """
848 """
849
849
850 try:
850 try:
851 firstpulled = repo.firstpulled
851 firstpulled = repo.firstpulled
852 except AttributeError:
852 except AttributeError:
853 raise error.Abort(_("pulled() only available in --lfrev"))
853 raise error.Abort(_("pulled() only available in --lfrev"))
854 return smartset.baseset([r for r in subset if r >= firstpulled])
854 return smartset.baseset([r for r in subset if r >= firstpulled])
855
855
856 @eh.wrapcommand('clone',
856 @eh.wrapcommand('clone',
857 opts=[('', 'all-largefiles', None,
857 opts=[('', 'all-largefiles', None,
858 _('download all versions of all largefiles'))])
858 _('download all versions of all largefiles'))])
859 def overrideclone(orig, ui, source, dest=None, **opts):
859 def overrideclone(orig, ui, source, dest=None, **opts):
860 d = dest
860 d = dest
861 if d is None:
861 if d is None:
862 d = hg.defaultdest(source)
862 d = hg.defaultdest(source)
863 if opts.get(r'all_largefiles') and not hg.islocal(d):
863 if opts.get(r'all_largefiles') and not hg.islocal(d):
864 raise error.Abort(_(
864 raise error.Abort(_(
865 '--all-largefiles is incompatible with non-local destination %s') %
865 '--all-largefiles is incompatible with non-local destination %s') %
866 d)
866 d)
867
867
868 return orig(ui, source, dest, **opts)
868 return orig(ui, source, dest, **opts)
869
869
870 @eh.wrapfunction(hg, 'clone')
870 @eh.wrapfunction(hg, 'clone')
871 def hgclone(orig, ui, opts, *args, **kwargs):
871 def hgclone(orig, ui, opts, *args, **kwargs):
872 result = orig(ui, opts, *args, **kwargs)
872 result = orig(ui, opts, *args, **kwargs)
873
873
874 if result is not None:
874 if result is not None:
875 sourcerepo, destrepo = result
875 sourcerepo, destrepo = result
876 repo = destrepo.local()
876 repo = destrepo.local()
877
877
878 # When cloning to a remote repo (like through SSH), no repo is available
878 # When cloning to a remote repo (like through SSH), no repo is available
879 # from the peer. Therefore the largefiles can't be downloaded and the
879 # from the peer. Therefore the largefiles can't be downloaded and the
880 # hgrc can't be updated.
880 # hgrc can't be updated.
881 if not repo:
881 if not repo:
882 return result
882 return result
883
883
884 # Caching is implicitly limited to 'rev' option, since the dest repo was
884 # Caching is implicitly limited to 'rev' option, since the dest repo was
885 # truncated at that point. The user may expect a download count with
885 # truncated at that point. The user may expect a download count with
886 # this option, so attempt whether or not this is a largefile repo.
886 # this option, so attempt whether or not this is a largefile repo.
887 if opts.get('all_largefiles'):
887 if opts.get('all_largefiles'):
888 success, missing = lfcommands.downloadlfiles(ui, repo, None)
888 success, missing = lfcommands.downloadlfiles(ui, repo, None)
889
889
890 if missing != 0:
890 if missing != 0:
891 return None
891 return None
892
892
893 return result
893 return result
894
894
895 @eh.wrapcommand('rebase', extension='rebase')
895 @eh.wrapcommand('rebase', extension='rebase')
896 def overriderebase(orig, ui, repo, **opts):
896 def overriderebase(orig, ui, repo, **opts):
897 if not util.safehasattr(repo, '_largefilesenabled'):
897 if not util.safehasattr(repo, '_largefilesenabled'):
898 return orig(ui, repo, **opts)
898 return orig(ui, repo, **opts)
899
899
900 resuming = opts.get(r'continue')
900 resuming = opts.get(r'continue')
901 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
901 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
902 repo._lfstatuswriters.append(lambda *msg, **opts: None)
902 repo._lfstatuswriters.append(lambda *msg, **opts: None)
903 try:
903 try:
904 return orig(ui, repo, **opts)
904 return orig(ui, repo, **opts)
905 finally:
905 finally:
906 repo._lfstatuswriters.pop()
906 repo._lfstatuswriters.pop()
907 repo._lfcommithooks.pop()
907 repo._lfcommithooks.pop()
908
908
909 @eh.wrapcommand('archive')
909 @eh.wrapcommand('archive')
910 def overridearchivecmd(orig, ui, repo, dest, **opts):
910 def overridearchivecmd(orig, ui, repo, dest, **opts):
911 repo.unfiltered().lfstatus = True
911 repo.unfiltered().lfstatus = True
912
912
913 try:
913 try:
914 return orig(ui, repo.unfiltered(), dest, **opts)
914 return orig(ui, repo.unfiltered(), dest, **opts)
915 finally:
915 finally:
916 repo.unfiltered().lfstatus = False
916 repo.unfiltered().lfstatus = False
917
917
918 @eh.wrapfunction(webcommands, 'archive')
918 @eh.wrapfunction(webcommands, 'archive')
919 def hgwebarchive(orig, web):
919 def hgwebarchive(orig, web):
920 web.repo.lfstatus = True
920 web.repo.lfstatus = True
921
921
922 try:
922 try:
923 return orig(web)
923 return orig(web)
924 finally:
924 finally:
925 web.repo.lfstatus = False
925 web.repo.lfstatus = False
926
926
927 @eh.wrapfunction(archival, 'archive')
927 @eh.wrapfunction(archival, 'archive')
928 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
928 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
929 prefix='', mtime=None, subrepos=None):
929 prefix='', mtime=None, subrepos=None):
930 # For some reason setting repo.lfstatus in hgwebarchive only changes the
930 # For some reason setting repo.lfstatus in hgwebarchive only changes the
931 # unfiltered repo's attr, so check that as well.
931 # unfiltered repo's attr, so check that as well.
932 if not repo.lfstatus and not repo.unfiltered().lfstatus:
932 if not repo.lfstatus and not repo.unfiltered().lfstatus:
933 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
933 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
934 subrepos)
934 subrepos)
935
935
936 # No need to lock because we are only reading history and
936 # No need to lock because we are only reading history and
937 # largefile caches, neither of which are modified.
937 # largefile caches, neither of which are modified.
938 if node is not None:
938 if node is not None:
939 lfcommands.cachelfiles(repo.ui, repo, node)
939 lfcommands.cachelfiles(repo.ui, repo, node)
940
940
941 if kind not in archival.archivers:
941 if kind not in archival.archivers:
942 raise error.Abort(_("unknown archive type '%s'") % kind)
942 raise error.Abort(_("unknown archive type '%s'") % kind)
943
943
944 ctx = repo[node]
944 ctx = repo[node]
945
945
946 if kind == 'files':
946 if kind == 'files':
947 if prefix:
947 if prefix:
948 raise error.Abort(
948 raise error.Abort(
949 _('cannot give prefix when archiving to files'))
949 _('cannot give prefix when archiving to files'))
950 else:
950 else:
951 prefix = archival.tidyprefix(dest, kind, prefix)
951 prefix = archival.tidyprefix(dest, kind, prefix)
952
952
953 def write(name, mode, islink, getdata):
953 def write(name, mode, islink, getdata):
954 if match and not match(name):
954 if match and not match(name):
955 return
955 return
956 data = getdata()
956 data = getdata()
957 if decode:
957 if decode:
958 data = repo.wwritedata(name, data)
958 data = repo.wwritedata(name, data)
959 archiver.addfile(prefix + name, mode, islink, data)
959 archiver.addfile(prefix + name, mode, islink, data)
960
960
961 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
961 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
962
962
963 if repo.ui.configbool("ui", "archivemeta"):
963 if repo.ui.configbool("ui", "archivemeta"):
964 write('.hg_archival.txt', 0o644, False,
964 write('.hg_archival.txt', 0o644, False,
965 lambda: archival.buildmetadata(ctx))
965 lambda: archival.buildmetadata(ctx))
966
966
967 for f in ctx:
967 for f in ctx:
968 ff = ctx.flags(f)
968 ff = ctx.flags(f)
969 getdata = ctx[f].data
969 getdata = ctx[f].data
970 lfile = lfutil.splitstandin(f)
970 lfile = lfutil.splitstandin(f)
971 if lfile is not None:
971 if lfile is not None:
972 if node is not None:
972 if node is not None:
973 path = lfutil.findfile(repo, getdata().strip())
973 path = lfutil.findfile(repo, getdata().strip())
974
974
975 if path is None:
975 if path is None:
976 raise error.Abort(
976 raise error.Abort(
977 _('largefile %s not found in repo store or system cache')
977 _('largefile %s not found in repo store or system cache')
978 % lfile)
978 % lfile)
979 else:
979 else:
980 path = lfile
980 path = lfile
981
981
982 f = lfile
982 f = lfile
983
983
984 getdata = lambda: util.readfile(path)
984 getdata = lambda: util.readfile(path)
985 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
985 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
986
986
987 if subrepos:
987 if subrepos:
988 for subpath in sorted(ctx.substate):
988 for subpath in sorted(ctx.substate):
989 sub = ctx.workingsub(subpath)
989 sub = ctx.workingsub(subpath)
990 submatch = matchmod.subdirmatcher(subpath, match)
990 submatch = matchmod.subdirmatcher(subpath, match)
991 subprefix = prefix + subpath + '/'
991 subprefix = prefix + subpath + '/'
992 sub._repo.lfstatus = True
992 sub._repo.lfstatus = True
993 sub.archive(archiver, subprefix, submatch)
993 sub.archive(archiver, subprefix, submatch)
994
994
995 archiver.done()
995 archiver.done()
996
996
997 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
997 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
998 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
998 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
999 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
999 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1000 if not lfenabled or not repo._repo.lfstatus:
1000 if not lfenabled or not repo._repo.lfstatus:
1001 return orig(repo, archiver, prefix, match, decode)
1001 return orig(repo, archiver, prefix, match, decode)
1002
1002
1003 repo._get(repo._state + ('hg',))
1003 repo._get(repo._state + ('hg',))
1004 rev = repo._state[1]
1004 rev = repo._state[1]
1005 ctx = repo._repo[rev]
1005 ctx = repo._repo[rev]
1006
1006
1007 if ctx.node() is not None:
1007 if ctx.node() is not None:
1008 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1008 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1009
1009
1010 def write(name, mode, islink, getdata):
1010 def write(name, mode, islink, getdata):
1011 # At this point, the standin has been replaced with the largefile name,
1011 # At this point, the standin has been replaced with the largefile name,
1012 # so the normal matcher works here without the lfutil variants.
1012 # so the normal matcher works here without the lfutil variants.
1013 if match and not match(f):
1013 if match and not match(f):
1014 return
1014 return
1015 data = getdata()
1015 data = getdata()
1016 if decode:
1016 if decode:
1017 data = repo._repo.wwritedata(name, data)
1017 data = repo._repo.wwritedata(name, data)
1018
1018
1019 archiver.addfile(prefix + name, mode, islink, data)
1019 archiver.addfile(prefix + name, mode, islink, data)
1020
1020
1021 for f in ctx:
1021 for f in ctx:
1022 ff = ctx.flags(f)
1022 ff = ctx.flags(f)
1023 getdata = ctx[f].data
1023 getdata = ctx[f].data
1024 lfile = lfutil.splitstandin(f)
1024 lfile = lfutil.splitstandin(f)
1025 if lfile is not None:
1025 if lfile is not None:
1026 if ctx.node() is not None:
1026 if ctx.node() is not None:
1027 path = lfutil.findfile(repo._repo, getdata().strip())
1027 path = lfutil.findfile(repo._repo, getdata().strip())
1028
1028
1029 if path is None:
1029 if path is None:
1030 raise error.Abort(
1030 raise error.Abort(
1031 _('largefile %s not found in repo store or system cache')
1031 _('largefile %s not found in repo store or system cache')
1032 % lfile)
1032 % lfile)
1033 else:
1033 else:
1034 path = lfile
1034 path = lfile
1035
1035
1036 f = lfile
1036 f = lfile
1037
1037
1038 getdata = lambda: util.readfile(os.path.join(prefix, path))
1038 getdata = lambda: util.readfile(os.path.join(prefix, path))
1039
1039
1040 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1040 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1041
1041
1042 for subpath in sorted(ctx.substate):
1042 for subpath in sorted(ctx.substate):
1043 sub = ctx.workingsub(subpath)
1043 sub = ctx.workingsub(subpath)
1044 submatch = matchmod.subdirmatcher(subpath, match)
1044 submatch = matchmod.subdirmatcher(subpath, match)
1045 subprefix = prefix + subpath + '/'
1045 subprefix = prefix + subpath + '/'
1046 sub._repo.lfstatus = True
1046 sub._repo.lfstatus = True
1047 sub.archive(archiver, subprefix, submatch, decode)
1047 sub.archive(archiver, subprefix, submatch, decode)
1048
1048
1049 # If a largefile is modified, the change is not reflected in its
1049 # If a largefile is modified, the change is not reflected in its
1050 # standin until a commit. cmdutil.bailifchanged() raises an exception
1050 # standin until a commit. cmdutil.bailifchanged() raises an exception
1051 # if the repo has uncommitted changes. Wrap it to also check if
1051 # if the repo has uncommitted changes. Wrap it to also check if
1052 # largefiles were changed. This is used by bisect, backout and fetch.
1052 # largefiles were changed. This is used by bisect, backout and fetch.
1053 @eh.wrapfunction(cmdutil, 'bailifchanged')
1053 @eh.wrapfunction(cmdutil, 'bailifchanged')
1054 def overridebailifchanged(orig, repo, *args, **kwargs):
1054 def overridebailifchanged(orig, repo, *args, **kwargs):
1055 orig(repo, *args, **kwargs)
1055 orig(repo, *args, **kwargs)
1056 repo.lfstatus = True
1056 repo.lfstatus = True
1057 s = repo.status()
1057 s = repo.status()
1058 repo.lfstatus = False
1058 repo.lfstatus = False
1059 if s.modified or s.added or s.removed or s.deleted:
1059 if s.modified or s.added or s.removed or s.deleted:
1060 raise error.Abort(_('uncommitted changes'))
1060 raise error.Abort(_('uncommitted changes'))
1061
1061
1062 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1062 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1063 def postcommitstatus(orig, repo, *args, **kwargs):
1063 def postcommitstatus(orig, repo, *args, **kwargs):
1064 repo.lfstatus = True
1064 repo.lfstatus = True
1065 try:
1065 try:
1066 return orig(repo, *args, **kwargs)
1066 return orig(repo, *args, **kwargs)
1067 finally:
1067 finally:
1068 repo.lfstatus = False
1068 repo.lfstatus = False
1069
1069
1070 @eh.wrapfunction(cmdutil, 'forget')
1070 @eh.wrapfunction(cmdutil, 'forget')
1071 def cmdutilforget(orig, ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
1071 def cmdutilforget(orig, ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
1072 interactive):
1072 interactive):
1073 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1073 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1074 bad, forgot = orig(ui, repo, normalmatcher, prefix, uipathfn, explicitonly,
1074 bad, forgot = orig(ui, repo, normalmatcher, prefix, uipathfn, explicitonly,
1075 dryrun, interactive)
1075 dryrun, interactive)
1076 m = composelargefilematcher(match, repo[None].manifest())
1076 m = composelargefilematcher(match, repo[None].manifest())
1077
1077
1078 try:
1078 try:
1079 repo.lfstatus = True
1079 repo.lfstatus = True
1080 s = repo.status(match=m, clean=True)
1080 s = repo.status(match=m, clean=True)
1081 finally:
1081 finally:
1082 repo.lfstatus = False
1082 repo.lfstatus = False
1083 manifest = repo[None].manifest()
1083 manifest = repo[None].manifest()
1084 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1084 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1085 forget = [f for f in forget if lfutil.standin(f) in manifest]
1085 forget = [f for f in forget if lfutil.standin(f) in manifest]
1086
1086
1087 for f in forget:
1087 for f in forget:
1088 fstandin = lfutil.standin(f)
1088 fstandin = lfutil.standin(f)
1089 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1089 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1090 ui.warn(_('not removing %s: file is already untracked\n')
1090 ui.warn(_('not removing %s: file is already untracked\n')
1091 % uipathfn(f))
1091 % uipathfn(f))
1092 bad.append(f)
1092 bad.append(f)
1093
1093
1094 for f in forget:
1094 for f in forget:
1095 if ui.verbose or not m.exact(f):
1095 if ui.verbose or not m.exact(f):
1096 ui.status(_('removing %s\n') % uipathfn(f))
1096 ui.status(_('removing %s\n') % uipathfn(f))
1097
1097
1098 # Need to lock because standin files are deleted then removed from the
1098 # Need to lock because standin files are deleted then removed from the
1099 # repository and we could race in-between.
1099 # repository and we could race in-between.
1100 with repo.wlock():
1100 with repo.wlock():
1101 lfdirstate = lfutil.openlfdirstate(ui, repo)
1101 lfdirstate = lfutil.openlfdirstate(ui, repo)
1102 for f in forget:
1102 for f in forget:
1103 if lfdirstate[f] == 'a':
1103 if lfdirstate[f] == 'a':
1104 lfdirstate.drop(f)
1104 lfdirstate.drop(f)
1105 else:
1105 else:
1106 lfdirstate.remove(f)
1106 lfdirstate.remove(f)
1107 lfdirstate.write()
1107 lfdirstate.write()
1108 standins = [lfutil.standin(f) for f in forget]
1108 standins = [lfutil.standin(f) for f in forget]
1109 for f in standins:
1109 for f in standins:
1110 repo.wvfs.unlinkpath(f, ignoremissing=True)
1110 repo.wvfs.unlinkpath(f, ignoremissing=True)
1111 rejected = repo[None].forget(standins)
1111 rejected = repo[None].forget(standins)
1112
1112
1113 bad.extend(f for f in rejected if f in m.files())
1113 bad.extend(f for f in rejected if f in m.files())
1114 forgot.extend(f for f in forget if f not in rejected)
1114 forgot.extend(f for f in forget if f not in rejected)
1115 return bad, forgot
1115 return bad, forgot
1116
1116
1117 def _getoutgoings(repo, other, missing, addfunc):
1117 def _getoutgoings(repo, other, missing, addfunc):
1118 """get pairs of filename and largefile hash in outgoing revisions
1118 """get pairs of filename and largefile hash in outgoing revisions
1119 in 'missing'.
1119 in 'missing'.
1120
1120
1121 largefiles already existing on 'other' repository are ignored.
1121 largefiles already existing on 'other' repository are ignored.
1122
1122
1123 'addfunc' is invoked with each unique pairs of filename and
1123 'addfunc' is invoked with each unique pairs of filename and
1124 largefile hash value.
1124 largefile hash value.
1125 """
1125 """
1126 knowns = set()
1126 knowns = set()
1127 lfhashes = set()
1127 lfhashes = set()
1128 def dedup(fn, lfhash):
1128 def dedup(fn, lfhash):
1129 k = (fn, lfhash)
1129 k = (fn, lfhash)
1130 if k not in knowns:
1130 if k not in knowns:
1131 knowns.add(k)
1131 knowns.add(k)
1132 lfhashes.add(lfhash)
1132 lfhashes.add(lfhash)
1133 lfutil.getlfilestoupload(repo, missing, dedup)
1133 lfutil.getlfilestoupload(repo, missing, dedup)
1134 if lfhashes:
1134 if lfhashes:
1135 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1135 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1136 for fn, lfhash in knowns:
1136 for fn, lfhash in knowns:
1137 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1137 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1138 addfunc(fn, lfhash)
1138 addfunc(fn, lfhash)
1139
1139
1140 def outgoinghook(ui, repo, other, opts, missing):
1140 def outgoinghook(ui, repo, other, opts, missing):
1141 if opts.pop('large', None):
1141 if opts.pop('large', None):
1142 lfhashes = set()
1142 lfhashes = set()
1143 if ui.debugflag:
1143 if ui.debugflag:
1144 toupload = {}
1144 toupload = {}
1145 def addfunc(fn, lfhash):
1145 def addfunc(fn, lfhash):
1146 if fn not in toupload:
1146 if fn not in toupload:
1147 toupload[fn] = []
1147 toupload[fn] = []
1148 toupload[fn].append(lfhash)
1148 toupload[fn].append(lfhash)
1149 lfhashes.add(lfhash)
1149 lfhashes.add(lfhash)
1150 def showhashes(fn):
1150 def showhashes(fn):
1151 for lfhash in sorted(toupload[fn]):
1151 for lfhash in sorted(toupload[fn]):
1152 ui.debug(' %s\n' % (lfhash))
1152 ui.debug(' %s\n' % (lfhash))
1153 else:
1153 else:
1154 toupload = set()
1154 toupload = set()
1155 def addfunc(fn, lfhash):
1155 def addfunc(fn, lfhash):
1156 toupload.add(fn)
1156 toupload.add(fn)
1157 lfhashes.add(lfhash)
1157 lfhashes.add(lfhash)
1158 def showhashes(fn):
1158 def showhashes(fn):
1159 pass
1159 pass
1160 _getoutgoings(repo, other, missing, addfunc)
1160 _getoutgoings(repo, other, missing, addfunc)
1161
1161
1162 if not toupload:
1162 if not toupload:
1163 ui.status(_('largefiles: no files to upload\n'))
1163 ui.status(_('largefiles: no files to upload\n'))
1164 else:
1164 else:
1165 ui.status(_('largefiles to upload (%d entities):\n')
1165 ui.status(_('largefiles to upload (%d entities):\n')
1166 % (len(lfhashes)))
1166 % (len(lfhashes)))
1167 for file in sorted(toupload):
1167 for file in sorted(toupload):
1168 ui.status(lfutil.splitstandin(file) + '\n')
1168 ui.status(lfutil.splitstandin(file) + '\n')
1169 showhashes(file)
1169 showhashes(file)
1170 ui.status('\n')
1170 ui.status('\n')
1171
1171
1172 @eh.wrapcommand('outgoing',
1172 @eh.wrapcommand('outgoing',
1173 opts=[('', 'large', None, _('display outgoing largefiles'))])
1173 opts=[('', 'large', None, _('display outgoing largefiles'))])
1174 def _outgoingcmd(orig, *args, **kwargs):
1174 def _outgoingcmd(orig, *args, **kwargs):
1175 # Nothing to do here other than add the extra help option- the hook above
1175 # Nothing to do here other than add the extra help option- the hook above
1176 # processes it.
1176 # processes it.
1177 return orig(*args, **kwargs)
1177 return orig(*args, **kwargs)
1178
1178
1179 def summaryremotehook(ui, repo, opts, changes):
1179 def summaryremotehook(ui, repo, opts, changes):
1180 largeopt = opts.get('large', False)
1180 largeopt = opts.get('large', False)
1181 if changes is None:
1181 if changes is None:
1182 if largeopt:
1182 if largeopt:
1183 return (False, True) # only outgoing check is needed
1183 return (False, True) # only outgoing check is needed
1184 else:
1184 else:
1185 return (False, False)
1185 return (False, False)
1186 elif largeopt:
1186 elif largeopt:
1187 url, branch, peer, outgoing = changes[1]
1187 url, branch, peer, outgoing = changes[1]
1188 if peer is None:
1188 if peer is None:
1189 # i18n: column positioning for "hg summary"
1189 # i18n: column positioning for "hg summary"
1190 ui.status(_('largefiles: (no remote repo)\n'))
1190 ui.status(_('largefiles: (no remote repo)\n'))
1191 return
1191 return
1192
1192
1193 toupload = set()
1193 toupload = set()
1194 lfhashes = set()
1194 lfhashes = set()
1195 def addfunc(fn, lfhash):
1195 def addfunc(fn, lfhash):
1196 toupload.add(fn)
1196 toupload.add(fn)
1197 lfhashes.add(lfhash)
1197 lfhashes.add(lfhash)
1198 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1198 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1199
1199
1200 if not toupload:
1200 if not toupload:
1201 # i18n: column positioning for "hg summary"
1201 # i18n: column positioning for "hg summary"
1202 ui.status(_('largefiles: (no files to upload)\n'))
1202 ui.status(_('largefiles: (no files to upload)\n'))
1203 else:
1203 else:
1204 # i18n: column positioning for "hg summary"
1204 # i18n: column positioning for "hg summary"
1205 ui.status(_('largefiles: %d entities for %d files to upload\n')
1205 ui.status(_('largefiles: %d entities for %d files to upload\n')
1206 % (len(lfhashes), len(toupload)))
1206 % (len(lfhashes), len(toupload)))
1207
1207
1208 @eh.wrapcommand('summary',
1208 @eh.wrapcommand('summary',
1209 opts=[('', 'large', None, _('display outgoing largefiles'))])
1209 opts=[('', 'large', None, _('display outgoing largefiles'))])
1210 def overridesummary(orig, ui, repo, *pats, **opts):
1210 def overridesummary(orig, ui, repo, *pats, **opts):
1211 try:
1211 try:
1212 repo.lfstatus = True
1212 repo.lfstatus = True
1213 orig(ui, repo, *pats, **opts)
1213 orig(ui, repo, *pats, **opts)
1214 finally:
1214 finally:
1215 repo.lfstatus = False
1215 repo.lfstatus = False
1216
1216
1217 @eh.wrapfunction(scmutil, 'addremove')
1217 @eh.wrapfunction(scmutil, 'addremove')
1218 def scmutiladdremove(orig, repo, matcher, prefix, uipathfn, opts=None):
1218 def scmutiladdremove(orig, repo, matcher, prefix, uipathfn, opts=None):
1219 if opts is None:
1219 if opts is None:
1220 opts = {}
1220 opts = {}
1221 if not lfutil.islfilesrepo(repo):
1221 if not lfutil.islfilesrepo(repo):
1222 return orig(repo, matcher, prefix, uipathfn, opts)
1222 return orig(repo, matcher, prefix, uipathfn, opts)
1223 # Get the list of missing largefiles so we can remove them
1223 # Get the list of missing largefiles so we can remove them
1224 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1224 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1225 unsure, s = lfdirstate.status(matchmod.always(), subrepos=[],
1225 unsure, s = lfdirstate.status(matchmod.always(), subrepos=[],
1226 ignored=False, clean=False, unknown=False)
1226 ignored=False, clean=False, unknown=False)
1227
1227
1228 # Call into the normal remove code, but the removing of the standin, we want
1228 # Call into the normal remove code, but the removing of the standin, we want
1229 # to have handled by original addremove. Monkey patching here makes sure
1229 # to have handled by original addremove. Monkey patching here makes sure
1230 # we don't remove the standin in the largefiles code, preventing a very
1230 # we don't remove the standin in the largefiles code, preventing a very
1231 # confused state later.
1231 # confused state later.
1232 if s.deleted:
1232 if s.deleted:
1233 m = copy.copy(matcher)
1233 m = copy.copy(matcher)
1234
1234
1235 # The m._files and m._map attributes are not changed to the deleted list
1235 # The m._files and m._map attributes are not changed to the deleted list
1236 # because that affects the m.exact() test, which in turn governs whether
1236 # because that affects the m.exact() test, which in turn governs whether
1237 # or not the file name is printed, and how. Simply limit the original
1237 # or not the file name is printed, and how. Simply limit the original
1238 # matches to those in the deleted status list.
1238 # matches to those in the deleted status list.
1239 matchfn = m.matchfn
1239 matchfn = m.matchfn
1240 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1240 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1241
1241
1242 removelargefiles(repo.ui, repo, True, m, uipathfn, opts.get('dry_run'),
1242 removelargefiles(repo.ui, repo, True, m, uipathfn, opts.get('dry_run'),
1243 **pycompat.strkwargs(opts))
1243 **pycompat.strkwargs(opts))
1244 # Call into the normal add code, and any files that *should* be added as
1244 # Call into the normal add code, and any files that *should* be added as
1245 # largefiles will be
1245 # largefiles will be
1246 added, bad = addlargefiles(repo.ui, repo, True, matcher, uipathfn,
1246 added, bad = addlargefiles(repo.ui, repo, True, matcher, uipathfn,
1247 **pycompat.strkwargs(opts))
1247 **pycompat.strkwargs(opts))
1248 # Now that we've handled largefiles, hand off to the original addremove
1248 # Now that we've handled largefiles, hand off to the original addremove
1249 # function to take care of the rest. Make sure it doesn't do anything with
1249 # function to take care of the rest. Make sure it doesn't do anything with
1250 # largefiles by passing a matcher that will ignore them.
1250 # largefiles by passing a matcher that will ignore them.
1251 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1251 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1252 return orig(repo, matcher, prefix, uipathfn, opts)
1252 return orig(repo, matcher, prefix, uipathfn, opts)
1253
1253
1254 # Calling purge with --all will cause the largefiles to be deleted.
1254 # Calling purge with --all will cause the largefiles to be deleted.
1255 # Override repo.status to prevent this from happening.
1255 # Override repo.status to prevent this from happening.
1256 @eh.wrapcommand('purge', extension='purge')
1256 @eh.wrapcommand('purge', extension='purge')
1257 def overridepurge(orig, ui, repo, *dirs, **opts):
1257 def overridepurge(orig, ui, repo, *dirs, **opts):
1258 # XXX Monkey patching a repoview will not work. The assigned attribute will
1258 # XXX Monkey patching a repoview will not work. The assigned attribute will
1259 # be set on the unfiltered repo, but we will only lookup attributes in the
1259 # be set on the unfiltered repo, but we will only lookup attributes in the
1260 # unfiltered repo if the lookup in the repoview object itself fails. As the
1260 # unfiltered repo if the lookup in the repoview object itself fails. As the
1261 # monkey patched method exists on the repoview class the lookup will not
1261 # monkey patched method exists on the repoview class the lookup will not
1262 # fail. As a result, the original version will shadow the monkey patched
1262 # fail. As a result, the original version will shadow the monkey patched
1263 # one, defeating the monkey patch.
1263 # one, defeating the monkey patch.
1264 #
1264 #
1265 # As a work around we use an unfiltered repo here. We should do something
1265 # As a work around we use an unfiltered repo here. We should do something
1266 # cleaner instead.
1266 # cleaner instead.
1267 repo = repo.unfiltered()
1267 repo = repo.unfiltered()
1268 oldstatus = repo.status
1268 oldstatus = repo.status
1269 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1269 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1270 clean=False, unknown=False, listsubrepos=False):
1270 clean=False, unknown=False, listsubrepos=False):
1271 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1271 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1272 listsubrepos)
1272 listsubrepos)
1273 lfdirstate = lfutil.openlfdirstate(ui, repo)
1273 lfdirstate = lfutil.openlfdirstate(ui, repo)
1274 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1274 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1275 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1275 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1276 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1276 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1277 unknown, ignored, r.clean)
1277 unknown, ignored, r.clean)
1278 repo.status = overridestatus
1278 repo.status = overridestatus
1279 orig(ui, repo, *dirs, **opts)
1279 orig(ui, repo, *dirs, **opts)
1280 repo.status = oldstatus
1280 repo.status = oldstatus
1281
1281
1282 @eh.wrapcommand('rollback')
1282 @eh.wrapcommand('rollback')
1283 def overriderollback(orig, ui, repo, **opts):
1283 def overriderollback(orig, ui, repo, **opts):
1284 with repo.wlock():
1284 with repo.wlock():
1285 before = repo.dirstate.parents()
1285 before = repo.dirstate.parents()
1286 orphans = set(f for f in repo.dirstate
1286 orphans = set(f for f in repo.dirstate
1287 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1287 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1288 result = orig(ui, repo, **opts)
1288 result = orig(ui, repo, **opts)
1289 after = repo.dirstate.parents()
1289 after = repo.dirstate.parents()
1290 if before == after:
1290 if before == after:
1291 return result # no need to restore standins
1291 return result # no need to restore standins
1292
1292
1293 pctx = repo['.']
1293 pctx = repo['.']
1294 for f in repo.dirstate:
1294 for f in repo.dirstate:
1295 if lfutil.isstandin(f):
1295 if lfutil.isstandin(f):
1296 orphans.discard(f)
1296 orphans.discard(f)
1297 if repo.dirstate[f] == 'r':
1297 if repo.dirstate[f] == 'r':
1298 repo.wvfs.unlinkpath(f, ignoremissing=True)
1298 repo.wvfs.unlinkpath(f, ignoremissing=True)
1299 elif f in pctx:
1299 elif f in pctx:
1300 fctx = pctx[f]
1300 fctx = pctx[f]
1301 repo.wwrite(f, fctx.data(), fctx.flags())
1301 repo.wwrite(f, fctx.data(), fctx.flags())
1302 else:
1302 else:
1303 # content of standin is not so important in 'a',
1303 # content of standin is not so important in 'a',
1304 # 'm' or 'n' (coming from the 2nd parent) cases
1304 # 'm' or 'n' (coming from the 2nd parent) cases
1305 lfutil.writestandin(repo, f, '', False)
1305 lfutil.writestandin(repo, f, '', False)
1306 for standin in orphans:
1306 for standin in orphans:
1307 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1307 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1308
1308
1309 lfdirstate = lfutil.openlfdirstate(ui, repo)
1309 lfdirstate = lfutil.openlfdirstate(ui, repo)
1310 orphans = set(lfdirstate)
1310 orphans = set(lfdirstate)
1311 lfiles = lfutil.listlfiles(repo)
1311 lfiles = lfutil.listlfiles(repo)
1312 for file in lfiles:
1312 for file in lfiles:
1313 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1313 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1314 orphans.discard(file)
1314 orphans.discard(file)
1315 for lfile in orphans:
1315 for lfile in orphans:
1316 lfdirstate.drop(lfile)
1316 lfdirstate.drop(lfile)
1317 lfdirstate.write()
1317 lfdirstate.write()
1318 return result
1318 return result
1319
1319
1320 @eh.wrapcommand('transplant', extension='transplant')
1320 @eh.wrapcommand('transplant', extension='transplant')
1321 def overridetransplant(orig, ui, repo, *revs, **opts):
1321 def overridetransplant(orig, ui, repo, *revs, **opts):
1322 resuming = opts.get(r'continue')
1322 resuming = opts.get(r'continue')
1323 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1323 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1324 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1324 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1325 try:
1325 try:
1326 result = orig(ui, repo, *revs, **opts)
1326 result = orig(ui, repo, *revs, **opts)
1327 finally:
1327 finally:
1328 repo._lfstatuswriters.pop()
1328 repo._lfstatuswriters.pop()
1329 repo._lfcommithooks.pop()
1329 repo._lfcommithooks.pop()
1330 return result
1330 return result
1331
1331
1332 @eh.wrapcommand('cat')
1332 @eh.wrapcommand('cat')
1333 def overridecat(orig, ui, repo, file1, *pats, **opts):
1333 def overridecat(orig, ui, repo, file1, *pats, **opts):
1334 opts = pycompat.byteskwargs(opts)
1334 opts = pycompat.byteskwargs(opts)
1335 ctx = scmutil.revsingle(repo, opts.get('rev'))
1335 ctx = scmutil.revsingle(repo, opts.get('rev'))
1336 err = 1
1336 err = 1
1337 notbad = set()
1337 notbad = set()
1338 m = scmutil.match(ctx, (file1,) + pats, opts)
1338 m = scmutil.match(ctx, (file1,) + pats, opts)
1339 origmatchfn = m.matchfn
1339 origmatchfn = m.matchfn
1340 def lfmatchfn(f):
1340 def lfmatchfn(f):
1341 if origmatchfn(f):
1341 if origmatchfn(f):
1342 return True
1342 return True
1343 lf = lfutil.splitstandin(f)
1343 lf = lfutil.splitstandin(f)
1344 if lf is None:
1344 if lf is None:
1345 return False
1345 return False
1346 notbad.add(lf)
1346 notbad.add(lf)
1347 return origmatchfn(lf)
1347 return origmatchfn(lf)
1348 m.matchfn = lfmatchfn
1348 m.matchfn = lfmatchfn
1349 origbadfn = m.bad
1349 origbadfn = m.bad
1350 def lfbadfn(f, msg):
1350 def lfbadfn(f, msg):
1351 if not f in notbad:
1351 if not f in notbad:
1352 origbadfn(f, msg)
1352 origbadfn(f, msg)
1353 m.bad = lfbadfn
1353 m.bad = lfbadfn
1354
1354
1355 origvisitdirfn = m.visitdir
1355 origvisitdirfn = m.visitdir
1356 def lfvisitdirfn(dir):
1356 def lfvisitdirfn(dir):
1357 if dir == lfutil.shortname:
1357 if dir == lfutil.shortname:
1358 return True
1358 return True
1359 ret = origvisitdirfn(dir)
1359 ret = origvisitdirfn(dir)
1360 if ret:
1360 if ret:
1361 return ret
1361 return ret
1362 lf = lfutil.splitstandin(dir)
1362 lf = lfutil.splitstandin(dir)
1363 if lf is None:
1363 if lf is None:
1364 return False
1364 return False
1365 return origvisitdirfn(lf)
1365 return origvisitdirfn(lf)
1366 m.visitdir = lfvisitdirfn
1366 m.visitdir = lfvisitdirfn
1367
1367
1368 for f in ctx.walk(m):
1368 for f in ctx.walk(m):
1369 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1369 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1370 lf = lfutil.splitstandin(f)
1370 lf = lfutil.splitstandin(f)
1371 if lf is None or origmatchfn(f):
1371 if lf is None or origmatchfn(f):
1372 # duplicating unreachable code from commands.cat
1372 # duplicating unreachable code from commands.cat
1373 data = ctx[f].data()
1373 data = ctx[f].data()
1374 if opts.get('decode'):
1374 if opts.get('decode'):
1375 data = repo.wwritedata(f, data)
1375 data = repo.wwritedata(f, data)
1376 fp.write(data)
1376 fp.write(data)
1377 else:
1377 else:
1378 hash = lfutil.readasstandin(ctx[f])
1378 hash = lfutil.readasstandin(ctx[f])
1379 if not lfutil.inusercache(repo.ui, hash):
1379 if not lfutil.inusercache(repo.ui, hash):
1380 store = storefactory.openstore(repo)
1380 store = storefactory.openstore(repo)
1381 success, missing = store.get([(lf, hash)])
1381 success, missing = store.get([(lf, hash)])
1382 if len(success) != 1:
1382 if len(success) != 1:
1383 raise error.Abort(
1383 raise error.Abort(
1384 _('largefile %s is not in cache and could not be '
1384 _('largefile %s is not in cache and could not be '
1385 'downloaded') % lf)
1385 'downloaded') % lf)
1386 path = lfutil.usercachepath(repo.ui, hash)
1386 path = lfutil.usercachepath(repo.ui, hash)
1387 with open(path, "rb") as fpin:
1387 with open(path, "rb") as fpin:
1388 for chunk in util.filechunkiter(fpin):
1388 for chunk in util.filechunkiter(fpin):
1389 fp.write(chunk)
1389 fp.write(chunk)
1390 err = 0
1390 err = 0
1391 return err
1391 return err
1392
1392
1393 @eh.wrapfunction(merge, 'update')
1393 @eh.wrapfunction(merge, 'update')
1394 def mergeupdate(orig, repo, node, branchmerge, force,
1394 def mergeupdate(orig, repo, node, branchmerge, force,
1395 *args, **kwargs):
1395 *args, **kwargs):
1396 matcher = kwargs.get(r'matcher', None)
1396 matcher = kwargs.get(r'matcher', None)
1397 # note if this is a partial update
1397 # note if this is a partial update
1398 partial = matcher and not matcher.always()
1398 partial = matcher and not matcher.always()
1399 with repo.wlock():
1399 with repo.wlock():
1400 # branch | | |
1400 # branch | | |
1401 # merge | force | partial | action
1401 # merge | force | partial | action
1402 # -------+-------+---------+--------------
1402 # -------+-------+---------+--------------
1403 # x | x | x | linear-merge
1403 # x | x | x | linear-merge
1404 # o | x | x | branch-merge
1404 # o | x | x | branch-merge
1405 # x | o | x | overwrite (as clean update)
1405 # x | o | x | overwrite (as clean update)
1406 # o | o | x | force-branch-merge (*1)
1406 # o | o | x | force-branch-merge (*1)
1407 # x | x | o | (*)
1407 # x | x | o | (*)
1408 # o | x | o | (*)
1408 # o | x | o | (*)
1409 # x | o | o | overwrite (as revert)
1409 # x | o | o | overwrite (as revert)
1410 # o | o | o | (*)
1410 # o | o | o | (*)
1411 #
1411 #
1412 # (*) don't care
1412 # (*) don't care
1413 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1413 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1414
1414
1415 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1415 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1416 unsure, s = lfdirstate.status(matchmod.always(), subrepos=[],
1416 unsure, s = lfdirstate.status(matchmod.always(), subrepos=[],
1417 ignored=False, clean=True, unknown=False)
1417 ignored=False, clean=True, unknown=False)
1418 oldclean = set(s.clean)
1418 oldclean = set(s.clean)
1419 pctx = repo['.']
1419 pctx = repo['.']
1420 dctx = repo[node]
1420 dctx = repo[node]
1421 for lfile in unsure + s.modified:
1421 for lfile in unsure + s.modified:
1422 lfileabs = repo.wvfs.join(lfile)
1422 lfileabs = repo.wvfs.join(lfile)
1423 if not repo.wvfs.exists(lfileabs):
1423 if not repo.wvfs.exists(lfileabs):
1424 continue
1424 continue
1425 lfhash = lfutil.hashfile(lfileabs)
1425 lfhash = lfutil.hashfile(lfileabs)
1426 standin = lfutil.standin(lfile)
1426 standin = lfutil.standin(lfile)
1427 lfutil.writestandin(repo, standin, lfhash,
1427 lfutil.writestandin(repo, standin, lfhash,
1428 lfutil.getexecutable(lfileabs))
1428 lfutil.getexecutable(lfileabs))
1429 if (standin in pctx and
1429 if (standin in pctx and
1430 lfhash == lfutil.readasstandin(pctx[standin])):
1430 lfhash == lfutil.readasstandin(pctx[standin])):
1431 oldclean.add(lfile)
1431 oldclean.add(lfile)
1432 for lfile in s.added:
1432 for lfile in s.added:
1433 fstandin = lfutil.standin(lfile)
1433 fstandin = lfutil.standin(lfile)
1434 if fstandin not in dctx:
1434 if fstandin not in dctx:
1435 # in this case, content of standin file is meaningless
1435 # in this case, content of standin file is meaningless
1436 # (in dctx, lfile is unknown, or normal file)
1436 # (in dctx, lfile is unknown, or normal file)
1437 continue
1437 continue
1438 lfutil.updatestandin(repo, lfile, fstandin)
1438 lfutil.updatestandin(repo, lfile, fstandin)
1439 # mark all clean largefiles as dirty, just in case the update gets
1439 # mark all clean largefiles as dirty, just in case the update gets
1440 # interrupted before largefiles and lfdirstate are synchronized
1440 # interrupted before largefiles and lfdirstate are synchronized
1441 for lfile in oldclean:
1441 for lfile in oldclean:
1442 lfdirstate.normallookup(lfile)
1442 lfdirstate.normallookup(lfile)
1443 lfdirstate.write()
1443 lfdirstate.write()
1444
1444
1445 oldstandins = lfutil.getstandinsstate(repo)
1445 oldstandins = lfutil.getstandinsstate(repo)
1446 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1446 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1447 # good candidate for in-memory merge (large files, custom dirstate,
1447 # good candidate for in-memory merge (large files, custom dirstate,
1448 # matcher usage).
1448 # matcher usage).
1449 kwargs[r'wc'] = repo[None]
1449 kwargs[r'wc'] = repo[None]
1450 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1450 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1451
1451
1452 newstandins = lfutil.getstandinsstate(repo)
1452 newstandins = lfutil.getstandinsstate(repo)
1453 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1453 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1454
1454
1455 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1455 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1456 # all the ones that didn't change as clean
1456 # all the ones that didn't change as clean
1457 for lfile in oldclean.difference(filelist):
1457 for lfile in oldclean.difference(filelist):
1458 lfdirstate.normal(lfile)
1458 lfdirstate.normal(lfile)
1459 lfdirstate.write()
1459 lfdirstate.write()
1460
1460
1461 if branchmerge or force or partial:
1461 if branchmerge or force or partial:
1462 filelist.extend(s.deleted + s.removed)
1462 filelist.extend(s.deleted + s.removed)
1463
1463
1464 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1464 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1465 normallookup=partial)
1465 normallookup=partial)
1466
1466
1467 return result
1467 return result
1468
1468
1469 @eh.wrapfunction(scmutil, 'marktouched')
1469 @eh.wrapfunction(scmutil, 'marktouched')
1470 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1470 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1471 result = orig(repo, files, *args, **kwargs)
1471 result = orig(repo, files, *args, **kwargs)
1472
1472
1473 filelist = []
1473 filelist = []
1474 for f in files:
1474 for f in files:
1475 lf = lfutil.splitstandin(f)
1475 lf = lfutil.splitstandin(f)
1476 if lf is not None:
1476 if lf is not None:
1477 filelist.append(lf)
1477 filelist.append(lf)
1478 if filelist:
1478 if filelist:
1479 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1479 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1480 printmessage=False, normallookup=True)
1480 printmessage=False, normallookup=True)
1481
1481
1482 return result
1482 return result
1483
1483
1484 @eh.wrapfunction(upgrade, 'preservedrequirements')
1484 @eh.wrapfunction(upgrade, 'preservedrequirements')
1485 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1485 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1486 def upgraderequirements(orig, repo):
1486 def upgraderequirements(orig, repo):
1487 reqs = orig(repo)
1487 reqs = orig(repo)
1488 if 'largefiles' in repo.requirements:
1488 if 'largefiles' in repo.requirements:
1489 reqs.add('largefiles')
1489 reqs.add('largefiles')
1490 return reqs
1490 return reqs
1491
1491
1492 _lfscheme = 'largefile://'
1492 _lfscheme = 'largefile://'
1493
1493
1494 @eh.wrapfunction(urlmod, 'open')
1494 @eh.wrapfunction(urlmod, 'open')
1495 def openlargefile(orig, ui, url_, data=None):
1495 def openlargefile(orig, ui, url_, data=None):
1496 if url_.startswith(_lfscheme):
1496 if url_.startswith(_lfscheme):
1497 if data:
1497 if data:
1498 msg = "cannot use data on a 'largefile://' url"
1498 msg = "cannot use data on a 'largefile://' url"
1499 raise error.ProgrammingError(msg)
1499 raise error.ProgrammingError(msg)
1500 lfid = url_[len(_lfscheme):]
1500 lfid = url_[len(_lfscheme):]
1501 return storefactory.getlfile(ui, lfid)
1501 return storefactory.getlfile(ui, lfid)
1502 else:
1502 else:
1503 return orig(ui, url_, data=data)
1503 return orig(ui, url_, data=data)
@@ -1,459 +1,459 b''
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 narrowspec,
23 narrowspec,
24 node,
24 node,
25 pycompat,
25 pycompat,
26 registrar,
26 registrar,
27 repair,
27 repair,
28 repository,
28 repository,
29 repoview,
29 repoview,
30 sparse,
30 sparse,
31 util,
31 util,
32 wireprototypes,
32 wireprototypes,
33 )
33 )
34
34
35 table = {}
35 table = {}
36 command = registrar.command(table)
36 command = registrar.command(table)
37
37
38 def setup():
38 def setup():
39 """Wraps user-facing mercurial commands with narrow-aware versions."""
39 """Wraps user-facing mercurial commands with narrow-aware versions."""
40
40
41 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
41 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
42 entry[1].append(('', 'narrow', None,
42 entry[1].append(('', 'narrow', None,
43 _("create a narrow clone of select files")))
43 _("create a narrow clone of select files")))
44 entry[1].append(('', 'depth', '',
44 entry[1].append(('', 'depth', '',
45 _("limit the history fetched by distance from heads")))
45 _("limit the history fetched by distance from heads")))
46 entry[1].append(('', 'narrowspec', '',
46 entry[1].append(('', 'narrowspec', '',
47 _("read narrowspecs from file")))
47 _("read narrowspecs from file")))
48 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
48 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
49 if 'sparse' not in extensions.enabled():
49 if 'sparse' not in extensions.enabled():
50 entry[1].append(('', 'include', [],
50 entry[1].append(('', 'include', [],
51 _("specifically fetch this file/directory")))
51 _("specifically fetch this file/directory")))
52 entry[1].append(
52 entry[1].append(
53 ('', 'exclude', [],
53 ('', 'exclude', [],
54 _("do not fetch this file/directory, even if included")))
54 _("do not fetch this file/directory, even if included")))
55
55
56 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
56 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
57 entry[1].append(('', 'depth', '',
57 entry[1].append(('', 'depth', '',
58 _("limit the history fetched by distance from heads")))
58 _("limit the history fetched by distance from heads")))
59
59
60 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
60 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
61
61
62 def clonenarrowcmd(orig, ui, repo, *args, **opts):
62 def clonenarrowcmd(orig, ui, repo, *args, **opts):
63 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
63 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
64 opts = pycompat.byteskwargs(opts)
64 opts = pycompat.byteskwargs(opts)
65 wrappedextraprepare = util.nullcontextmanager()
65 wrappedextraprepare = util.nullcontextmanager()
66 narrowspecfile = opts['narrowspec']
66 narrowspecfile = opts['narrowspec']
67
67
68 if narrowspecfile:
68 if narrowspecfile:
69 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
69 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
70 ui.status(_("reading narrowspec from '%s'\n") % filepath)
70 ui.status(_("reading narrowspec from '%s'\n") % filepath)
71 try:
71 try:
72 fdata = util.readfile(filepath)
72 fdata = util.readfile(filepath)
73 except IOError as inst:
73 except IOError as inst:
74 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
74 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
75 (filepath, encoding.strtolocal(inst.strerror)))
75 (filepath, encoding.strtolocal(inst.strerror)))
76
76
77 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
77 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
78 if profiles:
78 if profiles:
79 raise error.Abort(_("cannot specify other files using '%include' in"
79 raise error.Abort(_("cannot specify other files using '%include' in"
80 " narrowspec"))
80 " narrowspec"))
81
81
82 narrowspec.validatepatterns(includes)
82 narrowspec.validatepatterns(includes)
83 narrowspec.validatepatterns(excludes)
83 narrowspec.validatepatterns(excludes)
84
84
85 # narrowspec is passed so we should assume that user wants narrow clone
85 # narrowspec is passed so we should assume that user wants narrow clone
86 opts['narrow'] = True
86 opts['narrow'] = True
87 opts['include'].extend(includes)
87 opts['include'].extend(includes)
88 opts['exclude'].extend(excludes)
88 opts['exclude'].extend(excludes)
89
89
90 if opts['narrow']:
90 if opts['narrow']:
91 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
91 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
92 orig(pullop, kwargs)
92 orig(pullop, kwargs)
93
93
94 if opts.get('depth'):
94 if opts.get('depth'):
95 kwargs['depth'] = opts['depth']
95 kwargs['depth'] = opts['depth']
96 wrappedextraprepare = extensions.wrappedfunction(exchange,
96 wrappedextraprepare = extensions.wrappedfunction(exchange,
97 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
97 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
98
98
99 with wrappedextraprepare:
99 with wrappedextraprepare:
100 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
100 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
101
101
102 def pullnarrowcmd(orig, ui, repo, *args, **opts):
102 def pullnarrowcmd(orig, ui, repo, *args, **opts):
103 """Wraps pull command to allow modifying narrow spec."""
103 """Wraps pull command to allow modifying narrow spec."""
104 wrappedextraprepare = util.nullcontextmanager()
104 wrappedextraprepare = util.nullcontextmanager()
105 if repository.NARROW_REQUIREMENT in repo.requirements:
105 if repository.NARROW_REQUIREMENT in repo.requirements:
106
106
107 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
107 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
108 orig(pullop, kwargs)
108 orig(pullop, kwargs)
109 if opts.get(r'depth'):
109 if opts.get(r'depth'):
110 kwargs['depth'] = opts[r'depth']
110 kwargs['depth'] = opts[r'depth']
111 wrappedextraprepare = extensions.wrappedfunction(exchange,
111 wrappedextraprepare = extensions.wrappedfunction(exchange,
112 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
112 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
113
113
114 with wrappedextraprepare:
114 with wrappedextraprepare:
115 return orig(ui, repo, *args, **opts)
115 return orig(ui, repo, *args, **opts)
116
116
117 def archivenarrowcmd(orig, ui, repo, *args, **opts):
117 def archivenarrowcmd(orig, ui, repo, *args, **opts):
118 """Wraps archive command to narrow the default includes."""
118 """Wraps archive command to narrow the default includes."""
119 if repository.NARROW_REQUIREMENT in repo.requirements:
119 if repository.NARROW_REQUIREMENT in repo.requirements:
120 repo_includes, repo_excludes = repo.narrowpats
120 repo_includes, repo_excludes = repo.narrowpats
121 includes = set(opts.get(r'include', []))
121 includes = set(opts.get(r'include', []))
122 excludes = set(opts.get(r'exclude', []))
122 excludes = set(opts.get(r'exclude', []))
123 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
123 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
124 includes, excludes, repo_includes, repo_excludes)
124 includes, excludes, repo_includes, repo_excludes)
125 if includes:
125 if includes:
126 opts[r'include'] = includes
126 opts[r'include'] = includes
127 if excludes:
127 if excludes:
128 opts[r'exclude'] = excludes
128 opts[r'exclude'] = excludes
129 return orig(ui, repo, *args, **opts)
129 return orig(ui, repo, *args, **opts)
130
130
131 def pullbundle2extraprepare(orig, pullop, kwargs):
131 def pullbundle2extraprepare(orig, pullop, kwargs):
132 repo = pullop.repo
132 repo = pullop.repo
133 if repository.NARROW_REQUIREMENT not in repo.requirements:
133 if repository.NARROW_REQUIREMENT not in repo.requirements:
134 return orig(pullop, kwargs)
134 return orig(pullop, kwargs)
135
135
136 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
136 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
137 raise error.Abort(_("server does not support narrow clones"))
137 raise error.Abort(_("server does not support narrow clones"))
138 orig(pullop, kwargs)
138 orig(pullop, kwargs)
139 kwargs['narrow'] = True
139 kwargs['narrow'] = True
140 include, exclude = repo.narrowpats
140 include, exclude = repo.narrowpats
141 kwargs['oldincludepats'] = include
141 kwargs['oldincludepats'] = include
142 kwargs['oldexcludepats'] = exclude
142 kwargs['oldexcludepats'] = exclude
143 if include:
143 if include:
144 kwargs['includepats'] = include
144 kwargs['includepats'] = include
145 if exclude:
145 if exclude:
146 kwargs['excludepats'] = exclude
146 kwargs['excludepats'] = exclude
147 # calculate known nodes only in ellipses cases because in non-ellipses cases
147 # calculate known nodes only in ellipses cases because in non-ellipses cases
148 # we have all the nodes
148 # we have all the nodes
149 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
149 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
150 kwargs['known'] = [node.hex(ctx.node()) for ctx in
150 kwargs['known'] = [node.hex(ctx.node()) for ctx in
151 repo.set('::%ln', pullop.common)
151 repo.set('::%ln', pullop.common)
152 if ctx.node() != node.nullid]
152 if ctx.node() != node.nullid]
153 if not kwargs['known']:
153 if not kwargs['known']:
154 # Mercurial serializes an empty list as '' and deserializes it as
154 # Mercurial serializes an empty list as '' and deserializes it as
155 # [''], so delete it instead to avoid handling the empty string on
155 # [''], so delete it instead to avoid handling the empty string on
156 # the server.
156 # the server.
157 del kwargs['known']
157 del kwargs['known']
158
158
159 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
159 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
160 pullbundle2extraprepare)
160 pullbundle2extraprepare)
161
161
162 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
162 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
163 newincludes, newexcludes, force):
163 newincludes, newexcludes, force):
164 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
164 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
165 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
165 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
166
166
167 # This is essentially doing "hg outgoing" to find all local-only
167 # This is essentially doing "hg outgoing" to find all local-only
168 # commits. We will then check that the local-only commits don't
168 # commits. We will then check that the local-only commits don't
169 # have any changes to files that will be untracked.
169 # have any changes to files that will be untracked.
170 unfi = repo.unfiltered()
170 unfi = repo.unfiltered()
171 outgoing = discovery.findcommonoutgoing(unfi, remote,
171 outgoing = discovery.findcommonoutgoing(unfi, remote,
172 commoninc=commoninc)
172 commoninc=commoninc)
173 ui.status(_('looking for local changes to affected paths\n'))
173 ui.status(_('looking for local changes to affected paths\n'))
174 localnodes = []
174 localnodes = []
175 for n in itertools.chain(outgoing.missing, outgoing.excluded):
175 for n in itertools.chain(outgoing.missing, outgoing.excluded):
176 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
176 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
177 localnodes.append(n)
177 localnodes.append(n)
178 revstostrip = unfi.revs('descendants(%ln)', localnodes)
178 revstostrip = unfi.revs('descendants(%ln)', localnodes)
179 hiddenrevs = repoview.filterrevs(repo, 'visible')
179 hiddenrevs = repoview.filterrevs(repo, 'visible')
180 visibletostrip = list(repo.changelog.node(r)
180 visibletostrip = list(repo.changelog.node(r)
181 for r in (revstostrip - hiddenrevs))
181 for r in (revstostrip - hiddenrevs))
182 if visibletostrip:
182 if visibletostrip:
183 ui.status(_('The following changeset(s) or their ancestors have '
183 ui.status(_('The following changeset(s) or their ancestors have '
184 'local changes not on the remote:\n'))
184 'local changes not on the remote:\n'))
185 maxnodes = 10
185 maxnodes = 10
186 if ui.verbose or len(visibletostrip) <= maxnodes:
186 if ui.verbose or len(visibletostrip) <= maxnodes:
187 for n in visibletostrip:
187 for n in visibletostrip:
188 ui.status('%s\n' % node.short(n))
188 ui.status('%s\n' % node.short(n))
189 else:
189 else:
190 for n in visibletostrip[:maxnodes]:
190 for n in visibletostrip[:maxnodes]:
191 ui.status('%s\n' % node.short(n))
191 ui.status('%s\n' % node.short(n))
192 ui.status(_('...and %d more, use --verbose to list all\n') %
192 ui.status(_('...and %d more, use --verbose to list all\n') %
193 (len(visibletostrip) - maxnodes))
193 (len(visibletostrip) - maxnodes))
194 if not force:
194 if not force:
195 raise error.Abort(_('local changes found'),
195 raise error.Abort(_('local changes found'),
196 hint=_('use --force-delete-local-changes to '
196 hint=_('use --force-delete-local-changes to '
197 'ignore'))
197 'ignore'))
198
198
199 with ui.uninterruptible():
199 with ui.uninterruptible():
200 if revstostrip:
200 if revstostrip:
201 tostrip = [unfi.changelog.node(r) for r in revstostrip]
201 tostrip = [unfi.changelog.node(r) for r in revstostrip]
202 if repo['.'].node() in tostrip:
202 if repo['.'].node() in tostrip:
203 # stripping working copy, so move to a different commit first
203 # stripping working copy, so move to a different commit first
204 urev = max(repo.revs('(::%n) - %ln + null',
204 urev = max(repo.revs('(::%n) - %ln + null',
205 repo['.'].node(), visibletostrip))
205 repo['.'].node(), visibletostrip))
206 hg.clean(repo, urev)
206 hg.clean(repo, urev)
207 overrides = {('devel', 'strip-obsmarkers'): False}
207 overrides = {('devel', 'strip-obsmarkers'): False}
208 with ui.configoverride(overrides, 'narrow'):
208 with ui.configoverride(overrides, 'narrow'):
209 repair.strip(ui, unfi, tostrip, topic='narrow')
209 repair.strip(ui, unfi, tostrip, topic='narrow')
210
210
211 todelete = []
211 todelete = []
212 for f, f2, size in repo.store.datafiles():
212 for f, f2, size in repo.store.datafiles():
213 if f.startswith('data/'):
213 if f.startswith('data/'):
214 file = f[5:-2]
214 file = f[5:-2]
215 if not newmatch(file):
215 if not newmatch(file):
216 todelete.append(f)
216 todelete.append(f)
217 elif f.startswith('meta/'):
217 elif f.startswith('meta/'):
218 dir = f[5:-13]
218 dir = f[5:-13]
219 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
219 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
220 include = True
220 include = True
221 for d in dirs:
221 for d in dirs:
222 visit = newmatch.visitdir(d)
222 visit = newmatch.visitdir(d)
223 if not visit:
223 if not visit:
224 include = False
224 include = False
225 break
225 break
226 if visit == 'all':
226 if visit == 'all':
227 break
227 break
228 if not include:
228 if not include:
229 todelete.append(f)
229 todelete.append(f)
230
230
231 repo.destroying()
231 repo.destroying()
232
232
233 with repo.transaction('narrowing'):
233 with repo.transaction('narrowing'):
234 # Update narrowspec before removing revlogs, so repo won't be
234 # Update narrowspec before removing revlogs, so repo won't be
235 # corrupt in case of crash
235 # corrupt in case of crash
236 repo.setnarrowpats(newincludes, newexcludes)
236 repo.setnarrowpats(newincludes, newexcludes)
237
237
238 for f in todelete:
238 for f in todelete:
239 ui.status(_('deleting %s\n') % f)
239 ui.status(_('deleting %s\n') % f)
240 util.unlinkpath(repo.svfs.join(f))
240 util.unlinkpath(repo.svfs.join(f))
241 repo.store.markremoved(f)
241 repo.store.markremoved(f)
242
242
243 narrowspec.updateworkingcopy(repo, assumeclean=True)
243 narrowspec.updateworkingcopy(repo, assumeclean=True)
244 narrowspec.copytoworkingcopy(repo)
244 narrowspec.copytoworkingcopy(repo)
245
245
246 repo.destroyed()
246 repo.destroyed()
247
247
248 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
248 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
249 newincludes, newexcludes):
249 newincludes, newexcludes):
250 # for now we assume that if a server has ellipses enabled, we will be
250 # for now we assume that if a server has ellipses enabled, we will be
251 # exchanging ellipses nodes. In future we should add ellipses as a client
251 # exchanging ellipses nodes. In future we should add ellipses as a client
252 # side requirement (maybe) to distinguish a client is shallow or not and
252 # side requirement (maybe) to distinguish a client is shallow or not and
253 # then send that information to server whether we want ellipses or not.
253 # then send that information to server whether we want ellipses or not.
254 # Theoretically a non-ellipses repo should be able to use narrow
254 # Theoretically a non-ellipses repo should be able to use narrow
255 # functionality from an ellipses enabled server
255 # functionality from an ellipses enabled server
256 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
256 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
257
257
258 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
258 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
259 orig(pullop, kwargs)
259 orig(pullop, kwargs)
260 # The old{in,ex}cludepats have already been set by orig()
260 # The old{in,ex}cludepats have already been set by orig()
261 kwargs['includepats'] = newincludes
261 kwargs['includepats'] = newincludes
262 kwargs['excludepats'] = newexcludes
262 kwargs['excludepats'] = newexcludes
263 wrappedextraprepare = extensions.wrappedfunction(exchange,
263 wrappedextraprepare = extensions.wrappedfunction(exchange,
264 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
264 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
265
265
266 # define a function that narrowbundle2 can call after creating the
266 # define a function that narrowbundle2 can call after creating the
267 # backup bundle, but before applying the bundle from the server
267 # backup bundle, but before applying the bundle from the server
268 def setnewnarrowpats():
268 def setnewnarrowpats():
269 repo.setnarrowpats(newincludes, newexcludes)
269 repo.setnarrowpats(newincludes, newexcludes)
270 repo.setnewnarrowpats = setnewnarrowpats
270 repo.setnewnarrowpats = setnewnarrowpats
271 # silence the devel-warning of applying an empty changegroup
271 # silence the devel-warning of applying an empty changegroup
272 overrides = {('devel', 'all-warnings'): False}
272 overrides = {('devel', 'all-warnings'): False}
273
273
274 with ui.uninterruptible():
274 with ui.uninterruptible():
275 common = commoninc[0]
275 common = commoninc[0]
276 if ellipsesremote:
276 if ellipsesremote:
277 ds = repo.dirstate
277 ds = repo.dirstate
278 p1, p2 = ds.p1(), ds.p2()
278 p1, p2 = ds.p1(), ds.p2()
279 with ds.parentchange():
279 with ds.parentchange():
280 ds.setparents(node.nullid, node.nullid)
280 ds.setparents(node.nullid, node.nullid)
281 with wrappedextraprepare,\
281 with wrappedextraprepare:
282 repo.ui.configoverride(overrides, 'widen'):
282 with repo.ui.configoverride(overrides, 'widen'):
283 exchange.pull(repo, remote, heads=common)
283 exchange.pull(repo, remote, heads=common)
284 with ds.parentchange():
284 with ds.parentchange():
285 ds.setparents(p1, p2)
285 ds.setparents(p1, p2)
286 else:
286 else:
287 with remote.commandexecutor() as e:
287 with remote.commandexecutor() as e:
288 bundle = e.callcommand('narrow_widen', {
288 bundle = e.callcommand('narrow_widen', {
289 'oldincludes': oldincludes,
289 'oldincludes': oldincludes,
290 'oldexcludes': oldexcludes,
290 'oldexcludes': oldexcludes,
291 'newincludes': newincludes,
291 'newincludes': newincludes,
292 'newexcludes': newexcludes,
292 'newexcludes': newexcludes,
293 'cgversion': '03',
293 'cgversion': '03',
294 'commonheads': common,
294 'commonheads': common,
295 'known': [],
295 'known': [],
296 'ellipses': False,
296 'ellipses': False,
297 }).result()
297 }).result()
298
298
299 with repo.transaction('widening') as tr,\
299 with repo.transaction('widening') as tr:
300 repo.ui.configoverride(overrides, 'widen'):
300 with repo.ui.configoverride(overrides, 'widen'):
301 tgetter = lambda: tr
301 tgetter = lambda: tr
302 bundle2.processbundle(repo, bundle,
302 bundle2.processbundle(repo, bundle,
303 transactiongetter=tgetter)
303 transactiongetter=tgetter)
304
304
305 with repo.transaction('widening'):
305 with repo.transaction('widening'):
306 repo.setnewnarrowpats()
306 repo.setnewnarrowpats()
307 narrowspec.updateworkingcopy(repo)
307 narrowspec.updateworkingcopy(repo)
308 narrowspec.copytoworkingcopy(repo)
308 narrowspec.copytoworkingcopy(repo)
309
309
310 # TODO(rdamazio): Make new matcher format and update description
310 # TODO(rdamazio): Make new matcher format and update description
311 @command('tracked',
311 @command('tracked',
312 [('', 'addinclude', [], _('new paths to include')),
312 [('', 'addinclude', [], _('new paths to include')),
313 ('', 'removeinclude', [], _('old paths to no longer include')),
313 ('', 'removeinclude', [], _('old paths to no longer include')),
314 ('', 'addexclude', [], _('new paths to exclude')),
314 ('', 'addexclude', [], _('new paths to exclude')),
315 ('', 'import-rules', '', _('import narrowspecs from a file')),
315 ('', 'import-rules', '', _('import narrowspecs from a file')),
316 ('', 'removeexclude', [], _('old paths to no longer exclude')),
316 ('', 'removeexclude', [], _('old paths to no longer exclude')),
317 ('', 'clear', False, _('whether to replace the existing narrowspec')),
317 ('', 'clear', False, _('whether to replace the existing narrowspec')),
318 ('', 'force-delete-local-changes', False,
318 ('', 'force-delete-local-changes', False,
319 _('forces deletion of local changes when narrowing')),
319 _('forces deletion of local changes when narrowing')),
320 ('', 'update-working-copy', False,
320 ('', 'update-working-copy', False,
321 _('update working copy when the store has changed')),
321 _('update working copy when the store has changed')),
322 ] + commands.remoteopts,
322 ] + commands.remoteopts,
323 _('[OPTIONS]... [REMOTE]'),
323 _('[OPTIONS]... [REMOTE]'),
324 inferrepo=True)
324 inferrepo=True)
325 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
325 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
326 """show or change the current narrowspec
326 """show or change the current narrowspec
327
327
328 With no argument, shows the current narrowspec entries, one per line. Each
328 With no argument, shows the current narrowspec entries, one per line. Each
329 line will be prefixed with 'I' or 'X' for included or excluded patterns,
329 line will be prefixed with 'I' or 'X' for included or excluded patterns,
330 respectively.
330 respectively.
331
331
332 The narrowspec is comprised of expressions to match remote files and/or
332 The narrowspec is comprised of expressions to match remote files and/or
333 directories that should be pulled into your client.
333 directories that should be pulled into your client.
334 The narrowspec has *include* and *exclude* expressions, with excludes always
334 The narrowspec has *include* and *exclude* expressions, with excludes always
335 trumping includes: that is, if a file matches an exclude expression, it will
335 trumping includes: that is, if a file matches an exclude expression, it will
336 be excluded even if it also matches an include expression.
336 be excluded even if it also matches an include expression.
337 Excluding files that were never included has no effect.
337 Excluding files that were never included has no effect.
338
338
339 Each included or excluded entry is in the format described by
339 Each included or excluded entry is in the format described by
340 'hg help patterns'.
340 'hg help patterns'.
341
341
342 The options allow you to add or remove included and excluded expressions.
342 The options allow you to add or remove included and excluded expressions.
343
343
344 If --clear is specified, then all previous includes and excludes are DROPPED
344 If --clear is specified, then all previous includes and excludes are DROPPED
345 and replaced by the new ones specified to --addinclude and --addexclude.
345 and replaced by the new ones specified to --addinclude and --addexclude.
346 If --clear is specified without any further options, the narrowspec will be
346 If --clear is specified without any further options, the narrowspec will be
347 empty and will not match any files.
347 empty and will not match any files.
348 """
348 """
349 opts = pycompat.byteskwargs(opts)
349 opts = pycompat.byteskwargs(opts)
350 if repository.NARROW_REQUIREMENT not in repo.requirements:
350 if repository.NARROW_REQUIREMENT not in repo.requirements:
351 raise error.Abort(_('the tracked command is only supported on '
351 raise error.Abort(_('the tracked command is only supported on '
352 'respositories cloned with --narrow'))
352 'respositories cloned with --narrow'))
353
353
354 # Before supporting, decide whether it "hg tracked --clear" should mean
354 # Before supporting, decide whether it "hg tracked --clear" should mean
355 # tracking no paths or all paths.
355 # tracking no paths or all paths.
356 if opts['clear']:
356 if opts['clear']:
357 raise error.Abort(_('the --clear option is not yet supported'))
357 raise error.Abort(_('the --clear option is not yet supported'))
358
358
359 # import rules from a file
359 # import rules from a file
360 newrules = opts.get('import_rules')
360 newrules = opts.get('import_rules')
361 if newrules:
361 if newrules:
362 try:
362 try:
363 filepath = os.path.join(encoding.getcwd(), newrules)
363 filepath = os.path.join(encoding.getcwd(), newrules)
364 fdata = util.readfile(filepath)
364 fdata = util.readfile(filepath)
365 except IOError as inst:
365 except IOError as inst:
366 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
366 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
367 (filepath, encoding.strtolocal(inst.strerror)))
367 (filepath, encoding.strtolocal(inst.strerror)))
368 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
368 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
369 'narrow')
369 'narrow')
370 if profiles:
370 if profiles:
371 raise error.Abort(_("including other spec files using '%include' "
371 raise error.Abort(_("including other spec files using '%include' "
372 "is not supported in narrowspec"))
372 "is not supported in narrowspec"))
373 opts['addinclude'].extend(includepats)
373 opts['addinclude'].extend(includepats)
374 opts['addexclude'].extend(excludepats)
374 opts['addexclude'].extend(excludepats)
375
375
376 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
376 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
377 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
377 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
378 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
378 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
379 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
379 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
380
380
381 update_working_copy = opts['update_working_copy']
381 update_working_copy = opts['update_working_copy']
382 only_show = not (addedincludes or removedincludes or addedexcludes or
382 only_show = not (addedincludes or removedincludes or addedexcludes or
383 removedexcludes or newrules or update_working_copy)
383 removedexcludes or newrules or update_working_copy)
384
384
385 oldincludes, oldexcludes = repo.narrowpats
385 oldincludes, oldexcludes = repo.narrowpats
386
386
387 # filter the user passed additions and deletions into actual additions and
387 # filter the user passed additions and deletions into actual additions and
388 # deletions of excludes and includes
388 # deletions of excludes and includes
389 addedincludes -= oldincludes
389 addedincludes -= oldincludes
390 removedincludes &= oldincludes
390 removedincludes &= oldincludes
391 addedexcludes -= oldexcludes
391 addedexcludes -= oldexcludes
392 removedexcludes &= oldexcludes
392 removedexcludes &= oldexcludes
393
393
394 widening = addedincludes or removedexcludes
394 widening = addedincludes or removedexcludes
395 narrowing = removedincludes or addedexcludes
395 narrowing = removedincludes or addedexcludes
396
396
397 # Only print the current narrowspec.
397 # Only print the current narrowspec.
398 if only_show:
398 if only_show:
399 ui.pager('tracked')
399 ui.pager('tracked')
400 fm = ui.formatter('narrow', opts)
400 fm = ui.formatter('narrow', opts)
401 for i in sorted(oldincludes):
401 for i in sorted(oldincludes):
402 fm.startitem()
402 fm.startitem()
403 fm.write('status', '%s ', 'I', label='narrow.included')
403 fm.write('status', '%s ', 'I', label='narrow.included')
404 fm.write('pat', '%s\n', i, label='narrow.included')
404 fm.write('pat', '%s\n', i, label='narrow.included')
405 for i in sorted(oldexcludes):
405 for i in sorted(oldexcludes):
406 fm.startitem()
406 fm.startitem()
407 fm.write('status', '%s ', 'X', label='narrow.excluded')
407 fm.write('status', '%s ', 'X', label='narrow.excluded')
408 fm.write('pat', '%s\n', i, label='narrow.excluded')
408 fm.write('pat', '%s\n', i, label='narrow.excluded')
409 fm.end()
409 fm.end()
410 return 0
410 return 0
411
411
412 if update_working_copy:
412 if update_working_copy:
413 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc'):
413 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc'):
414 narrowspec.updateworkingcopy(repo)
414 narrowspec.updateworkingcopy(repo)
415 narrowspec.copytoworkingcopy(repo)
415 narrowspec.copytoworkingcopy(repo)
416 return 0
416 return 0
417
417
418 if not widening and not narrowing:
418 if not widening and not narrowing:
419 ui.status(_("nothing to widen or narrow\n"))
419 ui.status(_("nothing to widen or narrow\n"))
420 return 0
420 return 0
421
421
422 with repo.wlock(), repo.lock():
422 with repo.wlock(), repo.lock():
423 cmdutil.bailifchanged(repo)
423 cmdutil.bailifchanged(repo)
424
424
425 # Find the revisions we have in common with the remote. These will
425 # Find the revisions we have in common with the remote. These will
426 # be used for finding local-only changes for narrowing. They will
426 # be used for finding local-only changes for narrowing. They will
427 # also define the set of revisions to update for widening.
427 # also define the set of revisions to update for widening.
428 remotepath = ui.expandpath(remotepath or 'default')
428 remotepath = ui.expandpath(remotepath or 'default')
429 url, branches = hg.parseurl(remotepath)
429 url, branches = hg.parseurl(remotepath)
430 ui.status(_('comparing with %s\n') % util.hidepassword(url))
430 ui.status(_('comparing with %s\n') % util.hidepassword(url))
431 remote = hg.peer(repo, opts, url)
431 remote = hg.peer(repo, opts, url)
432
432
433 # check narrow support before doing anything if widening needs to be
433 # check narrow support before doing anything if widening needs to be
434 # performed. In future we should also abort if client is ellipses and
434 # performed. In future we should also abort if client is ellipses and
435 # server does not support ellipses
435 # server does not support ellipses
436 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
436 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
437 raise error.Abort(_("server does not support narrow clones"))
437 raise error.Abort(_("server does not support narrow clones"))
438
438
439 commoninc = discovery.findcommonincoming(repo, remote)
439 commoninc = discovery.findcommonincoming(repo, remote)
440
440
441 if narrowing:
441 if narrowing:
442 newincludes = oldincludes - removedincludes
442 newincludes = oldincludes - removedincludes
443 newexcludes = oldexcludes | addedexcludes
443 newexcludes = oldexcludes | addedexcludes
444 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
444 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
445 newincludes, newexcludes,
445 newincludes, newexcludes,
446 opts['force_delete_local_changes'])
446 opts['force_delete_local_changes'])
447 # _narrow() updated the narrowspec and _widen() below needs to
447 # _narrow() updated the narrowspec and _widen() below needs to
448 # use the updated values as its base (otherwise removed includes
448 # use the updated values as its base (otherwise removed includes
449 # and addedexcludes will be lost in the resulting narrowspec)
449 # and addedexcludes will be lost in the resulting narrowspec)
450 oldincludes = newincludes
450 oldincludes = newincludes
451 oldexcludes = newexcludes
451 oldexcludes = newexcludes
452
452
453 if widening:
453 if widening:
454 newincludes = oldincludes | addedincludes
454 newincludes = oldincludes | addedincludes
455 newexcludes = oldexcludes - removedexcludes
455 newexcludes = oldexcludes - removedexcludes
456 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
456 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
457 newincludes, newexcludes)
457 newincludes, newexcludes)
458
458
459 return 0
459 return 0
@@ -1,2698 +1,2698 b''
1 # exchange.py - utility to exchange data between repos.
1 # exchange.py - utility to exchange data between repos.
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
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 collections
10 import collections
11 import hashlib
11 import hashlib
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 bin,
15 bin,
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 )
19 )
20 from .thirdparty import (
20 from .thirdparty import (
21 attr,
21 attr,
22 )
22 )
23 from . import (
23 from . import (
24 bookmarks as bookmod,
24 bookmarks as bookmod,
25 bundle2,
25 bundle2,
26 changegroup,
26 changegroup,
27 discovery,
27 discovery,
28 error,
28 error,
29 exchangev2,
29 exchangev2,
30 lock as lockmod,
30 lock as lockmod,
31 logexchange,
31 logexchange,
32 narrowspec,
32 narrowspec,
33 obsolete,
33 obsolete,
34 phases,
34 phases,
35 pushkey,
35 pushkey,
36 pycompat,
36 pycompat,
37 repository,
37 repository,
38 scmutil,
38 scmutil,
39 sslutil,
39 sslutil,
40 streamclone,
40 streamclone,
41 url as urlmod,
41 url as urlmod,
42 util,
42 util,
43 wireprototypes,
43 wireprototypes,
44 )
44 )
45 from .utils import (
45 from .utils import (
46 stringutil,
46 stringutil,
47 )
47 )
48
48
49 urlerr = util.urlerr
49 urlerr = util.urlerr
50 urlreq = util.urlreq
50 urlreq = util.urlreq
51
51
52 _NARROWACL_SECTION = 'narrowhgacl'
52 _NARROWACL_SECTION = 'narrowhgacl'
53
53
54 # Maps bundle version human names to changegroup versions.
54 # Maps bundle version human names to changegroup versions.
55 _bundlespeccgversions = {'v1': '01',
55 _bundlespeccgversions = {'v1': '01',
56 'v2': '02',
56 'v2': '02',
57 'packed1': 's1',
57 'packed1': 's1',
58 'bundle2': '02', #legacy
58 'bundle2': '02', #legacy
59 }
59 }
60
60
61 # Maps bundle version with content opts to choose which part to bundle
61 # Maps bundle version with content opts to choose which part to bundle
62 _bundlespeccontentopts = {
62 _bundlespeccontentopts = {
63 'v1': {
63 'v1': {
64 'changegroup': True,
64 'changegroup': True,
65 'cg.version': '01',
65 'cg.version': '01',
66 'obsolescence': False,
66 'obsolescence': False,
67 'phases': False,
67 'phases': False,
68 'tagsfnodescache': False,
68 'tagsfnodescache': False,
69 'revbranchcache': False
69 'revbranchcache': False
70 },
70 },
71 'v2': {
71 'v2': {
72 'changegroup': True,
72 'changegroup': True,
73 'cg.version': '02',
73 'cg.version': '02',
74 'obsolescence': False,
74 'obsolescence': False,
75 'phases': False,
75 'phases': False,
76 'tagsfnodescache': True,
76 'tagsfnodescache': True,
77 'revbranchcache': True
77 'revbranchcache': True
78 },
78 },
79 'packed1' : {
79 'packed1' : {
80 'cg.version': 's1'
80 'cg.version': 's1'
81 }
81 }
82 }
82 }
83 _bundlespeccontentopts['bundle2'] = _bundlespeccontentopts['v2']
83 _bundlespeccontentopts['bundle2'] = _bundlespeccontentopts['v2']
84
84
85 _bundlespecvariants = {"streamv2": {"changegroup": False, "streamv2": True,
85 _bundlespecvariants = {"streamv2": {"changegroup": False, "streamv2": True,
86 "tagsfnodescache": False,
86 "tagsfnodescache": False,
87 "revbranchcache": False}}
87 "revbranchcache": False}}
88
88
89 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
89 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
90 _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}
90 _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}
91
91
92 @attr.s
92 @attr.s
93 class bundlespec(object):
93 class bundlespec(object):
94 compression = attr.ib()
94 compression = attr.ib()
95 wirecompression = attr.ib()
95 wirecompression = attr.ib()
96 version = attr.ib()
96 version = attr.ib()
97 wireversion = attr.ib()
97 wireversion = attr.ib()
98 params = attr.ib()
98 params = attr.ib()
99 contentopts = attr.ib()
99 contentopts = attr.ib()
100
100
101 def parsebundlespec(repo, spec, strict=True):
101 def parsebundlespec(repo, spec, strict=True):
102 """Parse a bundle string specification into parts.
102 """Parse a bundle string specification into parts.
103
103
104 Bundle specifications denote a well-defined bundle/exchange format.
104 Bundle specifications denote a well-defined bundle/exchange format.
105 The content of a given specification should not change over time in
105 The content of a given specification should not change over time in
106 order to ensure that bundles produced by a newer version of Mercurial are
106 order to ensure that bundles produced by a newer version of Mercurial are
107 readable from an older version.
107 readable from an older version.
108
108
109 The string currently has the form:
109 The string currently has the form:
110
110
111 <compression>-<type>[;<parameter0>[;<parameter1>]]
111 <compression>-<type>[;<parameter0>[;<parameter1>]]
112
112
113 Where <compression> is one of the supported compression formats
113 Where <compression> is one of the supported compression formats
114 and <type> is (currently) a version string. A ";" can follow the type and
114 and <type> is (currently) a version string. A ";" can follow the type and
115 all text afterwards is interpreted as URI encoded, ";" delimited key=value
115 all text afterwards is interpreted as URI encoded, ";" delimited key=value
116 pairs.
116 pairs.
117
117
118 If ``strict`` is True (the default) <compression> is required. Otherwise,
118 If ``strict`` is True (the default) <compression> is required. Otherwise,
119 it is optional.
119 it is optional.
120
120
121 Returns a bundlespec object of (compression, version, parameters).
121 Returns a bundlespec object of (compression, version, parameters).
122 Compression will be ``None`` if not in strict mode and a compression isn't
122 Compression will be ``None`` if not in strict mode and a compression isn't
123 defined.
123 defined.
124
124
125 An ``InvalidBundleSpecification`` is raised when the specification is
125 An ``InvalidBundleSpecification`` is raised when the specification is
126 not syntactically well formed.
126 not syntactically well formed.
127
127
128 An ``UnsupportedBundleSpecification`` is raised when the compression or
128 An ``UnsupportedBundleSpecification`` is raised when the compression or
129 bundle type/version is not recognized.
129 bundle type/version is not recognized.
130
130
131 Note: this function will likely eventually return a more complex data
131 Note: this function will likely eventually return a more complex data
132 structure, including bundle2 part information.
132 structure, including bundle2 part information.
133 """
133 """
134 def parseparams(s):
134 def parseparams(s):
135 if ';' not in s:
135 if ';' not in s:
136 return s, {}
136 return s, {}
137
137
138 params = {}
138 params = {}
139 version, paramstr = s.split(';', 1)
139 version, paramstr = s.split(';', 1)
140
140
141 for p in paramstr.split(';'):
141 for p in paramstr.split(';'):
142 if '=' not in p:
142 if '=' not in p:
143 raise error.InvalidBundleSpecification(
143 raise error.InvalidBundleSpecification(
144 _('invalid bundle specification: '
144 _('invalid bundle specification: '
145 'missing "=" in parameter: %s') % p)
145 'missing "=" in parameter: %s') % p)
146
146
147 key, value = p.split('=', 1)
147 key, value = p.split('=', 1)
148 key = urlreq.unquote(key)
148 key = urlreq.unquote(key)
149 value = urlreq.unquote(value)
149 value = urlreq.unquote(value)
150 params[key] = value
150 params[key] = value
151
151
152 return version, params
152 return version, params
153
153
154
154
155 if strict and '-' not in spec:
155 if strict and '-' not in spec:
156 raise error.InvalidBundleSpecification(
156 raise error.InvalidBundleSpecification(
157 _('invalid bundle specification; '
157 _('invalid bundle specification; '
158 'must be prefixed with compression: %s') % spec)
158 'must be prefixed with compression: %s') % spec)
159
159
160 if '-' in spec:
160 if '-' in spec:
161 compression, version = spec.split('-', 1)
161 compression, version = spec.split('-', 1)
162
162
163 if compression not in util.compengines.supportedbundlenames:
163 if compression not in util.compengines.supportedbundlenames:
164 raise error.UnsupportedBundleSpecification(
164 raise error.UnsupportedBundleSpecification(
165 _('%s compression is not supported') % compression)
165 _('%s compression is not supported') % compression)
166
166
167 version, params = parseparams(version)
167 version, params = parseparams(version)
168
168
169 if version not in _bundlespeccgversions:
169 if version not in _bundlespeccgversions:
170 raise error.UnsupportedBundleSpecification(
170 raise error.UnsupportedBundleSpecification(
171 _('%s is not a recognized bundle version') % version)
171 _('%s is not a recognized bundle version') % version)
172 else:
172 else:
173 # Value could be just the compression or just the version, in which
173 # Value could be just the compression or just the version, in which
174 # case some defaults are assumed (but only when not in strict mode).
174 # case some defaults are assumed (but only when not in strict mode).
175 assert not strict
175 assert not strict
176
176
177 spec, params = parseparams(spec)
177 spec, params = parseparams(spec)
178
178
179 if spec in util.compengines.supportedbundlenames:
179 if spec in util.compengines.supportedbundlenames:
180 compression = spec
180 compression = spec
181 version = 'v1'
181 version = 'v1'
182 # Generaldelta repos require v2.
182 # Generaldelta repos require v2.
183 if 'generaldelta' in repo.requirements:
183 if 'generaldelta' in repo.requirements:
184 version = 'v2'
184 version = 'v2'
185 # Modern compression engines require v2.
185 # Modern compression engines require v2.
186 if compression not in _bundlespecv1compengines:
186 if compression not in _bundlespecv1compengines:
187 version = 'v2'
187 version = 'v2'
188 elif spec in _bundlespeccgversions:
188 elif spec in _bundlespeccgversions:
189 if spec == 'packed1':
189 if spec == 'packed1':
190 compression = 'none'
190 compression = 'none'
191 else:
191 else:
192 compression = 'bzip2'
192 compression = 'bzip2'
193 version = spec
193 version = spec
194 else:
194 else:
195 raise error.UnsupportedBundleSpecification(
195 raise error.UnsupportedBundleSpecification(
196 _('%s is not a recognized bundle specification') % spec)
196 _('%s is not a recognized bundle specification') % spec)
197
197
198 # Bundle version 1 only supports a known set of compression engines.
198 # Bundle version 1 only supports a known set of compression engines.
199 if version == 'v1' and compression not in _bundlespecv1compengines:
199 if version == 'v1' and compression not in _bundlespecv1compengines:
200 raise error.UnsupportedBundleSpecification(
200 raise error.UnsupportedBundleSpecification(
201 _('compression engine %s is not supported on v1 bundles') %
201 _('compression engine %s is not supported on v1 bundles') %
202 compression)
202 compression)
203
203
204 # The specification for packed1 can optionally declare the data formats
204 # The specification for packed1 can optionally declare the data formats
205 # required to apply it. If we see this metadata, compare against what the
205 # required to apply it. If we see this metadata, compare against what the
206 # repo supports and error if the bundle isn't compatible.
206 # repo supports and error if the bundle isn't compatible.
207 if version == 'packed1' and 'requirements' in params:
207 if version == 'packed1' and 'requirements' in params:
208 requirements = set(params['requirements'].split(','))
208 requirements = set(params['requirements'].split(','))
209 missingreqs = requirements - repo.supportedformats
209 missingreqs = requirements - repo.supportedformats
210 if missingreqs:
210 if missingreqs:
211 raise error.UnsupportedBundleSpecification(
211 raise error.UnsupportedBundleSpecification(
212 _('missing support for repository features: %s') %
212 _('missing support for repository features: %s') %
213 ', '.join(sorted(missingreqs)))
213 ', '.join(sorted(missingreqs)))
214
214
215 # Compute contentopts based on the version
215 # Compute contentopts based on the version
216 contentopts = _bundlespeccontentopts.get(version, {}).copy()
216 contentopts = _bundlespeccontentopts.get(version, {}).copy()
217
217
218 # Process the variants
218 # Process the variants
219 if "stream" in params and params["stream"] == "v2":
219 if "stream" in params and params["stream"] == "v2":
220 variant = _bundlespecvariants["streamv2"]
220 variant = _bundlespecvariants["streamv2"]
221 contentopts.update(variant)
221 contentopts.update(variant)
222
222
223 engine = util.compengines.forbundlename(compression)
223 engine = util.compengines.forbundlename(compression)
224 compression, wirecompression = engine.bundletype()
224 compression, wirecompression = engine.bundletype()
225 wireversion = _bundlespeccgversions[version]
225 wireversion = _bundlespeccgversions[version]
226
226
227 return bundlespec(compression, wirecompression, version, wireversion,
227 return bundlespec(compression, wirecompression, version, wireversion,
228 params, contentopts)
228 params, contentopts)
229
229
230 def readbundle(ui, fh, fname, vfs=None):
230 def readbundle(ui, fh, fname, vfs=None):
231 header = changegroup.readexactly(fh, 4)
231 header = changegroup.readexactly(fh, 4)
232
232
233 alg = None
233 alg = None
234 if not fname:
234 if not fname:
235 fname = "stream"
235 fname = "stream"
236 if not header.startswith('HG') and header.startswith('\0'):
236 if not header.startswith('HG') and header.startswith('\0'):
237 fh = changegroup.headerlessfixup(fh, header)
237 fh = changegroup.headerlessfixup(fh, header)
238 header = "HG10"
238 header = "HG10"
239 alg = 'UN'
239 alg = 'UN'
240 elif vfs:
240 elif vfs:
241 fname = vfs.join(fname)
241 fname = vfs.join(fname)
242
242
243 magic, version = header[0:2], header[2:4]
243 magic, version = header[0:2], header[2:4]
244
244
245 if magic != 'HG':
245 if magic != 'HG':
246 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
246 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
247 if version == '10':
247 if version == '10':
248 if alg is None:
248 if alg is None:
249 alg = changegroup.readexactly(fh, 2)
249 alg = changegroup.readexactly(fh, 2)
250 return changegroup.cg1unpacker(fh, alg)
250 return changegroup.cg1unpacker(fh, alg)
251 elif version.startswith('2'):
251 elif version.startswith('2'):
252 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
252 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
253 elif version == 'S1':
253 elif version == 'S1':
254 return streamclone.streamcloneapplier(fh)
254 return streamclone.streamcloneapplier(fh)
255 else:
255 else:
256 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
256 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
257
257
258 def getbundlespec(ui, fh):
258 def getbundlespec(ui, fh):
259 """Infer the bundlespec from a bundle file handle.
259 """Infer the bundlespec from a bundle file handle.
260
260
261 The input file handle is seeked and the original seek position is not
261 The input file handle is seeked and the original seek position is not
262 restored.
262 restored.
263 """
263 """
264 def speccompression(alg):
264 def speccompression(alg):
265 try:
265 try:
266 return util.compengines.forbundletype(alg).bundletype()[0]
266 return util.compengines.forbundletype(alg).bundletype()[0]
267 except KeyError:
267 except KeyError:
268 return None
268 return None
269
269
270 b = readbundle(ui, fh, None)
270 b = readbundle(ui, fh, None)
271 if isinstance(b, changegroup.cg1unpacker):
271 if isinstance(b, changegroup.cg1unpacker):
272 alg = b._type
272 alg = b._type
273 if alg == '_truncatedBZ':
273 if alg == '_truncatedBZ':
274 alg = 'BZ'
274 alg = 'BZ'
275 comp = speccompression(alg)
275 comp = speccompression(alg)
276 if not comp:
276 if not comp:
277 raise error.Abort(_('unknown compression algorithm: %s') % alg)
277 raise error.Abort(_('unknown compression algorithm: %s') % alg)
278 return '%s-v1' % comp
278 return '%s-v1' % comp
279 elif isinstance(b, bundle2.unbundle20):
279 elif isinstance(b, bundle2.unbundle20):
280 if 'Compression' in b.params:
280 if 'Compression' in b.params:
281 comp = speccompression(b.params['Compression'])
281 comp = speccompression(b.params['Compression'])
282 if not comp:
282 if not comp:
283 raise error.Abort(_('unknown compression algorithm: %s') % comp)
283 raise error.Abort(_('unknown compression algorithm: %s') % comp)
284 else:
284 else:
285 comp = 'none'
285 comp = 'none'
286
286
287 version = None
287 version = None
288 for part in b.iterparts():
288 for part in b.iterparts():
289 if part.type == 'changegroup':
289 if part.type == 'changegroup':
290 version = part.params['version']
290 version = part.params['version']
291 if version in ('01', '02'):
291 if version in ('01', '02'):
292 version = 'v2'
292 version = 'v2'
293 else:
293 else:
294 raise error.Abort(_('changegroup version %s does not have '
294 raise error.Abort(_('changegroup version %s does not have '
295 'a known bundlespec') % version,
295 'a known bundlespec') % version,
296 hint=_('try upgrading your Mercurial '
296 hint=_('try upgrading your Mercurial '
297 'client'))
297 'client'))
298 elif part.type == 'stream2' and version is None:
298 elif part.type == 'stream2' and version is None:
299 # A stream2 part requires to be part of a v2 bundle
299 # A stream2 part requires to be part of a v2 bundle
300 requirements = urlreq.unquote(part.params['requirements'])
300 requirements = urlreq.unquote(part.params['requirements'])
301 splitted = requirements.split()
301 splitted = requirements.split()
302 params = bundle2._formatrequirementsparams(splitted)
302 params = bundle2._formatrequirementsparams(splitted)
303 return 'none-v2;stream=v2;%s' % params
303 return 'none-v2;stream=v2;%s' % params
304
304
305 if not version:
305 if not version:
306 raise error.Abort(_('could not identify changegroup version in '
306 raise error.Abort(_('could not identify changegroup version in '
307 'bundle'))
307 'bundle'))
308
308
309 return '%s-%s' % (comp, version)
309 return '%s-%s' % (comp, version)
310 elif isinstance(b, streamclone.streamcloneapplier):
310 elif isinstance(b, streamclone.streamcloneapplier):
311 requirements = streamclone.readbundle1header(fh)[2]
311 requirements = streamclone.readbundle1header(fh)[2]
312 formatted = bundle2._formatrequirementsparams(requirements)
312 formatted = bundle2._formatrequirementsparams(requirements)
313 return 'none-packed1;%s' % formatted
313 return 'none-packed1;%s' % formatted
314 else:
314 else:
315 raise error.Abort(_('unknown bundle type: %s') % b)
315 raise error.Abort(_('unknown bundle type: %s') % b)
316
316
317 def _computeoutgoing(repo, heads, common):
317 def _computeoutgoing(repo, heads, common):
318 """Computes which revs are outgoing given a set of common
318 """Computes which revs are outgoing given a set of common
319 and a set of heads.
319 and a set of heads.
320
320
321 This is a separate function so extensions can have access to
321 This is a separate function so extensions can have access to
322 the logic.
322 the logic.
323
323
324 Returns a discovery.outgoing object.
324 Returns a discovery.outgoing object.
325 """
325 """
326 cl = repo.changelog
326 cl = repo.changelog
327 if common:
327 if common:
328 hasnode = cl.hasnode
328 hasnode = cl.hasnode
329 common = [n for n in common if hasnode(n)]
329 common = [n for n in common if hasnode(n)]
330 else:
330 else:
331 common = [nullid]
331 common = [nullid]
332 if not heads:
332 if not heads:
333 heads = cl.heads()
333 heads = cl.heads()
334 return discovery.outgoing(repo, common, heads)
334 return discovery.outgoing(repo, common, heads)
335
335
336 def _checkpublish(pushop):
336 def _checkpublish(pushop):
337 repo = pushop.repo
337 repo = pushop.repo
338 ui = repo.ui
338 ui = repo.ui
339 behavior = ui.config('experimental', 'auto-publish')
339 behavior = ui.config('experimental', 'auto-publish')
340 if pushop.publish or behavior not in ('warn', 'confirm', 'abort'):
340 if pushop.publish or behavior not in ('warn', 'confirm', 'abort'):
341 return
341 return
342 remotephases = listkeys(pushop.remote, 'phases')
342 remotephases = listkeys(pushop.remote, 'phases')
343 if not remotephases.get('publishing', False):
343 if not remotephases.get('publishing', False):
344 return
344 return
345
345
346 if pushop.revs is None:
346 if pushop.revs is None:
347 published = repo.filtered('served').revs('not public()')
347 published = repo.filtered('served').revs('not public()')
348 else:
348 else:
349 published = repo.revs('::%ln - public()', pushop.revs)
349 published = repo.revs('::%ln - public()', pushop.revs)
350 if published:
350 if published:
351 if behavior == 'warn':
351 if behavior == 'warn':
352 ui.warn(_('%i changesets about to be published\n')
352 ui.warn(_('%i changesets about to be published\n')
353 % len(published))
353 % len(published))
354 elif behavior == 'confirm':
354 elif behavior == 'confirm':
355 if ui.promptchoice(_('push and publish %i changesets (yn)?'
355 if ui.promptchoice(_('push and publish %i changesets (yn)?'
356 '$$ &Yes $$ &No') % len(published)):
356 '$$ &Yes $$ &No') % len(published)):
357 raise error.Abort(_('user quit'))
357 raise error.Abort(_('user quit'))
358 elif behavior == 'abort':
358 elif behavior == 'abort':
359 msg = _('push would publish %i changesets') % len(published)
359 msg = _('push would publish %i changesets') % len(published)
360 hint = _("use --publish or adjust 'experimental.auto-publish'"
360 hint = _("use --publish or adjust 'experimental.auto-publish'"
361 " config")
361 " config")
362 raise error.Abort(msg, hint=hint)
362 raise error.Abort(msg, hint=hint)
363
363
364 def _forcebundle1(op):
364 def _forcebundle1(op):
365 """return true if a pull/push must use bundle1
365 """return true if a pull/push must use bundle1
366
366
367 This function is used to allow testing of the older bundle version"""
367 This function is used to allow testing of the older bundle version"""
368 ui = op.repo.ui
368 ui = op.repo.ui
369 # The goal is this config is to allow developer to choose the bundle
369 # The goal is this config is to allow developer to choose the bundle
370 # version used during exchanged. This is especially handy during test.
370 # version used during exchanged. This is especially handy during test.
371 # Value is a list of bundle version to be picked from, highest version
371 # Value is a list of bundle version to be picked from, highest version
372 # should be used.
372 # should be used.
373 #
373 #
374 # developer config: devel.legacy.exchange
374 # developer config: devel.legacy.exchange
375 exchange = ui.configlist('devel', 'legacy.exchange')
375 exchange = ui.configlist('devel', 'legacy.exchange')
376 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
376 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
377 return forcebundle1 or not op.remote.capable('bundle2')
377 return forcebundle1 or not op.remote.capable('bundle2')
378
378
379 class pushoperation(object):
379 class pushoperation(object):
380 """A object that represent a single push operation
380 """A object that represent a single push operation
381
381
382 Its purpose is to carry push related state and very common operations.
382 Its purpose is to carry push related state and very common operations.
383
383
384 A new pushoperation should be created at the beginning of each push and
384 A new pushoperation should be created at the beginning of each push and
385 discarded afterward.
385 discarded afterward.
386 """
386 """
387
387
388 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
388 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
389 bookmarks=(), publish=False, pushvars=None):
389 bookmarks=(), publish=False, pushvars=None):
390 # repo we push from
390 # repo we push from
391 self.repo = repo
391 self.repo = repo
392 self.ui = repo.ui
392 self.ui = repo.ui
393 # repo we push to
393 # repo we push to
394 self.remote = remote
394 self.remote = remote
395 # force option provided
395 # force option provided
396 self.force = force
396 self.force = force
397 # revs to be pushed (None is "all")
397 # revs to be pushed (None is "all")
398 self.revs = revs
398 self.revs = revs
399 # bookmark explicitly pushed
399 # bookmark explicitly pushed
400 self.bookmarks = bookmarks
400 self.bookmarks = bookmarks
401 # allow push of new branch
401 # allow push of new branch
402 self.newbranch = newbranch
402 self.newbranch = newbranch
403 # step already performed
403 # step already performed
404 # (used to check what steps have been already performed through bundle2)
404 # (used to check what steps have been already performed through bundle2)
405 self.stepsdone = set()
405 self.stepsdone = set()
406 # Integer version of the changegroup push result
406 # Integer version of the changegroup push result
407 # - None means nothing to push
407 # - None means nothing to push
408 # - 0 means HTTP error
408 # - 0 means HTTP error
409 # - 1 means we pushed and remote head count is unchanged *or*
409 # - 1 means we pushed and remote head count is unchanged *or*
410 # we have outgoing changesets but refused to push
410 # we have outgoing changesets but refused to push
411 # - other values as described by addchangegroup()
411 # - other values as described by addchangegroup()
412 self.cgresult = None
412 self.cgresult = None
413 # Boolean value for the bookmark push
413 # Boolean value for the bookmark push
414 self.bkresult = None
414 self.bkresult = None
415 # discover.outgoing object (contains common and outgoing data)
415 # discover.outgoing object (contains common and outgoing data)
416 self.outgoing = None
416 self.outgoing = None
417 # all remote topological heads before the push
417 # all remote topological heads before the push
418 self.remoteheads = None
418 self.remoteheads = None
419 # Details of the remote branch pre and post push
419 # Details of the remote branch pre and post push
420 #
420 #
421 # mapping: {'branch': ([remoteheads],
421 # mapping: {'branch': ([remoteheads],
422 # [newheads],
422 # [newheads],
423 # [unsyncedheads],
423 # [unsyncedheads],
424 # [discardedheads])}
424 # [discardedheads])}
425 # - branch: the branch name
425 # - branch: the branch name
426 # - remoteheads: the list of remote heads known locally
426 # - remoteheads: the list of remote heads known locally
427 # None if the branch is new
427 # None if the branch is new
428 # - newheads: the new remote heads (known locally) with outgoing pushed
428 # - newheads: the new remote heads (known locally) with outgoing pushed
429 # - unsyncedheads: the list of remote heads unknown locally.
429 # - unsyncedheads: the list of remote heads unknown locally.
430 # - discardedheads: the list of remote heads made obsolete by the push
430 # - discardedheads: the list of remote heads made obsolete by the push
431 self.pushbranchmap = None
431 self.pushbranchmap = None
432 # testable as a boolean indicating if any nodes are missing locally.
432 # testable as a boolean indicating if any nodes are missing locally.
433 self.incoming = None
433 self.incoming = None
434 # summary of the remote phase situation
434 # summary of the remote phase situation
435 self.remotephases = None
435 self.remotephases = None
436 # phases changes that must be pushed along side the changesets
436 # phases changes that must be pushed along side the changesets
437 self.outdatedphases = None
437 self.outdatedphases = None
438 # phases changes that must be pushed if changeset push fails
438 # phases changes that must be pushed if changeset push fails
439 self.fallbackoutdatedphases = None
439 self.fallbackoutdatedphases = None
440 # outgoing obsmarkers
440 # outgoing obsmarkers
441 self.outobsmarkers = set()
441 self.outobsmarkers = set()
442 # outgoing bookmarks
442 # outgoing bookmarks
443 self.outbookmarks = []
443 self.outbookmarks = []
444 # transaction manager
444 # transaction manager
445 self.trmanager = None
445 self.trmanager = None
446 # map { pushkey partid -> callback handling failure}
446 # map { pushkey partid -> callback handling failure}
447 # used to handle exception from mandatory pushkey part failure
447 # used to handle exception from mandatory pushkey part failure
448 self.pkfailcb = {}
448 self.pkfailcb = {}
449 # an iterable of pushvars or None
449 # an iterable of pushvars or None
450 self.pushvars = pushvars
450 self.pushvars = pushvars
451 # publish pushed changesets
451 # publish pushed changesets
452 self.publish = publish
452 self.publish = publish
453
453
454 @util.propertycache
454 @util.propertycache
455 def futureheads(self):
455 def futureheads(self):
456 """future remote heads if the changeset push succeeds"""
456 """future remote heads if the changeset push succeeds"""
457 return self.outgoing.missingheads
457 return self.outgoing.missingheads
458
458
459 @util.propertycache
459 @util.propertycache
460 def fallbackheads(self):
460 def fallbackheads(self):
461 """future remote heads if the changeset push fails"""
461 """future remote heads if the changeset push fails"""
462 if self.revs is None:
462 if self.revs is None:
463 # not target to push, all common are relevant
463 # not target to push, all common are relevant
464 return self.outgoing.commonheads
464 return self.outgoing.commonheads
465 unfi = self.repo.unfiltered()
465 unfi = self.repo.unfiltered()
466 # I want cheads = heads(::missingheads and ::commonheads)
466 # I want cheads = heads(::missingheads and ::commonheads)
467 # (missingheads is revs with secret changeset filtered out)
467 # (missingheads is revs with secret changeset filtered out)
468 #
468 #
469 # This can be expressed as:
469 # This can be expressed as:
470 # cheads = ( (missingheads and ::commonheads)
470 # cheads = ( (missingheads and ::commonheads)
471 # + (commonheads and ::missingheads))"
471 # + (commonheads and ::missingheads))"
472 # )
472 # )
473 #
473 #
474 # while trying to push we already computed the following:
474 # while trying to push we already computed the following:
475 # common = (::commonheads)
475 # common = (::commonheads)
476 # missing = ((commonheads::missingheads) - commonheads)
476 # missing = ((commonheads::missingheads) - commonheads)
477 #
477 #
478 # We can pick:
478 # We can pick:
479 # * missingheads part of common (::commonheads)
479 # * missingheads part of common (::commonheads)
480 common = self.outgoing.common
480 common = self.outgoing.common
481 nm = self.repo.changelog.nodemap
481 nm = self.repo.changelog.nodemap
482 cheads = [node for node in self.revs if nm[node] in common]
482 cheads = [node for node in self.revs if nm[node] in common]
483 # and
483 # and
484 # * commonheads parents on missing
484 # * commonheads parents on missing
485 revset = unfi.set('%ln and parents(roots(%ln))',
485 revset = unfi.set('%ln and parents(roots(%ln))',
486 self.outgoing.commonheads,
486 self.outgoing.commonheads,
487 self.outgoing.missing)
487 self.outgoing.missing)
488 cheads.extend(c.node() for c in revset)
488 cheads.extend(c.node() for c in revset)
489 return cheads
489 return cheads
490
490
491 @property
491 @property
492 def commonheads(self):
492 def commonheads(self):
493 """set of all common heads after changeset bundle push"""
493 """set of all common heads after changeset bundle push"""
494 if self.cgresult:
494 if self.cgresult:
495 return self.futureheads
495 return self.futureheads
496 else:
496 else:
497 return self.fallbackheads
497 return self.fallbackheads
498
498
499 # mapping of message used when pushing bookmark
499 # mapping of message used when pushing bookmark
500 bookmsgmap = {'update': (_("updating bookmark %s\n"),
500 bookmsgmap = {'update': (_("updating bookmark %s\n"),
501 _('updating bookmark %s failed!\n')),
501 _('updating bookmark %s failed!\n')),
502 'export': (_("exporting bookmark %s\n"),
502 'export': (_("exporting bookmark %s\n"),
503 _('exporting bookmark %s failed!\n')),
503 _('exporting bookmark %s failed!\n')),
504 'delete': (_("deleting remote bookmark %s\n"),
504 'delete': (_("deleting remote bookmark %s\n"),
505 _('deleting remote bookmark %s failed!\n')),
505 _('deleting remote bookmark %s failed!\n')),
506 }
506 }
507
507
508
508
509 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
509 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
510 publish=False, opargs=None):
510 publish=False, opargs=None):
511 '''Push outgoing changesets (limited by revs) from a local
511 '''Push outgoing changesets (limited by revs) from a local
512 repository to remote. Return an integer:
512 repository to remote. Return an integer:
513 - None means nothing to push
513 - None means nothing to push
514 - 0 means HTTP error
514 - 0 means HTTP error
515 - 1 means we pushed and remote head count is unchanged *or*
515 - 1 means we pushed and remote head count is unchanged *or*
516 we have outgoing changesets but refused to push
516 we have outgoing changesets but refused to push
517 - other values as described by addchangegroup()
517 - other values as described by addchangegroup()
518 '''
518 '''
519 if opargs is None:
519 if opargs is None:
520 opargs = {}
520 opargs = {}
521 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
521 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
522 publish, **pycompat.strkwargs(opargs))
522 publish, **pycompat.strkwargs(opargs))
523 if pushop.remote.local():
523 if pushop.remote.local():
524 missing = (set(pushop.repo.requirements)
524 missing = (set(pushop.repo.requirements)
525 - pushop.remote.local().supported)
525 - pushop.remote.local().supported)
526 if missing:
526 if missing:
527 msg = _("required features are not"
527 msg = _("required features are not"
528 " supported in the destination:"
528 " supported in the destination:"
529 " %s") % (', '.join(sorted(missing)))
529 " %s") % (', '.join(sorted(missing)))
530 raise error.Abort(msg)
530 raise error.Abort(msg)
531
531
532 if not pushop.remote.canpush():
532 if not pushop.remote.canpush():
533 raise error.Abort(_("destination does not support push"))
533 raise error.Abort(_("destination does not support push"))
534
534
535 if not pushop.remote.capable('unbundle'):
535 if not pushop.remote.capable('unbundle'):
536 raise error.Abort(_('cannot push: destination does not support the '
536 raise error.Abort(_('cannot push: destination does not support the '
537 'unbundle wire protocol command'))
537 'unbundle wire protocol command'))
538
538
539 # get lock as we might write phase data
539 # get lock as we might write phase data
540 wlock = lock = None
540 wlock = lock = None
541 try:
541 try:
542 # bundle2 push may receive a reply bundle touching bookmarks or other
542 # bundle2 push may receive a reply bundle touching bookmarks or other
543 # things requiring the wlock. Take it now to ensure proper ordering.
543 # things requiring the wlock. Take it now to ensure proper ordering.
544 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
544 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
545 if (not _forcebundle1(pushop)) and maypushback:
545 if (not _forcebundle1(pushop)) and maypushback:
546 wlock = pushop.repo.wlock()
546 wlock = pushop.repo.wlock()
547 lock = pushop.repo.lock()
547 lock = pushop.repo.lock()
548 pushop.trmanager = transactionmanager(pushop.repo,
548 pushop.trmanager = transactionmanager(pushop.repo,
549 'push-response',
549 'push-response',
550 pushop.remote.url())
550 pushop.remote.url())
551 except error.LockUnavailable as err:
551 except error.LockUnavailable as err:
552 # source repo cannot be locked.
552 # source repo cannot be locked.
553 # We do not abort the push, but just disable the local phase
553 # We do not abort the push, but just disable the local phase
554 # synchronisation.
554 # synchronisation.
555 msg = ('cannot lock source repository: %s\n'
555 msg = ('cannot lock source repository: %s\n'
556 % stringutil.forcebytestr(err))
556 % stringutil.forcebytestr(err))
557 pushop.ui.debug(msg)
557 pushop.ui.debug(msg)
558
558
559 with wlock or util.nullcontextmanager(), \
559 with wlock or util.nullcontextmanager():
560 lock or util.nullcontextmanager(), \
560 with lock or util.nullcontextmanager():
561 pushop.trmanager or util.nullcontextmanager():
561 with pushop.trmanager or util.nullcontextmanager():
562 pushop.repo.checkpush(pushop)
562 pushop.repo.checkpush(pushop)
563 _checkpublish(pushop)
563 _checkpublish(pushop)
564 _pushdiscovery(pushop)
564 _pushdiscovery(pushop)
565 if not _forcebundle1(pushop):
565 if not _forcebundle1(pushop):
566 _pushbundle2(pushop)
566 _pushbundle2(pushop)
567 _pushchangeset(pushop)
567 _pushchangeset(pushop)
568 _pushsyncphase(pushop)
568 _pushsyncphase(pushop)
569 _pushobsolete(pushop)
569 _pushobsolete(pushop)
570 _pushbookmark(pushop)
570 _pushbookmark(pushop)
571
571
572 if repo.ui.configbool('experimental', 'remotenames'):
572 if repo.ui.configbool('experimental', 'remotenames'):
573 logexchange.pullremotenames(repo, remote)
573 logexchange.pullremotenames(repo, remote)
574
574
575 return pushop
575 return pushop
576
576
577 # list of steps to perform discovery before push
577 # list of steps to perform discovery before push
578 pushdiscoveryorder = []
578 pushdiscoveryorder = []
579
579
580 # Mapping between step name and function
580 # Mapping between step name and function
581 #
581 #
582 # This exists to help extensions wrap steps if necessary
582 # This exists to help extensions wrap steps if necessary
583 pushdiscoverymapping = {}
583 pushdiscoverymapping = {}
584
584
585 def pushdiscovery(stepname):
585 def pushdiscovery(stepname):
586 """decorator for function performing discovery before push
586 """decorator for function performing discovery before push
587
587
588 The function is added to the step -> function mapping and appended to the
588 The function is added to the step -> function mapping and appended to the
589 list of steps. Beware that decorated function will be added in order (this
589 list of steps. Beware that decorated function will be added in order (this
590 may matter).
590 may matter).
591
591
592 You can only use this decorator for a new step, if you want to wrap a step
592 You can only use this decorator for a new step, if you want to wrap a step
593 from an extension, change the pushdiscovery dictionary directly."""
593 from an extension, change the pushdiscovery dictionary directly."""
594 def dec(func):
594 def dec(func):
595 assert stepname not in pushdiscoverymapping
595 assert stepname not in pushdiscoverymapping
596 pushdiscoverymapping[stepname] = func
596 pushdiscoverymapping[stepname] = func
597 pushdiscoveryorder.append(stepname)
597 pushdiscoveryorder.append(stepname)
598 return func
598 return func
599 return dec
599 return dec
600
600
601 def _pushdiscovery(pushop):
601 def _pushdiscovery(pushop):
602 """Run all discovery steps"""
602 """Run all discovery steps"""
603 for stepname in pushdiscoveryorder:
603 for stepname in pushdiscoveryorder:
604 step = pushdiscoverymapping[stepname]
604 step = pushdiscoverymapping[stepname]
605 step(pushop)
605 step(pushop)
606
606
607 @pushdiscovery('changeset')
607 @pushdiscovery('changeset')
608 def _pushdiscoverychangeset(pushop):
608 def _pushdiscoverychangeset(pushop):
609 """discover the changeset that need to be pushed"""
609 """discover the changeset that need to be pushed"""
610 fci = discovery.findcommonincoming
610 fci = discovery.findcommonincoming
611 if pushop.revs:
611 if pushop.revs:
612 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force,
612 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force,
613 ancestorsof=pushop.revs)
613 ancestorsof=pushop.revs)
614 else:
614 else:
615 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
615 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
616 common, inc, remoteheads = commoninc
616 common, inc, remoteheads = commoninc
617 fco = discovery.findcommonoutgoing
617 fco = discovery.findcommonoutgoing
618 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
618 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
619 commoninc=commoninc, force=pushop.force)
619 commoninc=commoninc, force=pushop.force)
620 pushop.outgoing = outgoing
620 pushop.outgoing = outgoing
621 pushop.remoteheads = remoteheads
621 pushop.remoteheads = remoteheads
622 pushop.incoming = inc
622 pushop.incoming = inc
623
623
624 @pushdiscovery('phase')
624 @pushdiscovery('phase')
625 def _pushdiscoveryphase(pushop):
625 def _pushdiscoveryphase(pushop):
626 """discover the phase that needs to be pushed
626 """discover the phase that needs to be pushed
627
627
628 (computed for both success and failure case for changesets push)"""
628 (computed for both success and failure case for changesets push)"""
629 outgoing = pushop.outgoing
629 outgoing = pushop.outgoing
630 unfi = pushop.repo.unfiltered()
630 unfi = pushop.repo.unfiltered()
631 remotephases = listkeys(pushop.remote, 'phases')
631 remotephases = listkeys(pushop.remote, 'phases')
632
632
633 if (pushop.ui.configbool('ui', '_usedassubrepo')
633 if (pushop.ui.configbool('ui', '_usedassubrepo')
634 and remotephases # server supports phases
634 and remotephases # server supports phases
635 and not pushop.outgoing.missing # no changesets to be pushed
635 and not pushop.outgoing.missing # no changesets to be pushed
636 and remotephases.get('publishing', False)):
636 and remotephases.get('publishing', False)):
637 # When:
637 # When:
638 # - this is a subrepo push
638 # - this is a subrepo push
639 # - and remote support phase
639 # - and remote support phase
640 # - and no changeset are to be pushed
640 # - and no changeset are to be pushed
641 # - and remote is publishing
641 # - and remote is publishing
642 # We may be in issue 3781 case!
642 # We may be in issue 3781 case!
643 # We drop the possible phase synchronisation done by
643 # We drop the possible phase synchronisation done by
644 # courtesy to publish changesets possibly locally draft
644 # courtesy to publish changesets possibly locally draft
645 # on the remote.
645 # on the remote.
646 pushop.outdatedphases = []
646 pushop.outdatedphases = []
647 pushop.fallbackoutdatedphases = []
647 pushop.fallbackoutdatedphases = []
648 return
648 return
649
649
650 pushop.remotephases = phases.remotephasessummary(pushop.repo,
650 pushop.remotephases = phases.remotephasessummary(pushop.repo,
651 pushop.fallbackheads,
651 pushop.fallbackheads,
652 remotephases)
652 remotephases)
653 droots = pushop.remotephases.draftroots
653 droots = pushop.remotephases.draftroots
654
654
655 extracond = ''
655 extracond = ''
656 if not pushop.remotephases.publishing:
656 if not pushop.remotephases.publishing:
657 extracond = ' and public()'
657 extracond = ' and public()'
658 revset = 'heads((%%ln::%%ln) %s)' % extracond
658 revset = 'heads((%%ln::%%ln) %s)' % extracond
659 # Get the list of all revs draft on remote by public here.
659 # Get the list of all revs draft on remote by public here.
660 # XXX Beware that revset break if droots is not strictly
660 # XXX Beware that revset break if droots is not strictly
661 # XXX root we may want to ensure it is but it is costly
661 # XXX root we may want to ensure it is but it is costly
662 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
662 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
663 if not pushop.remotephases.publishing and pushop.publish:
663 if not pushop.remotephases.publishing and pushop.publish:
664 future = list(unfi.set('%ln and (not public() or %ln::)',
664 future = list(unfi.set('%ln and (not public() or %ln::)',
665 pushop.futureheads, droots))
665 pushop.futureheads, droots))
666 elif not outgoing.missing:
666 elif not outgoing.missing:
667 future = fallback
667 future = fallback
668 else:
668 else:
669 # adds changeset we are going to push as draft
669 # adds changeset we are going to push as draft
670 #
670 #
671 # should not be necessary for publishing server, but because of an
671 # should not be necessary for publishing server, but because of an
672 # issue fixed in xxxxx we have to do it anyway.
672 # issue fixed in xxxxx we have to do it anyway.
673 fdroots = list(unfi.set('roots(%ln + %ln::)',
673 fdroots = list(unfi.set('roots(%ln + %ln::)',
674 outgoing.missing, droots))
674 outgoing.missing, droots))
675 fdroots = [f.node() for f in fdroots]
675 fdroots = [f.node() for f in fdroots]
676 future = list(unfi.set(revset, fdroots, pushop.futureheads))
676 future = list(unfi.set(revset, fdroots, pushop.futureheads))
677 pushop.outdatedphases = future
677 pushop.outdatedphases = future
678 pushop.fallbackoutdatedphases = fallback
678 pushop.fallbackoutdatedphases = fallback
679
679
680 @pushdiscovery('obsmarker')
680 @pushdiscovery('obsmarker')
681 def _pushdiscoveryobsmarkers(pushop):
681 def _pushdiscoveryobsmarkers(pushop):
682 if not obsolete.isenabled(pushop.repo, obsolete.exchangeopt):
682 if not obsolete.isenabled(pushop.repo, obsolete.exchangeopt):
683 return
683 return
684
684
685 if not pushop.repo.obsstore:
685 if not pushop.repo.obsstore:
686 return
686 return
687
687
688 if 'obsolete' not in listkeys(pushop.remote, 'namespaces'):
688 if 'obsolete' not in listkeys(pushop.remote, 'namespaces'):
689 return
689 return
690
690
691 repo = pushop.repo
691 repo = pushop.repo
692 # very naive computation, that can be quite expensive on big repo.
692 # very naive computation, that can be quite expensive on big repo.
693 # However: evolution is currently slow on them anyway.
693 # However: evolution is currently slow on them anyway.
694 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
694 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
695 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
695 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
696
696
697 @pushdiscovery('bookmarks')
697 @pushdiscovery('bookmarks')
698 def _pushdiscoverybookmarks(pushop):
698 def _pushdiscoverybookmarks(pushop):
699 ui = pushop.ui
699 ui = pushop.ui
700 repo = pushop.repo.unfiltered()
700 repo = pushop.repo.unfiltered()
701 remote = pushop.remote
701 remote = pushop.remote
702 ui.debug("checking for updated bookmarks\n")
702 ui.debug("checking for updated bookmarks\n")
703 ancestors = ()
703 ancestors = ()
704 if pushop.revs:
704 if pushop.revs:
705 revnums = pycompat.maplist(repo.changelog.rev, pushop.revs)
705 revnums = pycompat.maplist(repo.changelog.rev, pushop.revs)
706 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
706 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
707
707
708 remotebookmark = listkeys(remote, 'bookmarks')
708 remotebookmark = listkeys(remote, 'bookmarks')
709
709
710 explicit = set([repo._bookmarks.expandname(bookmark)
710 explicit = set([repo._bookmarks.expandname(bookmark)
711 for bookmark in pushop.bookmarks])
711 for bookmark in pushop.bookmarks])
712
712
713 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
713 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
714 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
714 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
715
715
716 def safehex(x):
716 def safehex(x):
717 if x is None:
717 if x is None:
718 return x
718 return x
719 return hex(x)
719 return hex(x)
720
720
721 def hexifycompbookmarks(bookmarks):
721 def hexifycompbookmarks(bookmarks):
722 return [(b, safehex(scid), safehex(dcid))
722 return [(b, safehex(scid), safehex(dcid))
723 for (b, scid, dcid) in bookmarks]
723 for (b, scid, dcid) in bookmarks]
724
724
725 comp = [hexifycompbookmarks(marks) for marks in comp]
725 comp = [hexifycompbookmarks(marks) for marks in comp]
726 return _processcompared(pushop, ancestors, explicit, remotebookmark, comp)
726 return _processcompared(pushop, ancestors, explicit, remotebookmark, comp)
727
727
728 def _processcompared(pushop, pushed, explicit, remotebms, comp):
728 def _processcompared(pushop, pushed, explicit, remotebms, comp):
729 """take decision on bookmark to pull from the remote bookmark
729 """take decision on bookmark to pull from the remote bookmark
730
730
731 Exist to help extensions who want to alter this behavior.
731 Exist to help extensions who want to alter this behavior.
732 """
732 """
733 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
733 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
734
734
735 repo = pushop.repo
735 repo = pushop.repo
736
736
737 for b, scid, dcid in advsrc:
737 for b, scid, dcid in advsrc:
738 if b in explicit:
738 if b in explicit:
739 explicit.remove(b)
739 explicit.remove(b)
740 if not pushed or repo[scid].rev() in pushed:
740 if not pushed or repo[scid].rev() in pushed:
741 pushop.outbookmarks.append((b, dcid, scid))
741 pushop.outbookmarks.append((b, dcid, scid))
742 # search added bookmark
742 # search added bookmark
743 for b, scid, dcid in addsrc:
743 for b, scid, dcid in addsrc:
744 if b in explicit:
744 if b in explicit:
745 explicit.remove(b)
745 explicit.remove(b)
746 pushop.outbookmarks.append((b, '', scid))
746 pushop.outbookmarks.append((b, '', scid))
747 # search for overwritten bookmark
747 # search for overwritten bookmark
748 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
748 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
749 if b in explicit:
749 if b in explicit:
750 explicit.remove(b)
750 explicit.remove(b)
751 pushop.outbookmarks.append((b, dcid, scid))
751 pushop.outbookmarks.append((b, dcid, scid))
752 # search for bookmark to delete
752 # search for bookmark to delete
753 for b, scid, dcid in adddst:
753 for b, scid, dcid in adddst:
754 if b in explicit:
754 if b in explicit:
755 explicit.remove(b)
755 explicit.remove(b)
756 # treat as "deleted locally"
756 # treat as "deleted locally"
757 pushop.outbookmarks.append((b, dcid, ''))
757 pushop.outbookmarks.append((b, dcid, ''))
758 # identical bookmarks shouldn't get reported
758 # identical bookmarks shouldn't get reported
759 for b, scid, dcid in same:
759 for b, scid, dcid in same:
760 if b in explicit:
760 if b in explicit:
761 explicit.remove(b)
761 explicit.remove(b)
762
762
763 if explicit:
763 if explicit:
764 explicit = sorted(explicit)
764 explicit = sorted(explicit)
765 # we should probably list all of them
765 # we should probably list all of them
766 pushop.ui.warn(_('bookmark %s does not exist on the local '
766 pushop.ui.warn(_('bookmark %s does not exist on the local '
767 'or remote repository!\n') % explicit[0])
767 'or remote repository!\n') % explicit[0])
768 pushop.bkresult = 2
768 pushop.bkresult = 2
769
769
770 pushop.outbookmarks.sort()
770 pushop.outbookmarks.sort()
771
771
772 def _pushcheckoutgoing(pushop):
772 def _pushcheckoutgoing(pushop):
773 outgoing = pushop.outgoing
773 outgoing = pushop.outgoing
774 unfi = pushop.repo.unfiltered()
774 unfi = pushop.repo.unfiltered()
775 if not outgoing.missing:
775 if not outgoing.missing:
776 # nothing to push
776 # nothing to push
777 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
777 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
778 return False
778 return False
779 # something to push
779 # something to push
780 if not pushop.force:
780 if not pushop.force:
781 # if repo.obsstore == False --> no obsolete
781 # if repo.obsstore == False --> no obsolete
782 # then, save the iteration
782 # then, save the iteration
783 if unfi.obsstore:
783 if unfi.obsstore:
784 # this message are here for 80 char limit reason
784 # this message are here for 80 char limit reason
785 mso = _("push includes obsolete changeset: %s!")
785 mso = _("push includes obsolete changeset: %s!")
786 mspd = _("push includes phase-divergent changeset: %s!")
786 mspd = _("push includes phase-divergent changeset: %s!")
787 mscd = _("push includes content-divergent changeset: %s!")
787 mscd = _("push includes content-divergent changeset: %s!")
788 mst = {"orphan": _("push includes orphan changeset: %s!"),
788 mst = {"orphan": _("push includes orphan changeset: %s!"),
789 "phase-divergent": mspd,
789 "phase-divergent": mspd,
790 "content-divergent": mscd}
790 "content-divergent": mscd}
791 # If we are to push if there is at least one
791 # If we are to push if there is at least one
792 # obsolete or unstable changeset in missing, at
792 # obsolete or unstable changeset in missing, at
793 # least one of the missinghead will be obsolete or
793 # least one of the missinghead will be obsolete or
794 # unstable. So checking heads only is ok
794 # unstable. So checking heads only is ok
795 for node in outgoing.missingheads:
795 for node in outgoing.missingheads:
796 ctx = unfi[node]
796 ctx = unfi[node]
797 if ctx.obsolete():
797 if ctx.obsolete():
798 raise error.Abort(mso % ctx)
798 raise error.Abort(mso % ctx)
799 elif ctx.isunstable():
799 elif ctx.isunstable():
800 # TODO print more than one instability in the abort
800 # TODO print more than one instability in the abort
801 # message
801 # message
802 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
802 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
803
803
804 discovery.checkheads(pushop)
804 discovery.checkheads(pushop)
805 return True
805 return True
806
806
807 # List of names of steps to perform for an outgoing bundle2, order matters.
807 # List of names of steps to perform for an outgoing bundle2, order matters.
808 b2partsgenorder = []
808 b2partsgenorder = []
809
809
810 # Mapping between step name and function
810 # Mapping between step name and function
811 #
811 #
812 # This exists to help extensions wrap steps if necessary
812 # This exists to help extensions wrap steps if necessary
813 b2partsgenmapping = {}
813 b2partsgenmapping = {}
814
814
815 def b2partsgenerator(stepname, idx=None):
815 def b2partsgenerator(stepname, idx=None):
816 """decorator for function generating bundle2 part
816 """decorator for function generating bundle2 part
817
817
818 The function is added to the step -> function mapping and appended to the
818 The function is added to the step -> function mapping and appended to the
819 list of steps. Beware that decorated functions will be added in order
819 list of steps. Beware that decorated functions will be added in order
820 (this may matter).
820 (this may matter).
821
821
822 You can only use this decorator for new steps, if you want to wrap a step
822 You can only use this decorator for new steps, if you want to wrap a step
823 from an extension, attack the b2partsgenmapping dictionary directly."""
823 from an extension, attack the b2partsgenmapping dictionary directly."""
824 def dec(func):
824 def dec(func):
825 assert stepname not in b2partsgenmapping
825 assert stepname not in b2partsgenmapping
826 b2partsgenmapping[stepname] = func
826 b2partsgenmapping[stepname] = func
827 if idx is None:
827 if idx is None:
828 b2partsgenorder.append(stepname)
828 b2partsgenorder.append(stepname)
829 else:
829 else:
830 b2partsgenorder.insert(idx, stepname)
830 b2partsgenorder.insert(idx, stepname)
831 return func
831 return func
832 return dec
832 return dec
833
833
834 def _pushb2ctxcheckheads(pushop, bundler):
834 def _pushb2ctxcheckheads(pushop, bundler):
835 """Generate race condition checking parts
835 """Generate race condition checking parts
836
836
837 Exists as an independent function to aid extensions
837 Exists as an independent function to aid extensions
838 """
838 """
839 # * 'force' do not check for push race,
839 # * 'force' do not check for push race,
840 # * if we don't push anything, there are nothing to check.
840 # * if we don't push anything, there are nothing to check.
841 if not pushop.force and pushop.outgoing.missingheads:
841 if not pushop.force and pushop.outgoing.missingheads:
842 allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())
842 allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())
843 emptyremote = pushop.pushbranchmap is None
843 emptyremote = pushop.pushbranchmap is None
844 if not allowunrelated or emptyremote:
844 if not allowunrelated or emptyremote:
845 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
845 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
846 else:
846 else:
847 affected = set()
847 affected = set()
848 for branch, heads in pushop.pushbranchmap.iteritems():
848 for branch, heads in pushop.pushbranchmap.iteritems():
849 remoteheads, newheads, unsyncedheads, discardedheads = heads
849 remoteheads, newheads, unsyncedheads, discardedheads = heads
850 if remoteheads is not None:
850 if remoteheads is not None:
851 remote = set(remoteheads)
851 remote = set(remoteheads)
852 affected |= set(discardedheads) & remote
852 affected |= set(discardedheads) & remote
853 affected |= remote - set(newheads)
853 affected |= remote - set(newheads)
854 if affected:
854 if affected:
855 data = iter(sorted(affected))
855 data = iter(sorted(affected))
856 bundler.newpart('check:updated-heads', data=data)
856 bundler.newpart('check:updated-heads', data=data)
857
857
858 def _pushing(pushop):
858 def _pushing(pushop):
859 """return True if we are pushing anything"""
859 """return True if we are pushing anything"""
860 return bool(pushop.outgoing.missing
860 return bool(pushop.outgoing.missing
861 or pushop.outdatedphases
861 or pushop.outdatedphases
862 or pushop.outobsmarkers
862 or pushop.outobsmarkers
863 or pushop.outbookmarks)
863 or pushop.outbookmarks)
864
864
865 @b2partsgenerator('check-bookmarks')
865 @b2partsgenerator('check-bookmarks')
866 def _pushb2checkbookmarks(pushop, bundler):
866 def _pushb2checkbookmarks(pushop, bundler):
867 """insert bookmark move checking"""
867 """insert bookmark move checking"""
868 if not _pushing(pushop) or pushop.force:
868 if not _pushing(pushop) or pushop.force:
869 return
869 return
870 b2caps = bundle2.bundle2caps(pushop.remote)
870 b2caps = bundle2.bundle2caps(pushop.remote)
871 hasbookmarkcheck = 'bookmarks' in b2caps
871 hasbookmarkcheck = 'bookmarks' in b2caps
872 if not (pushop.outbookmarks and hasbookmarkcheck):
872 if not (pushop.outbookmarks and hasbookmarkcheck):
873 return
873 return
874 data = []
874 data = []
875 for book, old, new in pushop.outbookmarks:
875 for book, old, new in pushop.outbookmarks:
876 old = bin(old)
876 old = bin(old)
877 data.append((book, old))
877 data.append((book, old))
878 checkdata = bookmod.binaryencode(data)
878 checkdata = bookmod.binaryencode(data)
879 bundler.newpart('check:bookmarks', data=checkdata)
879 bundler.newpart('check:bookmarks', data=checkdata)
880
880
881 @b2partsgenerator('check-phases')
881 @b2partsgenerator('check-phases')
882 def _pushb2checkphases(pushop, bundler):
882 def _pushb2checkphases(pushop, bundler):
883 """insert phase move checking"""
883 """insert phase move checking"""
884 if not _pushing(pushop) or pushop.force:
884 if not _pushing(pushop) or pushop.force:
885 return
885 return
886 b2caps = bundle2.bundle2caps(pushop.remote)
886 b2caps = bundle2.bundle2caps(pushop.remote)
887 hasphaseheads = 'heads' in b2caps.get('phases', ())
887 hasphaseheads = 'heads' in b2caps.get('phases', ())
888 if pushop.remotephases is not None and hasphaseheads:
888 if pushop.remotephases is not None and hasphaseheads:
889 # check that the remote phase has not changed
889 # check that the remote phase has not changed
890 checks = [[] for p in phases.allphases]
890 checks = [[] for p in phases.allphases]
891 checks[phases.public].extend(pushop.remotephases.publicheads)
891 checks[phases.public].extend(pushop.remotephases.publicheads)
892 checks[phases.draft].extend(pushop.remotephases.draftroots)
892 checks[phases.draft].extend(pushop.remotephases.draftroots)
893 if any(checks):
893 if any(checks):
894 for nodes in checks:
894 for nodes in checks:
895 nodes.sort()
895 nodes.sort()
896 checkdata = phases.binaryencode(checks)
896 checkdata = phases.binaryencode(checks)
897 bundler.newpart('check:phases', data=checkdata)
897 bundler.newpart('check:phases', data=checkdata)
898
898
899 @b2partsgenerator('changeset')
899 @b2partsgenerator('changeset')
900 def _pushb2ctx(pushop, bundler):
900 def _pushb2ctx(pushop, bundler):
901 """handle changegroup push through bundle2
901 """handle changegroup push through bundle2
902
902
903 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
903 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
904 """
904 """
905 if 'changesets' in pushop.stepsdone:
905 if 'changesets' in pushop.stepsdone:
906 return
906 return
907 pushop.stepsdone.add('changesets')
907 pushop.stepsdone.add('changesets')
908 # Send known heads to the server for race detection.
908 # Send known heads to the server for race detection.
909 if not _pushcheckoutgoing(pushop):
909 if not _pushcheckoutgoing(pushop):
910 return
910 return
911 pushop.repo.prepushoutgoinghooks(pushop)
911 pushop.repo.prepushoutgoinghooks(pushop)
912
912
913 _pushb2ctxcheckheads(pushop, bundler)
913 _pushb2ctxcheckheads(pushop, bundler)
914
914
915 b2caps = bundle2.bundle2caps(pushop.remote)
915 b2caps = bundle2.bundle2caps(pushop.remote)
916 version = '01'
916 version = '01'
917 cgversions = b2caps.get('changegroup')
917 cgversions = b2caps.get('changegroup')
918 if cgversions: # 3.1 and 3.2 ship with an empty value
918 if cgversions: # 3.1 and 3.2 ship with an empty value
919 cgversions = [v for v in cgversions
919 cgversions = [v for v in cgversions
920 if v in changegroup.supportedoutgoingversions(
920 if v in changegroup.supportedoutgoingversions(
921 pushop.repo)]
921 pushop.repo)]
922 if not cgversions:
922 if not cgversions:
923 raise error.Abort(_('no common changegroup version'))
923 raise error.Abort(_('no common changegroup version'))
924 version = max(cgversions)
924 version = max(cgversions)
925 cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
925 cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
926 'push')
926 'push')
927 cgpart = bundler.newpart('changegroup', data=cgstream)
927 cgpart = bundler.newpart('changegroup', data=cgstream)
928 if cgversions:
928 if cgversions:
929 cgpart.addparam('version', version)
929 cgpart.addparam('version', version)
930 if 'treemanifest' in pushop.repo.requirements:
930 if 'treemanifest' in pushop.repo.requirements:
931 cgpart.addparam('treemanifest', '1')
931 cgpart.addparam('treemanifest', '1')
932 def handlereply(op):
932 def handlereply(op):
933 """extract addchangegroup returns from server reply"""
933 """extract addchangegroup returns from server reply"""
934 cgreplies = op.records.getreplies(cgpart.id)
934 cgreplies = op.records.getreplies(cgpart.id)
935 assert len(cgreplies['changegroup']) == 1
935 assert len(cgreplies['changegroup']) == 1
936 pushop.cgresult = cgreplies['changegroup'][0]['return']
936 pushop.cgresult = cgreplies['changegroup'][0]['return']
937 return handlereply
937 return handlereply
938
938
939 @b2partsgenerator('phase')
939 @b2partsgenerator('phase')
940 def _pushb2phases(pushop, bundler):
940 def _pushb2phases(pushop, bundler):
941 """handle phase push through bundle2"""
941 """handle phase push through bundle2"""
942 if 'phases' in pushop.stepsdone:
942 if 'phases' in pushop.stepsdone:
943 return
943 return
944 b2caps = bundle2.bundle2caps(pushop.remote)
944 b2caps = bundle2.bundle2caps(pushop.remote)
945 ui = pushop.repo.ui
945 ui = pushop.repo.ui
946
946
947 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
947 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
948 haspushkey = 'pushkey' in b2caps
948 haspushkey = 'pushkey' in b2caps
949 hasphaseheads = 'heads' in b2caps.get('phases', ())
949 hasphaseheads = 'heads' in b2caps.get('phases', ())
950
950
951 if hasphaseheads and not legacyphase:
951 if hasphaseheads and not legacyphase:
952 return _pushb2phaseheads(pushop, bundler)
952 return _pushb2phaseheads(pushop, bundler)
953 elif haspushkey:
953 elif haspushkey:
954 return _pushb2phasespushkey(pushop, bundler)
954 return _pushb2phasespushkey(pushop, bundler)
955
955
956 def _pushb2phaseheads(pushop, bundler):
956 def _pushb2phaseheads(pushop, bundler):
957 """push phase information through a bundle2 - binary part"""
957 """push phase information through a bundle2 - binary part"""
958 pushop.stepsdone.add('phases')
958 pushop.stepsdone.add('phases')
959 if pushop.outdatedphases:
959 if pushop.outdatedphases:
960 updates = [[] for p in phases.allphases]
960 updates = [[] for p in phases.allphases]
961 updates[0].extend(h.node() for h in pushop.outdatedphases)
961 updates[0].extend(h.node() for h in pushop.outdatedphases)
962 phasedata = phases.binaryencode(updates)
962 phasedata = phases.binaryencode(updates)
963 bundler.newpart('phase-heads', data=phasedata)
963 bundler.newpart('phase-heads', data=phasedata)
964
964
965 def _pushb2phasespushkey(pushop, bundler):
965 def _pushb2phasespushkey(pushop, bundler):
966 """push phase information through a bundle2 - pushkey part"""
966 """push phase information through a bundle2 - pushkey part"""
967 pushop.stepsdone.add('phases')
967 pushop.stepsdone.add('phases')
968 part2node = []
968 part2node = []
969
969
970 def handlefailure(pushop, exc):
970 def handlefailure(pushop, exc):
971 targetid = int(exc.partid)
971 targetid = int(exc.partid)
972 for partid, node in part2node:
972 for partid, node in part2node:
973 if partid == targetid:
973 if partid == targetid:
974 raise error.Abort(_('updating %s to public failed') % node)
974 raise error.Abort(_('updating %s to public failed') % node)
975
975
976 enc = pushkey.encode
976 enc = pushkey.encode
977 for newremotehead in pushop.outdatedphases:
977 for newremotehead in pushop.outdatedphases:
978 part = bundler.newpart('pushkey')
978 part = bundler.newpart('pushkey')
979 part.addparam('namespace', enc('phases'))
979 part.addparam('namespace', enc('phases'))
980 part.addparam('key', enc(newremotehead.hex()))
980 part.addparam('key', enc(newremotehead.hex()))
981 part.addparam('old', enc('%d' % phases.draft))
981 part.addparam('old', enc('%d' % phases.draft))
982 part.addparam('new', enc('%d' % phases.public))
982 part.addparam('new', enc('%d' % phases.public))
983 part2node.append((part.id, newremotehead))
983 part2node.append((part.id, newremotehead))
984 pushop.pkfailcb[part.id] = handlefailure
984 pushop.pkfailcb[part.id] = handlefailure
985
985
986 def handlereply(op):
986 def handlereply(op):
987 for partid, node in part2node:
987 for partid, node in part2node:
988 partrep = op.records.getreplies(partid)
988 partrep = op.records.getreplies(partid)
989 results = partrep['pushkey']
989 results = partrep['pushkey']
990 assert len(results) <= 1
990 assert len(results) <= 1
991 msg = None
991 msg = None
992 if not results:
992 if not results:
993 msg = _('server ignored update of %s to public!\n') % node
993 msg = _('server ignored update of %s to public!\n') % node
994 elif not int(results[0]['return']):
994 elif not int(results[0]['return']):
995 msg = _('updating %s to public failed!\n') % node
995 msg = _('updating %s to public failed!\n') % node
996 if msg is not None:
996 if msg is not None:
997 pushop.ui.warn(msg)
997 pushop.ui.warn(msg)
998 return handlereply
998 return handlereply
999
999
1000 @b2partsgenerator('obsmarkers')
1000 @b2partsgenerator('obsmarkers')
1001 def _pushb2obsmarkers(pushop, bundler):
1001 def _pushb2obsmarkers(pushop, bundler):
1002 if 'obsmarkers' in pushop.stepsdone:
1002 if 'obsmarkers' in pushop.stepsdone:
1003 return
1003 return
1004 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
1004 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
1005 if obsolete.commonversion(remoteversions) is None:
1005 if obsolete.commonversion(remoteversions) is None:
1006 return
1006 return
1007 pushop.stepsdone.add('obsmarkers')
1007 pushop.stepsdone.add('obsmarkers')
1008 if pushop.outobsmarkers:
1008 if pushop.outobsmarkers:
1009 markers = sorted(pushop.outobsmarkers)
1009 markers = sorted(pushop.outobsmarkers)
1010 bundle2.buildobsmarkerspart(bundler, markers)
1010 bundle2.buildobsmarkerspart(bundler, markers)
1011
1011
1012 @b2partsgenerator('bookmarks')
1012 @b2partsgenerator('bookmarks')
1013 def _pushb2bookmarks(pushop, bundler):
1013 def _pushb2bookmarks(pushop, bundler):
1014 """handle bookmark push through bundle2"""
1014 """handle bookmark push through bundle2"""
1015 if 'bookmarks' in pushop.stepsdone:
1015 if 'bookmarks' in pushop.stepsdone:
1016 return
1016 return
1017 b2caps = bundle2.bundle2caps(pushop.remote)
1017 b2caps = bundle2.bundle2caps(pushop.remote)
1018
1018
1019 legacy = pushop.repo.ui.configlist('devel', 'legacy.exchange')
1019 legacy = pushop.repo.ui.configlist('devel', 'legacy.exchange')
1020 legacybooks = 'bookmarks' in legacy
1020 legacybooks = 'bookmarks' in legacy
1021
1021
1022 if not legacybooks and 'bookmarks' in b2caps:
1022 if not legacybooks and 'bookmarks' in b2caps:
1023 return _pushb2bookmarkspart(pushop, bundler)
1023 return _pushb2bookmarkspart(pushop, bundler)
1024 elif 'pushkey' in b2caps:
1024 elif 'pushkey' in b2caps:
1025 return _pushb2bookmarkspushkey(pushop, bundler)
1025 return _pushb2bookmarkspushkey(pushop, bundler)
1026
1026
1027 def _bmaction(old, new):
1027 def _bmaction(old, new):
1028 """small utility for bookmark pushing"""
1028 """small utility for bookmark pushing"""
1029 if not old:
1029 if not old:
1030 return 'export'
1030 return 'export'
1031 elif not new:
1031 elif not new:
1032 return 'delete'
1032 return 'delete'
1033 return 'update'
1033 return 'update'
1034
1034
1035 def _pushb2bookmarkspart(pushop, bundler):
1035 def _pushb2bookmarkspart(pushop, bundler):
1036 pushop.stepsdone.add('bookmarks')
1036 pushop.stepsdone.add('bookmarks')
1037 if not pushop.outbookmarks:
1037 if not pushop.outbookmarks:
1038 return
1038 return
1039
1039
1040 allactions = []
1040 allactions = []
1041 data = []
1041 data = []
1042 for book, old, new in pushop.outbookmarks:
1042 for book, old, new in pushop.outbookmarks:
1043 new = bin(new)
1043 new = bin(new)
1044 data.append((book, new))
1044 data.append((book, new))
1045 allactions.append((book, _bmaction(old, new)))
1045 allactions.append((book, _bmaction(old, new)))
1046 checkdata = bookmod.binaryencode(data)
1046 checkdata = bookmod.binaryencode(data)
1047 bundler.newpart('bookmarks', data=checkdata)
1047 bundler.newpart('bookmarks', data=checkdata)
1048
1048
1049 def handlereply(op):
1049 def handlereply(op):
1050 ui = pushop.ui
1050 ui = pushop.ui
1051 # if success
1051 # if success
1052 for book, action in allactions:
1052 for book, action in allactions:
1053 ui.status(bookmsgmap[action][0] % book)
1053 ui.status(bookmsgmap[action][0] % book)
1054
1054
1055 return handlereply
1055 return handlereply
1056
1056
1057 def _pushb2bookmarkspushkey(pushop, bundler):
1057 def _pushb2bookmarkspushkey(pushop, bundler):
1058 pushop.stepsdone.add('bookmarks')
1058 pushop.stepsdone.add('bookmarks')
1059 part2book = []
1059 part2book = []
1060 enc = pushkey.encode
1060 enc = pushkey.encode
1061
1061
1062 def handlefailure(pushop, exc):
1062 def handlefailure(pushop, exc):
1063 targetid = int(exc.partid)
1063 targetid = int(exc.partid)
1064 for partid, book, action in part2book:
1064 for partid, book, action in part2book:
1065 if partid == targetid:
1065 if partid == targetid:
1066 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
1066 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
1067 # we should not be called for part we did not generated
1067 # we should not be called for part we did not generated
1068 assert False
1068 assert False
1069
1069
1070 for book, old, new in pushop.outbookmarks:
1070 for book, old, new in pushop.outbookmarks:
1071 part = bundler.newpart('pushkey')
1071 part = bundler.newpart('pushkey')
1072 part.addparam('namespace', enc('bookmarks'))
1072 part.addparam('namespace', enc('bookmarks'))
1073 part.addparam('key', enc(book))
1073 part.addparam('key', enc(book))
1074 part.addparam('old', enc(old))
1074 part.addparam('old', enc(old))
1075 part.addparam('new', enc(new))
1075 part.addparam('new', enc(new))
1076 action = 'update'
1076 action = 'update'
1077 if not old:
1077 if not old:
1078 action = 'export'
1078 action = 'export'
1079 elif not new:
1079 elif not new:
1080 action = 'delete'
1080 action = 'delete'
1081 part2book.append((part.id, book, action))
1081 part2book.append((part.id, book, action))
1082 pushop.pkfailcb[part.id] = handlefailure
1082 pushop.pkfailcb[part.id] = handlefailure
1083
1083
1084 def handlereply(op):
1084 def handlereply(op):
1085 ui = pushop.ui
1085 ui = pushop.ui
1086 for partid, book, action in part2book:
1086 for partid, book, action in part2book:
1087 partrep = op.records.getreplies(partid)
1087 partrep = op.records.getreplies(partid)
1088 results = partrep['pushkey']
1088 results = partrep['pushkey']
1089 assert len(results) <= 1
1089 assert len(results) <= 1
1090 if not results:
1090 if not results:
1091 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
1091 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
1092 else:
1092 else:
1093 ret = int(results[0]['return'])
1093 ret = int(results[0]['return'])
1094 if ret:
1094 if ret:
1095 ui.status(bookmsgmap[action][0] % book)
1095 ui.status(bookmsgmap[action][0] % book)
1096 else:
1096 else:
1097 ui.warn(bookmsgmap[action][1] % book)
1097 ui.warn(bookmsgmap[action][1] % book)
1098 if pushop.bkresult is not None:
1098 if pushop.bkresult is not None:
1099 pushop.bkresult = 1
1099 pushop.bkresult = 1
1100 return handlereply
1100 return handlereply
1101
1101
1102 @b2partsgenerator('pushvars', idx=0)
1102 @b2partsgenerator('pushvars', idx=0)
1103 def _getbundlesendvars(pushop, bundler):
1103 def _getbundlesendvars(pushop, bundler):
1104 '''send shellvars via bundle2'''
1104 '''send shellvars via bundle2'''
1105 pushvars = pushop.pushvars
1105 pushvars = pushop.pushvars
1106 if pushvars:
1106 if pushvars:
1107 shellvars = {}
1107 shellvars = {}
1108 for raw in pushvars:
1108 for raw in pushvars:
1109 if '=' not in raw:
1109 if '=' not in raw:
1110 msg = ("unable to parse variable '%s', should follow "
1110 msg = ("unable to parse variable '%s', should follow "
1111 "'KEY=VALUE' or 'KEY=' format")
1111 "'KEY=VALUE' or 'KEY=' format")
1112 raise error.Abort(msg % raw)
1112 raise error.Abort(msg % raw)
1113 k, v = raw.split('=', 1)
1113 k, v = raw.split('=', 1)
1114 shellvars[k] = v
1114 shellvars[k] = v
1115
1115
1116 part = bundler.newpart('pushvars')
1116 part = bundler.newpart('pushvars')
1117
1117
1118 for key, value in shellvars.iteritems():
1118 for key, value in shellvars.iteritems():
1119 part.addparam(key, value, mandatory=False)
1119 part.addparam(key, value, mandatory=False)
1120
1120
1121 def _pushbundle2(pushop):
1121 def _pushbundle2(pushop):
1122 """push data to the remote using bundle2
1122 """push data to the remote using bundle2
1123
1123
1124 The only currently supported type of data is changegroup but this will
1124 The only currently supported type of data is changegroup but this will
1125 evolve in the future."""
1125 evolve in the future."""
1126 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
1126 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
1127 pushback = (pushop.trmanager
1127 pushback = (pushop.trmanager
1128 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
1128 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
1129
1129
1130 # create reply capability
1130 # create reply capability
1131 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
1131 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
1132 allowpushback=pushback,
1132 allowpushback=pushback,
1133 role='client'))
1133 role='client'))
1134 bundler.newpart('replycaps', data=capsblob)
1134 bundler.newpart('replycaps', data=capsblob)
1135 replyhandlers = []
1135 replyhandlers = []
1136 for partgenname in b2partsgenorder:
1136 for partgenname in b2partsgenorder:
1137 partgen = b2partsgenmapping[partgenname]
1137 partgen = b2partsgenmapping[partgenname]
1138 ret = partgen(pushop, bundler)
1138 ret = partgen(pushop, bundler)
1139 if callable(ret):
1139 if callable(ret):
1140 replyhandlers.append(ret)
1140 replyhandlers.append(ret)
1141 # do not push if nothing to push
1141 # do not push if nothing to push
1142 if bundler.nbparts <= 1:
1142 if bundler.nbparts <= 1:
1143 return
1143 return
1144 stream = util.chunkbuffer(bundler.getchunks())
1144 stream = util.chunkbuffer(bundler.getchunks())
1145 try:
1145 try:
1146 try:
1146 try:
1147 with pushop.remote.commandexecutor() as e:
1147 with pushop.remote.commandexecutor() as e:
1148 reply = e.callcommand('unbundle', {
1148 reply = e.callcommand('unbundle', {
1149 'bundle': stream,
1149 'bundle': stream,
1150 'heads': ['force'],
1150 'heads': ['force'],
1151 'url': pushop.remote.url(),
1151 'url': pushop.remote.url(),
1152 }).result()
1152 }).result()
1153 except error.BundleValueError as exc:
1153 except error.BundleValueError as exc:
1154 raise error.Abort(_('missing support for %s') % exc)
1154 raise error.Abort(_('missing support for %s') % exc)
1155 try:
1155 try:
1156 trgetter = None
1156 trgetter = None
1157 if pushback:
1157 if pushback:
1158 trgetter = pushop.trmanager.transaction
1158 trgetter = pushop.trmanager.transaction
1159 op = bundle2.processbundle(pushop.repo, reply, trgetter)
1159 op = bundle2.processbundle(pushop.repo, reply, trgetter)
1160 except error.BundleValueError as exc:
1160 except error.BundleValueError as exc:
1161 raise error.Abort(_('missing support for %s') % exc)
1161 raise error.Abort(_('missing support for %s') % exc)
1162 except bundle2.AbortFromPart as exc:
1162 except bundle2.AbortFromPart as exc:
1163 pushop.ui.status(_('remote: %s\n') % exc)
1163 pushop.ui.status(_('remote: %s\n') % exc)
1164 if exc.hint is not None:
1164 if exc.hint is not None:
1165 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
1165 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
1166 raise error.Abort(_('push failed on remote'))
1166 raise error.Abort(_('push failed on remote'))
1167 except error.PushkeyFailed as exc:
1167 except error.PushkeyFailed as exc:
1168 partid = int(exc.partid)
1168 partid = int(exc.partid)
1169 if partid not in pushop.pkfailcb:
1169 if partid not in pushop.pkfailcb:
1170 raise
1170 raise
1171 pushop.pkfailcb[partid](pushop, exc)
1171 pushop.pkfailcb[partid](pushop, exc)
1172 for rephand in replyhandlers:
1172 for rephand in replyhandlers:
1173 rephand(op)
1173 rephand(op)
1174
1174
1175 def _pushchangeset(pushop):
1175 def _pushchangeset(pushop):
1176 """Make the actual push of changeset bundle to remote repo"""
1176 """Make the actual push of changeset bundle to remote repo"""
1177 if 'changesets' in pushop.stepsdone:
1177 if 'changesets' in pushop.stepsdone:
1178 return
1178 return
1179 pushop.stepsdone.add('changesets')
1179 pushop.stepsdone.add('changesets')
1180 if not _pushcheckoutgoing(pushop):
1180 if not _pushcheckoutgoing(pushop):
1181 return
1181 return
1182
1182
1183 # Should have verified this in push().
1183 # Should have verified this in push().
1184 assert pushop.remote.capable('unbundle')
1184 assert pushop.remote.capable('unbundle')
1185
1185
1186 pushop.repo.prepushoutgoinghooks(pushop)
1186 pushop.repo.prepushoutgoinghooks(pushop)
1187 outgoing = pushop.outgoing
1187 outgoing = pushop.outgoing
1188 # TODO: get bundlecaps from remote
1188 # TODO: get bundlecaps from remote
1189 bundlecaps = None
1189 bundlecaps = None
1190 # create a changegroup from local
1190 # create a changegroup from local
1191 if pushop.revs is None and not (outgoing.excluded
1191 if pushop.revs is None and not (outgoing.excluded
1192 or pushop.repo.changelog.filteredrevs):
1192 or pushop.repo.changelog.filteredrevs):
1193 # push everything,
1193 # push everything,
1194 # use the fast path, no race possible on push
1194 # use the fast path, no race possible on push
1195 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
1195 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
1196 fastpath=True, bundlecaps=bundlecaps)
1196 fastpath=True, bundlecaps=bundlecaps)
1197 else:
1197 else:
1198 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
1198 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
1199 'push', bundlecaps=bundlecaps)
1199 'push', bundlecaps=bundlecaps)
1200
1200
1201 # apply changegroup to remote
1201 # apply changegroup to remote
1202 # local repo finds heads on server, finds out what
1202 # local repo finds heads on server, finds out what
1203 # revs it must push. once revs transferred, if server
1203 # revs it must push. once revs transferred, if server
1204 # finds it has different heads (someone else won
1204 # finds it has different heads (someone else won
1205 # commit/push race), server aborts.
1205 # commit/push race), server aborts.
1206 if pushop.force:
1206 if pushop.force:
1207 remoteheads = ['force']
1207 remoteheads = ['force']
1208 else:
1208 else:
1209 remoteheads = pushop.remoteheads
1209 remoteheads = pushop.remoteheads
1210 # ssh: return remote's addchangegroup()
1210 # ssh: return remote's addchangegroup()
1211 # http: return remote's addchangegroup() or 0 for error
1211 # http: return remote's addchangegroup() or 0 for error
1212 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
1212 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
1213 pushop.repo.url())
1213 pushop.repo.url())
1214
1214
1215 def _pushsyncphase(pushop):
1215 def _pushsyncphase(pushop):
1216 """synchronise phase information locally and remotely"""
1216 """synchronise phase information locally and remotely"""
1217 cheads = pushop.commonheads
1217 cheads = pushop.commonheads
1218 # even when we don't push, exchanging phase data is useful
1218 # even when we don't push, exchanging phase data is useful
1219 remotephases = listkeys(pushop.remote, 'phases')
1219 remotephases = listkeys(pushop.remote, 'phases')
1220 if (pushop.ui.configbool('ui', '_usedassubrepo')
1220 if (pushop.ui.configbool('ui', '_usedassubrepo')
1221 and remotephases # server supports phases
1221 and remotephases # server supports phases
1222 and pushop.cgresult is None # nothing was pushed
1222 and pushop.cgresult is None # nothing was pushed
1223 and remotephases.get('publishing', False)):
1223 and remotephases.get('publishing', False)):
1224 # When:
1224 # When:
1225 # - this is a subrepo push
1225 # - this is a subrepo push
1226 # - and remote support phase
1226 # - and remote support phase
1227 # - and no changeset was pushed
1227 # - and no changeset was pushed
1228 # - and remote is publishing
1228 # - and remote is publishing
1229 # We may be in issue 3871 case!
1229 # We may be in issue 3871 case!
1230 # We drop the possible phase synchronisation done by
1230 # We drop the possible phase synchronisation done by
1231 # courtesy to publish changesets possibly locally draft
1231 # courtesy to publish changesets possibly locally draft
1232 # on the remote.
1232 # on the remote.
1233 remotephases = {'publishing': 'True'}
1233 remotephases = {'publishing': 'True'}
1234 if not remotephases: # old server or public only reply from non-publishing
1234 if not remotephases: # old server or public only reply from non-publishing
1235 _localphasemove(pushop, cheads)
1235 _localphasemove(pushop, cheads)
1236 # don't push any phase data as there is nothing to push
1236 # don't push any phase data as there is nothing to push
1237 else:
1237 else:
1238 ana = phases.analyzeremotephases(pushop.repo, cheads,
1238 ana = phases.analyzeremotephases(pushop.repo, cheads,
1239 remotephases)
1239 remotephases)
1240 pheads, droots = ana
1240 pheads, droots = ana
1241 ### Apply remote phase on local
1241 ### Apply remote phase on local
1242 if remotephases.get('publishing', False):
1242 if remotephases.get('publishing', False):
1243 _localphasemove(pushop, cheads)
1243 _localphasemove(pushop, cheads)
1244 else: # publish = False
1244 else: # publish = False
1245 _localphasemove(pushop, pheads)
1245 _localphasemove(pushop, pheads)
1246 _localphasemove(pushop, cheads, phases.draft)
1246 _localphasemove(pushop, cheads, phases.draft)
1247 ### Apply local phase on remote
1247 ### Apply local phase on remote
1248
1248
1249 if pushop.cgresult:
1249 if pushop.cgresult:
1250 if 'phases' in pushop.stepsdone:
1250 if 'phases' in pushop.stepsdone:
1251 # phases already pushed though bundle2
1251 # phases already pushed though bundle2
1252 return
1252 return
1253 outdated = pushop.outdatedphases
1253 outdated = pushop.outdatedphases
1254 else:
1254 else:
1255 outdated = pushop.fallbackoutdatedphases
1255 outdated = pushop.fallbackoutdatedphases
1256
1256
1257 pushop.stepsdone.add('phases')
1257 pushop.stepsdone.add('phases')
1258
1258
1259 # filter heads already turned public by the push
1259 # filter heads already turned public by the push
1260 outdated = [c for c in outdated if c.node() not in pheads]
1260 outdated = [c for c in outdated if c.node() not in pheads]
1261 # fallback to independent pushkey command
1261 # fallback to independent pushkey command
1262 for newremotehead in outdated:
1262 for newremotehead in outdated:
1263 with pushop.remote.commandexecutor() as e:
1263 with pushop.remote.commandexecutor() as e:
1264 r = e.callcommand('pushkey', {
1264 r = e.callcommand('pushkey', {
1265 'namespace': 'phases',
1265 'namespace': 'phases',
1266 'key': newremotehead.hex(),
1266 'key': newremotehead.hex(),
1267 'old': '%d' % phases.draft,
1267 'old': '%d' % phases.draft,
1268 'new': '%d' % phases.public
1268 'new': '%d' % phases.public
1269 }).result()
1269 }).result()
1270
1270
1271 if not r:
1271 if not r:
1272 pushop.ui.warn(_('updating %s to public failed!\n')
1272 pushop.ui.warn(_('updating %s to public failed!\n')
1273 % newremotehead)
1273 % newremotehead)
1274
1274
1275 def _localphasemove(pushop, nodes, phase=phases.public):
1275 def _localphasemove(pushop, nodes, phase=phases.public):
1276 """move <nodes> to <phase> in the local source repo"""
1276 """move <nodes> to <phase> in the local source repo"""
1277 if pushop.trmanager:
1277 if pushop.trmanager:
1278 phases.advanceboundary(pushop.repo,
1278 phases.advanceboundary(pushop.repo,
1279 pushop.trmanager.transaction(),
1279 pushop.trmanager.transaction(),
1280 phase,
1280 phase,
1281 nodes)
1281 nodes)
1282 else:
1282 else:
1283 # repo is not locked, do not change any phases!
1283 # repo is not locked, do not change any phases!
1284 # Informs the user that phases should have been moved when
1284 # Informs the user that phases should have been moved when
1285 # applicable.
1285 # applicable.
1286 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1286 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1287 phasestr = phases.phasenames[phase]
1287 phasestr = phases.phasenames[phase]
1288 if actualmoves:
1288 if actualmoves:
1289 pushop.ui.status(_('cannot lock source repo, skipping '
1289 pushop.ui.status(_('cannot lock source repo, skipping '
1290 'local %s phase update\n') % phasestr)
1290 'local %s phase update\n') % phasestr)
1291
1291
1292 def _pushobsolete(pushop):
1292 def _pushobsolete(pushop):
1293 """utility function to push obsolete markers to a remote"""
1293 """utility function to push obsolete markers to a remote"""
1294 if 'obsmarkers' in pushop.stepsdone:
1294 if 'obsmarkers' in pushop.stepsdone:
1295 return
1295 return
1296 repo = pushop.repo
1296 repo = pushop.repo
1297 remote = pushop.remote
1297 remote = pushop.remote
1298 pushop.stepsdone.add('obsmarkers')
1298 pushop.stepsdone.add('obsmarkers')
1299 if pushop.outobsmarkers:
1299 if pushop.outobsmarkers:
1300 pushop.ui.debug('try to push obsolete markers to remote\n')
1300 pushop.ui.debug('try to push obsolete markers to remote\n')
1301 rslts = []
1301 rslts = []
1302 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1302 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1303 for key in sorted(remotedata, reverse=True):
1303 for key in sorted(remotedata, reverse=True):
1304 # reverse sort to ensure we end with dump0
1304 # reverse sort to ensure we end with dump0
1305 data = remotedata[key]
1305 data = remotedata[key]
1306 rslts.append(remote.pushkey('obsolete', key, '', data))
1306 rslts.append(remote.pushkey('obsolete', key, '', data))
1307 if [r for r in rslts if not r]:
1307 if [r for r in rslts if not r]:
1308 msg = _('failed to push some obsolete markers!\n')
1308 msg = _('failed to push some obsolete markers!\n')
1309 repo.ui.warn(msg)
1309 repo.ui.warn(msg)
1310
1310
1311 def _pushbookmark(pushop):
1311 def _pushbookmark(pushop):
1312 """Update bookmark position on remote"""
1312 """Update bookmark position on remote"""
1313 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1313 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1314 return
1314 return
1315 pushop.stepsdone.add('bookmarks')
1315 pushop.stepsdone.add('bookmarks')
1316 ui = pushop.ui
1316 ui = pushop.ui
1317 remote = pushop.remote
1317 remote = pushop.remote
1318
1318
1319 for b, old, new in pushop.outbookmarks:
1319 for b, old, new in pushop.outbookmarks:
1320 action = 'update'
1320 action = 'update'
1321 if not old:
1321 if not old:
1322 action = 'export'
1322 action = 'export'
1323 elif not new:
1323 elif not new:
1324 action = 'delete'
1324 action = 'delete'
1325
1325
1326 with remote.commandexecutor() as e:
1326 with remote.commandexecutor() as e:
1327 r = e.callcommand('pushkey', {
1327 r = e.callcommand('pushkey', {
1328 'namespace': 'bookmarks',
1328 'namespace': 'bookmarks',
1329 'key': b,
1329 'key': b,
1330 'old': old,
1330 'old': old,
1331 'new': new,
1331 'new': new,
1332 }).result()
1332 }).result()
1333
1333
1334 if r:
1334 if r:
1335 ui.status(bookmsgmap[action][0] % b)
1335 ui.status(bookmsgmap[action][0] % b)
1336 else:
1336 else:
1337 ui.warn(bookmsgmap[action][1] % b)
1337 ui.warn(bookmsgmap[action][1] % b)
1338 # discovery can have set the value form invalid entry
1338 # discovery can have set the value form invalid entry
1339 if pushop.bkresult is not None:
1339 if pushop.bkresult is not None:
1340 pushop.bkresult = 1
1340 pushop.bkresult = 1
1341
1341
1342 class pulloperation(object):
1342 class pulloperation(object):
1343 """A object that represent a single pull operation
1343 """A object that represent a single pull operation
1344
1344
1345 It purpose is to carry pull related state and very common operation.
1345 It purpose is to carry pull related state and very common operation.
1346
1346
1347 A new should be created at the beginning of each pull and discarded
1347 A new should be created at the beginning of each pull and discarded
1348 afterward.
1348 afterward.
1349 """
1349 """
1350
1350
1351 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1351 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1352 remotebookmarks=None, streamclonerequested=None,
1352 remotebookmarks=None, streamclonerequested=None,
1353 includepats=None, excludepats=None, depth=None):
1353 includepats=None, excludepats=None, depth=None):
1354 # repo we pull into
1354 # repo we pull into
1355 self.repo = repo
1355 self.repo = repo
1356 # repo we pull from
1356 # repo we pull from
1357 self.remote = remote
1357 self.remote = remote
1358 # revision we try to pull (None is "all")
1358 # revision we try to pull (None is "all")
1359 self.heads = heads
1359 self.heads = heads
1360 # bookmark pulled explicitly
1360 # bookmark pulled explicitly
1361 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1361 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1362 for bookmark in bookmarks]
1362 for bookmark in bookmarks]
1363 # do we force pull?
1363 # do we force pull?
1364 self.force = force
1364 self.force = force
1365 # whether a streaming clone was requested
1365 # whether a streaming clone was requested
1366 self.streamclonerequested = streamclonerequested
1366 self.streamclonerequested = streamclonerequested
1367 # transaction manager
1367 # transaction manager
1368 self.trmanager = None
1368 self.trmanager = None
1369 # set of common changeset between local and remote before pull
1369 # set of common changeset between local and remote before pull
1370 self.common = None
1370 self.common = None
1371 # set of pulled head
1371 # set of pulled head
1372 self.rheads = None
1372 self.rheads = None
1373 # list of missing changeset to fetch remotely
1373 # list of missing changeset to fetch remotely
1374 self.fetch = None
1374 self.fetch = None
1375 # remote bookmarks data
1375 # remote bookmarks data
1376 self.remotebookmarks = remotebookmarks
1376 self.remotebookmarks = remotebookmarks
1377 # result of changegroup pulling (used as return code by pull)
1377 # result of changegroup pulling (used as return code by pull)
1378 self.cgresult = None
1378 self.cgresult = None
1379 # list of step already done
1379 # list of step already done
1380 self.stepsdone = set()
1380 self.stepsdone = set()
1381 # Whether we attempted a clone from pre-generated bundles.
1381 # Whether we attempted a clone from pre-generated bundles.
1382 self.clonebundleattempted = False
1382 self.clonebundleattempted = False
1383 # Set of file patterns to include.
1383 # Set of file patterns to include.
1384 self.includepats = includepats
1384 self.includepats = includepats
1385 # Set of file patterns to exclude.
1385 # Set of file patterns to exclude.
1386 self.excludepats = excludepats
1386 self.excludepats = excludepats
1387 # Number of ancestor changesets to pull from each pulled head.
1387 # Number of ancestor changesets to pull from each pulled head.
1388 self.depth = depth
1388 self.depth = depth
1389
1389
1390 @util.propertycache
1390 @util.propertycache
1391 def pulledsubset(self):
1391 def pulledsubset(self):
1392 """heads of the set of changeset target by the pull"""
1392 """heads of the set of changeset target by the pull"""
1393 # compute target subset
1393 # compute target subset
1394 if self.heads is None:
1394 if self.heads is None:
1395 # We pulled every thing possible
1395 # We pulled every thing possible
1396 # sync on everything common
1396 # sync on everything common
1397 c = set(self.common)
1397 c = set(self.common)
1398 ret = list(self.common)
1398 ret = list(self.common)
1399 for n in self.rheads:
1399 for n in self.rheads:
1400 if n not in c:
1400 if n not in c:
1401 ret.append(n)
1401 ret.append(n)
1402 return ret
1402 return ret
1403 else:
1403 else:
1404 # We pulled a specific subset
1404 # We pulled a specific subset
1405 # sync on this subset
1405 # sync on this subset
1406 return self.heads
1406 return self.heads
1407
1407
1408 @util.propertycache
1408 @util.propertycache
1409 def canusebundle2(self):
1409 def canusebundle2(self):
1410 return not _forcebundle1(self)
1410 return not _forcebundle1(self)
1411
1411
1412 @util.propertycache
1412 @util.propertycache
1413 def remotebundle2caps(self):
1413 def remotebundle2caps(self):
1414 return bundle2.bundle2caps(self.remote)
1414 return bundle2.bundle2caps(self.remote)
1415
1415
1416 def gettransaction(self):
1416 def gettransaction(self):
1417 # deprecated; talk to trmanager directly
1417 # deprecated; talk to trmanager directly
1418 return self.trmanager.transaction()
1418 return self.trmanager.transaction()
1419
1419
1420 class transactionmanager(util.transactional):
1420 class transactionmanager(util.transactional):
1421 """An object to manage the life cycle of a transaction
1421 """An object to manage the life cycle of a transaction
1422
1422
1423 It creates the transaction on demand and calls the appropriate hooks when
1423 It creates the transaction on demand and calls the appropriate hooks when
1424 closing the transaction."""
1424 closing the transaction."""
1425 def __init__(self, repo, source, url):
1425 def __init__(self, repo, source, url):
1426 self.repo = repo
1426 self.repo = repo
1427 self.source = source
1427 self.source = source
1428 self.url = url
1428 self.url = url
1429 self._tr = None
1429 self._tr = None
1430
1430
1431 def transaction(self):
1431 def transaction(self):
1432 """Return an open transaction object, constructing if necessary"""
1432 """Return an open transaction object, constructing if necessary"""
1433 if not self._tr:
1433 if not self._tr:
1434 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1434 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1435 self._tr = self.repo.transaction(trname)
1435 self._tr = self.repo.transaction(trname)
1436 self._tr.hookargs['source'] = self.source
1436 self._tr.hookargs['source'] = self.source
1437 self._tr.hookargs['url'] = self.url
1437 self._tr.hookargs['url'] = self.url
1438 return self._tr
1438 return self._tr
1439
1439
1440 def close(self):
1440 def close(self):
1441 """close transaction if created"""
1441 """close transaction if created"""
1442 if self._tr is not None:
1442 if self._tr is not None:
1443 self._tr.close()
1443 self._tr.close()
1444
1444
1445 def release(self):
1445 def release(self):
1446 """release transaction if created"""
1446 """release transaction if created"""
1447 if self._tr is not None:
1447 if self._tr is not None:
1448 self._tr.release()
1448 self._tr.release()
1449
1449
1450 def listkeys(remote, namespace):
1450 def listkeys(remote, namespace):
1451 with remote.commandexecutor() as e:
1451 with remote.commandexecutor() as e:
1452 return e.callcommand('listkeys', {'namespace': namespace}).result()
1452 return e.callcommand('listkeys', {'namespace': namespace}).result()
1453
1453
1454 def _fullpullbundle2(repo, pullop):
1454 def _fullpullbundle2(repo, pullop):
1455 # The server may send a partial reply, i.e. when inlining
1455 # The server may send a partial reply, i.e. when inlining
1456 # pre-computed bundles. In that case, update the common
1456 # pre-computed bundles. In that case, update the common
1457 # set based on the results and pull another bundle.
1457 # set based on the results and pull another bundle.
1458 #
1458 #
1459 # There are two indicators that the process is finished:
1459 # There are two indicators that the process is finished:
1460 # - no changeset has been added, or
1460 # - no changeset has been added, or
1461 # - all remote heads are known locally.
1461 # - all remote heads are known locally.
1462 # The head check must use the unfiltered view as obsoletion
1462 # The head check must use the unfiltered view as obsoletion
1463 # markers can hide heads.
1463 # markers can hide heads.
1464 unfi = repo.unfiltered()
1464 unfi = repo.unfiltered()
1465 unficl = unfi.changelog
1465 unficl = unfi.changelog
1466 def headsofdiff(h1, h2):
1466 def headsofdiff(h1, h2):
1467 """Returns heads(h1 % h2)"""
1467 """Returns heads(h1 % h2)"""
1468 res = unfi.set('heads(%ln %% %ln)', h1, h2)
1468 res = unfi.set('heads(%ln %% %ln)', h1, h2)
1469 return set(ctx.node() for ctx in res)
1469 return set(ctx.node() for ctx in res)
1470 def headsofunion(h1, h2):
1470 def headsofunion(h1, h2):
1471 """Returns heads((h1 + h2) - null)"""
1471 """Returns heads((h1 + h2) - null)"""
1472 res = unfi.set('heads((%ln + %ln - null))', h1, h2)
1472 res = unfi.set('heads((%ln + %ln - null))', h1, h2)
1473 return set(ctx.node() for ctx in res)
1473 return set(ctx.node() for ctx in res)
1474 while True:
1474 while True:
1475 old_heads = unficl.heads()
1475 old_heads = unficl.heads()
1476 clstart = len(unficl)
1476 clstart = len(unficl)
1477 _pullbundle2(pullop)
1477 _pullbundle2(pullop)
1478 if repository.NARROW_REQUIREMENT in repo.requirements:
1478 if repository.NARROW_REQUIREMENT in repo.requirements:
1479 # XXX narrow clones filter the heads on the server side during
1479 # XXX narrow clones filter the heads on the server side during
1480 # XXX getbundle and result in partial replies as well.
1480 # XXX getbundle and result in partial replies as well.
1481 # XXX Disable pull bundles in this case as band aid to avoid
1481 # XXX Disable pull bundles in this case as band aid to avoid
1482 # XXX extra round trips.
1482 # XXX extra round trips.
1483 break
1483 break
1484 if clstart == len(unficl):
1484 if clstart == len(unficl):
1485 break
1485 break
1486 if all(unficl.hasnode(n) for n in pullop.rheads):
1486 if all(unficl.hasnode(n) for n in pullop.rheads):
1487 break
1487 break
1488 new_heads = headsofdiff(unficl.heads(), old_heads)
1488 new_heads = headsofdiff(unficl.heads(), old_heads)
1489 pullop.common = headsofunion(new_heads, pullop.common)
1489 pullop.common = headsofunion(new_heads, pullop.common)
1490 pullop.rheads = set(pullop.rheads) - pullop.common
1490 pullop.rheads = set(pullop.rheads) - pullop.common
1491
1491
1492 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1492 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1493 streamclonerequested=None, includepats=None, excludepats=None,
1493 streamclonerequested=None, includepats=None, excludepats=None,
1494 depth=None):
1494 depth=None):
1495 """Fetch repository data from a remote.
1495 """Fetch repository data from a remote.
1496
1496
1497 This is the main function used to retrieve data from a remote repository.
1497 This is the main function used to retrieve data from a remote repository.
1498
1498
1499 ``repo`` is the local repository to clone into.
1499 ``repo`` is the local repository to clone into.
1500 ``remote`` is a peer instance.
1500 ``remote`` is a peer instance.
1501 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1501 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1502 default) means to pull everything from the remote.
1502 default) means to pull everything from the remote.
1503 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1503 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1504 default, all remote bookmarks are pulled.
1504 default, all remote bookmarks are pulled.
1505 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1505 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1506 initialization.
1506 initialization.
1507 ``streamclonerequested`` is a boolean indicating whether a "streaming
1507 ``streamclonerequested`` is a boolean indicating whether a "streaming
1508 clone" is requested. A "streaming clone" is essentially a raw file copy
1508 clone" is requested. A "streaming clone" is essentially a raw file copy
1509 of revlogs from the server. This only works when the local repository is
1509 of revlogs from the server. This only works when the local repository is
1510 empty. The default value of ``None`` means to respect the server
1510 empty. The default value of ``None`` means to respect the server
1511 configuration for preferring stream clones.
1511 configuration for preferring stream clones.
1512 ``includepats`` and ``excludepats`` define explicit file patterns to
1512 ``includepats`` and ``excludepats`` define explicit file patterns to
1513 include and exclude in storage, respectively. If not defined, narrow
1513 include and exclude in storage, respectively. If not defined, narrow
1514 patterns from the repo instance are used, if available.
1514 patterns from the repo instance are used, if available.
1515 ``depth`` is an integer indicating the DAG depth of history we're
1515 ``depth`` is an integer indicating the DAG depth of history we're
1516 interested in. If defined, for each revision specified in ``heads``, we
1516 interested in. If defined, for each revision specified in ``heads``, we
1517 will fetch up to this many of its ancestors and data associated with them.
1517 will fetch up to this many of its ancestors and data associated with them.
1518
1518
1519 Returns the ``pulloperation`` created for this pull.
1519 Returns the ``pulloperation`` created for this pull.
1520 """
1520 """
1521 if opargs is None:
1521 if opargs is None:
1522 opargs = {}
1522 opargs = {}
1523
1523
1524 # We allow the narrow patterns to be passed in explicitly to provide more
1524 # We allow the narrow patterns to be passed in explicitly to provide more
1525 # flexibility for API consumers.
1525 # flexibility for API consumers.
1526 if includepats or excludepats:
1526 if includepats or excludepats:
1527 includepats = includepats or set()
1527 includepats = includepats or set()
1528 excludepats = excludepats or set()
1528 excludepats = excludepats or set()
1529 else:
1529 else:
1530 includepats, excludepats = repo.narrowpats
1530 includepats, excludepats = repo.narrowpats
1531
1531
1532 narrowspec.validatepatterns(includepats)
1532 narrowspec.validatepatterns(includepats)
1533 narrowspec.validatepatterns(excludepats)
1533 narrowspec.validatepatterns(excludepats)
1534
1534
1535 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1535 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1536 streamclonerequested=streamclonerequested,
1536 streamclonerequested=streamclonerequested,
1537 includepats=includepats, excludepats=excludepats,
1537 includepats=includepats, excludepats=excludepats,
1538 depth=depth,
1538 depth=depth,
1539 **pycompat.strkwargs(opargs))
1539 **pycompat.strkwargs(opargs))
1540
1540
1541 peerlocal = pullop.remote.local()
1541 peerlocal = pullop.remote.local()
1542 if peerlocal:
1542 if peerlocal:
1543 missing = set(peerlocal.requirements) - pullop.repo.supported
1543 missing = set(peerlocal.requirements) - pullop.repo.supported
1544 if missing:
1544 if missing:
1545 msg = _("required features are not"
1545 msg = _("required features are not"
1546 " supported in the destination:"
1546 " supported in the destination:"
1547 " %s") % (', '.join(sorted(missing)))
1547 " %s") % (', '.join(sorted(missing)))
1548 raise error.Abort(msg)
1548 raise error.Abort(msg)
1549
1549
1550 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1550 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1551 with repo.wlock(), repo.lock(), pullop.trmanager:
1551 with repo.wlock(), repo.lock(), pullop.trmanager:
1552 # Use the modern wire protocol, if available.
1552 # Use the modern wire protocol, if available.
1553 if remote.capable('command-changesetdata'):
1553 if remote.capable('command-changesetdata'):
1554 exchangev2.pull(pullop)
1554 exchangev2.pull(pullop)
1555 else:
1555 else:
1556 # This should ideally be in _pullbundle2(). However, it needs to run
1556 # This should ideally be in _pullbundle2(). However, it needs to run
1557 # before discovery to avoid extra work.
1557 # before discovery to avoid extra work.
1558 _maybeapplyclonebundle(pullop)
1558 _maybeapplyclonebundle(pullop)
1559 streamclone.maybeperformlegacystreamclone(pullop)
1559 streamclone.maybeperformlegacystreamclone(pullop)
1560 _pulldiscovery(pullop)
1560 _pulldiscovery(pullop)
1561 if pullop.canusebundle2:
1561 if pullop.canusebundle2:
1562 _fullpullbundle2(repo, pullop)
1562 _fullpullbundle2(repo, pullop)
1563 _pullchangeset(pullop)
1563 _pullchangeset(pullop)
1564 _pullphase(pullop)
1564 _pullphase(pullop)
1565 _pullbookmarks(pullop)
1565 _pullbookmarks(pullop)
1566 _pullobsolete(pullop)
1566 _pullobsolete(pullop)
1567
1567
1568 # storing remotenames
1568 # storing remotenames
1569 if repo.ui.configbool('experimental', 'remotenames'):
1569 if repo.ui.configbool('experimental', 'remotenames'):
1570 logexchange.pullremotenames(repo, remote)
1570 logexchange.pullremotenames(repo, remote)
1571
1571
1572 return pullop
1572 return pullop
1573
1573
1574 # list of steps to perform discovery before pull
1574 # list of steps to perform discovery before pull
1575 pulldiscoveryorder = []
1575 pulldiscoveryorder = []
1576
1576
1577 # Mapping between step name and function
1577 # Mapping between step name and function
1578 #
1578 #
1579 # This exists to help extensions wrap steps if necessary
1579 # This exists to help extensions wrap steps if necessary
1580 pulldiscoverymapping = {}
1580 pulldiscoverymapping = {}
1581
1581
1582 def pulldiscovery(stepname):
1582 def pulldiscovery(stepname):
1583 """decorator for function performing discovery before pull
1583 """decorator for function performing discovery before pull
1584
1584
1585 The function is added to the step -> function mapping and appended to the
1585 The function is added to the step -> function mapping and appended to the
1586 list of steps. Beware that decorated function will be added in order (this
1586 list of steps. Beware that decorated function will be added in order (this
1587 may matter).
1587 may matter).
1588
1588
1589 You can only use this decorator for a new step, if you want to wrap a step
1589 You can only use this decorator for a new step, if you want to wrap a step
1590 from an extension, change the pulldiscovery dictionary directly."""
1590 from an extension, change the pulldiscovery dictionary directly."""
1591 def dec(func):
1591 def dec(func):
1592 assert stepname not in pulldiscoverymapping
1592 assert stepname not in pulldiscoverymapping
1593 pulldiscoverymapping[stepname] = func
1593 pulldiscoverymapping[stepname] = func
1594 pulldiscoveryorder.append(stepname)
1594 pulldiscoveryorder.append(stepname)
1595 return func
1595 return func
1596 return dec
1596 return dec
1597
1597
1598 def _pulldiscovery(pullop):
1598 def _pulldiscovery(pullop):
1599 """Run all discovery steps"""
1599 """Run all discovery steps"""
1600 for stepname in pulldiscoveryorder:
1600 for stepname in pulldiscoveryorder:
1601 step = pulldiscoverymapping[stepname]
1601 step = pulldiscoverymapping[stepname]
1602 step(pullop)
1602 step(pullop)
1603
1603
1604 @pulldiscovery('b1:bookmarks')
1604 @pulldiscovery('b1:bookmarks')
1605 def _pullbookmarkbundle1(pullop):
1605 def _pullbookmarkbundle1(pullop):
1606 """fetch bookmark data in bundle1 case
1606 """fetch bookmark data in bundle1 case
1607
1607
1608 If not using bundle2, we have to fetch bookmarks before changeset
1608 If not using bundle2, we have to fetch bookmarks before changeset
1609 discovery to reduce the chance and impact of race conditions."""
1609 discovery to reduce the chance and impact of race conditions."""
1610 if pullop.remotebookmarks is not None:
1610 if pullop.remotebookmarks is not None:
1611 return
1611 return
1612 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1612 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1613 # all known bundle2 servers now support listkeys, but lets be nice with
1613 # all known bundle2 servers now support listkeys, but lets be nice with
1614 # new implementation.
1614 # new implementation.
1615 return
1615 return
1616 books = listkeys(pullop.remote, 'bookmarks')
1616 books = listkeys(pullop.remote, 'bookmarks')
1617 pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)
1617 pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)
1618
1618
1619
1619
1620 @pulldiscovery('changegroup')
1620 @pulldiscovery('changegroup')
1621 def _pulldiscoverychangegroup(pullop):
1621 def _pulldiscoverychangegroup(pullop):
1622 """discovery phase for the pull
1622 """discovery phase for the pull
1623
1623
1624 Current handle changeset discovery only, will change handle all discovery
1624 Current handle changeset discovery only, will change handle all discovery
1625 at some point."""
1625 at some point."""
1626 tmp = discovery.findcommonincoming(pullop.repo,
1626 tmp = discovery.findcommonincoming(pullop.repo,
1627 pullop.remote,
1627 pullop.remote,
1628 heads=pullop.heads,
1628 heads=pullop.heads,
1629 force=pullop.force)
1629 force=pullop.force)
1630 common, fetch, rheads = tmp
1630 common, fetch, rheads = tmp
1631 nm = pullop.repo.unfiltered().changelog.nodemap
1631 nm = pullop.repo.unfiltered().changelog.nodemap
1632 if fetch and rheads:
1632 if fetch and rheads:
1633 # If a remote heads is filtered locally, put in back in common.
1633 # If a remote heads is filtered locally, put in back in common.
1634 #
1634 #
1635 # This is a hackish solution to catch most of "common but locally
1635 # This is a hackish solution to catch most of "common but locally
1636 # hidden situation". We do not performs discovery on unfiltered
1636 # hidden situation". We do not performs discovery on unfiltered
1637 # repository because it end up doing a pathological amount of round
1637 # repository because it end up doing a pathological amount of round
1638 # trip for w huge amount of changeset we do not care about.
1638 # trip for w huge amount of changeset we do not care about.
1639 #
1639 #
1640 # If a set of such "common but filtered" changeset exist on the server
1640 # If a set of such "common but filtered" changeset exist on the server
1641 # but are not including a remote heads, we'll not be able to detect it,
1641 # but are not including a remote heads, we'll not be able to detect it,
1642 scommon = set(common)
1642 scommon = set(common)
1643 for n in rheads:
1643 for n in rheads:
1644 if n in nm:
1644 if n in nm:
1645 if n not in scommon:
1645 if n not in scommon:
1646 common.append(n)
1646 common.append(n)
1647 if set(rheads).issubset(set(common)):
1647 if set(rheads).issubset(set(common)):
1648 fetch = []
1648 fetch = []
1649 pullop.common = common
1649 pullop.common = common
1650 pullop.fetch = fetch
1650 pullop.fetch = fetch
1651 pullop.rheads = rheads
1651 pullop.rheads = rheads
1652
1652
1653 def _pullbundle2(pullop):
1653 def _pullbundle2(pullop):
1654 """pull data using bundle2
1654 """pull data using bundle2
1655
1655
1656 For now, the only supported data are changegroup."""
1656 For now, the only supported data are changegroup."""
1657 kwargs = {'bundlecaps': caps20to10(pullop.repo, role='client')}
1657 kwargs = {'bundlecaps': caps20to10(pullop.repo, role='client')}
1658
1658
1659 # make ui easier to access
1659 # make ui easier to access
1660 ui = pullop.repo.ui
1660 ui = pullop.repo.ui
1661
1661
1662 # At the moment we don't do stream clones over bundle2. If that is
1662 # At the moment we don't do stream clones over bundle2. If that is
1663 # implemented then here's where the check for that will go.
1663 # implemented then here's where the check for that will go.
1664 streaming = streamclone.canperformstreamclone(pullop, bundle2=True)[0]
1664 streaming = streamclone.canperformstreamclone(pullop, bundle2=True)[0]
1665
1665
1666 # declare pull perimeters
1666 # declare pull perimeters
1667 kwargs['common'] = pullop.common
1667 kwargs['common'] = pullop.common
1668 kwargs['heads'] = pullop.heads or pullop.rheads
1668 kwargs['heads'] = pullop.heads or pullop.rheads
1669
1669
1670 # check server supports narrow and then adding includepats and excludepats
1670 # check server supports narrow and then adding includepats and excludepats
1671 servernarrow = pullop.remote.capable(wireprototypes.NARROWCAP)
1671 servernarrow = pullop.remote.capable(wireprototypes.NARROWCAP)
1672 if servernarrow and pullop.includepats:
1672 if servernarrow and pullop.includepats:
1673 kwargs['includepats'] = pullop.includepats
1673 kwargs['includepats'] = pullop.includepats
1674 if servernarrow and pullop.excludepats:
1674 if servernarrow and pullop.excludepats:
1675 kwargs['excludepats'] = pullop.excludepats
1675 kwargs['excludepats'] = pullop.excludepats
1676
1676
1677 if streaming:
1677 if streaming:
1678 kwargs['cg'] = False
1678 kwargs['cg'] = False
1679 kwargs['stream'] = True
1679 kwargs['stream'] = True
1680 pullop.stepsdone.add('changegroup')
1680 pullop.stepsdone.add('changegroup')
1681 pullop.stepsdone.add('phases')
1681 pullop.stepsdone.add('phases')
1682
1682
1683 else:
1683 else:
1684 # pulling changegroup
1684 # pulling changegroup
1685 pullop.stepsdone.add('changegroup')
1685 pullop.stepsdone.add('changegroup')
1686
1686
1687 kwargs['cg'] = pullop.fetch
1687 kwargs['cg'] = pullop.fetch
1688
1688
1689 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
1689 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
1690 hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
1690 hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
1691 if (not legacyphase and hasbinaryphase):
1691 if (not legacyphase and hasbinaryphase):
1692 kwargs['phases'] = True
1692 kwargs['phases'] = True
1693 pullop.stepsdone.add('phases')
1693 pullop.stepsdone.add('phases')
1694
1694
1695 if 'listkeys' in pullop.remotebundle2caps:
1695 if 'listkeys' in pullop.remotebundle2caps:
1696 if 'phases' not in pullop.stepsdone:
1696 if 'phases' not in pullop.stepsdone:
1697 kwargs['listkeys'] = ['phases']
1697 kwargs['listkeys'] = ['phases']
1698
1698
1699 bookmarksrequested = False
1699 bookmarksrequested = False
1700 legacybookmark = 'bookmarks' in ui.configlist('devel', 'legacy.exchange')
1700 legacybookmark = 'bookmarks' in ui.configlist('devel', 'legacy.exchange')
1701 hasbinarybook = 'bookmarks' in pullop.remotebundle2caps
1701 hasbinarybook = 'bookmarks' in pullop.remotebundle2caps
1702
1702
1703 if pullop.remotebookmarks is not None:
1703 if pullop.remotebookmarks is not None:
1704 pullop.stepsdone.add('request-bookmarks')
1704 pullop.stepsdone.add('request-bookmarks')
1705
1705
1706 if ('request-bookmarks' not in pullop.stepsdone
1706 if ('request-bookmarks' not in pullop.stepsdone
1707 and pullop.remotebookmarks is None
1707 and pullop.remotebookmarks is None
1708 and not legacybookmark and hasbinarybook):
1708 and not legacybookmark and hasbinarybook):
1709 kwargs['bookmarks'] = True
1709 kwargs['bookmarks'] = True
1710 bookmarksrequested = True
1710 bookmarksrequested = True
1711
1711
1712 if 'listkeys' in pullop.remotebundle2caps:
1712 if 'listkeys' in pullop.remotebundle2caps:
1713 if 'request-bookmarks' not in pullop.stepsdone:
1713 if 'request-bookmarks' not in pullop.stepsdone:
1714 # make sure to always includes bookmark data when migrating
1714 # make sure to always includes bookmark data when migrating
1715 # `hg incoming --bundle` to using this function.
1715 # `hg incoming --bundle` to using this function.
1716 pullop.stepsdone.add('request-bookmarks')
1716 pullop.stepsdone.add('request-bookmarks')
1717 kwargs.setdefault('listkeys', []).append('bookmarks')
1717 kwargs.setdefault('listkeys', []).append('bookmarks')
1718
1718
1719 # If this is a full pull / clone and the server supports the clone bundles
1719 # If this is a full pull / clone and the server supports the clone bundles
1720 # feature, tell the server whether we attempted a clone bundle. The
1720 # feature, tell the server whether we attempted a clone bundle. The
1721 # presence of this flag indicates the client supports clone bundles. This
1721 # presence of this flag indicates the client supports clone bundles. This
1722 # will enable the server to treat clients that support clone bundles
1722 # will enable the server to treat clients that support clone bundles
1723 # differently from those that don't.
1723 # differently from those that don't.
1724 if (pullop.remote.capable('clonebundles')
1724 if (pullop.remote.capable('clonebundles')
1725 and pullop.heads is None and list(pullop.common) == [nullid]):
1725 and pullop.heads is None and list(pullop.common) == [nullid]):
1726 kwargs['cbattempted'] = pullop.clonebundleattempted
1726 kwargs['cbattempted'] = pullop.clonebundleattempted
1727
1727
1728 if streaming:
1728 if streaming:
1729 pullop.repo.ui.status(_('streaming all changes\n'))
1729 pullop.repo.ui.status(_('streaming all changes\n'))
1730 elif not pullop.fetch:
1730 elif not pullop.fetch:
1731 pullop.repo.ui.status(_("no changes found\n"))
1731 pullop.repo.ui.status(_("no changes found\n"))
1732 pullop.cgresult = 0
1732 pullop.cgresult = 0
1733 else:
1733 else:
1734 if pullop.heads is None and list(pullop.common) == [nullid]:
1734 if pullop.heads is None and list(pullop.common) == [nullid]:
1735 pullop.repo.ui.status(_("requesting all changes\n"))
1735 pullop.repo.ui.status(_("requesting all changes\n"))
1736 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1736 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1737 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1737 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1738 if obsolete.commonversion(remoteversions) is not None:
1738 if obsolete.commonversion(remoteversions) is not None:
1739 kwargs['obsmarkers'] = True
1739 kwargs['obsmarkers'] = True
1740 pullop.stepsdone.add('obsmarkers')
1740 pullop.stepsdone.add('obsmarkers')
1741 _pullbundle2extraprepare(pullop, kwargs)
1741 _pullbundle2extraprepare(pullop, kwargs)
1742
1742
1743 with pullop.remote.commandexecutor() as e:
1743 with pullop.remote.commandexecutor() as e:
1744 args = dict(kwargs)
1744 args = dict(kwargs)
1745 args['source'] = 'pull'
1745 args['source'] = 'pull'
1746 bundle = e.callcommand('getbundle', args).result()
1746 bundle = e.callcommand('getbundle', args).result()
1747
1747
1748 try:
1748 try:
1749 op = bundle2.bundleoperation(pullop.repo, pullop.gettransaction,
1749 op = bundle2.bundleoperation(pullop.repo, pullop.gettransaction,
1750 source='pull')
1750 source='pull')
1751 op.modes['bookmarks'] = 'records'
1751 op.modes['bookmarks'] = 'records'
1752 bundle2.processbundle(pullop.repo, bundle, op=op)
1752 bundle2.processbundle(pullop.repo, bundle, op=op)
1753 except bundle2.AbortFromPart as exc:
1753 except bundle2.AbortFromPart as exc:
1754 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1754 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1755 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1755 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1756 except error.BundleValueError as exc:
1756 except error.BundleValueError as exc:
1757 raise error.Abort(_('missing support for %s') % exc)
1757 raise error.Abort(_('missing support for %s') % exc)
1758
1758
1759 if pullop.fetch:
1759 if pullop.fetch:
1760 pullop.cgresult = bundle2.combinechangegroupresults(op)
1760 pullop.cgresult = bundle2.combinechangegroupresults(op)
1761
1761
1762 # processing phases change
1762 # processing phases change
1763 for namespace, value in op.records['listkeys']:
1763 for namespace, value in op.records['listkeys']:
1764 if namespace == 'phases':
1764 if namespace == 'phases':
1765 _pullapplyphases(pullop, value)
1765 _pullapplyphases(pullop, value)
1766
1766
1767 # processing bookmark update
1767 # processing bookmark update
1768 if bookmarksrequested:
1768 if bookmarksrequested:
1769 books = {}
1769 books = {}
1770 for record in op.records['bookmarks']:
1770 for record in op.records['bookmarks']:
1771 books[record['bookmark']] = record["node"]
1771 books[record['bookmark']] = record["node"]
1772 pullop.remotebookmarks = books
1772 pullop.remotebookmarks = books
1773 else:
1773 else:
1774 for namespace, value in op.records['listkeys']:
1774 for namespace, value in op.records['listkeys']:
1775 if namespace == 'bookmarks':
1775 if namespace == 'bookmarks':
1776 pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)
1776 pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)
1777
1777
1778 # bookmark data were either already there or pulled in the bundle
1778 # bookmark data were either already there or pulled in the bundle
1779 if pullop.remotebookmarks is not None:
1779 if pullop.remotebookmarks is not None:
1780 _pullbookmarks(pullop)
1780 _pullbookmarks(pullop)
1781
1781
1782 def _pullbundle2extraprepare(pullop, kwargs):
1782 def _pullbundle2extraprepare(pullop, kwargs):
1783 """hook function so that extensions can extend the getbundle call"""
1783 """hook function so that extensions can extend the getbundle call"""
1784
1784
1785 def _pullchangeset(pullop):
1785 def _pullchangeset(pullop):
1786 """pull changeset from unbundle into the local repo"""
1786 """pull changeset from unbundle into the local repo"""
1787 # We delay the open of the transaction as late as possible so we
1787 # We delay the open of the transaction as late as possible so we
1788 # don't open transaction for nothing or you break future useful
1788 # don't open transaction for nothing or you break future useful
1789 # rollback call
1789 # rollback call
1790 if 'changegroup' in pullop.stepsdone:
1790 if 'changegroup' in pullop.stepsdone:
1791 return
1791 return
1792 pullop.stepsdone.add('changegroup')
1792 pullop.stepsdone.add('changegroup')
1793 if not pullop.fetch:
1793 if not pullop.fetch:
1794 pullop.repo.ui.status(_("no changes found\n"))
1794 pullop.repo.ui.status(_("no changes found\n"))
1795 pullop.cgresult = 0
1795 pullop.cgresult = 0
1796 return
1796 return
1797 tr = pullop.gettransaction()
1797 tr = pullop.gettransaction()
1798 if pullop.heads is None and list(pullop.common) == [nullid]:
1798 if pullop.heads is None and list(pullop.common) == [nullid]:
1799 pullop.repo.ui.status(_("requesting all changes\n"))
1799 pullop.repo.ui.status(_("requesting all changes\n"))
1800 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1800 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1801 # issue1320, avoid a race if remote changed after discovery
1801 # issue1320, avoid a race if remote changed after discovery
1802 pullop.heads = pullop.rheads
1802 pullop.heads = pullop.rheads
1803
1803
1804 if pullop.remote.capable('getbundle'):
1804 if pullop.remote.capable('getbundle'):
1805 # TODO: get bundlecaps from remote
1805 # TODO: get bundlecaps from remote
1806 cg = pullop.remote.getbundle('pull', common=pullop.common,
1806 cg = pullop.remote.getbundle('pull', common=pullop.common,
1807 heads=pullop.heads or pullop.rheads)
1807 heads=pullop.heads or pullop.rheads)
1808 elif pullop.heads is None:
1808 elif pullop.heads is None:
1809 with pullop.remote.commandexecutor() as e:
1809 with pullop.remote.commandexecutor() as e:
1810 cg = e.callcommand('changegroup', {
1810 cg = e.callcommand('changegroup', {
1811 'nodes': pullop.fetch,
1811 'nodes': pullop.fetch,
1812 'source': 'pull',
1812 'source': 'pull',
1813 }).result()
1813 }).result()
1814
1814
1815 elif not pullop.remote.capable('changegroupsubset'):
1815 elif not pullop.remote.capable('changegroupsubset'):
1816 raise error.Abort(_("partial pull cannot be done because "
1816 raise error.Abort(_("partial pull cannot be done because "
1817 "other repository doesn't support "
1817 "other repository doesn't support "
1818 "changegroupsubset."))
1818 "changegroupsubset."))
1819 else:
1819 else:
1820 with pullop.remote.commandexecutor() as e:
1820 with pullop.remote.commandexecutor() as e:
1821 cg = e.callcommand('changegroupsubset', {
1821 cg = e.callcommand('changegroupsubset', {
1822 'bases': pullop.fetch,
1822 'bases': pullop.fetch,
1823 'heads': pullop.heads,
1823 'heads': pullop.heads,
1824 'source': 'pull',
1824 'source': 'pull',
1825 }).result()
1825 }).result()
1826
1826
1827 bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',
1827 bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',
1828 pullop.remote.url())
1828 pullop.remote.url())
1829 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
1829 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
1830
1830
1831 def _pullphase(pullop):
1831 def _pullphase(pullop):
1832 # Get remote phases data from remote
1832 # Get remote phases data from remote
1833 if 'phases' in pullop.stepsdone:
1833 if 'phases' in pullop.stepsdone:
1834 return
1834 return
1835 remotephases = listkeys(pullop.remote, 'phases')
1835 remotephases = listkeys(pullop.remote, 'phases')
1836 _pullapplyphases(pullop, remotephases)
1836 _pullapplyphases(pullop, remotephases)
1837
1837
1838 def _pullapplyphases(pullop, remotephases):
1838 def _pullapplyphases(pullop, remotephases):
1839 """apply phase movement from observed remote state"""
1839 """apply phase movement from observed remote state"""
1840 if 'phases' in pullop.stepsdone:
1840 if 'phases' in pullop.stepsdone:
1841 return
1841 return
1842 pullop.stepsdone.add('phases')
1842 pullop.stepsdone.add('phases')
1843 publishing = bool(remotephases.get('publishing', False))
1843 publishing = bool(remotephases.get('publishing', False))
1844 if remotephases and not publishing:
1844 if remotephases and not publishing:
1845 # remote is new and non-publishing
1845 # remote is new and non-publishing
1846 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1846 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1847 pullop.pulledsubset,
1847 pullop.pulledsubset,
1848 remotephases)
1848 remotephases)
1849 dheads = pullop.pulledsubset
1849 dheads = pullop.pulledsubset
1850 else:
1850 else:
1851 # Remote is old or publishing all common changesets
1851 # Remote is old or publishing all common changesets
1852 # should be seen as public
1852 # should be seen as public
1853 pheads = pullop.pulledsubset
1853 pheads = pullop.pulledsubset
1854 dheads = []
1854 dheads = []
1855 unfi = pullop.repo.unfiltered()
1855 unfi = pullop.repo.unfiltered()
1856 phase = unfi._phasecache.phase
1856 phase = unfi._phasecache.phase
1857 rev = unfi.changelog.nodemap.get
1857 rev = unfi.changelog.nodemap.get
1858 public = phases.public
1858 public = phases.public
1859 draft = phases.draft
1859 draft = phases.draft
1860
1860
1861 # exclude changesets already public locally and update the others
1861 # exclude changesets already public locally and update the others
1862 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1862 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1863 if pheads:
1863 if pheads:
1864 tr = pullop.gettransaction()
1864 tr = pullop.gettransaction()
1865 phases.advanceboundary(pullop.repo, tr, public, pheads)
1865 phases.advanceboundary(pullop.repo, tr, public, pheads)
1866
1866
1867 # exclude changesets already draft locally and update the others
1867 # exclude changesets already draft locally and update the others
1868 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1868 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1869 if dheads:
1869 if dheads:
1870 tr = pullop.gettransaction()
1870 tr = pullop.gettransaction()
1871 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1871 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1872
1872
1873 def _pullbookmarks(pullop):
1873 def _pullbookmarks(pullop):
1874 """process the remote bookmark information to update the local one"""
1874 """process the remote bookmark information to update the local one"""
1875 if 'bookmarks' in pullop.stepsdone:
1875 if 'bookmarks' in pullop.stepsdone:
1876 return
1876 return
1877 pullop.stepsdone.add('bookmarks')
1877 pullop.stepsdone.add('bookmarks')
1878 repo = pullop.repo
1878 repo = pullop.repo
1879 remotebookmarks = pullop.remotebookmarks
1879 remotebookmarks = pullop.remotebookmarks
1880 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1880 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1881 pullop.remote.url(),
1881 pullop.remote.url(),
1882 pullop.gettransaction,
1882 pullop.gettransaction,
1883 explicit=pullop.explicitbookmarks)
1883 explicit=pullop.explicitbookmarks)
1884
1884
1885 def _pullobsolete(pullop):
1885 def _pullobsolete(pullop):
1886 """utility function to pull obsolete markers from a remote
1886 """utility function to pull obsolete markers from a remote
1887
1887
1888 The `gettransaction` is function that return the pull transaction, creating
1888 The `gettransaction` is function that return the pull transaction, creating
1889 one if necessary. We return the transaction to inform the calling code that
1889 one if necessary. We return the transaction to inform the calling code that
1890 a new transaction have been created (when applicable).
1890 a new transaction have been created (when applicable).
1891
1891
1892 Exists mostly to allow overriding for experimentation purpose"""
1892 Exists mostly to allow overriding for experimentation purpose"""
1893 if 'obsmarkers' in pullop.stepsdone:
1893 if 'obsmarkers' in pullop.stepsdone:
1894 return
1894 return
1895 pullop.stepsdone.add('obsmarkers')
1895 pullop.stepsdone.add('obsmarkers')
1896 tr = None
1896 tr = None
1897 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1897 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1898 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1898 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1899 remoteobs = listkeys(pullop.remote, 'obsolete')
1899 remoteobs = listkeys(pullop.remote, 'obsolete')
1900 if 'dump0' in remoteobs:
1900 if 'dump0' in remoteobs:
1901 tr = pullop.gettransaction()
1901 tr = pullop.gettransaction()
1902 markers = []
1902 markers = []
1903 for key in sorted(remoteobs, reverse=True):
1903 for key in sorted(remoteobs, reverse=True):
1904 if key.startswith('dump'):
1904 if key.startswith('dump'):
1905 data = util.b85decode(remoteobs[key])
1905 data = util.b85decode(remoteobs[key])
1906 version, newmarks = obsolete._readmarkers(data)
1906 version, newmarks = obsolete._readmarkers(data)
1907 markers += newmarks
1907 markers += newmarks
1908 if markers:
1908 if markers:
1909 pullop.repo.obsstore.add(tr, markers)
1909 pullop.repo.obsstore.add(tr, markers)
1910 pullop.repo.invalidatevolatilesets()
1910 pullop.repo.invalidatevolatilesets()
1911 return tr
1911 return tr
1912
1912
1913 def applynarrowacl(repo, kwargs):
1913 def applynarrowacl(repo, kwargs):
1914 """Apply narrow fetch access control.
1914 """Apply narrow fetch access control.
1915
1915
1916 This massages the named arguments for getbundle wire protocol commands
1916 This massages the named arguments for getbundle wire protocol commands
1917 so requested data is filtered through access control rules.
1917 so requested data is filtered through access control rules.
1918 """
1918 """
1919 ui = repo.ui
1919 ui = repo.ui
1920 # TODO this assumes existence of HTTP and is a layering violation.
1920 # TODO this assumes existence of HTTP and is a layering violation.
1921 username = ui.shortuser(ui.environ.get('REMOTE_USER') or ui.username())
1921 username = ui.shortuser(ui.environ.get('REMOTE_USER') or ui.username())
1922 user_includes = ui.configlist(
1922 user_includes = ui.configlist(
1923 _NARROWACL_SECTION, username + '.includes',
1923 _NARROWACL_SECTION, username + '.includes',
1924 ui.configlist(_NARROWACL_SECTION, 'default.includes'))
1924 ui.configlist(_NARROWACL_SECTION, 'default.includes'))
1925 user_excludes = ui.configlist(
1925 user_excludes = ui.configlist(
1926 _NARROWACL_SECTION, username + '.excludes',
1926 _NARROWACL_SECTION, username + '.excludes',
1927 ui.configlist(_NARROWACL_SECTION, 'default.excludes'))
1927 ui.configlist(_NARROWACL_SECTION, 'default.excludes'))
1928 if not user_includes:
1928 if not user_includes:
1929 raise error.Abort(_("{} configuration for user {} is empty")
1929 raise error.Abort(_("{} configuration for user {} is empty")
1930 .format(_NARROWACL_SECTION, username))
1930 .format(_NARROWACL_SECTION, username))
1931
1931
1932 user_includes = [
1932 user_includes = [
1933 'path:.' if p == '*' else 'path:' + p for p in user_includes]
1933 'path:.' if p == '*' else 'path:' + p for p in user_includes]
1934 user_excludes = [
1934 user_excludes = [
1935 'path:.' if p == '*' else 'path:' + p for p in user_excludes]
1935 'path:.' if p == '*' else 'path:' + p for p in user_excludes]
1936
1936
1937 req_includes = set(kwargs.get(r'includepats', []))
1937 req_includes = set(kwargs.get(r'includepats', []))
1938 req_excludes = set(kwargs.get(r'excludepats', []))
1938 req_excludes = set(kwargs.get(r'excludepats', []))
1939
1939
1940 req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns(
1940 req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns(
1941 req_includes, req_excludes, user_includes, user_excludes)
1941 req_includes, req_excludes, user_includes, user_excludes)
1942
1942
1943 if invalid_includes:
1943 if invalid_includes:
1944 raise error.Abort(
1944 raise error.Abort(
1945 _("The following includes are not accessible for {}: {}")
1945 _("The following includes are not accessible for {}: {}")
1946 .format(username, invalid_includes))
1946 .format(username, invalid_includes))
1947
1947
1948 new_args = {}
1948 new_args = {}
1949 new_args.update(kwargs)
1949 new_args.update(kwargs)
1950 new_args[r'narrow'] = True
1950 new_args[r'narrow'] = True
1951 new_args[r'narrow_acl'] = True
1951 new_args[r'narrow_acl'] = True
1952 new_args[r'includepats'] = req_includes
1952 new_args[r'includepats'] = req_includes
1953 if req_excludes:
1953 if req_excludes:
1954 new_args[r'excludepats'] = req_excludes
1954 new_args[r'excludepats'] = req_excludes
1955
1955
1956 return new_args
1956 return new_args
1957
1957
1958 def _computeellipsis(repo, common, heads, known, match, depth=None):
1958 def _computeellipsis(repo, common, heads, known, match, depth=None):
1959 """Compute the shape of a narrowed DAG.
1959 """Compute the shape of a narrowed DAG.
1960
1960
1961 Args:
1961 Args:
1962 repo: The repository we're transferring.
1962 repo: The repository we're transferring.
1963 common: The roots of the DAG range we're transferring.
1963 common: The roots of the DAG range we're transferring.
1964 May be just [nullid], which means all ancestors of heads.
1964 May be just [nullid], which means all ancestors of heads.
1965 heads: The heads of the DAG range we're transferring.
1965 heads: The heads of the DAG range we're transferring.
1966 match: The narrowmatcher that allows us to identify relevant changes.
1966 match: The narrowmatcher that allows us to identify relevant changes.
1967 depth: If not None, only consider nodes to be full nodes if they are at
1967 depth: If not None, only consider nodes to be full nodes if they are at
1968 most depth changesets away from one of heads.
1968 most depth changesets away from one of heads.
1969
1969
1970 Returns:
1970 Returns:
1971 A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
1971 A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
1972
1972
1973 visitnodes: The list of nodes (either full or ellipsis) which
1973 visitnodes: The list of nodes (either full or ellipsis) which
1974 need to be sent to the client.
1974 need to be sent to the client.
1975 relevant_nodes: The set of changelog nodes which change a file inside
1975 relevant_nodes: The set of changelog nodes which change a file inside
1976 the narrowspec. The client needs these as non-ellipsis nodes.
1976 the narrowspec. The client needs these as non-ellipsis nodes.
1977 ellipsisroots: A dict of {rev: parents} that is used in
1977 ellipsisroots: A dict of {rev: parents} that is used in
1978 narrowchangegroup to produce ellipsis nodes with the
1978 narrowchangegroup to produce ellipsis nodes with the
1979 correct parents.
1979 correct parents.
1980 """
1980 """
1981 cl = repo.changelog
1981 cl = repo.changelog
1982 mfl = repo.manifestlog
1982 mfl = repo.manifestlog
1983
1983
1984 clrev = cl.rev
1984 clrev = cl.rev
1985
1985
1986 commonrevs = {clrev(n) for n in common} | {nullrev}
1986 commonrevs = {clrev(n) for n in common} | {nullrev}
1987 headsrevs = {clrev(n) for n in heads}
1987 headsrevs = {clrev(n) for n in heads}
1988
1988
1989 if depth:
1989 if depth:
1990 revdepth = {h: 0 for h in headsrevs}
1990 revdepth = {h: 0 for h in headsrevs}
1991
1991
1992 ellipsisheads = collections.defaultdict(set)
1992 ellipsisheads = collections.defaultdict(set)
1993 ellipsisroots = collections.defaultdict(set)
1993 ellipsisroots = collections.defaultdict(set)
1994
1994
1995 def addroot(head, curchange):
1995 def addroot(head, curchange):
1996 """Add a root to an ellipsis head, splitting heads with 3 roots."""
1996 """Add a root to an ellipsis head, splitting heads with 3 roots."""
1997 ellipsisroots[head].add(curchange)
1997 ellipsisroots[head].add(curchange)
1998 # Recursively split ellipsis heads with 3 roots by finding the
1998 # Recursively split ellipsis heads with 3 roots by finding the
1999 # roots' youngest common descendant which is an elided merge commit.
1999 # roots' youngest common descendant which is an elided merge commit.
2000 # That descendant takes 2 of the 3 roots as its own, and becomes a
2000 # That descendant takes 2 of the 3 roots as its own, and becomes a
2001 # root of the head.
2001 # root of the head.
2002 while len(ellipsisroots[head]) > 2:
2002 while len(ellipsisroots[head]) > 2:
2003 child, roots = splithead(head)
2003 child, roots = splithead(head)
2004 splitroots(head, child, roots)
2004 splitroots(head, child, roots)
2005 head = child # Recurse in case we just added a 3rd root
2005 head = child # Recurse in case we just added a 3rd root
2006
2006
2007 def splitroots(head, child, roots):
2007 def splitroots(head, child, roots):
2008 ellipsisroots[head].difference_update(roots)
2008 ellipsisroots[head].difference_update(roots)
2009 ellipsisroots[head].add(child)
2009 ellipsisroots[head].add(child)
2010 ellipsisroots[child].update(roots)
2010 ellipsisroots[child].update(roots)
2011 ellipsisroots[child].discard(child)
2011 ellipsisroots[child].discard(child)
2012
2012
2013 def splithead(head):
2013 def splithead(head):
2014 r1, r2, r3 = sorted(ellipsisroots[head])
2014 r1, r2, r3 = sorted(ellipsisroots[head])
2015 for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
2015 for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
2016 mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
2016 mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
2017 nr1, head, nr2, head)
2017 nr1, head, nr2, head)
2018 for j in mid:
2018 for j in mid:
2019 if j == nr2:
2019 if j == nr2:
2020 return nr2, (nr1, nr2)
2020 return nr2, (nr1, nr2)
2021 if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
2021 if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
2022 return j, (nr1, nr2)
2022 return j, (nr1, nr2)
2023 raise error.Abort(_('Failed to split up ellipsis node! head: %d, '
2023 raise error.Abort(_('Failed to split up ellipsis node! head: %d, '
2024 'roots: %d %d %d') % (head, r1, r2, r3))
2024 'roots: %d %d %d') % (head, r1, r2, r3))
2025
2025
2026 missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
2026 missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
2027 visit = reversed(missing)
2027 visit = reversed(missing)
2028 relevant_nodes = set()
2028 relevant_nodes = set()
2029 visitnodes = [cl.node(m) for m in missing]
2029 visitnodes = [cl.node(m) for m in missing]
2030 required = set(headsrevs) | known
2030 required = set(headsrevs) | known
2031 for rev in visit:
2031 for rev in visit:
2032 clrev = cl.changelogrevision(rev)
2032 clrev = cl.changelogrevision(rev)
2033 ps = [prev for prev in cl.parentrevs(rev) if prev != nullrev]
2033 ps = [prev for prev in cl.parentrevs(rev) if prev != nullrev]
2034 if depth is not None:
2034 if depth is not None:
2035 curdepth = revdepth[rev]
2035 curdepth = revdepth[rev]
2036 for p in ps:
2036 for p in ps:
2037 revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
2037 revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
2038 needed = False
2038 needed = False
2039 shallow_enough = depth is None or revdepth[rev] <= depth
2039 shallow_enough = depth is None or revdepth[rev] <= depth
2040 if shallow_enough:
2040 if shallow_enough:
2041 curmf = mfl[clrev.manifest].read()
2041 curmf = mfl[clrev.manifest].read()
2042 if ps:
2042 if ps:
2043 # We choose to not trust the changed files list in
2043 # We choose to not trust the changed files list in
2044 # changesets because it's not always correct. TODO: could
2044 # changesets because it's not always correct. TODO: could
2045 # we trust it for the non-merge case?
2045 # we trust it for the non-merge case?
2046 p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
2046 p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
2047 needed = bool(curmf.diff(p1mf, match))
2047 needed = bool(curmf.diff(p1mf, match))
2048 if not needed and len(ps) > 1:
2048 if not needed and len(ps) > 1:
2049 # For merge changes, the list of changed files is not
2049 # For merge changes, the list of changed files is not
2050 # helpful, since we need to emit the merge if a file
2050 # helpful, since we need to emit the merge if a file
2051 # in the narrow spec has changed on either side of the
2051 # in the narrow spec has changed on either side of the
2052 # merge. As a result, we do a manifest diff to check.
2052 # merge. As a result, we do a manifest diff to check.
2053 p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
2053 p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
2054 needed = bool(curmf.diff(p2mf, match))
2054 needed = bool(curmf.diff(p2mf, match))
2055 else:
2055 else:
2056 # For a root node, we need to include the node if any
2056 # For a root node, we need to include the node if any
2057 # files in the node match the narrowspec.
2057 # files in the node match the narrowspec.
2058 needed = any(curmf.walk(match))
2058 needed = any(curmf.walk(match))
2059
2059
2060 if needed:
2060 if needed:
2061 for head in ellipsisheads[rev]:
2061 for head in ellipsisheads[rev]:
2062 addroot(head, rev)
2062 addroot(head, rev)
2063 for p in ps:
2063 for p in ps:
2064 required.add(p)
2064 required.add(p)
2065 relevant_nodes.add(cl.node(rev))
2065 relevant_nodes.add(cl.node(rev))
2066 else:
2066 else:
2067 if not ps:
2067 if not ps:
2068 ps = [nullrev]
2068 ps = [nullrev]
2069 if rev in required:
2069 if rev in required:
2070 for head in ellipsisheads[rev]:
2070 for head in ellipsisheads[rev]:
2071 addroot(head, rev)
2071 addroot(head, rev)
2072 for p in ps:
2072 for p in ps:
2073 ellipsisheads[p].add(rev)
2073 ellipsisheads[p].add(rev)
2074 else:
2074 else:
2075 for p in ps:
2075 for p in ps:
2076 ellipsisheads[p] |= ellipsisheads[rev]
2076 ellipsisheads[p] |= ellipsisheads[rev]
2077
2077
2078 # add common changesets as roots of their reachable ellipsis heads
2078 # add common changesets as roots of their reachable ellipsis heads
2079 for c in commonrevs:
2079 for c in commonrevs:
2080 for head in ellipsisheads[c]:
2080 for head in ellipsisheads[c]:
2081 addroot(head, c)
2081 addroot(head, c)
2082 return visitnodes, relevant_nodes, ellipsisroots
2082 return visitnodes, relevant_nodes, ellipsisroots
2083
2083
2084 def caps20to10(repo, role):
2084 def caps20to10(repo, role):
2085 """return a set with appropriate options to use bundle20 during getbundle"""
2085 """return a set with appropriate options to use bundle20 during getbundle"""
2086 caps = {'HG20'}
2086 caps = {'HG20'}
2087 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role=role))
2087 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role=role))
2088 caps.add('bundle2=' + urlreq.quote(capsblob))
2088 caps.add('bundle2=' + urlreq.quote(capsblob))
2089 return caps
2089 return caps
2090
2090
2091 # List of names of steps to perform for a bundle2 for getbundle, order matters.
2091 # List of names of steps to perform for a bundle2 for getbundle, order matters.
2092 getbundle2partsorder = []
2092 getbundle2partsorder = []
2093
2093
2094 # Mapping between step name and function
2094 # Mapping between step name and function
2095 #
2095 #
2096 # This exists to help extensions wrap steps if necessary
2096 # This exists to help extensions wrap steps if necessary
2097 getbundle2partsmapping = {}
2097 getbundle2partsmapping = {}
2098
2098
2099 def getbundle2partsgenerator(stepname, idx=None):
2099 def getbundle2partsgenerator(stepname, idx=None):
2100 """decorator for function generating bundle2 part for getbundle
2100 """decorator for function generating bundle2 part for getbundle
2101
2101
2102 The function is added to the step -> function mapping and appended to the
2102 The function is added to the step -> function mapping and appended to the
2103 list of steps. Beware that decorated functions will be added in order
2103 list of steps. Beware that decorated functions will be added in order
2104 (this may matter).
2104 (this may matter).
2105
2105
2106 You can only use this decorator for new steps, if you want to wrap a step
2106 You can only use this decorator for new steps, if you want to wrap a step
2107 from an extension, attack the getbundle2partsmapping dictionary directly."""
2107 from an extension, attack the getbundle2partsmapping dictionary directly."""
2108 def dec(func):
2108 def dec(func):
2109 assert stepname not in getbundle2partsmapping
2109 assert stepname not in getbundle2partsmapping
2110 getbundle2partsmapping[stepname] = func
2110 getbundle2partsmapping[stepname] = func
2111 if idx is None:
2111 if idx is None:
2112 getbundle2partsorder.append(stepname)
2112 getbundle2partsorder.append(stepname)
2113 else:
2113 else:
2114 getbundle2partsorder.insert(idx, stepname)
2114 getbundle2partsorder.insert(idx, stepname)
2115 return func
2115 return func
2116 return dec
2116 return dec
2117
2117
2118 def bundle2requested(bundlecaps):
2118 def bundle2requested(bundlecaps):
2119 if bundlecaps is not None:
2119 if bundlecaps is not None:
2120 return any(cap.startswith('HG2') for cap in bundlecaps)
2120 return any(cap.startswith('HG2') for cap in bundlecaps)
2121 return False
2121 return False
2122
2122
2123 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
2123 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
2124 **kwargs):
2124 **kwargs):
2125 """Return chunks constituting a bundle's raw data.
2125 """Return chunks constituting a bundle's raw data.
2126
2126
2127 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
2127 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
2128 passed.
2128 passed.
2129
2129
2130 Returns a 2-tuple of a dict with metadata about the generated bundle
2130 Returns a 2-tuple of a dict with metadata about the generated bundle
2131 and an iterator over raw chunks (of varying sizes).
2131 and an iterator over raw chunks (of varying sizes).
2132 """
2132 """
2133 kwargs = pycompat.byteskwargs(kwargs)
2133 kwargs = pycompat.byteskwargs(kwargs)
2134 info = {}
2134 info = {}
2135 usebundle2 = bundle2requested(bundlecaps)
2135 usebundle2 = bundle2requested(bundlecaps)
2136 # bundle10 case
2136 # bundle10 case
2137 if not usebundle2:
2137 if not usebundle2:
2138 if bundlecaps and not kwargs.get('cg', True):
2138 if bundlecaps and not kwargs.get('cg', True):
2139 raise ValueError(_('request for bundle10 must include changegroup'))
2139 raise ValueError(_('request for bundle10 must include changegroup'))
2140
2140
2141 if kwargs:
2141 if kwargs:
2142 raise ValueError(_('unsupported getbundle arguments: %s')
2142 raise ValueError(_('unsupported getbundle arguments: %s')
2143 % ', '.join(sorted(kwargs.keys())))
2143 % ', '.join(sorted(kwargs.keys())))
2144 outgoing = _computeoutgoing(repo, heads, common)
2144 outgoing = _computeoutgoing(repo, heads, common)
2145 info['bundleversion'] = 1
2145 info['bundleversion'] = 1
2146 return info, changegroup.makestream(repo, outgoing, '01', source,
2146 return info, changegroup.makestream(repo, outgoing, '01', source,
2147 bundlecaps=bundlecaps)
2147 bundlecaps=bundlecaps)
2148
2148
2149 # bundle20 case
2149 # bundle20 case
2150 info['bundleversion'] = 2
2150 info['bundleversion'] = 2
2151 b2caps = {}
2151 b2caps = {}
2152 for bcaps in bundlecaps:
2152 for bcaps in bundlecaps:
2153 if bcaps.startswith('bundle2='):
2153 if bcaps.startswith('bundle2='):
2154 blob = urlreq.unquote(bcaps[len('bundle2='):])
2154 blob = urlreq.unquote(bcaps[len('bundle2='):])
2155 b2caps.update(bundle2.decodecaps(blob))
2155 b2caps.update(bundle2.decodecaps(blob))
2156 bundler = bundle2.bundle20(repo.ui, b2caps)
2156 bundler = bundle2.bundle20(repo.ui, b2caps)
2157
2157
2158 kwargs['heads'] = heads
2158 kwargs['heads'] = heads
2159 kwargs['common'] = common
2159 kwargs['common'] = common
2160
2160
2161 for name in getbundle2partsorder:
2161 for name in getbundle2partsorder:
2162 func = getbundle2partsmapping[name]
2162 func = getbundle2partsmapping[name]
2163 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
2163 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
2164 **pycompat.strkwargs(kwargs))
2164 **pycompat.strkwargs(kwargs))
2165
2165
2166 info['prefercompressed'] = bundler.prefercompressed
2166 info['prefercompressed'] = bundler.prefercompressed
2167
2167
2168 return info, bundler.getchunks()
2168 return info, bundler.getchunks()
2169
2169
2170 @getbundle2partsgenerator('stream2')
2170 @getbundle2partsgenerator('stream2')
2171 def _getbundlestream2(bundler, repo, *args, **kwargs):
2171 def _getbundlestream2(bundler, repo, *args, **kwargs):
2172 return bundle2.addpartbundlestream2(bundler, repo, **kwargs)
2172 return bundle2.addpartbundlestream2(bundler, repo, **kwargs)
2173
2173
2174 @getbundle2partsgenerator('changegroup')
2174 @getbundle2partsgenerator('changegroup')
2175 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
2175 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
2176 b2caps=None, heads=None, common=None, **kwargs):
2176 b2caps=None, heads=None, common=None, **kwargs):
2177 """add a changegroup part to the requested bundle"""
2177 """add a changegroup part to the requested bundle"""
2178 if not kwargs.get(r'cg', True):
2178 if not kwargs.get(r'cg', True):
2179 return
2179 return
2180
2180
2181 version = '01'
2181 version = '01'
2182 cgversions = b2caps.get('changegroup')
2182 cgversions = b2caps.get('changegroup')
2183 if cgversions: # 3.1 and 3.2 ship with an empty value
2183 if cgversions: # 3.1 and 3.2 ship with an empty value
2184 cgversions = [v for v in cgversions
2184 cgversions = [v for v in cgversions
2185 if v in changegroup.supportedoutgoingversions(repo)]
2185 if v in changegroup.supportedoutgoingversions(repo)]
2186 if not cgversions:
2186 if not cgversions:
2187 raise error.Abort(_('no common changegroup version'))
2187 raise error.Abort(_('no common changegroup version'))
2188 version = max(cgversions)
2188 version = max(cgversions)
2189
2189
2190 outgoing = _computeoutgoing(repo, heads, common)
2190 outgoing = _computeoutgoing(repo, heads, common)
2191 if not outgoing.missing:
2191 if not outgoing.missing:
2192 return
2192 return
2193
2193
2194 if kwargs.get(r'narrow', False):
2194 if kwargs.get(r'narrow', False):
2195 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
2195 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
2196 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
2196 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
2197 matcher = narrowspec.match(repo.root, include=include, exclude=exclude)
2197 matcher = narrowspec.match(repo.root, include=include, exclude=exclude)
2198 else:
2198 else:
2199 matcher = None
2199 matcher = None
2200
2200
2201 cgstream = changegroup.makestream(repo, outgoing, version, source,
2201 cgstream = changegroup.makestream(repo, outgoing, version, source,
2202 bundlecaps=bundlecaps, matcher=matcher)
2202 bundlecaps=bundlecaps, matcher=matcher)
2203
2203
2204 part = bundler.newpart('changegroup', data=cgstream)
2204 part = bundler.newpart('changegroup', data=cgstream)
2205 if cgversions:
2205 if cgversions:
2206 part.addparam('version', version)
2206 part.addparam('version', version)
2207
2207
2208 part.addparam('nbchanges', '%d' % len(outgoing.missing),
2208 part.addparam('nbchanges', '%d' % len(outgoing.missing),
2209 mandatory=False)
2209 mandatory=False)
2210
2210
2211 if 'treemanifest' in repo.requirements:
2211 if 'treemanifest' in repo.requirements:
2212 part.addparam('treemanifest', '1')
2212 part.addparam('treemanifest', '1')
2213
2213
2214 if (kwargs.get(r'narrow', False) and kwargs.get(r'narrow_acl', False)
2214 if (kwargs.get(r'narrow', False) and kwargs.get(r'narrow_acl', False)
2215 and (include or exclude)):
2215 and (include or exclude)):
2216 narrowspecpart = bundler.newpart('narrow:spec')
2216 narrowspecpart = bundler.newpart('narrow:spec')
2217 if include:
2217 if include:
2218 narrowspecpart.addparam(
2218 narrowspecpart.addparam(
2219 'include', '\n'.join(include), mandatory=True)
2219 'include', '\n'.join(include), mandatory=True)
2220 if exclude:
2220 if exclude:
2221 narrowspecpart.addparam(
2221 narrowspecpart.addparam(
2222 'exclude', '\n'.join(exclude), mandatory=True)
2222 'exclude', '\n'.join(exclude), mandatory=True)
2223
2223
2224 @getbundle2partsgenerator('bookmarks')
2224 @getbundle2partsgenerator('bookmarks')
2225 def _getbundlebookmarkpart(bundler, repo, source, bundlecaps=None,
2225 def _getbundlebookmarkpart(bundler, repo, source, bundlecaps=None,
2226 b2caps=None, **kwargs):
2226 b2caps=None, **kwargs):
2227 """add a bookmark part to the requested bundle"""
2227 """add a bookmark part to the requested bundle"""
2228 if not kwargs.get(r'bookmarks', False):
2228 if not kwargs.get(r'bookmarks', False):
2229 return
2229 return
2230 if 'bookmarks' not in b2caps:
2230 if 'bookmarks' not in b2caps:
2231 raise error.Abort(_('no common bookmarks exchange method'))
2231 raise error.Abort(_('no common bookmarks exchange method'))
2232 books = bookmod.listbinbookmarks(repo)
2232 books = bookmod.listbinbookmarks(repo)
2233 data = bookmod.binaryencode(books)
2233 data = bookmod.binaryencode(books)
2234 if data:
2234 if data:
2235 bundler.newpart('bookmarks', data=data)
2235 bundler.newpart('bookmarks', data=data)
2236
2236
2237 @getbundle2partsgenerator('listkeys')
2237 @getbundle2partsgenerator('listkeys')
2238 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
2238 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
2239 b2caps=None, **kwargs):
2239 b2caps=None, **kwargs):
2240 """add parts containing listkeys namespaces to the requested bundle"""
2240 """add parts containing listkeys namespaces to the requested bundle"""
2241 listkeys = kwargs.get(r'listkeys', ())
2241 listkeys = kwargs.get(r'listkeys', ())
2242 for namespace in listkeys:
2242 for namespace in listkeys:
2243 part = bundler.newpart('listkeys')
2243 part = bundler.newpart('listkeys')
2244 part.addparam('namespace', namespace)
2244 part.addparam('namespace', namespace)
2245 keys = repo.listkeys(namespace).items()
2245 keys = repo.listkeys(namespace).items()
2246 part.data = pushkey.encodekeys(keys)
2246 part.data = pushkey.encodekeys(keys)
2247
2247
2248 @getbundle2partsgenerator('obsmarkers')
2248 @getbundle2partsgenerator('obsmarkers')
2249 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
2249 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
2250 b2caps=None, heads=None, **kwargs):
2250 b2caps=None, heads=None, **kwargs):
2251 """add an obsolescence markers part to the requested bundle"""
2251 """add an obsolescence markers part to the requested bundle"""
2252 if kwargs.get(r'obsmarkers', False):
2252 if kwargs.get(r'obsmarkers', False):
2253 if heads is None:
2253 if heads is None:
2254 heads = repo.heads()
2254 heads = repo.heads()
2255 subset = [c.node() for c in repo.set('::%ln', heads)]
2255 subset = [c.node() for c in repo.set('::%ln', heads)]
2256 markers = repo.obsstore.relevantmarkers(subset)
2256 markers = repo.obsstore.relevantmarkers(subset)
2257 markers = sorted(markers)
2257 markers = sorted(markers)
2258 bundle2.buildobsmarkerspart(bundler, markers)
2258 bundle2.buildobsmarkerspart(bundler, markers)
2259
2259
2260 @getbundle2partsgenerator('phases')
2260 @getbundle2partsgenerator('phases')
2261 def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
2261 def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
2262 b2caps=None, heads=None, **kwargs):
2262 b2caps=None, heads=None, **kwargs):
2263 """add phase heads part to the requested bundle"""
2263 """add phase heads part to the requested bundle"""
2264 if kwargs.get(r'phases', False):
2264 if kwargs.get(r'phases', False):
2265 if not 'heads' in b2caps.get('phases'):
2265 if not 'heads' in b2caps.get('phases'):
2266 raise error.Abort(_('no common phases exchange method'))
2266 raise error.Abort(_('no common phases exchange method'))
2267 if heads is None:
2267 if heads is None:
2268 heads = repo.heads()
2268 heads = repo.heads()
2269
2269
2270 headsbyphase = collections.defaultdict(set)
2270 headsbyphase = collections.defaultdict(set)
2271 if repo.publishing():
2271 if repo.publishing():
2272 headsbyphase[phases.public] = heads
2272 headsbyphase[phases.public] = heads
2273 else:
2273 else:
2274 # find the appropriate heads to move
2274 # find the appropriate heads to move
2275
2275
2276 phase = repo._phasecache.phase
2276 phase = repo._phasecache.phase
2277 node = repo.changelog.node
2277 node = repo.changelog.node
2278 rev = repo.changelog.rev
2278 rev = repo.changelog.rev
2279 for h in heads:
2279 for h in heads:
2280 headsbyphase[phase(repo, rev(h))].add(h)
2280 headsbyphase[phase(repo, rev(h))].add(h)
2281 seenphases = list(headsbyphase.keys())
2281 seenphases = list(headsbyphase.keys())
2282
2282
2283 # We do not handle anything but public and draft phase for now)
2283 # We do not handle anything but public and draft phase for now)
2284 if seenphases:
2284 if seenphases:
2285 assert max(seenphases) <= phases.draft
2285 assert max(seenphases) <= phases.draft
2286
2286
2287 # if client is pulling non-public changesets, we need to find
2287 # if client is pulling non-public changesets, we need to find
2288 # intermediate public heads.
2288 # intermediate public heads.
2289 draftheads = headsbyphase.get(phases.draft, set())
2289 draftheads = headsbyphase.get(phases.draft, set())
2290 if draftheads:
2290 if draftheads:
2291 publicheads = headsbyphase.get(phases.public, set())
2291 publicheads = headsbyphase.get(phases.public, set())
2292
2292
2293 revset = 'heads(only(%ln, %ln) and public())'
2293 revset = 'heads(only(%ln, %ln) and public())'
2294 extraheads = repo.revs(revset, draftheads, publicheads)
2294 extraheads = repo.revs(revset, draftheads, publicheads)
2295 for r in extraheads:
2295 for r in extraheads:
2296 headsbyphase[phases.public].add(node(r))
2296 headsbyphase[phases.public].add(node(r))
2297
2297
2298 # transform data in a format used by the encoding function
2298 # transform data in a format used by the encoding function
2299 phasemapping = []
2299 phasemapping = []
2300 for phase in phases.allphases:
2300 for phase in phases.allphases:
2301 phasemapping.append(sorted(headsbyphase[phase]))
2301 phasemapping.append(sorted(headsbyphase[phase]))
2302
2302
2303 # generate the actual part
2303 # generate the actual part
2304 phasedata = phases.binaryencode(phasemapping)
2304 phasedata = phases.binaryencode(phasemapping)
2305 bundler.newpart('phase-heads', data=phasedata)
2305 bundler.newpart('phase-heads', data=phasedata)
2306
2306
2307 @getbundle2partsgenerator('hgtagsfnodes')
2307 @getbundle2partsgenerator('hgtagsfnodes')
2308 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
2308 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
2309 b2caps=None, heads=None, common=None,
2309 b2caps=None, heads=None, common=None,
2310 **kwargs):
2310 **kwargs):
2311 """Transfer the .hgtags filenodes mapping.
2311 """Transfer the .hgtags filenodes mapping.
2312
2312
2313 Only values for heads in this bundle will be transferred.
2313 Only values for heads in this bundle will be transferred.
2314
2314
2315 The part data consists of pairs of 20 byte changeset node and .hgtags
2315 The part data consists of pairs of 20 byte changeset node and .hgtags
2316 filenodes raw values.
2316 filenodes raw values.
2317 """
2317 """
2318 # Don't send unless:
2318 # Don't send unless:
2319 # - changeset are being exchanged,
2319 # - changeset are being exchanged,
2320 # - the client supports it.
2320 # - the client supports it.
2321 if not (kwargs.get(r'cg', True) and 'hgtagsfnodes' in b2caps):
2321 if not (kwargs.get(r'cg', True) and 'hgtagsfnodes' in b2caps):
2322 return
2322 return
2323
2323
2324 outgoing = _computeoutgoing(repo, heads, common)
2324 outgoing = _computeoutgoing(repo, heads, common)
2325 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
2325 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
2326
2326
2327 @getbundle2partsgenerator('cache:rev-branch-cache')
2327 @getbundle2partsgenerator('cache:rev-branch-cache')
2328 def _getbundlerevbranchcache(bundler, repo, source, bundlecaps=None,
2328 def _getbundlerevbranchcache(bundler, repo, source, bundlecaps=None,
2329 b2caps=None, heads=None, common=None,
2329 b2caps=None, heads=None, common=None,
2330 **kwargs):
2330 **kwargs):
2331 """Transfer the rev-branch-cache mapping
2331 """Transfer the rev-branch-cache mapping
2332
2332
2333 The payload is a series of data related to each branch
2333 The payload is a series of data related to each branch
2334
2334
2335 1) branch name length
2335 1) branch name length
2336 2) number of open heads
2336 2) number of open heads
2337 3) number of closed heads
2337 3) number of closed heads
2338 4) open heads nodes
2338 4) open heads nodes
2339 5) closed heads nodes
2339 5) closed heads nodes
2340 """
2340 """
2341 # Don't send unless:
2341 # Don't send unless:
2342 # - changeset are being exchanged,
2342 # - changeset are being exchanged,
2343 # - the client supports it.
2343 # - the client supports it.
2344 # - narrow bundle isn't in play (not currently compatible).
2344 # - narrow bundle isn't in play (not currently compatible).
2345 if (not kwargs.get(r'cg', True)
2345 if (not kwargs.get(r'cg', True)
2346 or 'rev-branch-cache' not in b2caps
2346 or 'rev-branch-cache' not in b2caps
2347 or kwargs.get(r'narrow', False)
2347 or kwargs.get(r'narrow', False)
2348 or repo.ui.has_section(_NARROWACL_SECTION)):
2348 or repo.ui.has_section(_NARROWACL_SECTION)):
2349 return
2349 return
2350
2350
2351 outgoing = _computeoutgoing(repo, heads, common)
2351 outgoing = _computeoutgoing(repo, heads, common)
2352 bundle2.addpartrevbranchcache(repo, bundler, outgoing)
2352 bundle2.addpartrevbranchcache(repo, bundler, outgoing)
2353
2353
2354 def check_heads(repo, their_heads, context):
2354 def check_heads(repo, their_heads, context):
2355 """check if the heads of a repo have been modified
2355 """check if the heads of a repo have been modified
2356
2356
2357 Used by peer for unbundling.
2357 Used by peer for unbundling.
2358 """
2358 """
2359 heads = repo.heads()
2359 heads = repo.heads()
2360 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
2360 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
2361 if not (their_heads == ['force'] or their_heads == heads or
2361 if not (their_heads == ['force'] or their_heads == heads or
2362 their_heads == ['hashed', heads_hash]):
2362 their_heads == ['hashed', heads_hash]):
2363 # someone else committed/pushed/unbundled while we
2363 # someone else committed/pushed/unbundled while we
2364 # were transferring data
2364 # were transferring data
2365 raise error.PushRaced('repository changed while %s - '
2365 raise error.PushRaced('repository changed while %s - '
2366 'please try again' % context)
2366 'please try again' % context)
2367
2367
2368 def unbundle(repo, cg, heads, source, url):
2368 def unbundle(repo, cg, heads, source, url):
2369 """Apply a bundle to a repo.
2369 """Apply a bundle to a repo.
2370
2370
2371 this function makes sure the repo is locked during the application and have
2371 this function makes sure the repo is locked during the application and have
2372 mechanism to check that no push race occurred between the creation of the
2372 mechanism to check that no push race occurred between the creation of the
2373 bundle and its application.
2373 bundle and its application.
2374
2374
2375 If the push was raced as PushRaced exception is raised."""
2375 If the push was raced as PushRaced exception is raised."""
2376 r = 0
2376 r = 0
2377 # need a transaction when processing a bundle2 stream
2377 # need a transaction when processing a bundle2 stream
2378 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
2378 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
2379 lockandtr = [None, None, None]
2379 lockandtr = [None, None, None]
2380 recordout = None
2380 recordout = None
2381 # quick fix for output mismatch with bundle2 in 3.4
2381 # quick fix for output mismatch with bundle2 in 3.4
2382 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
2382 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
2383 if url.startswith('remote:http:') or url.startswith('remote:https:'):
2383 if url.startswith('remote:http:') or url.startswith('remote:https:'):
2384 captureoutput = True
2384 captureoutput = True
2385 try:
2385 try:
2386 # note: outside bundle1, 'heads' is expected to be empty and this
2386 # note: outside bundle1, 'heads' is expected to be empty and this
2387 # 'check_heads' call wil be a no-op
2387 # 'check_heads' call wil be a no-op
2388 check_heads(repo, heads, 'uploading changes')
2388 check_heads(repo, heads, 'uploading changes')
2389 # push can proceed
2389 # push can proceed
2390 if not isinstance(cg, bundle2.unbundle20):
2390 if not isinstance(cg, bundle2.unbundle20):
2391 # legacy case: bundle1 (changegroup 01)
2391 # legacy case: bundle1 (changegroup 01)
2392 txnname = "\n".join([source, util.hidepassword(url)])
2392 txnname = "\n".join([source, util.hidepassword(url)])
2393 with repo.lock(), repo.transaction(txnname) as tr:
2393 with repo.lock(), repo.transaction(txnname) as tr:
2394 op = bundle2.applybundle(repo, cg, tr, source, url)
2394 op = bundle2.applybundle(repo, cg, tr, source, url)
2395 r = bundle2.combinechangegroupresults(op)
2395 r = bundle2.combinechangegroupresults(op)
2396 else:
2396 else:
2397 r = None
2397 r = None
2398 try:
2398 try:
2399 def gettransaction():
2399 def gettransaction():
2400 if not lockandtr[2]:
2400 if not lockandtr[2]:
2401 lockandtr[0] = repo.wlock()
2401 lockandtr[0] = repo.wlock()
2402 lockandtr[1] = repo.lock()
2402 lockandtr[1] = repo.lock()
2403 lockandtr[2] = repo.transaction(source)
2403 lockandtr[2] = repo.transaction(source)
2404 lockandtr[2].hookargs['source'] = source
2404 lockandtr[2].hookargs['source'] = source
2405 lockandtr[2].hookargs['url'] = url
2405 lockandtr[2].hookargs['url'] = url
2406 lockandtr[2].hookargs['bundle2'] = '1'
2406 lockandtr[2].hookargs['bundle2'] = '1'
2407 return lockandtr[2]
2407 return lockandtr[2]
2408
2408
2409 # Do greedy locking by default until we're satisfied with lazy
2409 # Do greedy locking by default until we're satisfied with lazy
2410 # locking.
2410 # locking.
2411 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
2411 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
2412 gettransaction()
2412 gettransaction()
2413
2413
2414 op = bundle2.bundleoperation(repo, gettransaction,
2414 op = bundle2.bundleoperation(repo, gettransaction,
2415 captureoutput=captureoutput,
2415 captureoutput=captureoutput,
2416 source='push')
2416 source='push')
2417 try:
2417 try:
2418 op = bundle2.processbundle(repo, cg, op=op)
2418 op = bundle2.processbundle(repo, cg, op=op)
2419 finally:
2419 finally:
2420 r = op.reply
2420 r = op.reply
2421 if captureoutput and r is not None:
2421 if captureoutput and r is not None:
2422 repo.ui.pushbuffer(error=True, subproc=True)
2422 repo.ui.pushbuffer(error=True, subproc=True)
2423 def recordout(output):
2423 def recordout(output):
2424 r.newpart('output', data=output, mandatory=False)
2424 r.newpart('output', data=output, mandatory=False)
2425 if lockandtr[2] is not None:
2425 if lockandtr[2] is not None:
2426 lockandtr[2].close()
2426 lockandtr[2].close()
2427 except BaseException as exc:
2427 except BaseException as exc:
2428 exc.duringunbundle2 = True
2428 exc.duringunbundle2 = True
2429 if captureoutput and r is not None:
2429 if captureoutput and r is not None:
2430 parts = exc._bundle2salvagedoutput = r.salvageoutput()
2430 parts = exc._bundle2salvagedoutput = r.salvageoutput()
2431 def recordout(output):
2431 def recordout(output):
2432 part = bundle2.bundlepart('output', data=output,
2432 part = bundle2.bundlepart('output', data=output,
2433 mandatory=False)
2433 mandatory=False)
2434 parts.append(part)
2434 parts.append(part)
2435 raise
2435 raise
2436 finally:
2436 finally:
2437 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
2437 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
2438 if recordout is not None:
2438 if recordout is not None:
2439 recordout(repo.ui.popbuffer())
2439 recordout(repo.ui.popbuffer())
2440 return r
2440 return r
2441
2441
2442 def _maybeapplyclonebundle(pullop):
2442 def _maybeapplyclonebundle(pullop):
2443 """Apply a clone bundle from a remote, if possible."""
2443 """Apply a clone bundle from a remote, if possible."""
2444
2444
2445 repo = pullop.repo
2445 repo = pullop.repo
2446 remote = pullop.remote
2446 remote = pullop.remote
2447
2447
2448 if not repo.ui.configbool('ui', 'clonebundles'):
2448 if not repo.ui.configbool('ui', 'clonebundles'):
2449 return
2449 return
2450
2450
2451 # Only run if local repo is empty.
2451 # Only run if local repo is empty.
2452 if len(repo):
2452 if len(repo):
2453 return
2453 return
2454
2454
2455 if pullop.heads:
2455 if pullop.heads:
2456 return
2456 return
2457
2457
2458 if not remote.capable('clonebundles'):
2458 if not remote.capable('clonebundles'):
2459 return
2459 return
2460
2460
2461 with remote.commandexecutor() as e:
2461 with remote.commandexecutor() as e:
2462 res = e.callcommand('clonebundles', {}).result()
2462 res = e.callcommand('clonebundles', {}).result()
2463
2463
2464 # If we call the wire protocol command, that's good enough to record the
2464 # If we call the wire protocol command, that's good enough to record the
2465 # attempt.
2465 # attempt.
2466 pullop.clonebundleattempted = True
2466 pullop.clonebundleattempted = True
2467
2467
2468 entries = parseclonebundlesmanifest(repo, res)
2468 entries = parseclonebundlesmanifest(repo, res)
2469 if not entries:
2469 if not entries:
2470 repo.ui.note(_('no clone bundles available on remote; '
2470 repo.ui.note(_('no clone bundles available on remote; '
2471 'falling back to regular clone\n'))
2471 'falling back to regular clone\n'))
2472 return
2472 return
2473
2473
2474 entries = filterclonebundleentries(
2474 entries = filterclonebundleentries(
2475 repo, entries, streamclonerequested=pullop.streamclonerequested)
2475 repo, entries, streamclonerequested=pullop.streamclonerequested)
2476
2476
2477 if not entries:
2477 if not entries:
2478 # There is a thundering herd concern here. However, if a server
2478 # There is a thundering herd concern here. However, if a server
2479 # operator doesn't advertise bundles appropriate for its clients,
2479 # operator doesn't advertise bundles appropriate for its clients,
2480 # they deserve what's coming. Furthermore, from a client's
2480 # they deserve what's coming. Furthermore, from a client's
2481 # perspective, no automatic fallback would mean not being able to
2481 # perspective, no automatic fallback would mean not being able to
2482 # clone!
2482 # clone!
2483 repo.ui.warn(_('no compatible clone bundles available on server; '
2483 repo.ui.warn(_('no compatible clone bundles available on server; '
2484 'falling back to regular clone\n'))
2484 'falling back to regular clone\n'))
2485 repo.ui.warn(_('(you may want to report this to the server '
2485 repo.ui.warn(_('(you may want to report this to the server '
2486 'operator)\n'))
2486 'operator)\n'))
2487 return
2487 return
2488
2488
2489 entries = sortclonebundleentries(repo.ui, entries)
2489 entries = sortclonebundleentries(repo.ui, entries)
2490
2490
2491 url = entries[0]['URL']
2491 url = entries[0]['URL']
2492 repo.ui.status(_('applying clone bundle from %s\n') % url)
2492 repo.ui.status(_('applying clone bundle from %s\n') % url)
2493 if trypullbundlefromurl(repo.ui, repo, url):
2493 if trypullbundlefromurl(repo.ui, repo, url):
2494 repo.ui.status(_('finished applying clone bundle\n'))
2494 repo.ui.status(_('finished applying clone bundle\n'))
2495 # Bundle failed.
2495 # Bundle failed.
2496 #
2496 #
2497 # We abort by default to avoid the thundering herd of
2497 # We abort by default to avoid the thundering herd of
2498 # clients flooding a server that was expecting expensive
2498 # clients flooding a server that was expecting expensive
2499 # clone load to be offloaded.
2499 # clone load to be offloaded.
2500 elif repo.ui.configbool('ui', 'clonebundlefallback'):
2500 elif repo.ui.configbool('ui', 'clonebundlefallback'):
2501 repo.ui.warn(_('falling back to normal clone\n'))
2501 repo.ui.warn(_('falling back to normal clone\n'))
2502 else:
2502 else:
2503 raise error.Abort(_('error applying bundle'),
2503 raise error.Abort(_('error applying bundle'),
2504 hint=_('if this error persists, consider contacting '
2504 hint=_('if this error persists, consider contacting '
2505 'the server operator or disable clone '
2505 'the server operator or disable clone '
2506 'bundles via '
2506 'bundles via '
2507 '"--config ui.clonebundles=false"'))
2507 '"--config ui.clonebundles=false"'))
2508
2508
2509 def parseclonebundlesmanifest(repo, s):
2509 def parseclonebundlesmanifest(repo, s):
2510 """Parses the raw text of a clone bundles manifest.
2510 """Parses the raw text of a clone bundles manifest.
2511
2511
2512 Returns a list of dicts. The dicts have a ``URL`` key corresponding
2512 Returns a list of dicts. The dicts have a ``URL`` key corresponding
2513 to the URL and other keys are the attributes for the entry.
2513 to the URL and other keys are the attributes for the entry.
2514 """
2514 """
2515 m = []
2515 m = []
2516 for line in s.splitlines():
2516 for line in s.splitlines():
2517 fields = line.split()
2517 fields = line.split()
2518 if not fields:
2518 if not fields:
2519 continue
2519 continue
2520 attrs = {'URL': fields[0]}
2520 attrs = {'URL': fields[0]}
2521 for rawattr in fields[1:]:
2521 for rawattr in fields[1:]:
2522 key, value = rawattr.split('=', 1)
2522 key, value = rawattr.split('=', 1)
2523 key = urlreq.unquote(key)
2523 key = urlreq.unquote(key)
2524 value = urlreq.unquote(value)
2524 value = urlreq.unquote(value)
2525 attrs[key] = value
2525 attrs[key] = value
2526
2526
2527 # Parse BUNDLESPEC into components. This makes client-side
2527 # Parse BUNDLESPEC into components. This makes client-side
2528 # preferences easier to specify since you can prefer a single
2528 # preferences easier to specify since you can prefer a single
2529 # component of the BUNDLESPEC.
2529 # component of the BUNDLESPEC.
2530 if key == 'BUNDLESPEC':
2530 if key == 'BUNDLESPEC':
2531 try:
2531 try:
2532 bundlespec = parsebundlespec(repo, value)
2532 bundlespec = parsebundlespec(repo, value)
2533 attrs['COMPRESSION'] = bundlespec.compression
2533 attrs['COMPRESSION'] = bundlespec.compression
2534 attrs['VERSION'] = bundlespec.version
2534 attrs['VERSION'] = bundlespec.version
2535 except error.InvalidBundleSpecification:
2535 except error.InvalidBundleSpecification:
2536 pass
2536 pass
2537 except error.UnsupportedBundleSpecification:
2537 except error.UnsupportedBundleSpecification:
2538 pass
2538 pass
2539
2539
2540 m.append(attrs)
2540 m.append(attrs)
2541
2541
2542 return m
2542 return m
2543
2543
2544 def isstreamclonespec(bundlespec):
2544 def isstreamclonespec(bundlespec):
2545 # Stream clone v1
2545 # Stream clone v1
2546 if (bundlespec.wirecompression == 'UN' and bundlespec.wireversion == 's1'):
2546 if (bundlespec.wirecompression == 'UN' and bundlespec.wireversion == 's1'):
2547 return True
2547 return True
2548
2548
2549 # Stream clone v2
2549 # Stream clone v2
2550 if (bundlespec.wirecompression == 'UN' and
2550 if (bundlespec.wirecompression == 'UN' and
2551 bundlespec.wireversion == '02' and
2551 bundlespec.wireversion == '02' and
2552 bundlespec.contentopts.get('streamv2')):
2552 bundlespec.contentopts.get('streamv2')):
2553 return True
2553 return True
2554
2554
2555 return False
2555 return False
2556
2556
2557 def filterclonebundleentries(repo, entries, streamclonerequested=False):
2557 def filterclonebundleentries(repo, entries, streamclonerequested=False):
2558 """Remove incompatible clone bundle manifest entries.
2558 """Remove incompatible clone bundle manifest entries.
2559
2559
2560 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
2560 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
2561 and returns a new list consisting of only the entries that this client
2561 and returns a new list consisting of only the entries that this client
2562 should be able to apply.
2562 should be able to apply.
2563
2563
2564 There is no guarantee we'll be able to apply all returned entries because
2564 There is no guarantee we'll be able to apply all returned entries because
2565 the metadata we use to filter on may be missing or wrong.
2565 the metadata we use to filter on may be missing or wrong.
2566 """
2566 """
2567 newentries = []
2567 newentries = []
2568 for entry in entries:
2568 for entry in entries:
2569 spec = entry.get('BUNDLESPEC')
2569 spec = entry.get('BUNDLESPEC')
2570 if spec:
2570 if spec:
2571 try:
2571 try:
2572 bundlespec = parsebundlespec(repo, spec, strict=True)
2572 bundlespec = parsebundlespec(repo, spec, strict=True)
2573
2573
2574 # If a stream clone was requested, filter out non-streamclone
2574 # If a stream clone was requested, filter out non-streamclone
2575 # entries.
2575 # entries.
2576 if streamclonerequested and not isstreamclonespec(bundlespec):
2576 if streamclonerequested and not isstreamclonespec(bundlespec):
2577 repo.ui.debug('filtering %s because not a stream clone\n' %
2577 repo.ui.debug('filtering %s because not a stream clone\n' %
2578 entry['URL'])
2578 entry['URL'])
2579 continue
2579 continue
2580
2580
2581 except error.InvalidBundleSpecification as e:
2581 except error.InvalidBundleSpecification as e:
2582 repo.ui.debug(stringutil.forcebytestr(e) + '\n')
2582 repo.ui.debug(stringutil.forcebytestr(e) + '\n')
2583 continue
2583 continue
2584 except error.UnsupportedBundleSpecification as e:
2584 except error.UnsupportedBundleSpecification as e:
2585 repo.ui.debug('filtering %s because unsupported bundle '
2585 repo.ui.debug('filtering %s because unsupported bundle '
2586 'spec: %s\n' % (
2586 'spec: %s\n' % (
2587 entry['URL'], stringutil.forcebytestr(e)))
2587 entry['URL'], stringutil.forcebytestr(e)))
2588 continue
2588 continue
2589 # If we don't have a spec and requested a stream clone, we don't know
2589 # If we don't have a spec and requested a stream clone, we don't know
2590 # what the entry is so don't attempt to apply it.
2590 # what the entry is so don't attempt to apply it.
2591 elif streamclonerequested:
2591 elif streamclonerequested:
2592 repo.ui.debug('filtering %s because cannot determine if a stream '
2592 repo.ui.debug('filtering %s because cannot determine if a stream '
2593 'clone bundle\n' % entry['URL'])
2593 'clone bundle\n' % entry['URL'])
2594 continue
2594 continue
2595
2595
2596 if 'REQUIRESNI' in entry and not sslutil.hassni:
2596 if 'REQUIRESNI' in entry and not sslutil.hassni:
2597 repo.ui.debug('filtering %s because SNI not supported\n' %
2597 repo.ui.debug('filtering %s because SNI not supported\n' %
2598 entry['URL'])
2598 entry['URL'])
2599 continue
2599 continue
2600
2600
2601 newentries.append(entry)
2601 newentries.append(entry)
2602
2602
2603 return newentries
2603 return newentries
2604
2604
2605 class clonebundleentry(object):
2605 class clonebundleentry(object):
2606 """Represents an item in a clone bundles manifest.
2606 """Represents an item in a clone bundles manifest.
2607
2607
2608 This rich class is needed to support sorting since sorted() in Python 3
2608 This rich class is needed to support sorting since sorted() in Python 3
2609 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
2609 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
2610 won't work.
2610 won't work.
2611 """
2611 """
2612
2612
2613 def __init__(self, value, prefers):
2613 def __init__(self, value, prefers):
2614 self.value = value
2614 self.value = value
2615 self.prefers = prefers
2615 self.prefers = prefers
2616
2616
2617 def _cmp(self, other):
2617 def _cmp(self, other):
2618 for prefkey, prefvalue in self.prefers:
2618 for prefkey, prefvalue in self.prefers:
2619 avalue = self.value.get(prefkey)
2619 avalue = self.value.get(prefkey)
2620 bvalue = other.value.get(prefkey)
2620 bvalue = other.value.get(prefkey)
2621
2621
2622 # Special case for b missing attribute and a matches exactly.
2622 # Special case for b missing attribute and a matches exactly.
2623 if avalue is not None and bvalue is None and avalue == prefvalue:
2623 if avalue is not None and bvalue is None and avalue == prefvalue:
2624 return -1
2624 return -1
2625
2625
2626 # Special case for a missing attribute and b matches exactly.
2626 # Special case for a missing attribute and b matches exactly.
2627 if bvalue is not None and avalue is None and bvalue == prefvalue:
2627 if bvalue is not None and avalue is None and bvalue == prefvalue:
2628 return 1
2628 return 1
2629
2629
2630 # We can't compare unless attribute present on both.
2630 # We can't compare unless attribute present on both.
2631 if avalue is None or bvalue is None:
2631 if avalue is None or bvalue is None:
2632 continue
2632 continue
2633
2633
2634 # Same values should fall back to next attribute.
2634 # Same values should fall back to next attribute.
2635 if avalue == bvalue:
2635 if avalue == bvalue:
2636 continue
2636 continue
2637
2637
2638 # Exact matches come first.
2638 # Exact matches come first.
2639 if avalue == prefvalue:
2639 if avalue == prefvalue:
2640 return -1
2640 return -1
2641 if bvalue == prefvalue:
2641 if bvalue == prefvalue:
2642 return 1
2642 return 1
2643
2643
2644 # Fall back to next attribute.
2644 # Fall back to next attribute.
2645 continue
2645 continue
2646
2646
2647 # If we got here we couldn't sort by attributes and prefers. Fall
2647 # If we got here we couldn't sort by attributes and prefers. Fall
2648 # back to index order.
2648 # back to index order.
2649 return 0
2649 return 0
2650
2650
2651 def __lt__(self, other):
2651 def __lt__(self, other):
2652 return self._cmp(other) < 0
2652 return self._cmp(other) < 0
2653
2653
2654 def __gt__(self, other):
2654 def __gt__(self, other):
2655 return self._cmp(other) > 0
2655 return self._cmp(other) > 0
2656
2656
2657 def __eq__(self, other):
2657 def __eq__(self, other):
2658 return self._cmp(other) == 0
2658 return self._cmp(other) == 0
2659
2659
2660 def __le__(self, other):
2660 def __le__(self, other):
2661 return self._cmp(other) <= 0
2661 return self._cmp(other) <= 0
2662
2662
2663 def __ge__(self, other):
2663 def __ge__(self, other):
2664 return self._cmp(other) >= 0
2664 return self._cmp(other) >= 0
2665
2665
2666 def __ne__(self, other):
2666 def __ne__(self, other):
2667 return self._cmp(other) != 0
2667 return self._cmp(other) != 0
2668
2668
2669 def sortclonebundleentries(ui, entries):
2669 def sortclonebundleentries(ui, entries):
2670 prefers = ui.configlist('ui', 'clonebundleprefers')
2670 prefers = ui.configlist('ui', 'clonebundleprefers')
2671 if not prefers:
2671 if not prefers:
2672 return list(entries)
2672 return list(entries)
2673
2673
2674 prefers = [p.split('=', 1) for p in prefers]
2674 prefers = [p.split('=', 1) for p in prefers]
2675
2675
2676 items = sorted(clonebundleentry(v, prefers) for v in entries)
2676 items = sorted(clonebundleentry(v, prefers) for v in entries)
2677 return [i.value for i in items]
2677 return [i.value for i in items]
2678
2678
2679 def trypullbundlefromurl(ui, repo, url):
2679 def trypullbundlefromurl(ui, repo, url):
2680 """Attempt to apply a bundle from a URL."""
2680 """Attempt to apply a bundle from a URL."""
2681 with repo.lock(), repo.transaction('bundleurl') as tr:
2681 with repo.lock(), repo.transaction('bundleurl') as tr:
2682 try:
2682 try:
2683 fh = urlmod.open(ui, url)
2683 fh = urlmod.open(ui, url)
2684 cg = readbundle(ui, fh, 'stream')
2684 cg = readbundle(ui, fh, 'stream')
2685
2685
2686 if isinstance(cg, streamclone.streamcloneapplier):
2686 if isinstance(cg, streamclone.streamcloneapplier):
2687 cg.apply(repo)
2687 cg.apply(repo)
2688 else:
2688 else:
2689 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)
2689 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)
2690 return True
2690 return True
2691 except urlerr.httperror as e:
2691 except urlerr.httperror as e:
2692 ui.warn(_('HTTP error fetching bundle: %s\n') %
2692 ui.warn(_('HTTP error fetching bundle: %s\n') %
2693 stringutil.forcebytestr(e))
2693 stringutil.forcebytestr(e))
2694 except urlerr.urlerror as e:
2694 except urlerr.urlerror as e:
2695 ui.warn(_('error fetching bundle: %s\n') %
2695 ui.warn(_('error fetching bundle: %s\n') %
2696 stringutil.forcebytestr(e.reason))
2696 stringutil.forcebytestr(e.reason))
2697
2697
2698 return False
2698 return False
General Comments 0
You need to be logged in to leave comments. Login now