##// END OF EJS Templates
changelog: handle writepending in the transaction...
Pierre-Yves David -
r23203:3872d563 default
parent child Browse files
Show More
@@ -1,832 +1,832 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 import weakref
8 import weakref
9 from i18n import _
9 from i18n import _
10 from node import nullrev, nullid, hex, short
10 from node import nullrev, nullid, hex, short
11 import mdiff, util, dagutil
11 import mdiff, util, dagutil
12 import struct, os, bz2, zlib, tempfile
12 import struct, os, bz2, zlib, tempfile
13 import discovery, error, phases, branchmap
13 import discovery, error, phases, branchmap
14
14
15 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
15 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
16 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
16 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
17
17
18 def readexactly(stream, n):
18 def readexactly(stream, n):
19 '''read n bytes from stream.read and abort if less was available'''
19 '''read n bytes from stream.read and abort if less was available'''
20 s = stream.read(n)
20 s = stream.read(n)
21 if len(s) < n:
21 if len(s) < n:
22 raise util.Abort(_("stream ended unexpectedly"
22 raise util.Abort(_("stream ended unexpectedly"
23 " (got %d bytes, expected %d)")
23 " (got %d bytes, expected %d)")
24 % (len(s), n))
24 % (len(s), n))
25 return s
25 return s
26
26
27 def getchunk(stream):
27 def getchunk(stream):
28 """return the next chunk from stream as a string"""
28 """return the next chunk from stream as a string"""
29 d = readexactly(stream, 4)
29 d = readexactly(stream, 4)
30 l = struct.unpack(">l", d)[0]
30 l = struct.unpack(">l", d)[0]
31 if l <= 4:
31 if l <= 4:
32 if l:
32 if l:
33 raise util.Abort(_("invalid chunk length %d") % l)
33 raise util.Abort(_("invalid chunk length %d") % l)
34 return ""
34 return ""
35 return readexactly(stream, l - 4)
35 return readexactly(stream, l - 4)
36
36
37 def chunkheader(length):
37 def chunkheader(length):
38 """return a changegroup chunk header (string)"""
38 """return a changegroup chunk header (string)"""
39 return struct.pack(">l", length + 4)
39 return struct.pack(">l", length + 4)
40
40
41 def closechunk():
41 def closechunk():
42 """return a changegroup chunk header (string) for a zero-length chunk"""
42 """return a changegroup chunk header (string) for a zero-length chunk"""
43 return struct.pack(">l", 0)
43 return struct.pack(">l", 0)
44
44
45 class nocompress(object):
45 class nocompress(object):
46 def compress(self, x):
46 def compress(self, x):
47 return x
47 return x
48 def flush(self):
48 def flush(self):
49 return ""
49 return ""
50
50
51 bundletypes = {
51 bundletypes = {
52 "": ("", nocompress), # only when using unbundle on ssh and old http servers
52 "": ("", nocompress), # only when using unbundle on ssh and old http servers
53 # since the unification ssh accepts a header but there
53 # since the unification ssh accepts a header but there
54 # is no capability signaling it.
54 # is no capability signaling it.
55 "HG10UN": ("HG10UN", nocompress),
55 "HG10UN": ("HG10UN", nocompress),
56 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
56 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
57 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
57 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
58 }
58 }
59
59
60 # hgweb uses this list to communicate its preferred type
60 # hgweb uses this list to communicate its preferred type
61 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
61 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
62
62
63 def writebundle(cg, filename, bundletype, vfs=None):
63 def writebundle(cg, filename, bundletype, vfs=None):
64 """Write a bundle file and return its filename.
64 """Write a bundle file and return its filename.
65
65
66 Existing files will not be overwritten.
66 Existing files will not be overwritten.
67 If no filename is specified, a temporary file is created.
67 If no filename is specified, a temporary file is created.
68 bz2 compression can be turned off.
68 bz2 compression can be turned off.
69 The bundle file will be deleted in case of errors.
69 The bundle file will be deleted in case of errors.
70 """
70 """
71
71
72 fh = None
72 fh = None
73 cleanup = None
73 cleanup = None
74 try:
74 try:
75 if filename:
75 if filename:
76 if vfs:
76 if vfs:
77 fh = vfs.open(filename, "wb")
77 fh = vfs.open(filename, "wb")
78 else:
78 else:
79 fh = open(filename, "wb")
79 fh = open(filename, "wb")
80 else:
80 else:
81 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
81 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
82 fh = os.fdopen(fd, "wb")
82 fh = os.fdopen(fd, "wb")
83 cleanup = filename
83 cleanup = filename
84
84
85 header, compressor = bundletypes[bundletype]
85 header, compressor = bundletypes[bundletype]
86 fh.write(header)
86 fh.write(header)
87 z = compressor()
87 z = compressor()
88
88
89 # parse the changegroup data, otherwise we will block
89 # parse the changegroup data, otherwise we will block
90 # in case of sshrepo because we don't know the end of the stream
90 # in case of sshrepo because we don't know the end of the stream
91
91
92 # an empty chunkgroup is the end of the changegroup
92 # an empty chunkgroup is the end of the changegroup
93 # a changegroup has at least 2 chunkgroups (changelog and manifest).
93 # a changegroup has at least 2 chunkgroups (changelog and manifest).
94 # after that, an empty chunkgroup is the end of the changegroup
94 # after that, an empty chunkgroup is the end of the changegroup
95 for chunk in cg.getchunks():
95 for chunk in cg.getchunks():
96 fh.write(z.compress(chunk))
96 fh.write(z.compress(chunk))
97 fh.write(z.flush())
97 fh.write(z.flush())
98 cleanup = None
98 cleanup = None
99 return filename
99 return filename
100 finally:
100 finally:
101 if fh is not None:
101 if fh is not None:
102 fh.close()
102 fh.close()
103 if cleanup is not None:
103 if cleanup is not None:
104 if filename and vfs:
104 if filename and vfs:
105 vfs.unlink(cleanup)
105 vfs.unlink(cleanup)
106 else:
106 else:
107 os.unlink(cleanup)
107 os.unlink(cleanup)
108
108
109 def decompressor(fh, alg):
109 def decompressor(fh, alg):
110 if alg == 'UN':
110 if alg == 'UN':
111 return fh
111 return fh
112 elif alg == 'GZ':
112 elif alg == 'GZ':
113 def generator(f):
113 def generator(f):
114 zd = zlib.decompressobj()
114 zd = zlib.decompressobj()
115 for chunk in util.filechunkiter(f):
115 for chunk in util.filechunkiter(f):
116 yield zd.decompress(chunk)
116 yield zd.decompress(chunk)
117 elif alg == 'BZ':
117 elif alg == 'BZ':
118 def generator(f):
118 def generator(f):
119 zd = bz2.BZ2Decompressor()
119 zd = bz2.BZ2Decompressor()
120 zd.decompress("BZ")
120 zd.decompress("BZ")
121 for chunk in util.filechunkiter(f, 4096):
121 for chunk in util.filechunkiter(f, 4096):
122 yield zd.decompress(chunk)
122 yield zd.decompress(chunk)
123 else:
123 else:
124 raise util.Abort("unknown bundle compression '%s'" % alg)
124 raise util.Abort("unknown bundle compression '%s'" % alg)
125 return util.chunkbuffer(generator(fh))
125 return util.chunkbuffer(generator(fh))
126
126
127 class cg1unpacker(object):
127 class cg1unpacker(object):
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
129 deltaheadersize = struct.calcsize(deltaheader)
129 deltaheadersize = struct.calcsize(deltaheader)
130 def __init__(self, fh, alg):
130 def __init__(self, fh, alg):
131 self._stream = decompressor(fh, alg)
131 self._stream = decompressor(fh, alg)
132 self._type = alg
132 self._type = alg
133 self.callback = None
133 self.callback = None
134 def compressed(self):
134 def compressed(self):
135 return self._type != 'UN'
135 return self._type != 'UN'
136 def read(self, l):
136 def read(self, l):
137 return self._stream.read(l)
137 return self._stream.read(l)
138 def seek(self, pos):
138 def seek(self, pos):
139 return self._stream.seek(pos)
139 return self._stream.seek(pos)
140 def tell(self):
140 def tell(self):
141 return self._stream.tell()
141 return self._stream.tell()
142 def close(self):
142 def close(self):
143 return self._stream.close()
143 return self._stream.close()
144
144
145 def chunklength(self):
145 def chunklength(self):
146 d = readexactly(self._stream, 4)
146 d = readexactly(self._stream, 4)
147 l = struct.unpack(">l", d)[0]
147 l = struct.unpack(">l", d)[0]
148 if l <= 4:
148 if l <= 4:
149 if l:
149 if l:
150 raise util.Abort(_("invalid chunk length %d") % l)
150 raise util.Abort(_("invalid chunk length %d") % l)
151 return 0
151 return 0
152 if self.callback:
152 if self.callback:
153 self.callback()
153 self.callback()
154 return l - 4
154 return l - 4
155
155
156 def changelogheader(self):
156 def changelogheader(self):
157 """v10 does not have a changelog header chunk"""
157 """v10 does not have a changelog header chunk"""
158 return {}
158 return {}
159
159
160 def manifestheader(self):
160 def manifestheader(self):
161 """v10 does not have a manifest header chunk"""
161 """v10 does not have a manifest header chunk"""
162 return {}
162 return {}
163
163
164 def filelogheader(self):
164 def filelogheader(self):
165 """return the header of the filelogs chunk, v10 only has the filename"""
165 """return the header of the filelogs chunk, v10 only has the filename"""
166 l = self.chunklength()
166 l = self.chunklength()
167 if not l:
167 if not l:
168 return {}
168 return {}
169 fname = readexactly(self._stream, l)
169 fname = readexactly(self._stream, l)
170 return {'filename': fname}
170 return {'filename': fname}
171
171
172 def _deltaheader(self, headertuple, prevnode):
172 def _deltaheader(self, headertuple, prevnode):
173 node, p1, p2, cs = headertuple
173 node, p1, p2, cs = headertuple
174 if prevnode is None:
174 if prevnode is None:
175 deltabase = p1
175 deltabase = p1
176 else:
176 else:
177 deltabase = prevnode
177 deltabase = prevnode
178 return node, p1, p2, deltabase, cs
178 return node, p1, p2, deltabase, cs
179
179
180 def deltachunk(self, prevnode):
180 def deltachunk(self, prevnode):
181 l = self.chunklength()
181 l = self.chunklength()
182 if not l:
182 if not l:
183 return {}
183 return {}
184 headerdata = readexactly(self._stream, self.deltaheadersize)
184 headerdata = readexactly(self._stream, self.deltaheadersize)
185 header = struct.unpack(self.deltaheader, headerdata)
185 header = struct.unpack(self.deltaheader, headerdata)
186 delta = readexactly(self._stream, l - self.deltaheadersize)
186 delta = readexactly(self._stream, l - self.deltaheadersize)
187 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
187 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
188 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
188 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
189 'deltabase': deltabase, 'delta': delta}
189 'deltabase': deltabase, 'delta': delta}
190
190
191 def getchunks(self):
191 def getchunks(self):
192 """returns all the chunks contains in the bundle
192 """returns all the chunks contains in the bundle
193
193
194 Used when you need to forward the binary stream to a file or another
194 Used when you need to forward the binary stream to a file or another
195 network API. To do so, it parse the changegroup data, otherwise it will
195 network API. To do so, it parse the changegroup data, otherwise it will
196 block in case of sshrepo because it don't know the end of the stream.
196 block in case of sshrepo because it don't know the end of the stream.
197 """
197 """
198 # an empty chunkgroup is the end of the changegroup
198 # an empty chunkgroup is the end of the changegroup
199 # a changegroup has at least 2 chunkgroups (changelog and manifest).
199 # a changegroup has at least 2 chunkgroups (changelog and manifest).
200 # after that, an empty chunkgroup is the end of the changegroup
200 # after that, an empty chunkgroup is the end of the changegroup
201 empty = False
201 empty = False
202 count = 0
202 count = 0
203 while not empty or count <= 2:
203 while not empty or count <= 2:
204 empty = True
204 empty = True
205 count += 1
205 count += 1
206 while True:
206 while True:
207 chunk = getchunk(self)
207 chunk = getchunk(self)
208 if not chunk:
208 if not chunk:
209 break
209 break
210 empty = False
210 empty = False
211 yield chunkheader(len(chunk))
211 yield chunkheader(len(chunk))
212 pos = 0
212 pos = 0
213 while pos < len(chunk):
213 while pos < len(chunk):
214 next = pos + 2**20
214 next = pos + 2**20
215 yield chunk[pos:next]
215 yield chunk[pos:next]
216 pos = next
216 pos = next
217 yield closechunk()
217 yield closechunk()
218
218
219 class cg2unpacker(cg1unpacker):
219 class cg2unpacker(cg1unpacker):
220 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
220 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
221 deltaheadersize = struct.calcsize(deltaheader)
221 deltaheadersize = struct.calcsize(deltaheader)
222
222
223 def _deltaheader(self, headertuple, prevnode):
223 def _deltaheader(self, headertuple, prevnode):
224 node, p1, p2, deltabase, cs = headertuple
224 node, p1, p2, deltabase, cs = headertuple
225 return node, p1, p2, deltabase, cs
225 return node, p1, p2, deltabase, cs
226
226
227 class headerlessfixup(object):
227 class headerlessfixup(object):
228 def __init__(self, fh, h):
228 def __init__(self, fh, h):
229 self._h = h
229 self._h = h
230 self._fh = fh
230 self._fh = fh
231 def read(self, n):
231 def read(self, n):
232 if self._h:
232 if self._h:
233 d, self._h = self._h[:n], self._h[n:]
233 d, self._h = self._h[:n], self._h[n:]
234 if len(d) < n:
234 if len(d) < n:
235 d += readexactly(self._fh, n - len(d))
235 d += readexactly(self._fh, n - len(d))
236 return d
236 return d
237 return readexactly(self._fh, n)
237 return readexactly(self._fh, n)
238
238
239 class cg1packer(object):
239 class cg1packer(object):
240 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
240 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
241 def __init__(self, repo, bundlecaps=None):
241 def __init__(self, repo, bundlecaps=None):
242 """Given a source repo, construct a bundler.
242 """Given a source repo, construct a bundler.
243
243
244 bundlecaps is optional and can be used to specify the set of
244 bundlecaps is optional and can be used to specify the set of
245 capabilities which can be used to build the bundle.
245 capabilities which can be used to build the bundle.
246 """
246 """
247 # Set of capabilities we can use to build the bundle.
247 # Set of capabilities we can use to build the bundle.
248 if bundlecaps is None:
248 if bundlecaps is None:
249 bundlecaps = set()
249 bundlecaps = set()
250 self._bundlecaps = bundlecaps
250 self._bundlecaps = bundlecaps
251 self._changelog = repo.changelog
251 self._changelog = repo.changelog
252 self._manifest = repo.manifest
252 self._manifest = repo.manifest
253 reorder = repo.ui.config('bundle', 'reorder', 'auto')
253 reorder = repo.ui.config('bundle', 'reorder', 'auto')
254 if reorder == 'auto':
254 if reorder == 'auto':
255 reorder = None
255 reorder = None
256 else:
256 else:
257 reorder = util.parsebool(reorder)
257 reorder = util.parsebool(reorder)
258 self._repo = repo
258 self._repo = repo
259 self._reorder = reorder
259 self._reorder = reorder
260 self._progress = repo.ui.progress
260 self._progress = repo.ui.progress
261 def close(self):
261 def close(self):
262 return closechunk()
262 return closechunk()
263
263
264 def fileheader(self, fname):
264 def fileheader(self, fname):
265 return chunkheader(len(fname)) + fname
265 return chunkheader(len(fname)) + fname
266
266
267 def group(self, nodelist, revlog, lookup, units=None, reorder=None):
267 def group(self, nodelist, revlog, lookup, units=None, reorder=None):
268 """Calculate a delta group, yielding a sequence of changegroup chunks
268 """Calculate a delta group, yielding a sequence of changegroup chunks
269 (strings).
269 (strings).
270
270
271 Given a list of changeset revs, return a set of deltas and
271 Given a list of changeset revs, return a set of deltas and
272 metadata corresponding to nodes. The first delta is
272 metadata corresponding to nodes. The first delta is
273 first parent(nodelist[0]) -> nodelist[0], the receiver is
273 first parent(nodelist[0]) -> nodelist[0], the receiver is
274 guaranteed to have this parent as it has all history before
274 guaranteed to have this parent as it has all history before
275 these changesets. In the case firstparent is nullrev the
275 these changesets. In the case firstparent is nullrev the
276 changegroup starts with a full revision.
276 changegroup starts with a full revision.
277
277
278 If units is not None, progress detail will be generated, units specifies
278 If units is not None, progress detail will be generated, units specifies
279 the type of revlog that is touched (changelog, manifest, etc.).
279 the type of revlog that is touched (changelog, manifest, etc.).
280 """
280 """
281 # if we don't have any revisions touched by these changesets, bail
281 # if we don't have any revisions touched by these changesets, bail
282 if len(nodelist) == 0:
282 if len(nodelist) == 0:
283 yield self.close()
283 yield self.close()
284 return
284 return
285
285
286 # for generaldelta revlogs, we linearize the revs; this will both be
286 # for generaldelta revlogs, we linearize the revs; this will both be
287 # much quicker and generate a much smaller bundle
287 # much quicker and generate a much smaller bundle
288 if (revlog._generaldelta and reorder is not False) or reorder:
288 if (revlog._generaldelta and reorder is not False) or reorder:
289 dag = dagutil.revlogdag(revlog)
289 dag = dagutil.revlogdag(revlog)
290 revs = set(revlog.rev(n) for n in nodelist)
290 revs = set(revlog.rev(n) for n in nodelist)
291 revs = dag.linearize(revs)
291 revs = dag.linearize(revs)
292 else:
292 else:
293 revs = sorted([revlog.rev(n) for n in nodelist])
293 revs = sorted([revlog.rev(n) for n in nodelist])
294
294
295 # add the parent of the first rev
295 # add the parent of the first rev
296 p = revlog.parentrevs(revs[0])[0]
296 p = revlog.parentrevs(revs[0])[0]
297 revs.insert(0, p)
297 revs.insert(0, p)
298
298
299 # build deltas
299 # build deltas
300 total = len(revs) - 1
300 total = len(revs) - 1
301 msgbundling = _('bundling')
301 msgbundling = _('bundling')
302 for r in xrange(len(revs) - 1):
302 for r in xrange(len(revs) - 1):
303 if units is not None:
303 if units is not None:
304 self._progress(msgbundling, r + 1, unit=units, total=total)
304 self._progress(msgbundling, r + 1, unit=units, total=total)
305 prev, curr = revs[r], revs[r + 1]
305 prev, curr = revs[r], revs[r + 1]
306 linknode = lookup(revlog.node(curr))
306 linknode = lookup(revlog.node(curr))
307 for c in self.revchunk(revlog, curr, prev, linknode):
307 for c in self.revchunk(revlog, curr, prev, linknode):
308 yield c
308 yield c
309
309
310 yield self.close()
310 yield self.close()
311
311
312 # filter any nodes that claim to be part of the known set
312 # filter any nodes that claim to be part of the known set
313 def prune(self, revlog, missing, commonrevs, source):
313 def prune(self, revlog, missing, commonrevs, source):
314 rr, rl = revlog.rev, revlog.linkrev
314 rr, rl = revlog.rev, revlog.linkrev
315 return [n for n in missing if rl(rr(n)) not in commonrevs]
315 return [n for n in missing if rl(rr(n)) not in commonrevs]
316
316
317 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
317 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
318 '''yield a sequence of changegroup chunks (strings)'''
318 '''yield a sequence of changegroup chunks (strings)'''
319 repo = self._repo
319 repo = self._repo
320 cl = self._changelog
320 cl = self._changelog
321 mf = self._manifest
321 mf = self._manifest
322 reorder = self._reorder
322 reorder = self._reorder
323 progress = self._progress
323 progress = self._progress
324
324
325 # for progress output
325 # for progress output
326 msgbundling = _('bundling')
326 msgbundling = _('bundling')
327
327
328 mfs = {} # needed manifests
328 mfs = {} # needed manifests
329 fnodes = {} # needed file nodes
329 fnodes = {} # needed file nodes
330 changedfiles = set()
330 changedfiles = set()
331
331
332 # Callback for the changelog, used to collect changed files and manifest
332 # Callback for the changelog, used to collect changed files and manifest
333 # nodes.
333 # nodes.
334 # Returns the linkrev node (identity in the changelog case).
334 # Returns the linkrev node (identity in the changelog case).
335 def lookupcl(x):
335 def lookupcl(x):
336 c = cl.read(x)
336 c = cl.read(x)
337 changedfiles.update(c[3])
337 changedfiles.update(c[3])
338 # record the first changeset introducing this manifest version
338 # record the first changeset introducing this manifest version
339 mfs.setdefault(c[0], x)
339 mfs.setdefault(c[0], x)
340 return x
340 return x
341
341
342 # Callback for the manifest, used to collect linkrevs for filelog
342 # Callback for the manifest, used to collect linkrevs for filelog
343 # revisions.
343 # revisions.
344 # Returns the linkrev node (collected in lookupcl).
344 # Returns the linkrev node (collected in lookupcl).
345 def lookupmf(x):
345 def lookupmf(x):
346 clnode = mfs[x]
346 clnode = mfs[x]
347 if not fastpathlinkrev:
347 if not fastpathlinkrev:
348 mdata = mf.readfast(x)
348 mdata = mf.readfast(x)
349 for f, n in mdata.iteritems():
349 for f, n in mdata.iteritems():
350 if f in changedfiles:
350 if f in changedfiles:
351 # record the first changeset introducing this filelog
351 # record the first changeset introducing this filelog
352 # version
352 # version
353 fnodes[f].setdefault(n, clnode)
353 fnodes[f].setdefault(n, clnode)
354 return clnode
354 return clnode
355
355
356 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets'),
356 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets'),
357 reorder=reorder):
357 reorder=reorder):
358 yield chunk
358 yield chunk
359 progress(msgbundling, None)
359 progress(msgbundling, None)
360
360
361 for f in changedfiles:
361 for f in changedfiles:
362 fnodes[f] = {}
362 fnodes[f] = {}
363 mfnodes = self.prune(mf, mfs, commonrevs, source)
363 mfnodes = self.prune(mf, mfs, commonrevs, source)
364 for chunk in self.group(mfnodes, mf, lookupmf, units=_('manifests'),
364 for chunk in self.group(mfnodes, mf, lookupmf, units=_('manifests'),
365 reorder=reorder):
365 reorder=reorder):
366 yield chunk
366 yield chunk
367 progress(msgbundling, None)
367 progress(msgbundling, None)
368
368
369 mfs.clear()
369 mfs.clear()
370 needed = set(cl.rev(x) for x in clnodes)
370 needed = set(cl.rev(x) for x in clnodes)
371
371
372 def linknodes(filerevlog, fname):
372 def linknodes(filerevlog, fname):
373 if fastpathlinkrev:
373 if fastpathlinkrev:
374 llr = filerevlog.linkrev
374 llr = filerevlog.linkrev
375 def genfilenodes():
375 def genfilenodes():
376 for r in filerevlog:
376 for r in filerevlog:
377 linkrev = llr(r)
377 linkrev = llr(r)
378 if linkrev in needed:
378 if linkrev in needed:
379 yield filerevlog.node(r), cl.node(linkrev)
379 yield filerevlog.node(r), cl.node(linkrev)
380 fnodes[fname] = dict(genfilenodes())
380 fnodes[fname] = dict(genfilenodes())
381 return fnodes.get(fname, {})
381 return fnodes.get(fname, {})
382
382
383 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
383 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
384 source):
384 source):
385 yield chunk
385 yield chunk
386
386
387 yield self.close()
387 yield self.close()
388 progress(msgbundling, None)
388 progress(msgbundling, None)
389
389
390 if clnodes:
390 if clnodes:
391 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
391 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
392
392
393 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
393 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
394 repo = self._repo
394 repo = self._repo
395 progress = self._progress
395 progress = self._progress
396 reorder = self._reorder
396 reorder = self._reorder
397 msgbundling = _('bundling')
397 msgbundling = _('bundling')
398
398
399 total = len(changedfiles)
399 total = len(changedfiles)
400 # for progress output
400 # for progress output
401 msgfiles = _('files')
401 msgfiles = _('files')
402 for i, fname in enumerate(sorted(changedfiles)):
402 for i, fname in enumerate(sorted(changedfiles)):
403 filerevlog = repo.file(fname)
403 filerevlog = repo.file(fname)
404 if not filerevlog:
404 if not filerevlog:
405 raise util.Abort(_("empty or missing revlog for %s") % fname)
405 raise util.Abort(_("empty or missing revlog for %s") % fname)
406
406
407 linkrevnodes = linknodes(filerevlog, fname)
407 linkrevnodes = linknodes(filerevlog, fname)
408 # Lookup for filenodes, we collected the linkrev nodes above in the
408 # Lookup for filenodes, we collected the linkrev nodes above in the
409 # fastpath case and with lookupmf in the slowpath case.
409 # fastpath case and with lookupmf in the slowpath case.
410 def lookupfilelog(x):
410 def lookupfilelog(x):
411 return linkrevnodes[x]
411 return linkrevnodes[x]
412
412
413 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs, source)
413 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs, source)
414 if filenodes:
414 if filenodes:
415 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
415 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
416 total=total)
416 total=total)
417 yield self.fileheader(fname)
417 yield self.fileheader(fname)
418 for chunk in self.group(filenodes, filerevlog, lookupfilelog,
418 for chunk in self.group(filenodes, filerevlog, lookupfilelog,
419 reorder=reorder):
419 reorder=reorder):
420 yield chunk
420 yield chunk
421
421
422 def deltaparent(self, revlog, rev, p1, p2, prev):
422 def deltaparent(self, revlog, rev, p1, p2, prev):
423 return prev
423 return prev
424
424
425 def revchunk(self, revlog, rev, prev, linknode):
425 def revchunk(self, revlog, rev, prev, linknode):
426 node = revlog.node(rev)
426 node = revlog.node(rev)
427 p1, p2 = revlog.parentrevs(rev)
427 p1, p2 = revlog.parentrevs(rev)
428 base = self.deltaparent(revlog, rev, p1, p2, prev)
428 base = self.deltaparent(revlog, rev, p1, p2, prev)
429
429
430 prefix = ''
430 prefix = ''
431 if base == nullrev:
431 if base == nullrev:
432 delta = revlog.revision(node)
432 delta = revlog.revision(node)
433 prefix = mdiff.trivialdiffheader(len(delta))
433 prefix = mdiff.trivialdiffheader(len(delta))
434 else:
434 else:
435 delta = revlog.revdiff(base, rev)
435 delta = revlog.revdiff(base, rev)
436 p1n, p2n = revlog.parents(node)
436 p1n, p2n = revlog.parents(node)
437 basenode = revlog.node(base)
437 basenode = revlog.node(base)
438 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
438 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
439 meta += prefix
439 meta += prefix
440 l = len(meta) + len(delta)
440 l = len(meta) + len(delta)
441 yield chunkheader(l)
441 yield chunkheader(l)
442 yield meta
442 yield meta
443 yield delta
443 yield delta
444 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
444 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
445 # do nothing with basenode, it is implicitly the previous one in HG10
445 # do nothing with basenode, it is implicitly the previous one in HG10
446 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
446 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
447
447
448 class cg2packer(cg1packer):
448 class cg2packer(cg1packer):
449
449
450 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
450 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
451
451
452 def group(self, nodelist, revlog, lookup, units=None, reorder=None):
452 def group(self, nodelist, revlog, lookup, units=None, reorder=None):
453 if (revlog._generaldelta and reorder is not True):
453 if (revlog._generaldelta and reorder is not True):
454 reorder = False
454 reorder = False
455 return cg1packer.group(self, nodelist, revlog, lookup,
455 return cg1packer.group(self, nodelist, revlog, lookup,
456 units=units, reorder=reorder)
456 units=units, reorder=reorder)
457
457
458 def deltaparent(self, revlog, rev, p1, p2, prev):
458 def deltaparent(self, revlog, rev, p1, p2, prev):
459 dp = revlog.deltaparent(rev)
459 dp = revlog.deltaparent(rev)
460 # avoid storing full revisions; pick prev in those cases
460 # avoid storing full revisions; pick prev in those cases
461 # also pick prev when we can't be sure remote has dp
461 # also pick prev when we can't be sure remote has dp
462 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
462 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
463 return prev
463 return prev
464 return dp
464 return dp
465
465
466 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
466 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
467 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
467 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
468
468
469 packermap = {'01': (cg1packer, cg1unpacker),
469 packermap = {'01': (cg1packer, cg1unpacker),
470 '02': (cg2packer, cg2unpacker)}
470 '02': (cg2packer, cg2unpacker)}
471
471
472 def _changegroupinfo(repo, nodes, source):
472 def _changegroupinfo(repo, nodes, source):
473 if repo.ui.verbose or source == 'bundle':
473 if repo.ui.verbose or source == 'bundle':
474 repo.ui.status(_("%d changesets found\n") % len(nodes))
474 repo.ui.status(_("%d changesets found\n") % len(nodes))
475 if repo.ui.debugflag:
475 if repo.ui.debugflag:
476 repo.ui.debug("list of changesets:\n")
476 repo.ui.debug("list of changesets:\n")
477 for node in nodes:
477 for node in nodes:
478 repo.ui.debug("%s\n" % hex(node))
478 repo.ui.debug("%s\n" % hex(node))
479
479
480 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
480 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
481 repo = repo.unfiltered()
481 repo = repo.unfiltered()
482 commonrevs = outgoing.common
482 commonrevs = outgoing.common
483 csets = outgoing.missing
483 csets = outgoing.missing
484 heads = outgoing.missingheads
484 heads = outgoing.missingheads
485 # We go through the fast path if we get told to, or if all (unfiltered
485 # We go through the fast path if we get told to, or if all (unfiltered
486 # heads have been requested (since we then know there all linkrevs will
486 # heads have been requested (since we then know there all linkrevs will
487 # be pulled by the client).
487 # be pulled by the client).
488 heads.sort()
488 heads.sort()
489 fastpathlinkrev = fastpath or (
489 fastpathlinkrev = fastpath or (
490 repo.filtername is None and heads == sorted(repo.heads()))
490 repo.filtername is None and heads == sorted(repo.heads()))
491
491
492 repo.hook('preoutgoing', throw=True, source=source)
492 repo.hook('preoutgoing', throw=True, source=source)
493 _changegroupinfo(repo, csets, source)
493 _changegroupinfo(repo, csets, source)
494 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
494 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
495
495
496 def getsubset(repo, outgoing, bundler, source, fastpath=False):
496 def getsubset(repo, outgoing, bundler, source, fastpath=False):
497 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
497 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
498 return cg1unpacker(util.chunkbuffer(gengroup), 'UN')
498 return cg1unpacker(util.chunkbuffer(gengroup), 'UN')
499
499
500 def changegroupsubset(repo, roots, heads, source):
500 def changegroupsubset(repo, roots, heads, source):
501 """Compute a changegroup consisting of all the nodes that are
501 """Compute a changegroup consisting of all the nodes that are
502 descendants of any of the roots and ancestors of any of the heads.
502 descendants of any of the roots and ancestors of any of the heads.
503 Return a chunkbuffer object whose read() method will return
503 Return a chunkbuffer object whose read() method will return
504 successive changegroup chunks.
504 successive changegroup chunks.
505
505
506 It is fairly complex as determining which filenodes and which
506 It is fairly complex as determining which filenodes and which
507 manifest nodes need to be included for the changeset to be complete
507 manifest nodes need to be included for the changeset to be complete
508 is non-trivial.
508 is non-trivial.
509
509
510 Another wrinkle is doing the reverse, figuring out which changeset in
510 Another wrinkle is doing the reverse, figuring out which changeset in
511 the changegroup a particular filenode or manifestnode belongs to.
511 the changegroup a particular filenode or manifestnode belongs to.
512 """
512 """
513 cl = repo.changelog
513 cl = repo.changelog
514 if not roots:
514 if not roots:
515 roots = [nullid]
515 roots = [nullid]
516 # TODO: remove call to nodesbetween.
516 # TODO: remove call to nodesbetween.
517 csets, roots, heads = cl.nodesbetween(roots, heads)
517 csets, roots, heads = cl.nodesbetween(roots, heads)
518 discbases = []
518 discbases = []
519 for n in roots:
519 for n in roots:
520 discbases.extend([p for p in cl.parents(n) if p != nullid])
520 discbases.extend([p for p in cl.parents(n) if p != nullid])
521 outgoing = discovery.outgoing(cl, discbases, heads)
521 outgoing = discovery.outgoing(cl, discbases, heads)
522 bundler = cg1packer(repo)
522 bundler = cg1packer(repo)
523 return getsubset(repo, outgoing, bundler, source)
523 return getsubset(repo, outgoing, bundler, source)
524
524
525 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
525 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
526 version='01'):
526 version='01'):
527 """Like getbundle, but taking a discovery.outgoing as an argument.
527 """Like getbundle, but taking a discovery.outgoing as an argument.
528
528
529 This is only implemented for local repos and reuses potentially
529 This is only implemented for local repos and reuses potentially
530 precomputed sets in outgoing. Returns a raw changegroup generator."""
530 precomputed sets in outgoing. Returns a raw changegroup generator."""
531 if not outgoing.missing:
531 if not outgoing.missing:
532 return None
532 return None
533 bundler = packermap[version][0](repo, bundlecaps)
533 bundler = packermap[version][0](repo, bundlecaps)
534 return getsubsetraw(repo, outgoing, bundler, source)
534 return getsubsetraw(repo, outgoing, bundler, source)
535
535
536 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None):
536 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None):
537 """Like getbundle, but taking a discovery.outgoing as an argument.
537 """Like getbundle, but taking a discovery.outgoing as an argument.
538
538
539 This is only implemented for local repos and reuses potentially
539 This is only implemented for local repos and reuses potentially
540 precomputed sets in outgoing."""
540 precomputed sets in outgoing."""
541 if not outgoing.missing:
541 if not outgoing.missing:
542 return None
542 return None
543 bundler = cg1packer(repo, bundlecaps)
543 bundler = cg1packer(repo, bundlecaps)
544 return getsubset(repo, outgoing, bundler, source)
544 return getsubset(repo, outgoing, bundler, source)
545
545
546 def _computeoutgoing(repo, heads, common):
546 def _computeoutgoing(repo, heads, common):
547 """Computes which revs are outgoing given a set of common
547 """Computes which revs are outgoing given a set of common
548 and a set of heads.
548 and a set of heads.
549
549
550 This is a separate function so extensions can have access to
550 This is a separate function so extensions can have access to
551 the logic.
551 the logic.
552
552
553 Returns a discovery.outgoing object.
553 Returns a discovery.outgoing object.
554 """
554 """
555 cl = repo.changelog
555 cl = repo.changelog
556 if common:
556 if common:
557 hasnode = cl.hasnode
557 hasnode = cl.hasnode
558 common = [n for n in common if hasnode(n)]
558 common = [n for n in common if hasnode(n)]
559 else:
559 else:
560 common = [nullid]
560 common = [nullid]
561 if not heads:
561 if not heads:
562 heads = cl.heads()
562 heads = cl.heads()
563 return discovery.outgoing(cl, common, heads)
563 return discovery.outgoing(cl, common, heads)
564
564
565 def getchangegroupraw(repo, source, heads=None, common=None, bundlecaps=None,
565 def getchangegroupraw(repo, source, heads=None, common=None, bundlecaps=None,
566 version='01'):
566 version='01'):
567 """Like changegroupsubset, but returns the set difference between the
567 """Like changegroupsubset, but returns the set difference between the
568 ancestors of heads and the ancestors common.
568 ancestors of heads and the ancestors common.
569
569
570 If heads is None, use the local heads. If common is None, use [nullid].
570 If heads is None, use the local heads. If common is None, use [nullid].
571
571
572 If version is None, use a version '1' changegroup.
572 If version is None, use a version '1' changegroup.
573
573
574 The nodes in common might not all be known locally due to the way the
574 The nodes in common might not all be known locally due to the way the
575 current discovery protocol works. Returns a raw changegroup generator.
575 current discovery protocol works. Returns a raw changegroup generator.
576 """
576 """
577 outgoing = _computeoutgoing(repo, heads, common)
577 outgoing = _computeoutgoing(repo, heads, common)
578 return getlocalchangegroupraw(repo, source, outgoing, bundlecaps=bundlecaps,
578 return getlocalchangegroupraw(repo, source, outgoing, bundlecaps=bundlecaps,
579 version=version)
579 version=version)
580
580
581 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None):
581 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None):
582 """Like changegroupsubset, but returns the set difference between the
582 """Like changegroupsubset, but returns the set difference between the
583 ancestors of heads and the ancestors common.
583 ancestors of heads and the ancestors common.
584
584
585 If heads is None, use the local heads. If common is None, use [nullid].
585 If heads is None, use the local heads. If common is None, use [nullid].
586
586
587 The nodes in common might not all be known locally due to the way the
587 The nodes in common might not all be known locally due to the way the
588 current discovery protocol works.
588 current discovery protocol works.
589 """
589 """
590 outgoing = _computeoutgoing(repo, heads, common)
590 outgoing = _computeoutgoing(repo, heads, common)
591 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps)
591 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps)
592
592
593 def changegroup(repo, basenodes, source):
593 def changegroup(repo, basenodes, source):
594 # to avoid a race we use changegroupsubset() (issue1320)
594 # to avoid a race we use changegroupsubset() (issue1320)
595 return changegroupsubset(repo, basenodes, repo.heads(), source)
595 return changegroupsubset(repo, basenodes, repo.heads(), source)
596
596
597 def addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
597 def addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
598 revisions = 0
598 revisions = 0
599 files = 0
599 files = 0
600 while True:
600 while True:
601 chunkdata = source.filelogheader()
601 chunkdata = source.filelogheader()
602 if not chunkdata:
602 if not chunkdata:
603 break
603 break
604 f = chunkdata["filename"]
604 f = chunkdata["filename"]
605 repo.ui.debug("adding %s revisions\n" % f)
605 repo.ui.debug("adding %s revisions\n" % f)
606 pr()
606 pr()
607 fl = repo.file(f)
607 fl = repo.file(f)
608 o = len(fl)
608 o = len(fl)
609 if not fl.addgroup(source, revmap, trp):
609 if not fl.addgroup(source, revmap, trp):
610 raise util.Abort(_("received file revlog group is empty"))
610 raise util.Abort(_("received file revlog group is empty"))
611 revisions += len(fl) - o
611 revisions += len(fl) - o
612 files += 1
612 files += 1
613 if f in needfiles:
613 if f in needfiles:
614 needs = needfiles[f]
614 needs = needfiles[f]
615 for new in xrange(o, len(fl)):
615 for new in xrange(o, len(fl)):
616 n = fl.node(new)
616 n = fl.node(new)
617 if n in needs:
617 if n in needs:
618 needs.remove(n)
618 needs.remove(n)
619 else:
619 else:
620 raise util.Abort(
620 raise util.Abort(
621 _("received spurious file revlog entry"))
621 _("received spurious file revlog entry"))
622 if not needs:
622 if not needs:
623 del needfiles[f]
623 del needfiles[f]
624 repo.ui.progress(_('files'), None)
624 repo.ui.progress(_('files'), None)
625
625
626 for f, needs in needfiles.iteritems():
626 for f, needs in needfiles.iteritems():
627 fl = repo.file(f)
627 fl = repo.file(f)
628 for n in needs:
628 for n in needs:
629 try:
629 try:
630 fl.rev(n)
630 fl.rev(n)
631 except error.LookupError:
631 except error.LookupError:
632 raise util.Abort(
632 raise util.Abort(
633 _('missing file data for %s:%s - run hg verify') %
633 _('missing file data for %s:%s - run hg verify') %
634 (f, hex(n)))
634 (f, hex(n)))
635
635
636 return revisions, files
636 return revisions, files
637
637
638 def addchangegroup(repo, source, srctype, url, emptyok=False,
638 def addchangegroup(repo, source, srctype, url, emptyok=False,
639 targetphase=phases.draft):
639 targetphase=phases.draft):
640 """Add the changegroup returned by source.read() to this repo.
640 """Add the changegroup returned by source.read() to this repo.
641 srctype is a string like 'push', 'pull', or 'unbundle'. url is
641 srctype is a string like 'push', 'pull', or 'unbundle'. url is
642 the URL of the repo where this changegroup is coming from.
642 the URL of the repo where this changegroup is coming from.
643
643
644 Return an integer summarizing the change to this repo:
644 Return an integer summarizing the change to this repo:
645 - nothing changed or no source: 0
645 - nothing changed or no source: 0
646 - more heads than before: 1+added heads (2..n)
646 - more heads than before: 1+added heads (2..n)
647 - fewer heads than before: -1-removed heads (-2..-n)
647 - fewer heads than before: -1-removed heads (-2..-n)
648 - number of heads stays the same: 1
648 - number of heads stays the same: 1
649 """
649 """
650 repo = repo.unfiltered()
650 repo = repo.unfiltered()
651 def csmap(x):
651 def csmap(x):
652 repo.ui.debug("add changeset %s\n" % short(x))
652 repo.ui.debug("add changeset %s\n" % short(x))
653 return len(cl)
653 return len(cl)
654
654
655 def revmap(x):
655 def revmap(x):
656 return cl.rev(x)
656 return cl.rev(x)
657
657
658 if not source:
658 if not source:
659 return 0
659 return 0
660
660
661 changesets = files = revisions = 0
661 changesets = files = revisions = 0
662 efiles = set()
662 efiles = set()
663
663
664 # write changelog data to temp files so concurrent readers will not see
665 # inconsistent view
666 cl = repo.changelog
667 cl.delayupdate()
668 oldheads = cl.heads()
669
670 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
664 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
671 # The transaction could have been created before and already carries source
665 # The transaction could have been created before and already carries source
672 # information. In this case we use the top level data. We overwrite the
666 # information. In this case we use the top level data. We overwrite the
673 # argument because we need to use the top level value (if they exist) in
667 # argument because we need to use the top level value (if they exist) in
674 # this function.
668 # this function.
675 srctype = tr.hookargs.setdefault('source', srctype)
669 srctype = tr.hookargs.setdefault('source', srctype)
676 url = tr.hookargs.setdefault('url', url)
670 url = tr.hookargs.setdefault('url', url)
671
672 # write changelog data to temp files so concurrent readers will not see
673 # inconsistent view
674 cl = repo.changelog
675 cl.delayupdate(tr)
676 oldheads = cl.heads()
677 try:
677 try:
678 repo.hook('prechangegroup', throw=True, **tr.hookargs)
678 repo.hook('prechangegroup', throw=True, **tr.hookargs)
679
679
680 trp = weakref.proxy(tr)
680 trp = weakref.proxy(tr)
681 # pull off the changeset group
681 # pull off the changeset group
682 repo.ui.status(_("adding changesets\n"))
682 repo.ui.status(_("adding changesets\n"))
683 clstart = len(cl)
683 clstart = len(cl)
684 class prog(object):
684 class prog(object):
685 step = _('changesets')
685 step = _('changesets')
686 count = 1
686 count = 1
687 ui = repo.ui
687 ui = repo.ui
688 total = None
688 total = None
689 def __call__(repo):
689 def __call__(repo):
690 repo.ui.progress(repo.step, repo.count, unit=_('chunks'),
690 repo.ui.progress(repo.step, repo.count, unit=_('chunks'),
691 total=repo.total)
691 total=repo.total)
692 repo.count += 1
692 repo.count += 1
693 pr = prog()
693 pr = prog()
694 source.callback = pr
694 source.callback = pr
695
695
696 source.changelogheader()
696 source.changelogheader()
697 srccontent = cl.addgroup(source, csmap, trp)
697 srccontent = cl.addgroup(source, csmap, trp)
698 if not (srccontent or emptyok):
698 if not (srccontent or emptyok):
699 raise util.Abort(_("received changelog group is empty"))
699 raise util.Abort(_("received changelog group is empty"))
700 clend = len(cl)
700 clend = len(cl)
701 changesets = clend - clstart
701 changesets = clend - clstart
702 for c in xrange(clstart, clend):
702 for c in xrange(clstart, clend):
703 efiles.update(repo[c].files())
703 efiles.update(repo[c].files())
704 efiles = len(efiles)
704 efiles = len(efiles)
705 repo.ui.progress(_('changesets'), None)
705 repo.ui.progress(_('changesets'), None)
706
706
707 # pull off the manifest group
707 # pull off the manifest group
708 repo.ui.status(_("adding manifests\n"))
708 repo.ui.status(_("adding manifests\n"))
709 pr.step = _('manifests')
709 pr.step = _('manifests')
710 pr.count = 1
710 pr.count = 1
711 pr.total = changesets # manifests <= changesets
711 pr.total = changesets # manifests <= changesets
712 # no need to check for empty manifest group here:
712 # no need to check for empty manifest group here:
713 # if the result of the merge of 1 and 2 is the same in 3 and 4,
713 # if the result of the merge of 1 and 2 is the same in 3 and 4,
714 # no new manifest will be created and the manifest group will
714 # no new manifest will be created and the manifest group will
715 # be empty during the pull
715 # be empty during the pull
716 source.manifestheader()
716 source.manifestheader()
717 repo.manifest.addgroup(source, revmap, trp)
717 repo.manifest.addgroup(source, revmap, trp)
718 repo.ui.progress(_('manifests'), None)
718 repo.ui.progress(_('manifests'), None)
719
719
720 needfiles = {}
720 needfiles = {}
721 if repo.ui.configbool('server', 'validate', default=False):
721 if repo.ui.configbool('server', 'validate', default=False):
722 # validate incoming csets have their manifests
722 # validate incoming csets have their manifests
723 for cset in xrange(clstart, clend):
723 for cset in xrange(clstart, clend):
724 mfest = repo.changelog.read(repo.changelog.node(cset))[0]
724 mfest = repo.changelog.read(repo.changelog.node(cset))[0]
725 mfest = repo.manifest.readdelta(mfest)
725 mfest = repo.manifest.readdelta(mfest)
726 # store file nodes we must see
726 # store file nodes we must see
727 for f, n in mfest.iteritems():
727 for f, n in mfest.iteritems():
728 needfiles.setdefault(f, set()).add(n)
728 needfiles.setdefault(f, set()).add(n)
729
729
730 # process the files
730 # process the files
731 repo.ui.status(_("adding file changes\n"))
731 repo.ui.status(_("adding file changes\n"))
732 pr.step = _('files')
732 pr.step = _('files')
733 pr.count = 1
733 pr.count = 1
734 pr.total = efiles
734 pr.total = efiles
735 source.callback = None
735 source.callback = None
736
736
737 newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr,
737 newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr,
738 needfiles)
738 needfiles)
739 revisions += newrevs
739 revisions += newrevs
740 files += newfiles
740 files += newfiles
741
741
742 dh = 0
742 dh = 0
743 if oldheads:
743 if oldheads:
744 heads = cl.heads()
744 heads = cl.heads()
745 dh = len(heads) - len(oldheads)
745 dh = len(heads) - len(oldheads)
746 for h in heads:
746 for h in heads:
747 if h not in oldheads and repo[h].closesbranch():
747 if h not in oldheads and repo[h].closesbranch():
748 dh -= 1
748 dh -= 1
749 htext = ""
749 htext = ""
750 if dh:
750 if dh:
751 htext = _(" (%+d heads)") % dh
751 htext = _(" (%+d heads)") % dh
752
752
753 repo.ui.status(_("added %d changesets"
753 repo.ui.status(_("added %d changesets"
754 " with %d changes to %d files%s\n")
754 " with %d changes to %d files%s\n")
755 % (changesets, revisions, files, htext))
755 % (changesets, revisions, files, htext))
756 repo.invalidatevolatilesets()
756 repo.invalidatevolatilesets()
757
757
758 if changesets > 0:
758 if changesets > 0:
759 p = lambda: cl.writepending() and repo.root or ""
759 p = lambda: tr.writepending() and repo.root or ""
760 if 'node' not in tr.hookargs:
760 if 'node' not in tr.hookargs:
761 tr.hookargs['node'] = hex(cl.node(clstart))
761 tr.hookargs['node'] = hex(cl.node(clstart))
762 hookargs = dict(tr.hookargs)
762 hookargs = dict(tr.hookargs)
763 else:
763 else:
764 hookargs = dict(tr.hookargs)
764 hookargs = dict(tr.hookargs)
765 hookargs['node'] = hex(cl.node(clstart))
765 hookargs['node'] = hex(cl.node(clstart))
766 repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs)
766 repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs)
767
767
768 added = [cl.node(r) for r in xrange(clstart, clend)]
768 added = [cl.node(r) for r in xrange(clstart, clend)]
769 publishing = repo.ui.configbool('phases', 'publish', True)
769 publishing = repo.ui.configbool('phases', 'publish', True)
770 if srctype in ('push', 'serve'):
770 if srctype in ('push', 'serve'):
771 # Old servers can not push the boundary themselves.
771 # Old servers can not push the boundary themselves.
772 # New servers won't push the boundary if changeset already
772 # New servers won't push the boundary if changeset already
773 # exists locally as secret
773 # exists locally as secret
774 #
774 #
775 # We should not use added here but the list of all change in
775 # We should not use added here but the list of all change in
776 # the bundle
776 # the bundle
777 if publishing:
777 if publishing:
778 phases.advanceboundary(repo, tr, phases.public, srccontent)
778 phases.advanceboundary(repo, tr, phases.public, srccontent)
779 else:
779 else:
780 # Those changesets have been pushed from the outside, their
780 # Those changesets have been pushed from the outside, their
781 # phases are going to be pushed alongside. Therefor
781 # phases are going to be pushed alongside. Therefor
782 # `targetphase` is ignored.
782 # `targetphase` is ignored.
783 phases.advanceboundary(repo, tr, phases.draft, srccontent)
783 phases.advanceboundary(repo, tr, phases.draft, srccontent)
784 phases.retractboundary(repo, tr, phases.draft, added)
784 phases.retractboundary(repo, tr, phases.draft, added)
785 elif srctype != 'strip':
785 elif srctype != 'strip':
786 # publishing only alter behavior during push
786 # publishing only alter behavior during push
787 #
787 #
788 # strip should not touch boundary at all
788 # strip should not touch boundary at all
789 phases.retractboundary(repo, tr, targetphase, added)
789 phases.retractboundary(repo, tr, targetphase, added)
790
790
791 # make changelog see real files again
791 # make changelog see real files again
792 cl.finalize(trp)
792 cl.finalize(trp)
793
793
794 tr.close()
794 tr.close()
795
795
796 if changesets > 0:
796 if changesets > 0:
797 if srctype != 'strip':
797 if srctype != 'strip':
798 # During strip, branchcache is invalid but coming call to
798 # During strip, branchcache is invalid but coming call to
799 # `destroyed` will repair it.
799 # `destroyed` will repair it.
800 # In other case we can safely update cache on disk.
800 # In other case we can safely update cache on disk.
801 branchmap.updatecache(repo.filtered('served'))
801 branchmap.updatecache(repo.filtered('served'))
802
802
803 def runhooks():
803 def runhooks():
804 # These hooks run when the lock releases, not when the
804 # These hooks run when the lock releases, not when the
805 # transaction closes. So it's possible for the changelog
805 # transaction closes. So it's possible for the changelog
806 # to have changed since we last saw it.
806 # to have changed since we last saw it.
807 if clstart >= len(repo):
807 if clstart >= len(repo):
808 return
808 return
809
809
810 # forcefully update the on-disk branch cache
810 # forcefully update the on-disk branch cache
811 repo.ui.debug("updating the branch cache\n")
811 repo.ui.debug("updating the branch cache\n")
812 repo.hook("changegroup", **hookargs)
812 repo.hook("changegroup", **hookargs)
813
813
814 for n in added:
814 for n in added:
815 args = hookargs.copy()
815 args = hookargs.copy()
816 args['node'] = hex(n)
816 args['node'] = hex(n)
817 repo.hook("incoming", **args)
817 repo.hook("incoming", **args)
818
818
819 newheads = [h for h in repo.heads() if h not in oldheads]
819 newheads = [h for h in repo.heads() if h not in oldheads]
820 repo.ui.log("incoming",
820 repo.ui.log("incoming",
821 "%s incoming changes - new heads: %s\n",
821 "%s incoming changes - new heads: %s\n",
822 len(added),
822 len(added),
823 ', '.join([hex(c[:6]) for c in newheads]))
823 ', '.join([hex(c[:6]) for c in newheads]))
824 repo._afterlock(runhooks)
824 repo._afterlock(runhooks)
825
825
826 finally:
826 finally:
827 tr.release()
827 tr.release()
828 # never return 0 here:
828 # never return 0 here:
829 if dh < 0:
829 if dh < 0:
830 return dh - 1
830 return dh - 1
831 else:
831 else:
832 return dh + 1
832 return dh + 1
@@ -1,374 +1,375 b''
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog 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 node import bin, hex, nullid
8 from node import bin, hex, nullid
9 from i18n import _
9 from i18n import _
10 import util, error, revlog, encoding
10 import util, error, revlog, encoding
11
11
12 _defaultextra = {'branch': 'default'}
12 _defaultextra = {'branch': 'default'}
13
13
14 def _string_escape(text):
14 def _string_escape(text):
15 """
15 """
16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
18 >>> s
18 >>> s
19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
20 >>> res = _string_escape(s)
20 >>> res = _string_escape(s)
21 >>> s == res.decode('string_escape')
21 >>> s == res.decode('string_escape')
22 True
22 True
23 """
23 """
24 # subset of the string_escape codec
24 # subset of the string_escape codec
25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
26 return text.replace('\0', '\\0')
26 return text.replace('\0', '\\0')
27
27
28 def decodeextra(text):
28 def decodeextra(text):
29 """
29 """
30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
31 ... ).iteritems())
31 ... ).iteritems())
32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
34 ... 'baz': chr(92) + chr(0) + '2'})
34 ... 'baz': chr(92) + chr(0) + '2'})
35 ... ).iteritems())
35 ... ).iteritems())
36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
37 """
37 """
38 extra = _defaultextra.copy()
38 extra = _defaultextra.copy()
39 for l in text.split('\0'):
39 for l in text.split('\0'):
40 if l:
40 if l:
41 if '\\0' in l:
41 if '\\0' in l:
42 # fix up \0 without getting into trouble with \\0
42 # fix up \0 without getting into trouble with \\0
43 l = l.replace('\\\\', '\\\\\n')
43 l = l.replace('\\\\', '\\\\\n')
44 l = l.replace('\\0', '\0')
44 l = l.replace('\\0', '\0')
45 l = l.replace('\n', '')
45 l = l.replace('\n', '')
46 k, v = l.decode('string_escape').split(':', 1)
46 k, v = l.decode('string_escape').split(':', 1)
47 extra[k] = v
47 extra[k] = v
48 return extra
48 return extra
49
49
50 def encodeextra(d):
50 def encodeextra(d):
51 # keys must be sorted to produce a deterministic changelog entry
51 # keys must be sorted to produce a deterministic changelog entry
52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
53 return "\0".join(items)
53 return "\0".join(items)
54
54
55 def stripdesc(desc):
55 def stripdesc(desc):
56 """strip trailing whitespace and leading and trailing empty lines"""
56 """strip trailing whitespace and leading and trailing empty lines"""
57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
58
58
59 class appender(object):
59 class appender(object):
60 '''the changelog index must be updated last on disk, so we use this class
60 '''the changelog index must be updated last on disk, so we use this class
61 to delay writes to it'''
61 to delay writes to it'''
62 def __init__(self, vfs, name, mode, buf):
62 def __init__(self, vfs, name, mode, buf):
63 self.data = buf
63 self.data = buf
64 fp = vfs(name, mode)
64 fp = vfs(name, mode)
65 self.fp = fp
65 self.fp = fp
66 self.offset = fp.tell()
66 self.offset = fp.tell()
67 self.size = vfs.fstat(fp).st_size
67 self.size = vfs.fstat(fp).st_size
68
68
69 def end(self):
69 def end(self):
70 return self.size + len("".join(self.data))
70 return self.size + len("".join(self.data))
71 def tell(self):
71 def tell(self):
72 return self.offset
72 return self.offset
73 def flush(self):
73 def flush(self):
74 pass
74 pass
75 def close(self):
75 def close(self):
76 self.fp.close()
76 self.fp.close()
77
77
78 def seek(self, offset, whence=0):
78 def seek(self, offset, whence=0):
79 '''virtual file offset spans real file and data'''
79 '''virtual file offset spans real file and data'''
80 if whence == 0:
80 if whence == 0:
81 self.offset = offset
81 self.offset = offset
82 elif whence == 1:
82 elif whence == 1:
83 self.offset += offset
83 self.offset += offset
84 elif whence == 2:
84 elif whence == 2:
85 self.offset = self.end() + offset
85 self.offset = self.end() + offset
86 if self.offset < self.size:
86 if self.offset < self.size:
87 self.fp.seek(self.offset)
87 self.fp.seek(self.offset)
88
88
89 def read(self, count=-1):
89 def read(self, count=-1):
90 '''only trick here is reads that span real file and data'''
90 '''only trick here is reads that span real file and data'''
91 ret = ""
91 ret = ""
92 if self.offset < self.size:
92 if self.offset < self.size:
93 s = self.fp.read(count)
93 s = self.fp.read(count)
94 ret = s
94 ret = s
95 self.offset += len(s)
95 self.offset += len(s)
96 if count > 0:
96 if count > 0:
97 count -= len(s)
97 count -= len(s)
98 if count != 0:
98 if count != 0:
99 doff = self.offset - self.size
99 doff = self.offset - self.size
100 self.data.insert(0, "".join(self.data))
100 self.data.insert(0, "".join(self.data))
101 del self.data[1:]
101 del self.data[1:]
102 s = self.data[0][doff:doff + count]
102 s = self.data[0][doff:doff + count]
103 self.offset += len(s)
103 self.offset += len(s)
104 ret += s
104 ret += s
105 return ret
105 return ret
106
106
107 def write(self, s):
107 def write(self, s):
108 self.data.append(str(s))
108 self.data.append(str(s))
109 self.offset += len(s)
109 self.offset += len(s)
110
110
111 def _divertopener(opener, target):
111 def _divertopener(opener, target):
112 """build an opener that writes in 'target.a' instead of 'target'"""
112 """build an opener that writes in 'target.a' instead of 'target'"""
113 def _divert(name, mode='r'):
113 def _divert(name, mode='r'):
114 if name != target:
114 if name != target:
115 return opener(name, mode)
115 return opener(name, mode)
116 return opener(name + ".a", mode)
116 return opener(name + ".a", mode)
117 return _divert
117 return _divert
118
118
119 def _delayopener(opener, target, buf):
119 def _delayopener(opener, target, buf):
120 """build an opener that stores chunks in 'buf' instead of 'target'"""
120 """build an opener that stores chunks in 'buf' instead of 'target'"""
121 def _delay(name, mode='r'):
121 def _delay(name, mode='r'):
122 if name != target:
122 if name != target:
123 return opener(name, mode)
123 return opener(name, mode)
124 return appender(opener, name, mode, buf)
124 return appender(opener, name, mode, buf)
125 return _delay
125 return _delay
126
126
127 class changelog(revlog.revlog):
127 class changelog(revlog.revlog):
128 def __init__(self, opener):
128 def __init__(self, opener):
129 revlog.revlog.__init__(self, opener, "00changelog.i")
129 revlog.revlog.__init__(self, opener, "00changelog.i")
130 if self._initempty:
130 if self._initempty:
131 # changelogs don't benefit from generaldelta
131 # changelogs don't benefit from generaldelta
132 self.version &= ~revlog.REVLOGGENERALDELTA
132 self.version &= ~revlog.REVLOGGENERALDELTA
133 self._generaldelta = False
133 self._generaldelta = False
134 self._realopener = opener
134 self._realopener = opener
135 self._delayed = False
135 self._delayed = False
136 self._delaybuf = None
136 self._delaybuf = None
137 self._divert = False
137 self._divert = False
138 self.filteredrevs = frozenset()
138 self.filteredrevs = frozenset()
139
139
140 def tip(self):
140 def tip(self):
141 """filtered version of revlog.tip"""
141 """filtered version of revlog.tip"""
142 for i in xrange(len(self) -1, -2, -1):
142 for i in xrange(len(self) -1, -2, -1):
143 if i not in self.filteredrevs:
143 if i not in self.filteredrevs:
144 return self.node(i)
144 return self.node(i)
145
145
146 def __iter__(self):
146 def __iter__(self):
147 """filtered version of revlog.__iter__"""
147 """filtered version of revlog.__iter__"""
148 if len(self.filteredrevs) == 0:
148 if len(self.filteredrevs) == 0:
149 return revlog.revlog.__iter__(self)
149 return revlog.revlog.__iter__(self)
150
150
151 def filterediter():
151 def filterediter():
152 for i in xrange(len(self)):
152 for i in xrange(len(self)):
153 if i not in self.filteredrevs:
153 if i not in self.filteredrevs:
154 yield i
154 yield i
155
155
156 return filterediter()
156 return filterediter()
157
157
158 def revs(self, start=0, stop=None):
158 def revs(self, start=0, stop=None):
159 """filtered version of revlog.revs"""
159 """filtered version of revlog.revs"""
160 for i in super(changelog, self).revs(start, stop):
160 for i in super(changelog, self).revs(start, stop):
161 if i not in self.filteredrevs:
161 if i not in self.filteredrevs:
162 yield i
162 yield i
163
163
164 @util.propertycache
164 @util.propertycache
165 def nodemap(self):
165 def nodemap(self):
166 # XXX need filtering too
166 # XXX need filtering too
167 self.rev(self.node(0))
167 self.rev(self.node(0))
168 return self._nodecache
168 return self._nodecache
169
169
170 def hasnode(self, node):
170 def hasnode(self, node):
171 """filtered version of revlog.hasnode"""
171 """filtered version of revlog.hasnode"""
172 try:
172 try:
173 i = self.rev(node)
173 i = self.rev(node)
174 return i not in self.filteredrevs
174 return i not in self.filteredrevs
175 except KeyError:
175 except KeyError:
176 return False
176 return False
177
177
178 def headrevs(self):
178 def headrevs(self):
179 if self.filteredrevs:
179 if self.filteredrevs:
180 try:
180 try:
181 return self.index.headrevsfiltered(self.filteredrevs)
181 return self.index.headrevsfiltered(self.filteredrevs)
182 # AttributeError covers non-c-extension environments and
182 # AttributeError covers non-c-extension environments and
183 # old c extensions without filter handling.
183 # old c extensions without filter handling.
184 except AttributeError:
184 except AttributeError:
185 return self._headrevs()
185 return self._headrevs()
186
186
187 return super(changelog, self).headrevs()
187 return super(changelog, self).headrevs()
188
188
189 def strip(self, *args, **kwargs):
189 def strip(self, *args, **kwargs):
190 # XXX make something better than assert
190 # XXX make something better than assert
191 # We can't expect proper strip behavior if we are filtered.
191 # We can't expect proper strip behavior if we are filtered.
192 assert not self.filteredrevs
192 assert not self.filteredrevs
193 super(changelog, self).strip(*args, **kwargs)
193 super(changelog, self).strip(*args, **kwargs)
194
194
195 def rev(self, node):
195 def rev(self, node):
196 """filtered version of revlog.rev"""
196 """filtered version of revlog.rev"""
197 r = super(changelog, self).rev(node)
197 r = super(changelog, self).rev(node)
198 if r in self.filteredrevs:
198 if r in self.filteredrevs:
199 raise error.FilteredLookupError(hex(node), self.indexfile,
199 raise error.FilteredLookupError(hex(node), self.indexfile,
200 _('filtered node'))
200 _('filtered node'))
201 return r
201 return r
202
202
203 def node(self, rev):
203 def node(self, rev):
204 """filtered version of revlog.node"""
204 """filtered version of revlog.node"""
205 if rev in self.filteredrevs:
205 if rev in self.filteredrevs:
206 raise error.FilteredIndexError(rev)
206 raise error.FilteredIndexError(rev)
207 return super(changelog, self).node(rev)
207 return super(changelog, self).node(rev)
208
208
209 def linkrev(self, rev):
209 def linkrev(self, rev):
210 """filtered version of revlog.linkrev"""
210 """filtered version of revlog.linkrev"""
211 if rev in self.filteredrevs:
211 if rev in self.filteredrevs:
212 raise error.FilteredIndexError(rev)
212 raise error.FilteredIndexError(rev)
213 return super(changelog, self).linkrev(rev)
213 return super(changelog, self).linkrev(rev)
214
214
215 def parentrevs(self, rev):
215 def parentrevs(self, rev):
216 """filtered version of revlog.parentrevs"""
216 """filtered version of revlog.parentrevs"""
217 if rev in self.filteredrevs:
217 if rev in self.filteredrevs:
218 raise error.FilteredIndexError(rev)
218 raise error.FilteredIndexError(rev)
219 return super(changelog, self).parentrevs(rev)
219 return super(changelog, self).parentrevs(rev)
220
220
221 def flags(self, rev):
221 def flags(self, rev):
222 """filtered version of revlog.flags"""
222 """filtered version of revlog.flags"""
223 if rev in self.filteredrevs:
223 if rev in self.filteredrevs:
224 raise error.FilteredIndexError(rev)
224 raise error.FilteredIndexError(rev)
225 return super(changelog, self).flags(rev)
225 return super(changelog, self).flags(rev)
226
226
227 def delayupdate(self):
227 def delayupdate(self, tr):
228 "delay visibility of index updates to other readers"
228 "delay visibility of index updates to other readers"
229
229
230 if not self._delayed:
230 if not self._delayed:
231 if len(self) == 0:
231 if len(self) == 0:
232 self._divert = True
232 self._divert = True
233 if self._realopener.exists(self.indexfile + '.a'):
233 if self._realopener.exists(self.indexfile + '.a'):
234 self._realopener.unlink(self.indexfile + '.a')
234 self._realopener.unlink(self.indexfile + '.a')
235 self.opener = _divertopener(self._realopener, self.indexfile)
235 self.opener = _divertopener(self._realopener, self.indexfile)
236 else:
236 else:
237 self._delaybuf = []
237 self._delaybuf = []
238 self.opener = _delayopener(self._realopener, self.indexfile,
238 self.opener = _delayopener(self._realopener, self.indexfile,
239 self._delaybuf)
239 self._delaybuf)
240 self._delayed = True
240 self._delayed = True
241 tr.addpending('cl-%i' % id(self), self._writepending)
241
242
242 def finalize(self, tr):
243 def finalize(self, tr):
243 "finalize index updates"
244 "finalize index updates"
244 self._delayed = False
245 self._delayed = False
245 self.opener = self._realopener
246 self.opener = self._realopener
246 # move redirected index data back into place
247 # move redirected index data back into place
247 if self._divert:
248 if self._divert:
248 assert not self._delaybuf
249 assert not self._delaybuf
249 tmpname = self.indexfile + ".a"
250 tmpname = self.indexfile + ".a"
250 nfile = self.opener.open(tmpname)
251 nfile = self.opener.open(tmpname)
251 nfile.close()
252 nfile.close()
252 self.opener.rename(tmpname, self.indexfile)
253 self.opener.rename(tmpname, self.indexfile)
253 elif self._delaybuf:
254 elif self._delaybuf:
254 fp = self.opener(self.indexfile, 'a')
255 fp = self.opener(self.indexfile, 'a')
255 fp.write("".join(self._delaybuf))
256 fp.write("".join(self._delaybuf))
256 fp.close()
257 fp.close()
257 self._delaybuf = None
258 self._delaybuf = None
258 self._divert = False
259 self._divert = False
259 # split when we're done
260 # split when we're done
260 self.checkinlinesize(tr)
261 self.checkinlinesize(tr)
261
262
262 def readpending(self, file):
263 def readpending(self, file):
263 r = revlog.revlog(self.opener, file)
264 r = revlog.revlog(self.opener, file)
264 self.index = r.index
265 self.index = r.index
265 self.nodemap = r.nodemap
266 self.nodemap = r.nodemap
266 self._nodecache = r._nodecache
267 self._nodecache = r._nodecache
267 self._chunkcache = r._chunkcache
268 self._chunkcache = r._chunkcache
268
269
269 def writepending(self):
270 def _writepending(self):
270 "create a file containing the unfinalized state for pretxnchangegroup"
271 "create a file containing the unfinalized state for pretxnchangegroup"
271 if self._delaybuf:
272 if self._delaybuf:
272 # make a temporary copy of the index
273 # make a temporary copy of the index
273 fp1 = self._realopener(self.indexfile)
274 fp1 = self._realopener(self.indexfile)
274 fp2 = self._realopener(self.indexfile + ".a", "w")
275 fp2 = self._realopener(self.indexfile + ".a", "w")
275 fp2.write(fp1.read())
276 fp2.write(fp1.read())
276 # add pending data
277 # add pending data
277 fp2.write("".join(self._delaybuf))
278 fp2.write("".join(self._delaybuf))
278 fp2.close()
279 fp2.close()
279 # switch modes so finalize can simply rename
280 # switch modes so finalize can simply rename
280 self._delaybuf = None
281 self._delaybuf = None
281 self._divert = True
282 self._divert = True
282 self.opener = _divertopener(self._realopener, self.indexfile)
283 self.opener = _divertopener(self._realopener, self.indexfile)
283
284
284 if self._divert:
285 if self._divert:
285 return True
286 return True
286
287
287 return False
288 return False
288
289
289 def checkinlinesize(self, tr, fp=None):
290 def checkinlinesize(self, tr, fp=None):
290 if not self._delayed:
291 if not self._delayed:
291 revlog.revlog.checkinlinesize(self, tr, fp)
292 revlog.revlog.checkinlinesize(self, tr, fp)
292
293
293 def read(self, node):
294 def read(self, node):
294 """
295 """
295 format used:
296 format used:
296 nodeid\n : manifest node in ascii
297 nodeid\n : manifest node in ascii
297 user\n : user, no \n or \r allowed
298 user\n : user, no \n or \r allowed
298 time tz extra\n : date (time is int or float, timezone is int)
299 time tz extra\n : date (time is int or float, timezone is int)
299 : extra is metadata, encoded and separated by '\0'
300 : extra is metadata, encoded and separated by '\0'
300 : older versions ignore it
301 : older versions ignore it
301 files\n\n : files modified by the cset, no \n or \r allowed
302 files\n\n : files modified by the cset, no \n or \r allowed
302 (.*) : comment (free text, ideally utf-8)
303 (.*) : comment (free text, ideally utf-8)
303
304
304 changelog v0 doesn't use extra
305 changelog v0 doesn't use extra
305 """
306 """
306 text = self.revision(node)
307 text = self.revision(node)
307 if not text:
308 if not text:
308 return (nullid, "", (0, 0), [], "", _defaultextra)
309 return (nullid, "", (0, 0), [], "", _defaultextra)
309 last = text.index("\n\n")
310 last = text.index("\n\n")
310 desc = encoding.tolocal(text[last + 2:])
311 desc = encoding.tolocal(text[last + 2:])
311 l = text[:last].split('\n')
312 l = text[:last].split('\n')
312 manifest = bin(l[0])
313 manifest = bin(l[0])
313 user = encoding.tolocal(l[1])
314 user = encoding.tolocal(l[1])
314
315
315 tdata = l[2].split(' ', 2)
316 tdata = l[2].split(' ', 2)
316 if len(tdata) != 3:
317 if len(tdata) != 3:
317 time = float(tdata[0])
318 time = float(tdata[0])
318 try:
319 try:
319 # various tools did silly things with the time zone field.
320 # various tools did silly things with the time zone field.
320 timezone = int(tdata[1])
321 timezone = int(tdata[1])
321 except ValueError:
322 except ValueError:
322 timezone = 0
323 timezone = 0
323 extra = _defaultextra
324 extra = _defaultextra
324 else:
325 else:
325 time, timezone = float(tdata[0]), int(tdata[1])
326 time, timezone = float(tdata[0]), int(tdata[1])
326 extra = decodeextra(tdata[2])
327 extra = decodeextra(tdata[2])
327
328
328 files = l[3:]
329 files = l[3:]
329 return (manifest, user, (time, timezone), files, desc, extra)
330 return (manifest, user, (time, timezone), files, desc, extra)
330
331
331 def add(self, manifest, files, desc, transaction, p1, p2,
332 def add(self, manifest, files, desc, transaction, p1, p2,
332 user, date=None, extra=None):
333 user, date=None, extra=None):
333 # Convert to UTF-8 encoded bytestrings as the very first
334 # Convert to UTF-8 encoded bytestrings as the very first
334 # thing: calling any method on a localstr object will turn it
335 # thing: calling any method on a localstr object will turn it
335 # into a str object and the cached UTF-8 string is thus lost.
336 # into a str object and the cached UTF-8 string is thus lost.
336 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
337 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
337
338
338 user = user.strip()
339 user = user.strip()
339 # An empty username or a username with a "\n" will make the
340 # An empty username or a username with a "\n" will make the
340 # revision text contain two "\n\n" sequences -> corrupt
341 # revision text contain two "\n\n" sequences -> corrupt
341 # repository since read cannot unpack the revision.
342 # repository since read cannot unpack the revision.
342 if not user:
343 if not user:
343 raise error.RevlogError(_("empty username"))
344 raise error.RevlogError(_("empty username"))
344 if "\n" in user:
345 if "\n" in user:
345 raise error.RevlogError(_("username %s contains a newline")
346 raise error.RevlogError(_("username %s contains a newline")
346 % repr(user))
347 % repr(user))
347
348
348 desc = stripdesc(desc)
349 desc = stripdesc(desc)
349
350
350 if date:
351 if date:
351 parseddate = "%d %d" % util.parsedate(date)
352 parseddate = "%d %d" % util.parsedate(date)
352 else:
353 else:
353 parseddate = "%d %d" % util.makedate()
354 parseddate = "%d %d" % util.makedate()
354 if extra:
355 if extra:
355 branch = extra.get("branch")
356 branch = extra.get("branch")
356 if branch in ("default", ""):
357 if branch in ("default", ""):
357 del extra["branch"]
358 del extra["branch"]
358 elif branch in (".", "null", "tip"):
359 elif branch in (".", "null", "tip"):
359 raise error.RevlogError(_('the name \'%s\' is reserved')
360 raise error.RevlogError(_('the name \'%s\' is reserved')
360 % branch)
361 % branch)
361 if extra:
362 if extra:
362 extra = encodeextra(extra)
363 extra = encodeextra(extra)
363 parseddate = "%s %s" % (parseddate, extra)
364 parseddate = "%s %s" % (parseddate, extra)
364 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
365 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
365 text = "\n".join(l)
366 text = "\n".join(l)
366 return self.addrevision(text, transaction, len(self), p1, p2)
367 return self.addrevision(text, transaction, len(self), p1, p2)
367
368
368 def branchinfo(self, rev):
369 def branchinfo(self, rev):
369 """return the branch name and open/close state of a revision
370 """return the branch name and open/close state of a revision
370
371
371 This function exists because creating a changectx object
372 This function exists because creating a changectx object
372 just to access this is costly."""
373 just to access this is costly."""
373 extra = self.read(rev)[5]
374 extra = self.read(rev)[5]
374 return encoding.tolocal(extra.get("branch")), 'close' in extra
375 return encoding.tolocal(extra.get("branch")), 'close' in extra
@@ -1,1300 +1,1297 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 i18n import _
8 from i18n import _
9 from node import hex, nullid
9 from node import hex, nullid
10 import errno, urllib
10 import errno, urllib
11 import util, scmutil, changegroup, base85, error
11 import util, scmutil, changegroup, base85, error
12 import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey
12 import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey
13
13
14 def readbundle(ui, fh, fname, vfs=None):
14 def readbundle(ui, fh, fname, vfs=None):
15 header = changegroup.readexactly(fh, 4)
15 header = changegroup.readexactly(fh, 4)
16
16
17 alg = None
17 alg = None
18 if not fname:
18 if not fname:
19 fname = "stream"
19 fname = "stream"
20 if not header.startswith('HG') and header.startswith('\0'):
20 if not header.startswith('HG') and header.startswith('\0'):
21 fh = changegroup.headerlessfixup(fh, header)
21 fh = changegroup.headerlessfixup(fh, header)
22 header = "HG10"
22 header = "HG10"
23 alg = 'UN'
23 alg = 'UN'
24 elif vfs:
24 elif vfs:
25 fname = vfs.join(fname)
25 fname = vfs.join(fname)
26
26
27 magic, version = header[0:2], header[2:4]
27 magic, version = header[0:2], header[2:4]
28
28
29 if magic != 'HG':
29 if magic != 'HG':
30 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
30 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
31 if version == '10':
31 if version == '10':
32 if alg is None:
32 if alg is None:
33 alg = changegroup.readexactly(fh, 2)
33 alg = changegroup.readexactly(fh, 2)
34 return changegroup.cg1unpacker(fh, alg)
34 return changegroup.cg1unpacker(fh, alg)
35 elif version == '2Y':
35 elif version == '2Y':
36 return bundle2.unbundle20(ui, fh, header=magic + version)
36 return bundle2.unbundle20(ui, fh, header=magic + version)
37 else:
37 else:
38 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
38 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
39
39
40 def buildobsmarkerspart(bundler, markers):
40 def buildobsmarkerspart(bundler, markers):
41 """add an obsmarker part to the bundler with <markers>
41 """add an obsmarker part to the bundler with <markers>
42
42
43 No part is created if markers is empty.
43 No part is created if markers is empty.
44 Raises ValueError if the bundler doesn't support any known obsmarker format.
44 Raises ValueError if the bundler doesn't support any known obsmarker format.
45 """
45 """
46 if markers:
46 if markers:
47 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
47 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
48 version = obsolete.commonversion(remoteversions)
48 version = obsolete.commonversion(remoteversions)
49 if version is None:
49 if version is None:
50 raise ValueError('bundler do not support common obsmarker format')
50 raise ValueError('bundler do not support common obsmarker format')
51 stream = obsolete.encodemarkers(markers, True, version=version)
51 stream = obsolete.encodemarkers(markers, True, version=version)
52 return bundler.newpart('B2X:OBSMARKERS', data=stream)
52 return bundler.newpart('B2X:OBSMARKERS', data=stream)
53 return None
53 return None
54
54
55 class pushoperation(object):
55 class pushoperation(object):
56 """A object that represent a single push operation
56 """A object that represent a single push operation
57
57
58 It purpose is to carry push related state and very common operation.
58 It purpose is to carry push related state and very common operation.
59
59
60 A new should be created at the beginning of each push and discarded
60 A new should be created at the beginning of each push and discarded
61 afterward.
61 afterward.
62 """
62 """
63
63
64 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
64 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
65 bookmarks=()):
65 bookmarks=()):
66 # repo we push from
66 # repo we push from
67 self.repo = repo
67 self.repo = repo
68 self.ui = repo.ui
68 self.ui = repo.ui
69 # repo we push to
69 # repo we push to
70 self.remote = remote
70 self.remote = remote
71 # force option provided
71 # force option provided
72 self.force = force
72 self.force = force
73 # revs to be pushed (None is "all")
73 # revs to be pushed (None is "all")
74 self.revs = revs
74 self.revs = revs
75 # bookmark explicitly pushed
75 # bookmark explicitly pushed
76 self.bookmarks = bookmarks
76 self.bookmarks = bookmarks
77 # allow push of new branch
77 # allow push of new branch
78 self.newbranch = newbranch
78 self.newbranch = newbranch
79 # did a local lock get acquired?
79 # did a local lock get acquired?
80 self.locallocked = None
80 self.locallocked = None
81 # step already performed
81 # step already performed
82 # (used to check what steps have been already performed through bundle2)
82 # (used to check what steps have been already performed through bundle2)
83 self.stepsdone = set()
83 self.stepsdone = set()
84 # Integer version of the changegroup push result
84 # Integer version of the changegroup push result
85 # - None means nothing to push
85 # - None means nothing to push
86 # - 0 means HTTP error
86 # - 0 means HTTP error
87 # - 1 means we pushed and remote head count is unchanged *or*
87 # - 1 means we pushed and remote head count is unchanged *or*
88 # we have outgoing changesets but refused to push
88 # we have outgoing changesets but refused to push
89 # - other values as described by addchangegroup()
89 # - other values as described by addchangegroup()
90 self.cgresult = None
90 self.cgresult = None
91 # Boolean value for the bookmark push
91 # Boolean value for the bookmark push
92 self.bkresult = None
92 self.bkresult = None
93 # discover.outgoing object (contains common and outgoing data)
93 # discover.outgoing object (contains common and outgoing data)
94 self.outgoing = None
94 self.outgoing = None
95 # all remote heads before the push
95 # all remote heads before the push
96 self.remoteheads = None
96 self.remoteheads = None
97 # testable as a boolean indicating if any nodes are missing locally.
97 # testable as a boolean indicating if any nodes are missing locally.
98 self.incoming = None
98 self.incoming = None
99 # phases changes that must be pushed along side the changesets
99 # phases changes that must be pushed along side the changesets
100 self.outdatedphases = None
100 self.outdatedphases = None
101 # phases changes that must be pushed if changeset push fails
101 # phases changes that must be pushed if changeset push fails
102 self.fallbackoutdatedphases = None
102 self.fallbackoutdatedphases = None
103 # outgoing obsmarkers
103 # outgoing obsmarkers
104 self.outobsmarkers = set()
104 self.outobsmarkers = set()
105 # outgoing bookmarks
105 # outgoing bookmarks
106 self.outbookmarks = []
106 self.outbookmarks = []
107
107
108 @util.propertycache
108 @util.propertycache
109 def futureheads(self):
109 def futureheads(self):
110 """future remote heads if the changeset push succeeds"""
110 """future remote heads if the changeset push succeeds"""
111 return self.outgoing.missingheads
111 return self.outgoing.missingheads
112
112
113 @util.propertycache
113 @util.propertycache
114 def fallbackheads(self):
114 def fallbackheads(self):
115 """future remote heads if the changeset push fails"""
115 """future remote heads if the changeset push fails"""
116 if self.revs is None:
116 if self.revs is None:
117 # not target to push, all common are relevant
117 # not target to push, all common are relevant
118 return self.outgoing.commonheads
118 return self.outgoing.commonheads
119 unfi = self.repo.unfiltered()
119 unfi = self.repo.unfiltered()
120 # I want cheads = heads(::missingheads and ::commonheads)
120 # I want cheads = heads(::missingheads and ::commonheads)
121 # (missingheads is revs with secret changeset filtered out)
121 # (missingheads is revs with secret changeset filtered out)
122 #
122 #
123 # This can be expressed as:
123 # This can be expressed as:
124 # cheads = ( (missingheads and ::commonheads)
124 # cheads = ( (missingheads and ::commonheads)
125 # + (commonheads and ::missingheads))"
125 # + (commonheads and ::missingheads))"
126 # )
126 # )
127 #
127 #
128 # while trying to push we already computed the following:
128 # while trying to push we already computed the following:
129 # common = (::commonheads)
129 # common = (::commonheads)
130 # missing = ((commonheads::missingheads) - commonheads)
130 # missing = ((commonheads::missingheads) - commonheads)
131 #
131 #
132 # We can pick:
132 # We can pick:
133 # * missingheads part of common (::commonheads)
133 # * missingheads part of common (::commonheads)
134 common = set(self.outgoing.common)
134 common = set(self.outgoing.common)
135 nm = self.repo.changelog.nodemap
135 nm = self.repo.changelog.nodemap
136 cheads = [node for node in self.revs if nm[node] in common]
136 cheads = [node for node in self.revs if nm[node] in common]
137 # and
137 # and
138 # * commonheads parents on missing
138 # * commonheads parents on missing
139 revset = unfi.set('%ln and parents(roots(%ln))',
139 revset = unfi.set('%ln and parents(roots(%ln))',
140 self.outgoing.commonheads,
140 self.outgoing.commonheads,
141 self.outgoing.missing)
141 self.outgoing.missing)
142 cheads.extend(c.node() for c in revset)
142 cheads.extend(c.node() for c in revset)
143 return cheads
143 return cheads
144
144
145 @property
145 @property
146 def commonheads(self):
146 def commonheads(self):
147 """set of all common heads after changeset bundle push"""
147 """set of all common heads after changeset bundle push"""
148 if self.cgresult:
148 if self.cgresult:
149 return self.futureheads
149 return self.futureheads
150 else:
150 else:
151 return self.fallbackheads
151 return self.fallbackheads
152
152
153 # mapping of message used when pushing bookmark
153 # mapping of message used when pushing bookmark
154 bookmsgmap = {'update': (_("updating bookmark %s\n"),
154 bookmsgmap = {'update': (_("updating bookmark %s\n"),
155 _('updating bookmark %s failed!\n')),
155 _('updating bookmark %s failed!\n')),
156 'export': (_("exporting bookmark %s\n"),
156 'export': (_("exporting bookmark %s\n"),
157 _('exporting bookmark %s failed!\n')),
157 _('exporting bookmark %s failed!\n')),
158 'delete': (_("deleting remote bookmark %s\n"),
158 'delete': (_("deleting remote bookmark %s\n"),
159 _('deleting remote bookmark %s failed!\n')),
159 _('deleting remote bookmark %s failed!\n')),
160 }
160 }
161
161
162
162
163 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=()):
163 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=()):
164 '''Push outgoing changesets (limited by revs) from a local
164 '''Push outgoing changesets (limited by revs) from a local
165 repository to remote. Return an integer:
165 repository to remote. Return an integer:
166 - None means nothing to push
166 - None means nothing to push
167 - 0 means HTTP error
167 - 0 means HTTP error
168 - 1 means we pushed and remote head count is unchanged *or*
168 - 1 means we pushed and remote head count is unchanged *or*
169 we have outgoing changesets but refused to push
169 we have outgoing changesets but refused to push
170 - other values as described by addchangegroup()
170 - other values as described by addchangegroup()
171 '''
171 '''
172 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks)
172 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks)
173 if pushop.remote.local():
173 if pushop.remote.local():
174 missing = (set(pushop.repo.requirements)
174 missing = (set(pushop.repo.requirements)
175 - pushop.remote.local().supported)
175 - pushop.remote.local().supported)
176 if missing:
176 if missing:
177 msg = _("required features are not"
177 msg = _("required features are not"
178 " supported in the destination:"
178 " supported in the destination:"
179 " %s") % (', '.join(sorted(missing)))
179 " %s") % (', '.join(sorted(missing)))
180 raise util.Abort(msg)
180 raise util.Abort(msg)
181
181
182 # there are two ways to push to remote repo:
182 # there are two ways to push to remote repo:
183 #
183 #
184 # addchangegroup assumes local user can lock remote
184 # addchangegroup assumes local user can lock remote
185 # repo (local filesystem, old ssh servers).
185 # repo (local filesystem, old ssh servers).
186 #
186 #
187 # unbundle assumes local user cannot lock remote repo (new ssh
187 # unbundle assumes local user cannot lock remote repo (new ssh
188 # servers, http servers).
188 # servers, http servers).
189
189
190 if not pushop.remote.canpush():
190 if not pushop.remote.canpush():
191 raise util.Abort(_("destination does not support push"))
191 raise util.Abort(_("destination does not support push"))
192 # get local lock as we might write phase data
192 # get local lock as we might write phase data
193 locallock = None
193 locallock = None
194 try:
194 try:
195 locallock = pushop.repo.lock()
195 locallock = pushop.repo.lock()
196 pushop.locallocked = True
196 pushop.locallocked = True
197 except IOError, err:
197 except IOError, err:
198 pushop.locallocked = False
198 pushop.locallocked = False
199 if err.errno != errno.EACCES:
199 if err.errno != errno.EACCES:
200 raise
200 raise
201 # source repo cannot be locked.
201 # source repo cannot be locked.
202 # We do not abort the push, but just disable the local phase
202 # We do not abort the push, but just disable the local phase
203 # synchronisation.
203 # synchronisation.
204 msg = 'cannot lock source repository: %s\n' % err
204 msg = 'cannot lock source repository: %s\n' % err
205 pushop.ui.debug(msg)
205 pushop.ui.debug(msg)
206 try:
206 try:
207 pushop.repo.checkpush(pushop)
207 pushop.repo.checkpush(pushop)
208 lock = None
208 lock = None
209 unbundle = pushop.remote.capable('unbundle')
209 unbundle = pushop.remote.capable('unbundle')
210 if not unbundle:
210 if not unbundle:
211 lock = pushop.remote.lock()
211 lock = pushop.remote.lock()
212 try:
212 try:
213 _pushdiscovery(pushop)
213 _pushdiscovery(pushop)
214 if (pushop.repo.ui.configbool('experimental', 'bundle2-exp',
214 if (pushop.repo.ui.configbool('experimental', 'bundle2-exp',
215 False)
215 False)
216 and pushop.remote.capable('bundle2-exp')):
216 and pushop.remote.capable('bundle2-exp')):
217 _pushbundle2(pushop)
217 _pushbundle2(pushop)
218 _pushchangeset(pushop)
218 _pushchangeset(pushop)
219 _pushsyncphase(pushop)
219 _pushsyncphase(pushop)
220 _pushobsolete(pushop)
220 _pushobsolete(pushop)
221 _pushbookmark(pushop)
221 _pushbookmark(pushop)
222 finally:
222 finally:
223 if lock is not None:
223 if lock is not None:
224 lock.release()
224 lock.release()
225 finally:
225 finally:
226 if locallock is not None:
226 if locallock is not None:
227 locallock.release()
227 locallock.release()
228
228
229 return pushop
229 return pushop
230
230
231 # list of steps to perform discovery before push
231 # list of steps to perform discovery before push
232 pushdiscoveryorder = []
232 pushdiscoveryorder = []
233
233
234 # Mapping between step name and function
234 # Mapping between step name and function
235 #
235 #
236 # This exists to help extensions wrap steps if necessary
236 # This exists to help extensions wrap steps if necessary
237 pushdiscoverymapping = {}
237 pushdiscoverymapping = {}
238
238
239 def pushdiscovery(stepname):
239 def pushdiscovery(stepname):
240 """decorator for function performing discovery before push
240 """decorator for function performing discovery before push
241
241
242 The function is added to the step -> function mapping and appended to the
242 The function is added to the step -> function mapping and appended to the
243 list of steps. Beware that decorated function will be added in order (this
243 list of steps. Beware that decorated function will be added in order (this
244 may matter).
244 may matter).
245
245
246 You can only use this decorator for a new step, if you want to wrap a step
246 You can only use this decorator for a new step, if you want to wrap a step
247 from an extension, change the pushdiscovery dictionary directly."""
247 from an extension, change the pushdiscovery dictionary directly."""
248 def dec(func):
248 def dec(func):
249 assert stepname not in pushdiscoverymapping
249 assert stepname not in pushdiscoverymapping
250 pushdiscoverymapping[stepname] = func
250 pushdiscoverymapping[stepname] = func
251 pushdiscoveryorder.append(stepname)
251 pushdiscoveryorder.append(stepname)
252 return func
252 return func
253 return dec
253 return dec
254
254
255 def _pushdiscovery(pushop):
255 def _pushdiscovery(pushop):
256 """Run all discovery steps"""
256 """Run all discovery steps"""
257 for stepname in pushdiscoveryorder:
257 for stepname in pushdiscoveryorder:
258 step = pushdiscoverymapping[stepname]
258 step = pushdiscoverymapping[stepname]
259 step(pushop)
259 step(pushop)
260
260
261 @pushdiscovery('changeset')
261 @pushdiscovery('changeset')
262 def _pushdiscoverychangeset(pushop):
262 def _pushdiscoverychangeset(pushop):
263 """discover the changeset that need to be pushed"""
263 """discover the changeset that need to be pushed"""
264 unfi = pushop.repo.unfiltered()
264 unfi = pushop.repo.unfiltered()
265 fci = discovery.findcommonincoming
265 fci = discovery.findcommonincoming
266 commoninc = fci(unfi, pushop.remote, force=pushop.force)
266 commoninc = fci(unfi, pushop.remote, force=pushop.force)
267 common, inc, remoteheads = commoninc
267 common, inc, remoteheads = commoninc
268 fco = discovery.findcommonoutgoing
268 fco = discovery.findcommonoutgoing
269 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
269 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
270 commoninc=commoninc, force=pushop.force)
270 commoninc=commoninc, force=pushop.force)
271 pushop.outgoing = outgoing
271 pushop.outgoing = outgoing
272 pushop.remoteheads = remoteheads
272 pushop.remoteheads = remoteheads
273 pushop.incoming = inc
273 pushop.incoming = inc
274
274
275 @pushdiscovery('phase')
275 @pushdiscovery('phase')
276 def _pushdiscoveryphase(pushop):
276 def _pushdiscoveryphase(pushop):
277 """discover the phase that needs to be pushed
277 """discover the phase that needs to be pushed
278
278
279 (computed for both success and failure case for changesets push)"""
279 (computed for both success and failure case for changesets push)"""
280 outgoing = pushop.outgoing
280 outgoing = pushop.outgoing
281 unfi = pushop.repo.unfiltered()
281 unfi = pushop.repo.unfiltered()
282 remotephases = pushop.remote.listkeys('phases')
282 remotephases = pushop.remote.listkeys('phases')
283 publishing = remotephases.get('publishing', False)
283 publishing = remotephases.get('publishing', False)
284 ana = phases.analyzeremotephases(pushop.repo,
284 ana = phases.analyzeremotephases(pushop.repo,
285 pushop.fallbackheads,
285 pushop.fallbackheads,
286 remotephases)
286 remotephases)
287 pheads, droots = ana
287 pheads, droots = ana
288 extracond = ''
288 extracond = ''
289 if not publishing:
289 if not publishing:
290 extracond = ' and public()'
290 extracond = ' and public()'
291 revset = 'heads((%%ln::%%ln) %s)' % extracond
291 revset = 'heads((%%ln::%%ln) %s)' % extracond
292 # Get the list of all revs draft on remote by public here.
292 # Get the list of all revs draft on remote by public here.
293 # XXX Beware that revset break if droots is not strictly
293 # XXX Beware that revset break if droots is not strictly
294 # XXX root we may want to ensure it is but it is costly
294 # XXX root we may want to ensure it is but it is costly
295 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
295 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
296 if not outgoing.missing:
296 if not outgoing.missing:
297 future = fallback
297 future = fallback
298 else:
298 else:
299 # adds changeset we are going to push as draft
299 # adds changeset we are going to push as draft
300 #
300 #
301 # should not be necessary for publishing server, but because of an
301 # should not be necessary for publishing server, but because of an
302 # issue fixed in xxxxx we have to do it anyway.
302 # issue fixed in xxxxx we have to do it anyway.
303 fdroots = list(unfi.set('roots(%ln + %ln::)',
303 fdroots = list(unfi.set('roots(%ln + %ln::)',
304 outgoing.missing, droots))
304 outgoing.missing, droots))
305 fdroots = [f.node() for f in fdroots]
305 fdroots = [f.node() for f in fdroots]
306 future = list(unfi.set(revset, fdroots, pushop.futureheads))
306 future = list(unfi.set(revset, fdroots, pushop.futureheads))
307 pushop.outdatedphases = future
307 pushop.outdatedphases = future
308 pushop.fallbackoutdatedphases = fallback
308 pushop.fallbackoutdatedphases = fallback
309
309
310 @pushdiscovery('obsmarker')
310 @pushdiscovery('obsmarker')
311 def _pushdiscoveryobsmarkers(pushop):
311 def _pushdiscoveryobsmarkers(pushop):
312 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
312 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
313 and pushop.repo.obsstore
313 and pushop.repo.obsstore
314 and 'obsolete' in pushop.remote.listkeys('namespaces')):
314 and 'obsolete' in pushop.remote.listkeys('namespaces')):
315 repo = pushop.repo
315 repo = pushop.repo
316 # very naive computation, that can be quite expensive on big repo.
316 # very naive computation, that can be quite expensive on big repo.
317 # However: evolution is currently slow on them anyway.
317 # However: evolution is currently slow on them anyway.
318 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
318 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
319 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
319 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
320
320
321 @pushdiscovery('bookmarks')
321 @pushdiscovery('bookmarks')
322 def _pushdiscoverybookmarks(pushop):
322 def _pushdiscoverybookmarks(pushop):
323 ui = pushop.ui
323 ui = pushop.ui
324 repo = pushop.repo.unfiltered()
324 repo = pushop.repo.unfiltered()
325 remote = pushop.remote
325 remote = pushop.remote
326 ui.debug("checking for updated bookmarks\n")
326 ui.debug("checking for updated bookmarks\n")
327 ancestors = ()
327 ancestors = ()
328 if pushop.revs:
328 if pushop.revs:
329 revnums = map(repo.changelog.rev, pushop.revs)
329 revnums = map(repo.changelog.rev, pushop.revs)
330 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
330 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
331 remotebookmark = remote.listkeys('bookmarks')
331 remotebookmark = remote.listkeys('bookmarks')
332
332
333 explicit = set(pushop.bookmarks)
333 explicit = set(pushop.bookmarks)
334
334
335 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
335 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
336 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
336 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
337 for b, scid, dcid in advsrc:
337 for b, scid, dcid in advsrc:
338 if b in explicit:
338 if b in explicit:
339 explicit.remove(b)
339 explicit.remove(b)
340 if not ancestors or repo[scid].rev() in ancestors:
340 if not ancestors or repo[scid].rev() in ancestors:
341 pushop.outbookmarks.append((b, dcid, scid))
341 pushop.outbookmarks.append((b, dcid, scid))
342 # search added bookmark
342 # search added bookmark
343 for b, scid, dcid in addsrc:
343 for b, scid, dcid in addsrc:
344 if b in explicit:
344 if b in explicit:
345 explicit.remove(b)
345 explicit.remove(b)
346 pushop.outbookmarks.append((b, '', scid))
346 pushop.outbookmarks.append((b, '', scid))
347 # search for overwritten bookmark
347 # search for overwritten bookmark
348 for b, scid, dcid in advdst + diverge + differ:
348 for b, scid, dcid in advdst + diverge + differ:
349 if b in explicit:
349 if b in explicit:
350 explicit.remove(b)
350 explicit.remove(b)
351 pushop.outbookmarks.append((b, dcid, scid))
351 pushop.outbookmarks.append((b, dcid, scid))
352 # search for bookmark to delete
352 # search for bookmark to delete
353 for b, scid, dcid in adddst:
353 for b, scid, dcid in adddst:
354 if b in explicit:
354 if b in explicit:
355 explicit.remove(b)
355 explicit.remove(b)
356 # treat as "deleted locally"
356 # treat as "deleted locally"
357 pushop.outbookmarks.append((b, dcid, ''))
357 pushop.outbookmarks.append((b, dcid, ''))
358 # identical bookmarks shouldn't get reported
358 # identical bookmarks shouldn't get reported
359 for b, scid, dcid in same:
359 for b, scid, dcid in same:
360 if b in explicit:
360 if b in explicit:
361 explicit.remove(b)
361 explicit.remove(b)
362
362
363 if explicit:
363 if explicit:
364 explicit = sorted(explicit)
364 explicit = sorted(explicit)
365 # we should probably list all of them
365 # we should probably list all of them
366 ui.warn(_('bookmark %s does not exist on the local '
366 ui.warn(_('bookmark %s does not exist on the local '
367 'or remote repository!\n') % explicit[0])
367 'or remote repository!\n') % explicit[0])
368 pushop.bkresult = 2
368 pushop.bkresult = 2
369
369
370 pushop.outbookmarks.sort()
370 pushop.outbookmarks.sort()
371
371
372 def _pushcheckoutgoing(pushop):
372 def _pushcheckoutgoing(pushop):
373 outgoing = pushop.outgoing
373 outgoing = pushop.outgoing
374 unfi = pushop.repo.unfiltered()
374 unfi = pushop.repo.unfiltered()
375 if not outgoing.missing:
375 if not outgoing.missing:
376 # nothing to push
376 # nothing to push
377 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
377 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
378 return False
378 return False
379 # something to push
379 # something to push
380 if not pushop.force:
380 if not pushop.force:
381 # if repo.obsstore == False --> no obsolete
381 # if repo.obsstore == False --> no obsolete
382 # then, save the iteration
382 # then, save the iteration
383 if unfi.obsstore:
383 if unfi.obsstore:
384 # this message are here for 80 char limit reason
384 # this message are here for 80 char limit reason
385 mso = _("push includes obsolete changeset: %s!")
385 mso = _("push includes obsolete changeset: %s!")
386 mst = {"unstable": _("push includes unstable changeset: %s!"),
386 mst = {"unstable": _("push includes unstable changeset: %s!"),
387 "bumped": _("push includes bumped changeset: %s!"),
387 "bumped": _("push includes bumped changeset: %s!"),
388 "divergent": _("push includes divergent changeset: %s!")}
388 "divergent": _("push includes divergent changeset: %s!")}
389 # If we are to push if there is at least one
389 # If we are to push if there is at least one
390 # obsolete or unstable changeset in missing, at
390 # obsolete or unstable changeset in missing, at
391 # least one of the missinghead will be obsolete or
391 # least one of the missinghead will be obsolete or
392 # unstable. So checking heads only is ok
392 # unstable. So checking heads only is ok
393 for node in outgoing.missingheads:
393 for node in outgoing.missingheads:
394 ctx = unfi[node]
394 ctx = unfi[node]
395 if ctx.obsolete():
395 if ctx.obsolete():
396 raise util.Abort(mso % ctx)
396 raise util.Abort(mso % ctx)
397 elif ctx.troubled():
397 elif ctx.troubled():
398 raise util.Abort(mst[ctx.troubles()[0]] % ctx)
398 raise util.Abort(mst[ctx.troubles()[0]] % ctx)
399 newbm = pushop.ui.configlist('bookmarks', 'pushing')
399 newbm = pushop.ui.configlist('bookmarks', 'pushing')
400 discovery.checkheads(unfi, pushop.remote, outgoing,
400 discovery.checkheads(unfi, pushop.remote, outgoing,
401 pushop.remoteheads,
401 pushop.remoteheads,
402 pushop.newbranch,
402 pushop.newbranch,
403 bool(pushop.incoming),
403 bool(pushop.incoming),
404 newbm)
404 newbm)
405 return True
405 return True
406
406
407 # List of names of steps to perform for an outgoing bundle2, order matters.
407 # List of names of steps to perform for an outgoing bundle2, order matters.
408 b2partsgenorder = []
408 b2partsgenorder = []
409
409
410 # Mapping between step name and function
410 # Mapping between step name and function
411 #
411 #
412 # This exists to help extensions wrap steps if necessary
412 # This exists to help extensions wrap steps if necessary
413 b2partsgenmapping = {}
413 b2partsgenmapping = {}
414
414
415 def b2partsgenerator(stepname):
415 def b2partsgenerator(stepname):
416 """decorator for function generating bundle2 part
416 """decorator for function generating bundle2 part
417
417
418 The function is added to the step -> function mapping and appended to the
418 The function is added to the step -> function mapping and appended to the
419 list of steps. Beware that decorated functions will be added in order
419 list of steps. Beware that decorated functions will be added in order
420 (this may matter).
420 (this may matter).
421
421
422 You can only use this decorator for new steps, if you want to wrap a step
422 You can only use this decorator for new steps, if you want to wrap a step
423 from an extension, attack the b2partsgenmapping dictionary directly."""
423 from an extension, attack the b2partsgenmapping dictionary directly."""
424 def dec(func):
424 def dec(func):
425 assert stepname not in b2partsgenmapping
425 assert stepname not in b2partsgenmapping
426 b2partsgenmapping[stepname] = func
426 b2partsgenmapping[stepname] = func
427 b2partsgenorder.append(stepname)
427 b2partsgenorder.append(stepname)
428 return func
428 return func
429 return dec
429 return dec
430
430
431 @b2partsgenerator('changeset')
431 @b2partsgenerator('changeset')
432 def _pushb2ctx(pushop, bundler):
432 def _pushb2ctx(pushop, bundler):
433 """handle changegroup push through bundle2
433 """handle changegroup push through bundle2
434
434
435 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
435 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
436 """
436 """
437 if 'changesets' in pushop.stepsdone:
437 if 'changesets' in pushop.stepsdone:
438 return
438 return
439 pushop.stepsdone.add('changesets')
439 pushop.stepsdone.add('changesets')
440 # Send known heads to the server for race detection.
440 # Send known heads to the server for race detection.
441 if not _pushcheckoutgoing(pushop):
441 if not _pushcheckoutgoing(pushop):
442 return
442 return
443 pushop.repo.prepushoutgoinghooks(pushop.repo,
443 pushop.repo.prepushoutgoinghooks(pushop.repo,
444 pushop.remote,
444 pushop.remote,
445 pushop.outgoing)
445 pushop.outgoing)
446 if not pushop.force:
446 if not pushop.force:
447 bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads))
447 bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads))
448 b2caps = bundle2.bundle2caps(pushop.remote)
448 b2caps = bundle2.bundle2caps(pushop.remote)
449 version = None
449 version = None
450 cgversions = b2caps.get('b2x:changegroup')
450 cgversions = b2caps.get('b2x:changegroup')
451 if cgversions is None:
451 if cgversions is None:
452 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
452 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
453 pushop.outgoing)
453 pushop.outgoing)
454 else:
454 else:
455 cgversions = [v for v in cgversions if v in changegroup.packermap]
455 cgversions = [v for v in cgversions if v in changegroup.packermap]
456 if not cgversions:
456 if not cgversions:
457 raise ValueError(_('no common changegroup version'))
457 raise ValueError(_('no common changegroup version'))
458 version = max(cgversions)
458 version = max(cgversions)
459 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
459 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
460 pushop.outgoing,
460 pushop.outgoing,
461 version=version)
461 version=version)
462 cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg)
462 cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg)
463 if version is not None:
463 if version is not None:
464 cgpart.addparam('version', version)
464 cgpart.addparam('version', version)
465 def handlereply(op):
465 def handlereply(op):
466 """extract addchangegroup returns from server reply"""
466 """extract addchangegroup returns from server reply"""
467 cgreplies = op.records.getreplies(cgpart.id)
467 cgreplies = op.records.getreplies(cgpart.id)
468 assert len(cgreplies['changegroup']) == 1
468 assert len(cgreplies['changegroup']) == 1
469 pushop.cgresult = cgreplies['changegroup'][0]['return']
469 pushop.cgresult = cgreplies['changegroup'][0]['return']
470 return handlereply
470 return handlereply
471
471
472 @b2partsgenerator('phase')
472 @b2partsgenerator('phase')
473 def _pushb2phases(pushop, bundler):
473 def _pushb2phases(pushop, bundler):
474 """handle phase push through bundle2"""
474 """handle phase push through bundle2"""
475 if 'phases' in pushop.stepsdone:
475 if 'phases' in pushop.stepsdone:
476 return
476 return
477 b2caps = bundle2.bundle2caps(pushop.remote)
477 b2caps = bundle2.bundle2caps(pushop.remote)
478 if not 'b2x:pushkey' in b2caps:
478 if not 'b2x:pushkey' in b2caps:
479 return
479 return
480 pushop.stepsdone.add('phases')
480 pushop.stepsdone.add('phases')
481 part2node = []
481 part2node = []
482 enc = pushkey.encode
482 enc = pushkey.encode
483 for newremotehead in pushop.outdatedphases:
483 for newremotehead in pushop.outdatedphases:
484 part = bundler.newpart('b2x:pushkey')
484 part = bundler.newpart('b2x:pushkey')
485 part.addparam('namespace', enc('phases'))
485 part.addparam('namespace', enc('phases'))
486 part.addparam('key', enc(newremotehead.hex()))
486 part.addparam('key', enc(newremotehead.hex()))
487 part.addparam('old', enc(str(phases.draft)))
487 part.addparam('old', enc(str(phases.draft)))
488 part.addparam('new', enc(str(phases.public)))
488 part.addparam('new', enc(str(phases.public)))
489 part2node.append((part.id, newremotehead))
489 part2node.append((part.id, newremotehead))
490 def handlereply(op):
490 def handlereply(op):
491 for partid, node in part2node:
491 for partid, node in part2node:
492 partrep = op.records.getreplies(partid)
492 partrep = op.records.getreplies(partid)
493 results = partrep['pushkey']
493 results = partrep['pushkey']
494 assert len(results) <= 1
494 assert len(results) <= 1
495 msg = None
495 msg = None
496 if not results:
496 if not results:
497 msg = _('server ignored update of %s to public!\n') % node
497 msg = _('server ignored update of %s to public!\n') % node
498 elif not int(results[0]['return']):
498 elif not int(results[0]['return']):
499 msg = _('updating %s to public failed!\n') % node
499 msg = _('updating %s to public failed!\n') % node
500 if msg is not None:
500 if msg is not None:
501 pushop.ui.warn(msg)
501 pushop.ui.warn(msg)
502 return handlereply
502 return handlereply
503
503
504 @b2partsgenerator('obsmarkers')
504 @b2partsgenerator('obsmarkers')
505 def _pushb2obsmarkers(pushop, bundler):
505 def _pushb2obsmarkers(pushop, bundler):
506 if 'obsmarkers' in pushop.stepsdone:
506 if 'obsmarkers' in pushop.stepsdone:
507 return
507 return
508 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
508 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
509 if obsolete.commonversion(remoteversions) is None:
509 if obsolete.commonversion(remoteversions) is None:
510 return
510 return
511 pushop.stepsdone.add('obsmarkers')
511 pushop.stepsdone.add('obsmarkers')
512 if pushop.outobsmarkers:
512 if pushop.outobsmarkers:
513 buildobsmarkerspart(bundler, pushop.outobsmarkers)
513 buildobsmarkerspart(bundler, pushop.outobsmarkers)
514
514
515 @b2partsgenerator('bookmarks')
515 @b2partsgenerator('bookmarks')
516 def _pushb2bookmarks(pushop, bundler):
516 def _pushb2bookmarks(pushop, bundler):
517 """handle phase push through bundle2"""
517 """handle phase push through bundle2"""
518 if 'bookmarks' in pushop.stepsdone:
518 if 'bookmarks' in pushop.stepsdone:
519 return
519 return
520 b2caps = bundle2.bundle2caps(pushop.remote)
520 b2caps = bundle2.bundle2caps(pushop.remote)
521 if 'b2x:pushkey' not in b2caps:
521 if 'b2x:pushkey' not in b2caps:
522 return
522 return
523 pushop.stepsdone.add('bookmarks')
523 pushop.stepsdone.add('bookmarks')
524 part2book = []
524 part2book = []
525 enc = pushkey.encode
525 enc = pushkey.encode
526 for book, old, new in pushop.outbookmarks:
526 for book, old, new in pushop.outbookmarks:
527 part = bundler.newpart('b2x:pushkey')
527 part = bundler.newpart('b2x:pushkey')
528 part.addparam('namespace', enc('bookmarks'))
528 part.addparam('namespace', enc('bookmarks'))
529 part.addparam('key', enc(book))
529 part.addparam('key', enc(book))
530 part.addparam('old', enc(old))
530 part.addparam('old', enc(old))
531 part.addparam('new', enc(new))
531 part.addparam('new', enc(new))
532 action = 'update'
532 action = 'update'
533 if not old:
533 if not old:
534 action = 'export'
534 action = 'export'
535 elif not new:
535 elif not new:
536 action = 'delete'
536 action = 'delete'
537 part2book.append((part.id, book, action))
537 part2book.append((part.id, book, action))
538
538
539
539
540 def handlereply(op):
540 def handlereply(op):
541 ui = pushop.ui
541 ui = pushop.ui
542 for partid, book, action in part2book:
542 for partid, book, action in part2book:
543 partrep = op.records.getreplies(partid)
543 partrep = op.records.getreplies(partid)
544 results = partrep['pushkey']
544 results = partrep['pushkey']
545 assert len(results) <= 1
545 assert len(results) <= 1
546 if not results:
546 if not results:
547 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
547 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
548 else:
548 else:
549 ret = int(results[0]['return'])
549 ret = int(results[0]['return'])
550 if ret:
550 if ret:
551 ui.status(bookmsgmap[action][0] % book)
551 ui.status(bookmsgmap[action][0] % book)
552 else:
552 else:
553 ui.warn(bookmsgmap[action][1] % book)
553 ui.warn(bookmsgmap[action][1] % book)
554 if pushop.bkresult is not None:
554 if pushop.bkresult is not None:
555 pushop.bkresult = 1
555 pushop.bkresult = 1
556 return handlereply
556 return handlereply
557
557
558
558
559 def _pushbundle2(pushop):
559 def _pushbundle2(pushop):
560 """push data to the remote using bundle2
560 """push data to the remote using bundle2
561
561
562 The only currently supported type of data is changegroup but this will
562 The only currently supported type of data is changegroup but this will
563 evolve in the future."""
563 evolve in the future."""
564 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
564 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
565 # create reply capability
565 # create reply capability
566 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
566 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
567 bundler.newpart('b2x:replycaps', data=capsblob)
567 bundler.newpart('b2x:replycaps', data=capsblob)
568 replyhandlers = []
568 replyhandlers = []
569 for partgenname in b2partsgenorder:
569 for partgenname in b2partsgenorder:
570 partgen = b2partsgenmapping[partgenname]
570 partgen = b2partsgenmapping[partgenname]
571 ret = partgen(pushop, bundler)
571 ret = partgen(pushop, bundler)
572 if callable(ret):
572 if callable(ret):
573 replyhandlers.append(ret)
573 replyhandlers.append(ret)
574 # do not push if nothing to push
574 # do not push if nothing to push
575 if bundler.nbparts <= 1:
575 if bundler.nbparts <= 1:
576 return
576 return
577 stream = util.chunkbuffer(bundler.getchunks())
577 stream = util.chunkbuffer(bundler.getchunks())
578 try:
578 try:
579 reply = pushop.remote.unbundle(stream, ['force'], 'push')
579 reply = pushop.remote.unbundle(stream, ['force'], 'push')
580 except error.BundleValueError, exc:
580 except error.BundleValueError, exc:
581 raise util.Abort('missing support for %s' % exc)
581 raise util.Abort('missing support for %s' % exc)
582 try:
582 try:
583 op = bundle2.processbundle(pushop.repo, reply)
583 op = bundle2.processbundle(pushop.repo, reply)
584 except error.BundleValueError, exc:
584 except error.BundleValueError, exc:
585 raise util.Abort('missing support for %s' % exc)
585 raise util.Abort('missing support for %s' % exc)
586 for rephand in replyhandlers:
586 for rephand in replyhandlers:
587 rephand(op)
587 rephand(op)
588
588
589 def _pushchangeset(pushop):
589 def _pushchangeset(pushop):
590 """Make the actual push of changeset bundle to remote repo"""
590 """Make the actual push of changeset bundle to remote repo"""
591 if 'changesets' in pushop.stepsdone:
591 if 'changesets' in pushop.stepsdone:
592 return
592 return
593 pushop.stepsdone.add('changesets')
593 pushop.stepsdone.add('changesets')
594 if not _pushcheckoutgoing(pushop):
594 if not _pushcheckoutgoing(pushop):
595 return
595 return
596 pushop.repo.prepushoutgoinghooks(pushop.repo,
596 pushop.repo.prepushoutgoinghooks(pushop.repo,
597 pushop.remote,
597 pushop.remote,
598 pushop.outgoing)
598 pushop.outgoing)
599 outgoing = pushop.outgoing
599 outgoing = pushop.outgoing
600 unbundle = pushop.remote.capable('unbundle')
600 unbundle = pushop.remote.capable('unbundle')
601 # TODO: get bundlecaps from remote
601 # TODO: get bundlecaps from remote
602 bundlecaps = None
602 bundlecaps = None
603 # create a changegroup from local
603 # create a changegroup from local
604 if pushop.revs is None and not (outgoing.excluded
604 if pushop.revs is None and not (outgoing.excluded
605 or pushop.repo.changelog.filteredrevs):
605 or pushop.repo.changelog.filteredrevs):
606 # push everything,
606 # push everything,
607 # use the fast path, no race possible on push
607 # use the fast path, no race possible on push
608 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
608 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
609 cg = changegroup.getsubset(pushop.repo,
609 cg = changegroup.getsubset(pushop.repo,
610 outgoing,
610 outgoing,
611 bundler,
611 bundler,
612 'push',
612 'push',
613 fastpath=True)
613 fastpath=True)
614 else:
614 else:
615 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
615 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
616 bundlecaps)
616 bundlecaps)
617
617
618 # apply changegroup to remote
618 # apply changegroup to remote
619 if unbundle:
619 if unbundle:
620 # local repo finds heads on server, finds out what
620 # local repo finds heads on server, finds out what
621 # revs it must push. once revs transferred, if server
621 # revs it must push. once revs transferred, if server
622 # finds it has different heads (someone else won
622 # finds it has different heads (someone else won
623 # commit/push race), server aborts.
623 # commit/push race), server aborts.
624 if pushop.force:
624 if pushop.force:
625 remoteheads = ['force']
625 remoteheads = ['force']
626 else:
626 else:
627 remoteheads = pushop.remoteheads
627 remoteheads = pushop.remoteheads
628 # ssh: return remote's addchangegroup()
628 # ssh: return remote's addchangegroup()
629 # http: return remote's addchangegroup() or 0 for error
629 # http: return remote's addchangegroup() or 0 for error
630 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
630 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
631 pushop.repo.url())
631 pushop.repo.url())
632 else:
632 else:
633 # we return an integer indicating remote head count
633 # we return an integer indicating remote head count
634 # change
634 # change
635 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
635 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
636 pushop.repo.url())
636 pushop.repo.url())
637
637
638 def _pushsyncphase(pushop):
638 def _pushsyncphase(pushop):
639 """synchronise phase information locally and remotely"""
639 """synchronise phase information locally and remotely"""
640 cheads = pushop.commonheads
640 cheads = pushop.commonheads
641 # even when we don't push, exchanging phase data is useful
641 # even when we don't push, exchanging phase data is useful
642 remotephases = pushop.remote.listkeys('phases')
642 remotephases = pushop.remote.listkeys('phases')
643 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
643 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
644 and remotephases # server supports phases
644 and remotephases # server supports phases
645 and pushop.cgresult is None # nothing was pushed
645 and pushop.cgresult is None # nothing was pushed
646 and remotephases.get('publishing', False)):
646 and remotephases.get('publishing', False)):
647 # When:
647 # When:
648 # - this is a subrepo push
648 # - this is a subrepo push
649 # - and remote support phase
649 # - and remote support phase
650 # - and no changeset was pushed
650 # - and no changeset was pushed
651 # - and remote is publishing
651 # - and remote is publishing
652 # We may be in issue 3871 case!
652 # We may be in issue 3871 case!
653 # We drop the possible phase synchronisation done by
653 # We drop the possible phase synchronisation done by
654 # courtesy to publish changesets possibly locally draft
654 # courtesy to publish changesets possibly locally draft
655 # on the remote.
655 # on the remote.
656 remotephases = {'publishing': 'True'}
656 remotephases = {'publishing': 'True'}
657 if not remotephases: # old server or public only reply from non-publishing
657 if not remotephases: # old server or public only reply from non-publishing
658 _localphasemove(pushop, cheads)
658 _localphasemove(pushop, cheads)
659 # don't push any phase data as there is nothing to push
659 # don't push any phase data as there is nothing to push
660 else:
660 else:
661 ana = phases.analyzeremotephases(pushop.repo, cheads,
661 ana = phases.analyzeremotephases(pushop.repo, cheads,
662 remotephases)
662 remotephases)
663 pheads, droots = ana
663 pheads, droots = ana
664 ### Apply remote phase on local
664 ### Apply remote phase on local
665 if remotephases.get('publishing', False):
665 if remotephases.get('publishing', False):
666 _localphasemove(pushop, cheads)
666 _localphasemove(pushop, cheads)
667 else: # publish = False
667 else: # publish = False
668 _localphasemove(pushop, pheads)
668 _localphasemove(pushop, pheads)
669 _localphasemove(pushop, cheads, phases.draft)
669 _localphasemove(pushop, cheads, phases.draft)
670 ### Apply local phase on remote
670 ### Apply local phase on remote
671
671
672 if pushop.cgresult:
672 if pushop.cgresult:
673 if 'phases' in pushop.stepsdone:
673 if 'phases' in pushop.stepsdone:
674 # phases already pushed though bundle2
674 # phases already pushed though bundle2
675 return
675 return
676 outdated = pushop.outdatedphases
676 outdated = pushop.outdatedphases
677 else:
677 else:
678 outdated = pushop.fallbackoutdatedphases
678 outdated = pushop.fallbackoutdatedphases
679
679
680 pushop.stepsdone.add('phases')
680 pushop.stepsdone.add('phases')
681
681
682 # filter heads already turned public by the push
682 # filter heads already turned public by the push
683 outdated = [c for c in outdated if c.node() not in pheads]
683 outdated = [c for c in outdated if c.node() not in pheads]
684 b2caps = bundle2.bundle2caps(pushop.remote)
684 b2caps = bundle2.bundle2caps(pushop.remote)
685 if 'b2x:pushkey' in b2caps:
685 if 'b2x:pushkey' in b2caps:
686 # server supports bundle2, let's do a batched push through it
686 # server supports bundle2, let's do a batched push through it
687 #
687 #
688 # This will eventually be unified with the changesets bundle2 push
688 # This will eventually be unified with the changesets bundle2 push
689 bundler = bundle2.bundle20(pushop.ui, b2caps)
689 bundler = bundle2.bundle20(pushop.ui, b2caps)
690 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
690 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
691 bundler.newpart('b2x:replycaps', data=capsblob)
691 bundler.newpart('b2x:replycaps', data=capsblob)
692 part2node = []
692 part2node = []
693 enc = pushkey.encode
693 enc = pushkey.encode
694 for newremotehead in outdated:
694 for newremotehead in outdated:
695 part = bundler.newpart('b2x:pushkey')
695 part = bundler.newpart('b2x:pushkey')
696 part.addparam('namespace', enc('phases'))
696 part.addparam('namespace', enc('phases'))
697 part.addparam('key', enc(newremotehead.hex()))
697 part.addparam('key', enc(newremotehead.hex()))
698 part.addparam('old', enc(str(phases.draft)))
698 part.addparam('old', enc(str(phases.draft)))
699 part.addparam('new', enc(str(phases.public)))
699 part.addparam('new', enc(str(phases.public)))
700 part2node.append((part.id, newremotehead))
700 part2node.append((part.id, newremotehead))
701 stream = util.chunkbuffer(bundler.getchunks())
701 stream = util.chunkbuffer(bundler.getchunks())
702 try:
702 try:
703 reply = pushop.remote.unbundle(stream, ['force'], 'push')
703 reply = pushop.remote.unbundle(stream, ['force'], 'push')
704 op = bundle2.processbundle(pushop.repo, reply)
704 op = bundle2.processbundle(pushop.repo, reply)
705 except error.BundleValueError, exc:
705 except error.BundleValueError, exc:
706 raise util.Abort('missing support for %s' % exc)
706 raise util.Abort('missing support for %s' % exc)
707 for partid, node in part2node:
707 for partid, node in part2node:
708 partrep = op.records.getreplies(partid)
708 partrep = op.records.getreplies(partid)
709 results = partrep['pushkey']
709 results = partrep['pushkey']
710 assert len(results) <= 1
710 assert len(results) <= 1
711 msg = None
711 msg = None
712 if not results:
712 if not results:
713 msg = _('server ignored update of %s to public!\n') % node
713 msg = _('server ignored update of %s to public!\n') % node
714 elif not int(results[0]['return']):
714 elif not int(results[0]['return']):
715 msg = _('updating %s to public failed!\n') % node
715 msg = _('updating %s to public failed!\n') % node
716 if msg is not None:
716 if msg is not None:
717 pushop.ui.warn(msg)
717 pushop.ui.warn(msg)
718
718
719 else:
719 else:
720 # fallback to independent pushkey command
720 # fallback to independent pushkey command
721 for newremotehead in outdated:
721 for newremotehead in outdated:
722 r = pushop.remote.pushkey('phases',
722 r = pushop.remote.pushkey('phases',
723 newremotehead.hex(),
723 newremotehead.hex(),
724 str(phases.draft),
724 str(phases.draft),
725 str(phases.public))
725 str(phases.public))
726 if not r:
726 if not r:
727 pushop.ui.warn(_('updating %s to public failed!\n')
727 pushop.ui.warn(_('updating %s to public failed!\n')
728 % newremotehead)
728 % newremotehead)
729
729
730 def _localphasemove(pushop, nodes, phase=phases.public):
730 def _localphasemove(pushop, nodes, phase=phases.public):
731 """move <nodes> to <phase> in the local source repo"""
731 """move <nodes> to <phase> in the local source repo"""
732 if pushop.locallocked:
732 if pushop.locallocked:
733 tr = pushop.repo.transaction('push-phase-sync')
733 tr = pushop.repo.transaction('push-phase-sync')
734 try:
734 try:
735 phases.advanceboundary(pushop.repo, tr, phase, nodes)
735 phases.advanceboundary(pushop.repo, tr, phase, nodes)
736 tr.close()
736 tr.close()
737 finally:
737 finally:
738 tr.release()
738 tr.release()
739 else:
739 else:
740 # repo is not locked, do not change any phases!
740 # repo is not locked, do not change any phases!
741 # Informs the user that phases should have been moved when
741 # Informs the user that phases should have been moved when
742 # applicable.
742 # applicable.
743 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
743 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
744 phasestr = phases.phasenames[phase]
744 phasestr = phases.phasenames[phase]
745 if actualmoves:
745 if actualmoves:
746 pushop.ui.status(_('cannot lock source repo, skipping '
746 pushop.ui.status(_('cannot lock source repo, skipping '
747 'local %s phase update\n') % phasestr)
747 'local %s phase update\n') % phasestr)
748
748
749 def _pushobsolete(pushop):
749 def _pushobsolete(pushop):
750 """utility function to push obsolete markers to a remote"""
750 """utility function to push obsolete markers to a remote"""
751 if 'obsmarkers' in pushop.stepsdone:
751 if 'obsmarkers' in pushop.stepsdone:
752 return
752 return
753 pushop.ui.debug('try to push obsolete markers to remote\n')
753 pushop.ui.debug('try to push obsolete markers to remote\n')
754 repo = pushop.repo
754 repo = pushop.repo
755 remote = pushop.remote
755 remote = pushop.remote
756 pushop.stepsdone.add('obsmarkers')
756 pushop.stepsdone.add('obsmarkers')
757 if pushop.outobsmarkers:
757 if pushop.outobsmarkers:
758 rslts = []
758 rslts = []
759 remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
759 remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
760 for key in sorted(remotedata, reverse=True):
760 for key in sorted(remotedata, reverse=True):
761 # reverse sort to ensure we end with dump0
761 # reverse sort to ensure we end with dump0
762 data = remotedata[key]
762 data = remotedata[key]
763 rslts.append(remote.pushkey('obsolete', key, '', data))
763 rslts.append(remote.pushkey('obsolete', key, '', data))
764 if [r for r in rslts if not r]:
764 if [r for r in rslts if not r]:
765 msg = _('failed to push some obsolete markers!\n')
765 msg = _('failed to push some obsolete markers!\n')
766 repo.ui.warn(msg)
766 repo.ui.warn(msg)
767
767
768 def _pushbookmark(pushop):
768 def _pushbookmark(pushop):
769 """Update bookmark position on remote"""
769 """Update bookmark position on remote"""
770 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
770 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
771 return
771 return
772 pushop.stepsdone.add('bookmarks')
772 pushop.stepsdone.add('bookmarks')
773 ui = pushop.ui
773 ui = pushop.ui
774 remote = pushop.remote
774 remote = pushop.remote
775
775
776 for b, old, new in pushop.outbookmarks:
776 for b, old, new in pushop.outbookmarks:
777 action = 'update'
777 action = 'update'
778 if not old:
778 if not old:
779 action = 'export'
779 action = 'export'
780 elif not new:
780 elif not new:
781 action = 'delete'
781 action = 'delete'
782 if remote.pushkey('bookmarks', b, old, new):
782 if remote.pushkey('bookmarks', b, old, new):
783 ui.status(bookmsgmap[action][0] % b)
783 ui.status(bookmsgmap[action][0] % b)
784 else:
784 else:
785 ui.warn(bookmsgmap[action][1] % b)
785 ui.warn(bookmsgmap[action][1] % b)
786 # discovery can have set the value form invalid entry
786 # discovery can have set the value form invalid entry
787 if pushop.bkresult is not None:
787 if pushop.bkresult is not None:
788 pushop.bkresult = 1
788 pushop.bkresult = 1
789
789
790 class pulloperation(object):
790 class pulloperation(object):
791 """A object that represent a single pull operation
791 """A object that represent a single pull operation
792
792
793 It purpose is to carry push related state and very common operation.
793 It purpose is to carry push related state and very common operation.
794
794
795 A new should be created at the beginning of each pull and discarded
795 A new should be created at the beginning of each pull and discarded
796 afterward.
796 afterward.
797 """
797 """
798
798
799 def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
799 def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
800 # repo we pull into
800 # repo we pull into
801 self.repo = repo
801 self.repo = repo
802 # repo we pull from
802 # repo we pull from
803 self.remote = remote
803 self.remote = remote
804 # revision we try to pull (None is "all")
804 # revision we try to pull (None is "all")
805 self.heads = heads
805 self.heads = heads
806 # bookmark pulled explicitly
806 # bookmark pulled explicitly
807 self.explicitbookmarks = bookmarks
807 self.explicitbookmarks = bookmarks
808 # do we force pull?
808 # do we force pull?
809 self.force = force
809 self.force = force
810 # the name the pull transaction
810 # the name the pull transaction
811 self._trname = 'pull\n' + util.hidepassword(remote.url())
811 self._trname = 'pull\n' + util.hidepassword(remote.url())
812 # hold the transaction once created
812 # hold the transaction once created
813 self._tr = None
813 self._tr = None
814 # set of common changeset between local and remote before pull
814 # set of common changeset between local and remote before pull
815 self.common = None
815 self.common = None
816 # set of pulled head
816 # set of pulled head
817 self.rheads = None
817 self.rheads = None
818 # list of missing changeset to fetch remotely
818 # list of missing changeset to fetch remotely
819 self.fetch = None
819 self.fetch = None
820 # remote bookmarks data
820 # remote bookmarks data
821 self.remotebookmarks = None
821 self.remotebookmarks = None
822 # result of changegroup pulling (used as return code by pull)
822 # result of changegroup pulling (used as return code by pull)
823 self.cgresult = None
823 self.cgresult = None
824 # list of step already done
824 # list of step already done
825 self.stepsdone = set()
825 self.stepsdone = set()
826
826
827 @util.propertycache
827 @util.propertycache
828 def pulledsubset(self):
828 def pulledsubset(self):
829 """heads of the set of changeset target by the pull"""
829 """heads of the set of changeset target by the pull"""
830 # compute target subset
830 # compute target subset
831 if self.heads is None:
831 if self.heads is None:
832 # We pulled every thing possible
832 # We pulled every thing possible
833 # sync on everything common
833 # sync on everything common
834 c = set(self.common)
834 c = set(self.common)
835 ret = list(self.common)
835 ret = list(self.common)
836 for n in self.rheads:
836 for n in self.rheads:
837 if n not in c:
837 if n not in c:
838 ret.append(n)
838 ret.append(n)
839 return ret
839 return ret
840 else:
840 else:
841 # We pulled a specific subset
841 # We pulled a specific subset
842 # sync on this subset
842 # sync on this subset
843 return self.heads
843 return self.heads
844
844
845 def gettransaction(self):
845 def gettransaction(self):
846 """get appropriate pull transaction, creating it if needed"""
846 """get appropriate pull transaction, creating it if needed"""
847 if self._tr is None:
847 if self._tr is None:
848 self._tr = self.repo.transaction(self._trname)
848 self._tr = self.repo.transaction(self._trname)
849 self._tr.hookargs['source'] = 'pull'
849 self._tr.hookargs['source'] = 'pull'
850 self._tr.hookargs['url'] = self.remote.url()
850 self._tr.hookargs['url'] = self.remote.url()
851 return self._tr
851 return self._tr
852
852
853 def closetransaction(self):
853 def closetransaction(self):
854 """close transaction if created"""
854 """close transaction if created"""
855 if self._tr is not None:
855 if self._tr is not None:
856 repo = self.repo
856 repo = self.repo
857 cl = repo.unfiltered().changelog
857 p = lambda: self._tr.writepending() and repo.root or ""
858 p = cl.writepending() and repo.root or ""
859 p = cl.writepending() and repo.root or ""
860 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
858 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
861 **self._tr.hookargs)
859 **self._tr.hookargs)
862 self._tr.close()
860 self._tr.close()
863 hookargs = dict(self._tr.hookargs)
861 hookargs = dict(self._tr.hookargs)
864 def runhooks():
862 def runhooks():
865 repo.hook('b2x-transactionclose', **hookargs)
863 repo.hook('b2x-transactionclose', **hookargs)
866 repo._afterlock(runhooks)
864 repo._afterlock(runhooks)
867
865
868 def releasetransaction(self):
866 def releasetransaction(self):
869 """release transaction if created"""
867 """release transaction if created"""
870 if self._tr is not None:
868 if self._tr is not None:
871 self._tr.release()
869 self._tr.release()
872
870
873 def pull(repo, remote, heads=None, force=False, bookmarks=()):
871 def pull(repo, remote, heads=None, force=False, bookmarks=()):
874 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
872 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
875 if pullop.remote.local():
873 if pullop.remote.local():
876 missing = set(pullop.remote.requirements) - pullop.repo.supported
874 missing = set(pullop.remote.requirements) - pullop.repo.supported
877 if missing:
875 if missing:
878 msg = _("required features are not"
876 msg = _("required features are not"
879 " supported in the destination:"
877 " supported in the destination:"
880 " %s") % (', '.join(sorted(missing)))
878 " %s") % (', '.join(sorted(missing)))
881 raise util.Abort(msg)
879 raise util.Abort(msg)
882
880
883 pullop.remotebookmarks = remote.listkeys('bookmarks')
881 pullop.remotebookmarks = remote.listkeys('bookmarks')
884 lock = pullop.repo.lock()
882 lock = pullop.repo.lock()
885 try:
883 try:
886 _pulldiscovery(pullop)
884 _pulldiscovery(pullop)
887 if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False)
885 if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False)
888 and pullop.remote.capable('bundle2-exp')):
886 and pullop.remote.capable('bundle2-exp')):
889 _pullbundle2(pullop)
887 _pullbundle2(pullop)
890 _pullchangeset(pullop)
888 _pullchangeset(pullop)
891 _pullphase(pullop)
889 _pullphase(pullop)
892 _pullbookmarks(pullop)
890 _pullbookmarks(pullop)
893 _pullobsolete(pullop)
891 _pullobsolete(pullop)
894 pullop.closetransaction()
892 pullop.closetransaction()
895 finally:
893 finally:
896 pullop.releasetransaction()
894 pullop.releasetransaction()
897 lock.release()
895 lock.release()
898
896
899 return pullop
897 return pullop
900
898
901 # list of steps to perform discovery before pull
899 # list of steps to perform discovery before pull
902 pulldiscoveryorder = []
900 pulldiscoveryorder = []
903
901
904 # Mapping between step name and function
902 # Mapping between step name and function
905 #
903 #
906 # This exists to help extensions wrap steps if necessary
904 # This exists to help extensions wrap steps if necessary
907 pulldiscoverymapping = {}
905 pulldiscoverymapping = {}
908
906
909 def pulldiscovery(stepname):
907 def pulldiscovery(stepname):
910 """decorator for function performing discovery before pull
908 """decorator for function performing discovery before pull
911
909
912 The function is added to the step -> function mapping and appended to the
910 The function is added to the step -> function mapping and appended to the
913 list of steps. Beware that decorated function will be added in order (this
911 list of steps. Beware that decorated function will be added in order (this
914 may matter).
912 may matter).
915
913
916 You can only use this decorator for a new step, if you want to wrap a step
914 You can only use this decorator for a new step, if you want to wrap a step
917 from an extension, change the pulldiscovery dictionary directly."""
915 from an extension, change the pulldiscovery dictionary directly."""
918 def dec(func):
916 def dec(func):
919 assert stepname not in pulldiscoverymapping
917 assert stepname not in pulldiscoverymapping
920 pulldiscoverymapping[stepname] = func
918 pulldiscoverymapping[stepname] = func
921 pulldiscoveryorder.append(stepname)
919 pulldiscoveryorder.append(stepname)
922 return func
920 return func
923 return dec
921 return dec
924
922
925 def _pulldiscovery(pullop):
923 def _pulldiscovery(pullop):
926 """Run all discovery steps"""
924 """Run all discovery steps"""
927 for stepname in pulldiscoveryorder:
925 for stepname in pulldiscoveryorder:
928 step = pulldiscoverymapping[stepname]
926 step = pulldiscoverymapping[stepname]
929 step(pullop)
927 step(pullop)
930
928
931 @pulldiscovery('changegroup')
929 @pulldiscovery('changegroup')
932 def _pulldiscoverychangegroup(pullop):
930 def _pulldiscoverychangegroup(pullop):
933 """discovery phase for the pull
931 """discovery phase for the pull
934
932
935 Current handle changeset discovery only, will change handle all discovery
933 Current handle changeset discovery only, will change handle all discovery
936 at some point."""
934 at some point."""
937 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(),
935 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(),
938 pullop.remote,
936 pullop.remote,
939 heads=pullop.heads,
937 heads=pullop.heads,
940 force=pullop.force)
938 force=pullop.force)
941 pullop.common, pullop.fetch, pullop.rheads = tmp
939 pullop.common, pullop.fetch, pullop.rheads = tmp
942
940
943 def _pullbundle2(pullop):
941 def _pullbundle2(pullop):
944 """pull data using bundle2
942 """pull data using bundle2
945
943
946 For now, the only supported data are changegroup."""
944 For now, the only supported data are changegroup."""
947 remotecaps = bundle2.bundle2caps(pullop.remote)
945 remotecaps = bundle2.bundle2caps(pullop.remote)
948 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
946 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
949 # pulling changegroup
947 # pulling changegroup
950 pullop.stepsdone.add('changegroup')
948 pullop.stepsdone.add('changegroup')
951
949
952 kwargs['common'] = pullop.common
950 kwargs['common'] = pullop.common
953 kwargs['heads'] = pullop.heads or pullop.rheads
951 kwargs['heads'] = pullop.heads or pullop.rheads
954 kwargs['cg'] = pullop.fetch
952 kwargs['cg'] = pullop.fetch
955 if 'b2x:listkeys' in remotecaps:
953 if 'b2x:listkeys' in remotecaps:
956 kwargs['listkeys'] = ['phase', 'bookmarks']
954 kwargs['listkeys'] = ['phase', 'bookmarks']
957 if not pullop.fetch:
955 if not pullop.fetch:
958 pullop.repo.ui.status(_("no changes found\n"))
956 pullop.repo.ui.status(_("no changes found\n"))
959 pullop.cgresult = 0
957 pullop.cgresult = 0
960 else:
958 else:
961 if pullop.heads is None and list(pullop.common) == [nullid]:
959 if pullop.heads is None and list(pullop.common) == [nullid]:
962 pullop.repo.ui.status(_("requesting all changes\n"))
960 pullop.repo.ui.status(_("requesting all changes\n"))
963 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
961 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
964 remoteversions = bundle2.obsmarkersversion(remotecaps)
962 remoteversions = bundle2.obsmarkersversion(remotecaps)
965 if obsolete.commonversion(remoteversions) is not None:
963 if obsolete.commonversion(remoteversions) is not None:
966 kwargs['obsmarkers'] = True
964 kwargs['obsmarkers'] = True
967 pullop.stepsdone.add('obsmarkers')
965 pullop.stepsdone.add('obsmarkers')
968 _pullbundle2extraprepare(pullop, kwargs)
966 _pullbundle2extraprepare(pullop, kwargs)
969 if kwargs.keys() == ['format']:
967 if kwargs.keys() == ['format']:
970 return # nothing to pull
968 return # nothing to pull
971 bundle = pullop.remote.getbundle('pull', **kwargs)
969 bundle = pullop.remote.getbundle('pull', **kwargs)
972 try:
970 try:
973 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
971 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
974 except error.BundleValueError, exc:
972 except error.BundleValueError, exc:
975 raise util.Abort('missing support for %s' % exc)
973 raise util.Abort('missing support for %s' % exc)
976
974
977 if pullop.fetch:
975 if pullop.fetch:
978 changedheads = 0
976 changedheads = 0
979 pullop.cgresult = 1
977 pullop.cgresult = 1
980 for cg in op.records['changegroup']:
978 for cg in op.records['changegroup']:
981 ret = cg['return']
979 ret = cg['return']
982 # If any changegroup result is 0, return 0
980 # If any changegroup result is 0, return 0
983 if ret == 0:
981 if ret == 0:
984 pullop.cgresult = 0
982 pullop.cgresult = 0
985 break
983 break
986 if ret < -1:
984 if ret < -1:
987 changedheads += ret + 1
985 changedheads += ret + 1
988 elif ret > 1:
986 elif ret > 1:
989 changedheads += ret - 1
987 changedheads += ret - 1
990 if changedheads > 0:
988 if changedheads > 0:
991 pullop.cgresult = 1 + changedheads
989 pullop.cgresult = 1 + changedheads
992 elif changedheads < 0:
990 elif changedheads < 0:
993 pullop.cgresult = -1 + changedheads
991 pullop.cgresult = -1 + changedheads
994
992
995 # processing phases change
993 # processing phases change
996 for namespace, value in op.records['listkeys']:
994 for namespace, value in op.records['listkeys']:
997 if namespace == 'phases':
995 if namespace == 'phases':
998 _pullapplyphases(pullop, value)
996 _pullapplyphases(pullop, value)
999
997
1000 # processing bookmark update
998 # processing bookmark update
1001 for namespace, value in op.records['listkeys']:
999 for namespace, value in op.records['listkeys']:
1002 if namespace == 'bookmarks':
1000 if namespace == 'bookmarks':
1003 pullop.remotebookmarks = value
1001 pullop.remotebookmarks = value
1004 _pullbookmarks(pullop)
1002 _pullbookmarks(pullop)
1005
1003
1006 def _pullbundle2extraprepare(pullop, kwargs):
1004 def _pullbundle2extraprepare(pullop, kwargs):
1007 """hook function so that extensions can extend the getbundle call"""
1005 """hook function so that extensions can extend the getbundle call"""
1008 pass
1006 pass
1009
1007
1010 def _pullchangeset(pullop):
1008 def _pullchangeset(pullop):
1011 """pull changeset from unbundle into the local repo"""
1009 """pull changeset from unbundle into the local repo"""
1012 # We delay the open of the transaction as late as possible so we
1010 # We delay the open of the transaction as late as possible so we
1013 # don't open transaction for nothing or you break future useful
1011 # don't open transaction for nothing or you break future useful
1014 # rollback call
1012 # rollback call
1015 if 'changegroup' in pullop.stepsdone:
1013 if 'changegroup' in pullop.stepsdone:
1016 return
1014 return
1017 pullop.stepsdone.add('changegroup')
1015 pullop.stepsdone.add('changegroup')
1018 if not pullop.fetch:
1016 if not pullop.fetch:
1019 pullop.repo.ui.status(_("no changes found\n"))
1017 pullop.repo.ui.status(_("no changes found\n"))
1020 pullop.cgresult = 0
1018 pullop.cgresult = 0
1021 return
1019 return
1022 pullop.gettransaction()
1020 pullop.gettransaction()
1023 if pullop.heads is None and list(pullop.common) == [nullid]:
1021 if pullop.heads is None and list(pullop.common) == [nullid]:
1024 pullop.repo.ui.status(_("requesting all changes\n"))
1022 pullop.repo.ui.status(_("requesting all changes\n"))
1025 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1023 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1026 # issue1320, avoid a race if remote changed after discovery
1024 # issue1320, avoid a race if remote changed after discovery
1027 pullop.heads = pullop.rheads
1025 pullop.heads = pullop.rheads
1028
1026
1029 if pullop.remote.capable('getbundle'):
1027 if pullop.remote.capable('getbundle'):
1030 # TODO: get bundlecaps from remote
1028 # TODO: get bundlecaps from remote
1031 cg = pullop.remote.getbundle('pull', common=pullop.common,
1029 cg = pullop.remote.getbundle('pull', common=pullop.common,
1032 heads=pullop.heads or pullop.rheads)
1030 heads=pullop.heads or pullop.rheads)
1033 elif pullop.heads is None:
1031 elif pullop.heads is None:
1034 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1032 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1035 elif not pullop.remote.capable('changegroupsubset'):
1033 elif not pullop.remote.capable('changegroupsubset'):
1036 raise util.Abort(_("partial pull cannot be done because "
1034 raise util.Abort(_("partial pull cannot be done because "
1037 "other repository doesn't support "
1035 "other repository doesn't support "
1038 "changegroupsubset."))
1036 "changegroupsubset."))
1039 else:
1037 else:
1040 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1038 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1041 pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
1039 pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
1042 pullop.remote.url())
1040 pullop.remote.url())
1043
1041
1044 def _pullphase(pullop):
1042 def _pullphase(pullop):
1045 # Get remote phases data from remote
1043 # Get remote phases data from remote
1046 if 'phases' in pullop.stepsdone:
1044 if 'phases' in pullop.stepsdone:
1047 return
1045 return
1048 remotephases = pullop.remote.listkeys('phases')
1046 remotephases = pullop.remote.listkeys('phases')
1049 _pullapplyphases(pullop, remotephases)
1047 _pullapplyphases(pullop, remotephases)
1050
1048
1051 def _pullapplyphases(pullop, remotephases):
1049 def _pullapplyphases(pullop, remotephases):
1052 """apply phase movement from observed remote state"""
1050 """apply phase movement from observed remote state"""
1053 if 'phases' in pullop.stepsdone:
1051 if 'phases' in pullop.stepsdone:
1054 return
1052 return
1055 pullop.stepsdone.add('phases')
1053 pullop.stepsdone.add('phases')
1056 publishing = bool(remotephases.get('publishing', False))
1054 publishing = bool(remotephases.get('publishing', False))
1057 if remotephases and not publishing:
1055 if remotephases and not publishing:
1058 # remote is new and unpublishing
1056 # remote is new and unpublishing
1059 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1057 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1060 pullop.pulledsubset,
1058 pullop.pulledsubset,
1061 remotephases)
1059 remotephases)
1062 dheads = pullop.pulledsubset
1060 dheads = pullop.pulledsubset
1063 else:
1061 else:
1064 # Remote is old or publishing all common changesets
1062 # Remote is old or publishing all common changesets
1065 # should be seen as public
1063 # should be seen as public
1066 pheads = pullop.pulledsubset
1064 pheads = pullop.pulledsubset
1067 dheads = []
1065 dheads = []
1068 unfi = pullop.repo.unfiltered()
1066 unfi = pullop.repo.unfiltered()
1069 phase = unfi._phasecache.phase
1067 phase = unfi._phasecache.phase
1070 rev = unfi.changelog.nodemap.get
1068 rev = unfi.changelog.nodemap.get
1071 public = phases.public
1069 public = phases.public
1072 draft = phases.draft
1070 draft = phases.draft
1073
1071
1074 # exclude changesets already public locally and update the others
1072 # exclude changesets already public locally and update the others
1075 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1073 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1076 if pheads:
1074 if pheads:
1077 tr = pullop.gettransaction()
1075 tr = pullop.gettransaction()
1078 phases.advanceboundary(pullop.repo, tr, public, pheads)
1076 phases.advanceboundary(pullop.repo, tr, public, pheads)
1079
1077
1080 # exclude changesets already draft locally and update the others
1078 # exclude changesets already draft locally and update the others
1081 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1079 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1082 if dheads:
1080 if dheads:
1083 tr = pullop.gettransaction()
1081 tr = pullop.gettransaction()
1084 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1082 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1085
1083
1086 def _pullbookmarks(pullop):
1084 def _pullbookmarks(pullop):
1087 """process the remote bookmark information to update the local one"""
1085 """process the remote bookmark information to update the local one"""
1088 if 'bookmarks' in pullop.stepsdone:
1086 if 'bookmarks' in pullop.stepsdone:
1089 return
1087 return
1090 pullop.stepsdone.add('bookmarks')
1088 pullop.stepsdone.add('bookmarks')
1091 repo = pullop.repo
1089 repo = pullop.repo
1092 remotebookmarks = pullop.remotebookmarks
1090 remotebookmarks = pullop.remotebookmarks
1093 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1091 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1094 pullop.remote.url(),
1092 pullop.remote.url(),
1095 pullop.gettransaction,
1093 pullop.gettransaction,
1096 explicit=pullop.explicitbookmarks)
1094 explicit=pullop.explicitbookmarks)
1097
1095
1098 def _pullobsolete(pullop):
1096 def _pullobsolete(pullop):
1099 """utility function to pull obsolete markers from a remote
1097 """utility function to pull obsolete markers from a remote
1100
1098
1101 The `gettransaction` is function that return the pull transaction, creating
1099 The `gettransaction` is function that return the pull transaction, creating
1102 one if necessary. We return the transaction to inform the calling code that
1100 one if necessary. We return the transaction to inform the calling code that
1103 a new transaction have been created (when applicable).
1101 a new transaction have been created (when applicable).
1104
1102
1105 Exists mostly to allow overriding for experimentation purpose"""
1103 Exists mostly to allow overriding for experimentation purpose"""
1106 if 'obsmarkers' in pullop.stepsdone:
1104 if 'obsmarkers' in pullop.stepsdone:
1107 return
1105 return
1108 pullop.stepsdone.add('obsmarkers')
1106 pullop.stepsdone.add('obsmarkers')
1109 tr = None
1107 tr = None
1110 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1108 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1111 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1109 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1112 remoteobs = pullop.remote.listkeys('obsolete')
1110 remoteobs = pullop.remote.listkeys('obsolete')
1113 if 'dump0' in remoteobs:
1111 if 'dump0' in remoteobs:
1114 tr = pullop.gettransaction()
1112 tr = pullop.gettransaction()
1115 for key in sorted(remoteobs, reverse=True):
1113 for key in sorted(remoteobs, reverse=True):
1116 if key.startswith('dump'):
1114 if key.startswith('dump'):
1117 data = base85.b85decode(remoteobs[key])
1115 data = base85.b85decode(remoteobs[key])
1118 pullop.repo.obsstore.mergemarkers(tr, data)
1116 pullop.repo.obsstore.mergemarkers(tr, data)
1119 pullop.repo.invalidatevolatilesets()
1117 pullop.repo.invalidatevolatilesets()
1120 return tr
1118 return tr
1121
1119
1122 def caps20to10(repo):
1120 def caps20to10(repo):
1123 """return a set with appropriate options to use bundle20 during getbundle"""
1121 """return a set with appropriate options to use bundle20 during getbundle"""
1124 caps = set(['HG2Y'])
1122 caps = set(['HG2Y'])
1125 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1123 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1126 caps.add('bundle2=' + urllib.quote(capsblob))
1124 caps.add('bundle2=' + urllib.quote(capsblob))
1127 return caps
1125 return caps
1128
1126
1129 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1127 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1130 getbundle2partsorder = []
1128 getbundle2partsorder = []
1131
1129
1132 # Mapping between step name and function
1130 # Mapping between step name and function
1133 #
1131 #
1134 # This exists to help extensions wrap steps if necessary
1132 # This exists to help extensions wrap steps if necessary
1135 getbundle2partsmapping = {}
1133 getbundle2partsmapping = {}
1136
1134
1137 def getbundle2partsgenerator(stepname):
1135 def getbundle2partsgenerator(stepname):
1138 """decorator for function generating bundle2 part for getbundle
1136 """decorator for function generating bundle2 part for getbundle
1139
1137
1140 The function is added to the step -> function mapping and appended to the
1138 The function is added to the step -> function mapping and appended to the
1141 list of steps. Beware that decorated functions will be added in order
1139 list of steps. Beware that decorated functions will be added in order
1142 (this may matter).
1140 (this may matter).
1143
1141
1144 You can only use this decorator for new steps, if you want to wrap a step
1142 You can only use this decorator for new steps, if you want to wrap a step
1145 from an extension, attack the getbundle2partsmapping dictionary directly."""
1143 from an extension, attack the getbundle2partsmapping dictionary directly."""
1146 def dec(func):
1144 def dec(func):
1147 assert stepname not in getbundle2partsmapping
1145 assert stepname not in getbundle2partsmapping
1148 getbundle2partsmapping[stepname] = func
1146 getbundle2partsmapping[stepname] = func
1149 getbundle2partsorder.append(stepname)
1147 getbundle2partsorder.append(stepname)
1150 return func
1148 return func
1151 return dec
1149 return dec
1152
1150
1153 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1151 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1154 **kwargs):
1152 **kwargs):
1155 """return a full bundle (with potentially multiple kind of parts)
1153 """return a full bundle (with potentially multiple kind of parts)
1156
1154
1157 Could be a bundle HG10 or a bundle HG2Y depending on bundlecaps
1155 Could be a bundle HG10 or a bundle HG2Y depending on bundlecaps
1158 passed. For now, the bundle can contain only changegroup, but this will
1156 passed. For now, the bundle can contain only changegroup, but this will
1159 changes when more part type will be available for bundle2.
1157 changes when more part type will be available for bundle2.
1160
1158
1161 This is different from changegroup.getchangegroup that only returns an HG10
1159 This is different from changegroup.getchangegroup that only returns an HG10
1162 changegroup bundle. They may eventually get reunited in the future when we
1160 changegroup bundle. They may eventually get reunited in the future when we
1163 have a clearer idea of the API we what to query different data.
1161 have a clearer idea of the API we what to query different data.
1164
1162
1165 The implementation is at a very early stage and will get massive rework
1163 The implementation is at a very early stage and will get massive rework
1166 when the API of bundle is refined.
1164 when the API of bundle is refined.
1167 """
1165 """
1168 # bundle10 case
1166 # bundle10 case
1169 if bundlecaps is None or 'HG2Y' not in bundlecaps:
1167 if bundlecaps is None or 'HG2Y' not in bundlecaps:
1170 if bundlecaps and not kwargs.get('cg', True):
1168 if bundlecaps and not kwargs.get('cg', True):
1171 raise ValueError(_('request for bundle10 must include changegroup'))
1169 raise ValueError(_('request for bundle10 must include changegroup'))
1172
1170
1173 if kwargs:
1171 if kwargs:
1174 raise ValueError(_('unsupported getbundle arguments: %s')
1172 raise ValueError(_('unsupported getbundle arguments: %s')
1175 % ', '.join(sorted(kwargs.keys())))
1173 % ', '.join(sorted(kwargs.keys())))
1176 return changegroup.getchangegroup(repo, source, heads=heads,
1174 return changegroup.getchangegroup(repo, source, heads=heads,
1177 common=common, bundlecaps=bundlecaps)
1175 common=common, bundlecaps=bundlecaps)
1178
1176
1179 # bundle20 case
1177 # bundle20 case
1180 b2caps = {}
1178 b2caps = {}
1181 for bcaps in bundlecaps:
1179 for bcaps in bundlecaps:
1182 if bcaps.startswith('bundle2='):
1180 if bcaps.startswith('bundle2='):
1183 blob = urllib.unquote(bcaps[len('bundle2='):])
1181 blob = urllib.unquote(bcaps[len('bundle2='):])
1184 b2caps.update(bundle2.decodecaps(blob))
1182 b2caps.update(bundle2.decodecaps(blob))
1185 bundler = bundle2.bundle20(repo.ui, b2caps)
1183 bundler = bundle2.bundle20(repo.ui, b2caps)
1186
1184
1187 for name in getbundle2partsorder:
1185 for name in getbundle2partsorder:
1188 func = getbundle2partsmapping[name]
1186 func = getbundle2partsmapping[name]
1189 kwargs['heads'] = heads
1187 kwargs['heads'] = heads
1190 kwargs['common'] = common
1188 kwargs['common'] = common
1191 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1189 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1192 **kwargs)
1190 **kwargs)
1193
1191
1194 return util.chunkbuffer(bundler.getchunks())
1192 return util.chunkbuffer(bundler.getchunks())
1195
1193
1196 @getbundle2partsgenerator('changegroup')
1194 @getbundle2partsgenerator('changegroup')
1197 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1195 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1198 b2caps=None, heads=None, common=None, **kwargs):
1196 b2caps=None, heads=None, common=None, **kwargs):
1199 """add a changegroup part to the requested bundle"""
1197 """add a changegroup part to the requested bundle"""
1200 cg = None
1198 cg = None
1201 if kwargs.get('cg', True):
1199 if kwargs.get('cg', True):
1202 # build changegroup bundle here.
1200 # build changegroup bundle here.
1203 version = None
1201 version = None
1204 cgversions = b2caps.get('b2x:changegroup')
1202 cgversions = b2caps.get('b2x:changegroup')
1205 if cgversions is None:
1203 if cgversions is None:
1206 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1204 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1207 common=common,
1205 common=common,
1208 bundlecaps=bundlecaps)
1206 bundlecaps=bundlecaps)
1209 else:
1207 else:
1210 cgversions = [v for v in cgversions if v in changegroup.packermap]
1208 cgversions = [v for v in cgversions if v in changegroup.packermap]
1211 if not cgversions:
1209 if not cgversions:
1212 raise ValueError(_('no common changegroup version'))
1210 raise ValueError(_('no common changegroup version'))
1213 version = max(cgversions)
1211 version = max(cgversions)
1214 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1212 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1215 common=common,
1213 common=common,
1216 bundlecaps=bundlecaps,
1214 bundlecaps=bundlecaps,
1217 version=version)
1215 version=version)
1218
1216
1219 if cg:
1217 if cg:
1220 part = bundler.newpart('b2x:changegroup', data=cg)
1218 part = bundler.newpart('b2x:changegroup', data=cg)
1221 if version is not None:
1219 if version is not None:
1222 part.addparam('version', version)
1220 part.addparam('version', version)
1223
1221
1224 @getbundle2partsgenerator('listkeys')
1222 @getbundle2partsgenerator('listkeys')
1225 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1223 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1226 b2caps=None, **kwargs):
1224 b2caps=None, **kwargs):
1227 """add parts containing listkeys namespaces to the requested bundle"""
1225 """add parts containing listkeys namespaces to the requested bundle"""
1228 listkeys = kwargs.get('listkeys', ())
1226 listkeys = kwargs.get('listkeys', ())
1229 for namespace in listkeys:
1227 for namespace in listkeys:
1230 part = bundler.newpart('b2x:listkeys')
1228 part = bundler.newpart('b2x:listkeys')
1231 part.addparam('namespace', namespace)
1229 part.addparam('namespace', namespace)
1232 keys = repo.listkeys(namespace).items()
1230 keys = repo.listkeys(namespace).items()
1233 part.data = pushkey.encodekeys(keys)
1231 part.data = pushkey.encodekeys(keys)
1234
1232
1235 @getbundle2partsgenerator('obsmarkers')
1233 @getbundle2partsgenerator('obsmarkers')
1236 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1234 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1237 b2caps=None, heads=None, **kwargs):
1235 b2caps=None, heads=None, **kwargs):
1238 """add an obsolescence markers part to the requested bundle"""
1236 """add an obsolescence markers part to the requested bundle"""
1239 if kwargs.get('obsmarkers', False):
1237 if kwargs.get('obsmarkers', False):
1240 if heads is None:
1238 if heads is None:
1241 heads = repo.heads()
1239 heads = repo.heads()
1242 subset = [c.node() for c in repo.set('::%ln', heads)]
1240 subset = [c.node() for c in repo.set('::%ln', heads)]
1243 markers = repo.obsstore.relevantmarkers(subset)
1241 markers = repo.obsstore.relevantmarkers(subset)
1244 buildobsmarkerspart(bundler, markers)
1242 buildobsmarkerspart(bundler, markers)
1245
1243
1246 def check_heads(repo, their_heads, context):
1244 def check_heads(repo, their_heads, context):
1247 """check if the heads of a repo have been modified
1245 """check if the heads of a repo have been modified
1248
1246
1249 Used by peer for unbundling.
1247 Used by peer for unbundling.
1250 """
1248 """
1251 heads = repo.heads()
1249 heads = repo.heads()
1252 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1250 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1253 if not (their_heads == ['force'] or their_heads == heads or
1251 if not (their_heads == ['force'] or their_heads == heads or
1254 their_heads == ['hashed', heads_hash]):
1252 their_heads == ['hashed', heads_hash]):
1255 # someone else committed/pushed/unbundled while we
1253 # someone else committed/pushed/unbundled while we
1256 # were transferring data
1254 # were transferring data
1257 raise error.PushRaced('repository changed while %s - '
1255 raise error.PushRaced('repository changed while %s - '
1258 'please try again' % context)
1256 'please try again' % context)
1259
1257
1260 def unbundle(repo, cg, heads, source, url):
1258 def unbundle(repo, cg, heads, source, url):
1261 """Apply a bundle to a repo.
1259 """Apply a bundle to a repo.
1262
1260
1263 this function makes sure the repo is locked during the application and have
1261 this function makes sure the repo is locked during the application and have
1264 mechanism to check that no push race occurred between the creation of the
1262 mechanism to check that no push race occurred between the creation of the
1265 bundle and its application.
1263 bundle and its application.
1266
1264
1267 If the push was raced as PushRaced exception is raised."""
1265 If the push was raced as PushRaced exception is raised."""
1268 r = 0
1266 r = 0
1269 # need a transaction when processing a bundle2 stream
1267 # need a transaction when processing a bundle2 stream
1270 tr = None
1268 tr = None
1271 lock = repo.lock()
1269 lock = repo.lock()
1272 try:
1270 try:
1273 check_heads(repo, heads, 'uploading changes')
1271 check_heads(repo, heads, 'uploading changes')
1274 # push can proceed
1272 # push can proceed
1275 if util.safehasattr(cg, 'params'):
1273 if util.safehasattr(cg, 'params'):
1276 try:
1274 try:
1277 tr = repo.transaction('unbundle')
1275 tr = repo.transaction('unbundle')
1278 tr.hookargs['source'] = source
1276 tr.hookargs['source'] = source
1279 tr.hookargs['url'] = url
1277 tr.hookargs['url'] = url
1280 tr.hookargs['bundle2-exp'] = '1'
1278 tr.hookargs['bundle2-exp'] = '1'
1281 r = bundle2.processbundle(repo, cg, lambda: tr).reply
1279 r = bundle2.processbundle(repo, cg, lambda: tr).reply
1282 cl = repo.unfiltered().changelog
1280 p = lambda: tr.writepending() and repo.root or ""
1283 p = cl.writepending() and repo.root or ""
1284 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
1281 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
1285 **tr.hookargs)
1282 **tr.hookargs)
1286 tr.close()
1283 tr.close()
1287 hookargs = dict(tr.hookargs)
1284 hookargs = dict(tr.hookargs)
1288 def runhooks():
1285 def runhooks():
1289 repo.hook('b2x-transactionclose', **hookargs)
1286 repo.hook('b2x-transactionclose', **hookargs)
1290 repo._afterlock(runhooks)
1287 repo._afterlock(runhooks)
1291 except Exception, exc:
1288 except Exception, exc:
1292 exc.duringunbundle2 = True
1289 exc.duringunbundle2 = True
1293 raise
1290 raise
1294 else:
1291 else:
1295 r = changegroup.addchangegroup(repo, cg, source, url)
1292 r = changegroup.addchangegroup(repo, cg, source, url)
1296 finally:
1293 finally:
1297 if tr is not None:
1294 if tr is not None:
1298 tr.release()
1295 tr.release()
1299 lock.release()
1296 lock.release()
1300 return r
1297 return r
@@ -1,1804 +1,1804 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 from node import hex, nullid, short
7 from node import hex, nullid, short
8 from i18n import _
8 from i18n import _
9 import urllib
9 import urllib
10 import peer, changegroup, subrepo, pushkey, obsolete, repoview
10 import peer, changegroup, subrepo, pushkey, obsolete, repoview
11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
12 import lock as lockmod
12 import lock as lockmod
13 import transaction, store, encoding, exchange, bundle2
13 import transaction, store, encoding, exchange, bundle2
14 import scmutil, util, extensions, hook, error, revset
14 import scmutil, util, extensions, hook, error, revset
15 import match as matchmod
15 import match as matchmod
16 import merge as mergemod
16 import merge as mergemod
17 import tags as tagsmod
17 import tags as tagsmod
18 from lock import release
18 from lock import release
19 import weakref, errno, os, time, inspect
19 import weakref, errno, os, time, inspect
20 import branchmap, pathutil
20 import branchmap, pathutil
21 propertycache = util.propertycache
21 propertycache = util.propertycache
22 filecache = scmutil.filecache
22 filecache = scmutil.filecache
23
23
24 class repofilecache(filecache):
24 class repofilecache(filecache):
25 """All filecache usage on repo are done for logic that should be unfiltered
25 """All filecache usage on repo are done for logic that should be unfiltered
26 """
26 """
27
27
28 def __get__(self, repo, type=None):
28 def __get__(self, repo, type=None):
29 return super(repofilecache, self).__get__(repo.unfiltered(), type)
29 return super(repofilecache, self).__get__(repo.unfiltered(), type)
30 def __set__(self, repo, value):
30 def __set__(self, repo, value):
31 return super(repofilecache, self).__set__(repo.unfiltered(), value)
31 return super(repofilecache, self).__set__(repo.unfiltered(), value)
32 def __delete__(self, repo):
32 def __delete__(self, repo):
33 return super(repofilecache, self).__delete__(repo.unfiltered())
33 return super(repofilecache, self).__delete__(repo.unfiltered())
34
34
35 class storecache(repofilecache):
35 class storecache(repofilecache):
36 """filecache for files in the store"""
36 """filecache for files in the store"""
37 def join(self, obj, fname):
37 def join(self, obj, fname):
38 return obj.sjoin(fname)
38 return obj.sjoin(fname)
39
39
40 class unfilteredpropertycache(propertycache):
40 class unfilteredpropertycache(propertycache):
41 """propertycache that apply to unfiltered repo only"""
41 """propertycache that apply to unfiltered repo only"""
42
42
43 def __get__(self, repo, type=None):
43 def __get__(self, repo, type=None):
44 unfi = repo.unfiltered()
44 unfi = repo.unfiltered()
45 if unfi is repo:
45 if unfi is repo:
46 return super(unfilteredpropertycache, self).__get__(unfi)
46 return super(unfilteredpropertycache, self).__get__(unfi)
47 return getattr(unfi, self.name)
47 return getattr(unfi, self.name)
48
48
49 class filteredpropertycache(propertycache):
49 class filteredpropertycache(propertycache):
50 """propertycache that must take filtering in account"""
50 """propertycache that must take filtering in account"""
51
51
52 def cachevalue(self, obj, value):
52 def cachevalue(self, obj, value):
53 object.__setattr__(obj, self.name, value)
53 object.__setattr__(obj, self.name, value)
54
54
55
55
56 def hasunfilteredcache(repo, name):
56 def hasunfilteredcache(repo, name):
57 """check if a repo has an unfilteredpropertycache value for <name>"""
57 """check if a repo has an unfilteredpropertycache value for <name>"""
58 return name in vars(repo.unfiltered())
58 return name in vars(repo.unfiltered())
59
59
60 def unfilteredmethod(orig):
60 def unfilteredmethod(orig):
61 """decorate method that always need to be run on unfiltered version"""
61 """decorate method that always need to be run on unfiltered version"""
62 def wrapper(repo, *args, **kwargs):
62 def wrapper(repo, *args, **kwargs):
63 return orig(repo.unfiltered(), *args, **kwargs)
63 return orig(repo.unfiltered(), *args, **kwargs)
64 return wrapper
64 return wrapper
65
65
66 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
66 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
67 'unbundle'))
67 'unbundle'))
68 legacycaps = moderncaps.union(set(['changegroupsubset']))
68 legacycaps = moderncaps.union(set(['changegroupsubset']))
69
69
70 class localpeer(peer.peerrepository):
70 class localpeer(peer.peerrepository):
71 '''peer for a local repo; reflects only the most recent API'''
71 '''peer for a local repo; reflects only the most recent API'''
72
72
73 def __init__(self, repo, caps=moderncaps):
73 def __init__(self, repo, caps=moderncaps):
74 peer.peerrepository.__init__(self)
74 peer.peerrepository.__init__(self)
75 self._repo = repo.filtered('served')
75 self._repo = repo.filtered('served')
76 self.ui = repo.ui
76 self.ui = repo.ui
77 self._caps = repo._restrictcapabilities(caps)
77 self._caps = repo._restrictcapabilities(caps)
78 self.requirements = repo.requirements
78 self.requirements = repo.requirements
79 self.supportedformats = repo.supportedformats
79 self.supportedformats = repo.supportedformats
80
80
81 def close(self):
81 def close(self):
82 self._repo.close()
82 self._repo.close()
83
83
84 def _capabilities(self):
84 def _capabilities(self):
85 return self._caps
85 return self._caps
86
86
87 def local(self):
87 def local(self):
88 return self._repo
88 return self._repo
89
89
90 def canpush(self):
90 def canpush(self):
91 return True
91 return True
92
92
93 def url(self):
93 def url(self):
94 return self._repo.url()
94 return self._repo.url()
95
95
96 def lookup(self, key):
96 def lookup(self, key):
97 return self._repo.lookup(key)
97 return self._repo.lookup(key)
98
98
99 def branchmap(self):
99 def branchmap(self):
100 return self._repo.branchmap()
100 return self._repo.branchmap()
101
101
102 def heads(self):
102 def heads(self):
103 return self._repo.heads()
103 return self._repo.heads()
104
104
105 def known(self, nodes):
105 def known(self, nodes):
106 return self._repo.known(nodes)
106 return self._repo.known(nodes)
107
107
108 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
108 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
109 format='HG10', **kwargs):
109 format='HG10', **kwargs):
110 cg = exchange.getbundle(self._repo, source, heads=heads,
110 cg = exchange.getbundle(self._repo, source, heads=heads,
111 common=common, bundlecaps=bundlecaps, **kwargs)
111 common=common, bundlecaps=bundlecaps, **kwargs)
112 if bundlecaps is not None and 'HG2Y' in bundlecaps:
112 if bundlecaps is not None and 'HG2Y' in bundlecaps:
113 # When requesting a bundle2, getbundle returns a stream to make the
113 # When requesting a bundle2, getbundle returns a stream to make the
114 # wire level function happier. We need to build a proper object
114 # wire level function happier. We need to build a proper object
115 # from it in local peer.
115 # from it in local peer.
116 cg = bundle2.unbundle20(self.ui, cg)
116 cg = bundle2.unbundle20(self.ui, cg)
117 return cg
117 return cg
118
118
119 # TODO We might want to move the next two calls into legacypeer and add
119 # TODO We might want to move the next two calls into legacypeer and add
120 # unbundle instead.
120 # unbundle instead.
121
121
122 def unbundle(self, cg, heads, url):
122 def unbundle(self, cg, heads, url):
123 """apply a bundle on a repo
123 """apply a bundle on a repo
124
124
125 This function handles the repo locking itself."""
125 This function handles the repo locking itself."""
126 try:
126 try:
127 cg = exchange.readbundle(self.ui, cg, None)
127 cg = exchange.readbundle(self.ui, cg, None)
128 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
128 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
129 if util.safehasattr(ret, 'getchunks'):
129 if util.safehasattr(ret, 'getchunks'):
130 # This is a bundle20 object, turn it into an unbundler.
130 # This is a bundle20 object, turn it into an unbundler.
131 # This little dance should be dropped eventually when the API
131 # This little dance should be dropped eventually when the API
132 # is finally improved.
132 # is finally improved.
133 stream = util.chunkbuffer(ret.getchunks())
133 stream = util.chunkbuffer(ret.getchunks())
134 ret = bundle2.unbundle20(self.ui, stream)
134 ret = bundle2.unbundle20(self.ui, stream)
135 return ret
135 return ret
136 except error.PushRaced, exc:
136 except error.PushRaced, exc:
137 raise error.ResponseError(_('push failed:'), str(exc))
137 raise error.ResponseError(_('push failed:'), str(exc))
138
138
139 def lock(self):
139 def lock(self):
140 return self._repo.lock()
140 return self._repo.lock()
141
141
142 def addchangegroup(self, cg, source, url):
142 def addchangegroup(self, cg, source, url):
143 return changegroup.addchangegroup(self._repo, cg, source, url)
143 return changegroup.addchangegroup(self._repo, cg, source, url)
144
144
145 def pushkey(self, namespace, key, old, new):
145 def pushkey(self, namespace, key, old, new):
146 return self._repo.pushkey(namespace, key, old, new)
146 return self._repo.pushkey(namespace, key, old, new)
147
147
148 def listkeys(self, namespace):
148 def listkeys(self, namespace):
149 return self._repo.listkeys(namespace)
149 return self._repo.listkeys(namespace)
150
150
151 def debugwireargs(self, one, two, three=None, four=None, five=None):
151 def debugwireargs(self, one, two, three=None, four=None, five=None):
152 '''used to test argument passing over the wire'''
152 '''used to test argument passing over the wire'''
153 return "%s %s %s %s %s" % (one, two, three, four, five)
153 return "%s %s %s %s %s" % (one, two, three, four, five)
154
154
155 class locallegacypeer(localpeer):
155 class locallegacypeer(localpeer):
156 '''peer extension which implements legacy methods too; used for tests with
156 '''peer extension which implements legacy methods too; used for tests with
157 restricted capabilities'''
157 restricted capabilities'''
158
158
159 def __init__(self, repo):
159 def __init__(self, repo):
160 localpeer.__init__(self, repo, caps=legacycaps)
160 localpeer.__init__(self, repo, caps=legacycaps)
161
161
162 def branches(self, nodes):
162 def branches(self, nodes):
163 return self._repo.branches(nodes)
163 return self._repo.branches(nodes)
164
164
165 def between(self, pairs):
165 def between(self, pairs):
166 return self._repo.between(pairs)
166 return self._repo.between(pairs)
167
167
168 def changegroup(self, basenodes, source):
168 def changegroup(self, basenodes, source):
169 return changegroup.changegroup(self._repo, basenodes, source)
169 return changegroup.changegroup(self._repo, basenodes, source)
170
170
171 def changegroupsubset(self, bases, heads, source):
171 def changegroupsubset(self, bases, heads, source):
172 return changegroup.changegroupsubset(self._repo, bases, heads, source)
172 return changegroup.changegroupsubset(self._repo, bases, heads, source)
173
173
174 class localrepository(object):
174 class localrepository(object):
175
175
176 supportedformats = set(('revlogv1', 'generaldelta'))
176 supportedformats = set(('revlogv1', 'generaldelta'))
177 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
177 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
178 'dotencode'))
178 'dotencode'))
179 openerreqs = set(('revlogv1', 'generaldelta'))
179 openerreqs = set(('revlogv1', 'generaldelta'))
180 requirements = ['revlogv1']
180 requirements = ['revlogv1']
181 filtername = None
181 filtername = None
182
182
183 # a list of (ui, featureset) functions.
183 # a list of (ui, featureset) functions.
184 # only functions defined in module of enabled extensions are invoked
184 # only functions defined in module of enabled extensions are invoked
185 featuresetupfuncs = set()
185 featuresetupfuncs = set()
186
186
187 def _baserequirements(self, create):
187 def _baserequirements(self, create):
188 return self.requirements[:]
188 return self.requirements[:]
189
189
190 def __init__(self, baseui, path=None, create=False):
190 def __init__(self, baseui, path=None, create=False):
191 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
191 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
192 self.wopener = self.wvfs
192 self.wopener = self.wvfs
193 self.root = self.wvfs.base
193 self.root = self.wvfs.base
194 self.path = self.wvfs.join(".hg")
194 self.path = self.wvfs.join(".hg")
195 self.origroot = path
195 self.origroot = path
196 self.auditor = pathutil.pathauditor(self.root, self._checknested)
196 self.auditor = pathutil.pathauditor(self.root, self._checknested)
197 self.vfs = scmutil.vfs(self.path)
197 self.vfs = scmutil.vfs(self.path)
198 self.opener = self.vfs
198 self.opener = self.vfs
199 self.baseui = baseui
199 self.baseui = baseui
200 self.ui = baseui.copy()
200 self.ui = baseui.copy()
201 self.ui.copy = baseui.copy # prevent copying repo configuration
201 self.ui.copy = baseui.copy # prevent copying repo configuration
202 # A list of callback to shape the phase if no data were found.
202 # A list of callback to shape the phase if no data were found.
203 # Callback are in the form: func(repo, roots) --> processed root.
203 # Callback are in the form: func(repo, roots) --> processed root.
204 # This list it to be filled by extension during repo setup
204 # This list it to be filled by extension during repo setup
205 self._phasedefaults = []
205 self._phasedefaults = []
206 try:
206 try:
207 self.ui.readconfig(self.join("hgrc"), self.root)
207 self.ui.readconfig(self.join("hgrc"), self.root)
208 extensions.loadall(self.ui)
208 extensions.loadall(self.ui)
209 except IOError:
209 except IOError:
210 pass
210 pass
211
211
212 if self.featuresetupfuncs:
212 if self.featuresetupfuncs:
213 self.supported = set(self._basesupported) # use private copy
213 self.supported = set(self._basesupported) # use private copy
214 extmods = set(m.__name__ for n, m
214 extmods = set(m.__name__ for n, m
215 in extensions.extensions(self.ui))
215 in extensions.extensions(self.ui))
216 for setupfunc in self.featuresetupfuncs:
216 for setupfunc in self.featuresetupfuncs:
217 if setupfunc.__module__ in extmods:
217 if setupfunc.__module__ in extmods:
218 setupfunc(self.ui, self.supported)
218 setupfunc(self.ui, self.supported)
219 else:
219 else:
220 self.supported = self._basesupported
220 self.supported = self._basesupported
221
221
222 if not self.vfs.isdir():
222 if not self.vfs.isdir():
223 if create:
223 if create:
224 if not self.wvfs.exists():
224 if not self.wvfs.exists():
225 self.wvfs.makedirs()
225 self.wvfs.makedirs()
226 self.vfs.makedir(notindexed=True)
226 self.vfs.makedir(notindexed=True)
227 requirements = self._baserequirements(create)
227 requirements = self._baserequirements(create)
228 if self.ui.configbool('format', 'usestore', True):
228 if self.ui.configbool('format', 'usestore', True):
229 self.vfs.mkdir("store")
229 self.vfs.mkdir("store")
230 requirements.append("store")
230 requirements.append("store")
231 if self.ui.configbool('format', 'usefncache', True):
231 if self.ui.configbool('format', 'usefncache', True):
232 requirements.append("fncache")
232 requirements.append("fncache")
233 if self.ui.configbool('format', 'dotencode', True):
233 if self.ui.configbool('format', 'dotencode', True):
234 requirements.append('dotencode')
234 requirements.append('dotencode')
235 # create an invalid changelog
235 # create an invalid changelog
236 self.vfs.append(
236 self.vfs.append(
237 "00changelog.i",
237 "00changelog.i",
238 '\0\0\0\2' # represents revlogv2
238 '\0\0\0\2' # represents revlogv2
239 ' dummy changelog to prevent using the old repo layout'
239 ' dummy changelog to prevent using the old repo layout'
240 )
240 )
241 if self.ui.configbool('format', 'generaldelta', False):
241 if self.ui.configbool('format', 'generaldelta', False):
242 requirements.append("generaldelta")
242 requirements.append("generaldelta")
243 requirements = set(requirements)
243 requirements = set(requirements)
244 else:
244 else:
245 raise error.RepoError(_("repository %s not found") % path)
245 raise error.RepoError(_("repository %s not found") % path)
246 elif create:
246 elif create:
247 raise error.RepoError(_("repository %s already exists") % path)
247 raise error.RepoError(_("repository %s already exists") % path)
248 else:
248 else:
249 try:
249 try:
250 requirements = scmutil.readrequires(self.vfs, self.supported)
250 requirements = scmutil.readrequires(self.vfs, self.supported)
251 except IOError, inst:
251 except IOError, inst:
252 if inst.errno != errno.ENOENT:
252 if inst.errno != errno.ENOENT:
253 raise
253 raise
254 requirements = set()
254 requirements = set()
255
255
256 self.sharedpath = self.path
256 self.sharedpath = self.path
257 try:
257 try:
258 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
258 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
259 realpath=True)
259 realpath=True)
260 s = vfs.base
260 s = vfs.base
261 if not vfs.exists():
261 if not vfs.exists():
262 raise error.RepoError(
262 raise error.RepoError(
263 _('.hg/sharedpath points to nonexistent directory %s') % s)
263 _('.hg/sharedpath points to nonexistent directory %s') % s)
264 self.sharedpath = s
264 self.sharedpath = s
265 except IOError, inst:
265 except IOError, inst:
266 if inst.errno != errno.ENOENT:
266 if inst.errno != errno.ENOENT:
267 raise
267 raise
268
268
269 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
269 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
270 self.spath = self.store.path
270 self.spath = self.store.path
271 self.svfs = self.store.vfs
271 self.svfs = self.store.vfs
272 self.sopener = self.svfs
272 self.sopener = self.svfs
273 self.sjoin = self.store.join
273 self.sjoin = self.store.join
274 self.vfs.createmode = self.store.createmode
274 self.vfs.createmode = self.store.createmode
275 self._applyrequirements(requirements)
275 self._applyrequirements(requirements)
276 if create:
276 if create:
277 self._writerequirements()
277 self._writerequirements()
278
278
279
279
280 self._branchcaches = {}
280 self._branchcaches = {}
281 self.filterpats = {}
281 self.filterpats = {}
282 self._datafilters = {}
282 self._datafilters = {}
283 self._transref = self._lockref = self._wlockref = None
283 self._transref = self._lockref = self._wlockref = None
284
284
285 # A cache for various files under .hg/ that tracks file changes,
285 # A cache for various files under .hg/ that tracks file changes,
286 # (used by the filecache decorator)
286 # (used by the filecache decorator)
287 #
287 #
288 # Maps a property name to its util.filecacheentry
288 # Maps a property name to its util.filecacheentry
289 self._filecache = {}
289 self._filecache = {}
290
290
291 # hold sets of revision to be filtered
291 # hold sets of revision to be filtered
292 # should be cleared when something might have changed the filter value:
292 # should be cleared when something might have changed the filter value:
293 # - new changesets,
293 # - new changesets,
294 # - phase change,
294 # - phase change,
295 # - new obsolescence marker,
295 # - new obsolescence marker,
296 # - working directory parent change,
296 # - working directory parent change,
297 # - bookmark changes
297 # - bookmark changes
298 self.filteredrevcache = {}
298 self.filteredrevcache = {}
299
299
300 def close(self):
300 def close(self):
301 pass
301 pass
302
302
303 def _restrictcapabilities(self, caps):
303 def _restrictcapabilities(self, caps):
304 # bundle2 is not ready for prime time, drop it unless explicitly
304 # bundle2 is not ready for prime time, drop it unless explicitly
305 # required by the tests (or some brave tester)
305 # required by the tests (or some brave tester)
306 if self.ui.configbool('experimental', 'bundle2-exp', False):
306 if self.ui.configbool('experimental', 'bundle2-exp', False):
307 caps = set(caps)
307 caps = set(caps)
308 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
308 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
309 caps.add('bundle2-exp=' + urllib.quote(capsblob))
309 caps.add('bundle2-exp=' + urllib.quote(capsblob))
310 return caps
310 return caps
311
311
312 def _applyrequirements(self, requirements):
312 def _applyrequirements(self, requirements):
313 self.requirements = requirements
313 self.requirements = requirements
314 self.sopener.options = dict((r, 1) for r in requirements
314 self.sopener.options = dict((r, 1) for r in requirements
315 if r in self.openerreqs)
315 if r in self.openerreqs)
316 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
316 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
317 if chunkcachesize is not None:
317 if chunkcachesize is not None:
318 self.sopener.options['chunkcachesize'] = chunkcachesize
318 self.sopener.options['chunkcachesize'] = chunkcachesize
319
319
320 def _writerequirements(self):
320 def _writerequirements(self):
321 reqfile = self.opener("requires", "w")
321 reqfile = self.opener("requires", "w")
322 for r in sorted(self.requirements):
322 for r in sorted(self.requirements):
323 reqfile.write("%s\n" % r)
323 reqfile.write("%s\n" % r)
324 reqfile.close()
324 reqfile.close()
325
325
326 def _checknested(self, path):
326 def _checknested(self, path):
327 """Determine if path is a legal nested repository."""
327 """Determine if path is a legal nested repository."""
328 if not path.startswith(self.root):
328 if not path.startswith(self.root):
329 return False
329 return False
330 subpath = path[len(self.root) + 1:]
330 subpath = path[len(self.root) + 1:]
331 normsubpath = util.pconvert(subpath)
331 normsubpath = util.pconvert(subpath)
332
332
333 # XXX: Checking against the current working copy is wrong in
333 # XXX: Checking against the current working copy is wrong in
334 # the sense that it can reject things like
334 # the sense that it can reject things like
335 #
335 #
336 # $ hg cat -r 10 sub/x.txt
336 # $ hg cat -r 10 sub/x.txt
337 #
337 #
338 # if sub/ is no longer a subrepository in the working copy
338 # if sub/ is no longer a subrepository in the working copy
339 # parent revision.
339 # parent revision.
340 #
340 #
341 # However, it can of course also allow things that would have
341 # However, it can of course also allow things that would have
342 # been rejected before, such as the above cat command if sub/
342 # been rejected before, such as the above cat command if sub/
343 # is a subrepository now, but was a normal directory before.
343 # is a subrepository now, but was a normal directory before.
344 # The old path auditor would have rejected by mistake since it
344 # The old path auditor would have rejected by mistake since it
345 # panics when it sees sub/.hg/.
345 # panics when it sees sub/.hg/.
346 #
346 #
347 # All in all, checking against the working copy seems sensible
347 # All in all, checking against the working copy seems sensible
348 # since we want to prevent access to nested repositories on
348 # since we want to prevent access to nested repositories on
349 # the filesystem *now*.
349 # the filesystem *now*.
350 ctx = self[None]
350 ctx = self[None]
351 parts = util.splitpath(subpath)
351 parts = util.splitpath(subpath)
352 while parts:
352 while parts:
353 prefix = '/'.join(parts)
353 prefix = '/'.join(parts)
354 if prefix in ctx.substate:
354 if prefix in ctx.substate:
355 if prefix == normsubpath:
355 if prefix == normsubpath:
356 return True
356 return True
357 else:
357 else:
358 sub = ctx.sub(prefix)
358 sub = ctx.sub(prefix)
359 return sub.checknested(subpath[len(prefix) + 1:])
359 return sub.checknested(subpath[len(prefix) + 1:])
360 else:
360 else:
361 parts.pop()
361 parts.pop()
362 return False
362 return False
363
363
364 def peer(self):
364 def peer(self):
365 return localpeer(self) # not cached to avoid reference cycle
365 return localpeer(self) # not cached to avoid reference cycle
366
366
367 def unfiltered(self):
367 def unfiltered(self):
368 """Return unfiltered version of the repository
368 """Return unfiltered version of the repository
369
369
370 Intended to be overwritten by filtered repo."""
370 Intended to be overwritten by filtered repo."""
371 return self
371 return self
372
372
373 def filtered(self, name):
373 def filtered(self, name):
374 """Return a filtered version of a repository"""
374 """Return a filtered version of a repository"""
375 # build a new class with the mixin and the current class
375 # build a new class with the mixin and the current class
376 # (possibly subclass of the repo)
376 # (possibly subclass of the repo)
377 class proxycls(repoview.repoview, self.unfiltered().__class__):
377 class proxycls(repoview.repoview, self.unfiltered().__class__):
378 pass
378 pass
379 return proxycls(self, name)
379 return proxycls(self, name)
380
380
381 @repofilecache('bookmarks')
381 @repofilecache('bookmarks')
382 def _bookmarks(self):
382 def _bookmarks(self):
383 return bookmarks.bmstore(self)
383 return bookmarks.bmstore(self)
384
384
385 @repofilecache('bookmarks.current')
385 @repofilecache('bookmarks.current')
386 def _bookmarkcurrent(self):
386 def _bookmarkcurrent(self):
387 return bookmarks.readcurrent(self)
387 return bookmarks.readcurrent(self)
388
388
389 def bookmarkheads(self, bookmark):
389 def bookmarkheads(self, bookmark):
390 name = bookmark.split('@', 1)[0]
390 name = bookmark.split('@', 1)[0]
391 heads = []
391 heads = []
392 for mark, n in self._bookmarks.iteritems():
392 for mark, n in self._bookmarks.iteritems():
393 if mark.split('@', 1)[0] == name:
393 if mark.split('@', 1)[0] == name:
394 heads.append(n)
394 heads.append(n)
395 return heads
395 return heads
396
396
397 @storecache('phaseroots')
397 @storecache('phaseroots')
398 def _phasecache(self):
398 def _phasecache(self):
399 return phases.phasecache(self, self._phasedefaults)
399 return phases.phasecache(self, self._phasedefaults)
400
400
401 @storecache('obsstore')
401 @storecache('obsstore')
402 def obsstore(self):
402 def obsstore(self):
403 # read default format for new obsstore.
403 # read default format for new obsstore.
404 defaultformat = self.ui.configint('format', 'obsstore-version', None)
404 defaultformat = self.ui.configint('format', 'obsstore-version', None)
405 # rely on obsstore class default when possible.
405 # rely on obsstore class default when possible.
406 kwargs = {}
406 kwargs = {}
407 if defaultformat is not None:
407 if defaultformat is not None:
408 kwargs['defaultformat'] = defaultformat
408 kwargs['defaultformat'] = defaultformat
409 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
409 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
410 store = obsolete.obsstore(self.sopener, readonly=readonly,
410 store = obsolete.obsstore(self.sopener, readonly=readonly,
411 **kwargs)
411 **kwargs)
412 if store and readonly:
412 if store and readonly:
413 # message is rare enough to not be translated
413 # message is rare enough to not be translated
414 msg = 'obsolete feature not enabled but %i markers found!\n'
414 msg = 'obsolete feature not enabled but %i markers found!\n'
415 self.ui.warn(msg % len(list(store)))
415 self.ui.warn(msg % len(list(store)))
416 return store
416 return store
417
417
418 @storecache('00changelog.i')
418 @storecache('00changelog.i')
419 def changelog(self):
419 def changelog(self):
420 c = changelog.changelog(self.sopener)
420 c = changelog.changelog(self.sopener)
421 if 'HG_PENDING' in os.environ:
421 if 'HG_PENDING' in os.environ:
422 p = os.environ['HG_PENDING']
422 p = os.environ['HG_PENDING']
423 if p.startswith(self.root):
423 if p.startswith(self.root):
424 c.readpending('00changelog.i.a')
424 c.readpending('00changelog.i.a')
425 return c
425 return c
426
426
427 @storecache('00manifest.i')
427 @storecache('00manifest.i')
428 def manifest(self):
428 def manifest(self):
429 return manifest.manifest(self.sopener)
429 return manifest.manifest(self.sopener)
430
430
431 @repofilecache('dirstate')
431 @repofilecache('dirstate')
432 def dirstate(self):
432 def dirstate(self):
433 warned = [0]
433 warned = [0]
434 def validate(node):
434 def validate(node):
435 try:
435 try:
436 self.changelog.rev(node)
436 self.changelog.rev(node)
437 return node
437 return node
438 except error.LookupError:
438 except error.LookupError:
439 if not warned[0]:
439 if not warned[0]:
440 warned[0] = True
440 warned[0] = True
441 self.ui.warn(_("warning: ignoring unknown"
441 self.ui.warn(_("warning: ignoring unknown"
442 " working parent %s!\n") % short(node))
442 " working parent %s!\n") % short(node))
443 return nullid
443 return nullid
444
444
445 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
445 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
446
446
447 def __getitem__(self, changeid):
447 def __getitem__(self, changeid):
448 if changeid is None:
448 if changeid is None:
449 return context.workingctx(self)
449 return context.workingctx(self)
450 return context.changectx(self, changeid)
450 return context.changectx(self, changeid)
451
451
452 def __contains__(self, changeid):
452 def __contains__(self, changeid):
453 try:
453 try:
454 return bool(self.lookup(changeid))
454 return bool(self.lookup(changeid))
455 except error.RepoLookupError:
455 except error.RepoLookupError:
456 return False
456 return False
457
457
458 def __nonzero__(self):
458 def __nonzero__(self):
459 return True
459 return True
460
460
461 def __len__(self):
461 def __len__(self):
462 return len(self.changelog)
462 return len(self.changelog)
463
463
464 def __iter__(self):
464 def __iter__(self):
465 return iter(self.changelog)
465 return iter(self.changelog)
466
466
467 def revs(self, expr, *args):
467 def revs(self, expr, *args):
468 '''Return a list of revisions matching the given revset'''
468 '''Return a list of revisions matching the given revset'''
469 expr = revset.formatspec(expr, *args)
469 expr = revset.formatspec(expr, *args)
470 m = revset.match(None, expr)
470 m = revset.match(None, expr)
471 return m(self, revset.spanset(self))
471 return m(self, revset.spanset(self))
472
472
473 def set(self, expr, *args):
473 def set(self, expr, *args):
474 '''
474 '''
475 Yield a context for each matching revision, after doing arg
475 Yield a context for each matching revision, after doing arg
476 replacement via revset.formatspec
476 replacement via revset.formatspec
477 '''
477 '''
478 for r in self.revs(expr, *args):
478 for r in self.revs(expr, *args):
479 yield self[r]
479 yield self[r]
480
480
481 def url(self):
481 def url(self):
482 return 'file:' + self.root
482 return 'file:' + self.root
483
483
484 def hook(self, name, throw=False, **args):
484 def hook(self, name, throw=False, **args):
485 """Call a hook, passing this repo instance.
485 """Call a hook, passing this repo instance.
486
486
487 This a convenience method to aid invoking hooks. Extensions likely
487 This a convenience method to aid invoking hooks. Extensions likely
488 won't call this unless they have registered a custom hook or are
488 won't call this unless they have registered a custom hook or are
489 replacing code that is expected to call a hook.
489 replacing code that is expected to call a hook.
490 """
490 """
491 return hook.hook(self.ui, self, name, throw, **args)
491 return hook.hook(self.ui, self, name, throw, **args)
492
492
493 @unfilteredmethod
493 @unfilteredmethod
494 def _tag(self, names, node, message, local, user, date, extra={},
494 def _tag(self, names, node, message, local, user, date, extra={},
495 editor=False):
495 editor=False):
496 if isinstance(names, str):
496 if isinstance(names, str):
497 names = (names,)
497 names = (names,)
498
498
499 branches = self.branchmap()
499 branches = self.branchmap()
500 for name in names:
500 for name in names:
501 self.hook('pretag', throw=True, node=hex(node), tag=name,
501 self.hook('pretag', throw=True, node=hex(node), tag=name,
502 local=local)
502 local=local)
503 if name in branches:
503 if name in branches:
504 self.ui.warn(_("warning: tag %s conflicts with existing"
504 self.ui.warn(_("warning: tag %s conflicts with existing"
505 " branch name\n") % name)
505 " branch name\n") % name)
506
506
507 def writetags(fp, names, munge, prevtags):
507 def writetags(fp, names, munge, prevtags):
508 fp.seek(0, 2)
508 fp.seek(0, 2)
509 if prevtags and prevtags[-1] != '\n':
509 if prevtags and prevtags[-1] != '\n':
510 fp.write('\n')
510 fp.write('\n')
511 for name in names:
511 for name in names:
512 m = munge and munge(name) or name
512 m = munge and munge(name) or name
513 if (self._tagscache.tagtypes and
513 if (self._tagscache.tagtypes and
514 name in self._tagscache.tagtypes):
514 name in self._tagscache.tagtypes):
515 old = self.tags().get(name, nullid)
515 old = self.tags().get(name, nullid)
516 fp.write('%s %s\n' % (hex(old), m))
516 fp.write('%s %s\n' % (hex(old), m))
517 fp.write('%s %s\n' % (hex(node), m))
517 fp.write('%s %s\n' % (hex(node), m))
518 fp.close()
518 fp.close()
519
519
520 prevtags = ''
520 prevtags = ''
521 if local:
521 if local:
522 try:
522 try:
523 fp = self.opener('localtags', 'r+')
523 fp = self.opener('localtags', 'r+')
524 except IOError:
524 except IOError:
525 fp = self.opener('localtags', 'a')
525 fp = self.opener('localtags', 'a')
526 else:
526 else:
527 prevtags = fp.read()
527 prevtags = fp.read()
528
528
529 # local tags are stored in the current charset
529 # local tags are stored in the current charset
530 writetags(fp, names, None, prevtags)
530 writetags(fp, names, None, prevtags)
531 for name in names:
531 for name in names:
532 self.hook('tag', node=hex(node), tag=name, local=local)
532 self.hook('tag', node=hex(node), tag=name, local=local)
533 return
533 return
534
534
535 try:
535 try:
536 fp = self.wfile('.hgtags', 'rb+')
536 fp = self.wfile('.hgtags', 'rb+')
537 except IOError, e:
537 except IOError, e:
538 if e.errno != errno.ENOENT:
538 if e.errno != errno.ENOENT:
539 raise
539 raise
540 fp = self.wfile('.hgtags', 'ab')
540 fp = self.wfile('.hgtags', 'ab')
541 else:
541 else:
542 prevtags = fp.read()
542 prevtags = fp.read()
543
543
544 # committed tags are stored in UTF-8
544 # committed tags are stored in UTF-8
545 writetags(fp, names, encoding.fromlocal, prevtags)
545 writetags(fp, names, encoding.fromlocal, prevtags)
546
546
547 fp.close()
547 fp.close()
548
548
549 self.invalidatecaches()
549 self.invalidatecaches()
550
550
551 if '.hgtags' not in self.dirstate:
551 if '.hgtags' not in self.dirstate:
552 self[None].add(['.hgtags'])
552 self[None].add(['.hgtags'])
553
553
554 m = matchmod.exact(self.root, '', ['.hgtags'])
554 m = matchmod.exact(self.root, '', ['.hgtags'])
555 tagnode = self.commit(message, user, date, extra=extra, match=m,
555 tagnode = self.commit(message, user, date, extra=extra, match=m,
556 editor=editor)
556 editor=editor)
557
557
558 for name in names:
558 for name in names:
559 self.hook('tag', node=hex(node), tag=name, local=local)
559 self.hook('tag', node=hex(node), tag=name, local=local)
560
560
561 return tagnode
561 return tagnode
562
562
563 def tag(self, names, node, message, local, user, date, editor=False):
563 def tag(self, names, node, message, local, user, date, editor=False):
564 '''tag a revision with one or more symbolic names.
564 '''tag a revision with one or more symbolic names.
565
565
566 names is a list of strings or, when adding a single tag, names may be a
566 names is a list of strings or, when adding a single tag, names may be a
567 string.
567 string.
568
568
569 if local is True, the tags are stored in a per-repository file.
569 if local is True, the tags are stored in a per-repository file.
570 otherwise, they are stored in the .hgtags file, and a new
570 otherwise, they are stored in the .hgtags file, and a new
571 changeset is committed with the change.
571 changeset is committed with the change.
572
572
573 keyword arguments:
573 keyword arguments:
574
574
575 local: whether to store tags in non-version-controlled file
575 local: whether to store tags in non-version-controlled file
576 (default False)
576 (default False)
577
577
578 message: commit message to use if committing
578 message: commit message to use if committing
579
579
580 user: name of user to use if committing
580 user: name of user to use if committing
581
581
582 date: date tuple to use if committing'''
582 date: date tuple to use if committing'''
583
583
584 if not local:
584 if not local:
585 m = matchmod.exact(self.root, '', ['.hgtags'])
585 m = matchmod.exact(self.root, '', ['.hgtags'])
586 if util.any(self.status(match=m, unknown=True, ignored=True)):
586 if util.any(self.status(match=m, unknown=True, ignored=True)):
587 raise util.Abort(_('working copy of .hgtags is changed'),
587 raise util.Abort(_('working copy of .hgtags is changed'),
588 hint=_('please commit .hgtags manually'))
588 hint=_('please commit .hgtags manually'))
589
589
590 self.tags() # instantiate the cache
590 self.tags() # instantiate the cache
591 self._tag(names, node, message, local, user, date, editor=editor)
591 self._tag(names, node, message, local, user, date, editor=editor)
592
592
593 @filteredpropertycache
593 @filteredpropertycache
594 def _tagscache(self):
594 def _tagscache(self):
595 '''Returns a tagscache object that contains various tags related
595 '''Returns a tagscache object that contains various tags related
596 caches.'''
596 caches.'''
597
597
598 # This simplifies its cache management by having one decorated
598 # This simplifies its cache management by having one decorated
599 # function (this one) and the rest simply fetch things from it.
599 # function (this one) and the rest simply fetch things from it.
600 class tagscache(object):
600 class tagscache(object):
601 def __init__(self):
601 def __init__(self):
602 # These two define the set of tags for this repository. tags
602 # These two define the set of tags for this repository. tags
603 # maps tag name to node; tagtypes maps tag name to 'global' or
603 # maps tag name to node; tagtypes maps tag name to 'global' or
604 # 'local'. (Global tags are defined by .hgtags across all
604 # 'local'. (Global tags are defined by .hgtags across all
605 # heads, and local tags are defined in .hg/localtags.)
605 # heads, and local tags are defined in .hg/localtags.)
606 # They constitute the in-memory cache of tags.
606 # They constitute the in-memory cache of tags.
607 self.tags = self.tagtypes = None
607 self.tags = self.tagtypes = None
608
608
609 self.nodetagscache = self.tagslist = None
609 self.nodetagscache = self.tagslist = None
610
610
611 cache = tagscache()
611 cache = tagscache()
612 cache.tags, cache.tagtypes = self._findtags()
612 cache.tags, cache.tagtypes = self._findtags()
613
613
614 return cache
614 return cache
615
615
616 def tags(self):
616 def tags(self):
617 '''return a mapping of tag to node'''
617 '''return a mapping of tag to node'''
618 t = {}
618 t = {}
619 if self.changelog.filteredrevs:
619 if self.changelog.filteredrevs:
620 tags, tt = self._findtags()
620 tags, tt = self._findtags()
621 else:
621 else:
622 tags = self._tagscache.tags
622 tags = self._tagscache.tags
623 for k, v in tags.iteritems():
623 for k, v in tags.iteritems():
624 try:
624 try:
625 # ignore tags to unknown nodes
625 # ignore tags to unknown nodes
626 self.changelog.rev(v)
626 self.changelog.rev(v)
627 t[k] = v
627 t[k] = v
628 except (error.LookupError, ValueError):
628 except (error.LookupError, ValueError):
629 pass
629 pass
630 return t
630 return t
631
631
632 def _findtags(self):
632 def _findtags(self):
633 '''Do the hard work of finding tags. Return a pair of dicts
633 '''Do the hard work of finding tags. Return a pair of dicts
634 (tags, tagtypes) where tags maps tag name to node, and tagtypes
634 (tags, tagtypes) where tags maps tag name to node, and tagtypes
635 maps tag name to a string like \'global\' or \'local\'.
635 maps tag name to a string like \'global\' or \'local\'.
636 Subclasses or extensions are free to add their own tags, but
636 Subclasses or extensions are free to add their own tags, but
637 should be aware that the returned dicts will be retained for the
637 should be aware that the returned dicts will be retained for the
638 duration of the localrepo object.'''
638 duration of the localrepo object.'''
639
639
640 # XXX what tagtype should subclasses/extensions use? Currently
640 # XXX what tagtype should subclasses/extensions use? Currently
641 # mq and bookmarks add tags, but do not set the tagtype at all.
641 # mq and bookmarks add tags, but do not set the tagtype at all.
642 # Should each extension invent its own tag type? Should there
642 # Should each extension invent its own tag type? Should there
643 # be one tagtype for all such "virtual" tags? Or is the status
643 # be one tagtype for all such "virtual" tags? Or is the status
644 # quo fine?
644 # quo fine?
645
645
646 alltags = {} # map tag name to (node, hist)
646 alltags = {} # map tag name to (node, hist)
647 tagtypes = {}
647 tagtypes = {}
648
648
649 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
649 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
650 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
650 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
651
651
652 # Build the return dicts. Have to re-encode tag names because
652 # Build the return dicts. Have to re-encode tag names because
653 # the tags module always uses UTF-8 (in order not to lose info
653 # the tags module always uses UTF-8 (in order not to lose info
654 # writing to the cache), but the rest of Mercurial wants them in
654 # writing to the cache), but the rest of Mercurial wants them in
655 # local encoding.
655 # local encoding.
656 tags = {}
656 tags = {}
657 for (name, (node, hist)) in alltags.iteritems():
657 for (name, (node, hist)) in alltags.iteritems():
658 if node != nullid:
658 if node != nullid:
659 tags[encoding.tolocal(name)] = node
659 tags[encoding.tolocal(name)] = node
660 tags['tip'] = self.changelog.tip()
660 tags['tip'] = self.changelog.tip()
661 tagtypes = dict([(encoding.tolocal(name), value)
661 tagtypes = dict([(encoding.tolocal(name), value)
662 for (name, value) in tagtypes.iteritems()])
662 for (name, value) in tagtypes.iteritems()])
663 return (tags, tagtypes)
663 return (tags, tagtypes)
664
664
665 def tagtype(self, tagname):
665 def tagtype(self, tagname):
666 '''
666 '''
667 return the type of the given tag. result can be:
667 return the type of the given tag. result can be:
668
668
669 'local' : a local tag
669 'local' : a local tag
670 'global' : a global tag
670 'global' : a global tag
671 None : tag does not exist
671 None : tag does not exist
672 '''
672 '''
673
673
674 return self._tagscache.tagtypes.get(tagname)
674 return self._tagscache.tagtypes.get(tagname)
675
675
676 def tagslist(self):
676 def tagslist(self):
677 '''return a list of tags ordered by revision'''
677 '''return a list of tags ordered by revision'''
678 if not self._tagscache.tagslist:
678 if not self._tagscache.tagslist:
679 l = []
679 l = []
680 for t, n in self.tags().iteritems():
680 for t, n in self.tags().iteritems():
681 l.append((self.changelog.rev(n), t, n))
681 l.append((self.changelog.rev(n), t, n))
682 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
682 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
683
683
684 return self._tagscache.tagslist
684 return self._tagscache.tagslist
685
685
686 def nodetags(self, node):
686 def nodetags(self, node):
687 '''return the tags associated with a node'''
687 '''return the tags associated with a node'''
688 if not self._tagscache.nodetagscache:
688 if not self._tagscache.nodetagscache:
689 nodetagscache = {}
689 nodetagscache = {}
690 for t, n in self._tagscache.tags.iteritems():
690 for t, n in self._tagscache.tags.iteritems():
691 nodetagscache.setdefault(n, []).append(t)
691 nodetagscache.setdefault(n, []).append(t)
692 for tags in nodetagscache.itervalues():
692 for tags in nodetagscache.itervalues():
693 tags.sort()
693 tags.sort()
694 self._tagscache.nodetagscache = nodetagscache
694 self._tagscache.nodetagscache = nodetagscache
695 return self._tagscache.nodetagscache.get(node, [])
695 return self._tagscache.nodetagscache.get(node, [])
696
696
697 def nodebookmarks(self, node):
697 def nodebookmarks(self, node):
698 marks = []
698 marks = []
699 for bookmark, n in self._bookmarks.iteritems():
699 for bookmark, n in self._bookmarks.iteritems():
700 if n == node:
700 if n == node:
701 marks.append(bookmark)
701 marks.append(bookmark)
702 return sorted(marks)
702 return sorted(marks)
703
703
704 def branchmap(self):
704 def branchmap(self):
705 '''returns a dictionary {branch: [branchheads]} with branchheads
705 '''returns a dictionary {branch: [branchheads]} with branchheads
706 ordered by increasing revision number'''
706 ordered by increasing revision number'''
707 branchmap.updatecache(self)
707 branchmap.updatecache(self)
708 return self._branchcaches[self.filtername]
708 return self._branchcaches[self.filtername]
709
709
710 def branchtip(self, branch):
710 def branchtip(self, branch):
711 '''return the tip node for a given branch'''
711 '''return the tip node for a given branch'''
712 try:
712 try:
713 return self.branchmap().branchtip(branch)
713 return self.branchmap().branchtip(branch)
714 except KeyError:
714 except KeyError:
715 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
715 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
716
716
717 def lookup(self, key):
717 def lookup(self, key):
718 return self[key].node()
718 return self[key].node()
719
719
720 def lookupbranch(self, key, remote=None):
720 def lookupbranch(self, key, remote=None):
721 repo = remote or self
721 repo = remote or self
722 if key in repo.branchmap():
722 if key in repo.branchmap():
723 return key
723 return key
724
724
725 repo = (remote and remote.local()) and remote or self
725 repo = (remote and remote.local()) and remote or self
726 return repo[key].branch()
726 return repo[key].branch()
727
727
728 def known(self, nodes):
728 def known(self, nodes):
729 nm = self.changelog.nodemap
729 nm = self.changelog.nodemap
730 pc = self._phasecache
730 pc = self._phasecache
731 result = []
731 result = []
732 for n in nodes:
732 for n in nodes:
733 r = nm.get(n)
733 r = nm.get(n)
734 resp = not (r is None or pc.phase(self, r) >= phases.secret)
734 resp = not (r is None or pc.phase(self, r) >= phases.secret)
735 result.append(resp)
735 result.append(resp)
736 return result
736 return result
737
737
738 def local(self):
738 def local(self):
739 return self
739 return self
740
740
741 def cancopy(self):
741 def cancopy(self):
742 # so statichttprepo's override of local() works
742 # so statichttprepo's override of local() works
743 if not self.local():
743 if not self.local():
744 return False
744 return False
745 if not self.ui.configbool('phases', 'publish', True):
745 if not self.ui.configbool('phases', 'publish', True):
746 return True
746 return True
747 # if publishing we can't copy if there is filtered content
747 # if publishing we can't copy if there is filtered content
748 return not self.filtered('visible').changelog.filteredrevs
748 return not self.filtered('visible').changelog.filteredrevs
749
749
750 def join(self, f, *insidef):
750 def join(self, f, *insidef):
751 return os.path.join(self.path, f, *insidef)
751 return os.path.join(self.path, f, *insidef)
752
752
753 def wjoin(self, f, *insidef):
753 def wjoin(self, f, *insidef):
754 return os.path.join(self.root, f, *insidef)
754 return os.path.join(self.root, f, *insidef)
755
755
756 def file(self, f):
756 def file(self, f):
757 if f[0] == '/':
757 if f[0] == '/':
758 f = f[1:]
758 f = f[1:]
759 return filelog.filelog(self.sopener, f)
759 return filelog.filelog(self.sopener, f)
760
760
761 def changectx(self, changeid):
761 def changectx(self, changeid):
762 return self[changeid]
762 return self[changeid]
763
763
764 def parents(self, changeid=None):
764 def parents(self, changeid=None):
765 '''get list of changectxs for parents of changeid'''
765 '''get list of changectxs for parents of changeid'''
766 return self[changeid].parents()
766 return self[changeid].parents()
767
767
768 def setparents(self, p1, p2=nullid):
768 def setparents(self, p1, p2=nullid):
769 self.dirstate.beginparentchange()
769 self.dirstate.beginparentchange()
770 copies = self.dirstate.setparents(p1, p2)
770 copies = self.dirstate.setparents(p1, p2)
771 pctx = self[p1]
771 pctx = self[p1]
772 if copies:
772 if copies:
773 # Adjust copy records, the dirstate cannot do it, it
773 # Adjust copy records, the dirstate cannot do it, it
774 # requires access to parents manifests. Preserve them
774 # requires access to parents manifests. Preserve them
775 # only for entries added to first parent.
775 # only for entries added to first parent.
776 for f in copies:
776 for f in copies:
777 if f not in pctx and copies[f] in pctx:
777 if f not in pctx and copies[f] in pctx:
778 self.dirstate.copy(copies[f], f)
778 self.dirstate.copy(copies[f], f)
779 if p2 == nullid:
779 if p2 == nullid:
780 for f, s in sorted(self.dirstate.copies().items()):
780 for f, s in sorted(self.dirstate.copies().items()):
781 if f not in pctx and s not in pctx:
781 if f not in pctx and s not in pctx:
782 self.dirstate.copy(None, f)
782 self.dirstate.copy(None, f)
783 self.dirstate.endparentchange()
783 self.dirstate.endparentchange()
784
784
785 def filectx(self, path, changeid=None, fileid=None):
785 def filectx(self, path, changeid=None, fileid=None):
786 """changeid can be a changeset revision, node, or tag.
786 """changeid can be a changeset revision, node, or tag.
787 fileid can be a file revision or node."""
787 fileid can be a file revision or node."""
788 return context.filectx(self, path, changeid, fileid)
788 return context.filectx(self, path, changeid, fileid)
789
789
790 def getcwd(self):
790 def getcwd(self):
791 return self.dirstate.getcwd()
791 return self.dirstate.getcwd()
792
792
793 def pathto(self, f, cwd=None):
793 def pathto(self, f, cwd=None):
794 return self.dirstate.pathto(f, cwd)
794 return self.dirstate.pathto(f, cwd)
795
795
796 def wfile(self, f, mode='r'):
796 def wfile(self, f, mode='r'):
797 return self.wopener(f, mode)
797 return self.wopener(f, mode)
798
798
799 def _link(self, f):
799 def _link(self, f):
800 return self.wvfs.islink(f)
800 return self.wvfs.islink(f)
801
801
802 def _loadfilter(self, filter):
802 def _loadfilter(self, filter):
803 if filter not in self.filterpats:
803 if filter not in self.filterpats:
804 l = []
804 l = []
805 for pat, cmd in self.ui.configitems(filter):
805 for pat, cmd in self.ui.configitems(filter):
806 if cmd == '!':
806 if cmd == '!':
807 continue
807 continue
808 mf = matchmod.match(self.root, '', [pat])
808 mf = matchmod.match(self.root, '', [pat])
809 fn = None
809 fn = None
810 params = cmd
810 params = cmd
811 for name, filterfn in self._datafilters.iteritems():
811 for name, filterfn in self._datafilters.iteritems():
812 if cmd.startswith(name):
812 if cmd.startswith(name):
813 fn = filterfn
813 fn = filterfn
814 params = cmd[len(name):].lstrip()
814 params = cmd[len(name):].lstrip()
815 break
815 break
816 if not fn:
816 if not fn:
817 fn = lambda s, c, **kwargs: util.filter(s, c)
817 fn = lambda s, c, **kwargs: util.filter(s, c)
818 # Wrap old filters not supporting keyword arguments
818 # Wrap old filters not supporting keyword arguments
819 if not inspect.getargspec(fn)[2]:
819 if not inspect.getargspec(fn)[2]:
820 oldfn = fn
820 oldfn = fn
821 fn = lambda s, c, **kwargs: oldfn(s, c)
821 fn = lambda s, c, **kwargs: oldfn(s, c)
822 l.append((mf, fn, params))
822 l.append((mf, fn, params))
823 self.filterpats[filter] = l
823 self.filterpats[filter] = l
824 return self.filterpats[filter]
824 return self.filterpats[filter]
825
825
826 def _filter(self, filterpats, filename, data):
826 def _filter(self, filterpats, filename, data):
827 for mf, fn, cmd in filterpats:
827 for mf, fn, cmd in filterpats:
828 if mf(filename):
828 if mf(filename):
829 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
829 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
830 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
830 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
831 break
831 break
832
832
833 return data
833 return data
834
834
835 @unfilteredpropertycache
835 @unfilteredpropertycache
836 def _encodefilterpats(self):
836 def _encodefilterpats(self):
837 return self._loadfilter('encode')
837 return self._loadfilter('encode')
838
838
839 @unfilteredpropertycache
839 @unfilteredpropertycache
840 def _decodefilterpats(self):
840 def _decodefilterpats(self):
841 return self._loadfilter('decode')
841 return self._loadfilter('decode')
842
842
843 def adddatafilter(self, name, filter):
843 def adddatafilter(self, name, filter):
844 self._datafilters[name] = filter
844 self._datafilters[name] = filter
845
845
846 def wread(self, filename):
846 def wread(self, filename):
847 if self._link(filename):
847 if self._link(filename):
848 data = self.wvfs.readlink(filename)
848 data = self.wvfs.readlink(filename)
849 else:
849 else:
850 data = self.wopener.read(filename)
850 data = self.wopener.read(filename)
851 return self._filter(self._encodefilterpats, filename, data)
851 return self._filter(self._encodefilterpats, filename, data)
852
852
853 def wwrite(self, filename, data, flags):
853 def wwrite(self, filename, data, flags):
854 data = self._filter(self._decodefilterpats, filename, data)
854 data = self._filter(self._decodefilterpats, filename, data)
855 if 'l' in flags:
855 if 'l' in flags:
856 self.wopener.symlink(data, filename)
856 self.wopener.symlink(data, filename)
857 else:
857 else:
858 self.wopener.write(filename, data)
858 self.wopener.write(filename, data)
859 if 'x' in flags:
859 if 'x' in flags:
860 self.wvfs.setflags(filename, False, True)
860 self.wvfs.setflags(filename, False, True)
861
861
862 def wwritedata(self, filename, data):
862 def wwritedata(self, filename, data):
863 return self._filter(self._decodefilterpats, filename, data)
863 return self._filter(self._decodefilterpats, filename, data)
864
864
865 def transaction(self, desc, report=None):
865 def transaction(self, desc, report=None):
866 tr = self._transref and self._transref() or None
866 tr = self._transref and self._transref() or None
867 if tr and tr.running():
867 if tr and tr.running():
868 return tr.nest()
868 return tr.nest()
869
869
870 # abort here if the journal already exists
870 # abort here if the journal already exists
871 if self.svfs.exists("journal"):
871 if self.svfs.exists("journal"):
872 raise error.RepoError(
872 raise error.RepoError(
873 _("abandoned transaction found"),
873 _("abandoned transaction found"),
874 hint=_("run 'hg recover' to clean up transaction"))
874 hint=_("run 'hg recover' to clean up transaction"))
875
875
876 def onclose():
876 def onclose():
877 self.store.write(self._transref())
877 self.store.write(self._transref())
878
878
879 self._writejournal(desc)
879 self._writejournal(desc)
880 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
880 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
881 rp = report and report or self.ui.warn
881 rp = report and report or self.ui.warn
882 tr = transaction.transaction(rp, self.sopener,
882 tr = transaction.transaction(rp, self.sopener,
883 "journal",
883 "journal",
884 aftertrans(renames),
884 aftertrans(renames),
885 self.store.createmode,
885 self.store.createmode,
886 onclose)
886 onclose)
887 self._transref = weakref.ref(tr)
887 self._transref = weakref.ref(tr)
888 return tr
888 return tr
889
889
890 def _journalfiles(self):
890 def _journalfiles(self):
891 return ((self.svfs, 'journal'),
891 return ((self.svfs, 'journal'),
892 (self.vfs, 'journal.dirstate'),
892 (self.vfs, 'journal.dirstate'),
893 (self.vfs, 'journal.branch'),
893 (self.vfs, 'journal.branch'),
894 (self.vfs, 'journal.desc'),
894 (self.vfs, 'journal.desc'),
895 (self.vfs, 'journal.bookmarks'),
895 (self.vfs, 'journal.bookmarks'),
896 (self.svfs, 'journal.phaseroots'))
896 (self.svfs, 'journal.phaseroots'))
897
897
898 def undofiles(self):
898 def undofiles(self):
899 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
899 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
900
900
901 def _writejournal(self, desc):
901 def _writejournal(self, desc):
902 self.opener.write("journal.dirstate",
902 self.opener.write("journal.dirstate",
903 self.opener.tryread("dirstate"))
903 self.opener.tryread("dirstate"))
904 self.opener.write("journal.branch",
904 self.opener.write("journal.branch",
905 encoding.fromlocal(self.dirstate.branch()))
905 encoding.fromlocal(self.dirstate.branch()))
906 self.opener.write("journal.desc",
906 self.opener.write("journal.desc",
907 "%d\n%s\n" % (len(self), desc))
907 "%d\n%s\n" % (len(self), desc))
908 self.opener.write("journal.bookmarks",
908 self.opener.write("journal.bookmarks",
909 self.opener.tryread("bookmarks"))
909 self.opener.tryread("bookmarks"))
910 self.sopener.write("journal.phaseroots",
910 self.sopener.write("journal.phaseroots",
911 self.sopener.tryread("phaseroots"))
911 self.sopener.tryread("phaseroots"))
912
912
913 def recover(self):
913 def recover(self):
914 lock = self.lock()
914 lock = self.lock()
915 try:
915 try:
916 if self.svfs.exists("journal"):
916 if self.svfs.exists("journal"):
917 self.ui.status(_("rolling back interrupted transaction\n"))
917 self.ui.status(_("rolling back interrupted transaction\n"))
918 transaction.rollback(self.sopener, "journal",
918 transaction.rollback(self.sopener, "journal",
919 self.ui.warn)
919 self.ui.warn)
920 self.invalidate()
920 self.invalidate()
921 return True
921 return True
922 else:
922 else:
923 self.ui.warn(_("no interrupted transaction available\n"))
923 self.ui.warn(_("no interrupted transaction available\n"))
924 return False
924 return False
925 finally:
925 finally:
926 lock.release()
926 lock.release()
927
927
928 def rollback(self, dryrun=False, force=False):
928 def rollback(self, dryrun=False, force=False):
929 wlock = lock = None
929 wlock = lock = None
930 try:
930 try:
931 wlock = self.wlock()
931 wlock = self.wlock()
932 lock = self.lock()
932 lock = self.lock()
933 if self.svfs.exists("undo"):
933 if self.svfs.exists("undo"):
934 return self._rollback(dryrun, force)
934 return self._rollback(dryrun, force)
935 else:
935 else:
936 self.ui.warn(_("no rollback information available\n"))
936 self.ui.warn(_("no rollback information available\n"))
937 return 1
937 return 1
938 finally:
938 finally:
939 release(lock, wlock)
939 release(lock, wlock)
940
940
941 @unfilteredmethod # Until we get smarter cache management
941 @unfilteredmethod # Until we get smarter cache management
942 def _rollback(self, dryrun, force):
942 def _rollback(self, dryrun, force):
943 ui = self.ui
943 ui = self.ui
944 try:
944 try:
945 args = self.opener.read('undo.desc').splitlines()
945 args = self.opener.read('undo.desc').splitlines()
946 (oldlen, desc, detail) = (int(args[0]), args[1], None)
946 (oldlen, desc, detail) = (int(args[0]), args[1], None)
947 if len(args) >= 3:
947 if len(args) >= 3:
948 detail = args[2]
948 detail = args[2]
949 oldtip = oldlen - 1
949 oldtip = oldlen - 1
950
950
951 if detail and ui.verbose:
951 if detail and ui.verbose:
952 msg = (_('repository tip rolled back to revision %s'
952 msg = (_('repository tip rolled back to revision %s'
953 ' (undo %s: %s)\n')
953 ' (undo %s: %s)\n')
954 % (oldtip, desc, detail))
954 % (oldtip, desc, detail))
955 else:
955 else:
956 msg = (_('repository tip rolled back to revision %s'
956 msg = (_('repository tip rolled back to revision %s'
957 ' (undo %s)\n')
957 ' (undo %s)\n')
958 % (oldtip, desc))
958 % (oldtip, desc))
959 except IOError:
959 except IOError:
960 msg = _('rolling back unknown transaction\n')
960 msg = _('rolling back unknown transaction\n')
961 desc = None
961 desc = None
962
962
963 if not force and self['.'] != self['tip'] and desc == 'commit':
963 if not force and self['.'] != self['tip'] and desc == 'commit':
964 raise util.Abort(
964 raise util.Abort(
965 _('rollback of last commit while not checked out '
965 _('rollback of last commit while not checked out '
966 'may lose data'), hint=_('use -f to force'))
966 'may lose data'), hint=_('use -f to force'))
967
967
968 ui.status(msg)
968 ui.status(msg)
969 if dryrun:
969 if dryrun:
970 return 0
970 return 0
971
971
972 parents = self.dirstate.parents()
972 parents = self.dirstate.parents()
973 self.destroying()
973 self.destroying()
974 transaction.rollback(self.sopener, 'undo', ui.warn)
974 transaction.rollback(self.sopener, 'undo', ui.warn)
975 if self.vfs.exists('undo.bookmarks'):
975 if self.vfs.exists('undo.bookmarks'):
976 self.vfs.rename('undo.bookmarks', 'bookmarks')
976 self.vfs.rename('undo.bookmarks', 'bookmarks')
977 if self.svfs.exists('undo.phaseroots'):
977 if self.svfs.exists('undo.phaseroots'):
978 self.svfs.rename('undo.phaseroots', 'phaseroots')
978 self.svfs.rename('undo.phaseroots', 'phaseroots')
979 self.invalidate()
979 self.invalidate()
980
980
981 parentgone = (parents[0] not in self.changelog.nodemap or
981 parentgone = (parents[0] not in self.changelog.nodemap or
982 parents[1] not in self.changelog.nodemap)
982 parents[1] not in self.changelog.nodemap)
983 if parentgone:
983 if parentgone:
984 self.vfs.rename('undo.dirstate', 'dirstate')
984 self.vfs.rename('undo.dirstate', 'dirstate')
985 try:
985 try:
986 branch = self.opener.read('undo.branch')
986 branch = self.opener.read('undo.branch')
987 self.dirstate.setbranch(encoding.tolocal(branch))
987 self.dirstate.setbranch(encoding.tolocal(branch))
988 except IOError:
988 except IOError:
989 ui.warn(_('named branch could not be reset: '
989 ui.warn(_('named branch could not be reset: '
990 'current branch is still \'%s\'\n')
990 'current branch is still \'%s\'\n')
991 % self.dirstate.branch())
991 % self.dirstate.branch())
992
992
993 self.dirstate.invalidate()
993 self.dirstate.invalidate()
994 parents = tuple([p.rev() for p in self.parents()])
994 parents = tuple([p.rev() for p in self.parents()])
995 if len(parents) > 1:
995 if len(parents) > 1:
996 ui.status(_('working directory now based on '
996 ui.status(_('working directory now based on '
997 'revisions %d and %d\n') % parents)
997 'revisions %d and %d\n') % parents)
998 else:
998 else:
999 ui.status(_('working directory now based on '
999 ui.status(_('working directory now based on '
1000 'revision %d\n') % parents)
1000 'revision %d\n') % parents)
1001 # TODO: if we know which new heads may result from this rollback, pass
1001 # TODO: if we know which new heads may result from this rollback, pass
1002 # them to destroy(), which will prevent the branchhead cache from being
1002 # them to destroy(), which will prevent the branchhead cache from being
1003 # invalidated.
1003 # invalidated.
1004 self.destroyed()
1004 self.destroyed()
1005 return 0
1005 return 0
1006
1006
1007 def invalidatecaches(self):
1007 def invalidatecaches(self):
1008
1008
1009 if '_tagscache' in vars(self):
1009 if '_tagscache' in vars(self):
1010 # can't use delattr on proxy
1010 # can't use delattr on proxy
1011 del self.__dict__['_tagscache']
1011 del self.__dict__['_tagscache']
1012
1012
1013 self.unfiltered()._branchcaches.clear()
1013 self.unfiltered()._branchcaches.clear()
1014 self.invalidatevolatilesets()
1014 self.invalidatevolatilesets()
1015
1015
1016 def invalidatevolatilesets(self):
1016 def invalidatevolatilesets(self):
1017 self.filteredrevcache.clear()
1017 self.filteredrevcache.clear()
1018 obsolete.clearobscaches(self)
1018 obsolete.clearobscaches(self)
1019
1019
1020 def invalidatedirstate(self):
1020 def invalidatedirstate(self):
1021 '''Invalidates the dirstate, causing the next call to dirstate
1021 '''Invalidates the dirstate, causing the next call to dirstate
1022 to check if it was modified since the last time it was read,
1022 to check if it was modified since the last time it was read,
1023 rereading it if it has.
1023 rereading it if it has.
1024
1024
1025 This is different to dirstate.invalidate() that it doesn't always
1025 This is different to dirstate.invalidate() that it doesn't always
1026 rereads the dirstate. Use dirstate.invalidate() if you want to
1026 rereads the dirstate. Use dirstate.invalidate() if you want to
1027 explicitly read the dirstate again (i.e. restoring it to a previous
1027 explicitly read the dirstate again (i.e. restoring it to a previous
1028 known good state).'''
1028 known good state).'''
1029 if hasunfilteredcache(self, 'dirstate'):
1029 if hasunfilteredcache(self, 'dirstate'):
1030 for k in self.dirstate._filecache:
1030 for k in self.dirstate._filecache:
1031 try:
1031 try:
1032 delattr(self.dirstate, k)
1032 delattr(self.dirstate, k)
1033 except AttributeError:
1033 except AttributeError:
1034 pass
1034 pass
1035 delattr(self.unfiltered(), 'dirstate')
1035 delattr(self.unfiltered(), 'dirstate')
1036
1036
1037 def invalidate(self):
1037 def invalidate(self):
1038 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1038 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1039 for k in self._filecache:
1039 for k in self._filecache:
1040 # dirstate is invalidated separately in invalidatedirstate()
1040 # dirstate is invalidated separately in invalidatedirstate()
1041 if k == 'dirstate':
1041 if k == 'dirstate':
1042 continue
1042 continue
1043
1043
1044 try:
1044 try:
1045 delattr(unfiltered, k)
1045 delattr(unfiltered, k)
1046 except AttributeError:
1046 except AttributeError:
1047 pass
1047 pass
1048 self.invalidatecaches()
1048 self.invalidatecaches()
1049 self.store.invalidatecaches()
1049 self.store.invalidatecaches()
1050
1050
1051 def invalidateall(self):
1051 def invalidateall(self):
1052 '''Fully invalidates both store and non-store parts, causing the
1052 '''Fully invalidates both store and non-store parts, causing the
1053 subsequent operation to reread any outside changes.'''
1053 subsequent operation to reread any outside changes.'''
1054 # extension should hook this to invalidate its caches
1054 # extension should hook this to invalidate its caches
1055 self.invalidate()
1055 self.invalidate()
1056 self.invalidatedirstate()
1056 self.invalidatedirstate()
1057
1057
1058 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc):
1058 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc):
1059 try:
1059 try:
1060 l = lockmod.lock(vfs, lockname, 0, releasefn, desc=desc)
1060 l = lockmod.lock(vfs, lockname, 0, releasefn, desc=desc)
1061 except error.LockHeld, inst:
1061 except error.LockHeld, inst:
1062 if not wait:
1062 if not wait:
1063 raise
1063 raise
1064 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1064 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1065 (desc, inst.locker))
1065 (desc, inst.locker))
1066 # default to 600 seconds timeout
1066 # default to 600 seconds timeout
1067 l = lockmod.lock(vfs, lockname,
1067 l = lockmod.lock(vfs, lockname,
1068 int(self.ui.config("ui", "timeout", "600")),
1068 int(self.ui.config("ui", "timeout", "600")),
1069 releasefn, desc=desc)
1069 releasefn, desc=desc)
1070 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1070 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1071 if acquirefn:
1071 if acquirefn:
1072 acquirefn()
1072 acquirefn()
1073 return l
1073 return l
1074
1074
1075 def _afterlock(self, callback):
1075 def _afterlock(self, callback):
1076 """add a callback to the current repository lock.
1076 """add a callback to the current repository lock.
1077
1077
1078 The callback will be executed on lock release."""
1078 The callback will be executed on lock release."""
1079 l = self._lockref and self._lockref()
1079 l = self._lockref and self._lockref()
1080 if l:
1080 if l:
1081 l.postrelease.append(callback)
1081 l.postrelease.append(callback)
1082 else:
1082 else:
1083 callback()
1083 callback()
1084
1084
1085 def lock(self, wait=True):
1085 def lock(self, wait=True):
1086 '''Lock the repository store (.hg/store) and return a weak reference
1086 '''Lock the repository store (.hg/store) and return a weak reference
1087 to the lock. Use this before modifying the store (e.g. committing or
1087 to the lock. Use this before modifying the store (e.g. committing or
1088 stripping). If you are opening a transaction, get a lock as well.)'''
1088 stripping). If you are opening a transaction, get a lock as well.)'''
1089 l = self._lockref and self._lockref()
1089 l = self._lockref and self._lockref()
1090 if l is not None and l.held:
1090 if l is not None and l.held:
1091 l.lock()
1091 l.lock()
1092 return l
1092 return l
1093
1093
1094 def unlock():
1094 def unlock():
1095 for k, ce in self._filecache.items():
1095 for k, ce in self._filecache.items():
1096 if k == 'dirstate' or k not in self.__dict__:
1096 if k == 'dirstate' or k not in self.__dict__:
1097 continue
1097 continue
1098 ce.refresh()
1098 ce.refresh()
1099
1099
1100 l = self._lock(self.svfs, "lock", wait, unlock,
1100 l = self._lock(self.svfs, "lock", wait, unlock,
1101 self.invalidate, _('repository %s') % self.origroot)
1101 self.invalidate, _('repository %s') % self.origroot)
1102 self._lockref = weakref.ref(l)
1102 self._lockref = weakref.ref(l)
1103 return l
1103 return l
1104
1104
1105 def wlock(self, wait=True):
1105 def wlock(self, wait=True):
1106 '''Lock the non-store parts of the repository (everything under
1106 '''Lock the non-store parts of the repository (everything under
1107 .hg except .hg/store) and return a weak reference to the lock.
1107 .hg except .hg/store) and return a weak reference to the lock.
1108 Use this before modifying files in .hg.'''
1108 Use this before modifying files in .hg.'''
1109 l = self._wlockref and self._wlockref()
1109 l = self._wlockref and self._wlockref()
1110 if l is not None and l.held:
1110 if l is not None and l.held:
1111 l.lock()
1111 l.lock()
1112 return l
1112 return l
1113
1113
1114 def unlock():
1114 def unlock():
1115 if self.dirstate.pendingparentchange():
1115 if self.dirstate.pendingparentchange():
1116 self.dirstate.invalidate()
1116 self.dirstate.invalidate()
1117 else:
1117 else:
1118 self.dirstate.write()
1118 self.dirstate.write()
1119
1119
1120 self._filecache['dirstate'].refresh()
1120 self._filecache['dirstate'].refresh()
1121
1121
1122 l = self._lock(self.vfs, "wlock", wait, unlock,
1122 l = self._lock(self.vfs, "wlock", wait, unlock,
1123 self.invalidatedirstate, _('working directory of %s') %
1123 self.invalidatedirstate, _('working directory of %s') %
1124 self.origroot)
1124 self.origroot)
1125 self._wlockref = weakref.ref(l)
1125 self._wlockref = weakref.ref(l)
1126 return l
1126 return l
1127
1127
1128 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1128 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1129 """
1129 """
1130 commit an individual file as part of a larger transaction
1130 commit an individual file as part of a larger transaction
1131 """
1131 """
1132
1132
1133 fname = fctx.path()
1133 fname = fctx.path()
1134 text = fctx.data()
1134 text = fctx.data()
1135 flog = self.file(fname)
1135 flog = self.file(fname)
1136 fparent1 = manifest1.get(fname, nullid)
1136 fparent1 = manifest1.get(fname, nullid)
1137 fparent2 = manifest2.get(fname, nullid)
1137 fparent2 = manifest2.get(fname, nullid)
1138
1138
1139 meta = {}
1139 meta = {}
1140 copy = fctx.renamed()
1140 copy = fctx.renamed()
1141 if copy and copy[0] != fname:
1141 if copy and copy[0] != fname:
1142 # Mark the new revision of this file as a copy of another
1142 # Mark the new revision of this file as a copy of another
1143 # file. This copy data will effectively act as a parent
1143 # file. This copy data will effectively act as a parent
1144 # of this new revision. If this is a merge, the first
1144 # of this new revision. If this is a merge, the first
1145 # parent will be the nullid (meaning "look up the copy data")
1145 # parent will be the nullid (meaning "look up the copy data")
1146 # and the second one will be the other parent. For example:
1146 # and the second one will be the other parent. For example:
1147 #
1147 #
1148 # 0 --- 1 --- 3 rev1 changes file foo
1148 # 0 --- 1 --- 3 rev1 changes file foo
1149 # \ / rev2 renames foo to bar and changes it
1149 # \ / rev2 renames foo to bar and changes it
1150 # \- 2 -/ rev3 should have bar with all changes and
1150 # \- 2 -/ rev3 should have bar with all changes and
1151 # should record that bar descends from
1151 # should record that bar descends from
1152 # bar in rev2 and foo in rev1
1152 # bar in rev2 and foo in rev1
1153 #
1153 #
1154 # this allows this merge to succeed:
1154 # this allows this merge to succeed:
1155 #
1155 #
1156 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1156 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1157 # \ / merging rev3 and rev4 should use bar@rev2
1157 # \ / merging rev3 and rev4 should use bar@rev2
1158 # \- 2 --- 4 as the merge base
1158 # \- 2 --- 4 as the merge base
1159 #
1159 #
1160
1160
1161 cfname = copy[0]
1161 cfname = copy[0]
1162 crev = manifest1.get(cfname)
1162 crev = manifest1.get(cfname)
1163 newfparent = fparent2
1163 newfparent = fparent2
1164
1164
1165 if manifest2: # branch merge
1165 if manifest2: # branch merge
1166 if fparent2 == nullid or crev is None: # copied on remote side
1166 if fparent2 == nullid or crev is None: # copied on remote side
1167 if cfname in manifest2:
1167 if cfname in manifest2:
1168 crev = manifest2[cfname]
1168 crev = manifest2[cfname]
1169 newfparent = fparent1
1169 newfparent = fparent1
1170
1170
1171 # find source in nearest ancestor if we've lost track
1171 # find source in nearest ancestor if we've lost track
1172 if not crev:
1172 if not crev:
1173 self.ui.debug(" %s: searching for copy revision for %s\n" %
1173 self.ui.debug(" %s: searching for copy revision for %s\n" %
1174 (fname, cfname))
1174 (fname, cfname))
1175 for ancestor in self[None].ancestors():
1175 for ancestor in self[None].ancestors():
1176 if cfname in ancestor:
1176 if cfname in ancestor:
1177 crev = ancestor[cfname].filenode()
1177 crev = ancestor[cfname].filenode()
1178 break
1178 break
1179
1179
1180 if crev:
1180 if crev:
1181 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1181 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1182 meta["copy"] = cfname
1182 meta["copy"] = cfname
1183 meta["copyrev"] = hex(crev)
1183 meta["copyrev"] = hex(crev)
1184 fparent1, fparent2 = nullid, newfparent
1184 fparent1, fparent2 = nullid, newfparent
1185 else:
1185 else:
1186 self.ui.warn(_("warning: can't find ancestor for '%s' "
1186 self.ui.warn(_("warning: can't find ancestor for '%s' "
1187 "copied from '%s'!\n") % (fname, cfname))
1187 "copied from '%s'!\n") % (fname, cfname))
1188
1188
1189 elif fparent1 == nullid:
1189 elif fparent1 == nullid:
1190 fparent1, fparent2 = fparent2, nullid
1190 fparent1, fparent2 = fparent2, nullid
1191 elif fparent2 != nullid:
1191 elif fparent2 != nullid:
1192 # is one parent an ancestor of the other?
1192 # is one parent an ancestor of the other?
1193 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1193 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1194 if fparent1 in fparentancestors:
1194 if fparent1 in fparentancestors:
1195 fparent1, fparent2 = fparent2, nullid
1195 fparent1, fparent2 = fparent2, nullid
1196 elif fparent2 in fparentancestors:
1196 elif fparent2 in fparentancestors:
1197 fparent2 = nullid
1197 fparent2 = nullid
1198
1198
1199 # is the file changed?
1199 # is the file changed?
1200 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1200 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1201 changelist.append(fname)
1201 changelist.append(fname)
1202 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1202 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1203 # are just the flags changed during merge?
1203 # are just the flags changed during merge?
1204 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1204 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1205 changelist.append(fname)
1205 changelist.append(fname)
1206
1206
1207 return fparent1
1207 return fparent1
1208
1208
1209 @unfilteredmethod
1209 @unfilteredmethod
1210 def commit(self, text="", user=None, date=None, match=None, force=False,
1210 def commit(self, text="", user=None, date=None, match=None, force=False,
1211 editor=False, extra={}):
1211 editor=False, extra={}):
1212 """Add a new revision to current repository.
1212 """Add a new revision to current repository.
1213
1213
1214 Revision information is gathered from the working directory,
1214 Revision information is gathered from the working directory,
1215 match can be used to filter the committed files. If editor is
1215 match can be used to filter the committed files. If editor is
1216 supplied, it is called to get a commit message.
1216 supplied, it is called to get a commit message.
1217 """
1217 """
1218
1218
1219 def fail(f, msg):
1219 def fail(f, msg):
1220 raise util.Abort('%s: %s' % (f, msg))
1220 raise util.Abort('%s: %s' % (f, msg))
1221
1221
1222 if not match:
1222 if not match:
1223 match = matchmod.always(self.root, '')
1223 match = matchmod.always(self.root, '')
1224
1224
1225 if not force:
1225 if not force:
1226 vdirs = []
1226 vdirs = []
1227 match.explicitdir = vdirs.append
1227 match.explicitdir = vdirs.append
1228 match.bad = fail
1228 match.bad = fail
1229
1229
1230 wlock = self.wlock()
1230 wlock = self.wlock()
1231 try:
1231 try:
1232 wctx = self[None]
1232 wctx = self[None]
1233 merge = len(wctx.parents()) > 1
1233 merge = len(wctx.parents()) > 1
1234
1234
1235 if (not force and merge and match and
1235 if (not force and merge and match and
1236 (match.files() or match.anypats())):
1236 (match.files() or match.anypats())):
1237 raise util.Abort(_('cannot partially commit a merge '
1237 raise util.Abort(_('cannot partially commit a merge '
1238 '(do not specify files or patterns)'))
1238 '(do not specify files or patterns)'))
1239
1239
1240 status = self.status(match=match, clean=force)
1240 status = self.status(match=match, clean=force)
1241 if force:
1241 if force:
1242 status.modified.extend(status.clean) # mq may commit clean files
1242 status.modified.extend(status.clean) # mq may commit clean files
1243
1243
1244 # check subrepos
1244 # check subrepos
1245 subs = []
1245 subs = []
1246 commitsubs = set()
1246 commitsubs = set()
1247 newstate = wctx.substate.copy()
1247 newstate = wctx.substate.copy()
1248 # only manage subrepos and .hgsubstate if .hgsub is present
1248 # only manage subrepos and .hgsubstate if .hgsub is present
1249 if '.hgsub' in wctx:
1249 if '.hgsub' in wctx:
1250 # we'll decide whether to track this ourselves, thanks
1250 # we'll decide whether to track this ourselves, thanks
1251 for c in status.modified, status.added, status.removed:
1251 for c in status.modified, status.added, status.removed:
1252 if '.hgsubstate' in c:
1252 if '.hgsubstate' in c:
1253 c.remove('.hgsubstate')
1253 c.remove('.hgsubstate')
1254
1254
1255 # compare current state to last committed state
1255 # compare current state to last committed state
1256 # build new substate based on last committed state
1256 # build new substate based on last committed state
1257 oldstate = wctx.p1().substate
1257 oldstate = wctx.p1().substate
1258 for s in sorted(newstate.keys()):
1258 for s in sorted(newstate.keys()):
1259 if not match(s):
1259 if not match(s):
1260 # ignore working copy, use old state if present
1260 # ignore working copy, use old state if present
1261 if s in oldstate:
1261 if s in oldstate:
1262 newstate[s] = oldstate[s]
1262 newstate[s] = oldstate[s]
1263 continue
1263 continue
1264 if not force:
1264 if not force:
1265 raise util.Abort(
1265 raise util.Abort(
1266 _("commit with new subrepo %s excluded") % s)
1266 _("commit with new subrepo %s excluded") % s)
1267 if wctx.sub(s).dirty(True):
1267 if wctx.sub(s).dirty(True):
1268 if not self.ui.configbool('ui', 'commitsubrepos'):
1268 if not self.ui.configbool('ui', 'commitsubrepos'):
1269 raise util.Abort(
1269 raise util.Abort(
1270 _("uncommitted changes in subrepo %s") % s,
1270 _("uncommitted changes in subrepo %s") % s,
1271 hint=_("use --subrepos for recursive commit"))
1271 hint=_("use --subrepos for recursive commit"))
1272 subs.append(s)
1272 subs.append(s)
1273 commitsubs.add(s)
1273 commitsubs.add(s)
1274 else:
1274 else:
1275 bs = wctx.sub(s).basestate()
1275 bs = wctx.sub(s).basestate()
1276 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1276 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1277 if oldstate.get(s, (None, None, None))[1] != bs:
1277 if oldstate.get(s, (None, None, None))[1] != bs:
1278 subs.append(s)
1278 subs.append(s)
1279
1279
1280 # check for removed subrepos
1280 # check for removed subrepos
1281 for p in wctx.parents():
1281 for p in wctx.parents():
1282 r = [s for s in p.substate if s not in newstate]
1282 r = [s for s in p.substate if s not in newstate]
1283 subs += [s for s in r if match(s)]
1283 subs += [s for s in r if match(s)]
1284 if subs:
1284 if subs:
1285 if (not match('.hgsub') and
1285 if (not match('.hgsub') and
1286 '.hgsub' in (wctx.modified() + wctx.added())):
1286 '.hgsub' in (wctx.modified() + wctx.added())):
1287 raise util.Abort(
1287 raise util.Abort(
1288 _("can't commit subrepos without .hgsub"))
1288 _("can't commit subrepos without .hgsub"))
1289 status.modified.insert(0, '.hgsubstate')
1289 status.modified.insert(0, '.hgsubstate')
1290
1290
1291 elif '.hgsub' in status.removed:
1291 elif '.hgsub' in status.removed:
1292 # clean up .hgsubstate when .hgsub is removed
1292 # clean up .hgsubstate when .hgsub is removed
1293 if ('.hgsubstate' in wctx and
1293 if ('.hgsubstate' in wctx and
1294 '.hgsubstate' not in (status.modified + status.added +
1294 '.hgsubstate' not in (status.modified + status.added +
1295 status.removed)):
1295 status.removed)):
1296 status.removed.insert(0, '.hgsubstate')
1296 status.removed.insert(0, '.hgsubstate')
1297
1297
1298 # make sure all explicit patterns are matched
1298 # make sure all explicit patterns are matched
1299 if not force and match.files():
1299 if not force and match.files():
1300 matched = set(status.modified + status.added + status.removed)
1300 matched = set(status.modified + status.added + status.removed)
1301
1301
1302 for f in match.files():
1302 for f in match.files():
1303 f = self.dirstate.normalize(f)
1303 f = self.dirstate.normalize(f)
1304 if f == '.' or f in matched or f in wctx.substate:
1304 if f == '.' or f in matched or f in wctx.substate:
1305 continue
1305 continue
1306 if f in status.deleted:
1306 if f in status.deleted:
1307 fail(f, _('file not found!'))
1307 fail(f, _('file not found!'))
1308 if f in vdirs: # visited directory
1308 if f in vdirs: # visited directory
1309 d = f + '/'
1309 d = f + '/'
1310 for mf in matched:
1310 for mf in matched:
1311 if mf.startswith(d):
1311 if mf.startswith(d):
1312 break
1312 break
1313 else:
1313 else:
1314 fail(f, _("no match under directory!"))
1314 fail(f, _("no match under directory!"))
1315 elif f not in self.dirstate:
1315 elif f not in self.dirstate:
1316 fail(f, _("file not tracked!"))
1316 fail(f, _("file not tracked!"))
1317
1317
1318 cctx = context.workingctx(self, text, user, date, extra, status)
1318 cctx = context.workingctx(self, text, user, date, extra, status)
1319
1319
1320 if (not force and not extra.get("close") and not merge
1320 if (not force and not extra.get("close") and not merge
1321 and not cctx.files()
1321 and not cctx.files()
1322 and wctx.branch() == wctx.p1().branch()):
1322 and wctx.branch() == wctx.p1().branch()):
1323 return None
1323 return None
1324
1324
1325 if merge and cctx.deleted():
1325 if merge and cctx.deleted():
1326 raise util.Abort(_("cannot commit merge with missing files"))
1326 raise util.Abort(_("cannot commit merge with missing files"))
1327
1327
1328 ms = mergemod.mergestate(self)
1328 ms = mergemod.mergestate(self)
1329 for f in status.modified:
1329 for f in status.modified:
1330 if f in ms and ms[f] == 'u':
1330 if f in ms and ms[f] == 'u':
1331 raise util.Abort(_("unresolved merge conflicts "
1331 raise util.Abort(_("unresolved merge conflicts "
1332 "(see hg help resolve)"))
1332 "(see hg help resolve)"))
1333
1333
1334 if editor:
1334 if editor:
1335 cctx._text = editor(self, cctx, subs)
1335 cctx._text = editor(self, cctx, subs)
1336 edited = (text != cctx._text)
1336 edited = (text != cctx._text)
1337
1337
1338 # Save commit message in case this transaction gets rolled back
1338 # Save commit message in case this transaction gets rolled back
1339 # (e.g. by a pretxncommit hook). Leave the content alone on
1339 # (e.g. by a pretxncommit hook). Leave the content alone on
1340 # the assumption that the user will use the same editor again.
1340 # the assumption that the user will use the same editor again.
1341 msgfn = self.savecommitmessage(cctx._text)
1341 msgfn = self.savecommitmessage(cctx._text)
1342
1342
1343 # commit subs and write new state
1343 # commit subs and write new state
1344 if subs:
1344 if subs:
1345 for s in sorted(commitsubs):
1345 for s in sorted(commitsubs):
1346 sub = wctx.sub(s)
1346 sub = wctx.sub(s)
1347 self.ui.status(_('committing subrepository %s\n') %
1347 self.ui.status(_('committing subrepository %s\n') %
1348 subrepo.subrelpath(sub))
1348 subrepo.subrelpath(sub))
1349 sr = sub.commit(cctx._text, user, date)
1349 sr = sub.commit(cctx._text, user, date)
1350 newstate[s] = (newstate[s][0], sr)
1350 newstate[s] = (newstate[s][0], sr)
1351 subrepo.writestate(self, newstate)
1351 subrepo.writestate(self, newstate)
1352
1352
1353 p1, p2 = self.dirstate.parents()
1353 p1, p2 = self.dirstate.parents()
1354 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1354 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1355 try:
1355 try:
1356 self.hook("precommit", throw=True, parent1=hookp1,
1356 self.hook("precommit", throw=True, parent1=hookp1,
1357 parent2=hookp2)
1357 parent2=hookp2)
1358 ret = self.commitctx(cctx, True)
1358 ret = self.commitctx(cctx, True)
1359 except: # re-raises
1359 except: # re-raises
1360 if edited:
1360 if edited:
1361 self.ui.write(
1361 self.ui.write(
1362 _('note: commit message saved in %s\n') % msgfn)
1362 _('note: commit message saved in %s\n') % msgfn)
1363 raise
1363 raise
1364
1364
1365 # update bookmarks, dirstate and mergestate
1365 # update bookmarks, dirstate and mergestate
1366 bookmarks.update(self, [p1, p2], ret)
1366 bookmarks.update(self, [p1, p2], ret)
1367 cctx.markcommitted(ret)
1367 cctx.markcommitted(ret)
1368 ms.reset()
1368 ms.reset()
1369 finally:
1369 finally:
1370 wlock.release()
1370 wlock.release()
1371
1371
1372 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1372 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1373 # hack for command that use a temporary commit (eg: histedit)
1373 # hack for command that use a temporary commit (eg: histedit)
1374 # temporary commit got stripped before hook release
1374 # temporary commit got stripped before hook release
1375 if node in self:
1375 if node in self:
1376 self.hook("commit", node=node, parent1=parent1,
1376 self.hook("commit", node=node, parent1=parent1,
1377 parent2=parent2)
1377 parent2=parent2)
1378 self._afterlock(commithook)
1378 self._afterlock(commithook)
1379 return ret
1379 return ret
1380
1380
1381 @unfilteredmethod
1381 @unfilteredmethod
1382 def commitctx(self, ctx, error=False):
1382 def commitctx(self, ctx, error=False):
1383 """Add a new revision to current repository.
1383 """Add a new revision to current repository.
1384 Revision information is passed via the context argument.
1384 Revision information is passed via the context argument.
1385 """
1385 """
1386
1386
1387 tr = None
1387 tr = None
1388 p1, p2 = ctx.p1(), ctx.p2()
1388 p1, p2 = ctx.p1(), ctx.p2()
1389 user = ctx.user()
1389 user = ctx.user()
1390
1390
1391 lock = self.lock()
1391 lock = self.lock()
1392 try:
1392 try:
1393 tr = self.transaction("commit")
1393 tr = self.transaction("commit")
1394 trp = weakref.proxy(tr)
1394 trp = weakref.proxy(tr)
1395
1395
1396 if ctx.files():
1396 if ctx.files():
1397 m1 = p1.manifest()
1397 m1 = p1.manifest()
1398 m2 = p2.manifest()
1398 m2 = p2.manifest()
1399 m = m1.copy()
1399 m = m1.copy()
1400
1400
1401 # check in files
1401 # check in files
1402 added = []
1402 added = []
1403 changed = []
1403 changed = []
1404 removed = list(ctx.removed())
1404 removed = list(ctx.removed())
1405 linkrev = len(self)
1405 linkrev = len(self)
1406 for f in sorted(ctx.modified() + ctx.added()):
1406 for f in sorted(ctx.modified() + ctx.added()):
1407 self.ui.note(f + "\n")
1407 self.ui.note(f + "\n")
1408 try:
1408 try:
1409 fctx = ctx[f]
1409 fctx = ctx[f]
1410 if fctx is None:
1410 if fctx is None:
1411 removed.append(f)
1411 removed.append(f)
1412 else:
1412 else:
1413 added.append(f)
1413 added.append(f)
1414 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1414 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1415 trp, changed)
1415 trp, changed)
1416 m.setflag(f, fctx.flags())
1416 m.setflag(f, fctx.flags())
1417 except OSError, inst:
1417 except OSError, inst:
1418 self.ui.warn(_("trouble committing %s!\n") % f)
1418 self.ui.warn(_("trouble committing %s!\n") % f)
1419 raise
1419 raise
1420 except IOError, inst:
1420 except IOError, inst:
1421 errcode = getattr(inst, 'errno', errno.ENOENT)
1421 errcode = getattr(inst, 'errno', errno.ENOENT)
1422 if error or errcode and errcode != errno.ENOENT:
1422 if error or errcode and errcode != errno.ENOENT:
1423 self.ui.warn(_("trouble committing %s!\n") % f)
1423 self.ui.warn(_("trouble committing %s!\n") % f)
1424 raise
1424 raise
1425
1425
1426 # update manifest
1426 # update manifest
1427 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1427 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1428 drop = [f for f in removed if f in m]
1428 drop = [f for f in removed if f in m]
1429 for f in drop:
1429 for f in drop:
1430 del m[f]
1430 del m[f]
1431 mn = self.manifest.add(m, trp, linkrev,
1431 mn = self.manifest.add(m, trp, linkrev,
1432 p1.manifestnode(), p2.manifestnode(),
1432 p1.manifestnode(), p2.manifestnode(),
1433 added, drop)
1433 added, drop)
1434 files = changed + removed
1434 files = changed + removed
1435 else:
1435 else:
1436 mn = p1.manifestnode()
1436 mn = p1.manifestnode()
1437 files = []
1437 files = []
1438
1438
1439 # update changelog
1439 # update changelog
1440 self.changelog.delayupdate()
1440 self.changelog.delayupdate(tr)
1441 n = self.changelog.add(mn, files, ctx.description(),
1441 n = self.changelog.add(mn, files, ctx.description(),
1442 trp, p1.node(), p2.node(),
1442 trp, p1.node(), p2.node(),
1443 user, ctx.date(), ctx.extra().copy())
1443 user, ctx.date(), ctx.extra().copy())
1444 p = lambda: self.changelog.writepending() and self.root or ""
1444 p = lambda: tr.writepending() and self.root or ""
1445 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1445 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1446 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1446 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1447 parent2=xp2, pending=p)
1447 parent2=xp2, pending=p)
1448 self.changelog.finalize(trp)
1448 self.changelog.finalize(trp)
1449 # set the new commit is proper phase
1449 # set the new commit is proper phase
1450 targetphase = subrepo.newcommitphase(self.ui, ctx)
1450 targetphase = subrepo.newcommitphase(self.ui, ctx)
1451 if targetphase:
1451 if targetphase:
1452 # retract boundary do not alter parent changeset.
1452 # retract boundary do not alter parent changeset.
1453 # if a parent have higher the resulting phase will
1453 # if a parent have higher the resulting phase will
1454 # be compliant anyway
1454 # be compliant anyway
1455 #
1455 #
1456 # if minimal phase was 0 we don't need to retract anything
1456 # if minimal phase was 0 we don't need to retract anything
1457 phases.retractboundary(self, tr, targetphase, [n])
1457 phases.retractboundary(self, tr, targetphase, [n])
1458 tr.close()
1458 tr.close()
1459 branchmap.updatecache(self.filtered('served'))
1459 branchmap.updatecache(self.filtered('served'))
1460 return n
1460 return n
1461 finally:
1461 finally:
1462 if tr:
1462 if tr:
1463 tr.release()
1463 tr.release()
1464 lock.release()
1464 lock.release()
1465
1465
1466 @unfilteredmethod
1466 @unfilteredmethod
1467 def destroying(self):
1467 def destroying(self):
1468 '''Inform the repository that nodes are about to be destroyed.
1468 '''Inform the repository that nodes are about to be destroyed.
1469 Intended for use by strip and rollback, so there's a common
1469 Intended for use by strip and rollback, so there's a common
1470 place for anything that has to be done before destroying history.
1470 place for anything that has to be done before destroying history.
1471
1471
1472 This is mostly useful for saving state that is in memory and waiting
1472 This is mostly useful for saving state that is in memory and waiting
1473 to be flushed when the current lock is released. Because a call to
1473 to be flushed when the current lock is released. Because a call to
1474 destroyed is imminent, the repo will be invalidated causing those
1474 destroyed is imminent, the repo will be invalidated causing those
1475 changes to stay in memory (waiting for the next unlock), or vanish
1475 changes to stay in memory (waiting for the next unlock), or vanish
1476 completely.
1476 completely.
1477 '''
1477 '''
1478 # When using the same lock to commit and strip, the phasecache is left
1478 # When using the same lock to commit and strip, the phasecache is left
1479 # dirty after committing. Then when we strip, the repo is invalidated,
1479 # dirty after committing. Then when we strip, the repo is invalidated,
1480 # causing those changes to disappear.
1480 # causing those changes to disappear.
1481 if '_phasecache' in vars(self):
1481 if '_phasecache' in vars(self):
1482 self._phasecache.write()
1482 self._phasecache.write()
1483
1483
1484 @unfilteredmethod
1484 @unfilteredmethod
1485 def destroyed(self):
1485 def destroyed(self):
1486 '''Inform the repository that nodes have been destroyed.
1486 '''Inform the repository that nodes have been destroyed.
1487 Intended for use by strip and rollback, so there's a common
1487 Intended for use by strip and rollback, so there's a common
1488 place for anything that has to be done after destroying history.
1488 place for anything that has to be done after destroying history.
1489 '''
1489 '''
1490 # When one tries to:
1490 # When one tries to:
1491 # 1) destroy nodes thus calling this method (e.g. strip)
1491 # 1) destroy nodes thus calling this method (e.g. strip)
1492 # 2) use phasecache somewhere (e.g. commit)
1492 # 2) use phasecache somewhere (e.g. commit)
1493 #
1493 #
1494 # then 2) will fail because the phasecache contains nodes that were
1494 # then 2) will fail because the phasecache contains nodes that were
1495 # removed. We can either remove phasecache from the filecache,
1495 # removed. We can either remove phasecache from the filecache,
1496 # causing it to reload next time it is accessed, or simply filter
1496 # causing it to reload next time it is accessed, or simply filter
1497 # the removed nodes now and write the updated cache.
1497 # the removed nodes now and write the updated cache.
1498 self._phasecache.filterunknown(self)
1498 self._phasecache.filterunknown(self)
1499 self._phasecache.write()
1499 self._phasecache.write()
1500
1500
1501 # update the 'served' branch cache to help read only server process
1501 # update the 'served' branch cache to help read only server process
1502 # Thanks to branchcache collaboration this is done from the nearest
1502 # Thanks to branchcache collaboration this is done from the nearest
1503 # filtered subset and it is expected to be fast.
1503 # filtered subset and it is expected to be fast.
1504 branchmap.updatecache(self.filtered('served'))
1504 branchmap.updatecache(self.filtered('served'))
1505
1505
1506 # Ensure the persistent tag cache is updated. Doing it now
1506 # Ensure the persistent tag cache is updated. Doing it now
1507 # means that the tag cache only has to worry about destroyed
1507 # means that the tag cache only has to worry about destroyed
1508 # heads immediately after a strip/rollback. That in turn
1508 # heads immediately after a strip/rollback. That in turn
1509 # guarantees that "cachetip == currenttip" (comparing both rev
1509 # guarantees that "cachetip == currenttip" (comparing both rev
1510 # and node) always means no nodes have been added or destroyed.
1510 # and node) always means no nodes have been added or destroyed.
1511
1511
1512 # XXX this is suboptimal when qrefresh'ing: we strip the current
1512 # XXX this is suboptimal when qrefresh'ing: we strip the current
1513 # head, refresh the tag cache, then immediately add a new head.
1513 # head, refresh the tag cache, then immediately add a new head.
1514 # But I think doing it this way is necessary for the "instant
1514 # But I think doing it this way is necessary for the "instant
1515 # tag cache retrieval" case to work.
1515 # tag cache retrieval" case to work.
1516 self.invalidate()
1516 self.invalidate()
1517
1517
1518 def walk(self, match, node=None):
1518 def walk(self, match, node=None):
1519 '''
1519 '''
1520 walk recursively through the directory tree or a given
1520 walk recursively through the directory tree or a given
1521 changeset, finding all files matched by the match
1521 changeset, finding all files matched by the match
1522 function
1522 function
1523 '''
1523 '''
1524 return self[node].walk(match)
1524 return self[node].walk(match)
1525
1525
1526 def status(self, node1='.', node2=None, match=None,
1526 def status(self, node1='.', node2=None, match=None,
1527 ignored=False, clean=False, unknown=False,
1527 ignored=False, clean=False, unknown=False,
1528 listsubrepos=False):
1528 listsubrepos=False):
1529 '''a convenience method that calls node1.status(node2)'''
1529 '''a convenience method that calls node1.status(node2)'''
1530 return self[node1].status(node2, match, ignored, clean, unknown,
1530 return self[node1].status(node2, match, ignored, clean, unknown,
1531 listsubrepos)
1531 listsubrepos)
1532
1532
1533 def heads(self, start=None):
1533 def heads(self, start=None):
1534 heads = self.changelog.heads(start)
1534 heads = self.changelog.heads(start)
1535 # sort the output in rev descending order
1535 # sort the output in rev descending order
1536 return sorted(heads, key=self.changelog.rev, reverse=True)
1536 return sorted(heads, key=self.changelog.rev, reverse=True)
1537
1537
1538 def branchheads(self, branch=None, start=None, closed=False):
1538 def branchheads(self, branch=None, start=None, closed=False):
1539 '''return a (possibly filtered) list of heads for the given branch
1539 '''return a (possibly filtered) list of heads for the given branch
1540
1540
1541 Heads are returned in topological order, from newest to oldest.
1541 Heads are returned in topological order, from newest to oldest.
1542 If branch is None, use the dirstate branch.
1542 If branch is None, use the dirstate branch.
1543 If start is not None, return only heads reachable from start.
1543 If start is not None, return only heads reachable from start.
1544 If closed is True, return heads that are marked as closed as well.
1544 If closed is True, return heads that are marked as closed as well.
1545 '''
1545 '''
1546 if branch is None:
1546 if branch is None:
1547 branch = self[None].branch()
1547 branch = self[None].branch()
1548 branches = self.branchmap()
1548 branches = self.branchmap()
1549 if branch not in branches:
1549 if branch not in branches:
1550 return []
1550 return []
1551 # the cache returns heads ordered lowest to highest
1551 # the cache returns heads ordered lowest to highest
1552 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1552 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1553 if start is not None:
1553 if start is not None:
1554 # filter out the heads that cannot be reached from startrev
1554 # filter out the heads that cannot be reached from startrev
1555 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1555 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1556 bheads = [h for h in bheads if h in fbheads]
1556 bheads = [h for h in bheads if h in fbheads]
1557 return bheads
1557 return bheads
1558
1558
1559 def branches(self, nodes):
1559 def branches(self, nodes):
1560 if not nodes:
1560 if not nodes:
1561 nodes = [self.changelog.tip()]
1561 nodes = [self.changelog.tip()]
1562 b = []
1562 b = []
1563 for n in nodes:
1563 for n in nodes:
1564 t = n
1564 t = n
1565 while True:
1565 while True:
1566 p = self.changelog.parents(n)
1566 p = self.changelog.parents(n)
1567 if p[1] != nullid or p[0] == nullid:
1567 if p[1] != nullid or p[0] == nullid:
1568 b.append((t, n, p[0], p[1]))
1568 b.append((t, n, p[0], p[1]))
1569 break
1569 break
1570 n = p[0]
1570 n = p[0]
1571 return b
1571 return b
1572
1572
1573 def between(self, pairs):
1573 def between(self, pairs):
1574 r = []
1574 r = []
1575
1575
1576 for top, bottom in pairs:
1576 for top, bottom in pairs:
1577 n, l, i = top, [], 0
1577 n, l, i = top, [], 0
1578 f = 1
1578 f = 1
1579
1579
1580 while n != bottom and n != nullid:
1580 while n != bottom and n != nullid:
1581 p = self.changelog.parents(n)[0]
1581 p = self.changelog.parents(n)[0]
1582 if i == f:
1582 if i == f:
1583 l.append(n)
1583 l.append(n)
1584 f = f * 2
1584 f = f * 2
1585 n = p
1585 n = p
1586 i += 1
1586 i += 1
1587
1587
1588 r.append(l)
1588 r.append(l)
1589
1589
1590 return r
1590 return r
1591
1591
1592 def checkpush(self, pushop):
1592 def checkpush(self, pushop):
1593 """Extensions can override this function if additional checks have
1593 """Extensions can override this function if additional checks have
1594 to be performed before pushing, or call it if they override push
1594 to be performed before pushing, or call it if they override push
1595 command.
1595 command.
1596 """
1596 """
1597 pass
1597 pass
1598
1598
1599 @unfilteredpropertycache
1599 @unfilteredpropertycache
1600 def prepushoutgoinghooks(self):
1600 def prepushoutgoinghooks(self):
1601 """Return util.hooks consists of "(repo, remote, outgoing)"
1601 """Return util.hooks consists of "(repo, remote, outgoing)"
1602 functions, which are called before pushing changesets.
1602 functions, which are called before pushing changesets.
1603 """
1603 """
1604 return util.hooks()
1604 return util.hooks()
1605
1605
1606 def stream_in(self, remote, requirements):
1606 def stream_in(self, remote, requirements):
1607 lock = self.lock()
1607 lock = self.lock()
1608 try:
1608 try:
1609 # Save remote branchmap. We will use it later
1609 # Save remote branchmap. We will use it later
1610 # to speed up branchcache creation
1610 # to speed up branchcache creation
1611 rbranchmap = None
1611 rbranchmap = None
1612 if remote.capable("branchmap"):
1612 if remote.capable("branchmap"):
1613 rbranchmap = remote.branchmap()
1613 rbranchmap = remote.branchmap()
1614
1614
1615 fp = remote.stream_out()
1615 fp = remote.stream_out()
1616 l = fp.readline()
1616 l = fp.readline()
1617 try:
1617 try:
1618 resp = int(l)
1618 resp = int(l)
1619 except ValueError:
1619 except ValueError:
1620 raise error.ResponseError(
1620 raise error.ResponseError(
1621 _('unexpected response from remote server:'), l)
1621 _('unexpected response from remote server:'), l)
1622 if resp == 1:
1622 if resp == 1:
1623 raise util.Abort(_('operation forbidden by server'))
1623 raise util.Abort(_('operation forbidden by server'))
1624 elif resp == 2:
1624 elif resp == 2:
1625 raise util.Abort(_('locking the remote repository failed'))
1625 raise util.Abort(_('locking the remote repository failed'))
1626 elif resp != 0:
1626 elif resp != 0:
1627 raise util.Abort(_('the server sent an unknown error code'))
1627 raise util.Abort(_('the server sent an unknown error code'))
1628 self.ui.status(_('streaming all changes\n'))
1628 self.ui.status(_('streaming all changes\n'))
1629 l = fp.readline()
1629 l = fp.readline()
1630 try:
1630 try:
1631 total_files, total_bytes = map(int, l.split(' ', 1))
1631 total_files, total_bytes = map(int, l.split(' ', 1))
1632 except (ValueError, TypeError):
1632 except (ValueError, TypeError):
1633 raise error.ResponseError(
1633 raise error.ResponseError(
1634 _('unexpected response from remote server:'), l)
1634 _('unexpected response from remote server:'), l)
1635 self.ui.status(_('%d files to transfer, %s of data\n') %
1635 self.ui.status(_('%d files to transfer, %s of data\n') %
1636 (total_files, util.bytecount(total_bytes)))
1636 (total_files, util.bytecount(total_bytes)))
1637 handled_bytes = 0
1637 handled_bytes = 0
1638 self.ui.progress(_('clone'), 0, total=total_bytes)
1638 self.ui.progress(_('clone'), 0, total=total_bytes)
1639 start = time.time()
1639 start = time.time()
1640
1640
1641 tr = self.transaction(_('clone'))
1641 tr = self.transaction(_('clone'))
1642 try:
1642 try:
1643 for i in xrange(total_files):
1643 for i in xrange(total_files):
1644 # XXX doesn't support '\n' or '\r' in filenames
1644 # XXX doesn't support '\n' or '\r' in filenames
1645 l = fp.readline()
1645 l = fp.readline()
1646 try:
1646 try:
1647 name, size = l.split('\0', 1)
1647 name, size = l.split('\0', 1)
1648 size = int(size)
1648 size = int(size)
1649 except (ValueError, TypeError):
1649 except (ValueError, TypeError):
1650 raise error.ResponseError(
1650 raise error.ResponseError(
1651 _('unexpected response from remote server:'), l)
1651 _('unexpected response from remote server:'), l)
1652 if self.ui.debugflag:
1652 if self.ui.debugflag:
1653 self.ui.debug('adding %s (%s)\n' %
1653 self.ui.debug('adding %s (%s)\n' %
1654 (name, util.bytecount(size)))
1654 (name, util.bytecount(size)))
1655 # for backwards compat, name was partially encoded
1655 # for backwards compat, name was partially encoded
1656 ofp = self.sopener(store.decodedir(name), 'w')
1656 ofp = self.sopener(store.decodedir(name), 'w')
1657 for chunk in util.filechunkiter(fp, limit=size):
1657 for chunk in util.filechunkiter(fp, limit=size):
1658 handled_bytes += len(chunk)
1658 handled_bytes += len(chunk)
1659 self.ui.progress(_('clone'), handled_bytes,
1659 self.ui.progress(_('clone'), handled_bytes,
1660 total=total_bytes)
1660 total=total_bytes)
1661 ofp.write(chunk)
1661 ofp.write(chunk)
1662 ofp.close()
1662 ofp.close()
1663 tr.close()
1663 tr.close()
1664 finally:
1664 finally:
1665 tr.release()
1665 tr.release()
1666
1666
1667 # Writing straight to files circumvented the inmemory caches
1667 # Writing straight to files circumvented the inmemory caches
1668 self.invalidate()
1668 self.invalidate()
1669
1669
1670 elapsed = time.time() - start
1670 elapsed = time.time() - start
1671 if elapsed <= 0:
1671 if elapsed <= 0:
1672 elapsed = 0.001
1672 elapsed = 0.001
1673 self.ui.progress(_('clone'), None)
1673 self.ui.progress(_('clone'), None)
1674 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1674 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1675 (util.bytecount(total_bytes), elapsed,
1675 (util.bytecount(total_bytes), elapsed,
1676 util.bytecount(total_bytes / elapsed)))
1676 util.bytecount(total_bytes / elapsed)))
1677
1677
1678 # new requirements = old non-format requirements +
1678 # new requirements = old non-format requirements +
1679 # new format-related
1679 # new format-related
1680 # requirements from the streamed-in repository
1680 # requirements from the streamed-in repository
1681 requirements.update(set(self.requirements) - self.supportedformats)
1681 requirements.update(set(self.requirements) - self.supportedformats)
1682 self._applyrequirements(requirements)
1682 self._applyrequirements(requirements)
1683 self._writerequirements()
1683 self._writerequirements()
1684
1684
1685 if rbranchmap:
1685 if rbranchmap:
1686 rbheads = []
1686 rbheads = []
1687 closed = []
1687 closed = []
1688 for bheads in rbranchmap.itervalues():
1688 for bheads in rbranchmap.itervalues():
1689 rbheads.extend(bheads)
1689 rbheads.extend(bheads)
1690 for h in bheads:
1690 for h in bheads:
1691 r = self.changelog.rev(h)
1691 r = self.changelog.rev(h)
1692 b, c = self.changelog.branchinfo(r)
1692 b, c = self.changelog.branchinfo(r)
1693 if c:
1693 if c:
1694 closed.append(h)
1694 closed.append(h)
1695
1695
1696 if rbheads:
1696 if rbheads:
1697 rtiprev = max((int(self.changelog.rev(node))
1697 rtiprev = max((int(self.changelog.rev(node))
1698 for node in rbheads))
1698 for node in rbheads))
1699 cache = branchmap.branchcache(rbranchmap,
1699 cache = branchmap.branchcache(rbranchmap,
1700 self[rtiprev].node(),
1700 self[rtiprev].node(),
1701 rtiprev,
1701 rtiprev,
1702 closednodes=closed)
1702 closednodes=closed)
1703 # Try to stick it as low as possible
1703 # Try to stick it as low as possible
1704 # filter above served are unlikely to be fetch from a clone
1704 # filter above served are unlikely to be fetch from a clone
1705 for candidate in ('base', 'immutable', 'served'):
1705 for candidate in ('base', 'immutable', 'served'):
1706 rview = self.filtered(candidate)
1706 rview = self.filtered(candidate)
1707 if cache.validfor(rview):
1707 if cache.validfor(rview):
1708 self._branchcaches[candidate] = cache
1708 self._branchcaches[candidate] = cache
1709 cache.write(rview)
1709 cache.write(rview)
1710 break
1710 break
1711 self.invalidate()
1711 self.invalidate()
1712 return len(self.heads()) + 1
1712 return len(self.heads()) + 1
1713 finally:
1713 finally:
1714 lock.release()
1714 lock.release()
1715
1715
1716 def clone(self, remote, heads=[], stream=False):
1716 def clone(self, remote, heads=[], stream=False):
1717 '''clone remote repository.
1717 '''clone remote repository.
1718
1718
1719 keyword arguments:
1719 keyword arguments:
1720 heads: list of revs to clone (forces use of pull)
1720 heads: list of revs to clone (forces use of pull)
1721 stream: use streaming clone if possible'''
1721 stream: use streaming clone if possible'''
1722
1722
1723 # now, all clients that can request uncompressed clones can
1723 # now, all clients that can request uncompressed clones can
1724 # read repo formats supported by all servers that can serve
1724 # read repo formats supported by all servers that can serve
1725 # them.
1725 # them.
1726
1726
1727 # if revlog format changes, client will have to check version
1727 # if revlog format changes, client will have to check version
1728 # and format flags on "stream" capability, and use
1728 # and format flags on "stream" capability, and use
1729 # uncompressed only if compatible.
1729 # uncompressed only if compatible.
1730
1730
1731 if not stream:
1731 if not stream:
1732 # if the server explicitly prefers to stream (for fast LANs)
1732 # if the server explicitly prefers to stream (for fast LANs)
1733 stream = remote.capable('stream-preferred')
1733 stream = remote.capable('stream-preferred')
1734
1734
1735 if stream and not heads:
1735 if stream and not heads:
1736 # 'stream' means remote revlog format is revlogv1 only
1736 # 'stream' means remote revlog format is revlogv1 only
1737 if remote.capable('stream'):
1737 if remote.capable('stream'):
1738 self.stream_in(remote, set(('revlogv1',)))
1738 self.stream_in(remote, set(('revlogv1',)))
1739 else:
1739 else:
1740 # otherwise, 'streamreqs' contains the remote revlog format
1740 # otherwise, 'streamreqs' contains the remote revlog format
1741 streamreqs = remote.capable('streamreqs')
1741 streamreqs = remote.capable('streamreqs')
1742 if streamreqs:
1742 if streamreqs:
1743 streamreqs = set(streamreqs.split(','))
1743 streamreqs = set(streamreqs.split(','))
1744 # if we support it, stream in and adjust our requirements
1744 # if we support it, stream in and adjust our requirements
1745 if not streamreqs - self.supportedformats:
1745 if not streamreqs - self.supportedformats:
1746 self.stream_in(remote, streamreqs)
1746 self.stream_in(remote, streamreqs)
1747
1747
1748 quiet = self.ui.backupconfig('ui', 'quietbookmarkmove')
1748 quiet = self.ui.backupconfig('ui', 'quietbookmarkmove')
1749 try:
1749 try:
1750 self.ui.setconfig('ui', 'quietbookmarkmove', True, 'clone')
1750 self.ui.setconfig('ui', 'quietbookmarkmove', True, 'clone')
1751 ret = exchange.pull(self, remote, heads).cgresult
1751 ret = exchange.pull(self, remote, heads).cgresult
1752 finally:
1752 finally:
1753 self.ui.restoreconfig(quiet)
1753 self.ui.restoreconfig(quiet)
1754 return ret
1754 return ret
1755
1755
1756 def pushkey(self, namespace, key, old, new):
1756 def pushkey(self, namespace, key, old, new):
1757 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1757 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1758 old=old, new=new)
1758 old=old, new=new)
1759 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1759 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1760 ret = pushkey.push(self, namespace, key, old, new)
1760 ret = pushkey.push(self, namespace, key, old, new)
1761 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1761 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1762 ret=ret)
1762 ret=ret)
1763 return ret
1763 return ret
1764
1764
1765 def listkeys(self, namespace):
1765 def listkeys(self, namespace):
1766 self.hook('prelistkeys', throw=True, namespace=namespace)
1766 self.hook('prelistkeys', throw=True, namespace=namespace)
1767 self.ui.debug('listing keys for "%s"\n' % namespace)
1767 self.ui.debug('listing keys for "%s"\n' % namespace)
1768 values = pushkey.list(self, namespace)
1768 values = pushkey.list(self, namespace)
1769 self.hook('listkeys', namespace=namespace, values=values)
1769 self.hook('listkeys', namespace=namespace, values=values)
1770 return values
1770 return values
1771
1771
1772 def debugwireargs(self, one, two, three=None, four=None, five=None):
1772 def debugwireargs(self, one, two, three=None, four=None, five=None):
1773 '''used to test argument passing over the wire'''
1773 '''used to test argument passing over the wire'''
1774 return "%s %s %s %s %s" % (one, two, three, four, five)
1774 return "%s %s %s %s %s" % (one, two, three, four, five)
1775
1775
1776 def savecommitmessage(self, text):
1776 def savecommitmessage(self, text):
1777 fp = self.opener('last-message.txt', 'wb')
1777 fp = self.opener('last-message.txt', 'wb')
1778 try:
1778 try:
1779 fp.write(text)
1779 fp.write(text)
1780 finally:
1780 finally:
1781 fp.close()
1781 fp.close()
1782 return self.pathto(fp.name[len(self.root) + 1:])
1782 return self.pathto(fp.name[len(self.root) + 1:])
1783
1783
1784 # used to avoid circular references so destructors work
1784 # used to avoid circular references so destructors work
1785 def aftertrans(files):
1785 def aftertrans(files):
1786 renamefiles = [tuple(t) for t in files]
1786 renamefiles = [tuple(t) for t in files]
1787 def a():
1787 def a():
1788 for vfs, src, dest in renamefiles:
1788 for vfs, src, dest in renamefiles:
1789 try:
1789 try:
1790 vfs.rename(src, dest)
1790 vfs.rename(src, dest)
1791 except OSError: # journal file does not yet exist
1791 except OSError: # journal file does not yet exist
1792 pass
1792 pass
1793 return a
1793 return a
1794
1794
1795 def undoname(fn):
1795 def undoname(fn):
1796 base, name = os.path.split(fn)
1796 base, name = os.path.split(fn)
1797 assert name.startswith('journal')
1797 assert name.startswith('journal')
1798 return os.path.join(base, name.replace('journal', 'undo', 1))
1798 return os.path.join(base, name.replace('journal', 'undo', 1))
1799
1799
1800 def instance(ui, path, create):
1800 def instance(ui, path, create):
1801 return localrepository(ui, util.urllocalpath(path), create)
1801 return localrepository(ui, util.urllocalpath(path), create)
1802
1802
1803 def islocal(path):
1803 def islocal(path):
1804 return True
1804 return True
General Comments 0
You need to be logged in to leave comments. Login now