##// END OF EJS Templates
localrepo: refactor prepushoutgoinghook to take a pushop...
Mads Kiilerich -
r28876:79b8f052 default
parent child Browse files
Show More
@@ -1,371 +1,372 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 '''setup for largefiles repositories: reposetup'''
9 '''setup for largefiles repositories: reposetup'''
10 import copy
10 import copy
11
11
12 from mercurial import error, match as match_, error
12 from mercurial import error, match as match_, error
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 from mercurial import scmutil, localrepo
14 from mercurial import scmutil, localrepo
15
15
16 import lfcommands
16 import lfcommands
17 import lfutil
17 import lfutil
18
18
19 def reposetup(ui, repo):
19 def reposetup(ui, repo):
20 # wire repositories should be given new wireproto functions
20 # wire repositories should be given new wireproto functions
21 # by "proto.wirereposetup()" via "hg.wirepeersetupfuncs"
21 # by "proto.wirereposetup()" via "hg.wirepeersetupfuncs"
22 if not repo.local():
22 if not repo.local():
23 return
23 return
24
24
25 class lfilesrepo(repo.__class__):
25 class lfilesrepo(repo.__class__):
26 # the mark to examine whether "repo" object enables largefiles or not
26 # the mark to examine whether "repo" object enables largefiles or not
27 _largefilesenabled = True
27 _largefilesenabled = True
28
28
29 lfstatus = False
29 lfstatus = False
30 def status_nolfiles(self, *args, **kwargs):
30 def status_nolfiles(self, *args, **kwargs):
31 return super(lfilesrepo, self).status(*args, **kwargs)
31 return super(lfilesrepo, self).status(*args, **kwargs)
32
32
33 # When lfstatus is set, return a context that gives the names
33 # When lfstatus is set, return a context that gives the names
34 # of largefiles instead of their corresponding standins and
34 # of largefiles instead of their corresponding standins and
35 # identifies the largefiles as always binary, regardless of
35 # identifies the largefiles as always binary, regardless of
36 # their actual contents.
36 # their actual contents.
37 def __getitem__(self, changeid):
37 def __getitem__(self, changeid):
38 ctx = super(lfilesrepo, self).__getitem__(changeid)
38 ctx = super(lfilesrepo, self).__getitem__(changeid)
39 if self.lfstatus:
39 if self.lfstatus:
40 class lfilesctx(ctx.__class__):
40 class lfilesctx(ctx.__class__):
41 def files(self):
41 def files(self):
42 filenames = super(lfilesctx, self).files()
42 filenames = super(lfilesctx, self).files()
43 return [lfutil.splitstandin(f) or f for f in filenames]
43 return [lfutil.splitstandin(f) or f for f in filenames]
44 def manifest(self):
44 def manifest(self):
45 man1 = super(lfilesctx, self).manifest()
45 man1 = super(lfilesctx, self).manifest()
46 class lfilesmanifest(man1.__class__):
46 class lfilesmanifest(man1.__class__):
47 def __contains__(self, filename):
47 def __contains__(self, filename):
48 orig = super(lfilesmanifest, self).__contains__
48 orig = super(lfilesmanifest, self).__contains__
49 return (orig(filename) or
49 return (orig(filename) or
50 orig(lfutil.standin(filename)))
50 orig(lfutil.standin(filename)))
51 man1.__class__ = lfilesmanifest
51 man1.__class__ = lfilesmanifest
52 return man1
52 return man1
53 def filectx(self, path, fileid=None, filelog=None):
53 def filectx(self, path, fileid=None, filelog=None):
54 orig = super(lfilesctx, self).filectx
54 orig = super(lfilesctx, self).filectx
55 try:
55 try:
56 if filelog is not None:
56 if filelog is not None:
57 result = orig(path, fileid, filelog)
57 result = orig(path, fileid, filelog)
58 else:
58 else:
59 result = orig(path, fileid)
59 result = orig(path, fileid)
60 except error.LookupError:
60 except error.LookupError:
61 # Adding a null character will cause Mercurial to
61 # Adding a null character will cause Mercurial to
62 # identify this as a binary file.
62 # identify this as a binary file.
63 if filelog is not None:
63 if filelog is not None:
64 result = orig(lfutil.standin(path), fileid,
64 result = orig(lfutil.standin(path), fileid,
65 filelog)
65 filelog)
66 else:
66 else:
67 result = orig(lfutil.standin(path), fileid)
67 result = orig(lfutil.standin(path), fileid)
68 olddata = result.data
68 olddata = result.data
69 result.data = lambda: olddata() + '\0'
69 result.data = lambda: olddata() + '\0'
70 return result
70 return result
71 ctx.__class__ = lfilesctx
71 ctx.__class__ = lfilesctx
72 return ctx
72 return ctx
73
73
74 # Figure out the status of big files and insert them into the
74 # Figure out the status of big files and insert them into the
75 # appropriate list in the result. Also removes standin files
75 # appropriate list in the result. Also removes standin files
76 # from the listing. Revert to the original status if
76 # from the listing. Revert to the original status if
77 # self.lfstatus is False.
77 # self.lfstatus is False.
78 # XXX large file status is buggy when used on repo proxy.
78 # XXX large file status is buggy when used on repo proxy.
79 # XXX this needs to be investigated.
79 # XXX this needs to be investigated.
80 @localrepo.unfilteredmethod
80 @localrepo.unfilteredmethod
81 def status(self, node1='.', node2=None, match=None, ignored=False,
81 def status(self, node1='.', node2=None, match=None, ignored=False,
82 clean=False, unknown=False, listsubrepos=False):
82 clean=False, unknown=False, listsubrepos=False):
83 listignored, listclean, listunknown = ignored, clean, unknown
83 listignored, listclean, listunknown = ignored, clean, unknown
84 orig = super(lfilesrepo, self).status
84 orig = super(lfilesrepo, self).status
85 if not self.lfstatus:
85 if not self.lfstatus:
86 return orig(node1, node2, match, listignored, listclean,
86 return orig(node1, node2, match, listignored, listclean,
87 listunknown, listsubrepos)
87 listunknown, listsubrepos)
88
88
89 # some calls in this function rely on the old version of status
89 # some calls in this function rely on the old version of status
90 self.lfstatus = False
90 self.lfstatus = False
91 ctx1 = self[node1]
91 ctx1 = self[node1]
92 ctx2 = self[node2]
92 ctx2 = self[node2]
93 working = ctx2.rev() is None
93 working = ctx2.rev() is None
94 parentworking = working and ctx1 == self['.']
94 parentworking = working and ctx1 == self['.']
95
95
96 if match is None:
96 if match is None:
97 match = match_.always(self.root, self.getcwd())
97 match = match_.always(self.root, self.getcwd())
98
98
99 wlock = None
99 wlock = None
100 try:
100 try:
101 try:
101 try:
102 # updating the dirstate is optional
102 # updating the dirstate is optional
103 # so we don't wait on the lock
103 # so we don't wait on the lock
104 wlock = self.wlock(False)
104 wlock = self.wlock(False)
105 except error.LockError:
105 except error.LockError:
106 pass
106 pass
107
107
108 # First check if paths or patterns were specified on the
108 # First check if paths or patterns were specified on the
109 # command line. If there were, and they don't match any
109 # command line. If there were, and they don't match any
110 # largefiles, we should just bail here and let super
110 # largefiles, we should just bail here and let super
111 # handle it -- thus gaining a big performance boost.
111 # handle it -- thus gaining a big performance boost.
112 lfdirstate = lfutil.openlfdirstate(ui, self)
112 lfdirstate = lfutil.openlfdirstate(ui, self)
113 if not match.always():
113 if not match.always():
114 for f in lfdirstate:
114 for f in lfdirstate:
115 if match(f):
115 if match(f):
116 break
116 break
117 else:
117 else:
118 return orig(node1, node2, match, listignored, listclean,
118 return orig(node1, node2, match, listignored, listclean,
119 listunknown, listsubrepos)
119 listunknown, listsubrepos)
120
120
121 # Create a copy of match that matches standins instead
121 # Create a copy of match that matches standins instead
122 # of largefiles.
122 # of largefiles.
123 def tostandins(files):
123 def tostandins(files):
124 if not working:
124 if not working:
125 return files
125 return files
126 newfiles = []
126 newfiles = []
127 dirstate = self.dirstate
127 dirstate = self.dirstate
128 for f in files:
128 for f in files:
129 sf = lfutil.standin(f)
129 sf = lfutil.standin(f)
130 if sf in dirstate:
130 if sf in dirstate:
131 newfiles.append(sf)
131 newfiles.append(sf)
132 elif sf in dirstate.dirs():
132 elif sf in dirstate.dirs():
133 # Directory entries could be regular or
133 # Directory entries could be regular or
134 # standin, check both
134 # standin, check both
135 newfiles.extend((f, sf))
135 newfiles.extend((f, sf))
136 else:
136 else:
137 newfiles.append(f)
137 newfiles.append(f)
138 return newfiles
138 return newfiles
139
139
140 m = copy.copy(match)
140 m = copy.copy(match)
141 m._files = tostandins(m._files)
141 m._files = tostandins(m._files)
142
142
143 result = orig(node1, node2, m, ignored, clean, unknown,
143 result = orig(node1, node2, m, ignored, clean, unknown,
144 listsubrepos)
144 listsubrepos)
145 if working:
145 if working:
146
146
147 def sfindirstate(f):
147 def sfindirstate(f):
148 sf = lfutil.standin(f)
148 sf = lfutil.standin(f)
149 dirstate = self.dirstate
149 dirstate = self.dirstate
150 return sf in dirstate or sf in dirstate.dirs()
150 return sf in dirstate or sf in dirstate.dirs()
151
151
152 match._files = [f for f in match._files
152 match._files = [f for f in match._files
153 if sfindirstate(f)]
153 if sfindirstate(f)]
154 # Don't waste time getting the ignored and unknown
154 # Don't waste time getting the ignored and unknown
155 # files from lfdirstate
155 # files from lfdirstate
156 unsure, s = lfdirstate.status(match, [], False, listclean,
156 unsure, s = lfdirstate.status(match, [], False, listclean,
157 False)
157 False)
158 (modified, added, removed, clean) = (s.modified, s.added,
158 (modified, added, removed, clean) = (s.modified, s.added,
159 s.removed, s.clean)
159 s.removed, s.clean)
160 if parentworking:
160 if parentworking:
161 for lfile in unsure:
161 for lfile in unsure:
162 standin = lfutil.standin(lfile)
162 standin = lfutil.standin(lfile)
163 if standin not in ctx1:
163 if standin not in ctx1:
164 # from second parent
164 # from second parent
165 modified.append(lfile)
165 modified.append(lfile)
166 elif ctx1[standin].data().strip() \
166 elif ctx1[standin].data().strip() \
167 != lfutil.hashfile(self.wjoin(lfile)):
167 != lfutil.hashfile(self.wjoin(lfile)):
168 modified.append(lfile)
168 modified.append(lfile)
169 else:
169 else:
170 if listclean:
170 if listclean:
171 clean.append(lfile)
171 clean.append(lfile)
172 lfdirstate.normal(lfile)
172 lfdirstate.normal(lfile)
173 else:
173 else:
174 tocheck = unsure + modified + added + clean
174 tocheck = unsure + modified + added + clean
175 modified, added, clean = [], [], []
175 modified, added, clean = [], [], []
176 checkexec = self.dirstate._checkexec
176 checkexec = self.dirstate._checkexec
177
177
178 for lfile in tocheck:
178 for lfile in tocheck:
179 standin = lfutil.standin(lfile)
179 standin = lfutil.standin(lfile)
180 if standin in ctx1:
180 if standin in ctx1:
181 abslfile = self.wjoin(lfile)
181 abslfile = self.wjoin(lfile)
182 if ((ctx1[standin].data().strip() !=
182 if ((ctx1[standin].data().strip() !=
183 lfutil.hashfile(abslfile)) or
183 lfutil.hashfile(abslfile)) or
184 (checkexec and
184 (checkexec and
185 ('x' in ctx1.flags(standin)) !=
185 ('x' in ctx1.flags(standin)) !=
186 bool(lfutil.getexecutable(abslfile)))):
186 bool(lfutil.getexecutable(abslfile)))):
187 modified.append(lfile)
187 modified.append(lfile)
188 elif listclean:
188 elif listclean:
189 clean.append(lfile)
189 clean.append(lfile)
190 else:
190 else:
191 added.append(lfile)
191 added.append(lfile)
192
192
193 # at this point, 'removed' contains largefiles
193 # at this point, 'removed' contains largefiles
194 # marked as 'R' in the working context.
194 # marked as 'R' in the working context.
195 # then, largefiles not managed also in the target
195 # then, largefiles not managed also in the target
196 # context should be excluded from 'removed'.
196 # context should be excluded from 'removed'.
197 removed = [lfile for lfile in removed
197 removed = [lfile for lfile in removed
198 if lfutil.standin(lfile) in ctx1]
198 if lfutil.standin(lfile) in ctx1]
199
199
200 # Standins no longer found in lfdirstate has been
200 # Standins no longer found in lfdirstate has been
201 # removed
201 # removed
202 for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
202 for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
203 lfile = lfutil.splitstandin(standin)
203 lfile = lfutil.splitstandin(standin)
204 if not match(lfile):
204 if not match(lfile):
205 continue
205 continue
206 if lfile not in lfdirstate:
206 if lfile not in lfdirstate:
207 removed.append(lfile)
207 removed.append(lfile)
208
208
209 # Filter result lists
209 # Filter result lists
210 result = list(result)
210 result = list(result)
211
211
212 # Largefiles are not really removed when they're
212 # Largefiles are not really removed when they're
213 # still in the normal dirstate. Likewise, normal
213 # still in the normal dirstate. Likewise, normal
214 # files are not really removed if they are still in
214 # files are not really removed if they are still in
215 # lfdirstate. This happens in merges where files
215 # lfdirstate. This happens in merges where files
216 # change type.
216 # change type.
217 removed = [f for f in removed
217 removed = [f for f in removed
218 if f not in self.dirstate]
218 if f not in self.dirstate]
219 result[2] = [f for f in result[2]
219 result[2] = [f for f in result[2]
220 if f not in lfdirstate]
220 if f not in lfdirstate]
221
221
222 lfiles = set(lfdirstate._map)
222 lfiles = set(lfdirstate._map)
223 # Unknown files
223 # Unknown files
224 result[4] = set(result[4]).difference(lfiles)
224 result[4] = set(result[4]).difference(lfiles)
225 # Ignored files
225 # Ignored files
226 result[5] = set(result[5]).difference(lfiles)
226 result[5] = set(result[5]).difference(lfiles)
227 # combine normal files and largefiles
227 # combine normal files and largefiles
228 normals = [[fn for fn in filelist
228 normals = [[fn for fn in filelist
229 if not lfutil.isstandin(fn)]
229 if not lfutil.isstandin(fn)]
230 for filelist in result]
230 for filelist in result]
231 lfstatus = (modified, added, removed, s.deleted, [], [],
231 lfstatus = (modified, added, removed, s.deleted, [], [],
232 clean)
232 clean)
233 result = [sorted(list1 + list2)
233 result = [sorted(list1 + list2)
234 for (list1, list2) in zip(normals, lfstatus)]
234 for (list1, list2) in zip(normals, lfstatus)]
235 else: # not against working directory
235 else: # not against working directory
236 result = [[lfutil.splitstandin(f) or f for f in items]
236 result = [[lfutil.splitstandin(f) or f for f in items]
237 for items in result]
237 for items in result]
238
238
239 if wlock:
239 if wlock:
240 lfdirstate.write()
240 lfdirstate.write()
241
241
242 finally:
242 finally:
243 if wlock:
243 if wlock:
244 wlock.release()
244 wlock.release()
245
245
246 self.lfstatus = True
246 self.lfstatus = True
247 return scmutil.status(*result)
247 return scmutil.status(*result)
248
248
249 def commitctx(self, ctx, *args, **kwargs):
249 def commitctx(self, ctx, *args, **kwargs):
250 node = super(lfilesrepo, self).commitctx(ctx, *args, **kwargs)
250 node = super(lfilesrepo, self).commitctx(ctx, *args, **kwargs)
251 class lfilesctx(ctx.__class__):
251 class lfilesctx(ctx.__class__):
252 def markcommitted(self, node):
252 def markcommitted(self, node):
253 orig = super(lfilesctx, self).markcommitted
253 orig = super(lfilesctx, self).markcommitted
254 return lfutil.markcommitted(orig, self, node)
254 return lfutil.markcommitted(orig, self, node)
255 ctx.__class__ = lfilesctx
255 ctx.__class__ = lfilesctx
256 return node
256 return node
257
257
258 # Before commit, largefile standins have not had their
258 # Before commit, largefile standins have not had their
259 # contents updated to reflect the hash of their largefile.
259 # contents updated to reflect the hash of their largefile.
260 # Do that here.
260 # Do that here.
261 def commit(self, text="", user=None, date=None, match=None,
261 def commit(self, text="", user=None, date=None, match=None,
262 force=False, editor=False, extra={}):
262 force=False, editor=False, extra={}):
263 orig = super(lfilesrepo, self).commit
263 orig = super(lfilesrepo, self).commit
264
264
265 with self.wlock():
265 with self.wlock():
266 lfcommithook = self._lfcommithooks[-1]
266 lfcommithook = self._lfcommithooks[-1]
267 match = lfcommithook(self, match)
267 match = lfcommithook(self, match)
268 result = orig(text=text, user=user, date=date, match=match,
268 result = orig(text=text, user=user, date=date, match=match,
269 force=force, editor=editor, extra=extra)
269 force=force, editor=editor, extra=extra)
270 return result
270 return result
271
271
272 def push(self, remote, force=False, revs=None, newbranch=False):
272 def push(self, remote, force=False, revs=None, newbranch=False):
273 if remote.local():
273 if remote.local():
274 missing = set(self.requirements) - remote.local().supported
274 missing = set(self.requirements) - remote.local().supported
275 if missing:
275 if missing:
276 msg = _("required features are not"
276 msg = _("required features are not"
277 " supported in the destination:"
277 " supported in the destination:"
278 " %s") % (', '.join(sorted(missing)))
278 " %s") % (', '.join(sorted(missing)))
279 raise error.Abort(msg)
279 raise error.Abort(msg)
280 return super(lfilesrepo, self).push(remote, force=force, revs=revs,
280 return super(lfilesrepo, self).push(remote, force=force, revs=revs,
281 newbranch=newbranch)
281 newbranch=newbranch)
282
282
283 # TODO: _subdirlfs should be moved into "lfutil.py", because
283 # TODO: _subdirlfs should be moved into "lfutil.py", because
284 # it is referred only from "lfutil.updatestandinsbymatch"
284 # it is referred only from "lfutil.updatestandinsbymatch"
285 def _subdirlfs(self, files, lfiles):
285 def _subdirlfs(self, files, lfiles):
286 '''
286 '''
287 Adjust matched file list
287 Adjust matched file list
288 If we pass a directory to commit whose only committable files
288 If we pass a directory to commit whose only committable files
289 are largefiles, the core commit code aborts before finding
289 are largefiles, the core commit code aborts before finding
290 the largefiles.
290 the largefiles.
291 So we do the following:
291 So we do the following:
292 For directories that only have largefiles as matches,
292 For directories that only have largefiles as matches,
293 we explicitly add the largefiles to the match list and remove
293 we explicitly add the largefiles to the match list and remove
294 the directory.
294 the directory.
295 In other cases, we leave the match list unmodified.
295 In other cases, we leave the match list unmodified.
296 '''
296 '''
297 actualfiles = []
297 actualfiles = []
298 dirs = []
298 dirs = []
299 regulars = []
299 regulars = []
300
300
301 for f in files:
301 for f in files:
302 if lfutil.isstandin(f + '/'):
302 if lfutil.isstandin(f + '/'):
303 raise error.Abort(
303 raise error.Abort(
304 _('file "%s" is a largefile standin') % f,
304 _('file "%s" is a largefile standin') % f,
305 hint=('commit the largefile itself instead'))
305 hint=('commit the largefile itself instead'))
306 # Scan directories
306 # Scan directories
307 if self.wvfs.isdir(f):
307 if self.wvfs.isdir(f):
308 dirs.append(f)
308 dirs.append(f)
309 else:
309 else:
310 regulars.append(f)
310 regulars.append(f)
311
311
312 for f in dirs:
312 for f in dirs:
313 matcheddir = False
313 matcheddir = False
314 d = self.dirstate.normalize(f) + '/'
314 d = self.dirstate.normalize(f) + '/'
315 # Check for matched normal files
315 # Check for matched normal files
316 for mf in regulars:
316 for mf in regulars:
317 if self.dirstate.normalize(mf).startswith(d):
317 if self.dirstate.normalize(mf).startswith(d):
318 actualfiles.append(f)
318 actualfiles.append(f)
319 matcheddir = True
319 matcheddir = True
320 break
320 break
321 if not matcheddir:
321 if not matcheddir:
322 # If no normal match, manually append
322 # If no normal match, manually append
323 # any matching largefiles
323 # any matching largefiles
324 for lf in lfiles:
324 for lf in lfiles:
325 if self.dirstate.normalize(lf).startswith(d):
325 if self.dirstate.normalize(lf).startswith(d):
326 actualfiles.append(lf)
326 actualfiles.append(lf)
327 if not matcheddir:
327 if not matcheddir:
328 # There may still be normal files in the dir, so
328 # There may still be normal files in the dir, so
329 # add a directory to the list, which
329 # add a directory to the list, which
330 # forces status/dirstate to walk all files and
330 # forces status/dirstate to walk all files and
331 # call the match function on the matcher, even
331 # call the match function on the matcher, even
332 # on case sensitive filesystems.
332 # on case sensitive filesystems.
333 actualfiles.append('.')
333 actualfiles.append('.')
334 matcheddir = True
334 matcheddir = True
335 # Nothing in dir, so readd it
335 # Nothing in dir, so readd it
336 # and let commit reject it
336 # and let commit reject it
337 if not matcheddir:
337 if not matcheddir:
338 actualfiles.append(f)
338 actualfiles.append(f)
339
339
340 # Always add normal files
340 # Always add normal files
341 actualfiles += regulars
341 actualfiles += regulars
342 return actualfiles
342 return actualfiles
343
343
344 repo.__class__ = lfilesrepo
344 repo.__class__ = lfilesrepo
345
345
346 # stack of hooks being executed before committing.
346 # stack of hooks being executed before committing.
347 # only last element ("_lfcommithooks[-1]") is used for each committing.
347 # only last element ("_lfcommithooks[-1]") is used for each committing.
348 repo._lfcommithooks = [lfutil.updatestandinsbymatch]
348 repo._lfcommithooks = [lfutil.updatestandinsbymatch]
349
349
350 # Stack of status writer functions taking "*msg, **opts" arguments
350 # Stack of status writer functions taking "*msg, **opts" arguments
351 # like "ui.status()". Only last element ("_lfstatuswriters[-1]")
351 # like "ui.status()". Only last element ("_lfstatuswriters[-1]")
352 # is used to write status out.
352 # is used to write status out.
353 repo._lfstatuswriters = [ui.status]
353 repo._lfstatuswriters = [ui.status]
354
354
355 def prepushoutgoinghook(local, remote, outgoing):
355 def prepushoutgoinghook(pushop):
356 if outgoing.missing:
356 if pushop.outgoing.missing:
357 toupload = set()
357 toupload = set()
358 addfunc = lambda fn, lfhash: toupload.add(lfhash)
358 addfunc = lambda fn, lfhash: toupload.add(lfhash)
359 lfutil.getlfilestoupload(local, outgoing.missing, addfunc)
359 lfutil.getlfilestoupload(pushop.repo, pushop.outgoing.missing,
360 lfcommands.uploadlfiles(ui, local, remote, toupload)
360 addfunc)
361 lfcommands.uploadlfiles(ui, pushop.repo, pushop.remote, toupload)
361 repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook)
362 repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook)
362
363
363 def checkrequireslfiles(ui, repo, **kwargs):
364 def checkrequireslfiles(ui, repo, **kwargs):
364 if 'largefiles' not in repo.requirements and any(
365 if 'largefiles' not in repo.requirements and any(
365 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
366 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
366 repo.requirements.add('largefiles')
367 repo.requirements.add('largefiles')
367 repo._writerequirements()
368 repo._writerequirements()
368
369
369 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles,
370 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles,
370 'largefiles')
371 'largefiles')
371 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles, 'largefiles')
372 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles, 'largefiles')
@@ -1,1933 +1,1929 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 errno
10 import errno
11 import urllib
11 import urllib
12 import urllib2
12 import urllib2
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 )
18 )
19 from . import (
19 from . import (
20 base85,
20 base85,
21 bookmarks as bookmod,
21 bookmarks as bookmod,
22 bundle2,
22 bundle2,
23 changegroup,
23 changegroup,
24 discovery,
24 discovery,
25 error,
25 error,
26 lock as lockmod,
26 lock as lockmod,
27 obsolete,
27 obsolete,
28 phases,
28 phases,
29 pushkey,
29 pushkey,
30 scmutil,
30 scmutil,
31 sslutil,
31 sslutil,
32 streamclone,
32 streamclone,
33 tags,
33 tags,
34 url as urlmod,
34 url as urlmod,
35 util,
35 util,
36 )
36 )
37
37
38 # Maps bundle compression human names to internal representation.
38 # Maps bundle compression human names to internal representation.
39 _bundlespeccompressions = {'none': None,
39 _bundlespeccompressions = {'none': None,
40 'bzip2': 'BZ',
40 'bzip2': 'BZ',
41 'gzip': 'GZ',
41 'gzip': 'GZ',
42 }
42 }
43
43
44 # Maps bundle version human names to changegroup versions.
44 # Maps bundle version human names to changegroup versions.
45 _bundlespeccgversions = {'v1': '01',
45 _bundlespeccgversions = {'v1': '01',
46 'v2': '02',
46 'v2': '02',
47 'packed1': 's1',
47 'packed1': 's1',
48 'bundle2': '02', #legacy
48 'bundle2': '02', #legacy
49 }
49 }
50
50
51 def parsebundlespec(repo, spec, strict=True, externalnames=False):
51 def parsebundlespec(repo, spec, strict=True, externalnames=False):
52 """Parse a bundle string specification into parts.
52 """Parse a bundle string specification into parts.
53
53
54 Bundle specifications denote a well-defined bundle/exchange format.
54 Bundle specifications denote a well-defined bundle/exchange format.
55 The content of a given specification should not change over time in
55 The content of a given specification should not change over time in
56 order to ensure that bundles produced by a newer version of Mercurial are
56 order to ensure that bundles produced by a newer version of Mercurial are
57 readable from an older version.
57 readable from an older version.
58
58
59 The string currently has the form:
59 The string currently has the form:
60
60
61 <compression>-<type>[;<parameter0>[;<parameter1>]]
61 <compression>-<type>[;<parameter0>[;<parameter1>]]
62
62
63 Where <compression> is one of the supported compression formats
63 Where <compression> is one of the supported compression formats
64 and <type> is (currently) a version string. A ";" can follow the type and
64 and <type> is (currently) a version string. A ";" can follow the type and
65 all text afterwards is interpretted as URI encoded, ";" delimited key=value
65 all text afterwards is interpretted as URI encoded, ";" delimited key=value
66 pairs.
66 pairs.
67
67
68 If ``strict`` is True (the default) <compression> is required. Otherwise,
68 If ``strict`` is True (the default) <compression> is required. Otherwise,
69 it is optional.
69 it is optional.
70
70
71 If ``externalnames`` is False (the default), the human-centric names will
71 If ``externalnames`` is False (the default), the human-centric names will
72 be converted to their internal representation.
72 be converted to their internal representation.
73
73
74 Returns a 3-tuple of (compression, version, parameters). Compression will
74 Returns a 3-tuple of (compression, version, parameters). Compression will
75 be ``None`` if not in strict mode and a compression isn't defined.
75 be ``None`` if not in strict mode and a compression isn't defined.
76
76
77 An ``InvalidBundleSpecification`` is raised when the specification is
77 An ``InvalidBundleSpecification`` is raised when the specification is
78 not syntactically well formed.
78 not syntactically well formed.
79
79
80 An ``UnsupportedBundleSpecification`` is raised when the compression or
80 An ``UnsupportedBundleSpecification`` is raised when the compression or
81 bundle type/version is not recognized.
81 bundle type/version is not recognized.
82
82
83 Note: this function will likely eventually return a more complex data
83 Note: this function will likely eventually return a more complex data
84 structure, including bundle2 part information.
84 structure, including bundle2 part information.
85 """
85 """
86 def parseparams(s):
86 def parseparams(s):
87 if ';' not in s:
87 if ';' not in s:
88 return s, {}
88 return s, {}
89
89
90 params = {}
90 params = {}
91 version, paramstr = s.split(';', 1)
91 version, paramstr = s.split(';', 1)
92
92
93 for p in paramstr.split(';'):
93 for p in paramstr.split(';'):
94 if '=' not in p:
94 if '=' not in p:
95 raise error.InvalidBundleSpecification(
95 raise error.InvalidBundleSpecification(
96 _('invalid bundle specification: '
96 _('invalid bundle specification: '
97 'missing "=" in parameter: %s') % p)
97 'missing "=" in parameter: %s') % p)
98
98
99 key, value = p.split('=', 1)
99 key, value = p.split('=', 1)
100 key = urllib.unquote(key)
100 key = urllib.unquote(key)
101 value = urllib.unquote(value)
101 value = urllib.unquote(value)
102 params[key] = value
102 params[key] = value
103
103
104 return version, params
104 return version, params
105
105
106
106
107 if strict and '-' not in spec:
107 if strict and '-' not in spec:
108 raise error.InvalidBundleSpecification(
108 raise error.InvalidBundleSpecification(
109 _('invalid bundle specification; '
109 _('invalid bundle specification; '
110 'must be prefixed with compression: %s') % spec)
110 'must be prefixed with compression: %s') % spec)
111
111
112 if '-' in spec:
112 if '-' in spec:
113 compression, version = spec.split('-', 1)
113 compression, version = spec.split('-', 1)
114
114
115 if compression not in _bundlespeccompressions:
115 if compression not in _bundlespeccompressions:
116 raise error.UnsupportedBundleSpecification(
116 raise error.UnsupportedBundleSpecification(
117 _('%s compression is not supported') % compression)
117 _('%s compression is not supported') % compression)
118
118
119 version, params = parseparams(version)
119 version, params = parseparams(version)
120
120
121 if version not in _bundlespeccgversions:
121 if version not in _bundlespeccgversions:
122 raise error.UnsupportedBundleSpecification(
122 raise error.UnsupportedBundleSpecification(
123 _('%s is not a recognized bundle version') % version)
123 _('%s is not a recognized bundle version') % version)
124 else:
124 else:
125 # Value could be just the compression or just the version, in which
125 # Value could be just the compression or just the version, in which
126 # case some defaults are assumed (but only when not in strict mode).
126 # case some defaults are assumed (but only when not in strict mode).
127 assert not strict
127 assert not strict
128
128
129 spec, params = parseparams(spec)
129 spec, params = parseparams(spec)
130
130
131 if spec in _bundlespeccompressions:
131 if spec in _bundlespeccompressions:
132 compression = spec
132 compression = spec
133 version = 'v1'
133 version = 'v1'
134 if 'generaldelta' in repo.requirements:
134 if 'generaldelta' in repo.requirements:
135 version = 'v2'
135 version = 'v2'
136 elif spec in _bundlespeccgversions:
136 elif spec in _bundlespeccgversions:
137 if spec == 'packed1':
137 if spec == 'packed1':
138 compression = 'none'
138 compression = 'none'
139 else:
139 else:
140 compression = 'bzip2'
140 compression = 'bzip2'
141 version = spec
141 version = spec
142 else:
142 else:
143 raise error.UnsupportedBundleSpecification(
143 raise error.UnsupportedBundleSpecification(
144 _('%s is not a recognized bundle specification') % spec)
144 _('%s is not a recognized bundle specification') % spec)
145
145
146 # The specification for packed1 can optionally declare the data formats
146 # The specification for packed1 can optionally declare the data formats
147 # required to apply it. If we see this metadata, compare against what the
147 # required to apply it. If we see this metadata, compare against what the
148 # repo supports and error if the bundle isn't compatible.
148 # repo supports and error if the bundle isn't compatible.
149 if version == 'packed1' and 'requirements' in params:
149 if version == 'packed1' and 'requirements' in params:
150 requirements = set(params['requirements'].split(','))
150 requirements = set(params['requirements'].split(','))
151 missingreqs = requirements - repo.supportedformats
151 missingreqs = requirements - repo.supportedformats
152 if missingreqs:
152 if missingreqs:
153 raise error.UnsupportedBundleSpecification(
153 raise error.UnsupportedBundleSpecification(
154 _('missing support for repository features: %s') %
154 _('missing support for repository features: %s') %
155 ', '.join(sorted(missingreqs)))
155 ', '.join(sorted(missingreqs)))
156
156
157 if not externalnames:
157 if not externalnames:
158 compression = _bundlespeccompressions[compression]
158 compression = _bundlespeccompressions[compression]
159 version = _bundlespeccgversions[version]
159 version = _bundlespeccgversions[version]
160 return compression, version, params
160 return compression, version, params
161
161
162 def readbundle(ui, fh, fname, vfs=None):
162 def readbundle(ui, fh, fname, vfs=None):
163 header = changegroup.readexactly(fh, 4)
163 header = changegroup.readexactly(fh, 4)
164
164
165 alg = None
165 alg = None
166 if not fname:
166 if not fname:
167 fname = "stream"
167 fname = "stream"
168 if not header.startswith('HG') and header.startswith('\0'):
168 if not header.startswith('HG') and header.startswith('\0'):
169 fh = changegroup.headerlessfixup(fh, header)
169 fh = changegroup.headerlessfixup(fh, header)
170 header = "HG10"
170 header = "HG10"
171 alg = 'UN'
171 alg = 'UN'
172 elif vfs:
172 elif vfs:
173 fname = vfs.join(fname)
173 fname = vfs.join(fname)
174
174
175 magic, version = header[0:2], header[2:4]
175 magic, version = header[0:2], header[2:4]
176
176
177 if magic != 'HG':
177 if magic != 'HG':
178 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
178 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
179 if version == '10':
179 if version == '10':
180 if alg is None:
180 if alg is None:
181 alg = changegroup.readexactly(fh, 2)
181 alg = changegroup.readexactly(fh, 2)
182 return changegroup.cg1unpacker(fh, alg)
182 return changegroup.cg1unpacker(fh, alg)
183 elif version.startswith('2'):
183 elif version.startswith('2'):
184 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
184 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
185 elif version == 'S1':
185 elif version == 'S1':
186 return streamclone.streamcloneapplier(fh)
186 return streamclone.streamcloneapplier(fh)
187 else:
187 else:
188 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
188 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
189
189
190 def getbundlespec(ui, fh):
190 def getbundlespec(ui, fh):
191 """Infer the bundlespec from a bundle file handle.
191 """Infer the bundlespec from a bundle file handle.
192
192
193 The input file handle is seeked and the original seek position is not
193 The input file handle is seeked and the original seek position is not
194 restored.
194 restored.
195 """
195 """
196 def speccompression(alg):
196 def speccompression(alg):
197 for k, v in _bundlespeccompressions.items():
197 for k, v in _bundlespeccompressions.items():
198 if v == alg:
198 if v == alg:
199 return k
199 return k
200 return None
200 return None
201
201
202 b = readbundle(ui, fh, None)
202 b = readbundle(ui, fh, None)
203 if isinstance(b, changegroup.cg1unpacker):
203 if isinstance(b, changegroup.cg1unpacker):
204 alg = b._type
204 alg = b._type
205 if alg == '_truncatedBZ':
205 if alg == '_truncatedBZ':
206 alg = 'BZ'
206 alg = 'BZ'
207 comp = speccompression(alg)
207 comp = speccompression(alg)
208 if not comp:
208 if not comp:
209 raise error.Abort(_('unknown compression algorithm: %s') % alg)
209 raise error.Abort(_('unknown compression algorithm: %s') % alg)
210 return '%s-v1' % comp
210 return '%s-v1' % comp
211 elif isinstance(b, bundle2.unbundle20):
211 elif isinstance(b, bundle2.unbundle20):
212 if 'Compression' in b.params:
212 if 'Compression' in b.params:
213 comp = speccompression(b.params['Compression'])
213 comp = speccompression(b.params['Compression'])
214 if not comp:
214 if not comp:
215 raise error.Abort(_('unknown compression algorithm: %s') % comp)
215 raise error.Abort(_('unknown compression algorithm: %s') % comp)
216 else:
216 else:
217 comp = 'none'
217 comp = 'none'
218
218
219 version = None
219 version = None
220 for part in b.iterparts():
220 for part in b.iterparts():
221 if part.type == 'changegroup':
221 if part.type == 'changegroup':
222 version = part.params['version']
222 version = part.params['version']
223 if version in ('01', '02'):
223 if version in ('01', '02'):
224 version = 'v2'
224 version = 'v2'
225 else:
225 else:
226 raise error.Abort(_('changegroup version %s does not have '
226 raise error.Abort(_('changegroup version %s does not have '
227 'a known bundlespec') % version,
227 'a known bundlespec') % version,
228 hint=_('try upgrading your Mercurial '
228 hint=_('try upgrading your Mercurial '
229 'client'))
229 'client'))
230
230
231 if not version:
231 if not version:
232 raise error.Abort(_('could not identify changegroup version in '
232 raise error.Abort(_('could not identify changegroup version in '
233 'bundle'))
233 'bundle'))
234
234
235 return '%s-%s' % (comp, version)
235 return '%s-%s' % (comp, version)
236 elif isinstance(b, streamclone.streamcloneapplier):
236 elif isinstance(b, streamclone.streamcloneapplier):
237 requirements = streamclone.readbundle1header(fh)[2]
237 requirements = streamclone.readbundle1header(fh)[2]
238 params = 'requirements=%s' % ','.join(sorted(requirements))
238 params = 'requirements=%s' % ','.join(sorted(requirements))
239 return 'none-packed1;%s' % urllib.quote(params)
239 return 'none-packed1;%s' % urllib.quote(params)
240 else:
240 else:
241 raise error.Abort(_('unknown bundle type: %s') % b)
241 raise error.Abort(_('unknown bundle type: %s') % b)
242
242
243 def buildobsmarkerspart(bundler, markers):
243 def buildobsmarkerspart(bundler, markers):
244 """add an obsmarker part to the bundler with <markers>
244 """add an obsmarker part to the bundler with <markers>
245
245
246 No part is created if markers is empty.
246 No part is created if markers is empty.
247 Raises ValueError if the bundler doesn't support any known obsmarker format.
247 Raises ValueError if the bundler doesn't support any known obsmarker format.
248 """
248 """
249 if markers:
249 if markers:
250 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
250 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
251 version = obsolete.commonversion(remoteversions)
251 version = obsolete.commonversion(remoteversions)
252 if version is None:
252 if version is None:
253 raise ValueError('bundler does not support common obsmarker format')
253 raise ValueError('bundler does not support common obsmarker format')
254 stream = obsolete.encodemarkers(markers, True, version=version)
254 stream = obsolete.encodemarkers(markers, True, version=version)
255 return bundler.newpart('obsmarkers', data=stream)
255 return bundler.newpart('obsmarkers', data=stream)
256 return None
256 return None
257
257
258 def _canusebundle2(op):
258 def _canusebundle2(op):
259 """return true if a pull/push can use bundle2
259 """return true if a pull/push can use bundle2
260
260
261 Feel free to nuke this function when we drop the experimental option"""
261 Feel free to nuke this function when we drop the experimental option"""
262 return (op.repo.ui.configbool('experimental', 'bundle2-exp', True)
262 return (op.repo.ui.configbool('experimental', 'bundle2-exp', True)
263 and op.remote.capable('bundle2'))
263 and op.remote.capable('bundle2'))
264
264
265
265
266 class pushoperation(object):
266 class pushoperation(object):
267 """A object that represent a single push operation
267 """A object that represent a single push operation
268
268
269 Its purpose is to carry push related state and very common operations.
269 Its purpose is to carry push related state and very common operations.
270
270
271 A new pushoperation should be created at the beginning of each push and
271 A new pushoperation should be created at the beginning of each push and
272 discarded afterward.
272 discarded afterward.
273 """
273 """
274
274
275 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
275 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
276 bookmarks=()):
276 bookmarks=()):
277 # repo we push from
277 # repo we push from
278 self.repo = repo
278 self.repo = repo
279 self.ui = repo.ui
279 self.ui = repo.ui
280 # repo we push to
280 # repo we push to
281 self.remote = remote
281 self.remote = remote
282 # force option provided
282 # force option provided
283 self.force = force
283 self.force = force
284 # revs to be pushed (None is "all")
284 # revs to be pushed (None is "all")
285 self.revs = revs
285 self.revs = revs
286 # bookmark explicitly pushed
286 # bookmark explicitly pushed
287 self.bookmarks = bookmarks
287 self.bookmarks = bookmarks
288 # allow push of new branch
288 # allow push of new branch
289 self.newbranch = newbranch
289 self.newbranch = newbranch
290 # did a local lock get acquired?
290 # did a local lock get acquired?
291 self.locallocked = None
291 self.locallocked = None
292 # step already performed
292 # step already performed
293 # (used to check what steps have been already performed through bundle2)
293 # (used to check what steps have been already performed through bundle2)
294 self.stepsdone = set()
294 self.stepsdone = set()
295 # Integer version of the changegroup push result
295 # Integer version of the changegroup push result
296 # - None means nothing to push
296 # - None means nothing to push
297 # - 0 means HTTP error
297 # - 0 means HTTP error
298 # - 1 means we pushed and remote head count is unchanged *or*
298 # - 1 means we pushed and remote head count is unchanged *or*
299 # we have outgoing changesets but refused to push
299 # we have outgoing changesets but refused to push
300 # - other values as described by addchangegroup()
300 # - other values as described by addchangegroup()
301 self.cgresult = None
301 self.cgresult = None
302 # Boolean value for the bookmark push
302 # Boolean value for the bookmark push
303 self.bkresult = None
303 self.bkresult = None
304 # discover.outgoing object (contains common and outgoing data)
304 # discover.outgoing object (contains common and outgoing data)
305 self.outgoing = None
305 self.outgoing = None
306 # all remote heads before the push
306 # all remote heads before the push
307 self.remoteheads = None
307 self.remoteheads = None
308 # testable as a boolean indicating if any nodes are missing locally.
308 # testable as a boolean indicating if any nodes are missing locally.
309 self.incoming = None
309 self.incoming = None
310 # phases changes that must be pushed along side the changesets
310 # phases changes that must be pushed along side the changesets
311 self.outdatedphases = None
311 self.outdatedphases = None
312 # phases changes that must be pushed if changeset push fails
312 # phases changes that must be pushed if changeset push fails
313 self.fallbackoutdatedphases = None
313 self.fallbackoutdatedphases = None
314 # outgoing obsmarkers
314 # outgoing obsmarkers
315 self.outobsmarkers = set()
315 self.outobsmarkers = set()
316 # outgoing bookmarks
316 # outgoing bookmarks
317 self.outbookmarks = []
317 self.outbookmarks = []
318 # transaction manager
318 # transaction manager
319 self.trmanager = None
319 self.trmanager = None
320 # map { pushkey partid -> callback handling failure}
320 # map { pushkey partid -> callback handling failure}
321 # used to handle exception from mandatory pushkey part failure
321 # used to handle exception from mandatory pushkey part failure
322 self.pkfailcb = {}
322 self.pkfailcb = {}
323
323
324 @util.propertycache
324 @util.propertycache
325 def futureheads(self):
325 def futureheads(self):
326 """future remote heads if the changeset push succeeds"""
326 """future remote heads if the changeset push succeeds"""
327 return self.outgoing.missingheads
327 return self.outgoing.missingheads
328
328
329 @util.propertycache
329 @util.propertycache
330 def fallbackheads(self):
330 def fallbackheads(self):
331 """future remote heads if the changeset push fails"""
331 """future remote heads if the changeset push fails"""
332 if self.revs is None:
332 if self.revs is None:
333 # not target to push, all common are relevant
333 # not target to push, all common are relevant
334 return self.outgoing.commonheads
334 return self.outgoing.commonheads
335 unfi = self.repo.unfiltered()
335 unfi = self.repo.unfiltered()
336 # I want cheads = heads(::missingheads and ::commonheads)
336 # I want cheads = heads(::missingheads and ::commonheads)
337 # (missingheads is revs with secret changeset filtered out)
337 # (missingheads is revs with secret changeset filtered out)
338 #
338 #
339 # This can be expressed as:
339 # This can be expressed as:
340 # cheads = ( (missingheads and ::commonheads)
340 # cheads = ( (missingheads and ::commonheads)
341 # + (commonheads and ::missingheads))"
341 # + (commonheads and ::missingheads))"
342 # )
342 # )
343 #
343 #
344 # while trying to push we already computed the following:
344 # while trying to push we already computed the following:
345 # common = (::commonheads)
345 # common = (::commonheads)
346 # missing = ((commonheads::missingheads) - commonheads)
346 # missing = ((commonheads::missingheads) - commonheads)
347 #
347 #
348 # We can pick:
348 # We can pick:
349 # * missingheads part of common (::commonheads)
349 # * missingheads part of common (::commonheads)
350 common = self.outgoing.common
350 common = self.outgoing.common
351 nm = self.repo.changelog.nodemap
351 nm = self.repo.changelog.nodemap
352 cheads = [node for node in self.revs if nm[node] in common]
352 cheads = [node for node in self.revs if nm[node] in common]
353 # and
353 # and
354 # * commonheads parents on missing
354 # * commonheads parents on missing
355 revset = unfi.set('%ln and parents(roots(%ln))',
355 revset = unfi.set('%ln and parents(roots(%ln))',
356 self.outgoing.commonheads,
356 self.outgoing.commonheads,
357 self.outgoing.missing)
357 self.outgoing.missing)
358 cheads.extend(c.node() for c in revset)
358 cheads.extend(c.node() for c in revset)
359 return cheads
359 return cheads
360
360
361 @property
361 @property
362 def commonheads(self):
362 def commonheads(self):
363 """set of all common heads after changeset bundle push"""
363 """set of all common heads after changeset bundle push"""
364 if self.cgresult:
364 if self.cgresult:
365 return self.futureheads
365 return self.futureheads
366 else:
366 else:
367 return self.fallbackheads
367 return self.fallbackheads
368
368
369 # mapping of message used when pushing bookmark
369 # mapping of message used when pushing bookmark
370 bookmsgmap = {'update': (_("updating bookmark %s\n"),
370 bookmsgmap = {'update': (_("updating bookmark %s\n"),
371 _('updating bookmark %s failed!\n')),
371 _('updating bookmark %s failed!\n')),
372 'export': (_("exporting bookmark %s\n"),
372 'export': (_("exporting bookmark %s\n"),
373 _('exporting bookmark %s failed!\n')),
373 _('exporting bookmark %s failed!\n')),
374 'delete': (_("deleting remote bookmark %s\n"),
374 'delete': (_("deleting remote bookmark %s\n"),
375 _('deleting remote bookmark %s failed!\n')),
375 _('deleting remote bookmark %s failed!\n')),
376 }
376 }
377
377
378
378
379 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
379 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
380 opargs=None):
380 opargs=None):
381 '''Push outgoing changesets (limited by revs) from a local
381 '''Push outgoing changesets (limited by revs) from a local
382 repository to remote. Return an integer:
382 repository to remote. Return an integer:
383 - None means nothing to push
383 - None means nothing to push
384 - 0 means HTTP error
384 - 0 means HTTP error
385 - 1 means we pushed and remote head count is unchanged *or*
385 - 1 means we pushed and remote head count is unchanged *or*
386 we have outgoing changesets but refused to push
386 we have outgoing changesets but refused to push
387 - other values as described by addchangegroup()
387 - other values as described by addchangegroup()
388 '''
388 '''
389 if opargs is None:
389 if opargs is None:
390 opargs = {}
390 opargs = {}
391 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
391 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
392 **opargs)
392 **opargs)
393 if pushop.remote.local():
393 if pushop.remote.local():
394 missing = (set(pushop.repo.requirements)
394 missing = (set(pushop.repo.requirements)
395 - pushop.remote.local().supported)
395 - pushop.remote.local().supported)
396 if missing:
396 if missing:
397 msg = _("required features are not"
397 msg = _("required features are not"
398 " supported in the destination:"
398 " supported in the destination:"
399 " %s") % (', '.join(sorted(missing)))
399 " %s") % (', '.join(sorted(missing)))
400 raise error.Abort(msg)
400 raise error.Abort(msg)
401
401
402 # there are two ways to push to remote repo:
402 # there are two ways to push to remote repo:
403 #
403 #
404 # addchangegroup assumes local user can lock remote
404 # addchangegroup assumes local user can lock remote
405 # repo (local filesystem, old ssh servers).
405 # repo (local filesystem, old ssh servers).
406 #
406 #
407 # unbundle assumes local user cannot lock remote repo (new ssh
407 # unbundle assumes local user cannot lock remote repo (new ssh
408 # servers, http servers).
408 # servers, http servers).
409
409
410 if not pushop.remote.canpush():
410 if not pushop.remote.canpush():
411 raise error.Abort(_("destination does not support push"))
411 raise error.Abort(_("destination does not support push"))
412 # get local lock as we might write phase data
412 # get local lock as we might write phase data
413 localwlock = locallock = None
413 localwlock = locallock = None
414 try:
414 try:
415 # bundle2 push may receive a reply bundle touching bookmarks or other
415 # bundle2 push may receive a reply bundle touching bookmarks or other
416 # things requiring the wlock. Take it now to ensure proper ordering.
416 # things requiring the wlock. Take it now to ensure proper ordering.
417 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
417 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
418 if _canusebundle2(pushop) and maypushback:
418 if _canusebundle2(pushop) and maypushback:
419 localwlock = pushop.repo.wlock()
419 localwlock = pushop.repo.wlock()
420 locallock = pushop.repo.lock()
420 locallock = pushop.repo.lock()
421 pushop.locallocked = True
421 pushop.locallocked = True
422 except IOError as err:
422 except IOError as err:
423 pushop.locallocked = False
423 pushop.locallocked = False
424 if err.errno != errno.EACCES:
424 if err.errno != errno.EACCES:
425 raise
425 raise
426 # source repo cannot be locked.
426 # source repo cannot be locked.
427 # We do not abort the push, but just disable the local phase
427 # We do not abort the push, but just disable the local phase
428 # synchronisation.
428 # synchronisation.
429 msg = 'cannot lock source repository: %s\n' % err
429 msg = 'cannot lock source repository: %s\n' % err
430 pushop.ui.debug(msg)
430 pushop.ui.debug(msg)
431 try:
431 try:
432 if pushop.locallocked:
432 if pushop.locallocked:
433 pushop.trmanager = transactionmanager(pushop.repo,
433 pushop.trmanager = transactionmanager(pushop.repo,
434 'push-response',
434 'push-response',
435 pushop.remote.url())
435 pushop.remote.url())
436 pushop.repo.checkpush(pushop)
436 pushop.repo.checkpush(pushop)
437 lock = None
437 lock = None
438 unbundle = pushop.remote.capable('unbundle')
438 unbundle = pushop.remote.capable('unbundle')
439 if not unbundle:
439 if not unbundle:
440 lock = pushop.remote.lock()
440 lock = pushop.remote.lock()
441 try:
441 try:
442 _pushdiscovery(pushop)
442 _pushdiscovery(pushop)
443 if _canusebundle2(pushop):
443 if _canusebundle2(pushop):
444 _pushbundle2(pushop)
444 _pushbundle2(pushop)
445 _pushchangeset(pushop)
445 _pushchangeset(pushop)
446 _pushsyncphase(pushop)
446 _pushsyncphase(pushop)
447 _pushobsolete(pushop)
447 _pushobsolete(pushop)
448 _pushbookmark(pushop)
448 _pushbookmark(pushop)
449 finally:
449 finally:
450 if lock is not None:
450 if lock is not None:
451 lock.release()
451 lock.release()
452 if pushop.trmanager:
452 if pushop.trmanager:
453 pushop.trmanager.close()
453 pushop.trmanager.close()
454 finally:
454 finally:
455 if pushop.trmanager:
455 if pushop.trmanager:
456 pushop.trmanager.release()
456 pushop.trmanager.release()
457 if locallock is not None:
457 if locallock is not None:
458 locallock.release()
458 locallock.release()
459 if localwlock is not None:
459 if localwlock is not None:
460 localwlock.release()
460 localwlock.release()
461
461
462 return pushop
462 return pushop
463
463
464 # list of steps to perform discovery before push
464 # list of steps to perform discovery before push
465 pushdiscoveryorder = []
465 pushdiscoveryorder = []
466
466
467 # Mapping between step name and function
467 # Mapping between step name and function
468 #
468 #
469 # This exists to help extensions wrap steps if necessary
469 # This exists to help extensions wrap steps if necessary
470 pushdiscoverymapping = {}
470 pushdiscoverymapping = {}
471
471
472 def pushdiscovery(stepname):
472 def pushdiscovery(stepname):
473 """decorator for function performing discovery before push
473 """decorator for function performing discovery before push
474
474
475 The function is added to the step -> function mapping and appended to the
475 The function is added to the step -> function mapping and appended to the
476 list of steps. Beware that decorated function will be added in order (this
476 list of steps. Beware that decorated function will be added in order (this
477 may matter).
477 may matter).
478
478
479 You can only use this decorator for a new step, if you want to wrap a step
479 You can only use this decorator for a new step, if you want to wrap a step
480 from an extension, change the pushdiscovery dictionary directly."""
480 from an extension, change the pushdiscovery dictionary directly."""
481 def dec(func):
481 def dec(func):
482 assert stepname not in pushdiscoverymapping
482 assert stepname not in pushdiscoverymapping
483 pushdiscoverymapping[stepname] = func
483 pushdiscoverymapping[stepname] = func
484 pushdiscoveryorder.append(stepname)
484 pushdiscoveryorder.append(stepname)
485 return func
485 return func
486 return dec
486 return dec
487
487
488 def _pushdiscovery(pushop):
488 def _pushdiscovery(pushop):
489 """Run all discovery steps"""
489 """Run all discovery steps"""
490 for stepname in pushdiscoveryorder:
490 for stepname in pushdiscoveryorder:
491 step = pushdiscoverymapping[stepname]
491 step = pushdiscoverymapping[stepname]
492 step(pushop)
492 step(pushop)
493
493
494 @pushdiscovery('changeset')
494 @pushdiscovery('changeset')
495 def _pushdiscoverychangeset(pushop):
495 def _pushdiscoverychangeset(pushop):
496 """discover the changeset that need to be pushed"""
496 """discover the changeset that need to be pushed"""
497 fci = discovery.findcommonincoming
497 fci = discovery.findcommonincoming
498 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
498 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
499 common, inc, remoteheads = commoninc
499 common, inc, remoteheads = commoninc
500 fco = discovery.findcommonoutgoing
500 fco = discovery.findcommonoutgoing
501 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
501 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
502 commoninc=commoninc, force=pushop.force)
502 commoninc=commoninc, force=pushop.force)
503 pushop.outgoing = outgoing
503 pushop.outgoing = outgoing
504 pushop.remoteheads = remoteheads
504 pushop.remoteheads = remoteheads
505 pushop.incoming = inc
505 pushop.incoming = inc
506
506
507 @pushdiscovery('phase')
507 @pushdiscovery('phase')
508 def _pushdiscoveryphase(pushop):
508 def _pushdiscoveryphase(pushop):
509 """discover the phase that needs to be pushed
509 """discover the phase that needs to be pushed
510
510
511 (computed for both success and failure case for changesets push)"""
511 (computed for both success and failure case for changesets push)"""
512 outgoing = pushop.outgoing
512 outgoing = pushop.outgoing
513 unfi = pushop.repo.unfiltered()
513 unfi = pushop.repo.unfiltered()
514 remotephases = pushop.remote.listkeys('phases')
514 remotephases = pushop.remote.listkeys('phases')
515 publishing = remotephases.get('publishing', False)
515 publishing = remotephases.get('publishing', False)
516 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
516 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
517 and remotephases # server supports phases
517 and remotephases # server supports phases
518 and not pushop.outgoing.missing # no changesets to be pushed
518 and not pushop.outgoing.missing # no changesets to be pushed
519 and publishing):
519 and publishing):
520 # When:
520 # When:
521 # - this is a subrepo push
521 # - this is a subrepo push
522 # - and remote support phase
522 # - and remote support phase
523 # - and no changeset are to be pushed
523 # - and no changeset are to be pushed
524 # - and remote is publishing
524 # - and remote is publishing
525 # We may be in issue 3871 case!
525 # We may be in issue 3871 case!
526 # We drop the possible phase synchronisation done by
526 # We drop the possible phase synchronisation done by
527 # courtesy to publish changesets possibly locally draft
527 # courtesy to publish changesets possibly locally draft
528 # on the remote.
528 # on the remote.
529 remotephases = {'publishing': 'True'}
529 remotephases = {'publishing': 'True'}
530 ana = phases.analyzeremotephases(pushop.repo,
530 ana = phases.analyzeremotephases(pushop.repo,
531 pushop.fallbackheads,
531 pushop.fallbackheads,
532 remotephases)
532 remotephases)
533 pheads, droots = ana
533 pheads, droots = ana
534 extracond = ''
534 extracond = ''
535 if not publishing:
535 if not publishing:
536 extracond = ' and public()'
536 extracond = ' and public()'
537 revset = 'heads((%%ln::%%ln) %s)' % extracond
537 revset = 'heads((%%ln::%%ln) %s)' % extracond
538 # Get the list of all revs draft on remote by public here.
538 # Get the list of all revs draft on remote by public here.
539 # XXX Beware that revset break if droots is not strictly
539 # XXX Beware that revset break if droots is not strictly
540 # XXX root we may want to ensure it is but it is costly
540 # XXX root we may want to ensure it is but it is costly
541 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
541 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
542 if not outgoing.missing:
542 if not outgoing.missing:
543 future = fallback
543 future = fallback
544 else:
544 else:
545 # adds changeset we are going to push as draft
545 # adds changeset we are going to push as draft
546 #
546 #
547 # should not be necessary for publishing server, but because of an
547 # should not be necessary for publishing server, but because of an
548 # issue fixed in xxxxx we have to do it anyway.
548 # issue fixed in xxxxx we have to do it anyway.
549 fdroots = list(unfi.set('roots(%ln + %ln::)',
549 fdroots = list(unfi.set('roots(%ln + %ln::)',
550 outgoing.missing, droots))
550 outgoing.missing, droots))
551 fdroots = [f.node() for f in fdroots]
551 fdroots = [f.node() for f in fdroots]
552 future = list(unfi.set(revset, fdroots, pushop.futureheads))
552 future = list(unfi.set(revset, fdroots, pushop.futureheads))
553 pushop.outdatedphases = future
553 pushop.outdatedphases = future
554 pushop.fallbackoutdatedphases = fallback
554 pushop.fallbackoutdatedphases = fallback
555
555
556 @pushdiscovery('obsmarker')
556 @pushdiscovery('obsmarker')
557 def _pushdiscoveryobsmarkers(pushop):
557 def _pushdiscoveryobsmarkers(pushop):
558 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
558 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
559 and pushop.repo.obsstore
559 and pushop.repo.obsstore
560 and 'obsolete' in pushop.remote.listkeys('namespaces')):
560 and 'obsolete' in pushop.remote.listkeys('namespaces')):
561 repo = pushop.repo
561 repo = pushop.repo
562 # very naive computation, that can be quite expensive on big repo.
562 # very naive computation, that can be quite expensive on big repo.
563 # However: evolution is currently slow on them anyway.
563 # However: evolution is currently slow on them anyway.
564 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
564 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
565 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
565 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
566
566
567 @pushdiscovery('bookmarks')
567 @pushdiscovery('bookmarks')
568 def _pushdiscoverybookmarks(pushop):
568 def _pushdiscoverybookmarks(pushop):
569 ui = pushop.ui
569 ui = pushop.ui
570 repo = pushop.repo.unfiltered()
570 repo = pushop.repo.unfiltered()
571 remote = pushop.remote
571 remote = pushop.remote
572 ui.debug("checking for updated bookmarks\n")
572 ui.debug("checking for updated bookmarks\n")
573 ancestors = ()
573 ancestors = ()
574 if pushop.revs:
574 if pushop.revs:
575 revnums = map(repo.changelog.rev, pushop.revs)
575 revnums = map(repo.changelog.rev, pushop.revs)
576 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
576 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
577 remotebookmark = remote.listkeys('bookmarks')
577 remotebookmark = remote.listkeys('bookmarks')
578
578
579 explicit = set([repo._bookmarks.expandname(bookmark)
579 explicit = set([repo._bookmarks.expandname(bookmark)
580 for bookmark in pushop.bookmarks])
580 for bookmark in pushop.bookmarks])
581
581
582 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
582 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
583 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
583 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
584 for b, scid, dcid in advsrc:
584 for b, scid, dcid in advsrc:
585 if b in explicit:
585 if b in explicit:
586 explicit.remove(b)
586 explicit.remove(b)
587 if not ancestors or repo[scid].rev() in ancestors:
587 if not ancestors or repo[scid].rev() in ancestors:
588 pushop.outbookmarks.append((b, dcid, scid))
588 pushop.outbookmarks.append((b, dcid, scid))
589 # search added bookmark
589 # search added bookmark
590 for b, scid, dcid in addsrc:
590 for b, scid, dcid in addsrc:
591 if b in explicit:
591 if b in explicit:
592 explicit.remove(b)
592 explicit.remove(b)
593 pushop.outbookmarks.append((b, '', scid))
593 pushop.outbookmarks.append((b, '', scid))
594 # search for overwritten bookmark
594 # search for overwritten bookmark
595 for b, scid, dcid in advdst + diverge + differ:
595 for b, scid, dcid in advdst + diverge + differ:
596 if b in explicit:
596 if b in explicit:
597 explicit.remove(b)
597 explicit.remove(b)
598 pushop.outbookmarks.append((b, dcid, scid))
598 pushop.outbookmarks.append((b, dcid, scid))
599 # search for bookmark to delete
599 # search for bookmark to delete
600 for b, scid, dcid in adddst:
600 for b, scid, dcid in adddst:
601 if b in explicit:
601 if b in explicit:
602 explicit.remove(b)
602 explicit.remove(b)
603 # treat as "deleted locally"
603 # treat as "deleted locally"
604 pushop.outbookmarks.append((b, dcid, ''))
604 pushop.outbookmarks.append((b, dcid, ''))
605 # identical bookmarks shouldn't get reported
605 # identical bookmarks shouldn't get reported
606 for b, scid, dcid in same:
606 for b, scid, dcid in same:
607 if b in explicit:
607 if b in explicit:
608 explicit.remove(b)
608 explicit.remove(b)
609
609
610 if explicit:
610 if explicit:
611 explicit = sorted(explicit)
611 explicit = sorted(explicit)
612 # we should probably list all of them
612 # we should probably list all of them
613 ui.warn(_('bookmark %s does not exist on the local '
613 ui.warn(_('bookmark %s does not exist on the local '
614 'or remote repository!\n') % explicit[0])
614 'or remote repository!\n') % explicit[0])
615 pushop.bkresult = 2
615 pushop.bkresult = 2
616
616
617 pushop.outbookmarks.sort()
617 pushop.outbookmarks.sort()
618
618
619 def _pushcheckoutgoing(pushop):
619 def _pushcheckoutgoing(pushop):
620 outgoing = pushop.outgoing
620 outgoing = pushop.outgoing
621 unfi = pushop.repo.unfiltered()
621 unfi = pushop.repo.unfiltered()
622 if not outgoing.missing:
622 if not outgoing.missing:
623 # nothing to push
623 # nothing to push
624 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
624 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
625 return False
625 return False
626 # something to push
626 # something to push
627 if not pushop.force:
627 if not pushop.force:
628 # if repo.obsstore == False --> no obsolete
628 # if repo.obsstore == False --> no obsolete
629 # then, save the iteration
629 # then, save the iteration
630 if unfi.obsstore:
630 if unfi.obsstore:
631 # this message are here for 80 char limit reason
631 # this message are here for 80 char limit reason
632 mso = _("push includes obsolete changeset: %s!")
632 mso = _("push includes obsolete changeset: %s!")
633 mst = {"unstable": _("push includes unstable changeset: %s!"),
633 mst = {"unstable": _("push includes unstable changeset: %s!"),
634 "bumped": _("push includes bumped changeset: %s!"),
634 "bumped": _("push includes bumped changeset: %s!"),
635 "divergent": _("push includes divergent changeset: %s!")}
635 "divergent": _("push includes divergent changeset: %s!")}
636 # If we are to push if there is at least one
636 # If we are to push if there is at least one
637 # obsolete or unstable changeset in missing, at
637 # obsolete or unstable changeset in missing, at
638 # least one of the missinghead will be obsolete or
638 # least one of the missinghead will be obsolete or
639 # unstable. So checking heads only is ok
639 # unstable. So checking heads only is ok
640 for node in outgoing.missingheads:
640 for node in outgoing.missingheads:
641 ctx = unfi[node]
641 ctx = unfi[node]
642 if ctx.obsolete():
642 if ctx.obsolete():
643 raise error.Abort(mso % ctx)
643 raise error.Abort(mso % ctx)
644 elif ctx.troubled():
644 elif ctx.troubled():
645 raise error.Abort(mst[ctx.troubles()[0]] % ctx)
645 raise error.Abort(mst[ctx.troubles()[0]] % ctx)
646
646
647 discovery.checkheads(pushop)
647 discovery.checkheads(pushop)
648 return True
648 return True
649
649
650 # List of names of steps to perform for an outgoing bundle2, order matters.
650 # List of names of steps to perform for an outgoing bundle2, order matters.
651 b2partsgenorder = []
651 b2partsgenorder = []
652
652
653 # Mapping between step name and function
653 # Mapping between step name and function
654 #
654 #
655 # This exists to help extensions wrap steps if necessary
655 # This exists to help extensions wrap steps if necessary
656 b2partsgenmapping = {}
656 b2partsgenmapping = {}
657
657
658 def b2partsgenerator(stepname, idx=None):
658 def b2partsgenerator(stepname, idx=None):
659 """decorator for function generating bundle2 part
659 """decorator for function generating bundle2 part
660
660
661 The function is added to the step -> function mapping and appended to the
661 The function is added to the step -> function mapping and appended to the
662 list of steps. Beware that decorated functions will be added in order
662 list of steps. Beware that decorated functions will be added in order
663 (this may matter).
663 (this may matter).
664
664
665 You can only use this decorator for new steps, if you want to wrap a step
665 You can only use this decorator for new steps, if you want to wrap a step
666 from an extension, attack the b2partsgenmapping dictionary directly."""
666 from an extension, attack the b2partsgenmapping dictionary directly."""
667 def dec(func):
667 def dec(func):
668 assert stepname not in b2partsgenmapping
668 assert stepname not in b2partsgenmapping
669 b2partsgenmapping[stepname] = func
669 b2partsgenmapping[stepname] = func
670 if idx is None:
670 if idx is None:
671 b2partsgenorder.append(stepname)
671 b2partsgenorder.append(stepname)
672 else:
672 else:
673 b2partsgenorder.insert(idx, stepname)
673 b2partsgenorder.insert(idx, stepname)
674 return func
674 return func
675 return dec
675 return dec
676
676
677 def _pushb2ctxcheckheads(pushop, bundler):
677 def _pushb2ctxcheckheads(pushop, bundler):
678 """Generate race condition checking parts
678 """Generate race condition checking parts
679
679
680 Exists as an independent function to aid extensions
680 Exists as an independent function to aid extensions
681 """
681 """
682 if not pushop.force:
682 if not pushop.force:
683 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
683 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
684
684
685 @b2partsgenerator('changeset')
685 @b2partsgenerator('changeset')
686 def _pushb2ctx(pushop, bundler):
686 def _pushb2ctx(pushop, bundler):
687 """handle changegroup push through bundle2
687 """handle changegroup push through bundle2
688
688
689 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
689 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
690 """
690 """
691 if 'changesets' in pushop.stepsdone:
691 if 'changesets' in pushop.stepsdone:
692 return
692 return
693 pushop.stepsdone.add('changesets')
693 pushop.stepsdone.add('changesets')
694 # Send known heads to the server for race detection.
694 # Send known heads to the server for race detection.
695 if not _pushcheckoutgoing(pushop):
695 if not _pushcheckoutgoing(pushop):
696 return
696 return
697 pushop.repo.prepushoutgoinghooks(pushop.repo,
697 pushop.repo.prepushoutgoinghooks(pushop)
698 pushop.remote,
699 pushop.outgoing)
700
698
701 _pushb2ctxcheckheads(pushop, bundler)
699 _pushb2ctxcheckheads(pushop, bundler)
702
700
703 b2caps = bundle2.bundle2caps(pushop.remote)
701 b2caps = bundle2.bundle2caps(pushop.remote)
704 version = '01'
702 version = '01'
705 cgversions = b2caps.get('changegroup')
703 cgversions = b2caps.get('changegroup')
706 if cgversions: # 3.1 and 3.2 ship with an empty value
704 if cgversions: # 3.1 and 3.2 ship with an empty value
707 cgversions = [v for v in cgversions
705 cgversions = [v for v in cgversions
708 if v in changegroup.supportedoutgoingversions(
706 if v in changegroup.supportedoutgoingversions(
709 pushop.repo)]
707 pushop.repo)]
710 if not cgversions:
708 if not cgversions:
711 raise ValueError(_('no common changegroup version'))
709 raise ValueError(_('no common changegroup version'))
712 version = max(cgversions)
710 version = max(cgversions)
713 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
711 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
714 pushop.outgoing,
712 pushop.outgoing,
715 version=version)
713 version=version)
716 cgpart = bundler.newpart('changegroup', data=cg)
714 cgpart = bundler.newpart('changegroup', data=cg)
717 if cgversions:
715 if cgversions:
718 cgpart.addparam('version', version)
716 cgpart.addparam('version', version)
719 if 'treemanifest' in pushop.repo.requirements:
717 if 'treemanifest' in pushop.repo.requirements:
720 cgpart.addparam('treemanifest', '1')
718 cgpart.addparam('treemanifest', '1')
721 def handlereply(op):
719 def handlereply(op):
722 """extract addchangegroup returns from server reply"""
720 """extract addchangegroup returns from server reply"""
723 cgreplies = op.records.getreplies(cgpart.id)
721 cgreplies = op.records.getreplies(cgpart.id)
724 assert len(cgreplies['changegroup']) == 1
722 assert len(cgreplies['changegroup']) == 1
725 pushop.cgresult = cgreplies['changegroup'][0]['return']
723 pushop.cgresult = cgreplies['changegroup'][0]['return']
726 return handlereply
724 return handlereply
727
725
728 @b2partsgenerator('phase')
726 @b2partsgenerator('phase')
729 def _pushb2phases(pushop, bundler):
727 def _pushb2phases(pushop, bundler):
730 """handle phase push through bundle2"""
728 """handle phase push through bundle2"""
731 if 'phases' in pushop.stepsdone:
729 if 'phases' in pushop.stepsdone:
732 return
730 return
733 b2caps = bundle2.bundle2caps(pushop.remote)
731 b2caps = bundle2.bundle2caps(pushop.remote)
734 if not 'pushkey' in b2caps:
732 if not 'pushkey' in b2caps:
735 return
733 return
736 pushop.stepsdone.add('phases')
734 pushop.stepsdone.add('phases')
737 part2node = []
735 part2node = []
738
736
739 def handlefailure(pushop, exc):
737 def handlefailure(pushop, exc):
740 targetid = int(exc.partid)
738 targetid = int(exc.partid)
741 for partid, node in part2node:
739 for partid, node in part2node:
742 if partid == targetid:
740 if partid == targetid:
743 raise error.Abort(_('updating %s to public failed') % node)
741 raise error.Abort(_('updating %s to public failed') % node)
744
742
745 enc = pushkey.encode
743 enc = pushkey.encode
746 for newremotehead in pushop.outdatedphases:
744 for newremotehead in pushop.outdatedphases:
747 part = bundler.newpart('pushkey')
745 part = bundler.newpart('pushkey')
748 part.addparam('namespace', enc('phases'))
746 part.addparam('namespace', enc('phases'))
749 part.addparam('key', enc(newremotehead.hex()))
747 part.addparam('key', enc(newremotehead.hex()))
750 part.addparam('old', enc(str(phases.draft)))
748 part.addparam('old', enc(str(phases.draft)))
751 part.addparam('new', enc(str(phases.public)))
749 part.addparam('new', enc(str(phases.public)))
752 part2node.append((part.id, newremotehead))
750 part2node.append((part.id, newremotehead))
753 pushop.pkfailcb[part.id] = handlefailure
751 pushop.pkfailcb[part.id] = handlefailure
754
752
755 def handlereply(op):
753 def handlereply(op):
756 for partid, node in part2node:
754 for partid, node in part2node:
757 partrep = op.records.getreplies(partid)
755 partrep = op.records.getreplies(partid)
758 results = partrep['pushkey']
756 results = partrep['pushkey']
759 assert len(results) <= 1
757 assert len(results) <= 1
760 msg = None
758 msg = None
761 if not results:
759 if not results:
762 msg = _('server ignored update of %s to public!\n') % node
760 msg = _('server ignored update of %s to public!\n') % node
763 elif not int(results[0]['return']):
761 elif not int(results[0]['return']):
764 msg = _('updating %s to public failed!\n') % node
762 msg = _('updating %s to public failed!\n') % node
765 if msg is not None:
763 if msg is not None:
766 pushop.ui.warn(msg)
764 pushop.ui.warn(msg)
767 return handlereply
765 return handlereply
768
766
769 @b2partsgenerator('obsmarkers')
767 @b2partsgenerator('obsmarkers')
770 def _pushb2obsmarkers(pushop, bundler):
768 def _pushb2obsmarkers(pushop, bundler):
771 if 'obsmarkers' in pushop.stepsdone:
769 if 'obsmarkers' in pushop.stepsdone:
772 return
770 return
773 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
771 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
774 if obsolete.commonversion(remoteversions) is None:
772 if obsolete.commonversion(remoteversions) is None:
775 return
773 return
776 pushop.stepsdone.add('obsmarkers')
774 pushop.stepsdone.add('obsmarkers')
777 if pushop.outobsmarkers:
775 if pushop.outobsmarkers:
778 markers = sorted(pushop.outobsmarkers)
776 markers = sorted(pushop.outobsmarkers)
779 buildobsmarkerspart(bundler, markers)
777 buildobsmarkerspart(bundler, markers)
780
778
781 @b2partsgenerator('bookmarks')
779 @b2partsgenerator('bookmarks')
782 def _pushb2bookmarks(pushop, bundler):
780 def _pushb2bookmarks(pushop, bundler):
783 """handle bookmark push through bundle2"""
781 """handle bookmark push through bundle2"""
784 if 'bookmarks' in pushop.stepsdone:
782 if 'bookmarks' in pushop.stepsdone:
785 return
783 return
786 b2caps = bundle2.bundle2caps(pushop.remote)
784 b2caps = bundle2.bundle2caps(pushop.remote)
787 if 'pushkey' not in b2caps:
785 if 'pushkey' not in b2caps:
788 return
786 return
789 pushop.stepsdone.add('bookmarks')
787 pushop.stepsdone.add('bookmarks')
790 part2book = []
788 part2book = []
791 enc = pushkey.encode
789 enc = pushkey.encode
792
790
793 def handlefailure(pushop, exc):
791 def handlefailure(pushop, exc):
794 targetid = int(exc.partid)
792 targetid = int(exc.partid)
795 for partid, book, action in part2book:
793 for partid, book, action in part2book:
796 if partid == targetid:
794 if partid == targetid:
797 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
795 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
798 # we should not be called for part we did not generated
796 # we should not be called for part we did not generated
799 assert False
797 assert False
800
798
801 for book, old, new in pushop.outbookmarks:
799 for book, old, new in pushop.outbookmarks:
802 part = bundler.newpart('pushkey')
800 part = bundler.newpart('pushkey')
803 part.addparam('namespace', enc('bookmarks'))
801 part.addparam('namespace', enc('bookmarks'))
804 part.addparam('key', enc(book))
802 part.addparam('key', enc(book))
805 part.addparam('old', enc(old))
803 part.addparam('old', enc(old))
806 part.addparam('new', enc(new))
804 part.addparam('new', enc(new))
807 action = 'update'
805 action = 'update'
808 if not old:
806 if not old:
809 action = 'export'
807 action = 'export'
810 elif not new:
808 elif not new:
811 action = 'delete'
809 action = 'delete'
812 part2book.append((part.id, book, action))
810 part2book.append((part.id, book, action))
813 pushop.pkfailcb[part.id] = handlefailure
811 pushop.pkfailcb[part.id] = handlefailure
814
812
815 def handlereply(op):
813 def handlereply(op):
816 ui = pushop.ui
814 ui = pushop.ui
817 for partid, book, action in part2book:
815 for partid, book, action in part2book:
818 partrep = op.records.getreplies(partid)
816 partrep = op.records.getreplies(partid)
819 results = partrep['pushkey']
817 results = partrep['pushkey']
820 assert len(results) <= 1
818 assert len(results) <= 1
821 if not results:
819 if not results:
822 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
820 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
823 else:
821 else:
824 ret = int(results[0]['return'])
822 ret = int(results[0]['return'])
825 if ret:
823 if ret:
826 ui.status(bookmsgmap[action][0] % book)
824 ui.status(bookmsgmap[action][0] % book)
827 else:
825 else:
828 ui.warn(bookmsgmap[action][1] % book)
826 ui.warn(bookmsgmap[action][1] % book)
829 if pushop.bkresult is not None:
827 if pushop.bkresult is not None:
830 pushop.bkresult = 1
828 pushop.bkresult = 1
831 return handlereply
829 return handlereply
832
830
833
831
834 def _pushbundle2(pushop):
832 def _pushbundle2(pushop):
835 """push data to the remote using bundle2
833 """push data to the remote using bundle2
836
834
837 The only currently supported type of data is changegroup but this will
835 The only currently supported type of data is changegroup but this will
838 evolve in the future."""
836 evolve in the future."""
839 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
837 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
840 pushback = (pushop.trmanager
838 pushback = (pushop.trmanager
841 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
839 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
842
840
843 # create reply capability
841 # create reply capability
844 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
842 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
845 allowpushback=pushback))
843 allowpushback=pushback))
846 bundler.newpart('replycaps', data=capsblob)
844 bundler.newpart('replycaps', data=capsblob)
847 replyhandlers = []
845 replyhandlers = []
848 for partgenname in b2partsgenorder:
846 for partgenname in b2partsgenorder:
849 partgen = b2partsgenmapping[partgenname]
847 partgen = b2partsgenmapping[partgenname]
850 ret = partgen(pushop, bundler)
848 ret = partgen(pushop, bundler)
851 if callable(ret):
849 if callable(ret):
852 replyhandlers.append(ret)
850 replyhandlers.append(ret)
853 # do not push if nothing to push
851 # do not push if nothing to push
854 if bundler.nbparts <= 1:
852 if bundler.nbparts <= 1:
855 return
853 return
856 stream = util.chunkbuffer(bundler.getchunks())
854 stream = util.chunkbuffer(bundler.getchunks())
857 try:
855 try:
858 try:
856 try:
859 reply = pushop.remote.unbundle(stream, ['force'], 'push')
857 reply = pushop.remote.unbundle(stream, ['force'], 'push')
860 except error.BundleValueError as exc:
858 except error.BundleValueError as exc:
861 raise error.Abort('missing support for %s' % exc)
859 raise error.Abort('missing support for %s' % exc)
862 try:
860 try:
863 trgetter = None
861 trgetter = None
864 if pushback:
862 if pushback:
865 trgetter = pushop.trmanager.transaction
863 trgetter = pushop.trmanager.transaction
866 op = bundle2.processbundle(pushop.repo, reply, trgetter)
864 op = bundle2.processbundle(pushop.repo, reply, trgetter)
867 except error.BundleValueError as exc:
865 except error.BundleValueError as exc:
868 raise error.Abort('missing support for %s' % exc)
866 raise error.Abort('missing support for %s' % exc)
869 except bundle2.AbortFromPart as exc:
867 except bundle2.AbortFromPart as exc:
870 pushop.ui.status(_('remote: %s\n') % exc)
868 pushop.ui.status(_('remote: %s\n') % exc)
871 raise error.Abort(_('push failed on remote'), hint=exc.hint)
869 raise error.Abort(_('push failed on remote'), hint=exc.hint)
872 except error.PushkeyFailed as exc:
870 except error.PushkeyFailed as exc:
873 partid = int(exc.partid)
871 partid = int(exc.partid)
874 if partid not in pushop.pkfailcb:
872 if partid not in pushop.pkfailcb:
875 raise
873 raise
876 pushop.pkfailcb[partid](pushop, exc)
874 pushop.pkfailcb[partid](pushop, exc)
877 for rephand in replyhandlers:
875 for rephand in replyhandlers:
878 rephand(op)
876 rephand(op)
879
877
880 def _pushchangeset(pushop):
878 def _pushchangeset(pushop):
881 """Make the actual push of changeset bundle to remote repo"""
879 """Make the actual push of changeset bundle to remote repo"""
882 if 'changesets' in pushop.stepsdone:
880 if 'changesets' in pushop.stepsdone:
883 return
881 return
884 pushop.stepsdone.add('changesets')
882 pushop.stepsdone.add('changesets')
885 if not _pushcheckoutgoing(pushop):
883 if not _pushcheckoutgoing(pushop):
886 return
884 return
887 pushop.repo.prepushoutgoinghooks(pushop.repo,
885 pushop.repo.prepushoutgoinghooks(pushop)
888 pushop.remote,
889 pushop.outgoing)
890 outgoing = pushop.outgoing
886 outgoing = pushop.outgoing
891 unbundle = pushop.remote.capable('unbundle')
887 unbundle = pushop.remote.capable('unbundle')
892 # TODO: get bundlecaps from remote
888 # TODO: get bundlecaps from remote
893 bundlecaps = None
889 bundlecaps = None
894 # create a changegroup from local
890 # create a changegroup from local
895 if pushop.revs is None and not (outgoing.excluded
891 if pushop.revs is None and not (outgoing.excluded
896 or pushop.repo.changelog.filteredrevs):
892 or pushop.repo.changelog.filteredrevs):
897 # push everything,
893 # push everything,
898 # use the fast path, no race possible on push
894 # use the fast path, no race possible on push
899 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
895 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
900 cg = changegroup.getsubset(pushop.repo,
896 cg = changegroup.getsubset(pushop.repo,
901 outgoing,
897 outgoing,
902 bundler,
898 bundler,
903 'push',
899 'push',
904 fastpath=True)
900 fastpath=True)
905 else:
901 else:
906 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
902 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
907 bundlecaps)
903 bundlecaps)
908
904
909 # apply changegroup to remote
905 # apply changegroup to remote
910 if unbundle:
906 if unbundle:
911 # local repo finds heads on server, finds out what
907 # local repo finds heads on server, finds out what
912 # revs it must push. once revs transferred, if server
908 # revs it must push. once revs transferred, if server
913 # finds it has different heads (someone else won
909 # finds it has different heads (someone else won
914 # commit/push race), server aborts.
910 # commit/push race), server aborts.
915 if pushop.force:
911 if pushop.force:
916 remoteheads = ['force']
912 remoteheads = ['force']
917 else:
913 else:
918 remoteheads = pushop.remoteheads
914 remoteheads = pushop.remoteheads
919 # ssh: return remote's addchangegroup()
915 # ssh: return remote's addchangegroup()
920 # http: return remote's addchangegroup() or 0 for error
916 # http: return remote's addchangegroup() or 0 for error
921 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
917 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
922 pushop.repo.url())
918 pushop.repo.url())
923 else:
919 else:
924 # we return an integer indicating remote head count
920 # we return an integer indicating remote head count
925 # change
921 # change
926 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
922 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
927 pushop.repo.url())
923 pushop.repo.url())
928
924
929 def _pushsyncphase(pushop):
925 def _pushsyncphase(pushop):
930 """synchronise phase information locally and remotely"""
926 """synchronise phase information locally and remotely"""
931 cheads = pushop.commonheads
927 cheads = pushop.commonheads
932 # even when we don't push, exchanging phase data is useful
928 # even when we don't push, exchanging phase data is useful
933 remotephases = pushop.remote.listkeys('phases')
929 remotephases = pushop.remote.listkeys('phases')
934 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
930 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
935 and remotephases # server supports phases
931 and remotephases # server supports phases
936 and pushop.cgresult is None # nothing was pushed
932 and pushop.cgresult is None # nothing was pushed
937 and remotephases.get('publishing', False)):
933 and remotephases.get('publishing', False)):
938 # When:
934 # When:
939 # - this is a subrepo push
935 # - this is a subrepo push
940 # - and remote support phase
936 # - and remote support phase
941 # - and no changeset was pushed
937 # - and no changeset was pushed
942 # - and remote is publishing
938 # - and remote is publishing
943 # We may be in issue 3871 case!
939 # We may be in issue 3871 case!
944 # We drop the possible phase synchronisation done by
940 # We drop the possible phase synchronisation done by
945 # courtesy to publish changesets possibly locally draft
941 # courtesy to publish changesets possibly locally draft
946 # on the remote.
942 # on the remote.
947 remotephases = {'publishing': 'True'}
943 remotephases = {'publishing': 'True'}
948 if not remotephases: # old server or public only reply from non-publishing
944 if not remotephases: # old server or public only reply from non-publishing
949 _localphasemove(pushop, cheads)
945 _localphasemove(pushop, cheads)
950 # don't push any phase data as there is nothing to push
946 # don't push any phase data as there is nothing to push
951 else:
947 else:
952 ana = phases.analyzeremotephases(pushop.repo, cheads,
948 ana = phases.analyzeremotephases(pushop.repo, cheads,
953 remotephases)
949 remotephases)
954 pheads, droots = ana
950 pheads, droots = ana
955 ### Apply remote phase on local
951 ### Apply remote phase on local
956 if remotephases.get('publishing', False):
952 if remotephases.get('publishing', False):
957 _localphasemove(pushop, cheads)
953 _localphasemove(pushop, cheads)
958 else: # publish = False
954 else: # publish = False
959 _localphasemove(pushop, pheads)
955 _localphasemove(pushop, pheads)
960 _localphasemove(pushop, cheads, phases.draft)
956 _localphasemove(pushop, cheads, phases.draft)
961 ### Apply local phase on remote
957 ### Apply local phase on remote
962
958
963 if pushop.cgresult:
959 if pushop.cgresult:
964 if 'phases' in pushop.stepsdone:
960 if 'phases' in pushop.stepsdone:
965 # phases already pushed though bundle2
961 # phases already pushed though bundle2
966 return
962 return
967 outdated = pushop.outdatedphases
963 outdated = pushop.outdatedphases
968 else:
964 else:
969 outdated = pushop.fallbackoutdatedphases
965 outdated = pushop.fallbackoutdatedphases
970
966
971 pushop.stepsdone.add('phases')
967 pushop.stepsdone.add('phases')
972
968
973 # filter heads already turned public by the push
969 # filter heads already turned public by the push
974 outdated = [c for c in outdated if c.node() not in pheads]
970 outdated = [c for c in outdated if c.node() not in pheads]
975 # fallback to independent pushkey command
971 # fallback to independent pushkey command
976 for newremotehead in outdated:
972 for newremotehead in outdated:
977 r = pushop.remote.pushkey('phases',
973 r = pushop.remote.pushkey('phases',
978 newremotehead.hex(),
974 newremotehead.hex(),
979 str(phases.draft),
975 str(phases.draft),
980 str(phases.public))
976 str(phases.public))
981 if not r:
977 if not r:
982 pushop.ui.warn(_('updating %s to public failed!\n')
978 pushop.ui.warn(_('updating %s to public failed!\n')
983 % newremotehead)
979 % newremotehead)
984
980
985 def _localphasemove(pushop, nodes, phase=phases.public):
981 def _localphasemove(pushop, nodes, phase=phases.public):
986 """move <nodes> to <phase> in the local source repo"""
982 """move <nodes> to <phase> in the local source repo"""
987 if pushop.trmanager:
983 if pushop.trmanager:
988 phases.advanceboundary(pushop.repo,
984 phases.advanceboundary(pushop.repo,
989 pushop.trmanager.transaction(),
985 pushop.trmanager.transaction(),
990 phase,
986 phase,
991 nodes)
987 nodes)
992 else:
988 else:
993 # repo is not locked, do not change any phases!
989 # repo is not locked, do not change any phases!
994 # Informs the user that phases should have been moved when
990 # Informs the user that phases should have been moved when
995 # applicable.
991 # applicable.
996 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
992 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
997 phasestr = phases.phasenames[phase]
993 phasestr = phases.phasenames[phase]
998 if actualmoves:
994 if actualmoves:
999 pushop.ui.status(_('cannot lock source repo, skipping '
995 pushop.ui.status(_('cannot lock source repo, skipping '
1000 'local %s phase update\n') % phasestr)
996 'local %s phase update\n') % phasestr)
1001
997
1002 def _pushobsolete(pushop):
998 def _pushobsolete(pushop):
1003 """utility function to push obsolete markers to a remote"""
999 """utility function to push obsolete markers to a remote"""
1004 if 'obsmarkers' in pushop.stepsdone:
1000 if 'obsmarkers' in pushop.stepsdone:
1005 return
1001 return
1006 repo = pushop.repo
1002 repo = pushop.repo
1007 remote = pushop.remote
1003 remote = pushop.remote
1008 pushop.stepsdone.add('obsmarkers')
1004 pushop.stepsdone.add('obsmarkers')
1009 if pushop.outobsmarkers:
1005 if pushop.outobsmarkers:
1010 pushop.ui.debug('try to push obsolete markers to remote\n')
1006 pushop.ui.debug('try to push obsolete markers to remote\n')
1011 rslts = []
1007 rslts = []
1012 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1008 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1013 for key in sorted(remotedata, reverse=True):
1009 for key in sorted(remotedata, reverse=True):
1014 # reverse sort to ensure we end with dump0
1010 # reverse sort to ensure we end with dump0
1015 data = remotedata[key]
1011 data = remotedata[key]
1016 rslts.append(remote.pushkey('obsolete', key, '', data))
1012 rslts.append(remote.pushkey('obsolete', key, '', data))
1017 if [r for r in rslts if not r]:
1013 if [r for r in rslts if not r]:
1018 msg = _('failed to push some obsolete markers!\n')
1014 msg = _('failed to push some obsolete markers!\n')
1019 repo.ui.warn(msg)
1015 repo.ui.warn(msg)
1020
1016
1021 def _pushbookmark(pushop):
1017 def _pushbookmark(pushop):
1022 """Update bookmark position on remote"""
1018 """Update bookmark position on remote"""
1023 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1019 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1024 return
1020 return
1025 pushop.stepsdone.add('bookmarks')
1021 pushop.stepsdone.add('bookmarks')
1026 ui = pushop.ui
1022 ui = pushop.ui
1027 remote = pushop.remote
1023 remote = pushop.remote
1028
1024
1029 for b, old, new in pushop.outbookmarks:
1025 for b, old, new in pushop.outbookmarks:
1030 action = 'update'
1026 action = 'update'
1031 if not old:
1027 if not old:
1032 action = 'export'
1028 action = 'export'
1033 elif not new:
1029 elif not new:
1034 action = 'delete'
1030 action = 'delete'
1035 if remote.pushkey('bookmarks', b, old, new):
1031 if remote.pushkey('bookmarks', b, old, new):
1036 ui.status(bookmsgmap[action][0] % b)
1032 ui.status(bookmsgmap[action][0] % b)
1037 else:
1033 else:
1038 ui.warn(bookmsgmap[action][1] % b)
1034 ui.warn(bookmsgmap[action][1] % b)
1039 # discovery can have set the value form invalid entry
1035 # discovery can have set the value form invalid entry
1040 if pushop.bkresult is not None:
1036 if pushop.bkresult is not None:
1041 pushop.bkresult = 1
1037 pushop.bkresult = 1
1042
1038
1043 class pulloperation(object):
1039 class pulloperation(object):
1044 """A object that represent a single pull operation
1040 """A object that represent a single pull operation
1045
1041
1046 It purpose is to carry pull related state and very common operation.
1042 It purpose is to carry pull related state and very common operation.
1047
1043
1048 A new should be created at the beginning of each pull and discarded
1044 A new should be created at the beginning of each pull and discarded
1049 afterward.
1045 afterward.
1050 """
1046 """
1051
1047
1052 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1048 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1053 remotebookmarks=None, streamclonerequested=None):
1049 remotebookmarks=None, streamclonerequested=None):
1054 # repo we pull into
1050 # repo we pull into
1055 self.repo = repo
1051 self.repo = repo
1056 # repo we pull from
1052 # repo we pull from
1057 self.remote = remote
1053 self.remote = remote
1058 # revision we try to pull (None is "all")
1054 # revision we try to pull (None is "all")
1059 self.heads = heads
1055 self.heads = heads
1060 # bookmark pulled explicitly
1056 # bookmark pulled explicitly
1061 self.explicitbookmarks = bookmarks
1057 self.explicitbookmarks = bookmarks
1062 # do we force pull?
1058 # do we force pull?
1063 self.force = force
1059 self.force = force
1064 # whether a streaming clone was requested
1060 # whether a streaming clone was requested
1065 self.streamclonerequested = streamclonerequested
1061 self.streamclonerequested = streamclonerequested
1066 # transaction manager
1062 # transaction manager
1067 self.trmanager = None
1063 self.trmanager = None
1068 # set of common changeset between local and remote before pull
1064 # set of common changeset between local and remote before pull
1069 self.common = None
1065 self.common = None
1070 # set of pulled head
1066 # set of pulled head
1071 self.rheads = None
1067 self.rheads = None
1072 # list of missing changeset to fetch remotely
1068 # list of missing changeset to fetch remotely
1073 self.fetch = None
1069 self.fetch = None
1074 # remote bookmarks data
1070 # remote bookmarks data
1075 self.remotebookmarks = remotebookmarks
1071 self.remotebookmarks = remotebookmarks
1076 # result of changegroup pulling (used as return code by pull)
1072 # result of changegroup pulling (used as return code by pull)
1077 self.cgresult = None
1073 self.cgresult = None
1078 # list of step already done
1074 # list of step already done
1079 self.stepsdone = set()
1075 self.stepsdone = set()
1080 # Whether we attempted a clone from pre-generated bundles.
1076 # Whether we attempted a clone from pre-generated bundles.
1081 self.clonebundleattempted = False
1077 self.clonebundleattempted = False
1082
1078
1083 @util.propertycache
1079 @util.propertycache
1084 def pulledsubset(self):
1080 def pulledsubset(self):
1085 """heads of the set of changeset target by the pull"""
1081 """heads of the set of changeset target by the pull"""
1086 # compute target subset
1082 # compute target subset
1087 if self.heads is None:
1083 if self.heads is None:
1088 # We pulled every thing possible
1084 # We pulled every thing possible
1089 # sync on everything common
1085 # sync on everything common
1090 c = set(self.common)
1086 c = set(self.common)
1091 ret = list(self.common)
1087 ret = list(self.common)
1092 for n in self.rheads:
1088 for n in self.rheads:
1093 if n not in c:
1089 if n not in c:
1094 ret.append(n)
1090 ret.append(n)
1095 return ret
1091 return ret
1096 else:
1092 else:
1097 # We pulled a specific subset
1093 # We pulled a specific subset
1098 # sync on this subset
1094 # sync on this subset
1099 return self.heads
1095 return self.heads
1100
1096
1101 @util.propertycache
1097 @util.propertycache
1102 def canusebundle2(self):
1098 def canusebundle2(self):
1103 return _canusebundle2(self)
1099 return _canusebundle2(self)
1104
1100
1105 @util.propertycache
1101 @util.propertycache
1106 def remotebundle2caps(self):
1102 def remotebundle2caps(self):
1107 return bundle2.bundle2caps(self.remote)
1103 return bundle2.bundle2caps(self.remote)
1108
1104
1109 def gettransaction(self):
1105 def gettransaction(self):
1110 # deprecated; talk to trmanager directly
1106 # deprecated; talk to trmanager directly
1111 return self.trmanager.transaction()
1107 return self.trmanager.transaction()
1112
1108
1113 class transactionmanager(object):
1109 class transactionmanager(object):
1114 """An object to manage the life cycle of a transaction
1110 """An object to manage the life cycle of a transaction
1115
1111
1116 It creates the transaction on demand and calls the appropriate hooks when
1112 It creates the transaction on demand and calls the appropriate hooks when
1117 closing the transaction."""
1113 closing the transaction."""
1118 def __init__(self, repo, source, url):
1114 def __init__(self, repo, source, url):
1119 self.repo = repo
1115 self.repo = repo
1120 self.source = source
1116 self.source = source
1121 self.url = url
1117 self.url = url
1122 self._tr = None
1118 self._tr = None
1123
1119
1124 def transaction(self):
1120 def transaction(self):
1125 """Return an open transaction object, constructing if necessary"""
1121 """Return an open transaction object, constructing if necessary"""
1126 if not self._tr:
1122 if not self._tr:
1127 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1123 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1128 self._tr = self.repo.transaction(trname)
1124 self._tr = self.repo.transaction(trname)
1129 self._tr.hookargs['source'] = self.source
1125 self._tr.hookargs['source'] = self.source
1130 self._tr.hookargs['url'] = self.url
1126 self._tr.hookargs['url'] = self.url
1131 return self._tr
1127 return self._tr
1132
1128
1133 def close(self):
1129 def close(self):
1134 """close transaction if created"""
1130 """close transaction if created"""
1135 if self._tr is not None:
1131 if self._tr is not None:
1136 self._tr.close()
1132 self._tr.close()
1137
1133
1138 def release(self):
1134 def release(self):
1139 """release transaction if created"""
1135 """release transaction if created"""
1140 if self._tr is not None:
1136 if self._tr is not None:
1141 self._tr.release()
1137 self._tr.release()
1142
1138
1143 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1139 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1144 streamclonerequested=None):
1140 streamclonerequested=None):
1145 """Fetch repository data from a remote.
1141 """Fetch repository data from a remote.
1146
1142
1147 This is the main function used to retrieve data from a remote repository.
1143 This is the main function used to retrieve data from a remote repository.
1148
1144
1149 ``repo`` is the local repository to clone into.
1145 ``repo`` is the local repository to clone into.
1150 ``remote`` is a peer instance.
1146 ``remote`` is a peer instance.
1151 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1147 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1152 default) means to pull everything from the remote.
1148 default) means to pull everything from the remote.
1153 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1149 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1154 default, all remote bookmarks are pulled.
1150 default, all remote bookmarks are pulled.
1155 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1151 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1156 initialization.
1152 initialization.
1157 ``streamclonerequested`` is a boolean indicating whether a "streaming
1153 ``streamclonerequested`` is a boolean indicating whether a "streaming
1158 clone" is requested. A "streaming clone" is essentially a raw file copy
1154 clone" is requested. A "streaming clone" is essentially a raw file copy
1159 of revlogs from the server. This only works when the local repository is
1155 of revlogs from the server. This only works when the local repository is
1160 empty. The default value of ``None`` means to respect the server
1156 empty. The default value of ``None`` means to respect the server
1161 configuration for preferring stream clones.
1157 configuration for preferring stream clones.
1162
1158
1163 Returns the ``pulloperation`` created for this pull.
1159 Returns the ``pulloperation`` created for this pull.
1164 """
1160 """
1165 if opargs is None:
1161 if opargs is None:
1166 opargs = {}
1162 opargs = {}
1167 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1163 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1168 streamclonerequested=streamclonerequested, **opargs)
1164 streamclonerequested=streamclonerequested, **opargs)
1169 if pullop.remote.local():
1165 if pullop.remote.local():
1170 missing = set(pullop.remote.requirements) - pullop.repo.supported
1166 missing = set(pullop.remote.requirements) - pullop.repo.supported
1171 if missing:
1167 if missing:
1172 msg = _("required features are not"
1168 msg = _("required features are not"
1173 " supported in the destination:"
1169 " supported in the destination:"
1174 " %s") % (', '.join(sorted(missing)))
1170 " %s") % (', '.join(sorted(missing)))
1175 raise error.Abort(msg)
1171 raise error.Abort(msg)
1176
1172
1177 lock = pullop.repo.lock()
1173 lock = pullop.repo.lock()
1178 try:
1174 try:
1179 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1175 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1180 streamclone.maybeperformlegacystreamclone(pullop)
1176 streamclone.maybeperformlegacystreamclone(pullop)
1181 # This should ideally be in _pullbundle2(). However, it needs to run
1177 # This should ideally be in _pullbundle2(). However, it needs to run
1182 # before discovery to avoid extra work.
1178 # before discovery to avoid extra work.
1183 _maybeapplyclonebundle(pullop)
1179 _maybeapplyclonebundle(pullop)
1184 _pulldiscovery(pullop)
1180 _pulldiscovery(pullop)
1185 if pullop.canusebundle2:
1181 if pullop.canusebundle2:
1186 _pullbundle2(pullop)
1182 _pullbundle2(pullop)
1187 _pullchangeset(pullop)
1183 _pullchangeset(pullop)
1188 _pullphase(pullop)
1184 _pullphase(pullop)
1189 _pullbookmarks(pullop)
1185 _pullbookmarks(pullop)
1190 _pullobsolete(pullop)
1186 _pullobsolete(pullop)
1191 pullop.trmanager.close()
1187 pullop.trmanager.close()
1192 finally:
1188 finally:
1193 pullop.trmanager.release()
1189 pullop.trmanager.release()
1194 lock.release()
1190 lock.release()
1195
1191
1196 return pullop
1192 return pullop
1197
1193
1198 # list of steps to perform discovery before pull
1194 # list of steps to perform discovery before pull
1199 pulldiscoveryorder = []
1195 pulldiscoveryorder = []
1200
1196
1201 # Mapping between step name and function
1197 # Mapping between step name and function
1202 #
1198 #
1203 # This exists to help extensions wrap steps if necessary
1199 # This exists to help extensions wrap steps if necessary
1204 pulldiscoverymapping = {}
1200 pulldiscoverymapping = {}
1205
1201
1206 def pulldiscovery(stepname):
1202 def pulldiscovery(stepname):
1207 """decorator for function performing discovery before pull
1203 """decorator for function performing discovery before pull
1208
1204
1209 The function is added to the step -> function mapping and appended to the
1205 The function is added to the step -> function mapping and appended to the
1210 list of steps. Beware that decorated function will be added in order (this
1206 list of steps. Beware that decorated function will be added in order (this
1211 may matter).
1207 may matter).
1212
1208
1213 You can only use this decorator for a new step, if you want to wrap a step
1209 You can only use this decorator for a new step, if you want to wrap a step
1214 from an extension, change the pulldiscovery dictionary directly."""
1210 from an extension, change the pulldiscovery dictionary directly."""
1215 def dec(func):
1211 def dec(func):
1216 assert stepname not in pulldiscoverymapping
1212 assert stepname not in pulldiscoverymapping
1217 pulldiscoverymapping[stepname] = func
1213 pulldiscoverymapping[stepname] = func
1218 pulldiscoveryorder.append(stepname)
1214 pulldiscoveryorder.append(stepname)
1219 return func
1215 return func
1220 return dec
1216 return dec
1221
1217
1222 def _pulldiscovery(pullop):
1218 def _pulldiscovery(pullop):
1223 """Run all discovery steps"""
1219 """Run all discovery steps"""
1224 for stepname in pulldiscoveryorder:
1220 for stepname in pulldiscoveryorder:
1225 step = pulldiscoverymapping[stepname]
1221 step = pulldiscoverymapping[stepname]
1226 step(pullop)
1222 step(pullop)
1227
1223
1228 @pulldiscovery('b1:bookmarks')
1224 @pulldiscovery('b1:bookmarks')
1229 def _pullbookmarkbundle1(pullop):
1225 def _pullbookmarkbundle1(pullop):
1230 """fetch bookmark data in bundle1 case
1226 """fetch bookmark data in bundle1 case
1231
1227
1232 If not using bundle2, we have to fetch bookmarks before changeset
1228 If not using bundle2, we have to fetch bookmarks before changeset
1233 discovery to reduce the chance and impact of race conditions."""
1229 discovery to reduce the chance and impact of race conditions."""
1234 if pullop.remotebookmarks is not None:
1230 if pullop.remotebookmarks is not None:
1235 return
1231 return
1236 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1232 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1237 # all known bundle2 servers now support listkeys, but lets be nice with
1233 # all known bundle2 servers now support listkeys, but lets be nice with
1238 # new implementation.
1234 # new implementation.
1239 return
1235 return
1240 pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
1236 pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
1241
1237
1242
1238
1243 @pulldiscovery('changegroup')
1239 @pulldiscovery('changegroup')
1244 def _pulldiscoverychangegroup(pullop):
1240 def _pulldiscoverychangegroup(pullop):
1245 """discovery phase for the pull
1241 """discovery phase for the pull
1246
1242
1247 Current handle changeset discovery only, will change handle all discovery
1243 Current handle changeset discovery only, will change handle all discovery
1248 at some point."""
1244 at some point."""
1249 tmp = discovery.findcommonincoming(pullop.repo,
1245 tmp = discovery.findcommonincoming(pullop.repo,
1250 pullop.remote,
1246 pullop.remote,
1251 heads=pullop.heads,
1247 heads=pullop.heads,
1252 force=pullop.force)
1248 force=pullop.force)
1253 common, fetch, rheads = tmp
1249 common, fetch, rheads = tmp
1254 nm = pullop.repo.unfiltered().changelog.nodemap
1250 nm = pullop.repo.unfiltered().changelog.nodemap
1255 if fetch and rheads:
1251 if fetch and rheads:
1256 # If a remote heads in filtered locally, lets drop it from the unknown
1252 # If a remote heads in filtered locally, lets drop it from the unknown
1257 # remote heads and put in back in common.
1253 # remote heads and put in back in common.
1258 #
1254 #
1259 # This is a hackish solution to catch most of "common but locally
1255 # This is a hackish solution to catch most of "common but locally
1260 # hidden situation". We do not performs discovery on unfiltered
1256 # hidden situation". We do not performs discovery on unfiltered
1261 # repository because it end up doing a pathological amount of round
1257 # repository because it end up doing a pathological amount of round
1262 # trip for w huge amount of changeset we do not care about.
1258 # trip for w huge amount of changeset we do not care about.
1263 #
1259 #
1264 # If a set of such "common but filtered" changeset exist on the server
1260 # If a set of such "common but filtered" changeset exist on the server
1265 # but are not including a remote heads, we'll not be able to detect it,
1261 # but are not including a remote heads, we'll not be able to detect it,
1266 scommon = set(common)
1262 scommon = set(common)
1267 filteredrheads = []
1263 filteredrheads = []
1268 for n in rheads:
1264 for n in rheads:
1269 if n in nm:
1265 if n in nm:
1270 if n not in scommon:
1266 if n not in scommon:
1271 common.append(n)
1267 common.append(n)
1272 else:
1268 else:
1273 filteredrheads.append(n)
1269 filteredrheads.append(n)
1274 if not filteredrheads:
1270 if not filteredrheads:
1275 fetch = []
1271 fetch = []
1276 rheads = filteredrheads
1272 rheads = filteredrheads
1277 pullop.common = common
1273 pullop.common = common
1278 pullop.fetch = fetch
1274 pullop.fetch = fetch
1279 pullop.rheads = rheads
1275 pullop.rheads = rheads
1280
1276
1281 def _pullbundle2(pullop):
1277 def _pullbundle2(pullop):
1282 """pull data using bundle2
1278 """pull data using bundle2
1283
1279
1284 For now, the only supported data are changegroup."""
1280 For now, the only supported data are changegroup."""
1285 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1281 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1286
1282
1287 streaming, streamreqs = streamclone.canperformstreamclone(pullop)
1283 streaming, streamreqs = streamclone.canperformstreamclone(pullop)
1288
1284
1289 # pulling changegroup
1285 # pulling changegroup
1290 pullop.stepsdone.add('changegroup')
1286 pullop.stepsdone.add('changegroup')
1291
1287
1292 kwargs['common'] = pullop.common
1288 kwargs['common'] = pullop.common
1293 kwargs['heads'] = pullop.heads or pullop.rheads
1289 kwargs['heads'] = pullop.heads or pullop.rheads
1294 kwargs['cg'] = pullop.fetch
1290 kwargs['cg'] = pullop.fetch
1295 if 'listkeys' in pullop.remotebundle2caps:
1291 if 'listkeys' in pullop.remotebundle2caps:
1296 kwargs['listkeys'] = ['phase']
1292 kwargs['listkeys'] = ['phase']
1297 if pullop.remotebookmarks is None:
1293 if pullop.remotebookmarks is None:
1298 # make sure to always includes bookmark data when migrating
1294 # make sure to always includes bookmark data when migrating
1299 # `hg incoming --bundle` to using this function.
1295 # `hg incoming --bundle` to using this function.
1300 kwargs['listkeys'].append('bookmarks')
1296 kwargs['listkeys'].append('bookmarks')
1301
1297
1302 # If this is a full pull / clone and the server supports the clone bundles
1298 # If this is a full pull / clone and the server supports the clone bundles
1303 # feature, tell the server whether we attempted a clone bundle. The
1299 # feature, tell the server whether we attempted a clone bundle. The
1304 # presence of this flag indicates the client supports clone bundles. This
1300 # presence of this flag indicates the client supports clone bundles. This
1305 # will enable the server to treat clients that support clone bundles
1301 # will enable the server to treat clients that support clone bundles
1306 # differently from those that don't.
1302 # differently from those that don't.
1307 if (pullop.remote.capable('clonebundles')
1303 if (pullop.remote.capable('clonebundles')
1308 and pullop.heads is None and list(pullop.common) == [nullid]):
1304 and pullop.heads is None and list(pullop.common) == [nullid]):
1309 kwargs['cbattempted'] = pullop.clonebundleattempted
1305 kwargs['cbattempted'] = pullop.clonebundleattempted
1310
1306
1311 if streaming:
1307 if streaming:
1312 pullop.repo.ui.status(_('streaming all changes\n'))
1308 pullop.repo.ui.status(_('streaming all changes\n'))
1313 elif not pullop.fetch:
1309 elif not pullop.fetch:
1314 pullop.repo.ui.status(_("no changes found\n"))
1310 pullop.repo.ui.status(_("no changes found\n"))
1315 pullop.cgresult = 0
1311 pullop.cgresult = 0
1316 else:
1312 else:
1317 if pullop.heads is None and list(pullop.common) == [nullid]:
1313 if pullop.heads is None and list(pullop.common) == [nullid]:
1318 pullop.repo.ui.status(_("requesting all changes\n"))
1314 pullop.repo.ui.status(_("requesting all changes\n"))
1319 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1315 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1320 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1316 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1321 if obsolete.commonversion(remoteversions) is not None:
1317 if obsolete.commonversion(remoteversions) is not None:
1322 kwargs['obsmarkers'] = True
1318 kwargs['obsmarkers'] = True
1323 pullop.stepsdone.add('obsmarkers')
1319 pullop.stepsdone.add('obsmarkers')
1324 _pullbundle2extraprepare(pullop, kwargs)
1320 _pullbundle2extraprepare(pullop, kwargs)
1325 bundle = pullop.remote.getbundle('pull', **kwargs)
1321 bundle = pullop.remote.getbundle('pull', **kwargs)
1326 try:
1322 try:
1327 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1323 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1328 except error.BundleValueError as exc:
1324 except error.BundleValueError as exc:
1329 raise error.Abort('missing support for %s' % exc)
1325 raise error.Abort('missing support for %s' % exc)
1330
1326
1331 if pullop.fetch:
1327 if pullop.fetch:
1332 results = [cg['return'] for cg in op.records['changegroup']]
1328 results = [cg['return'] for cg in op.records['changegroup']]
1333 pullop.cgresult = changegroup.combineresults(results)
1329 pullop.cgresult = changegroup.combineresults(results)
1334
1330
1335 # processing phases change
1331 # processing phases change
1336 for namespace, value in op.records['listkeys']:
1332 for namespace, value in op.records['listkeys']:
1337 if namespace == 'phases':
1333 if namespace == 'phases':
1338 _pullapplyphases(pullop, value)
1334 _pullapplyphases(pullop, value)
1339
1335
1340 # processing bookmark update
1336 # processing bookmark update
1341 for namespace, value in op.records['listkeys']:
1337 for namespace, value in op.records['listkeys']:
1342 if namespace == 'bookmarks':
1338 if namespace == 'bookmarks':
1343 pullop.remotebookmarks = value
1339 pullop.remotebookmarks = value
1344
1340
1345 # bookmark data were either already there or pulled in the bundle
1341 # bookmark data were either already there or pulled in the bundle
1346 if pullop.remotebookmarks is not None:
1342 if pullop.remotebookmarks is not None:
1347 _pullbookmarks(pullop)
1343 _pullbookmarks(pullop)
1348
1344
1349 def _pullbundle2extraprepare(pullop, kwargs):
1345 def _pullbundle2extraprepare(pullop, kwargs):
1350 """hook function so that extensions can extend the getbundle call"""
1346 """hook function so that extensions can extend the getbundle call"""
1351 pass
1347 pass
1352
1348
1353 def _pullchangeset(pullop):
1349 def _pullchangeset(pullop):
1354 """pull changeset from unbundle into the local repo"""
1350 """pull changeset from unbundle into the local repo"""
1355 # We delay the open of the transaction as late as possible so we
1351 # We delay the open of the transaction as late as possible so we
1356 # don't open transaction for nothing or you break future useful
1352 # don't open transaction for nothing or you break future useful
1357 # rollback call
1353 # rollback call
1358 if 'changegroup' in pullop.stepsdone:
1354 if 'changegroup' in pullop.stepsdone:
1359 return
1355 return
1360 pullop.stepsdone.add('changegroup')
1356 pullop.stepsdone.add('changegroup')
1361 if not pullop.fetch:
1357 if not pullop.fetch:
1362 pullop.repo.ui.status(_("no changes found\n"))
1358 pullop.repo.ui.status(_("no changes found\n"))
1363 pullop.cgresult = 0
1359 pullop.cgresult = 0
1364 return
1360 return
1365 pullop.gettransaction()
1361 pullop.gettransaction()
1366 if pullop.heads is None and list(pullop.common) == [nullid]:
1362 if pullop.heads is None and list(pullop.common) == [nullid]:
1367 pullop.repo.ui.status(_("requesting all changes\n"))
1363 pullop.repo.ui.status(_("requesting all changes\n"))
1368 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1364 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1369 # issue1320, avoid a race if remote changed after discovery
1365 # issue1320, avoid a race if remote changed after discovery
1370 pullop.heads = pullop.rheads
1366 pullop.heads = pullop.rheads
1371
1367
1372 if pullop.remote.capable('getbundle'):
1368 if pullop.remote.capable('getbundle'):
1373 # TODO: get bundlecaps from remote
1369 # TODO: get bundlecaps from remote
1374 cg = pullop.remote.getbundle('pull', common=pullop.common,
1370 cg = pullop.remote.getbundle('pull', common=pullop.common,
1375 heads=pullop.heads or pullop.rheads)
1371 heads=pullop.heads or pullop.rheads)
1376 elif pullop.heads is None:
1372 elif pullop.heads is None:
1377 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1373 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1378 elif not pullop.remote.capable('changegroupsubset'):
1374 elif not pullop.remote.capable('changegroupsubset'):
1379 raise error.Abort(_("partial pull cannot be done because "
1375 raise error.Abort(_("partial pull cannot be done because "
1380 "other repository doesn't support "
1376 "other repository doesn't support "
1381 "changegroupsubset."))
1377 "changegroupsubset."))
1382 else:
1378 else:
1383 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1379 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1384 pullop.cgresult = cg.apply(pullop.repo, 'pull', pullop.remote.url())
1380 pullop.cgresult = cg.apply(pullop.repo, 'pull', pullop.remote.url())
1385
1381
1386 def _pullphase(pullop):
1382 def _pullphase(pullop):
1387 # Get remote phases data from remote
1383 # Get remote phases data from remote
1388 if 'phases' in pullop.stepsdone:
1384 if 'phases' in pullop.stepsdone:
1389 return
1385 return
1390 remotephases = pullop.remote.listkeys('phases')
1386 remotephases = pullop.remote.listkeys('phases')
1391 _pullapplyphases(pullop, remotephases)
1387 _pullapplyphases(pullop, remotephases)
1392
1388
1393 def _pullapplyphases(pullop, remotephases):
1389 def _pullapplyphases(pullop, remotephases):
1394 """apply phase movement from observed remote state"""
1390 """apply phase movement from observed remote state"""
1395 if 'phases' in pullop.stepsdone:
1391 if 'phases' in pullop.stepsdone:
1396 return
1392 return
1397 pullop.stepsdone.add('phases')
1393 pullop.stepsdone.add('phases')
1398 publishing = bool(remotephases.get('publishing', False))
1394 publishing = bool(remotephases.get('publishing', False))
1399 if remotephases and not publishing:
1395 if remotephases and not publishing:
1400 # remote is new and unpublishing
1396 # remote is new and unpublishing
1401 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1397 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1402 pullop.pulledsubset,
1398 pullop.pulledsubset,
1403 remotephases)
1399 remotephases)
1404 dheads = pullop.pulledsubset
1400 dheads = pullop.pulledsubset
1405 else:
1401 else:
1406 # Remote is old or publishing all common changesets
1402 # Remote is old or publishing all common changesets
1407 # should be seen as public
1403 # should be seen as public
1408 pheads = pullop.pulledsubset
1404 pheads = pullop.pulledsubset
1409 dheads = []
1405 dheads = []
1410 unfi = pullop.repo.unfiltered()
1406 unfi = pullop.repo.unfiltered()
1411 phase = unfi._phasecache.phase
1407 phase = unfi._phasecache.phase
1412 rev = unfi.changelog.nodemap.get
1408 rev = unfi.changelog.nodemap.get
1413 public = phases.public
1409 public = phases.public
1414 draft = phases.draft
1410 draft = phases.draft
1415
1411
1416 # exclude changesets already public locally and update the others
1412 # exclude changesets already public locally and update the others
1417 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1413 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1418 if pheads:
1414 if pheads:
1419 tr = pullop.gettransaction()
1415 tr = pullop.gettransaction()
1420 phases.advanceboundary(pullop.repo, tr, public, pheads)
1416 phases.advanceboundary(pullop.repo, tr, public, pheads)
1421
1417
1422 # exclude changesets already draft locally and update the others
1418 # exclude changesets already draft locally and update the others
1423 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1419 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1424 if dheads:
1420 if dheads:
1425 tr = pullop.gettransaction()
1421 tr = pullop.gettransaction()
1426 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1422 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1427
1423
1428 def _pullbookmarks(pullop):
1424 def _pullbookmarks(pullop):
1429 """process the remote bookmark information to update the local one"""
1425 """process the remote bookmark information to update the local one"""
1430 if 'bookmarks' in pullop.stepsdone:
1426 if 'bookmarks' in pullop.stepsdone:
1431 return
1427 return
1432 pullop.stepsdone.add('bookmarks')
1428 pullop.stepsdone.add('bookmarks')
1433 repo = pullop.repo
1429 repo = pullop.repo
1434 remotebookmarks = pullop.remotebookmarks
1430 remotebookmarks = pullop.remotebookmarks
1435 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1431 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1436 pullop.remote.url(),
1432 pullop.remote.url(),
1437 pullop.gettransaction,
1433 pullop.gettransaction,
1438 explicit=pullop.explicitbookmarks)
1434 explicit=pullop.explicitbookmarks)
1439
1435
1440 def _pullobsolete(pullop):
1436 def _pullobsolete(pullop):
1441 """utility function to pull obsolete markers from a remote
1437 """utility function to pull obsolete markers from a remote
1442
1438
1443 The `gettransaction` is function that return the pull transaction, creating
1439 The `gettransaction` is function that return the pull transaction, creating
1444 one if necessary. We return the transaction to inform the calling code that
1440 one if necessary. We return the transaction to inform the calling code that
1445 a new transaction have been created (when applicable).
1441 a new transaction have been created (when applicable).
1446
1442
1447 Exists mostly to allow overriding for experimentation purpose"""
1443 Exists mostly to allow overriding for experimentation purpose"""
1448 if 'obsmarkers' in pullop.stepsdone:
1444 if 'obsmarkers' in pullop.stepsdone:
1449 return
1445 return
1450 pullop.stepsdone.add('obsmarkers')
1446 pullop.stepsdone.add('obsmarkers')
1451 tr = None
1447 tr = None
1452 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1448 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1453 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1449 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1454 remoteobs = pullop.remote.listkeys('obsolete')
1450 remoteobs = pullop.remote.listkeys('obsolete')
1455 if 'dump0' in remoteobs:
1451 if 'dump0' in remoteobs:
1456 tr = pullop.gettransaction()
1452 tr = pullop.gettransaction()
1457 markers = []
1453 markers = []
1458 for key in sorted(remoteobs, reverse=True):
1454 for key in sorted(remoteobs, reverse=True):
1459 if key.startswith('dump'):
1455 if key.startswith('dump'):
1460 data = base85.b85decode(remoteobs[key])
1456 data = base85.b85decode(remoteobs[key])
1461 version, newmarks = obsolete._readmarkers(data)
1457 version, newmarks = obsolete._readmarkers(data)
1462 markers += newmarks
1458 markers += newmarks
1463 if markers:
1459 if markers:
1464 pullop.repo.obsstore.add(tr, markers)
1460 pullop.repo.obsstore.add(tr, markers)
1465 pullop.repo.invalidatevolatilesets()
1461 pullop.repo.invalidatevolatilesets()
1466 return tr
1462 return tr
1467
1463
1468 def caps20to10(repo):
1464 def caps20to10(repo):
1469 """return a set with appropriate options to use bundle20 during getbundle"""
1465 """return a set with appropriate options to use bundle20 during getbundle"""
1470 caps = set(['HG20'])
1466 caps = set(['HG20'])
1471 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1467 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1472 caps.add('bundle2=' + urllib.quote(capsblob))
1468 caps.add('bundle2=' + urllib.quote(capsblob))
1473 return caps
1469 return caps
1474
1470
1475 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1471 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1476 getbundle2partsorder = []
1472 getbundle2partsorder = []
1477
1473
1478 # Mapping between step name and function
1474 # Mapping between step name and function
1479 #
1475 #
1480 # This exists to help extensions wrap steps if necessary
1476 # This exists to help extensions wrap steps if necessary
1481 getbundle2partsmapping = {}
1477 getbundle2partsmapping = {}
1482
1478
1483 def getbundle2partsgenerator(stepname, idx=None):
1479 def getbundle2partsgenerator(stepname, idx=None):
1484 """decorator for function generating bundle2 part for getbundle
1480 """decorator for function generating bundle2 part for getbundle
1485
1481
1486 The function is added to the step -> function mapping and appended to the
1482 The function is added to the step -> function mapping and appended to the
1487 list of steps. Beware that decorated functions will be added in order
1483 list of steps. Beware that decorated functions will be added in order
1488 (this may matter).
1484 (this may matter).
1489
1485
1490 You can only use this decorator for new steps, if you want to wrap a step
1486 You can only use this decorator for new steps, if you want to wrap a step
1491 from an extension, attack the getbundle2partsmapping dictionary directly."""
1487 from an extension, attack the getbundle2partsmapping dictionary directly."""
1492 def dec(func):
1488 def dec(func):
1493 assert stepname not in getbundle2partsmapping
1489 assert stepname not in getbundle2partsmapping
1494 getbundle2partsmapping[stepname] = func
1490 getbundle2partsmapping[stepname] = func
1495 if idx is None:
1491 if idx is None:
1496 getbundle2partsorder.append(stepname)
1492 getbundle2partsorder.append(stepname)
1497 else:
1493 else:
1498 getbundle2partsorder.insert(idx, stepname)
1494 getbundle2partsorder.insert(idx, stepname)
1499 return func
1495 return func
1500 return dec
1496 return dec
1501
1497
1502 def bundle2requested(bundlecaps):
1498 def bundle2requested(bundlecaps):
1503 if bundlecaps is not None:
1499 if bundlecaps is not None:
1504 return any(cap.startswith('HG2') for cap in bundlecaps)
1500 return any(cap.startswith('HG2') for cap in bundlecaps)
1505 return False
1501 return False
1506
1502
1507 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1503 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1508 **kwargs):
1504 **kwargs):
1509 """return a full bundle (with potentially multiple kind of parts)
1505 """return a full bundle (with potentially multiple kind of parts)
1510
1506
1511 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1507 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1512 passed. For now, the bundle can contain only changegroup, but this will
1508 passed. For now, the bundle can contain only changegroup, but this will
1513 changes when more part type will be available for bundle2.
1509 changes when more part type will be available for bundle2.
1514
1510
1515 This is different from changegroup.getchangegroup that only returns an HG10
1511 This is different from changegroup.getchangegroup that only returns an HG10
1516 changegroup bundle. They may eventually get reunited in the future when we
1512 changegroup bundle. They may eventually get reunited in the future when we
1517 have a clearer idea of the API we what to query different data.
1513 have a clearer idea of the API we what to query different data.
1518
1514
1519 The implementation is at a very early stage and will get massive rework
1515 The implementation is at a very early stage and will get massive rework
1520 when the API of bundle is refined.
1516 when the API of bundle is refined.
1521 """
1517 """
1522 usebundle2 = bundle2requested(bundlecaps)
1518 usebundle2 = bundle2requested(bundlecaps)
1523 # bundle10 case
1519 # bundle10 case
1524 if not usebundle2:
1520 if not usebundle2:
1525 if bundlecaps and not kwargs.get('cg', True):
1521 if bundlecaps and not kwargs.get('cg', True):
1526 raise ValueError(_('request for bundle10 must include changegroup'))
1522 raise ValueError(_('request for bundle10 must include changegroup'))
1527
1523
1528 if kwargs:
1524 if kwargs:
1529 raise ValueError(_('unsupported getbundle arguments: %s')
1525 raise ValueError(_('unsupported getbundle arguments: %s')
1530 % ', '.join(sorted(kwargs.keys())))
1526 % ', '.join(sorted(kwargs.keys())))
1531 return changegroup.getchangegroup(repo, source, heads=heads,
1527 return changegroup.getchangegroup(repo, source, heads=heads,
1532 common=common, bundlecaps=bundlecaps)
1528 common=common, bundlecaps=bundlecaps)
1533
1529
1534 # bundle20 case
1530 # bundle20 case
1535 b2caps = {}
1531 b2caps = {}
1536 for bcaps in bundlecaps:
1532 for bcaps in bundlecaps:
1537 if bcaps.startswith('bundle2='):
1533 if bcaps.startswith('bundle2='):
1538 blob = urllib.unquote(bcaps[len('bundle2='):])
1534 blob = urllib.unquote(bcaps[len('bundle2='):])
1539 b2caps.update(bundle2.decodecaps(blob))
1535 b2caps.update(bundle2.decodecaps(blob))
1540 bundler = bundle2.bundle20(repo.ui, b2caps)
1536 bundler = bundle2.bundle20(repo.ui, b2caps)
1541
1537
1542 kwargs['heads'] = heads
1538 kwargs['heads'] = heads
1543 kwargs['common'] = common
1539 kwargs['common'] = common
1544
1540
1545 for name in getbundle2partsorder:
1541 for name in getbundle2partsorder:
1546 func = getbundle2partsmapping[name]
1542 func = getbundle2partsmapping[name]
1547 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1543 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1548 **kwargs)
1544 **kwargs)
1549
1545
1550 return util.chunkbuffer(bundler.getchunks())
1546 return util.chunkbuffer(bundler.getchunks())
1551
1547
1552 @getbundle2partsgenerator('changegroup')
1548 @getbundle2partsgenerator('changegroup')
1553 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1549 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1554 b2caps=None, heads=None, common=None, **kwargs):
1550 b2caps=None, heads=None, common=None, **kwargs):
1555 """add a changegroup part to the requested bundle"""
1551 """add a changegroup part to the requested bundle"""
1556 cg = None
1552 cg = None
1557 if kwargs.get('cg', True):
1553 if kwargs.get('cg', True):
1558 # build changegroup bundle here.
1554 # build changegroup bundle here.
1559 version = '01'
1555 version = '01'
1560 cgversions = b2caps.get('changegroup')
1556 cgversions = b2caps.get('changegroup')
1561 if cgversions: # 3.1 and 3.2 ship with an empty value
1557 if cgversions: # 3.1 and 3.2 ship with an empty value
1562 cgversions = [v for v in cgversions
1558 cgversions = [v for v in cgversions
1563 if v in changegroup.supportedoutgoingversions(repo)]
1559 if v in changegroup.supportedoutgoingversions(repo)]
1564 if not cgversions:
1560 if not cgversions:
1565 raise ValueError(_('no common changegroup version'))
1561 raise ValueError(_('no common changegroup version'))
1566 version = max(cgversions)
1562 version = max(cgversions)
1567 outgoing = changegroup.computeoutgoing(repo, heads, common)
1563 outgoing = changegroup.computeoutgoing(repo, heads, common)
1568 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
1564 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
1569 bundlecaps=bundlecaps,
1565 bundlecaps=bundlecaps,
1570 version=version)
1566 version=version)
1571
1567
1572 if cg:
1568 if cg:
1573 part = bundler.newpart('changegroup', data=cg)
1569 part = bundler.newpart('changegroup', data=cg)
1574 if cgversions:
1570 if cgversions:
1575 part.addparam('version', version)
1571 part.addparam('version', version)
1576 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
1572 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
1577 if 'treemanifest' in repo.requirements:
1573 if 'treemanifest' in repo.requirements:
1578 part.addparam('treemanifest', '1')
1574 part.addparam('treemanifest', '1')
1579
1575
1580 @getbundle2partsgenerator('listkeys')
1576 @getbundle2partsgenerator('listkeys')
1581 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1577 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1582 b2caps=None, **kwargs):
1578 b2caps=None, **kwargs):
1583 """add parts containing listkeys namespaces to the requested bundle"""
1579 """add parts containing listkeys namespaces to the requested bundle"""
1584 listkeys = kwargs.get('listkeys', ())
1580 listkeys = kwargs.get('listkeys', ())
1585 for namespace in listkeys:
1581 for namespace in listkeys:
1586 part = bundler.newpart('listkeys')
1582 part = bundler.newpart('listkeys')
1587 part.addparam('namespace', namespace)
1583 part.addparam('namespace', namespace)
1588 keys = repo.listkeys(namespace).items()
1584 keys = repo.listkeys(namespace).items()
1589 part.data = pushkey.encodekeys(keys)
1585 part.data = pushkey.encodekeys(keys)
1590
1586
1591 @getbundle2partsgenerator('obsmarkers')
1587 @getbundle2partsgenerator('obsmarkers')
1592 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1588 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1593 b2caps=None, heads=None, **kwargs):
1589 b2caps=None, heads=None, **kwargs):
1594 """add an obsolescence markers part to the requested bundle"""
1590 """add an obsolescence markers part to the requested bundle"""
1595 if kwargs.get('obsmarkers', False):
1591 if kwargs.get('obsmarkers', False):
1596 if heads is None:
1592 if heads is None:
1597 heads = repo.heads()
1593 heads = repo.heads()
1598 subset = [c.node() for c in repo.set('::%ln', heads)]
1594 subset = [c.node() for c in repo.set('::%ln', heads)]
1599 markers = repo.obsstore.relevantmarkers(subset)
1595 markers = repo.obsstore.relevantmarkers(subset)
1600 markers = sorted(markers)
1596 markers = sorted(markers)
1601 buildobsmarkerspart(bundler, markers)
1597 buildobsmarkerspart(bundler, markers)
1602
1598
1603 @getbundle2partsgenerator('hgtagsfnodes')
1599 @getbundle2partsgenerator('hgtagsfnodes')
1604 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1600 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1605 b2caps=None, heads=None, common=None,
1601 b2caps=None, heads=None, common=None,
1606 **kwargs):
1602 **kwargs):
1607 """Transfer the .hgtags filenodes mapping.
1603 """Transfer the .hgtags filenodes mapping.
1608
1604
1609 Only values for heads in this bundle will be transferred.
1605 Only values for heads in this bundle will be transferred.
1610
1606
1611 The part data consists of pairs of 20 byte changeset node and .hgtags
1607 The part data consists of pairs of 20 byte changeset node and .hgtags
1612 filenodes raw values.
1608 filenodes raw values.
1613 """
1609 """
1614 # Don't send unless:
1610 # Don't send unless:
1615 # - changeset are being exchanged,
1611 # - changeset are being exchanged,
1616 # - the client supports it.
1612 # - the client supports it.
1617 if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
1613 if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
1618 return
1614 return
1619
1615
1620 outgoing = changegroup.computeoutgoing(repo, heads, common)
1616 outgoing = changegroup.computeoutgoing(repo, heads, common)
1621
1617
1622 if not outgoing.missingheads:
1618 if not outgoing.missingheads:
1623 return
1619 return
1624
1620
1625 cache = tags.hgtagsfnodescache(repo.unfiltered())
1621 cache = tags.hgtagsfnodescache(repo.unfiltered())
1626 chunks = []
1622 chunks = []
1627
1623
1628 # .hgtags fnodes are only relevant for head changesets. While we could
1624 # .hgtags fnodes are only relevant for head changesets. While we could
1629 # transfer values for all known nodes, there will likely be little to
1625 # transfer values for all known nodes, there will likely be little to
1630 # no benefit.
1626 # no benefit.
1631 #
1627 #
1632 # We don't bother using a generator to produce output data because
1628 # We don't bother using a generator to produce output data because
1633 # a) we only have 40 bytes per head and even esoteric numbers of heads
1629 # a) we only have 40 bytes per head and even esoteric numbers of heads
1634 # consume little memory (1M heads is 40MB) b) we don't want to send the
1630 # consume little memory (1M heads is 40MB) b) we don't want to send the
1635 # part if we don't have entries and knowing if we have entries requires
1631 # part if we don't have entries and knowing if we have entries requires
1636 # cache lookups.
1632 # cache lookups.
1637 for node in outgoing.missingheads:
1633 for node in outgoing.missingheads:
1638 # Don't compute missing, as this may slow down serving.
1634 # Don't compute missing, as this may slow down serving.
1639 fnode = cache.getfnode(node, computemissing=False)
1635 fnode = cache.getfnode(node, computemissing=False)
1640 if fnode is not None:
1636 if fnode is not None:
1641 chunks.extend([node, fnode])
1637 chunks.extend([node, fnode])
1642
1638
1643 if chunks:
1639 if chunks:
1644 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1640 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1645
1641
1646 def check_heads(repo, their_heads, context):
1642 def check_heads(repo, their_heads, context):
1647 """check if the heads of a repo have been modified
1643 """check if the heads of a repo have been modified
1648
1644
1649 Used by peer for unbundling.
1645 Used by peer for unbundling.
1650 """
1646 """
1651 heads = repo.heads()
1647 heads = repo.heads()
1652 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1648 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1653 if not (their_heads == ['force'] or their_heads == heads or
1649 if not (their_heads == ['force'] or their_heads == heads or
1654 their_heads == ['hashed', heads_hash]):
1650 their_heads == ['hashed', heads_hash]):
1655 # someone else committed/pushed/unbundled while we
1651 # someone else committed/pushed/unbundled while we
1656 # were transferring data
1652 # were transferring data
1657 raise error.PushRaced('repository changed while %s - '
1653 raise error.PushRaced('repository changed while %s - '
1658 'please try again' % context)
1654 'please try again' % context)
1659
1655
1660 def unbundle(repo, cg, heads, source, url):
1656 def unbundle(repo, cg, heads, source, url):
1661 """Apply a bundle to a repo.
1657 """Apply a bundle to a repo.
1662
1658
1663 this function makes sure the repo is locked during the application and have
1659 this function makes sure the repo is locked during the application and have
1664 mechanism to check that no push race occurred between the creation of the
1660 mechanism to check that no push race occurred between the creation of the
1665 bundle and its application.
1661 bundle and its application.
1666
1662
1667 If the push was raced as PushRaced exception is raised."""
1663 If the push was raced as PushRaced exception is raised."""
1668 r = 0
1664 r = 0
1669 # need a transaction when processing a bundle2 stream
1665 # need a transaction when processing a bundle2 stream
1670 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1666 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1671 lockandtr = [None, None, None]
1667 lockandtr = [None, None, None]
1672 recordout = None
1668 recordout = None
1673 # quick fix for output mismatch with bundle2 in 3.4
1669 # quick fix for output mismatch with bundle2 in 3.4
1674 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture',
1670 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture',
1675 False)
1671 False)
1676 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1672 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1677 captureoutput = True
1673 captureoutput = True
1678 try:
1674 try:
1679 check_heads(repo, heads, 'uploading changes')
1675 check_heads(repo, heads, 'uploading changes')
1680 # push can proceed
1676 # push can proceed
1681 if util.safehasattr(cg, 'params'):
1677 if util.safehasattr(cg, 'params'):
1682 r = None
1678 r = None
1683 try:
1679 try:
1684 def gettransaction():
1680 def gettransaction():
1685 if not lockandtr[2]:
1681 if not lockandtr[2]:
1686 lockandtr[0] = repo.wlock()
1682 lockandtr[0] = repo.wlock()
1687 lockandtr[1] = repo.lock()
1683 lockandtr[1] = repo.lock()
1688 lockandtr[2] = repo.transaction(source)
1684 lockandtr[2] = repo.transaction(source)
1689 lockandtr[2].hookargs['source'] = source
1685 lockandtr[2].hookargs['source'] = source
1690 lockandtr[2].hookargs['url'] = url
1686 lockandtr[2].hookargs['url'] = url
1691 lockandtr[2].hookargs['bundle2'] = '1'
1687 lockandtr[2].hookargs['bundle2'] = '1'
1692 return lockandtr[2]
1688 return lockandtr[2]
1693
1689
1694 # Do greedy locking by default until we're satisfied with lazy
1690 # Do greedy locking by default until we're satisfied with lazy
1695 # locking.
1691 # locking.
1696 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1692 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1697 gettransaction()
1693 gettransaction()
1698
1694
1699 op = bundle2.bundleoperation(repo, gettransaction,
1695 op = bundle2.bundleoperation(repo, gettransaction,
1700 captureoutput=captureoutput)
1696 captureoutput=captureoutput)
1701 try:
1697 try:
1702 op = bundle2.processbundle(repo, cg, op=op)
1698 op = bundle2.processbundle(repo, cg, op=op)
1703 finally:
1699 finally:
1704 r = op.reply
1700 r = op.reply
1705 if captureoutput and r is not None:
1701 if captureoutput and r is not None:
1706 repo.ui.pushbuffer(error=True, subproc=True)
1702 repo.ui.pushbuffer(error=True, subproc=True)
1707 def recordout(output):
1703 def recordout(output):
1708 r.newpart('output', data=output, mandatory=False)
1704 r.newpart('output', data=output, mandatory=False)
1709 if lockandtr[2] is not None:
1705 if lockandtr[2] is not None:
1710 lockandtr[2].close()
1706 lockandtr[2].close()
1711 except BaseException as exc:
1707 except BaseException as exc:
1712 exc.duringunbundle2 = True
1708 exc.duringunbundle2 = True
1713 if captureoutput and r is not None:
1709 if captureoutput and r is not None:
1714 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1710 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1715 def recordout(output):
1711 def recordout(output):
1716 part = bundle2.bundlepart('output', data=output,
1712 part = bundle2.bundlepart('output', data=output,
1717 mandatory=False)
1713 mandatory=False)
1718 parts.append(part)
1714 parts.append(part)
1719 raise
1715 raise
1720 else:
1716 else:
1721 lockandtr[1] = repo.lock()
1717 lockandtr[1] = repo.lock()
1722 r = cg.apply(repo, source, url)
1718 r = cg.apply(repo, source, url)
1723 finally:
1719 finally:
1724 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1720 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1725 if recordout is not None:
1721 if recordout is not None:
1726 recordout(repo.ui.popbuffer())
1722 recordout(repo.ui.popbuffer())
1727 return r
1723 return r
1728
1724
1729 def _maybeapplyclonebundle(pullop):
1725 def _maybeapplyclonebundle(pullop):
1730 """Apply a clone bundle from a remote, if possible."""
1726 """Apply a clone bundle from a remote, if possible."""
1731
1727
1732 repo = pullop.repo
1728 repo = pullop.repo
1733 remote = pullop.remote
1729 remote = pullop.remote
1734
1730
1735 if not repo.ui.configbool('ui', 'clonebundles', True):
1731 if not repo.ui.configbool('ui', 'clonebundles', True):
1736 return
1732 return
1737
1733
1738 # Only run if local repo is empty.
1734 # Only run if local repo is empty.
1739 if len(repo):
1735 if len(repo):
1740 return
1736 return
1741
1737
1742 if pullop.heads:
1738 if pullop.heads:
1743 return
1739 return
1744
1740
1745 if not remote.capable('clonebundles'):
1741 if not remote.capable('clonebundles'):
1746 return
1742 return
1747
1743
1748 res = remote._call('clonebundles')
1744 res = remote._call('clonebundles')
1749
1745
1750 # If we call the wire protocol command, that's good enough to record the
1746 # If we call the wire protocol command, that's good enough to record the
1751 # attempt.
1747 # attempt.
1752 pullop.clonebundleattempted = True
1748 pullop.clonebundleattempted = True
1753
1749
1754 entries = parseclonebundlesmanifest(repo, res)
1750 entries = parseclonebundlesmanifest(repo, res)
1755 if not entries:
1751 if not entries:
1756 repo.ui.note(_('no clone bundles available on remote; '
1752 repo.ui.note(_('no clone bundles available on remote; '
1757 'falling back to regular clone\n'))
1753 'falling back to regular clone\n'))
1758 return
1754 return
1759
1755
1760 entries = filterclonebundleentries(repo, entries)
1756 entries = filterclonebundleentries(repo, entries)
1761 if not entries:
1757 if not entries:
1762 # There is a thundering herd concern here. However, if a server
1758 # There is a thundering herd concern here. However, if a server
1763 # operator doesn't advertise bundles appropriate for its clients,
1759 # operator doesn't advertise bundles appropriate for its clients,
1764 # they deserve what's coming. Furthermore, from a client's
1760 # they deserve what's coming. Furthermore, from a client's
1765 # perspective, no automatic fallback would mean not being able to
1761 # perspective, no automatic fallback would mean not being able to
1766 # clone!
1762 # clone!
1767 repo.ui.warn(_('no compatible clone bundles available on server; '
1763 repo.ui.warn(_('no compatible clone bundles available on server; '
1768 'falling back to regular clone\n'))
1764 'falling back to regular clone\n'))
1769 repo.ui.warn(_('(you may want to report this to the server '
1765 repo.ui.warn(_('(you may want to report this to the server '
1770 'operator)\n'))
1766 'operator)\n'))
1771 return
1767 return
1772
1768
1773 entries = sortclonebundleentries(repo.ui, entries)
1769 entries = sortclonebundleentries(repo.ui, entries)
1774
1770
1775 url = entries[0]['URL']
1771 url = entries[0]['URL']
1776 repo.ui.status(_('applying clone bundle from %s\n') % url)
1772 repo.ui.status(_('applying clone bundle from %s\n') % url)
1777 if trypullbundlefromurl(repo.ui, repo, url):
1773 if trypullbundlefromurl(repo.ui, repo, url):
1778 repo.ui.status(_('finished applying clone bundle\n'))
1774 repo.ui.status(_('finished applying clone bundle\n'))
1779 # Bundle failed.
1775 # Bundle failed.
1780 #
1776 #
1781 # We abort by default to avoid the thundering herd of
1777 # We abort by default to avoid the thundering herd of
1782 # clients flooding a server that was expecting expensive
1778 # clients flooding a server that was expecting expensive
1783 # clone load to be offloaded.
1779 # clone load to be offloaded.
1784 elif repo.ui.configbool('ui', 'clonebundlefallback', False):
1780 elif repo.ui.configbool('ui', 'clonebundlefallback', False):
1785 repo.ui.warn(_('falling back to normal clone\n'))
1781 repo.ui.warn(_('falling back to normal clone\n'))
1786 else:
1782 else:
1787 raise error.Abort(_('error applying bundle'),
1783 raise error.Abort(_('error applying bundle'),
1788 hint=_('if this error persists, consider contacting '
1784 hint=_('if this error persists, consider contacting '
1789 'the server operator or disable clone '
1785 'the server operator or disable clone '
1790 'bundles via '
1786 'bundles via '
1791 '"--config ui.clonebundles=false"'))
1787 '"--config ui.clonebundles=false"'))
1792
1788
1793 def parseclonebundlesmanifest(repo, s):
1789 def parseclonebundlesmanifest(repo, s):
1794 """Parses the raw text of a clone bundles manifest.
1790 """Parses the raw text of a clone bundles manifest.
1795
1791
1796 Returns a list of dicts. The dicts have a ``URL`` key corresponding
1792 Returns a list of dicts. The dicts have a ``URL`` key corresponding
1797 to the URL and other keys are the attributes for the entry.
1793 to the URL and other keys are the attributes for the entry.
1798 """
1794 """
1799 m = []
1795 m = []
1800 for line in s.splitlines():
1796 for line in s.splitlines():
1801 fields = line.split()
1797 fields = line.split()
1802 if not fields:
1798 if not fields:
1803 continue
1799 continue
1804 attrs = {'URL': fields[0]}
1800 attrs = {'URL': fields[0]}
1805 for rawattr in fields[1:]:
1801 for rawattr in fields[1:]:
1806 key, value = rawattr.split('=', 1)
1802 key, value = rawattr.split('=', 1)
1807 key = urllib.unquote(key)
1803 key = urllib.unquote(key)
1808 value = urllib.unquote(value)
1804 value = urllib.unquote(value)
1809 attrs[key] = value
1805 attrs[key] = value
1810
1806
1811 # Parse BUNDLESPEC into components. This makes client-side
1807 # Parse BUNDLESPEC into components. This makes client-side
1812 # preferences easier to specify since you can prefer a single
1808 # preferences easier to specify since you can prefer a single
1813 # component of the BUNDLESPEC.
1809 # component of the BUNDLESPEC.
1814 if key == 'BUNDLESPEC':
1810 if key == 'BUNDLESPEC':
1815 try:
1811 try:
1816 comp, version, params = parsebundlespec(repo, value,
1812 comp, version, params = parsebundlespec(repo, value,
1817 externalnames=True)
1813 externalnames=True)
1818 attrs['COMPRESSION'] = comp
1814 attrs['COMPRESSION'] = comp
1819 attrs['VERSION'] = version
1815 attrs['VERSION'] = version
1820 except error.InvalidBundleSpecification:
1816 except error.InvalidBundleSpecification:
1821 pass
1817 pass
1822 except error.UnsupportedBundleSpecification:
1818 except error.UnsupportedBundleSpecification:
1823 pass
1819 pass
1824
1820
1825 m.append(attrs)
1821 m.append(attrs)
1826
1822
1827 return m
1823 return m
1828
1824
1829 def filterclonebundleentries(repo, entries):
1825 def filterclonebundleentries(repo, entries):
1830 """Remove incompatible clone bundle manifest entries.
1826 """Remove incompatible clone bundle manifest entries.
1831
1827
1832 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
1828 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
1833 and returns a new list consisting of only the entries that this client
1829 and returns a new list consisting of only the entries that this client
1834 should be able to apply.
1830 should be able to apply.
1835
1831
1836 There is no guarantee we'll be able to apply all returned entries because
1832 There is no guarantee we'll be able to apply all returned entries because
1837 the metadata we use to filter on may be missing or wrong.
1833 the metadata we use to filter on may be missing or wrong.
1838 """
1834 """
1839 newentries = []
1835 newentries = []
1840 for entry in entries:
1836 for entry in entries:
1841 spec = entry.get('BUNDLESPEC')
1837 spec = entry.get('BUNDLESPEC')
1842 if spec:
1838 if spec:
1843 try:
1839 try:
1844 parsebundlespec(repo, spec, strict=True)
1840 parsebundlespec(repo, spec, strict=True)
1845 except error.InvalidBundleSpecification as e:
1841 except error.InvalidBundleSpecification as e:
1846 repo.ui.debug(str(e) + '\n')
1842 repo.ui.debug(str(e) + '\n')
1847 continue
1843 continue
1848 except error.UnsupportedBundleSpecification as e:
1844 except error.UnsupportedBundleSpecification as e:
1849 repo.ui.debug('filtering %s because unsupported bundle '
1845 repo.ui.debug('filtering %s because unsupported bundle '
1850 'spec: %s\n' % (entry['URL'], str(e)))
1846 'spec: %s\n' % (entry['URL'], str(e)))
1851 continue
1847 continue
1852
1848
1853 if 'REQUIRESNI' in entry and not sslutil.hassni:
1849 if 'REQUIRESNI' in entry and not sslutil.hassni:
1854 repo.ui.debug('filtering %s because SNI not supported\n' %
1850 repo.ui.debug('filtering %s because SNI not supported\n' %
1855 entry['URL'])
1851 entry['URL'])
1856 continue
1852 continue
1857
1853
1858 newentries.append(entry)
1854 newentries.append(entry)
1859
1855
1860 return newentries
1856 return newentries
1861
1857
1862 def sortclonebundleentries(ui, entries):
1858 def sortclonebundleentries(ui, entries):
1863 prefers = ui.configlist('ui', 'clonebundleprefers', default=[])
1859 prefers = ui.configlist('ui', 'clonebundleprefers', default=[])
1864 if not prefers:
1860 if not prefers:
1865 return list(entries)
1861 return list(entries)
1866
1862
1867 prefers = [p.split('=', 1) for p in prefers]
1863 prefers = [p.split('=', 1) for p in prefers]
1868
1864
1869 # Our sort function.
1865 # Our sort function.
1870 def compareentry(a, b):
1866 def compareentry(a, b):
1871 for prefkey, prefvalue in prefers:
1867 for prefkey, prefvalue in prefers:
1872 avalue = a.get(prefkey)
1868 avalue = a.get(prefkey)
1873 bvalue = b.get(prefkey)
1869 bvalue = b.get(prefkey)
1874
1870
1875 # Special case for b missing attribute and a matches exactly.
1871 # Special case for b missing attribute and a matches exactly.
1876 if avalue is not None and bvalue is None and avalue == prefvalue:
1872 if avalue is not None and bvalue is None and avalue == prefvalue:
1877 return -1
1873 return -1
1878
1874
1879 # Special case for a missing attribute and b matches exactly.
1875 # Special case for a missing attribute and b matches exactly.
1880 if bvalue is not None and avalue is None and bvalue == prefvalue:
1876 if bvalue is not None and avalue is None and bvalue == prefvalue:
1881 return 1
1877 return 1
1882
1878
1883 # We can't compare unless attribute present on both.
1879 # We can't compare unless attribute present on both.
1884 if avalue is None or bvalue is None:
1880 if avalue is None or bvalue is None:
1885 continue
1881 continue
1886
1882
1887 # Same values should fall back to next attribute.
1883 # Same values should fall back to next attribute.
1888 if avalue == bvalue:
1884 if avalue == bvalue:
1889 continue
1885 continue
1890
1886
1891 # Exact matches come first.
1887 # Exact matches come first.
1892 if avalue == prefvalue:
1888 if avalue == prefvalue:
1893 return -1
1889 return -1
1894 if bvalue == prefvalue:
1890 if bvalue == prefvalue:
1895 return 1
1891 return 1
1896
1892
1897 # Fall back to next attribute.
1893 # Fall back to next attribute.
1898 continue
1894 continue
1899
1895
1900 # If we got here we couldn't sort by attributes and prefers. Fall
1896 # If we got here we couldn't sort by attributes and prefers. Fall
1901 # back to index order.
1897 # back to index order.
1902 return 0
1898 return 0
1903
1899
1904 return sorted(entries, cmp=compareentry)
1900 return sorted(entries, cmp=compareentry)
1905
1901
1906 def trypullbundlefromurl(ui, repo, url):
1902 def trypullbundlefromurl(ui, repo, url):
1907 """Attempt to apply a bundle from a URL."""
1903 """Attempt to apply a bundle from a URL."""
1908 lock = repo.lock()
1904 lock = repo.lock()
1909 try:
1905 try:
1910 tr = repo.transaction('bundleurl')
1906 tr = repo.transaction('bundleurl')
1911 try:
1907 try:
1912 try:
1908 try:
1913 fh = urlmod.open(ui, url)
1909 fh = urlmod.open(ui, url)
1914 cg = readbundle(ui, fh, 'stream')
1910 cg = readbundle(ui, fh, 'stream')
1915
1911
1916 if isinstance(cg, bundle2.unbundle20):
1912 if isinstance(cg, bundle2.unbundle20):
1917 bundle2.processbundle(repo, cg, lambda: tr)
1913 bundle2.processbundle(repo, cg, lambda: tr)
1918 elif isinstance(cg, streamclone.streamcloneapplier):
1914 elif isinstance(cg, streamclone.streamcloneapplier):
1919 cg.apply(repo)
1915 cg.apply(repo)
1920 else:
1916 else:
1921 cg.apply(repo, 'clonebundles', url)
1917 cg.apply(repo, 'clonebundles', url)
1922 tr.close()
1918 tr.close()
1923 return True
1919 return True
1924 except urllib2.HTTPError as e:
1920 except urllib2.HTTPError as e:
1925 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
1921 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
1926 except urllib2.URLError as e:
1922 except urllib2.URLError as e:
1927 ui.warn(_('error fetching bundle: %s\n') % e.reason[1])
1923 ui.warn(_('error fetching bundle: %s\n') % e.reason[1])
1928
1924
1929 return False
1925 return False
1930 finally:
1926 finally:
1931 tr.release()
1927 tr.release()
1932 finally:
1928 finally:
1933 lock.release()
1929 lock.release()
@@ -1,1982 +1,1982 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
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 errno
10 import errno
11 import inspect
11 import inspect
12 import os
12 import os
13 import random
13 import random
14 import time
14 import time
15 import urllib
15 import urllib
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 hex,
20 hex,
21 nullid,
21 nullid,
22 short,
22 short,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from . import (
25 from . import (
26 bookmarks,
26 bookmarks,
27 branchmap,
27 branchmap,
28 bundle2,
28 bundle2,
29 changegroup,
29 changegroup,
30 changelog,
30 changelog,
31 cmdutil,
31 cmdutil,
32 context,
32 context,
33 dirstate,
33 dirstate,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 filelog,
38 filelog,
39 hook,
39 hook,
40 lock as lockmod,
40 lock as lockmod,
41 manifest,
41 manifest,
42 match as matchmod,
42 match as matchmod,
43 merge as mergemod,
43 merge as mergemod,
44 namespaces,
44 namespaces,
45 obsolete,
45 obsolete,
46 pathutil,
46 pathutil,
47 peer,
47 peer,
48 phases,
48 phases,
49 pushkey,
49 pushkey,
50 repoview,
50 repoview,
51 revset,
51 revset,
52 scmutil,
52 scmutil,
53 store,
53 store,
54 subrepo,
54 subrepo,
55 tags as tagsmod,
55 tags as tagsmod,
56 transaction,
56 transaction,
57 util,
57 util,
58 )
58 )
59
59
60 release = lockmod.release
60 release = lockmod.release
61 propertycache = util.propertycache
61 propertycache = util.propertycache
62 filecache = scmutil.filecache
62 filecache = scmutil.filecache
63
63
64 class repofilecache(filecache):
64 class repofilecache(filecache):
65 """All filecache usage on repo are done for logic that should be unfiltered
65 """All filecache usage on repo are done for logic that should be unfiltered
66 """
66 """
67
67
68 def __get__(self, repo, type=None):
68 def __get__(self, repo, type=None):
69 return super(repofilecache, self).__get__(repo.unfiltered(), type)
69 return super(repofilecache, self).__get__(repo.unfiltered(), type)
70 def __set__(self, repo, value):
70 def __set__(self, repo, value):
71 return super(repofilecache, self).__set__(repo.unfiltered(), value)
71 return super(repofilecache, self).__set__(repo.unfiltered(), value)
72 def __delete__(self, repo):
72 def __delete__(self, repo):
73 return super(repofilecache, self).__delete__(repo.unfiltered())
73 return super(repofilecache, self).__delete__(repo.unfiltered())
74
74
75 class storecache(repofilecache):
75 class storecache(repofilecache):
76 """filecache for files in the store"""
76 """filecache for files in the store"""
77 def join(self, obj, fname):
77 def join(self, obj, fname):
78 return obj.sjoin(fname)
78 return obj.sjoin(fname)
79
79
80 class unfilteredpropertycache(propertycache):
80 class unfilteredpropertycache(propertycache):
81 """propertycache that apply to unfiltered repo only"""
81 """propertycache that apply to unfiltered repo only"""
82
82
83 def __get__(self, repo, type=None):
83 def __get__(self, repo, type=None):
84 unfi = repo.unfiltered()
84 unfi = repo.unfiltered()
85 if unfi is repo:
85 if unfi is repo:
86 return super(unfilteredpropertycache, self).__get__(unfi)
86 return super(unfilteredpropertycache, self).__get__(unfi)
87 return getattr(unfi, self.name)
87 return getattr(unfi, self.name)
88
88
89 class filteredpropertycache(propertycache):
89 class filteredpropertycache(propertycache):
90 """propertycache that must take filtering in account"""
90 """propertycache that must take filtering in account"""
91
91
92 def cachevalue(self, obj, value):
92 def cachevalue(self, obj, value):
93 object.__setattr__(obj, self.name, value)
93 object.__setattr__(obj, self.name, value)
94
94
95
95
96 def hasunfilteredcache(repo, name):
96 def hasunfilteredcache(repo, name):
97 """check if a repo has an unfilteredpropertycache value for <name>"""
97 """check if a repo has an unfilteredpropertycache value for <name>"""
98 return name in vars(repo.unfiltered())
98 return name in vars(repo.unfiltered())
99
99
100 def unfilteredmethod(orig):
100 def unfilteredmethod(orig):
101 """decorate method that always need to be run on unfiltered version"""
101 """decorate method that always need to be run on unfiltered version"""
102 def wrapper(repo, *args, **kwargs):
102 def wrapper(repo, *args, **kwargs):
103 return orig(repo.unfiltered(), *args, **kwargs)
103 return orig(repo.unfiltered(), *args, **kwargs)
104 return wrapper
104 return wrapper
105
105
106 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
106 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
107 'unbundle'))
107 'unbundle'))
108 legacycaps = moderncaps.union(set(['changegroupsubset']))
108 legacycaps = moderncaps.union(set(['changegroupsubset']))
109
109
110 class localpeer(peer.peerrepository):
110 class localpeer(peer.peerrepository):
111 '''peer for a local repo; reflects only the most recent API'''
111 '''peer for a local repo; reflects only the most recent API'''
112
112
113 def __init__(self, repo, caps=moderncaps):
113 def __init__(self, repo, caps=moderncaps):
114 peer.peerrepository.__init__(self)
114 peer.peerrepository.__init__(self)
115 self._repo = repo.filtered('served')
115 self._repo = repo.filtered('served')
116 self.ui = repo.ui
116 self.ui = repo.ui
117 self._caps = repo._restrictcapabilities(caps)
117 self._caps = repo._restrictcapabilities(caps)
118 self.requirements = repo.requirements
118 self.requirements = repo.requirements
119 self.supportedformats = repo.supportedformats
119 self.supportedformats = repo.supportedformats
120
120
121 def close(self):
121 def close(self):
122 self._repo.close()
122 self._repo.close()
123
123
124 def _capabilities(self):
124 def _capabilities(self):
125 return self._caps
125 return self._caps
126
126
127 def local(self):
127 def local(self):
128 return self._repo
128 return self._repo
129
129
130 def canpush(self):
130 def canpush(self):
131 return True
131 return True
132
132
133 def url(self):
133 def url(self):
134 return self._repo.url()
134 return self._repo.url()
135
135
136 def lookup(self, key):
136 def lookup(self, key):
137 return self._repo.lookup(key)
137 return self._repo.lookup(key)
138
138
139 def branchmap(self):
139 def branchmap(self):
140 return self._repo.branchmap()
140 return self._repo.branchmap()
141
141
142 def heads(self):
142 def heads(self):
143 return self._repo.heads()
143 return self._repo.heads()
144
144
145 def known(self, nodes):
145 def known(self, nodes):
146 return self._repo.known(nodes)
146 return self._repo.known(nodes)
147
147
148 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
148 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
149 **kwargs):
149 **kwargs):
150 cg = exchange.getbundle(self._repo, source, heads=heads,
150 cg = exchange.getbundle(self._repo, source, heads=heads,
151 common=common, bundlecaps=bundlecaps, **kwargs)
151 common=common, bundlecaps=bundlecaps, **kwargs)
152 if bundlecaps is not None and 'HG20' in bundlecaps:
152 if bundlecaps is not None and 'HG20' in bundlecaps:
153 # When requesting a bundle2, getbundle returns a stream to make the
153 # When requesting a bundle2, getbundle returns a stream to make the
154 # wire level function happier. We need to build a proper object
154 # wire level function happier. We need to build a proper object
155 # from it in local peer.
155 # from it in local peer.
156 cg = bundle2.getunbundler(self.ui, cg)
156 cg = bundle2.getunbundler(self.ui, cg)
157 return cg
157 return cg
158
158
159 # TODO We might want to move the next two calls into legacypeer and add
159 # TODO We might want to move the next two calls into legacypeer and add
160 # unbundle instead.
160 # unbundle instead.
161
161
162 def unbundle(self, cg, heads, url):
162 def unbundle(self, cg, heads, url):
163 """apply a bundle on a repo
163 """apply a bundle on a repo
164
164
165 This function handles the repo locking itself."""
165 This function handles the repo locking itself."""
166 try:
166 try:
167 try:
167 try:
168 cg = exchange.readbundle(self.ui, cg, None)
168 cg = exchange.readbundle(self.ui, cg, None)
169 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
169 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
170 if util.safehasattr(ret, 'getchunks'):
170 if util.safehasattr(ret, 'getchunks'):
171 # This is a bundle20 object, turn it into an unbundler.
171 # This is a bundle20 object, turn it into an unbundler.
172 # This little dance should be dropped eventually when the
172 # This little dance should be dropped eventually when the
173 # API is finally improved.
173 # API is finally improved.
174 stream = util.chunkbuffer(ret.getchunks())
174 stream = util.chunkbuffer(ret.getchunks())
175 ret = bundle2.getunbundler(self.ui, stream)
175 ret = bundle2.getunbundler(self.ui, stream)
176 return ret
176 return ret
177 except Exception as exc:
177 except Exception as exc:
178 # If the exception contains output salvaged from a bundle2
178 # If the exception contains output salvaged from a bundle2
179 # reply, we need to make sure it is printed before continuing
179 # reply, we need to make sure it is printed before continuing
180 # to fail. So we build a bundle2 with such output and consume
180 # to fail. So we build a bundle2 with such output and consume
181 # it directly.
181 # it directly.
182 #
182 #
183 # This is not very elegant but allows a "simple" solution for
183 # This is not very elegant but allows a "simple" solution for
184 # issue4594
184 # issue4594
185 output = getattr(exc, '_bundle2salvagedoutput', ())
185 output = getattr(exc, '_bundle2salvagedoutput', ())
186 if output:
186 if output:
187 bundler = bundle2.bundle20(self._repo.ui)
187 bundler = bundle2.bundle20(self._repo.ui)
188 for out in output:
188 for out in output:
189 bundler.addpart(out)
189 bundler.addpart(out)
190 stream = util.chunkbuffer(bundler.getchunks())
190 stream = util.chunkbuffer(bundler.getchunks())
191 b = bundle2.getunbundler(self.ui, stream)
191 b = bundle2.getunbundler(self.ui, stream)
192 bundle2.processbundle(self._repo, b)
192 bundle2.processbundle(self._repo, b)
193 raise
193 raise
194 except error.PushRaced as exc:
194 except error.PushRaced as exc:
195 raise error.ResponseError(_('push failed:'), str(exc))
195 raise error.ResponseError(_('push failed:'), str(exc))
196
196
197 def lock(self):
197 def lock(self):
198 return self._repo.lock()
198 return self._repo.lock()
199
199
200 def addchangegroup(self, cg, source, url):
200 def addchangegroup(self, cg, source, url):
201 return cg.apply(self._repo, source, url)
201 return cg.apply(self._repo, source, url)
202
202
203 def pushkey(self, namespace, key, old, new):
203 def pushkey(self, namespace, key, old, new):
204 return self._repo.pushkey(namespace, key, old, new)
204 return self._repo.pushkey(namespace, key, old, new)
205
205
206 def listkeys(self, namespace):
206 def listkeys(self, namespace):
207 return self._repo.listkeys(namespace)
207 return self._repo.listkeys(namespace)
208
208
209 def debugwireargs(self, one, two, three=None, four=None, five=None):
209 def debugwireargs(self, one, two, three=None, four=None, five=None):
210 '''used to test argument passing over the wire'''
210 '''used to test argument passing over the wire'''
211 return "%s %s %s %s %s" % (one, two, three, four, five)
211 return "%s %s %s %s %s" % (one, two, three, four, five)
212
212
213 class locallegacypeer(localpeer):
213 class locallegacypeer(localpeer):
214 '''peer extension which implements legacy methods too; used for tests with
214 '''peer extension which implements legacy methods too; used for tests with
215 restricted capabilities'''
215 restricted capabilities'''
216
216
217 def __init__(self, repo):
217 def __init__(self, repo):
218 localpeer.__init__(self, repo, caps=legacycaps)
218 localpeer.__init__(self, repo, caps=legacycaps)
219
219
220 def branches(self, nodes):
220 def branches(self, nodes):
221 return self._repo.branches(nodes)
221 return self._repo.branches(nodes)
222
222
223 def between(self, pairs):
223 def between(self, pairs):
224 return self._repo.between(pairs)
224 return self._repo.between(pairs)
225
225
226 def changegroup(self, basenodes, source):
226 def changegroup(self, basenodes, source):
227 return changegroup.changegroup(self._repo, basenodes, source)
227 return changegroup.changegroup(self._repo, basenodes, source)
228
228
229 def changegroupsubset(self, bases, heads, source):
229 def changegroupsubset(self, bases, heads, source):
230 return changegroup.changegroupsubset(self._repo, bases, heads, source)
230 return changegroup.changegroupsubset(self._repo, bases, heads, source)
231
231
232 class localrepository(object):
232 class localrepository(object):
233
233
234 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
234 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
235 'manifestv2'))
235 'manifestv2'))
236 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
236 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
237 'dotencode'))
237 'dotencode'))
238 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
238 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
239 filtername = None
239 filtername = None
240
240
241 # a list of (ui, featureset) functions.
241 # a list of (ui, featureset) functions.
242 # only functions defined in module of enabled extensions are invoked
242 # only functions defined in module of enabled extensions are invoked
243 featuresetupfuncs = set()
243 featuresetupfuncs = set()
244
244
245 def __init__(self, baseui, path=None, create=False):
245 def __init__(self, baseui, path=None, create=False):
246 self.requirements = set()
246 self.requirements = set()
247 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
247 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
248 self.wopener = self.wvfs
248 self.wopener = self.wvfs
249 self.root = self.wvfs.base
249 self.root = self.wvfs.base
250 self.path = self.wvfs.join(".hg")
250 self.path = self.wvfs.join(".hg")
251 self.origroot = path
251 self.origroot = path
252 self.auditor = pathutil.pathauditor(self.root, self._checknested)
252 self.auditor = pathutil.pathauditor(self.root, self._checknested)
253 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
253 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
254 realfs=False)
254 realfs=False)
255 self.vfs = scmutil.vfs(self.path)
255 self.vfs = scmutil.vfs(self.path)
256 self.opener = self.vfs
256 self.opener = self.vfs
257 self.baseui = baseui
257 self.baseui = baseui
258 self.ui = baseui.copy()
258 self.ui = baseui.copy()
259 self.ui.copy = baseui.copy # prevent copying repo configuration
259 self.ui.copy = baseui.copy # prevent copying repo configuration
260 # A list of callback to shape the phase if no data were found.
260 # A list of callback to shape the phase if no data were found.
261 # Callback are in the form: func(repo, roots) --> processed root.
261 # Callback are in the form: func(repo, roots) --> processed root.
262 # This list it to be filled by extension during repo setup
262 # This list it to be filled by extension during repo setup
263 self._phasedefaults = []
263 self._phasedefaults = []
264 try:
264 try:
265 self.ui.readconfig(self.join("hgrc"), self.root)
265 self.ui.readconfig(self.join("hgrc"), self.root)
266 extensions.loadall(self.ui)
266 extensions.loadall(self.ui)
267 except IOError:
267 except IOError:
268 pass
268 pass
269
269
270 if self.featuresetupfuncs:
270 if self.featuresetupfuncs:
271 self.supported = set(self._basesupported) # use private copy
271 self.supported = set(self._basesupported) # use private copy
272 extmods = set(m.__name__ for n, m
272 extmods = set(m.__name__ for n, m
273 in extensions.extensions(self.ui))
273 in extensions.extensions(self.ui))
274 for setupfunc in self.featuresetupfuncs:
274 for setupfunc in self.featuresetupfuncs:
275 if setupfunc.__module__ in extmods:
275 if setupfunc.__module__ in extmods:
276 setupfunc(self.ui, self.supported)
276 setupfunc(self.ui, self.supported)
277 else:
277 else:
278 self.supported = self._basesupported
278 self.supported = self._basesupported
279
279
280 if not self.vfs.isdir():
280 if not self.vfs.isdir():
281 if create:
281 if create:
282 self.requirements = newreporequirements(self)
282 self.requirements = newreporequirements(self)
283
283
284 if not self.wvfs.exists():
284 if not self.wvfs.exists():
285 self.wvfs.makedirs()
285 self.wvfs.makedirs()
286 self.vfs.makedir(notindexed=True)
286 self.vfs.makedir(notindexed=True)
287
287
288 if 'store' in self.requirements:
288 if 'store' in self.requirements:
289 self.vfs.mkdir("store")
289 self.vfs.mkdir("store")
290
290
291 # create an invalid changelog
291 # create an invalid changelog
292 self.vfs.append(
292 self.vfs.append(
293 "00changelog.i",
293 "00changelog.i",
294 '\0\0\0\2' # represents revlogv2
294 '\0\0\0\2' # represents revlogv2
295 ' dummy changelog to prevent using the old repo layout'
295 ' dummy changelog to prevent using the old repo layout'
296 )
296 )
297 else:
297 else:
298 raise error.RepoError(_("repository %s not found") % path)
298 raise error.RepoError(_("repository %s not found") % path)
299 elif create:
299 elif create:
300 raise error.RepoError(_("repository %s already exists") % path)
300 raise error.RepoError(_("repository %s already exists") % path)
301 else:
301 else:
302 try:
302 try:
303 self.requirements = scmutil.readrequires(
303 self.requirements = scmutil.readrequires(
304 self.vfs, self.supported)
304 self.vfs, self.supported)
305 except IOError as inst:
305 except IOError as inst:
306 if inst.errno != errno.ENOENT:
306 if inst.errno != errno.ENOENT:
307 raise
307 raise
308
308
309 self.sharedpath = self.path
309 self.sharedpath = self.path
310 try:
310 try:
311 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
311 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
312 realpath=True)
312 realpath=True)
313 s = vfs.base
313 s = vfs.base
314 if not vfs.exists():
314 if not vfs.exists():
315 raise error.RepoError(
315 raise error.RepoError(
316 _('.hg/sharedpath points to nonexistent directory %s') % s)
316 _('.hg/sharedpath points to nonexistent directory %s') % s)
317 self.sharedpath = s
317 self.sharedpath = s
318 except IOError as inst:
318 except IOError as inst:
319 if inst.errno != errno.ENOENT:
319 if inst.errno != errno.ENOENT:
320 raise
320 raise
321
321
322 self.store = store.store(
322 self.store = store.store(
323 self.requirements, self.sharedpath, scmutil.vfs)
323 self.requirements, self.sharedpath, scmutil.vfs)
324 self.spath = self.store.path
324 self.spath = self.store.path
325 self.svfs = self.store.vfs
325 self.svfs = self.store.vfs
326 self.sjoin = self.store.join
326 self.sjoin = self.store.join
327 self.vfs.createmode = self.store.createmode
327 self.vfs.createmode = self.store.createmode
328 self._applyopenerreqs()
328 self._applyopenerreqs()
329 if create:
329 if create:
330 self._writerequirements()
330 self._writerequirements()
331
331
332 self._dirstatevalidatewarned = False
332 self._dirstatevalidatewarned = False
333
333
334 self._branchcaches = {}
334 self._branchcaches = {}
335 self._revbranchcache = None
335 self._revbranchcache = None
336 self.filterpats = {}
336 self.filterpats = {}
337 self._datafilters = {}
337 self._datafilters = {}
338 self._transref = self._lockref = self._wlockref = None
338 self._transref = self._lockref = self._wlockref = None
339
339
340 # A cache for various files under .hg/ that tracks file changes,
340 # A cache for various files under .hg/ that tracks file changes,
341 # (used by the filecache decorator)
341 # (used by the filecache decorator)
342 #
342 #
343 # Maps a property name to its util.filecacheentry
343 # Maps a property name to its util.filecacheentry
344 self._filecache = {}
344 self._filecache = {}
345
345
346 # hold sets of revision to be filtered
346 # hold sets of revision to be filtered
347 # should be cleared when something might have changed the filter value:
347 # should be cleared when something might have changed the filter value:
348 # - new changesets,
348 # - new changesets,
349 # - phase change,
349 # - phase change,
350 # - new obsolescence marker,
350 # - new obsolescence marker,
351 # - working directory parent change,
351 # - working directory parent change,
352 # - bookmark changes
352 # - bookmark changes
353 self.filteredrevcache = {}
353 self.filteredrevcache = {}
354
354
355 # generic mapping between names and nodes
355 # generic mapping between names and nodes
356 self.names = namespaces.namespaces()
356 self.names = namespaces.namespaces()
357
357
358 def close(self):
358 def close(self):
359 self._writecaches()
359 self._writecaches()
360
360
361 def _writecaches(self):
361 def _writecaches(self):
362 if self._revbranchcache:
362 if self._revbranchcache:
363 self._revbranchcache.write()
363 self._revbranchcache.write()
364
364
365 def _restrictcapabilities(self, caps):
365 def _restrictcapabilities(self, caps):
366 if self.ui.configbool('experimental', 'bundle2-advertise', True):
366 if self.ui.configbool('experimental', 'bundle2-advertise', True):
367 caps = set(caps)
367 caps = set(caps)
368 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
368 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
369 caps.add('bundle2=' + urllib.quote(capsblob))
369 caps.add('bundle2=' + urllib.quote(capsblob))
370 return caps
370 return caps
371
371
372 def _applyopenerreqs(self):
372 def _applyopenerreqs(self):
373 self.svfs.options = dict((r, 1) for r in self.requirements
373 self.svfs.options = dict((r, 1) for r in self.requirements
374 if r in self.openerreqs)
374 if r in self.openerreqs)
375 # experimental config: format.chunkcachesize
375 # experimental config: format.chunkcachesize
376 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
376 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
377 if chunkcachesize is not None:
377 if chunkcachesize is not None:
378 self.svfs.options['chunkcachesize'] = chunkcachesize
378 self.svfs.options['chunkcachesize'] = chunkcachesize
379 # experimental config: format.maxchainlen
379 # experimental config: format.maxchainlen
380 maxchainlen = self.ui.configint('format', 'maxchainlen')
380 maxchainlen = self.ui.configint('format', 'maxchainlen')
381 if maxchainlen is not None:
381 if maxchainlen is not None:
382 self.svfs.options['maxchainlen'] = maxchainlen
382 self.svfs.options['maxchainlen'] = maxchainlen
383 # experimental config: format.manifestcachesize
383 # experimental config: format.manifestcachesize
384 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
384 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
385 if manifestcachesize is not None:
385 if manifestcachesize is not None:
386 self.svfs.options['manifestcachesize'] = manifestcachesize
386 self.svfs.options['manifestcachesize'] = manifestcachesize
387 # experimental config: format.aggressivemergedeltas
387 # experimental config: format.aggressivemergedeltas
388 aggressivemergedeltas = self.ui.configbool('format',
388 aggressivemergedeltas = self.ui.configbool('format',
389 'aggressivemergedeltas', False)
389 'aggressivemergedeltas', False)
390 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
390 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
391 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
391 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
392
392
393 def _writerequirements(self):
393 def _writerequirements(self):
394 scmutil.writerequires(self.vfs, self.requirements)
394 scmutil.writerequires(self.vfs, self.requirements)
395
395
396 def _checknested(self, path):
396 def _checknested(self, path):
397 """Determine if path is a legal nested repository."""
397 """Determine if path is a legal nested repository."""
398 if not path.startswith(self.root):
398 if not path.startswith(self.root):
399 return False
399 return False
400 subpath = path[len(self.root) + 1:]
400 subpath = path[len(self.root) + 1:]
401 normsubpath = util.pconvert(subpath)
401 normsubpath = util.pconvert(subpath)
402
402
403 # XXX: Checking against the current working copy is wrong in
403 # XXX: Checking against the current working copy is wrong in
404 # the sense that it can reject things like
404 # the sense that it can reject things like
405 #
405 #
406 # $ hg cat -r 10 sub/x.txt
406 # $ hg cat -r 10 sub/x.txt
407 #
407 #
408 # if sub/ is no longer a subrepository in the working copy
408 # if sub/ is no longer a subrepository in the working copy
409 # parent revision.
409 # parent revision.
410 #
410 #
411 # However, it can of course also allow things that would have
411 # However, it can of course also allow things that would have
412 # been rejected before, such as the above cat command if sub/
412 # been rejected before, such as the above cat command if sub/
413 # is a subrepository now, but was a normal directory before.
413 # is a subrepository now, but was a normal directory before.
414 # The old path auditor would have rejected by mistake since it
414 # The old path auditor would have rejected by mistake since it
415 # panics when it sees sub/.hg/.
415 # panics when it sees sub/.hg/.
416 #
416 #
417 # All in all, checking against the working copy seems sensible
417 # All in all, checking against the working copy seems sensible
418 # since we want to prevent access to nested repositories on
418 # since we want to prevent access to nested repositories on
419 # the filesystem *now*.
419 # the filesystem *now*.
420 ctx = self[None]
420 ctx = self[None]
421 parts = util.splitpath(subpath)
421 parts = util.splitpath(subpath)
422 while parts:
422 while parts:
423 prefix = '/'.join(parts)
423 prefix = '/'.join(parts)
424 if prefix in ctx.substate:
424 if prefix in ctx.substate:
425 if prefix == normsubpath:
425 if prefix == normsubpath:
426 return True
426 return True
427 else:
427 else:
428 sub = ctx.sub(prefix)
428 sub = ctx.sub(prefix)
429 return sub.checknested(subpath[len(prefix) + 1:])
429 return sub.checknested(subpath[len(prefix) + 1:])
430 else:
430 else:
431 parts.pop()
431 parts.pop()
432 return False
432 return False
433
433
434 def peer(self):
434 def peer(self):
435 return localpeer(self) # not cached to avoid reference cycle
435 return localpeer(self) # not cached to avoid reference cycle
436
436
437 def unfiltered(self):
437 def unfiltered(self):
438 """Return unfiltered version of the repository
438 """Return unfiltered version of the repository
439
439
440 Intended to be overwritten by filtered repo."""
440 Intended to be overwritten by filtered repo."""
441 return self
441 return self
442
442
443 def filtered(self, name):
443 def filtered(self, name):
444 """Return a filtered version of a repository"""
444 """Return a filtered version of a repository"""
445 # build a new class with the mixin and the current class
445 # build a new class with the mixin and the current class
446 # (possibly subclass of the repo)
446 # (possibly subclass of the repo)
447 class proxycls(repoview.repoview, self.unfiltered().__class__):
447 class proxycls(repoview.repoview, self.unfiltered().__class__):
448 pass
448 pass
449 return proxycls(self, name)
449 return proxycls(self, name)
450
450
451 @repofilecache('bookmarks', 'bookmarks.current')
451 @repofilecache('bookmarks', 'bookmarks.current')
452 def _bookmarks(self):
452 def _bookmarks(self):
453 return bookmarks.bmstore(self)
453 return bookmarks.bmstore(self)
454
454
455 @property
455 @property
456 def _activebookmark(self):
456 def _activebookmark(self):
457 return self._bookmarks.active
457 return self._bookmarks.active
458
458
459 def bookmarkheads(self, bookmark):
459 def bookmarkheads(self, bookmark):
460 name = bookmark.split('@', 1)[0]
460 name = bookmark.split('@', 1)[0]
461 heads = []
461 heads = []
462 for mark, n in self._bookmarks.iteritems():
462 for mark, n in self._bookmarks.iteritems():
463 if mark.split('@', 1)[0] == name:
463 if mark.split('@', 1)[0] == name:
464 heads.append(n)
464 heads.append(n)
465 return heads
465 return heads
466
466
467 # _phaserevs and _phasesets depend on changelog. what we need is to
467 # _phaserevs and _phasesets depend on changelog. what we need is to
468 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
468 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
469 # can't be easily expressed in filecache mechanism.
469 # can't be easily expressed in filecache mechanism.
470 @storecache('phaseroots', '00changelog.i')
470 @storecache('phaseroots', '00changelog.i')
471 def _phasecache(self):
471 def _phasecache(self):
472 return phases.phasecache(self, self._phasedefaults)
472 return phases.phasecache(self, self._phasedefaults)
473
473
474 @storecache('obsstore')
474 @storecache('obsstore')
475 def obsstore(self):
475 def obsstore(self):
476 # read default format for new obsstore.
476 # read default format for new obsstore.
477 # developer config: format.obsstore-version
477 # developer config: format.obsstore-version
478 defaultformat = self.ui.configint('format', 'obsstore-version', None)
478 defaultformat = self.ui.configint('format', 'obsstore-version', None)
479 # rely on obsstore class default when possible.
479 # rely on obsstore class default when possible.
480 kwargs = {}
480 kwargs = {}
481 if defaultformat is not None:
481 if defaultformat is not None:
482 kwargs['defaultformat'] = defaultformat
482 kwargs['defaultformat'] = defaultformat
483 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
483 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
484 store = obsolete.obsstore(self.svfs, readonly=readonly,
484 store = obsolete.obsstore(self.svfs, readonly=readonly,
485 **kwargs)
485 **kwargs)
486 if store and readonly:
486 if store and readonly:
487 self.ui.warn(
487 self.ui.warn(
488 _('obsolete feature not enabled but %i markers found!\n')
488 _('obsolete feature not enabled but %i markers found!\n')
489 % len(list(store)))
489 % len(list(store)))
490 return store
490 return store
491
491
492 @storecache('00changelog.i')
492 @storecache('00changelog.i')
493 def changelog(self):
493 def changelog(self):
494 c = changelog.changelog(self.svfs)
494 c = changelog.changelog(self.svfs)
495 if 'HG_PENDING' in os.environ:
495 if 'HG_PENDING' in os.environ:
496 p = os.environ['HG_PENDING']
496 p = os.environ['HG_PENDING']
497 if p.startswith(self.root):
497 if p.startswith(self.root):
498 c.readpending('00changelog.i.a')
498 c.readpending('00changelog.i.a')
499 return c
499 return c
500
500
501 @storecache('00manifest.i')
501 @storecache('00manifest.i')
502 def manifest(self):
502 def manifest(self):
503 return manifest.manifest(self.svfs)
503 return manifest.manifest(self.svfs)
504
504
505 def dirlog(self, dir):
505 def dirlog(self, dir):
506 return self.manifest.dirlog(dir)
506 return self.manifest.dirlog(dir)
507
507
508 @repofilecache('dirstate')
508 @repofilecache('dirstate')
509 def dirstate(self):
509 def dirstate(self):
510 return dirstate.dirstate(self.vfs, self.ui, self.root,
510 return dirstate.dirstate(self.vfs, self.ui, self.root,
511 self._dirstatevalidate)
511 self._dirstatevalidate)
512
512
513 def _dirstatevalidate(self, node):
513 def _dirstatevalidate(self, node):
514 try:
514 try:
515 self.changelog.rev(node)
515 self.changelog.rev(node)
516 return node
516 return node
517 except error.LookupError:
517 except error.LookupError:
518 if not self._dirstatevalidatewarned:
518 if not self._dirstatevalidatewarned:
519 self._dirstatevalidatewarned = True
519 self._dirstatevalidatewarned = True
520 self.ui.warn(_("warning: ignoring unknown"
520 self.ui.warn(_("warning: ignoring unknown"
521 " working parent %s!\n") % short(node))
521 " working parent %s!\n") % short(node))
522 return nullid
522 return nullid
523
523
524 def __getitem__(self, changeid):
524 def __getitem__(self, changeid):
525 if changeid is None or changeid == wdirrev:
525 if changeid is None or changeid == wdirrev:
526 return context.workingctx(self)
526 return context.workingctx(self)
527 if isinstance(changeid, slice):
527 if isinstance(changeid, slice):
528 return [context.changectx(self, i)
528 return [context.changectx(self, i)
529 for i in xrange(*changeid.indices(len(self)))
529 for i in xrange(*changeid.indices(len(self)))
530 if i not in self.changelog.filteredrevs]
530 if i not in self.changelog.filteredrevs]
531 return context.changectx(self, changeid)
531 return context.changectx(self, changeid)
532
532
533 def __contains__(self, changeid):
533 def __contains__(self, changeid):
534 try:
534 try:
535 self[changeid]
535 self[changeid]
536 return True
536 return True
537 except error.RepoLookupError:
537 except error.RepoLookupError:
538 return False
538 return False
539
539
540 def __nonzero__(self):
540 def __nonzero__(self):
541 return True
541 return True
542
542
543 def __len__(self):
543 def __len__(self):
544 return len(self.changelog)
544 return len(self.changelog)
545
545
546 def __iter__(self):
546 def __iter__(self):
547 return iter(self.changelog)
547 return iter(self.changelog)
548
548
549 def revs(self, expr, *args):
549 def revs(self, expr, *args):
550 '''Find revisions matching a revset.
550 '''Find revisions matching a revset.
551
551
552 The revset is specified as a string ``expr`` that may contain
552 The revset is specified as a string ``expr`` that may contain
553 %-formatting to escape certain types. See ``revset.formatspec``.
553 %-formatting to escape certain types. See ``revset.formatspec``.
554
554
555 Return a revset.abstractsmartset, which is a list-like interface
555 Return a revset.abstractsmartset, which is a list-like interface
556 that contains integer revisions.
556 that contains integer revisions.
557 '''
557 '''
558 expr = revset.formatspec(expr, *args)
558 expr = revset.formatspec(expr, *args)
559 m = revset.match(None, expr)
559 m = revset.match(None, expr)
560 return m(self)
560 return m(self)
561
561
562 def set(self, expr, *args):
562 def set(self, expr, *args):
563 '''Find revisions matching a revset and emit changectx instances.
563 '''Find revisions matching a revset and emit changectx instances.
564
564
565 This is a convenience wrapper around ``revs()`` that iterates the
565 This is a convenience wrapper around ``revs()`` that iterates the
566 result and is a generator of changectx instances.
566 result and is a generator of changectx instances.
567 '''
567 '''
568 for r in self.revs(expr, *args):
568 for r in self.revs(expr, *args):
569 yield self[r]
569 yield self[r]
570
570
571 def url(self):
571 def url(self):
572 return 'file:' + self.root
572 return 'file:' + self.root
573
573
574 def hook(self, name, throw=False, **args):
574 def hook(self, name, throw=False, **args):
575 """Call a hook, passing this repo instance.
575 """Call a hook, passing this repo instance.
576
576
577 This a convenience method to aid invoking hooks. Extensions likely
577 This a convenience method to aid invoking hooks. Extensions likely
578 won't call this unless they have registered a custom hook or are
578 won't call this unless they have registered a custom hook or are
579 replacing code that is expected to call a hook.
579 replacing code that is expected to call a hook.
580 """
580 """
581 return hook.hook(self.ui, self, name, throw, **args)
581 return hook.hook(self.ui, self, name, throw, **args)
582
582
583 @unfilteredmethod
583 @unfilteredmethod
584 def _tag(self, names, node, message, local, user, date, extra=None,
584 def _tag(self, names, node, message, local, user, date, extra=None,
585 editor=False):
585 editor=False):
586 if isinstance(names, str):
586 if isinstance(names, str):
587 names = (names,)
587 names = (names,)
588
588
589 branches = self.branchmap()
589 branches = self.branchmap()
590 for name in names:
590 for name in names:
591 self.hook('pretag', throw=True, node=hex(node), tag=name,
591 self.hook('pretag', throw=True, node=hex(node), tag=name,
592 local=local)
592 local=local)
593 if name in branches:
593 if name in branches:
594 self.ui.warn(_("warning: tag %s conflicts with existing"
594 self.ui.warn(_("warning: tag %s conflicts with existing"
595 " branch name\n") % name)
595 " branch name\n") % name)
596
596
597 def writetags(fp, names, munge, prevtags):
597 def writetags(fp, names, munge, prevtags):
598 fp.seek(0, 2)
598 fp.seek(0, 2)
599 if prevtags and prevtags[-1] != '\n':
599 if prevtags and prevtags[-1] != '\n':
600 fp.write('\n')
600 fp.write('\n')
601 for name in names:
601 for name in names:
602 if munge:
602 if munge:
603 m = munge(name)
603 m = munge(name)
604 else:
604 else:
605 m = name
605 m = name
606
606
607 if (self._tagscache.tagtypes and
607 if (self._tagscache.tagtypes and
608 name in self._tagscache.tagtypes):
608 name in self._tagscache.tagtypes):
609 old = self.tags().get(name, nullid)
609 old = self.tags().get(name, nullid)
610 fp.write('%s %s\n' % (hex(old), m))
610 fp.write('%s %s\n' % (hex(old), m))
611 fp.write('%s %s\n' % (hex(node), m))
611 fp.write('%s %s\n' % (hex(node), m))
612 fp.close()
612 fp.close()
613
613
614 prevtags = ''
614 prevtags = ''
615 if local:
615 if local:
616 try:
616 try:
617 fp = self.vfs('localtags', 'r+')
617 fp = self.vfs('localtags', 'r+')
618 except IOError:
618 except IOError:
619 fp = self.vfs('localtags', 'a')
619 fp = self.vfs('localtags', 'a')
620 else:
620 else:
621 prevtags = fp.read()
621 prevtags = fp.read()
622
622
623 # local tags are stored in the current charset
623 # local tags are stored in the current charset
624 writetags(fp, names, None, prevtags)
624 writetags(fp, names, None, prevtags)
625 for name in names:
625 for name in names:
626 self.hook('tag', node=hex(node), tag=name, local=local)
626 self.hook('tag', node=hex(node), tag=name, local=local)
627 return
627 return
628
628
629 try:
629 try:
630 fp = self.wfile('.hgtags', 'rb+')
630 fp = self.wfile('.hgtags', 'rb+')
631 except IOError as e:
631 except IOError as e:
632 if e.errno != errno.ENOENT:
632 if e.errno != errno.ENOENT:
633 raise
633 raise
634 fp = self.wfile('.hgtags', 'ab')
634 fp = self.wfile('.hgtags', 'ab')
635 else:
635 else:
636 prevtags = fp.read()
636 prevtags = fp.read()
637
637
638 # committed tags are stored in UTF-8
638 # committed tags are stored in UTF-8
639 writetags(fp, names, encoding.fromlocal, prevtags)
639 writetags(fp, names, encoding.fromlocal, prevtags)
640
640
641 fp.close()
641 fp.close()
642
642
643 self.invalidatecaches()
643 self.invalidatecaches()
644
644
645 if '.hgtags' not in self.dirstate:
645 if '.hgtags' not in self.dirstate:
646 self[None].add(['.hgtags'])
646 self[None].add(['.hgtags'])
647
647
648 m = matchmod.exact(self.root, '', ['.hgtags'])
648 m = matchmod.exact(self.root, '', ['.hgtags'])
649 tagnode = self.commit(message, user, date, extra=extra, match=m,
649 tagnode = self.commit(message, user, date, extra=extra, match=m,
650 editor=editor)
650 editor=editor)
651
651
652 for name in names:
652 for name in names:
653 self.hook('tag', node=hex(node), tag=name, local=local)
653 self.hook('tag', node=hex(node), tag=name, local=local)
654
654
655 return tagnode
655 return tagnode
656
656
657 def tag(self, names, node, message, local, user, date, editor=False):
657 def tag(self, names, node, message, local, user, date, editor=False):
658 '''tag a revision with one or more symbolic names.
658 '''tag a revision with one or more symbolic names.
659
659
660 names is a list of strings or, when adding a single tag, names may be a
660 names is a list of strings or, when adding a single tag, names may be a
661 string.
661 string.
662
662
663 if local is True, the tags are stored in a per-repository file.
663 if local is True, the tags are stored in a per-repository file.
664 otherwise, they are stored in the .hgtags file, and a new
664 otherwise, they are stored in the .hgtags file, and a new
665 changeset is committed with the change.
665 changeset is committed with the change.
666
666
667 keyword arguments:
667 keyword arguments:
668
668
669 local: whether to store tags in non-version-controlled file
669 local: whether to store tags in non-version-controlled file
670 (default False)
670 (default False)
671
671
672 message: commit message to use if committing
672 message: commit message to use if committing
673
673
674 user: name of user to use if committing
674 user: name of user to use if committing
675
675
676 date: date tuple to use if committing'''
676 date: date tuple to use if committing'''
677
677
678 if not local:
678 if not local:
679 m = matchmod.exact(self.root, '', ['.hgtags'])
679 m = matchmod.exact(self.root, '', ['.hgtags'])
680 if any(self.status(match=m, unknown=True, ignored=True)):
680 if any(self.status(match=m, unknown=True, ignored=True)):
681 raise error.Abort(_('working copy of .hgtags is changed'),
681 raise error.Abort(_('working copy of .hgtags is changed'),
682 hint=_('please commit .hgtags manually'))
682 hint=_('please commit .hgtags manually'))
683
683
684 self.tags() # instantiate the cache
684 self.tags() # instantiate the cache
685 self._tag(names, node, message, local, user, date, editor=editor)
685 self._tag(names, node, message, local, user, date, editor=editor)
686
686
687 @filteredpropertycache
687 @filteredpropertycache
688 def _tagscache(self):
688 def _tagscache(self):
689 '''Returns a tagscache object that contains various tags related
689 '''Returns a tagscache object that contains various tags related
690 caches.'''
690 caches.'''
691
691
692 # This simplifies its cache management by having one decorated
692 # This simplifies its cache management by having one decorated
693 # function (this one) and the rest simply fetch things from it.
693 # function (this one) and the rest simply fetch things from it.
694 class tagscache(object):
694 class tagscache(object):
695 def __init__(self):
695 def __init__(self):
696 # These two define the set of tags for this repository. tags
696 # These two define the set of tags for this repository. tags
697 # maps tag name to node; tagtypes maps tag name to 'global' or
697 # maps tag name to node; tagtypes maps tag name to 'global' or
698 # 'local'. (Global tags are defined by .hgtags across all
698 # 'local'. (Global tags are defined by .hgtags across all
699 # heads, and local tags are defined in .hg/localtags.)
699 # heads, and local tags are defined in .hg/localtags.)
700 # They constitute the in-memory cache of tags.
700 # They constitute the in-memory cache of tags.
701 self.tags = self.tagtypes = None
701 self.tags = self.tagtypes = None
702
702
703 self.nodetagscache = self.tagslist = None
703 self.nodetagscache = self.tagslist = None
704
704
705 cache = tagscache()
705 cache = tagscache()
706 cache.tags, cache.tagtypes = self._findtags()
706 cache.tags, cache.tagtypes = self._findtags()
707
707
708 return cache
708 return cache
709
709
710 def tags(self):
710 def tags(self):
711 '''return a mapping of tag to node'''
711 '''return a mapping of tag to node'''
712 t = {}
712 t = {}
713 if self.changelog.filteredrevs:
713 if self.changelog.filteredrevs:
714 tags, tt = self._findtags()
714 tags, tt = self._findtags()
715 else:
715 else:
716 tags = self._tagscache.tags
716 tags = self._tagscache.tags
717 for k, v in tags.iteritems():
717 for k, v in tags.iteritems():
718 try:
718 try:
719 # ignore tags to unknown nodes
719 # ignore tags to unknown nodes
720 self.changelog.rev(v)
720 self.changelog.rev(v)
721 t[k] = v
721 t[k] = v
722 except (error.LookupError, ValueError):
722 except (error.LookupError, ValueError):
723 pass
723 pass
724 return t
724 return t
725
725
726 def _findtags(self):
726 def _findtags(self):
727 '''Do the hard work of finding tags. Return a pair of dicts
727 '''Do the hard work of finding tags. Return a pair of dicts
728 (tags, tagtypes) where tags maps tag name to node, and tagtypes
728 (tags, tagtypes) where tags maps tag name to node, and tagtypes
729 maps tag name to a string like \'global\' or \'local\'.
729 maps tag name to a string like \'global\' or \'local\'.
730 Subclasses or extensions are free to add their own tags, but
730 Subclasses or extensions are free to add their own tags, but
731 should be aware that the returned dicts will be retained for the
731 should be aware that the returned dicts will be retained for the
732 duration of the localrepo object.'''
732 duration of the localrepo object.'''
733
733
734 # XXX what tagtype should subclasses/extensions use? Currently
734 # XXX what tagtype should subclasses/extensions use? Currently
735 # mq and bookmarks add tags, but do not set the tagtype at all.
735 # mq and bookmarks add tags, but do not set the tagtype at all.
736 # Should each extension invent its own tag type? Should there
736 # Should each extension invent its own tag type? Should there
737 # be one tagtype for all such "virtual" tags? Or is the status
737 # be one tagtype for all such "virtual" tags? Or is the status
738 # quo fine?
738 # quo fine?
739
739
740 alltags = {} # map tag name to (node, hist)
740 alltags = {} # map tag name to (node, hist)
741 tagtypes = {}
741 tagtypes = {}
742
742
743 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
743 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
744 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
744 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
745
745
746 # Build the return dicts. Have to re-encode tag names because
746 # Build the return dicts. Have to re-encode tag names because
747 # the tags module always uses UTF-8 (in order not to lose info
747 # the tags module always uses UTF-8 (in order not to lose info
748 # writing to the cache), but the rest of Mercurial wants them in
748 # writing to the cache), but the rest of Mercurial wants them in
749 # local encoding.
749 # local encoding.
750 tags = {}
750 tags = {}
751 for (name, (node, hist)) in alltags.iteritems():
751 for (name, (node, hist)) in alltags.iteritems():
752 if node != nullid:
752 if node != nullid:
753 tags[encoding.tolocal(name)] = node
753 tags[encoding.tolocal(name)] = node
754 tags['tip'] = self.changelog.tip()
754 tags['tip'] = self.changelog.tip()
755 tagtypes = dict([(encoding.tolocal(name), value)
755 tagtypes = dict([(encoding.tolocal(name), value)
756 for (name, value) in tagtypes.iteritems()])
756 for (name, value) in tagtypes.iteritems()])
757 return (tags, tagtypes)
757 return (tags, tagtypes)
758
758
759 def tagtype(self, tagname):
759 def tagtype(self, tagname):
760 '''
760 '''
761 return the type of the given tag. result can be:
761 return the type of the given tag. result can be:
762
762
763 'local' : a local tag
763 'local' : a local tag
764 'global' : a global tag
764 'global' : a global tag
765 None : tag does not exist
765 None : tag does not exist
766 '''
766 '''
767
767
768 return self._tagscache.tagtypes.get(tagname)
768 return self._tagscache.tagtypes.get(tagname)
769
769
770 def tagslist(self):
770 def tagslist(self):
771 '''return a list of tags ordered by revision'''
771 '''return a list of tags ordered by revision'''
772 if not self._tagscache.tagslist:
772 if not self._tagscache.tagslist:
773 l = []
773 l = []
774 for t, n in self.tags().iteritems():
774 for t, n in self.tags().iteritems():
775 l.append((self.changelog.rev(n), t, n))
775 l.append((self.changelog.rev(n), t, n))
776 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
776 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
777
777
778 return self._tagscache.tagslist
778 return self._tagscache.tagslist
779
779
780 def nodetags(self, node):
780 def nodetags(self, node):
781 '''return the tags associated with a node'''
781 '''return the tags associated with a node'''
782 if not self._tagscache.nodetagscache:
782 if not self._tagscache.nodetagscache:
783 nodetagscache = {}
783 nodetagscache = {}
784 for t, n in self._tagscache.tags.iteritems():
784 for t, n in self._tagscache.tags.iteritems():
785 nodetagscache.setdefault(n, []).append(t)
785 nodetagscache.setdefault(n, []).append(t)
786 for tags in nodetagscache.itervalues():
786 for tags in nodetagscache.itervalues():
787 tags.sort()
787 tags.sort()
788 self._tagscache.nodetagscache = nodetagscache
788 self._tagscache.nodetagscache = nodetagscache
789 return self._tagscache.nodetagscache.get(node, [])
789 return self._tagscache.nodetagscache.get(node, [])
790
790
791 def nodebookmarks(self, node):
791 def nodebookmarks(self, node):
792 """return the list of bookmarks pointing to the specified node"""
792 """return the list of bookmarks pointing to the specified node"""
793 marks = []
793 marks = []
794 for bookmark, n in self._bookmarks.iteritems():
794 for bookmark, n in self._bookmarks.iteritems():
795 if n == node:
795 if n == node:
796 marks.append(bookmark)
796 marks.append(bookmark)
797 return sorted(marks)
797 return sorted(marks)
798
798
799 def branchmap(self):
799 def branchmap(self):
800 '''returns a dictionary {branch: [branchheads]} with branchheads
800 '''returns a dictionary {branch: [branchheads]} with branchheads
801 ordered by increasing revision number'''
801 ordered by increasing revision number'''
802 branchmap.updatecache(self)
802 branchmap.updatecache(self)
803 return self._branchcaches[self.filtername]
803 return self._branchcaches[self.filtername]
804
804
805 @unfilteredmethod
805 @unfilteredmethod
806 def revbranchcache(self):
806 def revbranchcache(self):
807 if not self._revbranchcache:
807 if not self._revbranchcache:
808 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
808 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
809 return self._revbranchcache
809 return self._revbranchcache
810
810
811 def branchtip(self, branch, ignoremissing=False):
811 def branchtip(self, branch, ignoremissing=False):
812 '''return the tip node for a given branch
812 '''return the tip node for a given branch
813
813
814 If ignoremissing is True, then this method will not raise an error.
814 If ignoremissing is True, then this method will not raise an error.
815 This is helpful for callers that only expect None for a missing branch
815 This is helpful for callers that only expect None for a missing branch
816 (e.g. namespace).
816 (e.g. namespace).
817
817
818 '''
818 '''
819 try:
819 try:
820 return self.branchmap().branchtip(branch)
820 return self.branchmap().branchtip(branch)
821 except KeyError:
821 except KeyError:
822 if not ignoremissing:
822 if not ignoremissing:
823 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
823 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
824 else:
824 else:
825 pass
825 pass
826
826
827 def lookup(self, key):
827 def lookup(self, key):
828 return self[key].node()
828 return self[key].node()
829
829
830 def lookupbranch(self, key, remote=None):
830 def lookupbranch(self, key, remote=None):
831 repo = remote or self
831 repo = remote or self
832 if key in repo.branchmap():
832 if key in repo.branchmap():
833 return key
833 return key
834
834
835 repo = (remote and remote.local()) and remote or self
835 repo = (remote and remote.local()) and remote or self
836 return repo[key].branch()
836 return repo[key].branch()
837
837
838 def known(self, nodes):
838 def known(self, nodes):
839 cl = self.changelog
839 cl = self.changelog
840 nm = cl.nodemap
840 nm = cl.nodemap
841 filtered = cl.filteredrevs
841 filtered = cl.filteredrevs
842 result = []
842 result = []
843 for n in nodes:
843 for n in nodes:
844 r = nm.get(n)
844 r = nm.get(n)
845 resp = not (r is None or r in filtered)
845 resp = not (r is None or r in filtered)
846 result.append(resp)
846 result.append(resp)
847 return result
847 return result
848
848
849 def local(self):
849 def local(self):
850 return self
850 return self
851
851
852 def publishing(self):
852 def publishing(self):
853 # it's safe (and desirable) to trust the publish flag unconditionally
853 # it's safe (and desirable) to trust the publish flag unconditionally
854 # so that we don't finalize changes shared between users via ssh or nfs
854 # so that we don't finalize changes shared between users via ssh or nfs
855 return self.ui.configbool('phases', 'publish', True, untrusted=True)
855 return self.ui.configbool('phases', 'publish', True, untrusted=True)
856
856
857 def cancopy(self):
857 def cancopy(self):
858 # so statichttprepo's override of local() works
858 # so statichttprepo's override of local() works
859 if not self.local():
859 if not self.local():
860 return False
860 return False
861 if not self.publishing():
861 if not self.publishing():
862 return True
862 return True
863 # if publishing we can't copy if there is filtered content
863 # if publishing we can't copy if there is filtered content
864 return not self.filtered('visible').changelog.filteredrevs
864 return not self.filtered('visible').changelog.filteredrevs
865
865
866 def shared(self):
866 def shared(self):
867 '''the type of shared repository (None if not shared)'''
867 '''the type of shared repository (None if not shared)'''
868 if self.sharedpath != self.path:
868 if self.sharedpath != self.path:
869 return 'store'
869 return 'store'
870 return None
870 return None
871
871
872 def join(self, f, *insidef):
872 def join(self, f, *insidef):
873 return self.vfs.join(os.path.join(f, *insidef))
873 return self.vfs.join(os.path.join(f, *insidef))
874
874
875 def wjoin(self, f, *insidef):
875 def wjoin(self, f, *insidef):
876 return self.vfs.reljoin(self.root, f, *insidef)
876 return self.vfs.reljoin(self.root, f, *insidef)
877
877
878 def file(self, f):
878 def file(self, f):
879 if f[0] == '/':
879 if f[0] == '/':
880 f = f[1:]
880 f = f[1:]
881 return filelog.filelog(self.svfs, f)
881 return filelog.filelog(self.svfs, f)
882
882
883 def parents(self, changeid=None):
883 def parents(self, changeid=None):
884 '''get list of changectxs for parents of changeid'''
884 '''get list of changectxs for parents of changeid'''
885 msg = 'repo.parents() is deprecated, use repo[%r].parents()' % changeid
885 msg = 'repo.parents() is deprecated, use repo[%r].parents()' % changeid
886 self.ui.deprecwarn(msg, '3.7')
886 self.ui.deprecwarn(msg, '3.7')
887 return self[changeid].parents()
887 return self[changeid].parents()
888
888
889 def changectx(self, changeid):
889 def changectx(self, changeid):
890 return self[changeid]
890 return self[changeid]
891
891
892 def setparents(self, p1, p2=nullid):
892 def setparents(self, p1, p2=nullid):
893 self.dirstate.beginparentchange()
893 self.dirstate.beginparentchange()
894 copies = self.dirstate.setparents(p1, p2)
894 copies = self.dirstate.setparents(p1, p2)
895 pctx = self[p1]
895 pctx = self[p1]
896 if copies:
896 if copies:
897 # Adjust copy records, the dirstate cannot do it, it
897 # Adjust copy records, the dirstate cannot do it, it
898 # requires access to parents manifests. Preserve them
898 # requires access to parents manifests. Preserve them
899 # only for entries added to first parent.
899 # only for entries added to first parent.
900 for f in copies:
900 for f in copies:
901 if f not in pctx and copies[f] in pctx:
901 if f not in pctx and copies[f] in pctx:
902 self.dirstate.copy(copies[f], f)
902 self.dirstate.copy(copies[f], f)
903 if p2 == nullid:
903 if p2 == nullid:
904 for f, s in sorted(self.dirstate.copies().items()):
904 for f, s in sorted(self.dirstate.copies().items()):
905 if f not in pctx and s not in pctx:
905 if f not in pctx and s not in pctx:
906 self.dirstate.copy(None, f)
906 self.dirstate.copy(None, f)
907 self.dirstate.endparentchange()
907 self.dirstate.endparentchange()
908
908
909 def filectx(self, path, changeid=None, fileid=None):
909 def filectx(self, path, changeid=None, fileid=None):
910 """changeid can be a changeset revision, node, or tag.
910 """changeid can be a changeset revision, node, or tag.
911 fileid can be a file revision or node."""
911 fileid can be a file revision or node."""
912 return context.filectx(self, path, changeid, fileid)
912 return context.filectx(self, path, changeid, fileid)
913
913
914 def getcwd(self):
914 def getcwd(self):
915 return self.dirstate.getcwd()
915 return self.dirstate.getcwd()
916
916
917 def pathto(self, f, cwd=None):
917 def pathto(self, f, cwd=None):
918 return self.dirstate.pathto(f, cwd)
918 return self.dirstate.pathto(f, cwd)
919
919
920 def wfile(self, f, mode='r'):
920 def wfile(self, f, mode='r'):
921 return self.wvfs(f, mode)
921 return self.wvfs(f, mode)
922
922
923 def _link(self, f):
923 def _link(self, f):
924 return self.wvfs.islink(f)
924 return self.wvfs.islink(f)
925
925
926 def _loadfilter(self, filter):
926 def _loadfilter(self, filter):
927 if filter not in self.filterpats:
927 if filter not in self.filterpats:
928 l = []
928 l = []
929 for pat, cmd in self.ui.configitems(filter):
929 for pat, cmd in self.ui.configitems(filter):
930 if cmd == '!':
930 if cmd == '!':
931 continue
931 continue
932 mf = matchmod.match(self.root, '', [pat])
932 mf = matchmod.match(self.root, '', [pat])
933 fn = None
933 fn = None
934 params = cmd
934 params = cmd
935 for name, filterfn in self._datafilters.iteritems():
935 for name, filterfn in self._datafilters.iteritems():
936 if cmd.startswith(name):
936 if cmd.startswith(name):
937 fn = filterfn
937 fn = filterfn
938 params = cmd[len(name):].lstrip()
938 params = cmd[len(name):].lstrip()
939 break
939 break
940 if not fn:
940 if not fn:
941 fn = lambda s, c, **kwargs: util.filter(s, c)
941 fn = lambda s, c, **kwargs: util.filter(s, c)
942 # Wrap old filters not supporting keyword arguments
942 # Wrap old filters not supporting keyword arguments
943 if not inspect.getargspec(fn)[2]:
943 if not inspect.getargspec(fn)[2]:
944 oldfn = fn
944 oldfn = fn
945 fn = lambda s, c, **kwargs: oldfn(s, c)
945 fn = lambda s, c, **kwargs: oldfn(s, c)
946 l.append((mf, fn, params))
946 l.append((mf, fn, params))
947 self.filterpats[filter] = l
947 self.filterpats[filter] = l
948 return self.filterpats[filter]
948 return self.filterpats[filter]
949
949
950 def _filter(self, filterpats, filename, data):
950 def _filter(self, filterpats, filename, data):
951 for mf, fn, cmd in filterpats:
951 for mf, fn, cmd in filterpats:
952 if mf(filename):
952 if mf(filename):
953 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
953 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
954 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
954 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
955 break
955 break
956
956
957 return data
957 return data
958
958
959 @unfilteredpropertycache
959 @unfilteredpropertycache
960 def _encodefilterpats(self):
960 def _encodefilterpats(self):
961 return self._loadfilter('encode')
961 return self._loadfilter('encode')
962
962
963 @unfilteredpropertycache
963 @unfilteredpropertycache
964 def _decodefilterpats(self):
964 def _decodefilterpats(self):
965 return self._loadfilter('decode')
965 return self._loadfilter('decode')
966
966
967 def adddatafilter(self, name, filter):
967 def adddatafilter(self, name, filter):
968 self._datafilters[name] = filter
968 self._datafilters[name] = filter
969
969
970 def wread(self, filename):
970 def wread(self, filename):
971 if self._link(filename):
971 if self._link(filename):
972 data = self.wvfs.readlink(filename)
972 data = self.wvfs.readlink(filename)
973 else:
973 else:
974 data = self.wvfs.read(filename)
974 data = self.wvfs.read(filename)
975 return self._filter(self._encodefilterpats, filename, data)
975 return self._filter(self._encodefilterpats, filename, data)
976
976
977 def wwrite(self, filename, data, flags, backgroundclose=False):
977 def wwrite(self, filename, data, flags, backgroundclose=False):
978 """write ``data`` into ``filename`` in the working directory
978 """write ``data`` into ``filename`` in the working directory
979
979
980 This returns length of written (maybe decoded) data.
980 This returns length of written (maybe decoded) data.
981 """
981 """
982 data = self._filter(self._decodefilterpats, filename, data)
982 data = self._filter(self._decodefilterpats, filename, data)
983 if 'l' in flags:
983 if 'l' in flags:
984 self.wvfs.symlink(data, filename)
984 self.wvfs.symlink(data, filename)
985 else:
985 else:
986 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
986 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
987 if 'x' in flags:
987 if 'x' in flags:
988 self.wvfs.setflags(filename, False, True)
988 self.wvfs.setflags(filename, False, True)
989 return len(data)
989 return len(data)
990
990
991 def wwritedata(self, filename, data):
991 def wwritedata(self, filename, data):
992 return self._filter(self._decodefilterpats, filename, data)
992 return self._filter(self._decodefilterpats, filename, data)
993
993
994 def currenttransaction(self):
994 def currenttransaction(self):
995 """return the current transaction or None if non exists"""
995 """return the current transaction or None if non exists"""
996 if self._transref:
996 if self._transref:
997 tr = self._transref()
997 tr = self._transref()
998 else:
998 else:
999 tr = None
999 tr = None
1000
1000
1001 if tr and tr.running():
1001 if tr and tr.running():
1002 return tr
1002 return tr
1003 return None
1003 return None
1004
1004
1005 def transaction(self, desc, report=None):
1005 def transaction(self, desc, report=None):
1006 if (self.ui.configbool('devel', 'all-warnings')
1006 if (self.ui.configbool('devel', 'all-warnings')
1007 or self.ui.configbool('devel', 'check-locks')):
1007 or self.ui.configbool('devel', 'check-locks')):
1008 l = self._lockref and self._lockref()
1008 l = self._lockref and self._lockref()
1009 if l is None or not l.held:
1009 if l is None or not l.held:
1010 self.ui.develwarn('transaction with no lock')
1010 self.ui.develwarn('transaction with no lock')
1011 tr = self.currenttransaction()
1011 tr = self.currenttransaction()
1012 if tr is not None:
1012 if tr is not None:
1013 return tr.nest()
1013 return tr.nest()
1014
1014
1015 # abort here if the journal already exists
1015 # abort here if the journal already exists
1016 if self.svfs.exists("journal"):
1016 if self.svfs.exists("journal"):
1017 raise error.RepoError(
1017 raise error.RepoError(
1018 _("abandoned transaction found"),
1018 _("abandoned transaction found"),
1019 hint=_("run 'hg recover' to clean up transaction"))
1019 hint=_("run 'hg recover' to clean up transaction"))
1020
1020
1021 # make journal.dirstate contain in-memory changes at this point
1021 # make journal.dirstate contain in-memory changes at this point
1022 self.dirstate.write(None)
1022 self.dirstate.write(None)
1023
1023
1024 idbase = "%.40f#%f" % (random.random(), time.time())
1024 idbase = "%.40f#%f" % (random.random(), time.time())
1025 txnid = 'TXN:' + util.sha1(idbase).hexdigest()
1025 txnid = 'TXN:' + util.sha1(idbase).hexdigest()
1026 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1026 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1027
1027
1028 self._writejournal(desc)
1028 self._writejournal(desc)
1029 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1029 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1030 if report:
1030 if report:
1031 rp = report
1031 rp = report
1032 else:
1032 else:
1033 rp = self.ui.warn
1033 rp = self.ui.warn
1034 vfsmap = {'plain': self.vfs} # root of .hg/
1034 vfsmap = {'plain': self.vfs} # root of .hg/
1035 # we must avoid cyclic reference between repo and transaction.
1035 # we must avoid cyclic reference between repo and transaction.
1036 reporef = weakref.ref(self)
1036 reporef = weakref.ref(self)
1037 def validate(tr):
1037 def validate(tr):
1038 """will run pre-closing hooks"""
1038 """will run pre-closing hooks"""
1039 reporef().hook('pretxnclose', throw=True,
1039 reporef().hook('pretxnclose', throw=True,
1040 txnname=desc, **tr.hookargs)
1040 txnname=desc, **tr.hookargs)
1041 def releasefn(tr, success):
1041 def releasefn(tr, success):
1042 repo = reporef()
1042 repo = reporef()
1043 if success:
1043 if success:
1044 # this should be explicitly invoked here, because
1044 # this should be explicitly invoked here, because
1045 # in-memory changes aren't written out at closing
1045 # in-memory changes aren't written out at closing
1046 # transaction, if tr.addfilegenerator (via
1046 # transaction, if tr.addfilegenerator (via
1047 # dirstate.write or so) isn't invoked while
1047 # dirstate.write or so) isn't invoked while
1048 # transaction running
1048 # transaction running
1049 repo.dirstate.write(None)
1049 repo.dirstate.write(None)
1050 else:
1050 else:
1051 # prevent in-memory changes from being written out at
1051 # prevent in-memory changes from being written out at
1052 # the end of outer wlock scope or so
1052 # the end of outer wlock scope or so
1053 repo.dirstate.invalidate()
1053 repo.dirstate.invalidate()
1054
1054
1055 # discard all changes (including ones already written
1055 # discard all changes (including ones already written
1056 # out) in this transaction
1056 # out) in this transaction
1057 repo.vfs.rename('journal.dirstate', 'dirstate')
1057 repo.vfs.rename('journal.dirstate', 'dirstate')
1058
1058
1059 repo.invalidate(clearfilecache=True)
1059 repo.invalidate(clearfilecache=True)
1060
1060
1061 tr = transaction.transaction(rp, self.svfs, vfsmap,
1061 tr = transaction.transaction(rp, self.svfs, vfsmap,
1062 "journal",
1062 "journal",
1063 "undo",
1063 "undo",
1064 aftertrans(renames),
1064 aftertrans(renames),
1065 self.store.createmode,
1065 self.store.createmode,
1066 validator=validate,
1066 validator=validate,
1067 releasefn=releasefn)
1067 releasefn=releasefn)
1068
1068
1069 tr.hookargs['txnid'] = txnid
1069 tr.hookargs['txnid'] = txnid
1070 # note: writing the fncache only during finalize mean that the file is
1070 # note: writing the fncache only during finalize mean that the file is
1071 # outdated when running hooks. As fncache is used for streaming clone,
1071 # outdated when running hooks. As fncache is used for streaming clone,
1072 # this is not expected to break anything that happen during the hooks.
1072 # this is not expected to break anything that happen during the hooks.
1073 tr.addfinalize('flush-fncache', self.store.write)
1073 tr.addfinalize('flush-fncache', self.store.write)
1074 def txnclosehook(tr2):
1074 def txnclosehook(tr2):
1075 """To be run if transaction is successful, will schedule a hook run
1075 """To be run if transaction is successful, will schedule a hook run
1076 """
1076 """
1077 # Don't reference tr2 in hook() so we don't hold a reference.
1077 # Don't reference tr2 in hook() so we don't hold a reference.
1078 # This reduces memory consumption when there are multiple
1078 # This reduces memory consumption when there are multiple
1079 # transactions per lock. This can likely go away if issue5045
1079 # transactions per lock. This can likely go away if issue5045
1080 # fixes the function accumulation.
1080 # fixes the function accumulation.
1081 hookargs = tr2.hookargs
1081 hookargs = tr2.hookargs
1082
1082
1083 def hook():
1083 def hook():
1084 reporef().hook('txnclose', throw=False, txnname=desc,
1084 reporef().hook('txnclose', throw=False, txnname=desc,
1085 **hookargs)
1085 **hookargs)
1086 reporef()._afterlock(hook)
1086 reporef()._afterlock(hook)
1087 tr.addfinalize('txnclose-hook', txnclosehook)
1087 tr.addfinalize('txnclose-hook', txnclosehook)
1088 def txnaborthook(tr2):
1088 def txnaborthook(tr2):
1089 """To be run if transaction is aborted
1089 """To be run if transaction is aborted
1090 """
1090 """
1091 reporef().hook('txnabort', throw=False, txnname=desc,
1091 reporef().hook('txnabort', throw=False, txnname=desc,
1092 **tr2.hookargs)
1092 **tr2.hookargs)
1093 tr.addabort('txnabort-hook', txnaborthook)
1093 tr.addabort('txnabort-hook', txnaborthook)
1094 # avoid eager cache invalidation. in-memory data should be identical
1094 # avoid eager cache invalidation. in-memory data should be identical
1095 # to stored data if transaction has no error.
1095 # to stored data if transaction has no error.
1096 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1096 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1097 self._transref = weakref.ref(tr)
1097 self._transref = weakref.ref(tr)
1098 return tr
1098 return tr
1099
1099
1100 def _journalfiles(self):
1100 def _journalfiles(self):
1101 return ((self.svfs, 'journal'),
1101 return ((self.svfs, 'journal'),
1102 (self.vfs, 'journal.dirstate'),
1102 (self.vfs, 'journal.dirstate'),
1103 (self.vfs, 'journal.branch'),
1103 (self.vfs, 'journal.branch'),
1104 (self.vfs, 'journal.desc'),
1104 (self.vfs, 'journal.desc'),
1105 (self.vfs, 'journal.bookmarks'),
1105 (self.vfs, 'journal.bookmarks'),
1106 (self.svfs, 'journal.phaseroots'))
1106 (self.svfs, 'journal.phaseroots'))
1107
1107
1108 def undofiles(self):
1108 def undofiles(self):
1109 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1109 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1110
1110
1111 def _writejournal(self, desc):
1111 def _writejournal(self, desc):
1112 self.vfs.write("journal.dirstate",
1112 self.vfs.write("journal.dirstate",
1113 self.vfs.tryread("dirstate"))
1113 self.vfs.tryread("dirstate"))
1114 self.vfs.write("journal.branch",
1114 self.vfs.write("journal.branch",
1115 encoding.fromlocal(self.dirstate.branch()))
1115 encoding.fromlocal(self.dirstate.branch()))
1116 self.vfs.write("journal.desc",
1116 self.vfs.write("journal.desc",
1117 "%d\n%s\n" % (len(self), desc))
1117 "%d\n%s\n" % (len(self), desc))
1118 self.vfs.write("journal.bookmarks",
1118 self.vfs.write("journal.bookmarks",
1119 self.vfs.tryread("bookmarks"))
1119 self.vfs.tryread("bookmarks"))
1120 self.svfs.write("journal.phaseroots",
1120 self.svfs.write("journal.phaseroots",
1121 self.svfs.tryread("phaseroots"))
1121 self.svfs.tryread("phaseroots"))
1122
1122
1123 def recover(self):
1123 def recover(self):
1124 with self.lock():
1124 with self.lock():
1125 if self.svfs.exists("journal"):
1125 if self.svfs.exists("journal"):
1126 self.ui.status(_("rolling back interrupted transaction\n"))
1126 self.ui.status(_("rolling back interrupted transaction\n"))
1127 vfsmap = {'': self.svfs,
1127 vfsmap = {'': self.svfs,
1128 'plain': self.vfs,}
1128 'plain': self.vfs,}
1129 transaction.rollback(self.svfs, vfsmap, "journal",
1129 transaction.rollback(self.svfs, vfsmap, "journal",
1130 self.ui.warn)
1130 self.ui.warn)
1131 self.invalidate()
1131 self.invalidate()
1132 return True
1132 return True
1133 else:
1133 else:
1134 self.ui.warn(_("no interrupted transaction available\n"))
1134 self.ui.warn(_("no interrupted transaction available\n"))
1135 return False
1135 return False
1136
1136
1137 def rollback(self, dryrun=False, force=False):
1137 def rollback(self, dryrun=False, force=False):
1138 wlock = lock = dsguard = None
1138 wlock = lock = dsguard = None
1139 try:
1139 try:
1140 wlock = self.wlock()
1140 wlock = self.wlock()
1141 lock = self.lock()
1141 lock = self.lock()
1142 if self.svfs.exists("undo"):
1142 if self.svfs.exists("undo"):
1143 dsguard = cmdutil.dirstateguard(self, 'rollback')
1143 dsguard = cmdutil.dirstateguard(self, 'rollback')
1144
1144
1145 return self._rollback(dryrun, force, dsguard)
1145 return self._rollback(dryrun, force, dsguard)
1146 else:
1146 else:
1147 self.ui.warn(_("no rollback information available\n"))
1147 self.ui.warn(_("no rollback information available\n"))
1148 return 1
1148 return 1
1149 finally:
1149 finally:
1150 release(dsguard, lock, wlock)
1150 release(dsguard, lock, wlock)
1151
1151
1152 @unfilteredmethod # Until we get smarter cache management
1152 @unfilteredmethod # Until we get smarter cache management
1153 def _rollback(self, dryrun, force, dsguard):
1153 def _rollback(self, dryrun, force, dsguard):
1154 ui = self.ui
1154 ui = self.ui
1155 try:
1155 try:
1156 args = self.vfs.read('undo.desc').splitlines()
1156 args = self.vfs.read('undo.desc').splitlines()
1157 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1157 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1158 if len(args) >= 3:
1158 if len(args) >= 3:
1159 detail = args[2]
1159 detail = args[2]
1160 oldtip = oldlen - 1
1160 oldtip = oldlen - 1
1161
1161
1162 if detail and ui.verbose:
1162 if detail and ui.verbose:
1163 msg = (_('repository tip rolled back to revision %s'
1163 msg = (_('repository tip rolled back to revision %s'
1164 ' (undo %s: %s)\n')
1164 ' (undo %s: %s)\n')
1165 % (oldtip, desc, detail))
1165 % (oldtip, desc, detail))
1166 else:
1166 else:
1167 msg = (_('repository tip rolled back to revision %s'
1167 msg = (_('repository tip rolled back to revision %s'
1168 ' (undo %s)\n')
1168 ' (undo %s)\n')
1169 % (oldtip, desc))
1169 % (oldtip, desc))
1170 except IOError:
1170 except IOError:
1171 msg = _('rolling back unknown transaction\n')
1171 msg = _('rolling back unknown transaction\n')
1172 desc = None
1172 desc = None
1173
1173
1174 if not force and self['.'] != self['tip'] and desc == 'commit':
1174 if not force and self['.'] != self['tip'] and desc == 'commit':
1175 raise error.Abort(
1175 raise error.Abort(
1176 _('rollback of last commit while not checked out '
1176 _('rollback of last commit while not checked out '
1177 'may lose data'), hint=_('use -f to force'))
1177 'may lose data'), hint=_('use -f to force'))
1178
1178
1179 ui.status(msg)
1179 ui.status(msg)
1180 if dryrun:
1180 if dryrun:
1181 return 0
1181 return 0
1182
1182
1183 parents = self.dirstate.parents()
1183 parents = self.dirstate.parents()
1184 self.destroying()
1184 self.destroying()
1185 vfsmap = {'plain': self.vfs, '': self.svfs}
1185 vfsmap = {'plain': self.vfs, '': self.svfs}
1186 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1186 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1187 if self.vfs.exists('undo.bookmarks'):
1187 if self.vfs.exists('undo.bookmarks'):
1188 self.vfs.rename('undo.bookmarks', 'bookmarks')
1188 self.vfs.rename('undo.bookmarks', 'bookmarks')
1189 if self.svfs.exists('undo.phaseroots'):
1189 if self.svfs.exists('undo.phaseroots'):
1190 self.svfs.rename('undo.phaseroots', 'phaseroots')
1190 self.svfs.rename('undo.phaseroots', 'phaseroots')
1191 self.invalidate()
1191 self.invalidate()
1192
1192
1193 parentgone = (parents[0] not in self.changelog.nodemap or
1193 parentgone = (parents[0] not in self.changelog.nodemap or
1194 parents[1] not in self.changelog.nodemap)
1194 parents[1] not in self.changelog.nodemap)
1195 if parentgone:
1195 if parentgone:
1196 # prevent dirstateguard from overwriting already restored one
1196 # prevent dirstateguard from overwriting already restored one
1197 dsguard.close()
1197 dsguard.close()
1198
1198
1199 self.vfs.rename('undo.dirstate', 'dirstate')
1199 self.vfs.rename('undo.dirstate', 'dirstate')
1200 try:
1200 try:
1201 branch = self.vfs.read('undo.branch')
1201 branch = self.vfs.read('undo.branch')
1202 self.dirstate.setbranch(encoding.tolocal(branch))
1202 self.dirstate.setbranch(encoding.tolocal(branch))
1203 except IOError:
1203 except IOError:
1204 ui.warn(_('named branch could not be reset: '
1204 ui.warn(_('named branch could not be reset: '
1205 'current branch is still \'%s\'\n')
1205 'current branch is still \'%s\'\n')
1206 % self.dirstate.branch())
1206 % self.dirstate.branch())
1207
1207
1208 self.dirstate.invalidate()
1208 self.dirstate.invalidate()
1209 parents = tuple([p.rev() for p in self[None].parents()])
1209 parents = tuple([p.rev() for p in self[None].parents()])
1210 if len(parents) > 1:
1210 if len(parents) > 1:
1211 ui.status(_('working directory now based on '
1211 ui.status(_('working directory now based on '
1212 'revisions %d and %d\n') % parents)
1212 'revisions %d and %d\n') % parents)
1213 else:
1213 else:
1214 ui.status(_('working directory now based on '
1214 ui.status(_('working directory now based on '
1215 'revision %d\n') % parents)
1215 'revision %d\n') % parents)
1216 mergemod.mergestate.clean(self, self['.'].node())
1216 mergemod.mergestate.clean(self, self['.'].node())
1217
1217
1218 # TODO: if we know which new heads may result from this rollback, pass
1218 # TODO: if we know which new heads may result from this rollback, pass
1219 # them to destroy(), which will prevent the branchhead cache from being
1219 # them to destroy(), which will prevent the branchhead cache from being
1220 # invalidated.
1220 # invalidated.
1221 self.destroyed()
1221 self.destroyed()
1222 return 0
1222 return 0
1223
1223
1224 def invalidatecaches(self):
1224 def invalidatecaches(self):
1225
1225
1226 if '_tagscache' in vars(self):
1226 if '_tagscache' in vars(self):
1227 # can't use delattr on proxy
1227 # can't use delattr on proxy
1228 del self.__dict__['_tagscache']
1228 del self.__dict__['_tagscache']
1229
1229
1230 self.unfiltered()._branchcaches.clear()
1230 self.unfiltered()._branchcaches.clear()
1231 self.invalidatevolatilesets()
1231 self.invalidatevolatilesets()
1232
1232
1233 def invalidatevolatilesets(self):
1233 def invalidatevolatilesets(self):
1234 self.filteredrevcache.clear()
1234 self.filteredrevcache.clear()
1235 obsolete.clearobscaches(self)
1235 obsolete.clearobscaches(self)
1236
1236
1237 def invalidatedirstate(self):
1237 def invalidatedirstate(self):
1238 '''Invalidates the dirstate, causing the next call to dirstate
1238 '''Invalidates the dirstate, causing the next call to dirstate
1239 to check if it was modified since the last time it was read,
1239 to check if it was modified since the last time it was read,
1240 rereading it if it has.
1240 rereading it if it has.
1241
1241
1242 This is different to dirstate.invalidate() that it doesn't always
1242 This is different to dirstate.invalidate() that it doesn't always
1243 rereads the dirstate. Use dirstate.invalidate() if you want to
1243 rereads the dirstate. Use dirstate.invalidate() if you want to
1244 explicitly read the dirstate again (i.e. restoring it to a previous
1244 explicitly read the dirstate again (i.e. restoring it to a previous
1245 known good state).'''
1245 known good state).'''
1246 if hasunfilteredcache(self, 'dirstate'):
1246 if hasunfilteredcache(self, 'dirstate'):
1247 for k in self.dirstate._filecache:
1247 for k in self.dirstate._filecache:
1248 try:
1248 try:
1249 delattr(self.dirstate, k)
1249 delattr(self.dirstate, k)
1250 except AttributeError:
1250 except AttributeError:
1251 pass
1251 pass
1252 delattr(self.unfiltered(), 'dirstate')
1252 delattr(self.unfiltered(), 'dirstate')
1253
1253
1254 def invalidate(self, clearfilecache=False):
1254 def invalidate(self, clearfilecache=False):
1255 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1255 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1256 for k in self._filecache.keys():
1256 for k in self._filecache.keys():
1257 # dirstate is invalidated separately in invalidatedirstate()
1257 # dirstate is invalidated separately in invalidatedirstate()
1258 if k == 'dirstate':
1258 if k == 'dirstate':
1259 continue
1259 continue
1260
1260
1261 if clearfilecache:
1261 if clearfilecache:
1262 del self._filecache[k]
1262 del self._filecache[k]
1263 try:
1263 try:
1264 delattr(unfiltered, k)
1264 delattr(unfiltered, k)
1265 except AttributeError:
1265 except AttributeError:
1266 pass
1266 pass
1267 self.invalidatecaches()
1267 self.invalidatecaches()
1268 self.store.invalidatecaches()
1268 self.store.invalidatecaches()
1269
1269
1270 def invalidateall(self):
1270 def invalidateall(self):
1271 '''Fully invalidates both store and non-store parts, causing the
1271 '''Fully invalidates both store and non-store parts, causing the
1272 subsequent operation to reread any outside changes.'''
1272 subsequent operation to reread any outside changes.'''
1273 # extension should hook this to invalidate its caches
1273 # extension should hook this to invalidate its caches
1274 self.invalidate()
1274 self.invalidate()
1275 self.invalidatedirstate()
1275 self.invalidatedirstate()
1276
1276
1277 def _refreshfilecachestats(self, tr):
1277 def _refreshfilecachestats(self, tr):
1278 """Reload stats of cached files so that they are flagged as valid"""
1278 """Reload stats of cached files so that they are flagged as valid"""
1279 for k, ce in self._filecache.items():
1279 for k, ce in self._filecache.items():
1280 if k == 'dirstate' or k not in self.__dict__:
1280 if k == 'dirstate' or k not in self.__dict__:
1281 continue
1281 continue
1282 ce.refresh()
1282 ce.refresh()
1283
1283
1284 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1284 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1285 inheritchecker=None, parentenvvar=None):
1285 inheritchecker=None, parentenvvar=None):
1286 parentlock = None
1286 parentlock = None
1287 # the contents of parentenvvar are used by the underlying lock to
1287 # the contents of parentenvvar are used by the underlying lock to
1288 # determine whether it can be inherited
1288 # determine whether it can be inherited
1289 if parentenvvar is not None:
1289 if parentenvvar is not None:
1290 parentlock = os.environ.get(parentenvvar)
1290 parentlock = os.environ.get(parentenvvar)
1291 try:
1291 try:
1292 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1292 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1293 acquirefn=acquirefn, desc=desc,
1293 acquirefn=acquirefn, desc=desc,
1294 inheritchecker=inheritchecker,
1294 inheritchecker=inheritchecker,
1295 parentlock=parentlock)
1295 parentlock=parentlock)
1296 except error.LockHeld as inst:
1296 except error.LockHeld as inst:
1297 if not wait:
1297 if not wait:
1298 raise
1298 raise
1299 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1299 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1300 (desc, inst.locker))
1300 (desc, inst.locker))
1301 # default to 600 seconds timeout
1301 # default to 600 seconds timeout
1302 l = lockmod.lock(vfs, lockname,
1302 l = lockmod.lock(vfs, lockname,
1303 int(self.ui.config("ui", "timeout", "600")),
1303 int(self.ui.config("ui", "timeout", "600")),
1304 releasefn=releasefn, acquirefn=acquirefn,
1304 releasefn=releasefn, acquirefn=acquirefn,
1305 desc=desc)
1305 desc=desc)
1306 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1306 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1307 return l
1307 return l
1308
1308
1309 def _afterlock(self, callback):
1309 def _afterlock(self, callback):
1310 """add a callback to be run when the repository is fully unlocked
1310 """add a callback to be run when the repository is fully unlocked
1311
1311
1312 The callback will be executed when the outermost lock is released
1312 The callback will be executed when the outermost lock is released
1313 (with wlock being higher level than 'lock')."""
1313 (with wlock being higher level than 'lock')."""
1314 for ref in (self._wlockref, self._lockref):
1314 for ref in (self._wlockref, self._lockref):
1315 l = ref and ref()
1315 l = ref and ref()
1316 if l and l.held:
1316 if l and l.held:
1317 l.postrelease.append(callback)
1317 l.postrelease.append(callback)
1318 break
1318 break
1319 else: # no lock have been found.
1319 else: # no lock have been found.
1320 callback()
1320 callback()
1321
1321
1322 def lock(self, wait=True):
1322 def lock(self, wait=True):
1323 '''Lock the repository store (.hg/store) and return a weak reference
1323 '''Lock the repository store (.hg/store) and return a weak reference
1324 to the lock. Use this before modifying the store (e.g. committing or
1324 to the lock. Use this before modifying the store (e.g. committing or
1325 stripping). If you are opening a transaction, get a lock as well.)
1325 stripping). If you are opening a transaction, get a lock as well.)
1326
1326
1327 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1327 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1328 'wlock' first to avoid a dead-lock hazard.'''
1328 'wlock' first to avoid a dead-lock hazard.'''
1329 l = self._lockref and self._lockref()
1329 l = self._lockref and self._lockref()
1330 if l is not None and l.held:
1330 if l is not None and l.held:
1331 l.lock()
1331 l.lock()
1332 return l
1332 return l
1333
1333
1334 l = self._lock(self.svfs, "lock", wait, None,
1334 l = self._lock(self.svfs, "lock", wait, None,
1335 self.invalidate, _('repository %s') % self.origroot)
1335 self.invalidate, _('repository %s') % self.origroot)
1336 self._lockref = weakref.ref(l)
1336 self._lockref = weakref.ref(l)
1337 return l
1337 return l
1338
1338
1339 def _wlockchecktransaction(self):
1339 def _wlockchecktransaction(self):
1340 if self.currenttransaction() is not None:
1340 if self.currenttransaction() is not None:
1341 raise error.LockInheritanceContractViolation(
1341 raise error.LockInheritanceContractViolation(
1342 'wlock cannot be inherited in the middle of a transaction')
1342 'wlock cannot be inherited in the middle of a transaction')
1343
1343
1344 def wlock(self, wait=True):
1344 def wlock(self, wait=True):
1345 '''Lock the non-store parts of the repository (everything under
1345 '''Lock the non-store parts of the repository (everything under
1346 .hg except .hg/store) and return a weak reference to the lock.
1346 .hg except .hg/store) and return a weak reference to the lock.
1347
1347
1348 Use this before modifying files in .hg.
1348 Use this before modifying files in .hg.
1349
1349
1350 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1350 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1351 'wlock' first to avoid a dead-lock hazard.'''
1351 'wlock' first to avoid a dead-lock hazard.'''
1352 l = self._wlockref and self._wlockref()
1352 l = self._wlockref and self._wlockref()
1353 if l is not None and l.held:
1353 if l is not None and l.held:
1354 l.lock()
1354 l.lock()
1355 return l
1355 return l
1356
1356
1357 # We do not need to check for non-waiting lock acquisition. Such
1357 # We do not need to check for non-waiting lock acquisition. Such
1358 # acquisition would not cause dead-lock as they would just fail.
1358 # acquisition would not cause dead-lock as they would just fail.
1359 if wait and (self.ui.configbool('devel', 'all-warnings')
1359 if wait and (self.ui.configbool('devel', 'all-warnings')
1360 or self.ui.configbool('devel', 'check-locks')):
1360 or self.ui.configbool('devel', 'check-locks')):
1361 l = self._lockref and self._lockref()
1361 l = self._lockref and self._lockref()
1362 if l is not None and l.held:
1362 if l is not None and l.held:
1363 self.ui.develwarn('"wlock" acquired after "lock"')
1363 self.ui.develwarn('"wlock" acquired after "lock"')
1364
1364
1365 def unlock():
1365 def unlock():
1366 if self.dirstate.pendingparentchange():
1366 if self.dirstate.pendingparentchange():
1367 self.dirstate.invalidate()
1367 self.dirstate.invalidate()
1368 else:
1368 else:
1369 self.dirstate.write(None)
1369 self.dirstate.write(None)
1370
1370
1371 self._filecache['dirstate'].refresh()
1371 self._filecache['dirstate'].refresh()
1372
1372
1373 l = self._lock(self.vfs, "wlock", wait, unlock,
1373 l = self._lock(self.vfs, "wlock", wait, unlock,
1374 self.invalidatedirstate, _('working directory of %s') %
1374 self.invalidatedirstate, _('working directory of %s') %
1375 self.origroot,
1375 self.origroot,
1376 inheritchecker=self._wlockchecktransaction,
1376 inheritchecker=self._wlockchecktransaction,
1377 parentenvvar='HG_WLOCK_LOCKER')
1377 parentenvvar='HG_WLOCK_LOCKER')
1378 self._wlockref = weakref.ref(l)
1378 self._wlockref = weakref.ref(l)
1379 return l
1379 return l
1380
1380
1381 def _currentlock(self, lockref):
1381 def _currentlock(self, lockref):
1382 """Returns the lock if it's held, or None if it's not."""
1382 """Returns the lock if it's held, or None if it's not."""
1383 if lockref is None:
1383 if lockref is None:
1384 return None
1384 return None
1385 l = lockref()
1385 l = lockref()
1386 if l is None or not l.held:
1386 if l is None or not l.held:
1387 return None
1387 return None
1388 return l
1388 return l
1389
1389
1390 def currentwlock(self):
1390 def currentwlock(self):
1391 """Returns the wlock if it's held, or None if it's not."""
1391 """Returns the wlock if it's held, or None if it's not."""
1392 return self._currentlock(self._wlockref)
1392 return self._currentlock(self._wlockref)
1393
1393
1394 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1394 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1395 """
1395 """
1396 commit an individual file as part of a larger transaction
1396 commit an individual file as part of a larger transaction
1397 """
1397 """
1398
1398
1399 fname = fctx.path()
1399 fname = fctx.path()
1400 fparent1 = manifest1.get(fname, nullid)
1400 fparent1 = manifest1.get(fname, nullid)
1401 fparent2 = manifest2.get(fname, nullid)
1401 fparent2 = manifest2.get(fname, nullid)
1402 if isinstance(fctx, context.filectx):
1402 if isinstance(fctx, context.filectx):
1403 node = fctx.filenode()
1403 node = fctx.filenode()
1404 if node in [fparent1, fparent2]:
1404 if node in [fparent1, fparent2]:
1405 self.ui.debug('reusing %s filelog entry\n' % fname)
1405 self.ui.debug('reusing %s filelog entry\n' % fname)
1406 return node
1406 return node
1407
1407
1408 flog = self.file(fname)
1408 flog = self.file(fname)
1409 meta = {}
1409 meta = {}
1410 copy = fctx.renamed()
1410 copy = fctx.renamed()
1411 if copy and copy[0] != fname:
1411 if copy and copy[0] != fname:
1412 # Mark the new revision of this file as a copy of another
1412 # Mark the new revision of this file as a copy of another
1413 # file. This copy data will effectively act as a parent
1413 # file. This copy data will effectively act as a parent
1414 # of this new revision. If this is a merge, the first
1414 # of this new revision. If this is a merge, the first
1415 # parent will be the nullid (meaning "look up the copy data")
1415 # parent will be the nullid (meaning "look up the copy data")
1416 # and the second one will be the other parent. For example:
1416 # and the second one will be the other parent. For example:
1417 #
1417 #
1418 # 0 --- 1 --- 3 rev1 changes file foo
1418 # 0 --- 1 --- 3 rev1 changes file foo
1419 # \ / rev2 renames foo to bar and changes it
1419 # \ / rev2 renames foo to bar and changes it
1420 # \- 2 -/ rev3 should have bar with all changes and
1420 # \- 2 -/ rev3 should have bar with all changes and
1421 # should record that bar descends from
1421 # should record that bar descends from
1422 # bar in rev2 and foo in rev1
1422 # bar in rev2 and foo in rev1
1423 #
1423 #
1424 # this allows this merge to succeed:
1424 # this allows this merge to succeed:
1425 #
1425 #
1426 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1426 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1427 # \ / merging rev3 and rev4 should use bar@rev2
1427 # \ / merging rev3 and rev4 should use bar@rev2
1428 # \- 2 --- 4 as the merge base
1428 # \- 2 --- 4 as the merge base
1429 #
1429 #
1430
1430
1431 cfname = copy[0]
1431 cfname = copy[0]
1432 crev = manifest1.get(cfname)
1432 crev = manifest1.get(cfname)
1433 newfparent = fparent2
1433 newfparent = fparent2
1434
1434
1435 if manifest2: # branch merge
1435 if manifest2: # branch merge
1436 if fparent2 == nullid or crev is None: # copied on remote side
1436 if fparent2 == nullid or crev is None: # copied on remote side
1437 if cfname in manifest2:
1437 if cfname in manifest2:
1438 crev = manifest2[cfname]
1438 crev = manifest2[cfname]
1439 newfparent = fparent1
1439 newfparent = fparent1
1440
1440
1441 # Here, we used to search backwards through history to try to find
1441 # Here, we used to search backwards through history to try to find
1442 # where the file copy came from if the source of a copy was not in
1442 # where the file copy came from if the source of a copy was not in
1443 # the parent directory. However, this doesn't actually make sense to
1443 # the parent directory. However, this doesn't actually make sense to
1444 # do (what does a copy from something not in your working copy even
1444 # do (what does a copy from something not in your working copy even
1445 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1445 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1446 # the user that copy information was dropped, so if they didn't
1446 # the user that copy information was dropped, so if they didn't
1447 # expect this outcome it can be fixed, but this is the correct
1447 # expect this outcome it can be fixed, but this is the correct
1448 # behavior in this circumstance.
1448 # behavior in this circumstance.
1449
1449
1450 if crev:
1450 if crev:
1451 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1451 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1452 meta["copy"] = cfname
1452 meta["copy"] = cfname
1453 meta["copyrev"] = hex(crev)
1453 meta["copyrev"] = hex(crev)
1454 fparent1, fparent2 = nullid, newfparent
1454 fparent1, fparent2 = nullid, newfparent
1455 else:
1455 else:
1456 self.ui.warn(_("warning: can't find ancestor for '%s' "
1456 self.ui.warn(_("warning: can't find ancestor for '%s' "
1457 "copied from '%s'!\n") % (fname, cfname))
1457 "copied from '%s'!\n") % (fname, cfname))
1458
1458
1459 elif fparent1 == nullid:
1459 elif fparent1 == nullid:
1460 fparent1, fparent2 = fparent2, nullid
1460 fparent1, fparent2 = fparent2, nullid
1461 elif fparent2 != nullid:
1461 elif fparent2 != nullid:
1462 # is one parent an ancestor of the other?
1462 # is one parent an ancestor of the other?
1463 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1463 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1464 if fparent1 in fparentancestors:
1464 if fparent1 in fparentancestors:
1465 fparent1, fparent2 = fparent2, nullid
1465 fparent1, fparent2 = fparent2, nullid
1466 elif fparent2 in fparentancestors:
1466 elif fparent2 in fparentancestors:
1467 fparent2 = nullid
1467 fparent2 = nullid
1468
1468
1469 # is the file changed?
1469 # is the file changed?
1470 text = fctx.data()
1470 text = fctx.data()
1471 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1471 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1472 changelist.append(fname)
1472 changelist.append(fname)
1473 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1473 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1474 # are just the flags changed during merge?
1474 # are just the flags changed during merge?
1475 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1475 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1476 changelist.append(fname)
1476 changelist.append(fname)
1477
1477
1478 return fparent1
1478 return fparent1
1479
1479
1480 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1480 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1481 """check for commit arguments that aren't commitable"""
1481 """check for commit arguments that aren't commitable"""
1482 if match.isexact() or match.prefix():
1482 if match.isexact() or match.prefix():
1483 matched = set(status.modified + status.added + status.removed)
1483 matched = set(status.modified + status.added + status.removed)
1484
1484
1485 for f in match.files():
1485 for f in match.files():
1486 f = self.dirstate.normalize(f)
1486 f = self.dirstate.normalize(f)
1487 if f == '.' or f in matched or f in wctx.substate:
1487 if f == '.' or f in matched or f in wctx.substate:
1488 continue
1488 continue
1489 if f in status.deleted:
1489 if f in status.deleted:
1490 fail(f, _('file not found!'))
1490 fail(f, _('file not found!'))
1491 if f in vdirs: # visited directory
1491 if f in vdirs: # visited directory
1492 d = f + '/'
1492 d = f + '/'
1493 for mf in matched:
1493 for mf in matched:
1494 if mf.startswith(d):
1494 if mf.startswith(d):
1495 break
1495 break
1496 else:
1496 else:
1497 fail(f, _("no match under directory!"))
1497 fail(f, _("no match under directory!"))
1498 elif f not in self.dirstate:
1498 elif f not in self.dirstate:
1499 fail(f, _("file not tracked!"))
1499 fail(f, _("file not tracked!"))
1500
1500
1501 @unfilteredmethod
1501 @unfilteredmethod
1502 def commit(self, text="", user=None, date=None, match=None, force=False,
1502 def commit(self, text="", user=None, date=None, match=None, force=False,
1503 editor=False, extra=None):
1503 editor=False, extra=None):
1504 """Add a new revision to current repository.
1504 """Add a new revision to current repository.
1505
1505
1506 Revision information is gathered from the working directory,
1506 Revision information is gathered from the working directory,
1507 match can be used to filter the committed files. If editor is
1507 match can be used to filter the committed files. If editor is
1508 supplied, it is called to get a commit message.
1508 supplied, it is called to get a commit message.
1509 """
1509 """
1510 if extra is None:
1510 if extra is None:
1511 extra = {}
1511 extra = {}
1512
1512
1513 def fail(f, msg):
1513 def fail(f, msg):
1514 raise error.Abort('%s: %s' % (f, msg))
1514 raise error.Abort('%s: %s' % (f, msg))
1515
1515
1516 if not match:
1516 if not match:
1517 match = matchmod.always(self.root, '')
1517 match = matchmod.always(self.root, '')
1518
1518
1519 if not force:
1519 if not force:
1520 vdirs = []
1520 vdirs = []
1521 match.explicitdir = vdirs.append
1521 match.explicitdir = vdirs.append
1522 match.bad = fail
1522 match.bad = fail
1523
1523
1524 wlock = lock = tr = None
1524 wlock = lock = tr = None
1525 try:
1525 try:
1526 wlock = self.wlock()
1526 wlock = self.wlock()
1527 lock = self.lock() # for recent changelog (see issue4368)
1527 lock = self.lock() # for recent changelog (see issue4368)
1528
1528
1529 wctx = self[None]
1529 wctx = self[None]
1530 merge = len(wctx.parents()) > 1
1530 merge = len(wctx.parents()) > 1
1531
1531
1532 if not force and merge and match.ispartial():
1532 if not force and merge and match.ispartial():
1533 raise error.Abort(_('cannot partially commit a merge '
1533 raise error.Abort(_('cannot partially commit a merge '
1534 '(do not specify files or patterns)'))
1534 '(do not specify files or patterns)'))
1535
1535
1536 status = self.status(match=match, clean=force)
1536 status = self.status(match=match, clean=force)
1537 if force:
1537 if force:
1538 status.modified.extend(status.clean) # mq may commit clean files
1538 status.modified.extend(status.clean) # mq may commit clean files
1539
1539
1540 # check subrepos
1540 # check subrepos
1541 subs = []
1541 subs = []
1542 commitsubs = set()
1542 commitsubs = set()
1543 newstate = wctx.substate.copy()
1543 newstate = wctx.substate.copy()
1544 # only manage subrepos and .hgsubstate if .hgsub is present
1544 # only manage subrepos and .hgsubstate if .hgsub is present
1545 if '.hgsub' in wctx:
1545 if '.hgsub' in wctx:
1546 # we'll decide whether to track this ourselves, thanks
1546 # we'll decide whether to track this ourselves, thanks
1547 for c in status.modified, status.added, status.removed:
1547 for c in status.modified, status.added, status.removed:
1548 if '.hgsubstate' in c:
1548 if '.hgsubstate' in c:
1549 c.remove('.hgsubstate')
1549 c.remove('.hgsubstate')
1550
1550
1551 # compare current state to last committed state
1551 # compare current state to last committed state
1552 # build new substate based on last committed state
1552 # build new substate based on last committed state
1553 oldstate = wctx.p1().substate
1553 oldstate = wctx.p1().substate
1554 for s in sorted(newstate.keys()):
1554 for s in sorted(newstate.keys()):
1555 if not match(s):
1555 if not match(s):
1556 # ignore working copy, use old state if present
1556 # ignore working copy, use old state if present
1557 if s in oldstate:
1557 if s in oldstate:
1558 newstate[s] = oldstate[s]
1558 newstate[s] = oldstate[s]
1559 continue
1559 continue
1560 if not force:
1560 if not force:
1561 raise error.Abort(
1561 raise error.Abort(
1562 _("commit with new subrepo %s excluded") % s)
1562 _("commit with new subrepo %s excluded") % s)
1563 dirtyreason = wctx.sub(s).dirtyreason(True)
1563 dirtyreason = wctx.sub(s).dirtyreason(True)
1564 if dirtyreason:
1564 if dirtyreason:
1565 if not self.ui.configbool('ui', 'commitsubrepos'):
1565 if not self.ui.configbool('ui', 'commitsubrepos'):
1566 raise error.Abort(dirtyreason,
1566 raise error.Abort(dirtyreason,
1567 hint=_("use --subrepos for recursive commit"))
1567 hint=_("use --subrepos for recursive commit"))
1568 subs.append(s)
1568 subs.append(s)
1569 commitsubs.add(s)
1569 commitsubs.add(s)
1570 else:
1570 else:
1571 bs = wctx.sub(s).basestate()
1571 bs = wctx.sub(s).basestate()
1572 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1572 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1573 if oldstate.get(s, (None, None, None))[1] != bs:
1573 if oldstate.get(s, (None, None, None))[1] != bs:
1574 subs.append(s)
1574 subs.append(s)
1575
1575
1576 # check for removed subrepos
1576 # check for removed subrepos
1577 for p in wctx.parents():
1577 for p in wctx.parents():
1578 r = [s for s in p.substate if s not in newstate]
1578 r = [s for s in p.substate if s not in newstate]
1579 subs += [s for s in r if match(s)]
1579 subs += [s for s in r if match(s)]
1580 if subs:
1580 if subs:
1581 if (not match('.hgsub') and
1581 if (not match('.hgsub') and
1582 '.hgsub' in (wctx.modified() + wctx.added())):
1582 '.hgsub' in (wctx.modified() + wctx.added())):
1583 raise error.Abort(
1583 raise error.Abort(
1584 _("can't commit subrepos without .hgsub"))
1584 _("can't commit subrepos without .hgsub"))
1585 status.modified.insert(0, '.hgsubstate')
1585 status.modified.insert(0, '.hgsubstate')
1586
1586
1587 elif '.hgsub' in status.removed:
1587 elif '.hgsub' in status.removed:
1588 # clean up .hgsubstate when .hgsub is removed
1588 # clean up .hgsubstate when .hgsub is removed
1589 if ('.hgsubstate' in wctx and
1589 if ('.hgsubstate' in wctx and
1590 '.hgsubstate' not in (status.modified + status.added +
1590 '.hgsubstate' not in (status.modified + status.added +
1591 status.removed)):
1591 status.removed)):
1592 status.removed.insert(0, '.hgsubstate')
1592 status.removed.insert(0, '.hgsubstate')
1593
1593
1594 # make sure all explicit patterns are matched
1594 # make sure all explicit patterns are matched
1595 if not force:
1595 if not force:
1596 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1596 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1597
1597
1598 cctx = context.workingcommitctx(self, status,
1598 cctx = context.workingcommitctx(self, status,
1599 text, user, date, extra)
1599 text, user, date, extra)
1600
1600
1601 # internal config: ui.allowemptycommit
1601 # internal config: ui.allowemptycommit
1602 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1602 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1603 or extra.get('close') or merge or cctx.files()
1603 or extra.get('close') or merge or cctx.files()
1604 or self.ui.configbool('ui', 'allowemptycommit'))
1604 or self.ui.configbool('ui', 'allowemptycommit'))
1605 if not allowemptycommit:
1605 if not allowemptycommit:
1606 return None
1606 return None
1607
1607
1608 if merge and cctx.deleted():
1608 if merge and cctx.deleted():
1609 raise error.Abort(_("cannot commit merge with missing files"))
1609 raise error.Abort(_("cannot commit merge with missing files"))
1610
1610
1611 ms = mergemod.mergestate.read(self)
1611 ms = mergemod.mergestate.read(self)
1612
1612
1613 if list(ms.unresolved()):
1613 if list(ms.unresolved()):
1614 raise error.Abort(_('unresolved merge conflicts '
1614 raise error.Abort(_('unresolved merge conflicts '
1615 '(see "hg help resolve")'))
1615 '(see "hg help resolve")'))
1616 if ms.mdstate() != 's' or list(ms.driverresolved()):
1616 if ms.mdstate() != 's' or list(ms.driverresolved()):
1617 raise error.Abort(_('driver-resolved merge conflicts'),
1617 raise error.Abort(_('driver-resolved merge conflicts'),
1618 hint=_('run "hg resolve --all" to resolve'))
1618 hint=_('run "hg resolve --all" to resolve'))
1619
1619
1620 if editor:
1620 if editor:
1621 cctx._text = editor(self, cctx, subs)
1621 cctx._text = editor(self, cctx, subs)
1622 edited = (text != cctx._text)
1622 edited = (text != cctx._text)
1623
1623
1624 # Save commit message in case this transaction gets rolled back
1624 # Save commit message in case this transaction gets rolled back
1625 # (e.g. by a pretxncommit hook). Leave the content alone on
1625 # (e.g. by a pretxncommit hook). Leave the content alone on
1626 # the assumption that the user will use the same editor again.
1626 # the assumption that the user will use the same editor again.
1627 msgfn = self.savecommitmessage(cctx._text)
1627 msgfn = self.savecommitmessage(cctx._text)
1628
1628
1629 # commit subs and write new state
1629 # commit subs and write new state
1630 if subs:
1630 if subs:
1631 for s in sorted(commitsubs):
1631 for s in sorted(commitsubs):
1632 sub = wctx.sub(s)
1632 sub = wctx.sub(s)
1633 self.ui.status(_('committing subrepository %s\n') %
1633 self.ui.status(_('committing subrepository %s\n') %
1634 subrepo.subrelpath(sub))
1634 subrepo.subrelpath(sub))
1635 sr = sub.commit(cctx._text, user, date)
1635 sr = sub.commit(cctx._text, user, date)
1636 newstate[s] = (newstate[s][0], sr)
1636 newstate[s] = (newstate[s][0], sr)
1637 subrepo.writestate(self, newstate)
1637 subrepo.writestate(self, newstate)
1638
1638
1639 p1, p2 = self.dirstate.parents()
1639 p1, p2 = self.dirstate.parents()
1640 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1640 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1641 try:
1641 try:
1642 self.hook("precommit", throw=True, parent1=hookp1,
1642 self.hook("precommit", throw=True, parent1=hookp1,
1643 parent2=hookp2)
1643 parent2=hookp2)
1644 tr = self.transaction('commit')
1644 tr = self.transaction('commit')
1645 ret = self.commitctx(cctx, True)
1645 ret = self.commitctx(cctx, True)
1646 except: # re-raises
1646 except: # re-raises
1647 if edited:
1647 if edited:
1648 self.ui.write(
1648 self.ui.write(
1649 _('note: commit message saved in %s\n') % msgfn)
1649 _('note: commit message saved in %s\n') % msgfn)
1650 raise
1650 raise
1651 # update bookmarks, dirstate and mergestate
1651 # update bookmarks, dirstate and mergestate
1652 bookmarks.update(self, [p1, p2], ret)
1652 bookmarks.update(self, [p1, p2], ret)
1653 cctx.markcommitted(ret)
1653 cctx.markcommitted(ret)
1654 ms.reset()
1654 ms.reset()
1655 tr.close()
1655 tr.close()
1656
1656
1657 finally:
1657 finally:
1658 lockmod.release(tr, lock, wlock)
1658 lockmod.release(tr, lock, wlock)
1659
1659
1660 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1660 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1661 # hack for command that use a temporary commit (eg: histedit)
1661 # hack for command that use a temporary commit (eg: histedit)
1662 # temporary commit got stripped before hook release
1662 # temporary commit got stripped before hook release
1663 if self.changelog.hasnode(ret):
1663 if self.changelog.hasnode(ret):
1664 self.hook("commit", node=node, parent1=parent1,
1664 self.hook("commit", node=node, parent1=parent1,
1665 parent2=parent2)
1665 parent2=parent2)
1666 self._afterlock(commithook)
1666 self._afterlock(commithook)
1667 return ret
1667 return ret
1668
1668
1669 @unfilteredmethod
1669 @unfilteredmethod
1670 def commitctx(self, ctx, error=False):
1670 def commitctx(self, ctx, error=False):
1671 """Add a new revision to current repository.
1671 """Add a new revision to current repository.
1672 Revision information is passed via the context argument.
1672 Revision information is passed via the context argument.
1673 """
1673 """
1674
1674
1675 tr = None
1675 tr = None
1676 p1, p2 = ctx.p1(), ctx.p2()
1676 p1, p2 = ctx.p1(), ctx.p2()
1677 user = ctx.user()
1677 user = ctx.user()
1678
1678
1679 lock = self.lock()
1679 lock = self.lock()
1680 try:
1680 try:
1681 tr = self.transaction("commit")
1681 tr = self.transaction("commit")
1682 trp = weakref.proxy(tr)
1682 trp = weakref.proxy(tr)
1683
1683
1684 if ctx.files():
1684 if ctx.files():
1685 m1 = p1.manifest()
1685 m1 = p1.manifest()
1686 m2 = p2.manifest()
1686 m2 = p2.manifest()
1687 m = m1.copy()
1687 m = m1.copy()
1688
1688
1689 # check in files
1689 # check in files
1690 added = []
1690 added = []
1691 changed = []
1691 changed = []
1692 removed = list(ctx.removed())
1692 removed = list(ctx.removed())
1693 linkrev = len(self)
1693 linkrev = len(self)
1694 self.ui.note(_("committing files:\n"))
1694 self.ui.note(_("committing files:\n"))
1695 for f in sorted(ctx.modified() + ctx.added()):
1695 for f in sorted(ctx.modified() + ctx.added()):
1696 self.ui.note(f + "\n")
1696 self.ui.note(f + "\n")
1697 try:
1697 try:
1698 fctx = ctx[f]
1698 fctx = ctx[f]
1699 if fctx is None:
1699 if fctx is None:
1700 removed.append(f)
1700 removed.append(f)
1701 else:
1701 else:
1702 added.append(f)
1702 added.append(f)
1703 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1703 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1704 trp, changed)
1704 trp, changed)
1705 m.setflag(f, fctx.flags())
1705 m.setflag(f, fctx.flags())
1706 except OSError as inst:
1706 except OSError as inst:
1707 self.ui.warn(_("trouble committing %s!\n") % f)
1707 self.ui.warn(_("trouble committing %s!\n") % f)
1708 raise
1708 raise
1709 except IOError as inst:
1709 except IOError as inst:
1710 errcode = getattr(inst, 'errno', errno.ENOENT)
1710 errcode = getattr(inst, 'errno', errno.ENOENT)
1711 if error or errcode and errcode != errno.ENOENT:
1711 if error or errcode and errcode != errno.ENOENT:
1712 self.ui.warn(_("trouble committing %s!\n") % f)
1712 self.ui.warn(_("trouble committing %s!\n") % f)
1713 raise
1713 raise
1714
1714
1715 # update manifest
1715 # update manifest
1716 self.ui.note(_("committing manifest\n"))
1716 self.ui.note(_("committing manifest\n"))
1717 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1717 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1718 drop = [f for f in removed if f in m]
1718 drop = [f for f in removed if f in m]
1719 for f in drop:
1719 for f in drop:
1720 del m[f]
1720 del m[f]
1721 mn = self.manifest.add(m, trp, linkrev,
1721 mn = self.manifest.add(m, trp, linkrev,
1722 p1.manifestnode(), p2.manifestnode(),
1722 p1.manifestnode(), p2.manifestnode(),
1723 added, drop)
1723 added, drop)
1724 files = changed + removed
1724 files = changed + removed
1725 else:
1725 else:
1726 mn = p1.manifestnode()
1726 mn = p1.manifestnode()
1727 files = []
1727 files = []
1728
1728
1729 # update changelog
1729 # update changelog
1730 self.ui.note(_("committing changelog\n"))
1730 self.ui.note(_("committing changelog\n"))
1731 self.changelog.delayupdate(tr)
1731 self.changelog.delayupdate(tr)
1732 n = self.changelog.add(mn, files, ctx.description(),
1732 n = self.changelog.add(mn, files, ctx.description(),
1733 trp, p1.node(), p2.node(),
1733 trp, p1.node(), p2.node(),
1734 user, ctx.date(), ctx.extra().copy())
1734 user, ctx.date(), ctx.extra().copy())
1735 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1735 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1736 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1736 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1737 parent2=xp2)
1737 parent2=xp2)
1738 # set the new commit is proper phase
1738 # set the new commit is proper phase
1739 targetphase = subrepo.newcommitphase(self.ui, ctx)
1739 targetphase = subrepo.newcommitphase(self.ui, ctx)
1740 if targetphase:
1740 if targetphase:
1741 # retract boundary do not alter parent changeset.
1741 # retract boundary do not alter parent changeset.
1742 # if a parent have higher the resulting phase will
1742 # if a parent have higher the resulting phase will
1743 # be compliant anyway
1743 # be compliant anyway
1744 #
1744 #
1745 # if minimal phase was 0 we don't need to retract anything
1745 # if minimal phase was 0 we don't need to retract anything
1746 phases.retractboundary(self, tr, targetphase, [n])
1746 phases.retractboundary(self, tr, targetphase, [n])
1747 tr.close()
1747 tr.close()
1748 branchmap.updatecache(self.filtered('served'))
1748 branchmap.updatecache(self.filtered('served'))
1749 return n
1749 return n
1750 finally:
1750 finally:
1751 if tr:
1751 if tr:
1752 tr.release()
1752 tr.release()
1753 lock.release()
1753 lock.release()
1754
1754
1755 @unfilteredmethod
1755 @unfilteredmethod
1756 def destroying(self):
1756 def destroying(self):
1757 '''Inform the repository that nodes are about to be destroyed.
1757 '''Inform the repository that nodes are about to be destroyed.
1758 Intended for use by strip and rollback, so there's a common
1758 Intended for use by strip and rollback, so there's a common
1759 place for anything that has to be done before destroying history.
1759 place for anything that has to be done before destroying history.
1760
1760
1761 This is mostly useful for saving state that is in memory and waiting
1761 This is mostly useful for saving state that is in memory and waiting
1762 to be flushed when the current lock is released. Because a call to
1762 to be flushed when the current lock is released. Because a call to
1763 destroyed is imminent, the repo will be invalidated causing those
1763 destroyed is imminent, the repo will be invalidated causing those
1764 changes to stay in memory (waiting for the next unlock), or vanish
1764 changes to stay in memory (waiting for the next unlock), or vanish
1765 completely.
1765 completely.
1766 '''
1766 '''
1767 # When using the same lock to commit and strip, the phasecache is left
1767 # When using the same lock to commit and strip, the phasecache is left
1768 # dirty after committing. Then when we strip, the repo is invalidated,
1768 # dirty after committing. Then when we strip, the repo is invalidated,
1769 # causing those changes to disappear.
1769 # causing those changes to disappear.
1770 if '_phasecache' in vars(self):
1770 if '_phasecache' in vars(self):
1771 self._phasecache.write()
1771 self._phasecache.write()
1772
1772
1773 @unfilteredmethod
1773 @unfilteredmethod
1774 def destroyed(self):
1774 def destroyed(self):
1775 '''Inform the repository that nodes have been destroyed.
1775 '''Inform the repository that nodes have been destroyed.
1776 Intended for use by strip and rollback, so there's a common
1776 Intended for use by strip and rollback, so there's a common
1777 place for anything that has to be done after destroying history.
1777 place for anything that has to be done after destroying history.
1778 '''
1778 '''
1779 # When one tries to:
1779 # When one tries to:
1780 # 1) destroy nodes thus calling this method (e.g. strip)
1780 # 1) destroy nodes thus calling this method (e.g. strip)
1781 # 2) use phasecache somewhere (e.g. commit)
1781 # 2) use phasecache somewhere (e.g. commit)
1782 #
1782 #
1783 # then 2) will fail because the phasecache contains nodes that were
1783 # then 2) will fail because the phasecache contains nodes that were
1784 # removed. We can either remove phasecache from the filecache,
1784 # removed. We can either remove phasecache from the filecache,
1785 # causing it to reload next time it is accessed, or simply filter
1785 # causing it to reload next time it is accessed, or simply filter
1786 # the removed nodes now and write the updated cache.
1786 # the removed nodes now and write the updated cache.
1787 self._phasecache.filterunknown(self)
1787 self._phasecache.filterunknown(self)
1788 self._phasecache.write()
1788 self._phasecache.write()
1789
1789
1790 # update the 'served' branch cache to help read only server process
1790 # update the 'served' branch cache to help read only server process
1791 # Thanks to branchcache collaboration this is done from the nearest
1791 # Thanks to branchcache collaboration this is done from the nearest
1792 # filtered subset and it is expected to be fast.
1792 # filtered subset and it is expected to be fast.
1793 branchmap.updatecache(self.filtered('served'))
1793 branchmap.updatecache(self.filtered('served'))
1794
1794
1795 # Ensure the persistent tag cache is updated. Doing it now
1795 # Ensure the persistent tag cache is updated. Doing it now
1796 # means that the tag cache only has to worry about destroyed
1796 # means that the tag cache only has to worry about destroyed
1797 # heads immediately after a strip/rollback. That in turn
1797 # heads immediately after a strip/rollback. That in turn
1798 # guarantees that "cachetip == currenttip" (comparing both rev
1798 # guarantees that "cachetip == currenttip" (comparing both rev
1799 # and node) always means no nodes have been added or destroyed.
1799 # and node) always means no nodes have been added or destroyed.
1800
1800
1801 # XXX this is suboptimal when qrefresh'ing: we strip the current
1801 # XXX this is suboptimal when qrefresh'ing: we strip the current
1802 # head, refresh the tag cache, then immediately add a new head.
1802 # head, refresh the tag cache, then immediately add a new head.
1803 # But I think doing it this way is necessary for the "instant
1803 # But I think doing it this way is necessary for the "instant
1804 # tag cache retrieval" case to work.
1804 # tag cache retrieval" case to work.
1805 self.invalidate()
1805 self.invalidate()
1806
1806
1807 def walk(self, match, node=None):
1807 def walk(self, match, node=None):
1808 '''
1808 '''
1809 walk recursively through the directory tree or a given
1809 walk recursively through the directory tree or a given
1810 changeset, finding all files matched by the match
1810 changeset, finding all files matched by the match
1811 function
1811 function
1812 '''
1812 '''
1813 return self[node].walk(match)
1813 return self[node].walk(match)
1814
1814
1815 def status(self, node1='.', node2=None, match=None,
1815 def status(self, node1='.', node2=None, match=None,
1816 ignored=False, clean=False, unknown=False,
1816 ignored=False, clean=False, unknown=False,
1817 listsubrepos=False):
1817 listsubrepos=False):
1818 '''a convenience method that calls node1.status(node2)'''
1818 '''a convenience method that calls node1.status(node2)'''
1819 return self[node1].status(node2, match, ignored, clean, unknown,
1819 return self[node1].status(node2, match, ignored, clean, unknown,
1820 listsubrepos)
1820 listsubrepos)
1821
1821
1822 def heads(self, start=None):
1822 def heads(self, start=None):
1823 heads = self.changelog.heads(start)
1823 heads = self.changelog.heads(start)
1824 # sort the output in rev descending order
1824 # sort the output in rev descending order
1825 return sorted(heads, key=self.changelog.rev, reverse=True)
1825 return sorted(heads, key=self.changelog.rev, reverse=True)
1826
1826
1827 def branchheads(self, branch=None, start=None, closed=False):
1827 def branchheads(self, branch=None, start=None, closed=False):
1828 '''return a (possibly filtered) list of heads for the given branch
1828 '''return a (possibly filtered) list of heads for the given branch
1829
1829
1830 Heads are returned in topological order, from newest to oldest.
1830 Heads are returned in topological order, from newest to oldest.
1831 If branch is None, use the dirstate branch.
1831 If branch is None, use the dirstate branch.
1832 If start is not None, return only heads reachable from start.
1832 If start is not None, return only heads reachable from start.
1833 If closed is True, return heads that are marked as closed as well.
1833 If closed is True, return heads that are marked as closed as well.
1834 '''
1834 '''
1835 if branch is None:
1835 if branch is None:
1836 branch = self[None].branch()
1836 branch = self[None].branch()
1837 branches = self.branchmap()
1837 branches = self.branchmap()
1838 if branch not in branches:
1838 if branch not in branches:
1839 return []
1839 return []
1840 # the cache returns heads ordered lowest to highest
1840 # the cache returns heads ordered lowest to highest
1841 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1841 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1842 if start is not None:
1842 if start is not None:
1843 # filter out the heads that cannot be reached from startrev
1843 # filter out the heads that cannot be reached from startrev
1844 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1844 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1845 bheads = [h for h in bheads if h in fbheads]
1845 bheads = [h for h in bheads if h in fbheads]
1846 return bheads
1846 return bheads
1847
1847
1848 def branches(self, nodes):
1848 def branches(self, nodes):
1849 if not nodes:
1849 if not nodes:
1850 nodes = [self.changelog.tip()]
1850 nodes = [self.changelog.tip()]
1851 b = []
1851 b = []
1852 for n in nodes:
1852 for n in nodes:
1853 t = n
1853 t = n
1854 while True:
1854 while True:
1855 p = self.changelog.parents(n)
1855 p = self.changelog.parents(n)
1856 if p[1] != nullid or p[0] == nullid:
1856 if p[1] != nullid or p[0] == nullid:
1857 b.append((t, n, p[0], p[1]))
1857 b.append((t, n, p[0], p[1]))
1858 break
1858 break
1859 n = p[0]
1859 n = p[0]
1860 return b
1860 return b
1861
1861
1862 def between(self, pairs):
1862 def between(self, pairs):
1863 r = []
1863 r = []
1864
1864
1865 for top, bottom in pairs:
1865 for top, bottom in pairs:
1866 n, l, i = top, [], 0
1866 n, l, i = top, [], 0
1867 f = 1
1867 f = 1
1868
1868
1869 while n != bottom and n != nullid:
1869 while n != bottom and n != nullid:
1870 p = self.changelog.parents(n)[0]
1870 p = self.changelog.parents(n)[0]
1871 if i == f:
1871 if i == f:
1872 l.append(n)
1872 l.append(n)
1873 f = f * 2
1873 f = f * 2
1874 n = p
1874 n = p
1875 i += 1
1875 i += 1
1876
1876
1877 r.append(l)
1877 r.append(l)
1878
1878
1879 return r
1879 return r
1880
1880
1881 def checkpush(self, pushop):
1881 def checkpush(self, pushop):
1882 """Extensions can override this function if additional checks have
1882 """Extensions can override this function if additional checks have
1883 to be performed before pushing, or call it if they override push
1883 to be performed before pushing, or call it if they override push
1884 command.
1884 command.
1885 """
1885 """
1886 pass
1886 pass
1887
1887
1888 @unfilteredpropertycache
1888 @unfilteredpropertycache
1889 def prepushoutgoinghooks(self):
1889 def prepushoutgoinghooks(self):
1890 """Return util.hooks consists of "(repo, remote, outgoing)"
1890 """Return util.hooks consists of a pushop with repo, remote, outgoing
1891 functions, which are called before pushing changesets.
1891 methods, which are called before pushing changesets.
1892 """
1892 """
1893 return util.hooks()
1893 return util.hooks()
1894
1894
1895 def pushkey(self, namespace, key, old, new):
1895 def pushkey(self, namespace, key, old, new):
1896 try:
1896 try:
1897 tr = self.currenttransaction()
1897 tr = self.currenttransaction()
1898 hookargs = {}
1898 hookargs = {}
1899 if tr is not None:
1899 if tr is not None:
1900 hookargs.update(tr.hookargs)
1900 hookargs.update(tr.hookargs)
1901 hookargs['namespace'] = namespace
1901 hookargs['namespace'] = namespace
1902 hookargs['key'] = key
1902 hookargs['key'] = key
1903 hookargs['old'] = old
1903 hookargs['old'] = old
1904 hookargs['new'] = new
1904 hookargs['new'] = new
1905 self.hook('prepushkey', throw=True, **hookargs)
1905 self.hook('prepushkey', throw=True, **hookargs)
1906 except error.HookAbort as exc:
1906 except error.HookAbort as exc:
1907 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1907 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1908 if exc.hint:
1908 if exc.hint:
1909 self.ui.write_err(_("(%s)\n") % exc.hint)
1909 self.ui.write_err(_("(%s)\n") % exc.hint)
1910 return False
1910 return False
1911 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1911 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1912 ret = pushkey.push(self, namespace, key, old, new)
1912 ret = pushkey.push(self, namespace, key, old, new)
1913 def runhook():
1913 def runhook():
1914 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1914 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1915 ret=ret)
1915 ret=ret)
1916 self._afterlock(runhook)
1916 self._afterlock(runhook)
1917 return ret
1917 return ret
1918
1918
1919 def listkeys(self, namespace):
1919 def listkeys(self, namespace):
1920 self.hook('prelistkeys', throw=True, namespace=namespace)
1920 self.hook('prelistkeys', throw=True, namespace=namespace)
1921 self.ui.debug('listing keys for "%s"\n' % namespace)
1921 self.ui.debug('listing keys for "%s"\n' % namespace)
1922 values = pushkey.list(self, namespace)
1922 values = pushkey.list(self, namespace)
1923 self.hook('listkeys', namespace=namespace, values=values)
1923 self.hook('listkeys', namespace=namespace, values=values)
1924 return values
1924 return values
1925
1925
1926 def debugwireargs(self, one, two, three=None, four=None, five=None):
1926 def debugwireargs(self, one, two, three=None, four=None, five=None):
1927 '''used to test argument passing over the wire'''
1927 '''used to test argument passing over the wire'''
1928 return "%s %s %s %s %s" % (one, two, three, four, five)
1928 return "%s %s %s %s %s" % (one, two, three, four, five)
1929
1929
1930 def savecommitmessage(self, text):
1930 def savecommitmessage(self, text):
1931 fp = self.vfs('last-message.txt', 'wb')
1931 fp = self.vfs('last-message.txt', 'wb')
1932 try:
1932 try:
1933 fp.write(text)
1933 fp.write(text)
1934 finally:
1934 finally:
1935 fp.close()
1935 fp.close()
1936 return self.pathto(fp.name[len(self.root) + 1:])
1936 return self.pathto(fp.name[len(self.root) + 1:])
1937
1937
1938 # used to avoid circular references so destructors work
1938 # used to avoid circular references so destructors work
1939 def aftertrans(files):
1939 def aftertrans(files):
1940 renamefiles = [tuple(t) for t in files]
1940 renamefiles = [tuple(t) for t in files]
1941 def a():
1941 def a():
1942 for vfs, src, dest in renamefiles:
1942 for vfs, src, dest in renamefiles:
1943 try:
1943 try:
1944 vfs.rename(src, dest)
1944 vfs.rename(src, dest)
1945 except OSError: # journal file does not yet exist
1945 except OSError: # journal file does not yet exist
1946 pass
1946 pass
1947 return a
1947 return a
1948
1948
1949 def undoname(fn):
1949 def undoname(fn):
1950 base, name = os.path.split(fn)
1950 base, name = os.path.split(fn)
1951 assert name.startswith('journal')
1951 assert name.startswith('journal')
1952 return os.path.join(base, name.replace('journal', 'undo', 1))
1952 return os.path.join(base, name.replace('journal', 'undo', 1))
1953
1953
1954 def instance(ui, path, create):
1954 def instance(ui, path, create):
1955 return localrepository(ui, util.urllocalpath(path), create)
1955 return localrepository(ui, util.urllocalpath(path), create)
1956
1956
1957 def islocal(path):
1957 def islocal(path):
1958 return True
1958 return True
1959
1959
1960 def newreporequirements(repo):
1960 def newreporequirements(repo):
1961 """Determine the set of requirements for a new local repository.
1961 """Determine the set of requirements for a new local repository.
1962
1962
1963 Extensions can wrap this function to specify custom requirements for
1963 Extensions can wrap this function to specify custom requirements for
1964 new repositories.
1964 new repositories.
1965 """
1965 """
1966 ui = repo.ui
1966 ui = repo.ui
1967 requirements = set(['revlogv1'])
1967 requirements = set(['revlogv1'])
1968 if ui.configbool('format', 'usestore', True):
1968 if ui.configbool('format', 'usestore', True):
1969 requirements.add('store')
1969 requirements.add('store')
1970 if ui.configbool('format', 'usefncache', True):
1970 if ui.configbool('format', 'usefncache', True):
1971 requirements.add('fncache')
1971 requirements.add('fncache')
1972 if ui.configbool('format', 'dotencode', True):
1972 if ui.configbool('format', 'dotencode', True):
1973 requirements.add('dotencode')
1973 requirements.add('dotencode')
1974
1974
1975 if scmutil.gdinitconfig(ui):
1975 if scmutil.gdinitconfig(ui):
1976 requirements.add('generaldelta')
1976 requirements.add('generaldelta')
1977 if ui.configbool('experimental', 'treemanifest', False):
1977 if ui.configbool('experimental', 'treemanifest', False):
1978 requirements.add('treemanifest')
1978 requirements.add('treemanifest')
1979 if ui.configbool('experimental', 'manifestv2', False):
1979 if ui.configbool('experimental', 'manifestv2', False):
1980 requirements.add('manifestv2')
1980 requirements.add('manifestv2')
1981
1981
1982 return requirements
1982 return requirements
General Comments 0
You need to be logged in to leave comments. Login now