##// END OF EJS Templates
manifest: add manifestctx.readdelta()...
Durham Goode -
r29938:a059b173 default
parent child Browse files
Show More
@@ -1,1027 +1,1027 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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import tempfile
12 import tempfile
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 branchmap,
23 branchmap,
24 dagutil,
24 dagutil,
25 discovery,
25 discovery,
26 error,
26 error,
27 mdiff,
27 mdiff,
28 phases,
28 phases,
29 util,
29 util,
30 )
30 )
31
31
32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
35
35
36 def readexactly(stream, n):
36 def readexactly(stream, n):
37 '''read n bytes from stream.read and abort if less was available'''
37 '''read n bytes from stream.read and abort if less was available'''
38 s = stream.read(n)
38 s = stream.read(n)
39 if len(s) < n:
39 if len(s) < n:
40 raise error.Abort(_("stream ended unexpectedly"
40 raise error.Abort(_("stream ended unexpectedly"
41 " (got %d bytes, expected %d)")
41 " (got %d bytes, expected %d)")
42 % (len(s), n))
42 % (len(s), n))
43 return s
43 return s
44
44
45 def getchunk(stream):
45 def getchunk(stream):
46 """return the next chunk from stream as a string"""
46 """return the next chunk from stream as a string"""
47 d = readexactly(stream, 4)
47 d = readexactly(stream, 4)
48 l = struct.unpack(">l", d)[0]
48 l = struct.unpack(">l", d)[0]
49 if l <= 4:
49 if l <= 4:
50 if l:
50 if l:
51 raise error.Abort(_("invalid chunk length %d") % l)
51 raise error.Abort(_("invalid chunk length %d") % l)
52 return ""
52 return ""
53 return readexactly(stream, l - 4)
53 return readexactly(stream, l - 4)
54
54
55 def chunkheader(length):
55 def chunkheader(length):
56 """return a changegroup chunk header (string)"""
56 """return a changegroup chunk header (string)"""
57 return struct.pack(">l", length + 4)
57 return struct.pack(">l", length + 4)
58
58
59 def closechunk():
59 def closechunk():
60 """return a changegroup chunk header (string) for a zero-length chunk"""
60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 return struct.pack(">l", 0)
61 return struct.pack(">l", 0)
62
62
63 def combineresults(results):
63 def combineresults(results):
64 """logic to combine 0 or more addchangegroup results into one"""
64 """logic to combine 0 or more addchangegroup results into one"""
65 changedheads = 0
65 changedheads = 0
66 result = 1
66 result = 1
67 for ret in results:
67 for ret in results:
68 # If any changegroup result is 0, return 0
68 # If any changegroup result is 0, return 0
69 if ret == 0:
69 if ret == 0:
70 result = 0
70 result = 0
71 break
71 break
72 if ret < -1:
72 if ret < -1:
73 changedheads += ret + 1
73 changedheads += ret + 1
74 elif ret > 1:
74 elif ret > 1:
75 changedheads += ret - 1
75 changedheads += ret - 1
76 if changedheads > 0:
76 if changedheads > 0:
77 result = 1 + changedheads
77 result = 1 + changedheads
78 elif changedheads < 0:
78 elif changedheads < 0:
79 result = -1 + changedheads
79 result = -1 + changedheads
80 return result
80 return result
81
81
82 def writechunks(ui, chunks, filename, vfs=None):
82 def writechunks(ui, chunks, filename, vfs=None):
83 """Write chunks to a file and return its filename.
83 """Write chunks to a file and return its filename.
84
84
85 The stream is assumed to be a bundle file.
85 The stream is assumed to be a bundle file.
86 Existing files will not be overwritten.
86 Existing files will not be overwritten.
87 If no filename is specified, a temporary file is created.
87 If no filename is specified, a temporary file is created.
88 """
88 """
89 fh = None
89 fh = None
90 cleanup = None
90 cleanup = None
91 try:
91 try:
92 if filename:
92 if filename:
93 if vfs:
93 if vfs:
94 fh = vfs.open(filename, "wb")
94 fh = vfs.open(filename, "wb")
95 else:
95 else:
96 fh = open(filename, "wb")
96 fh = open(filename, "wb")
97 else:
97 else:
98 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
98 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
99 fh = os.fdopen(fd, "wb")
99 fh = os.fdopen(fd, "wb")
100 cleanup = filename
100 cleanup = filename
101 for c in chunks:
101 for c in chunks:
102 fh.write(c)
102 fh.write(c)
103 cleanup = None
103 cleanup = None
104 return filename
104 return filename
105 finally:
105 finally:
106 if fh is not None:
106 if fh is not None:
107 fh.close()
107 fh.close()
108 if cleanup is not None:
108 if cleanup is not None:
109 if filename and vfs:
109 if filename and vfs:
110 vfs.unlink(cleanup)
110 vfs.unlink(cleanup)
111 else:
111 else:
112 os.unlink(cleanup)
112 os.unlink(cleanup)
113
113
114 class cg1unpacker(object):
114 class cg1unpacker(object):
115 """Unpacker for cg1 changegroup streams.
115 """Unpacker for cg1 changegroup streams.
116
116
117 A changegroup unpacker handles the framing of the revision data in
117 A changegroup unpacker handles the framing of the revision data in
118 the wire format. Most consumers will want to use the apply()
118 the wire format. Most consumers will want to use the apply()
119 method to add the changes from the changegroup to a repository.
119 method to add the changes from the changegroup to a repository.
120
120
121 If you're forwarding a changegroup unmodified to another consumer,
121 If you're forwarding a changegroup unmodified to another consumer,
122 use getchunks(), which returns an iterator of changegroup
122 use getchunks(), which returns an iterator of changegroup
123 chunks. This is mostly useful for cases where you need to know the
123 chunks. This is mostly useful for cases where you need to know the
124 data stream has ended by observing the end of the changegroup.
124 data stream has ended by observing the end of the changegroup.
125
125
126 deltachunk() is useful only if you're applying delta data. Most
126 deltachunk() is useful only if you're applying delta data. Most
127 consumers should prefer apply() instead.
127 consumers should prefer apply() instead.
128
128
129 A few other public methods exist. Those are used only for
129 A few other public methods exist. Those are used only for
130 bundlerepo and some debug commands - their use is discouraged.
130 bundlerepo and some debug commands - their use is discouraged.
131 """
131 """
132 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
132 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
133 deltaheadersize = struct.calcsize(deltaheader)
133 deltaheadersize = struct.calcsize(deltaheader)
134 version = '01'
134 version = '01'
135 _grouplistcount = 1 # One list of files after the manifests
135 _grouplistcount = 1 # One list of files after the manifests
136
136
137 def __init__(self, fh, alg, extras=None):
137 def __init__(self, fh, alg, extras=None):
138 if alg == 'UN':
138 if alg == 'UN':
139 alg = None # get more modern without breaking too much
139 alg = None # get more modern without breaking too much
140 if not alg in util.decompressors:
140 if not alg in util.decompressors:
141 raise error.Abort(_('unknown stream compression type: %s')
141 raise error.Abort(_('unknown stream compression type: %s')
142 % alg)
142 % alg)
143 if alg == 'BZ':
143 if alg == 'BZ':
144 alg = '_truncatedBZ'
144 alg = '_truncatedBZ'
145 self._stream = util.decompressors[alg](fh)
145 self._stream = util.decompressors[alg](fh)
146 self._type = alg
146 self._type = alg
147 self.extras = extras or {}
147 self.extras = extras or {}
148 self.callback = None
148 self.callback = None
149
149
150 # These methods (compressed, read, seek, tell) all appear to only
150 # These methods (compressed, read, seek, tell) all appear to only
151 # be used by bundlerepo, but it's a little hard to tell.
151 # be used by bundlerepo, but it's a little hard to tell.
152 def compressed(self):
152 def compressed(self):
153 return self._type is not None
153 return self._type is not None
154 def read(self, l):
154 def read(self, l):
155 return self._stream.read(l)
155 return self._stream.read(l)
156 def seek(self, pos):
156 def seek(self, pos):
157 return self._stream.seek(pos)
157 return self._stream.seek(pos)
158 def tell(self):
158 def tell(self):
159 return self._stream.tell()
159 return self._stream.tell()
160 def close(self):
160 def close(self):
161 return self._stream.close()
161 return self._stream.close()
162
162
163 def _chunklength(self):
163 def _chunklength(self):
164 d = readexactly(self._stream, 4)
164 d = readexactly(self._stream, 4)
165 l = struct.unpack(">l", d)[0]
165 l = struct.unpack(">l", d)[0]
166 if l <= 4:
166 if l <= 4:
167 if l:
167 if l:
168 raise error.Abort(_("invalid chunk length %d") % l)
168 raise error.Abort(_("invalid chunk length %d") % l)
169 return 0
169 return 0
170 if self.callback:
170 if self.callback:
171 self.callback()
171 self.callback()
172 return l - 4
172 return l - 4
173
173
174 def changelogheader(self):
174 def changelogheader(self):
175 """v10 does not have a changelog header chunk"""
175 """v10 does not have a changelog header chunk"""
176 return {}
176 return {}
177
177
178 def manifestheader(self):
178 def manifestheader(self):
179 """v10 does not have a manifest header chunk"""
179 """v10 does not have a manifest header chunk"""
180 return {}
180 return {}
181
181
182 def filelogheader(self):
182 def filelogheader(self):
183 """return the header of the filelogs chunk, v10 only has the filename"""
183 """return the header of the filelogs chunk, v10 only has the filename"""
184 l = self._chunklength()
184 l = self._chunklength()
185 if not l:
185 if not l:
186 return {}
186 return {}
187 fname = readexactly(self._stream, l)
187 fname = readexactly(self._stream, l)
188 return {'filename': fname}
188 return {'filename': fname}
189
189
190 def _deltaheader(self, headertuple, prevnode):
190 def _deltaheader(self, headertuple, prevnode):
191 node, p1, p2, cs = headertuple
191 node, p1, p2, cs = headertuple
192 if prevnode is None:
192 if prevnode is None:
193 deltabase = p1
193 deltabase = p1
194 else:
194 else:
195 deltabase = prevnode
195 deltabase = prevnode
196 flags = 0
196 flags = 0
197 return node, p1, p2, deltabase, cs, flags
197 return node, p1, p2, deltabase, cs, flags
198
198
199 def deltachunk(self, prevnode):
199 def deltachunk(self, prevnode):
200 l = self._chunklength()
200 l = self._chunklength()
201 if not l:
201 if not l:
202 return {}
202 return {}
203 headerdata = readexactly(self._stream, self.deltaheadersize)
203 headerdata = readexactly(self._stream, self.deltaheadersize)
204 header = struct.unpack(self.deltaheader, headerdata)
204 header = struct.unpack(self.deltaheader, headerdata)
205 delta = readexactly(self._stream, l - self.deltaheadersize)
205 delta = readexactly(self._stream, l - self.deltaheadersize)
206 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
206 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
207 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
207 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
208 'deltabase': deltabase, 'delta': delta, 'flags': flags}
208 'deltabase': deltabase, 'delta': delta, 'flags': flags}
209
209
210 def getchunks(self):
210 def getchunks(self):
211 """returns all the chunks contains in the bundle
211 """returns all the chunks contains in the bundle
212
212
213 Used when you need to forward the binary stream to a file or another
213 Used when you need to forward the binary stream to a file or another
214 network API. To do so, it parse the changegroup data, otherwise it will
214 network API. To do so, it parse the changegroup data, otherwise it will
215 block in case of sshrepo because it don't know the end of the stream.
215 block in case of sshrepo because it don't know the end of the stream.
216 """
216 """
217 # an empty chunkgroup is the end of the changegroup
217 # an empty chunkgroup is the end of the changegroup
218 # a changegroup has at least 2 chunkgroups (changelog and manifest).
218 # a changegroup has at least 2 chunkgroups (changelog and manifest).
219 # after that, changegroup versions 1 and 2 have a series of groups
219 # after that, changegroup versions 1 and 2 have a series of groups
220 # with one group per file. changegroup 3 has a series of directory
220 # with one group per file. changegroup 3 has a series of directory
221 # manifests before the files.
221 # manifests before the files.
222 count = 0
222 count = 0
223 emptycount = 0
223 emptycount = 0
224 while emptycount < self._grouplistcount:
224 while emptycount < self._grouplistcount:
225 empty = True
225 empty = True
226 count += 1
226 count += 1
227 while True:
227 while True:
228 chunk = getchunk(self)
228 chunk = getchunk(self)
229 if not chunk:
229 if not chunk:
230 if empty and count > 2:
230 if empty and count > 2:
231 emptycount += 1
231 emptycount += 1
232 break
232 break
233 empty = False
233 empty = False
234 yield chunkheader(len(chunk))
234 yield chunkheader(len(chunk))
235 pos = 0
235 pos = 0
236 while pos < len(chunk):
236 while pos < len(chunk):
237 next = pos + 2**20
237 next = pos + 2**20
238 yield chunk[pos:next]
238 yield chunk[pos:next]
239 pos = next
239 pos = next
240 yield closechunk()
240 yield closechunk()
241
241
242 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
242 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
243 # We know that we'll never have more manifests than we had
243 # We know that we'll never have more manifests than we had
244 # changesets.
244 # changesets.
245 self.callback = prog(_('manifests'), numchanges)
245 self.callback = prog(_('manifests'), numchanges)
246 # no need to check for empty manifest group here:
246 # no need to check for empty manifest group here:
247 # if the result of the merge of 1 and 2 is the same in 3 and 4,
247 # if the result of the merge of 1 and 2 is the same in 3 and 4,
248 # no new manifest will be created and the manifest group will
248 # no new manifest will be created and the manifest group will
249 # be empty during the pull
249 # be empty during the pull
250 self.manifestheader()
250 self.manifestheader()
251 repo.manifest.addgroup(self, revmap, trp)
251 repo.manifest.addgroup(self, revmap, trp)
252 repo.ui.progress(_('manifests'), None)
252 repo.ui.progress(_('manifests'), None)
253 self.callback = None
253 self.callback = None
254
254
255 def apply(self, repo, srctype, url, emptyok=False,
255 def apply(self, repo, srctype, url, emptyok=False,
256 targetphase=phases.draft, expectedtotal=None):
256 targetphase=phases.draft, expectedtotal=None):
257 """Add the changegroup returned by source.read() to this repo.
257 """Add the changegroup returned by source.read() to this repo.
258 srctype is a string like 'push', 'pull', or 'unbundle'. url is
258 srctype is a string like 'push', 'pull', or 'unbundle'. url is
259 the URL of the repo where this changegroup is coming from.
259 the URL of the repo where this changegroup is coming from.
260
260
261 Return an integer summarizing the change to this repo:
261 Return an integer summarizing the change to this repo:
262 - nothing changed or no source: 0
262 - nothing changed or no source: 0
263 - more heads than before: 1+added heads (2..n)
263 - more heads than before: 1+added heads (2..n)
264 - fewer heads than before: -1-removed heads (-2..-n)
264 - fewer heads than before: -1-removed heads (-2..-n)
265 - number of heads stays the same: 1
265 - number of heads stays the same: 1
266 """
266 """
267 repo = repo.unfiltered()
267 repo = repo.unfiltered()
268 def csmap(x):
268 def csmap(x):
269 repo.ui.debug("add changeset %s\n" % short(x))
269 repo.ui.debug("add changeset %s\n" % short(x))
270 return len(cl)
270 return len(cl)
271
271
272 def revmap(x):
272 def revmap(x):
273 return cl.rev(x)
273 return cl.rev(x)
274
274
275 changesets = files = revisions = 0
275 changesets = files = revisions = 0
276
276
277 try:
277 try:
278 with repo.transaction("\n".join([srctype,
278 with repo.transaction("\n".join([srctype,
279 util.hidepassword(url)])) as tr:
279 util.hidepassword(url)])) as tr:
280 # The transaction could have been created before and already
280 # The transaction could have been created before and already
281 # carries source information. In this case we use the top
281 # carries source information. In this case we use the top
282 # level data. We overwrite the argument because we need to use
282 # level data. We overwrite the argument because we need to use
283 # the top level value (if they exist) in this function.
283 # the top level value (if they exist) in this function.
284 srctype = tr.hookargs.setdefault('source', srctype)
284 srctype = tr.hookargs.setdefault('source', srctype)
285 url = tr.hookargs.setdefault('url', url)
285 url = tr.hookargs.setdefault('url', url)
286 repo.hook('prechangegroup', throw=True, **tr.hookargs)
286 repo.hook('prechangegroup', throw=True, **tr.hookargs)
287
287
288 # write changelog data to temp files so concurrent readers
288 # write changelog data to temp files so concurrent readers
289 # will not see an inconsistent view
289 # will not see an inconsistent view
290 cl = repo.changelog
290 cl = repo.changelog
291 cl.delayupdate(tr)
291 cl.delayupdate(tr)
292 oldheads = cl.heads()
292 oldheads = cl.heads()
293
293
294 trp = weakref.proxy(tr)
294 trp = weakref.proxy(tr)
295 # pull off the changeset group
295 # pull off the changeset group
296 repo.ui.status(_("adding changesets\n"))
296 repo.ui.status(_("adding changesets\n"))
297 clstart = len(cl)
297 clstart = len(cl)
298 class prog(object):
298 class prog(object):
299 def __init__(self, step, total):
299 def __init__(self, step, total):
300 self._step = step
300 self._step = step
301 self._total = total
301 self._total = total
302 self._count = 1
302 self._count = 1
303 def __call__(self):
303 def __call__(self):
304 repo.ui.progress(self._step, self._count,
304 repo.ui.progress(self._step, self._count,
305 unit=_('chunks'), total=self._total)
305 unit=_('chunks'), total=self._total)
306 self._count += 1
306 self._count += 1
307 self.callback = prog(_('changesets'), expectedtotal)
307 self.callback = prog(_('changesets'), expectedtotal)
308
308
309 efiles = set()
309 efiles = set()
310 def onchangelog(cl, node):
310 def onchangelog(cl, node):
311 efiles.update(cl.readfiles(node))
311 efiles.update(cl.readfiles(node))
312
312
313 self.changelogheader()
313 self.changelogheader()
314 srccontent = cl.addgroup(self, csmap, trp,
314 srccontent = cl.addgroup(self, csmap, trp,
315 addrevisioncb=onchangelog)
315 addrevisioncb=onchangelog)
316 efiles = len(efiles)
316 efiles = len(efiles)
317
317
318 if not (srccontent or emptyok):
318 if not (srccontent or emptyok):
319 raise error.Abort(_("received changelog group is empty"))
319 raise error.Abort(_("received changelog group is empty"))
320 clend = len(cl)
320 clend = len(cl)
321 changesets = clend - clstart
321 changesets = clend - clstart
322 repo.ui.progress(_('changesets'), None)
322 repo.ui.progress(_('changesets'), None)
323 self.callback = None
323 self.callback = None
324
324
325 # pull off the manifest group
325 # pull off the manifest group
326 repo.ui.status(_("adding manifests\n"))
326 repo.ui.status(_("adding manifests\n"))
327 self._unpackmanifests(repo, revmap, trp, prog, changesets)
327 self._unpackmanifests(repo, revmap, trp, prog, changesets)
328
328
329 needfiles = {}
329 needfiles = {}
330 if repo.ui.configbool('server', 'validate', default=False):
330 if repo.ui.configbool('server', 'validate', default=False):
331 # validate incoming csets have their manifests
331 # validate incoming csets have their manifests
332 for cset in xrange(clstart, clend):
332 for cset in xrange(clstart, clend):
333 mfnode = repo.changelog.read(
333 mfnode = repo.changelog.read(
334 repo.changelog.node(cset))[0]
334 repo.changelog.node(cset))[0]
335 mfest = repo.manifest.readdelta(mfnode)
335 mfest = repo.manifestlog[mfnode].readdelta()
336 # store file nodes we must see
336 # store file nodes we must see
337 for f, n in mfest.iteritems():
337 for f, n in mfest.iteritems():
338 needfiles.setdefault(f, set()).add(n)
338 needfiles.setdefault(f, set()).add(n)
339
339
340 # process the files
340 # process the files
341 repo.ui.status(_("adding file changes\n"))
341 repo.ui.status(_("adding file changes\n"))
342 newrevs, newfiles = _addchangegroupfiles(
342 newrevs, newfiles = _addchangegroupfiles(
343 repo, self, revmap, trp, efiles, needfiles)
343 repo, self, revmap, trp, efiles, needfiles)
344 revisions += newrevs
344 revisions += newrevs
345 files += newfiles
345 files += newfiles
346
346
347 dh = 0
347 dh = 0
348 if oldheads:
348 if oldheads:
349 heads = cl.heads()
349 heads = cl.heads()
350 dh = len(heads) - len(oldheads)
350 dh = len(heads) - len(oldheads)
351 for h in heads:
351 for h in heads:
352 if h not in oldheads and repo[h].closesbranch():
352 if h not in oldheads and repo[h].closesbranch():
353 dh -= 1
353 dh -= 1
354 htext = ""
354 htext = ""
355 if dh:
355 if dh:
356 htext = _(" (%+d heads)") % dh
356 htext = _(" (%+d heads)") % dh
357
357
358 repo.ui.status(_("added %d changesets"
358 repo.ui.status(_("added %d changesets"
359 " with %d changes to %d files%s\n")
359 " with %d changes to %d files%s\n")
360 % (changesets, revisions, files, htext))
360 % (changesets, revisions, files, htext))
361 repo.invalidatevolatilesets()
361 repo.invalidatevolatilesets()
362
362
363 if changesets > 0:
363 if changesets > 0:
364 if 'node' not in tr.hookargs:
364 if 'node' not in tr.hookargs:
365 tr.hookargs['node'] = hex(cl.node(clstart))
365 tr.hookargs['node'] = hex(cl.node(clstart))
366 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
366 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
367 hookargs = dict(tr.hookargs)
367 hookargs = dict(tr.hookargs)
368 else:
368 else:
369 hookargs = dict(tr.hookargs)
369 hookargs = dict(tr.hookargs)
370 hookargs['node'] = hex(cl.node(clstart))
370 hookargs['node'] = hex(cl.node(clstart))
371 hookargs['node_last'] = hex(cl.node(clend - 1))
371 hookargs['node_last'] = hex(cl.node(clend - 1))
372 repo.hook('pretxnchangegroup', throw=True, **hookargs)
372 repo.hook('pretxnchangegroup', throw=True, **hookargs)
373
373
374 added = [cl.node(r) for r in xrange(clstart, clend)]
374 added = [cl.node(r) for r in xrange(clstart, clend)]
375 publishing = repo.publishing()
375 publishing = repo.publishing()
376 if srctype in ('push', 'serve'):
376 if srctype in ('push', 'serve'):
377 # Old servers can not push the boundary themselves.
377 # Old servers can not push the boundary themselves.
378 # New servers won't push the boundary if changeset already
378 # New servers won't push the boundary if changeset already
379 # exists locally as secret
379 # exists locally as secret
380 #
380 #
381 # We should not use added here but the list of all change in
381 # We should not use added here but the list of all change in
382 # the bundle
382 # the bundle
383 if publishing:
383 if publishing:
384 phases.advanceboundary(repo, tr, phases.public,
384 phases.advanceboundary(repo, tr, phases.public,
385 srccontent)
385 srccontent)
386 else:
386 else:
387 # Those changesets have been pushed from the
387 # Those changesets have been pushed from the
388 # outside, their phases are going to be pushed
388 # outside, their phases are going to be pushed
389 # alongside. Therefor `targetphase` is
389 # alongside. Therefor `targetphase` is
390 # ignored.
390 # ignored.
391 phases.advanceboundary(repo, tr, phases.draft,
391 phases.advanceboundary(repo, tr, phases.draft,
392 srccontent)
392 srccontent)
393 phases.retractboundary(repo, tr, phases.draft, added)
393 phases.retractboundary(repo, tr, phases.draft, added)
394 elif srctype != 'strip':
394 elif srctype != 'strip':
395 # publishing only alter behavior during push
395 # publishing only alter behavior during push
396 #
396 #
397 # strip should not touch boundary at all
397 # strip should not touch boundary at all
398 phases.retractboundary(repo, tr, targetphase, added)
398 phases.retractboundary(repo, tr, targetphase, added)
399
399
400 if changesets > 0:
400 if changesets > 0:
401 if srctype != 'strip':
401 if srctype != 'strip':
402 # During strip, branchcache is invalid but
402 # During strip, branchcache is invalid but
403 # coming call to `destroyed` will repair it.
403 # coming call to `destroyed` will repair it.
404 # In other case we can safely update cache on
404 # In other case we can safely update cache on
405 # disk.
405 # disk.
406 repo.ui.debug('updating the branch cache\n')
406 repo.ui.debug('updating the branch cache\n')
407 branchmap.updatecache(repo.filtered('served'))
407 branchmap.updatecache(repo.filtered('served'))
408
408
409 def runhooks():
409 def runhooks():
410 # These hooks run when the lock releases, not when the
410 # These hooks run when the lock releases, not when the
411 # transaction closes. So it's possible for the changelog
411 # transaction closes. So it's possible for the changelog
412 # to have changed since we last saw it.
412 # to have changed since we last saw it.
413 if clstart >= len(repo):
413 if clstart >= len(repo):
414 return
414 return
415
415
416 repo.hook("changegroup", **hookargs)
416 repo.hook("changegroup", **hookargs)
417
417
418 for n in added:
418 for n in added:
419 args = hookargs.copy()
419 args = hookargs.copy()
420 args['node'] = hex(n)
420 args['node'] = hex(n)
421 del args['node_last']
421 del args['node_last']
422 repo.hook("incoming", **args)
422 repo.hook("incoming", **args)
423
423
424 newheads = [h for h in repo.heads()
424 newheads = [h for h in repo.heads()
425 if h not in oldheads]
425 if h not in oldheads]
426 repo.ui.log("incoming",
426 repo.ui.log("incoming",
427 "%s incoming changes - new heads: %s\n",
427 "%s incoming changes - new heads: %s\n",
428 len(added),
428 len(added),
429 ', '.join([hex(c[:6]) for c in newheads]))
429 ', '.join([hex(c[:6]) for c in newheads]))
430
430
431 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
431 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
432 lambda tr: repo._afterlock(runhooks))
432 lambda tr: repo._afterlock(runhooks))
433 finally:
433 finally:
434 repo.ui.flush()
434 repo.ui.flush()
435 # never return 0 here:
435 # never return 0 here:
436 if dh < 0:
436 if dh < 0:
437 return dh - 1
437 return dh - 1
438 else:
438 else:
439 return dh + 1
439 return dh + 1
440
440
441 class cg2unpacker(cg1unpacker):
441 class cg2unpacker(cg1unpacker):
442 """Unpacker for cg2 streams.
442 """Unpacker for cg2 streams.
443
443
444 cg2 streams add support for generaldelta, so the delta header
444 cg2 streams add support for generaldelta, so the delta header
445 format is slightly different. All other features about the data
445 format is slightly different. All other features about the data
446 remain the same.
446 remain the same.
447 """
447 """
448 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
448 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
449 deltaheadersize = struct.calcsize(deltaheader)
449 deltaheadersize = struct.calcsize(deltaheader)
450 version = '02'
450 version = '02'
451
451
452 def _deltaheader(self, headertuple, prevnode):
452 def _deltaheader(self, headertuple, prevnode):
453 node, p1, p2, deltabase, cs = headertuple
453 node, p1, p2, deltabase, cs = headertuple
454 flags = 0
454 flags = 0
455 return node, p1, p2, deltabase, cs, flags
455 return node, p1, p2, deltabase, cs, flags
456
456
457 class cg3unpacker(cg2unpacker):
457 class cg3unpacker(cg2unpacker):
458 """Unpacker for cg3 streams.
458 """Unpacker for cg3 streams.
459
459
460 cg3 streams add support for exchanging treemanifests and revlog
460 cg3 streams add support for exchanging treemanifests and revlog
461 flags. It adds the revlog flags to the delta header and an empty chunk
461 flags. It adds the revlog flags to the delta header and an empty chunk
462 separating manifests and files.
462 separating manifests and files.
463 """
463 """
464 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
464 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
465 deltaheadersize = struct.calcsize(deltaheader)
465 deltaheadersize = struct.calcsize(deltaheader)
466 version = '03'
466 version = '03'
467 _grouplistcount = 2 # One list of manifests and one list of files
467 _grouplistcount = 2 # One list of manifests and one list of files
468
468
469 def _deltaheader(self, headertuple, prevnode):
469 def _deltaheader(self, headertuple, prevnode):
470 node, p1, p2, deltabase, cs, flags = headertuple
470 node, p1, p2, deltabase, cs, flags = headertuple
471 return node, p1, p2, deltabase, cs, flags
471 return node, p1, p2, deltabase, cs, flags
472
472
473 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
473 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
474 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
474 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
475 numchanges)
475 numchanges)
476 for chunkdata in iter(self.filelogheader, {}):
476 for chunkdata in iter(self.filelogheader, {}):
477 # If we get here, there are directory manifests in the changegroup
477 # If we get here, there are directory manifests in the changegroup
478 d = chunkdata["filename"]
478 d = chunkdata["filename"]
479 repo.ui.debug("adding %s revisions\n" % d)
479 repo.ui.debug("adding %s revisions\n" % d)
480 dirlog = repo.manifest.dirlog(d)
480 dirlog = repo.manifest.dirlog(d)
481 if not dirlog.addgroup(self, revmap, trp):
481 if not dirlog.addgroup(self, revmap, trp):
482 raise error.Abort(_("received dir revlog group is empty"))
482 raise error.Abort(_("received dir revlog group is empty"))
483
483
484 class headerlessfixup(object):
484 class headerlessfixup(object):
485 def __init__(self, fh, h):
485 def __init__(self, fh, h):
486 self._h = h
486 self._h = h
487 self._fh = fh
487 self._fh = fh
488 def read(self, n):
488 def read(self, n):
489 if self._h:
489 if self._h:
490 d, self._h = self._h[:n], self._h[n:]
490 d, self._h = self._h[:n], self._h[n:]
491 if len(d) < n:
491 if len(d) < n:
492 d += readexactly(self._fh, n - len(d))
492 d += readexactly(self._fh, n - len(d))
493 return d
493 return d
494 return readexactly(self._fh, n)
494 return readexactly(self._fh, n)
495
495
496 class cg1packer(object):
496 class cg1packer(object):
497 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
497 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
498 version = '01'
498 version = '01'
499 def __init__(self, repo, bundlecaps=None):
499 def __init__(self, repo, bundlecaps=None):
500 """Given a source repo, construct a bundler.
500 """Given a source repo, construct a bundler.
501
501
502 bundlecaps is optional and can be used to specify the set of
502 bundlecaps is optional and can be used to specify the set of
503 capabilities which can be used to build the bundle.
503 capabilities which can be used to build the bundle.
504 """
504 """
505 # Set of capabilities we can use to build the bundle.
505 # Set of capabilities we can use to build the bundle.
506 if bundlecaps is None:
506 if bundlecaps is None:
507 bundlecaps = set()
507 bundlecaps = set()
508 self._bundlecaps = bundlecaps
508 self._bundlecaps = bundlecaps
509 # experimental config: bundle.reorder
509 # experimental config: bundle.reorder
510 reorder = repo.ui.config('bundle', 'reorder', 'auto')
510 reorder = repo.ui.config('bundle', 'reorder', 'auto')
511 if reorder == 'auto':
511 if reorder == 'auto':
512 reorder = None
512 reorder = None
513 else:
513 else:
514 reorder = util.parsebool(reorder)
514 reorder = util.parsebool(reorder)
515 self._repo = repo
515 self._repo = repo
516 self._reorder = reorder
516 self._reorder = reorder
517 self._progress = repo.ui.progress
517 self._progress = repo.ui.progress
518 if self._repo.ui.verbose and not self._repo.ui.debugflag:
518 if self._repo.ui.verbose and not self._repo.ui.debugflag:
519 self._verbosenote = self._repo.ui.note
519 self._verbosenote = self._repo.ui.note
520 else:
520 else:
521 self._verbosenote = lambda s: None
521 self._verbosenote = lambda s: None
522
522
523 def close(self):
523 def close(self):
524 return closechunk()
524 return closechunk()
525
525
526 def fileheader(self, fname):
526 def fileheader(self, fname):
527 return chunkheader(len(fname)) + fname
527 return chunkheader(len(fname)) + fname
528
528
529 # Extracted both for clarity and for overriding in extensions.
529 # Extracted both for clarity and for overriding in extensions.
530 def _sortgroup(self, revlog, nodelist, lookup):
530 def _sortgroup(self, revlog, nodelist, lookup):
531 """Sort nodes for change group and turn them into revnums."""
531 """Sort nodes for change group and turn them into revnums."""
532 # for generaldelta revlogs, we linearize the revs; this will both be
532 # for generaldelta revlogs, we linearize the revs; this will both be
533 # much quicker and generate a much smaller bundle
533 # much quicker and generate a much smaller bundle
534 if (revlog._generaldelta and self._reorder is None) or self._reorder:
534 if (revlog._generaldelta and self._reorder is None) or self._reorder:
535 dag = dagutil.revlogdag(revlog)
535 dag = dagutil.revlogdag(revlog)
536 return dag.linearize(set(revlog.rev(n) for n in nodelist))
536 return dag.linearize(set(revlog.rev(n) for n in nodelist))
537 else:
537 else:
538 return sorted([revlog.rev(n) for n in nodelist])
538 return sorted([revlog.rev(n) for n in nodelist])
539
539
540 def group(self, nodelist, revlog, lookup, units=None):
540 def group(self, nodelist, revlog, lookup, units=None):
541 """Calculate a delta group, yielding a sequence of changegroup chunks
541 """Calculate a delta group, yielding a sequence of changegroup chunks
542 (strings).
542 (strings).
543
543
544 Given a list of changeset revs, return a set of deltas and
544 Given a list of changeset revs, return a set of deltas and
545 metadata corresponding to nodes. The first delta is
545 metadata corresponding to nodes. The first delta is
546 first parent(nodelist[0]) -> nodelist[0], the receiver is
546 first parent(nodelist[0]) -> nodelist[0], the receiver is
547 guaranteed to have this parent as it has all history before
547 guaranteed to have this parent as it has all history before
548 these changesets. In the case firstparent is nullrev the
548 these changesets. In the case firstparent is nullrev the
549 changegroup starts with a full revision.
549 changegroup starts with a full revision.
550
550
551 If units is not None, progress detail will be generated, units specifies
551 If units is not None, progress detail will be generated, units specifies
552 the type of revlog that is touched (changelog, manifest, etc.).
552 the type of revlog that is touched (changelog, manifest, etc.).
553 """
553 """
554 # if we don't have any revisions touched by these changesets, bail
554 # if we don't have any revisions touched by these changesets, bail
555 if len(nodelist) == 0:
555 if len(nodelist) == 0:
556 yield self.close()
556 yield self.close()
557 return
557 return
558
558
559 revs = self._sortgroup(revlog, nodelist, lookup)
559 revs = self._sortgroup(revlog, nodelist, lookup)
560
560
561 # add the parent of the first rev
561 # add the parent of the first rev
562 p = revlog.parentrevs(revs[0])[0]
562 p = revlog.parentrevs(revs[0])[0]
563 revs.insert(0, p)
563 revs.insert(0, p)
564
564
565 # build deltas
565 # build deltas
566 total = len(revs) - 1
566 total = len(revs) - 1
567 msgbundling = _('bundling')
567 msgbundling = _('bundling')
568 for r in xrange(len(revs) - 1):
568 for r in xrange(len(revs) - 1):
569 if units is not None:
569 if units is not None:
570 self._progress(msgbundling, r + 1, unit=units, total=total)
570 self._progress(msgbundling, r + 1, unit=units, total=total)
571 prev, curr = revs[r], revs[r + 1]
571 prev, curr = revs[r], revs[r + 1]
572 linknode = lookup(revlog.node(curr))
572 linknode = lookup(revlog.node(curr))
573 for c in self.revchunk(revlog, curr, prev, linknode):
573 for c in self.revchunk(revlog, curr, prev, linknode):
574 yield c
574 yield c
575
575
576 if units is not None:
576 if units is not None:
577 self._progress(msgbundling, None)
577 self._progress(msgbundling, None)
578 yield self.close()
578 yield self.close()
579
579
580 # filter any nodes that claim to be part of the known set
580 # filter any nodes that claim to be part of the known set
581 def prune(self, revlog, missing, commonrevs):
581 def prune(self, revlog, missing, commonrevs):
582 rr, rl = revlog.rev, revlog.linkrev
582 rr, rl = revlog.rev, revlog.linkrev
583 return [n for n in missing if rl(rr(n)) not in commonrevs]
583 return [n for n in missing if rl(rr(n)) not in commonrevs]
584
584
585 def _packmanifests(self, dir, mfnodes, lookuplinknode):
585 def _packmanifests(self, dir, mfnodes, lookuplinknode):
586 """Pack flat manifests into a changegroup stream."""
586 """Pack flat manifests into a changegroup stream."""
587 assert not dir
587 assert not dir
588 for chunk in self.group(mfnodes, self._repo.manifest,
588 for chunk in self.group(mfnodes, self._repo.manifest,
589 lookuplinknode, units=_('manifests')):
589 lookuplinknode, units=_('manifests')):
590 yield chunk
590 yield chunk
591
591
592 def _manifestsdone(self):
592 def _manifestsdone(self):
593 return ''
593 return ''
594
594
595 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
595 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
596 '''yield a sequence of changegroup chunks (strings)'''
596 '''yield a sequence of changegroup chunks (strings)'''
597 repo = self._repo
597 repo = self._repo
598 cl = repo.changelog
598 cl = repo.changelog
599
599
600 clrevorder = {}
600 clrevorder = {}
601 mfs = {} # needed manifests
601 mfs = {} # needed manifests
602 fnodes = {} # needed file nodes
602 fnodes = {} # needed file nodes
603 changedfiles = set()
603 changedfiles = set()
604
604
605 # Callback for the changelog, used to collect changed files and manifest
605 # Callback for the changelog, used to collect changed files and manifest
606 # nodes.
606 # nodes.
607 # Returns the linkrev node (identity in the changelog case).
607 # Returns the linkrev node (identity in the changelog case).
608 def lookupcl(x):
608 def lookupcl(x):
609 c = cl.read(x)
609 c = cl.read(x)
610 clrevorder[x] = len(clrevorder)
610 clrevorder[x] = len(clrevorder)
611 n = c[0]
611 n = c[0]
612 # record the first changeset introducing this manifest version
612 # record the first changeset introducing this manifest version
613 mfs.setdefault(n, x)
613 mfs.setdefault(n, x)
614 # Record a complete list of potentially-changed files in
614 # Record a complete list of potentially-changed files in
615 # this manifest.
615 # this manifest.
616 changedfiles.update(c[3])
616 changedfiles.update(c[3])
617 return x
617 return x
618
618
619 self._verbosenote(_('uncompressed size of bundle content:\n'))
619 self._verbosenote(_('uncompressed size of bundle content:\n'))
620 size = 0
620 size = 0
621 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
621 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
622 size += len(chunk)
622 size += len(chunk)
623 yield chunk
623 yield chunk
624 self._verbosenote(_('%8.i (changelog)\n') % size)
624 self._verbosenote(_('%8.i (changelog)\n') % size)
625
625
626 # We need to make sure that the linkrev in the changegroup refers to
626 # We need to make sure that the linkrev in the changegroup refers to
627 # the first changeset that introduced the manifest or file revision.
627 # the first changeset that introduced the manifest or file revision.
628 # The fastpath is usually safer than the slowpath, because the filelogs
628 # The fastpath is usually safer than the slowpath, because the filelogs
629 # are walked in revlog order.
629 # are walked in revlog order.
630 #
630 #
631 # When taking the slowpath with reorder=None and the manifest revlog
631 # When taking the slowpath with reorder=None and the manifest revlog
632 # uses generaldelta, the manifest may be walked in the "wrong" order.
632 # uses generaldelta, the manifest may be walked in the "wrong" order.
633 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
633 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
634 # cc0ff93d0c0c).
634 # cc0ff93d0c0c).
635 #
635 #
636 # When taking the fastpath, we are only vulnerable to reordering
636 # When taking the fastpath, we are only vulnerable to reordering
637 # of the changelog itself. The changelog never uses generaldelta, so
637 # of the changelog itself. The changelog never uses generaldelta, so
638 # it is only reordered when reorder=True. To handle this case, we
638 # it is only reordered when reorder=True. To handle this case, we
639 # simply take the slowpath, which already has the 'clrevorder' logic.
639 # simply take the slowpath, which already has the 'clrevorder' logic.
640 # This was also fixed in cc0ff93d0c0c.
640 # This was also fixed in cc0ff93d0c0c.
641 fastpathlinkrev = fastpathlinkrev and not self._reorder
641 fastpathlinkrev = fastpathlinkrev and not self._reorder
642 # Treemanifests don't work correctly with fastpathlinkrev
642 # Treemanifests don't work correctly with fastpathlinkrev
643 # either, because we don't discover which directory nodes to
643 # either, because we don't discover which directory nodes to
644 # send along with files. This could probably be fixed.
644 # send along with files. This could probably be fixed.
645 fastpathlinkrev = fastpathlinkrev and (
645 fastpathlinkrev = fastpathlinkrev and (
646 'treemanifest' not in repo.requirements)
646 'treemanifest' not in repo.requirements)
647
647
648 for chunk in self.generatemanifests(commonrevs, clrevorder,
648 for chunk in self.generatemanifests(commonrevs, clrevorder,
649 fastpathlinkrev, mfs, fnodes):
649 fastpathlinkrev, mfs, fnodes):
650 yield chunk
650 yield chunk
651 mfs.clear()
651 mfs.clear()
652 clrevs = set(cl.rev(x) for x in clnodes)
652 clrevs = set(cl.rev(x) for x in clnodes)
653
653
654 if not fastpathlinkrev:
654 if not fastpathlinkrev:
655 def linknodes(unused, fname):
655 def linknodes(unused, fname):
656 return fnodes.get(fname, {})
656 return fnodes.get(fname, {})
657 else:
657 else:
658 cln = cl.node
658 cln = cl.node
659 def linknodes(filerevlog, fname):
659 def linknodes(filerevlog, fname):
660 llr = filerevlog.linkrev
660 llr = filerevlog.linkrev
661 fln = filerevlog.node
661 fln = filerevlog.node
662 revs = ((r, llr(r)) for r in filerevlog)
662 revs = ((r, llr(r)) for r in filerevlog)
663 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
663 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
664
664
665 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
665 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
666 source):
666 source):
667 yield chunk
667 yield chunk
668
668
669 yield self.close()
669 yield self.close()
670
670
671 if clnodes:
671 if clnodes:
672 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
672 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
673
673
674 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
674 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
675 fnodes):
675 fnodes):
676 repo = self._repo
676 repo = self._repo
677 dirlog = repo.manifest.dirlog
677 dirlog = repo.manifest.dirlog
678 tmfnodes = {'': mfs}
678 tmfnodes = {'': mfs}
679
679
680 # Callback for the manifest, used to collect linkrevs for filelog
680 # Callback for the manifest, used to collect linkrevs for filelog
681 # revisions.
681 # revisions.
682 # Returns the linkrev node (collected in lookupcl).
682 # Returns the linkrev node (collected in lookupcl).
683 def makelookupmflinknode(dir):
683 def makelookupmflinknode(dir):
684 if fastpathlinkrev:
684 if fastpathlinkrev:
685 assert not dir
685 assert not dir
686 return mfs.__getitem__
686 return mfs.__getitem__
687
687
688 def lookupmflinknode(x):
688 def lookupmflinknode(x):
689 """Callback for looking up the linknode for manifests.
689 """Callback for looking up the linknode for manifests.
690
690
691 Returns the linkrev node for the specified manifest.
691 Returns the linkrev node for the specified manifest.
692
692
693 SIDE EFFECT:
693 SIDE EFFECT:
694
694
695 1) fclnodes gets populated with the list of relevant
695 1) fclnodes gets populated with the list of relevant
696 file nodes if we're not using fastpathlinkrev
696 file nodes if we're not using fastpathlinkrev
697 2) When treemanifests are in use, collects treemanifest nodes
697 2) When treemanifests are in use, collects treemanifest nodes
698 to send
698 to send
699
699
700 Note that this means manifests must be completely sent to
700 Note that this means manifests must be completely sent to
701 the client before you can trust the list of files and
701 the client before you can trust the list of files and
702 treemanifests to send.
702 treemanifests to send.
703 """
703 """
704 clnode = tmfnodes[dir][x]
704 clnode = tmfnodes[dir][x]
705 mdata = dirlog(dir).readshallowfast(x)
705 mdata = dirlog(dir).readshallowfast(x)
706 for p, n, fl in mdata.iterentries():
706 for p, n, fl in mdata.iterentries():
707 if fl == 't': # subdirectory manifest
707 if fl == 't': # subdirectory manifest
708 subdir = dir + p + '/'
708 subdir = dir + p + '/'
709 tmfclnodes = tmfnodes.setdefault(subdir, {})
709 tmfclnodes = tmfnodes.setdefault(subdir, {})
710 tmfclnode = tmfclnodes.setdefault(n, clnode)
710 tmfclnode = tmfclnodes.setdefault(n, clnode)
711 if clrevorder[clnode] < clrevorder[tmfclnode]:
711 if clrevorder[clnode] < clrevorder[tmfclnode]:
712 tmfclnodes[n] = clnode
712 tmfclnodes[n] = clnode
713 else:
713 else:
714 f = dir + p
714 f = dir + p
715 fclnodes = fnodes.setdefault(f, {})
715 fclnodes = fnodes.setdefault(f, {})
716 fclnode = fclnodes.setdefault(n, clnode)
716 fclnode = fclnodes.setdefault(n, clnode)
717 if clrevorder[clnode] < clrevorder[fclnode]:
717 if clrevorder[clnode] < clrevorder[fclnode]:
718 fclnodes[n] = clnode
718 fclnodes[n] = clnode
719 return clnode
719 return clnode
720 return lookupmflinknode
720 return lookupmflinknode
721
721
722 size = 0
722 size = 0
723 while tmfnodes:
723 while tmfnodes:
724 dir = min(tmfnodes)
724 dir = min(tmfnodes)
725 nodes = tmfnodes[dir]
725 nodes = tmfnodes[dir]
726 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
726 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
727 if not dir or prunednodes:
727 if not dir or prunednodes:
728 for x in self._packmanifests(dir, prunednodes,
728 for x in self._packmanifests(dir, prunednodes,
729 makelookupmflinknode(dir)):
729 makelookupmflinknode(dir)):
730 size += len(x)
730 size += len(x)
731 yield x
731 yield x
732 del tmfnodes[dir]
732 del tmfnodes[dir]
733 self._verbosenote(_('%8.i (manifests)\n') % size)
733 self._verbosenote(_('%8.i (manifests)\n') % size)
734 yield self._manifestsdone()
734 yield self._manifestsdone()
735
735
736 # The 'source' parameter is useful for extensions
736 # The 'source' parameter is useful for extensions
737 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
737 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
738 repo = self._repo
738 repo = self._repo
739 progress = self._progress
739 progress = self._progress
740 msgbundling = _('bundling')
740 msgbundling = _('bundling')
741
741
742 total = len(changedfiles)
742 total = len(changedfiles)
743 # for progress output
743 # for progress output
744 msgfiles = _('files')
744 msgfiles = _('files')
745 for i, fname in enumerate(sorted(changedfiles)):
745 for i, fname in enumerate(sorted(changedfiles)):
746 filerevlog = repo.file(fname)
746 filerevlog = repo.file(fname)
747 if not filerevlog:
747 if not filerevlog:
748 raise error.Abort(_("empty or missing revlog for %s") % fname)
748 raise error.Abort(_("empty or missing revlog for %s") % fname)
749
749
750 linkrevnodes = linknodes(filerevlog, fname)
750 linkrevnodes = linknodes(filerevlog, fname)
751 # Lookup for filenodes, we collected the linkrev nodes above in the
751 # Lookup for filenodes, we collected the linkrev nodes above in the
752 # fastpath case and with lookupmf in the slowpath case.
752 # fastpath case and with lookupmf in the slowpath case.
753 def lookupfilelog(x):
753 def lookupfilelog(x):
754 return linkrevnodes[x]
754 return linkrevnodes[x]
755
755
756 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
756 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
757 if filenodes:
757 if filenodes:
758 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
758 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
759 total=total)
759 total=total)
760 h = self.fileheader(fname)
760 h = self.fileheader(fname)
761 size = len(h)
761 size = len(h)
762 yield h
762 yield h
763 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
763 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
764 size += len(chunk)
764 size += len(chunk)
765 yield chunk
765 yield chunk
766 self._verbosenote(_('%8.i %s\n') % (size, fname))
766 self._verbosenote(_('%8.i %s\n') % (size, fname))
767 progress(msgbundling, None)
767 progress(msgbundling, None)
768
768
769 def deltaparent(self, revlog, rev, p1, p2, prev):
769 def deltaparent(self, revlog, rev, p1, p2, prev):
770 return prev
770 return prev
771
771
772 def revchunk(self, revlog, rev, prev, linknode):
772 def revchunk(self, revlog, rev, prev, linknode):
773 node = revlog.node(rev)
773 node = revlog.node(rev)
774 p1, p2 = revlog.parentrevs(rev)
774 p1, p2 = revlog.parentrevs(rev)
775 base = self.deltaparent(revlog, rev, p1, p2, prev)
775 base = self.deltaparent(revlog, rev, p1, p2, prev)
776
776
777 prefix = ''
777 prefix = ''
778 if revlog.iscensored(base) or revlog.iscensored(rev):
778 if revlog.iscensored(base) or revlog.iscensored(rev):
779 try:
779 try:
780 delta = revlog.revision(node)
780 delta = revlog.revision(node)
781 except error.CensoredNodeError as e:
781 except error.CensoredNodeError as e:
782 delta = e.tombstone
782 delta = e.tombstone
783 if base == nullrev:
783 if base == nullrev:
784 prefix = mdiff.trivialdiffheader(len(delta))
784 prefix = mdiff.trivialdiffheader(len(delta))
785 else:
785 else:
786 baselen = revlog.rawsize(base)
786 baselen = revlog.rawsize(base)
787 prefix = mdiff.replacediffheader(baselen, len(delta))
787 prefix = mdiff.replacediffheader(baselen, len(delta))
788 elif base == nullrev:
788 elif base == nullrev:
789 delta = revlog.revision(node)
789 delta = revlog.revision(node)
790 prefix = mdiff.trivialdiffheader(len(delta))
790 prefix = mdiff.trivialdiffheader(len(delta))
791 else:
791 else:
792 delta = revlog.revdiff(base, rev)
792 delta = revlog.revdiff(base, rev)
793 p1n, p2n = revlog.parents(node)
793 p1n, p2n = revlog.parents(node)
794 basenode = revlog.node(base)
794 basenode = revlog.node(base)
795 flags = revlog.flags(rev)
795 flags = revlog.flags(rev)
796 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
796 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
797 meta += prefix
797 meta += prefix
798 l = len(meta) + len(delta)
798 l = len(meta) + len(delta)
799 yield chunkheader(l)
799 yield chunkheader(l)
800 yield meta
800 yield meta
801 yield delta
801 yield delta
802 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
802 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
803 # do nothing with basenode, it is implicitly the previous one in HG10
803 # do nothing with basenode, it is implicitly the previous one in HG10
804 # do nothing with flags, it is implicitly 0 for cg1 and cg2
804 # do nothing with flags, it is implicitly 0 for cg1 and cg2
805 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
805 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
806
806
807 class cg2packer(cg1packer):
807 class cg2packer(cg1packer):
808 version = '02'
808 version = '02'
809 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
809 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
810
810
811 def __init__(self, repo, bundlecaps=None):
811 def __init__(self, repo, bundlecaps=None):
812 super(cg2packer, self).__init__(repo, bundlecaps)
812 super(cg2packer, self).__init__(repo, bundlecaps)
813 if self._reorder is None:
813 if self._reorder is None:
814 # Since generaldelta is directly supported by cg2, reordering
814 # Since generaldelta is directly supported by cg2, reordering
815 # generally doesn't help, so we disable it by default (treating
815 # generally doesn't help, so we disable it by default (treating
816 # bundle.reorder=auto just like bundle.reorder=False).
816 # bundle.reorder=auto just like bundle.reorder=False).
817 self._reorder = False
817 self._reorder = False
818
818
819 def deltaparent(self, revlog, rev, p1, p2, prev):
819 def deltaparent(self, revlog, rev, p1, p2, prev):
820 dp = revlog.deltaparent(rev)
820 dp = revlog.deltaparent(rev)
821 # avoid storing full revisions; pick prev in those cases
821 # avoid storing full revisions; pick prev in those cases
822 # also pick prev when we can't be sure remote has dp
822 # also pick prev when we can't be sure remote has dp
823 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
823 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
824 return prev
824 return prev
825 return dp
825 return dp
826
826
827 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
827 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
828 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
828 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
829 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
829 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
830
830
831 class cg3packer(cg2packer):
831 class cg3packer(cg2packer):
832 version = '03'
832 version = '03'
833 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
833 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
834
834
835 def _packmanifests(self, dir, mfnodes, lookuplinknode):
835 def _packmanifests(self, dir, mfnodes, lookuplinknode):
836 if dir:
836 if dir:
837 yield self.fileheader(dir)
837 yield self.fileheader(dir)
838 for chunk in self.group(mfnodes, self._repo.manifest.dirlog(dir),
838 for chunk in self.group(mfnodes, self._repo.manifest.dirlog(dir),
839 lookuplinknode, units=_('manifests')):
839 lookuplinknode, units=_('manifests')):
840 yield chunk
840 yield chunk
841
841
842 def _manifestsdone(self):
842 def _manifestsdone(self):
843 return self.close()
843 return self.close()
844
844
845 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
845 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
846 return struct.pack(
846 return struct.pack(
847 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
847 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
848
848
849 _packermap = {'01': (cg1packer, cg1unpacker),
849 _packermap = {'01': (cg1packer, cg1unpacker),
850 # cg2 adds support for exchanging generaldelta
850 # cg2 adds support for exchanging generaldelta
851 '02': (cg2packer, cg2unpacker),
851 '02': (cg2packer, cg2unpacker),
852 # cg3 adds support for exchanging revlog flags and treemanifests
852 # cg3 adds support for exchanging revlog flags and treemanifests
853 '03': (cg3packer, cg3unpacker),
853 '03': (cg3packer, cg3unpacker),
854 }
854 }
855
855
856 def allsupportedversions(ui):
856 def allsupportedversions(ui):
857 versions = set(_packermap.keys())
857 versions = set(_packermap.keys())
858 versions.discard('03')
858 versions.discard('03')
859 if (ui.configbool('experimental', 'changegroup3') or
859 if (ui.configbool('experimental', 'changegroup3') or
860 ui.configbool('experimental', 'treemanifest')):
860 ui.configbool('experimental', 'treemanifest')):
861 versions.add('03')
861 versions.add('03')
862 return versions
862 return versions
863
863
864 # Changegroup versions that can be applied to the repo
864 # Changegroup versions that can be applied to the repo
865 def supportedincomingversions(repo):
865 def supportedincomingversions(repo):
866 versions = allsupportedversions(repo.ui)
866 versions = allsupportedversions(repo.ui)
867 if 'treemanifest' in repo.requirements:
867 if 'treemanifest' in repo.requirements:
868 versions.add('03')
868 versions.add('03')
869 return versions
869 return versions
870
870
871 # Changegroup versions that can be created from the repo
871 # Changegroup versions that can be created from the repo
872 def supportedoutgoingversions(repo):
872 def supportedoutgoingversions(repo):
873 versions = allsupportedversions(repo.ui)
873 versions = allsupportedversions(repo.ui)
874 if 'treemanifest' in repo.requirements:
874 if 'treemanifest' in repo.requirements:
875 # Versions 01 and 02 support only flat manifests and it's just too
875 # Versions 01 and 02 support only flat manifests and it's just too
876 # expensive to convert between the flat manifest and tree manifest on
876 # expensive to convert between the flat manifest and tree manifest on
877 # the fly. Since tree manifests are hashed differently, all of history
877 # the fly. Since tree manifests are hashed differently, all of history
878 # would have to be converted. Instead, we simply don't even pretend to
878 # would have to be converted. Instead, we simply don't even pretend to
879 # support versions 01 and 02.
879 # support versions 01 and 02.
880 versions.discard('01')
880 versions.discard('01')
881 versions.discard('02')
881 versions.discard('02')
882 versions.add('03')
882 versions.add('03')
883 return versions
883 return versions
884
884
885 def safeversion(repo):
885 def safeversion(repo):
886 # Finds the smallest version that it's safe to assume clients of the repo
886 # Finds the smallest version that it's safe to assume clients of the repo
887 # will support. For example, all hg versions that support generaldelta also
887 # will support. For example, all hg versions that support generaldelta also
888 # support changegroup 02.
888 # support changegroup 02.
889 versions = supportedoutgoingversions(repo)
889 versions = supportedoutgoingversions(repo)
890 if 'generaldelta' in repo.requirements:
890 if 'generaldelta' in repo.requirements:
891 versions.discard('01')
891 versions.discard('01')
892 assert versions
892 assert versions
893 return min(versions)
893 return min(versions)
894
894
895 def getbundler(version, repo, bundlecaps=None):
895 def getbundler(version, repo, bundlecaps=None):
896 assert version in supportedoutgoingversions(repo)
896 assert version in supportedoutgoingversions(repo)
897 return _packermap[version][0](repo, bundlecaps)
897 return _packermap[version][0](repo, bundlecaps)
898
898
899 def getunbundler(version, fh, alg, extras=None):
899 def getunbundler(version, fh, alg, extras=None):
900 return _packermap[version][1](fh, alg, extras=extras)
900 return _packermap[version][1](fh, alg, extras=extras)
901
901
902 def _changegroupinfo(repo, nodes, source):
902 def _changegroupinfo(repo, nodes, source):
903 if repo.ui.verbose or source == 'bundle':
903 if repo.ui.verbose or source == 'bundle':
904 repo.ui.status(_("%d changesets found\n") % len(nodes))
904 repo.ui.status(_("%d changesets found\n") % len(nodes))
905 if repo.ui.debugflag:
905 if repo.ui.debugflag:
906 repo.ui.debug("list of changesets:\n")
906 repo.ui.debug("list of changesets:\n")
907 for node in nodes:
907 for node in nodes:
908 repo.ui.debug("%s\n" % hex(node))
908 repo.ui.debug("%s\n" % hex(node))
909
909
910 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
910 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
911 repo = repo.unfiltered()
911 repo = repo.unfiltered()
912 commonrevs = outgoing.common
912 commonrevs = outgoing.common
913 csets = outgoing.missing
913 csets = outgoing.missing
914 heads = outgoing.missingheads
914 heads = outgoing.missingheads
915 # We go through the fast path if we get told to, or if all (unfiltered
915 # We go through the fast path if we get told to, or if all (unfiltered
916 # heads have been requested (since we then know there all linkrevs will
916 # heads have been requested (since we then know there all linkrevs will
917 # be pulled by the client).
917 # be pulled by the client).
918 heads.sort()
918 heads.sort()
919 fastpathlinkrev = fastpath or (
919 fastpathlinkrev = fastpath or (
920 repo.filtername is None and heads == sorted(repo.heads()))
920 repo.filtername is None and heads == sorted(repo.heads()))
921
921
922 repo.hook('preoutgoing', throw=True, source=source)
922 repo.hook('preoutgoing', throw=True, source=source)
923 _changegroupinfo(repo, csets, source)
923 _changegroupinfo(repo, csets, source)
924 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
924 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
925
925
926 def getsubset(repo, outgoing, bundler, source, fastpath=False):
926 def getsubset(repo, outgoing, bundler, source, fastpath=False):
927 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
927 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
928 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
928 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
929 {'clcount': len(outgoing.missing)})
929 {'clcount': len(outgoing.missing)})
930
930
931 def changegroupsubset(repo, roots, heads, source, version='01'):
931 def changegroupsubset(repo, roots, heads, source, version='01'):
932 """Compute a changegroup consisting of all the nodes that are
932 """Compute a changegroup consisting of all the nodes that are
933 descendants of any of the roots and ancestors of any of the heads.
933 descendants of any of the roots and ancestors of any of the heads.
934 Return a chunkbuffer object whose read() method will return
934 Return a chunkbuffer object whose read() method will return
935 successive changegroup chunks.
935 successive changegroup chunks.
936
936
937 It is fairly complex as determining which filenodes and which
937 It is fairly complex as determining which filenodes and which
938 manifest nodes need to be included for the changeset to be complete
938 manifest nodes need to be included for the changeset to be complete
939 is non-trivial.
939 is non-trivial.
940
940
941 Another wrinkle is doing the reverse, figuring out which changeset in
941 Another wrinkle is doing the reverse, figuring out which changeset in
942 the changegroup a particular filenode or manifestnode belongs to.
942 the changegroup a particular filenode or manifestnode belongs to.
943 """
943 """
944 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
944 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
945 bundler = getbundler(version, repo)
945 bundler = getbundler(version, repo)
946 return getsubset(repo, outgoing, bundler, source)
946 return getsubset(repo, outgoing, bundler, source)
947
947
948 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
948 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
949 version='01'):
949 version='01'):
950 """Like getbundle, but taking a discovery.outgoing as an argument.
950 """Like getbundle, but taking a discovery.outgoing as an argument.
951
951
952 This is only implemented for local repos and reuses potentially
952 This is only implemented for local repos and reuses potentially
953 precomputed sets in outgoing. Returns a raw changegroup generator."""
953 precomputed sets in outgoing. Returns a raw changegroup generator."""
954 if not outgoing.missing:
954 if not outgoing.missing:
955 return None
955 return None
956 bundler = getbundler(version, repo, bundlecaps)
956 bundler = getbundler(version, repo, bundlecaps)
957 return getsubsetraw(repo, outgoing, bundler, source)
957 return getsubsetraw(repo, outgoing, bundler, source)
958
958
959 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
959 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
960 version='01'):
960 version='01'):
961 """Like getbundle, but taking a discovery.outgoing as an argument.
961 """Like getbundle, but taking a discovery.outgoing as an argument.
962
962
963 This is only implemented for local repos and reuses potentially
963 This is only implemented for local repos and reuses potentially
964 precomputed sets in outgoing."""
964 precomputed sets in outgoing."""
965 if not outgoing.missing:
965 if not outgoing.missing:
966 return None
966 return None
967 bundler = getbundler(version, repo, bundlecaps)
967 bundler = getbundler(version, repo, bundlecaps)
968 return getsubset(repo, outgoing, bundler, source)
968 return getsubset(repo, outgoing, bundler, source)
969
969
970 def getchangegroup(repo, source, outgoing, bundlecaps=None,
970 def getchangegroup(repo, source, outgoing, bundlecaps=None,
971 version='01'):
971 version='01'):
972 """Like changegroupsubset, but returns the set difference between the
972 """Like changegroupsubset, but returns the set difference between the
973 ancestors of heads and the ancestors common.
973 ancestors of heads and the ancestors common.
974
974
975 If heads is None, use the local heads. If common is None, use [nullid].
975 If heads is None, use the local heads. If common is None, use [nullid].
976
976
977 The nodes in common might not all be known locally due to the way the
977 The nodes in common might not all be known locally due to the way the
978 current discovery protocol works.
978 current discovery protocol works.
979 """
979 """
980 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
980 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
981 version=version)
981 version=version)
982
982
983 def changegroup(repo, basenodes, source):
983 def changegroup(repo, basenodes, source):
984 # to avoid a race we use changegroupsubset() (issue1320)
984 # to avoid a race we use changegroupsubset() (issue1320)
985 return changegroupsubset(repo, basenodes, repo.heads(), source)
985 return changegroupsubset(repo, basenodes, repo.heads(), source)
986
986
987 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
987 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
988 revisions = 0
988 revisions = 0
989 files = 0
989 files = 0
990 for chunkdata in iter(source.filelogheader, {}):
990 for chunkdata in iter(source.filelogheader, {}):
991 files += 1
991 files += 1
992 f = chunkdata["filename"]
992 f = chunkdata["filename"]
993 repo.ui.debug("adding %s revisions\n" % f)
993 repo.ui.debug("adding %s revisions\n" % f)
994 repo.ui.progress(_('files'), files, unit=_('files'),
994 repo.ui.progress(_('files'), files, unit=_('files'),
995 total=expectedfiles)
995 total=expectedfiles)
996 fl = repo.file(f)
996 fl = repo.file(f)
997 o = len(fl)
997 o = len(fl)
998 try:
998 try:
999 if not fl.addgroup(source, revmap, trp):
999 if not fl.addgroup(source, revmap, trp):
1000 raise error.Abort(_("received file revlog group is empty"))
1000 raise error.Abort(_("received file revlog group is empty"))
1001 except error.CensoredBaseError as e:
1001 except error.CensoredBaseError as e:
1002 raise error.Abort(_("received delta base is censored: %s") % e)
1002 raise error.Abort(_("received delta base is censored: %s") % e)
1003 revisions += len(fl) - o
1003 revisions += len(fl) - o
1004 if f in needfiles:
1004 if f in needfiles:
1005 needs = needfiles[f]
1005 needs = needfiles[f]
1006 for new in xrange(o, len(fl)):
1006 for new in xrange(o, len(fl)):
1007 n = fl.node(new)
1007 n = fl.node(new)
1008 if n in needs:
1008 if n in needs:
1009 needs.remove(n)
1009 needs.remove(n)
1010 else:
1010 else:
1011 raise error.Abort(
1011 raise error.Abort(
1012 _("received spurious file revlog entry"))
1012 _("received spurious file revlog entry"))
1013 if not needs:
1013 if not needs:
1014 del needfiles[f]
1014 del needfiles[f]
1015 repo.ui.progress(_('files'), None)
1015 repo.ui.progress(_('files'), None)
1016
1016
1017 for f, needs in needfiles.iteritems():
1017 for f, needs in needfiles.iteritems():
1018 fl = repo.file(f)
1018 fl = repo.file(f)
1019 for n in needs:
1019 for n in needs:
1020 try:
1020 try:
1021 fl.rev(n)
1021 fl.rev(n)
1022 except error.LookupError:
1022 except error.LookupError:
1023 raise error.Abort(
1023 raise error.Abort(
1024 _('missing file data for %s:%s - run hg verify') %
1024 _('missing file data for %s:%s - run hg verify') %
1025 (f, hex(n)))
1025 (f, hex(n)))
1026
1026
1027 return revisions, files
1027 return revisions, files
@@ -1,1984 +1,1985 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 bin,
17 bin,
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirid,
22 wdirid,
23 )
23 )
24 from . import (
24 from . import (
25 encoding,
25 encoding,
26 error,
26 error,
27 fileset,
27 fileset,
28 match as matchmod,
28 match as matchmod,
29 mdiff,
29 mdiff,
30 obsolete as obsmod,
30 obsolete as obsmod,
31 patch,
31 patch,
32 phases,
32 phases,
33 repoview,
33 repoview,
34 revlog,
34 revlog,
35 scmutil,
35 scmutil,
36 subrepo,
36 subrepo,
37 util,
37 util,
38 )
38 )
39
39
40 propertycache = util.propertycache
40 propertycache = util.propertycache
41
41
42 # Phony node value to stand-in for new files in some uses of
42 # Phony node value to stand-in for new files in some uses of
43 # manifests. Manifests support 21-byte hashes for nodes which are
43 # manifests. Manifests support 21-byte hashes for nodes which are
44 # dirty in the working copy.
44 # dirty in the working copy.
45 _newnode = '!' * 21
45 _newnode = '!' * 21
46
46
47 nonascii = re.compile(r'[^\x21-\x7f]').search
47 nonascii = re.compile(r'[^\x21-\x7f]').search
48
48
49 class basectx(object):
49 class basectx(object):
50 """A basectx object represents the common logic for its children:
50 """A basectx object represents the common logic for its children:
51 changectx: read-only context that is already present in the repo,
51 changectx: read-only context that is already present in the repo,
52 workingctx: a context that represents the working directory and can
52 workingctx: a context that represents the working directory and can
53 be committed,
53 be committed,
54 memctx: a context that represents changes in-memory and can also
54 memctx: a context that represents changes in-memory and can also
55 be committed."""
55 be committed."""
56 def __new__(cls, repo, changeid='', *args, **kwargs):
56 def __new__(cls, repo, changeid='', *args, **kwargs):
57 if isinstance(changeid, basectx):
57 if isinstance(changeid, basectx):
58 return changeid
58 return changeid
59
59
60 o = super(basectx, cls).__new__(cls)
60 o = super(basectx, cls).__new__(cls)
61
61
62 o._repo = repo
62 o._repo = repo
63 o._rev = nullrev
63 o._rev = nullrev
64 o._node = nullid
64 o._node = nullid
65
65
66 return o
66 return o
67
67
68 def __str__(self):
68 def __str__(self):
69 return short(self.node())
69 return short(self.node())
70
70
71 def __int__(self):
71 def __int__(self):
72 return self.rev()
72 return self.rev()
73
73
74 def __repr__(self):
74 def __repr__(self):
75 return "<%s %s>" % (type(self).__name__, str(self))
75 return "<%s %s>" % (type(self).__name__, str(self))
76
76
77 def __eq__(self, other):
77 def __eq__(self, other):
78 try:
78 try:
79 return type(self) == type(other) and self._rev == other._rev
79 return type(self) == type(other) and self._rev == other._rev
80 except AttributeError:
80 except AttributeError:
81 return False
81 return False
82
82
83 def __ne__(self, other):
83 def __ne__(self, other):
84 return not (self == other)
84 return not (self == other)
85
85
86 def __contains__(self, key):
86 def __contains__(self, key):
87 return key in self._manifest
87 return key in self._manifest
88
88
89 def __getitem__(self, key):
89 def __getitem__(self, key):
90 return self.filectx(key)
90 return self.filectx(key)
91
91
92 def __iter__(self):
92 def __iter__(self):
93 return iter(self._manifest)
93 return iter(self._manifest)
94
94
95 def _manifestmatches(self, match, s):
95 def _manifestmatches(self, match, s):
96 """generate a new manifest filtered by the match argument
96 """generate a new manifest filtered by the match argument
97
97
98 This method is for internal use only and mainly exists to provide an
98 This method is for internal use only and mainly exists to provide an
99 object oriented way for other contexts to customize the manifest
99 object oriented way for other contexts to customize the manifest
100 generation.
100 generation.
101 """
101 """
102 return self.manifest().matches(match)
102 return self.manifest().matches(match)
103
103
104 def _matchstatus(self, other, match):
104 def _matchstatus(self, other, match):
105 """return match.always if match is none
105 """return match.always if match is none
106
106
107 This internal method provides a way for child objects to override the
107 This internal method provides a way for child objects to override the
108 match operator.
108 match operator.
109 """
109 """
110 return match or matchmod.always(self._repo.root, self._repo.getcwd())
110 return match or matchmod.always(self._repo.root, self._repo.getcwd())
111
111
112 def _buildstatus(self, other, s, match, listignored, listclean,
112 def _buildstatus(self, other, s, match, listignored, listclean,
113 listunknown):
113 listunknown):
114 """build a status with respect to another context"""
114 """build a status with respect to another context"""
115 # Load earliest manifest first for caching reasons. More specifically,
115 # Load earliest manifest first for caching reasons. More specifically,
116 # if you have revisions 1000 and 1001, 1001 is probably stored as a
116 # if you have revisions 1000 and 1001, 1001 is probably stored as a
117 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
117 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
118 # 1000 and cache it so that when you read 1001, we just need to apply a
118 # 1000 and cache it so that when you read 1001, we just need to apply a
119 # delta to what's in the cache. So that's one full reconstruction + one
119 # delta to what's in the cache. So that's one full reconstruction + one
120 # delta application.
120 # delta application.
121 if self.rev() is not None and self.rev() < other.rev():
121 if self.rev() is not None and self.rev() < other.rev():
122 self.manifest()
122 self.manifest()
123 mf1 = other._manifestmatches(match, s)
123 mf1 = other._manifestmatches(match, s)
124 mf2 = self._manifestmatches(match, s)
124 mf2 = self._manifestmatches(match, s)
125
125
126 modified, added = [], []
126 modified, added = [], []
127 removed = []
127 removed = []
128 clean = []
128 clean = []
129 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
129 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
130 deletedset = set(deleted)
130 deletedset = set(deleted)
131 d = mf1.diff(mf2, clean=listclean)
131 d = mf1.diff(mf2, clean=listclean)
132 for fn, value in d.iteritems():
132 for fn, value in d.iteritems():
133 if fn in deletedset:
133 if fn in deletedset:
134 continue
134 continue
135 if value is None:
135 if value is None:
136 clean.append(fn)
136 clean.append(fn)
137 continue
137 continue
138 (node1, flag1), (node2, flag2) = value
138 (node1, flag1), (node2, flag2) = value
139 if node1 is None:
139 if node1 is None:
140 added.append(fn)
140 added.append(fn)
141 elif node2 is None:
141 elif node2 is None:
142 removed.append(fn)
142 removed.append(fn)
143 elif flag1 != flag2:
143 elif flag1 != flag2:
144 modified.append(fn)
144 modified.append(fn)
145 elif node2 != _newnode:
145 elif node2 != _newnode:
146 # When comparing files between two commits, we save time by
146 # When comparing files between two commits, we save time by
147 # not comparing the file contents when the nodeids differ.
147 # not comparing the file contents when the nodeids differ.
148 # Note that this means we incorrectly report a reverted change
148 # Note that this means we incorrectly report a reverted change
149 # to a file as a modification.
149 # to a file as a modification.
150 modified.append(fn)
150 modified.append(fn)
151 elif self[fn].cmp(other[fn]):
151 elif self[fn].cmp(other[fn]):
152 modified.append(fn)
152 modified.append(fn)
153 else:
153 else:
154 clean.append(fn)
154 clean.append(fn)
155
155
156 if removed:
156 if removed:
157 # need to filter files if they are already reported as removed
157 # need to filter files if they are already reported as removed
158 unknown = [fn for fn in unknown if fn not in mf1]
158 unknown = [fn for fn in unknown if fn not in mf1]
159 ignored = [fn for fn in ignored if fn not in mf1]
159 ignored = [fn for fn in ignored if fn not in mf1]
160 # if they're deleted, don't report them as removed
160 # if they're deleted, don't report them as removed
161 removed = [fn for fn in removed if fn not in deletedset]
161 removed = [fn for fn in removed if fn not in deletedset]
162
162
163 return scmutil.status(modified, added, removed, deleted, unknown,
163 return scmutil.status(modified, added, removed, deleted, unknown,
164 ignored, clean)
164 ignored, clean)
165
165
166 @propertycache
166 @propertycache
167 def substate(self):
167 def substate(self):
168 return subrepo.state(self, self._repo.ui)
168 return subrepo.state(self, self._repo.ui)
169
169
170 def subrev(self, subpath):
170 def subrev(self, subpath):
171 return self.substate[subpath][1]
171 return self.substate[subpath][1]
172
172
173 def rev(self):
173 def rev(self):
174 return self._rev
174 return self._rev
175 def node(self):
175 def node(self):
176 return self._node
176 return self._node
177 def hex(self):
177 def hex(self):
178 return hex(self.node())
178 return hex(self.node())
179 def manifest(self):
179 def manifest(self):
180 return self._manifest
180 return self._manifest
181 def repo(self):
181 def repo(self):
182 return self._repo
182 return self._repo
183 def phasestr(self):
183 def phasestr(self):
184 return phases.phasenames[self.phase()]
184 return phases.phasenames[self.phase()]
185 def mutable(self):
185 def mutable(self):
186 return self.phase() > phases.public
186 return self.phase() > phases.public
187
187
188 def getfileset(self, expr):
188 def getfileset(self, expr):
189 return fileset.getfileset(self, expr)
189 return fileset.getfileset(self, expr)
190
190
191 def obsolete(self):
191 def obsolete(self):
192 """True if the changeset is obsolete"""
192 """True if the changeset is obsolete"""
193 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
193 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
194
194
195 def extinct(self):
195 def extinct(self):
196 """True if the changeset is extinct"""
196 """True if the changeset is extinct"""
197 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
197 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
198
198
199 def unstable(self):
199 def unstable(self):
200 """True if the changeset is not obsolete but it's ancestor are"""
200 """True if the changeset is not obsolete but it's ancestor are"""
201 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
201 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
202
202
203 def bumped(self):
203 def bumped(self):
204 """True if the changeset try to be a successor of a public changeset
204 """True if the changeset try to be a successor of a public changeset
205
205
206 Only non-public and non-obsolete changesets may be bumped.
206 Only non-public and non-obsolete changesets may be bumped.
207 """
207 """
208 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
208 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
209
209
210 def divergent(self):
210 def divergent(self):
211 """Is a successors of a changeset with multiple possible successors set
211 """Is a successors of a changeset with multiple possible successors set
212
212
213 Only non-public and non-obsolete changesets may be divergent.
213 Only non-public and non-obsolete changesets may be divergent.
214 """
214 """
215 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
215 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
216
216
217 def troubled(self):
217 def troubled(self):
218 """True if the changeset is either unstable, bumped or divergent"""
218 """True if the changeset is either unstable, bumped or divergent"""
219 return self.unstable() or self.bumped() or self.divergent()
219 return self.unstable() or self.bumped() or self.divergent()
220
220
221 def troubles(self):
221 def troubles(self):
222 """return the list of troubles affecting this changesets.
222 """return the list of troubles affecting this changesets.
223
223
224 Troubles are returned as strings. possible values are:
224 Troubles are returned as strings. possible values are:
225 - unstable,
225 - unstable,
226 - bumped,
226 - bumped,
227 - divergent.
227 - divergent.
228 """
228 """
229 troubles = []
229 troubles = []
230 if self.unstable():
230 if self.unstable():
231 troubles.append('unstable')
231 troubles.append('unstable')
232 if self.bumped():
232 if self.bumped():
233 troubles.append('bumped')
233 troubles.append('bumped')
234 if self.divergent():
234 if self.divergent():
235 troubles.append('divergent')
235 troubles.append('divergent')
236 return troubles
236 return troubles
237
237
238 def parents(self):
238 def parents(self):
239 """return contexts for each parent changeset"""
239 """return contexts for each parent changeset"""
240 return self._parents
240 return self._parents
241
241
242 def p1(self):
242 def p1(self):
243 return self._parents[0]
243 return self._parents[0]
244
244
245 def p2(self):
245 def p2(self):
246 parents = self._parents
246 parents = self._parents
247 if len(parents) == 2:
247 if len(parents) == 2:
248 return parents[1]
248 return parents[1]
249 return changectx(self._repo, nullrev)
249 return changectx(self._repo, nullrev)
250
250
251 def _fileinfo(self, path):
251 def _fileinfo(self, path):
252 if '_manifest' in self.__dict__:
252 if '_manifest' in self.__dict__:
253 try:
253 try:
254 return self._manifest[path], self._manifest.flags(path)
254 return self._manifest[path], self._manifest.flags(path)
255 except KeyError:
255 except KeyError:
256 raise error.ManifestLookupError(self._node, path,
256 raise error.ManifestLookupError(self._node, path,
257 _('not found in manifest'))
257 _('not found in manifest'))
258 if '_manifestdelta' in self.__dict__ or path in self.files():
258 if '_manifestdelta' in self.__dict__ or path in self.files():
259 if path in self._manifestdelta:
259 if path in self._manifestdelta:
260 return (self._manifestdelta[path],
260 return (self._manifestdelta[path],
261 self._manifestdelta.flags(path))
261 self._manifestdelta.flags(path))
262 node, flag = self._repo.manifest.find(self._changeset.manifest, path)
262 node, flag = self._repo.manifest.find(self._changeset.manifest, path)
263 if not node:
263 if not node:
264 raise error.ManifestLookupError(self._node, path,
264 raise error.ManifestLookupError(self._node, path,
265 _('not found in manifest'))
265 _('not found in manifest'))
266
266
267 return node, flag
267 return node, flag
268
268
269 def filenode(self, path):
269 def filenode(self, path):
270 return self._fileinfo(path)[0]
270 return self._fileinfo(path)[0]
271
271
272 def flags(self, path):
272 def flags(self, path):
273 try:
273 try:
274 return self._fileinfo(path)[1]
274 return self._fileinfo(path)[1]
275 except error.LookupError:
275 except error.LookupError:
276 return ''
276 return ''
277
277
278 def sub(self, path, allowcreate=True):
278 def sub(self, path, allowcreate=True):
279 '''return a subrepo for the stored revision of path, never wdir()'''
279 '''return a subrepo for the stored revision of path, never wdir()'''
280 return subrepo.subrepo(self, path, allowcreate=allowcreate)
280 return subrepo.subrepo(self, path, allowcreate=allowcreate)
281
281
282 def nullsub(self, path, pctx):
282 def nullsub(self, path, pctx):
283 return subrepo.nullsubrepo(self, path, pctx)
283 return subrepo.nullsubrepo(self, path, pctx)
284
284
285 def workingsub(self, path):
285 def workingsub(self, path):
286 '''return a subrepo for the stored revision, or wdir if this is a wdir
286 '''return a subrepo for the stored revision, or wdir if this is a wdir
287 context.
287 context.
288 '''
288 '''
289 return subrepo.subrepo(self, path, allowwdir=True)
289 return subrepo.subrepo(self, path, allowwdir=True)
290
290
291 def match(self, pats=[], include=None, exclude=None, default='glob',
291 def match(self, pats=[], include=None, exclude=None, default='glob',
292 listsubrepos=False, badfn=None):
292 listsubrepos=False, badfn=None):
293 r = self._repo
293 r = self._repo
294 return matchmod.match(r.root, r.getcwd(), pats,
294 return matchmod.match(r.root, r.getcwd(), pats,
295 include, exclude, default,
295 include, exclude, default,
296 auditor=r.nofsauditor, ctx=self,
296 auditor=r.nofsauditor, ctx=self,
297 listsubrepos=listsubrepos, badfn=badfn)
297 listsubrepos=listsubrepos, badfn=badfn)
298
298
299 def diff(self, ctx2=None, match=None, **opts):
299 def diff(self, ctx2=None, match=None, **opts):
300 """Returns a diff generator for the given contexts and matcher"""
300 """Returns a diff generator for the given contexts and matcher"""
301 if ctx2 is None:
301 if ctx2 is None:
302 ctx2 = self.p1()
302 ctx2 = self.p1()
303 if ctx2 is not None:
303 if ctx2 is not None:
304 ctx2 = self._repo[ctx2]
304 ctx2 = self._repo[ctx2]
305 diffopts = patch.diffopts(self._repo.ui, opts)
305 diffopts = patch.diffopts(self._repo.ui, opts)
306 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
306 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
307
307
308 def dirs(self):
308 def dirs(self):
309 return self._manifest.dirs()
309 return self._manifest.dirs()
310
310
311 def hasdir(self, dir):
311 def hasdir(self, dir):
312 return self._manifest.hasdir(dir)
312 return self._manifest.hasdir(dir)
313
313
314 def dirty(self, missing=False, merge=True, branch=True):
314 def dirty(self, missing=False, merge=True, branch=True):
315 return False
315 return False
316
316
317 def status(self, other=None, match=None, listignored=False,
317 def status(self, other=None, match=None, listignored=False,
318 listclean=False, listunknown=False, listsubrepos=False):
318 listclean=False, listunknown=False, listsubrepos=False):
319 """return status of files between two nodes or node and working
319 """return status of files between two nodes or node and working
320 directory.
320 directory.
321
321
322 If other is None, compare this node with working directory.
322 If other is None, compare this node with working directory.
323
323
324 returns (modified, added, removed, deleted, unknown, ignored, clean)
324 returns (modified, added, removed, deleted, unknown, ignored, clean)
325 """
325 """
326
326
327 ctx1 = self
327 ctx1 = self
328 ctx2 = self._repo[other]
328 ctx2 = self._repo[other]
329
329
330 # This next code block is, admittedly, fragile logic that tests for
330 # This next code block is, admittedly, fragile logic that tests for
331 # reversing the contexts and wouldn't need to exist if it weren't for
331 # reversing the contexts and wouldn't need to exist if it weren't for
332 # the fast (and common) code path of comparing the working directory
332 # the fast (and common) code path of comparing the working directory
333 # with its first parent.
333 # with its first parent.
334 #
334 #
335 # What we're aiming for here is the ability to call:
335 # What we're aiming for here is the ability to call:
336 #
336 #
337 # workingctx.status(parentctx)
337 # workingctx.status(parentctx)
338 #
338 #
339 # If we always built the manifest for each context and compared those,
339 # If we always built the manifest for each context and compared those,
340 # then we'd be done. But the special case of the above call means we
340 # then we'd be done. But the special case of the above call means we
341 # just copy the manifest of the parent.
341 # just copy the manifest of the parent.
342 reversed = False
342 reversed = False
343 if (not isinstance(ctx1, changectx)
343 if (not isinstance(ctx1, changectx)
344 and isinstance(ctx2, changectx)):
344 and isinstance(ctx2, changectx)):
345 reversed = True
345 reversed = True
346 ctx1, ctx2 = ctx2, ctx1
346 ctx1, ctx2 = ctx2, ctx1
347
347
348 match = ctx2._matchstatus(ctx1, match)
348 match = ctx2._matchstatus(ctx1, match)
349 r = scmutil.status([], [], [], [], [], [], [])
349 r = scmutil.status([], [], [], [], [], [], [])
350 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
350 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
351 listunknown)
351 listunknown)
352
352
353 if reversed:
353 if reversed:
354 # Reverse added and removed. Clear deleted, unknown and ignored as
354 # Reverse added and removed. Clear deleted, unknown and ignored as
355 # these make no sense to reverse.
355 # these make no sense to reverse.
356 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
356 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
357 r.clean)
357 r.clean)
358
358
359 if listsubrepos:
359 if listsubrepos:
360 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
360 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
361 try:
361 try:
362 rev2 = ctx2.subrev(subpath)
362 rev2 = ctx2.subrev(subpath)
363 except KeyError:
363 except KeyError:
364 # A subrepo that existed in node1 was deleted between
364 # A subrepo that existed in node1 was deleted between
365 # node1 and node2 (inclusive). Thus, ctx2's substate
365 # node1 and node2 (inclusive). Thus, ctx2's substate
366 # won't contain that subpath. The best we can do ignore it.
366 # won't contain that subpath. The best we can do ignore it.
367 rev2 = None
367 rev2 = None
368 submatch = matchmod.subdirmatcher(subpath, match)
368 submatch = matchmod.subdirmatcher(subpath, match)
369 s = sub.status(rev2, match=submatch, ignored=listignored,
369 s = sub.status(rev2, match=submatch, ignored=listignored,
370 clean=listclean, unknown=listunknown,
370 clean=listclean, unknown=listunknown,
371 listsubrepos=True)
371 listsubrepos=True)
372 for rfiles, sfiles in zip(r, s):
372 for rfiles, sfiles in zip(r, s):
373 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
373 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
374
374
375 for l in r:
375 for l in r:
376 l.sort()
376 l.sort()
377
377
378 return r
378 return r
379
379
380
380
381 def makememctx(repo, parents, text, user, date, branch, files, store,
381 def makememctx(repo, parents, text, user, date, branch, files, store,
382 editor=None, extra=None):
382 editor=None, extra=None):
383 def getfilectx(repo, memctx, path):
383 def getfilectx(repo, memctx, path):
384 data, mode, copied = store.getfile(path)
384 data, mode, copied = store.getfile(path)
385 if data is None:
385 if data is None:
386 return None
386 return None
387 islink, isexec = mode
387 islink, isexec = mode
388 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
388 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
389 copied=copied, memctx=memctx)
389 copied=copied, memctx=memctx)
390 if extra is None:
390 if extra is None:
391 extra = {}
391 extra = {}
392 if branch:
392 if branch:
393 extra['branch'] = encoding.fromlocal(branch)
393 extra['branch'] = encoding.fromlocal(branch)
394 ctx = memctx(repo, parents, text, files, getfilectx, user,
394 ctx = memctx(repo, parents, text, files, getfilectx, user,
395 date, extra, editor)
395 date, extra, editor)
396 return ctx
396 return ctx
397
397
398 class changectx(basectx):
398 class changectx(basectx):
399 """A changecontext object makes access to data related to a particular
399 """A changecontext object makes access to data related to a particular
400 changeset convenient. It represents a read-only context already present in
400 changeset convenient. It represents a read-only context already present in
401 the repo."""
401 the repo."""
402 def __init__(self, repo, changeid=''):
402 def __init__(self, repo, changeid=''):
403 """changeid is a revision number, node, or tag"""
403 """changeid is a revision number, node, or tag"""
404
404
405 # since basectx.__new__ already took care of copying the object, we
405 # since basectx.__new__ already took care of copying the object, we
406 # don't need to do anything in __init__, so we just exit here
406 # don't need to do anything in __init__, so we just exit here
407 if isinstance(changeid, basectx):
407 if isinstance(changeid, basectx):
408 return
408 return
409
409
410 if changeid == '':
410 if changeid == '':
411 changeid = '.'
411 changeid = '.'
412 self._repo = repo
412 self._repo = repo
413
413
414 try:
414 try:
415 if isinstance(changeid, int):
415 if isinstance(changeid, int):
416 self._node = repo.changelog.node(changeid)
416 self._node = repo.changelog.node(changeid)
417 self._rev = changeid
417 self._rev = changeid
418 return
418 return
419 if isinstance(changeid, long):
419 if isinstance(changeid, long):
420 changeid = str(changeid)
420 changeid = str(changeid)
421 if changeid == 'null':
421 if changeid == 'null':
422 self._node = nullid
422 self._node = nullid
423 self._rev = nullrev
423 self._rev = nullrev
424 return
424 return
425 if changeid == 'tip':
425 if changeid == 'tip':
426 self._node = repo.changelog.tip()
426 self._node = repo.changelog.tip()
427 self._rev = repo.changelog.rev(self._node)
427 self._rev = repo.changelog.rev(self._node)
428 return
428 return
429 if changeid == '.' or changeid == repo.dirstate.p1():
429 if changeid == '.' or changeid == repo.dirstate.p1():
430 # this is a hack to delay/avoid loading obsmarkers
430 # this is a hack to delay/avoid loading obsmarkers
431 # when we know that '.' won't be hidden
431 # when we know that '.' won't be hidden
432 self._node = repo.dirstate.p1()
432 self._node = repo.dirstate.p1()
433 self._rev = repo.unfiltered().changelog.rev(self._node)
433 self._rev = repo.unfiltered().changelog.rev(self._node)
434 return
434 return
435 if len(changeid) == 20:
435 if len(changeid) == 20:
436 try:
436 try:
437 self._node = changeid
437 self._node = changeid
438 self._rev = repo.changelog.rev(changeid)
438 self._rev = repo.changelog.rev(changeid)
439 return
439 return
440 except error.FilteredRepoLookupError:
440 except error.FilteredRepoLookupError:
441 raise
441 raise
442 except LookupError:
442 except LookupError:
443 pass
443 pass
444
444
445 try:
445 try:
446 r = int(changeid)
446 r = int(changeid)
447 if str(r) != changeid:
447 if str(r) != changeid:
448 raise ValueError
448 raise ValueError
449 l = len(repo.changelog)
449 l = len(repo.changelog)
450 if r < 0:
450 if r < 0:
451 r += l
451 r += l
452 if r < 0 or r >= l:
452 if r < 0 or r >= l:
453 raise ValueError
453 raise ValueError
454 self._rev = r
454 self._rev = r
455 self._node = repo.changelog.node(r)
455 self._node = repo.changelog.node(r)
456 return
456 return
457 except error.FilteredIndexError:
457 except error.FilteredIndexError:
458 raise
458 raise
459 except (ValueError, OverflowError, IndexError):
459 except (ValueError, OverflowError, IndexError):
460 pass
460 pass
461
461
462 if len(changeid) == 40:
462 if len(changeid) == 40:
463 try:
463 try:
464 self._node = bin(changeid)
464 self._node = bin(changeid)
465 self._rev = repo.changelog.rev(self._node)
465 self._rev = repo.changelog.rev(self._node)
466 return
466 return
467 except error.FilteredLookupError:
467 except error.FilteredLookupError:
468 raise
468 raise
469 except (TypeError, LookupError):
469 except (TypeError, LookupError):
470 pass
470 pass
471
471
472 # lookup bookmarks through the name interface
472 # lookup bookmarks through the name interface
473 try:
473 try:
474 self._node = repo.names.singlenode(repo, changeid)
474 self._node = repo.names.singlenode(repo, changeid)
475 self._rev = repo.changelog.rev(self._node)
475 self._rev = repo.changelog.rev(self._node)
476 return
476 return
477 except KeyError:
477 except KeyError:
478 pass
478 pass
479 except error.FilteredRepoLookupError:
479 except error.FilteredRepoLookupError:
480 raise
480 raise
481 except error.RepoLookupError:
481 except error.RepoLookupError:
482 pass
482 pass
483
483
484 self._node = repo.unfiltered().changelog._partialmatch(changeid)
484 self._node = repo.unfiltered().changelog._partialmatch(changeid)
485 if self._node is not None:
485 if self._node is not None:
486 self._rev = repo.changelog.rev(self._node)
486 self._rev = repo.changelog.rev(self._node)
487 return
487 return
488
488
489 # lookup failed
489 # lookup failed
490 # check if it might have come from damaged dirstate
490 # check if it might have come from damaged dirstate
491 #
491 #
492 # XXX we could avoid the unfiltered if we had a recognizable
492 # XXX we could avoid the unfiltered if we had a recognizable
493 # exception for filtered changeset access
493 # exception for filtered changeset access
494 if changeid in repo.unfiltered().dirstate.parents():
494 if changeid in repo.unfiltered().dirstate.parents():
495 msg = _("working directory has unknown parent '%s'!")
495 msg = _("working directory has unknown parent '%s'!")
496 raise error.Abort(msg % short(changeid))
496 raise error.Abort(msg % short(changeid))
497 try:
497 try:
498 if len(changeid) == 20 and nonascii(changeid):
498 if len(changeid) == 20 and nonascii(changeid):
499 changeid = hex(changeid)
499 changeid = hex(changeid)
500 except TypeError:
500 except TypeError:
501 pass
501 pass
502 except (error.FilteredIndexError, error.FilteredLookupError,
502 except (error.FilteredIndexError, error.FilteredLookupError,
503 error.FilteredRepoLookupError):
503 error.FilteredRepoLookupError):
504 if repo.filtername.startswith('visible'):
504 if repo.filtername.startswith('visible'):
505 msg = _("hidden revision '%s'") % changeid
505 msg = _("hidden revision '%s'") % changeid
506 hint = _('use --hidden to access hidden revisions')
506 hint = _('use --hidden to access hidden revisions')
507 raise error.FilteredRepoLookupError(msg, hint=hint)
507 raise error.FilteredRepoLookupError(msg, hint=hint)
508 msg = _("filtered revision '%s' (not in '%s' subset)")
508 msg = _("filtered revision '%s' (not in '%s' subset)")
509 msg %= (changeid, repo.filtername)
509 msg %= (changeid, repo.filtername)
510 raise error.FilteredRepoLookupError(msg)
510 raise error.FilteredRepoLookupError(msg)
511 except IndexError:
511 except IndexError:
512 pass
512 pass
513 raise error.RepoLookupError(
513 raise error.RepoLookupError(
514 _("unknown revision '%s'") % changeid)
514 _("unknown revision '%s'") % changeid)
515
515
516 def __hash__(self):
516 def __hash__(self):
517 try:
517 try:
518 return hash(self._rev)
518 return hash(self._rev)
519 except AttributeError:
519 except AttributeError:
520 return id(self)
520 return id(self)
521
521
522 def __nonzero__(self):
522 def __nonzero__(self):
523 return self._rev != nullrev
523 return self._rev != nullrev
524
524
525 @propertycache
525 @propertycache
526 def _changeset(self):
526 def _changeset(self):
527 return self._repo.changelog.changelogrevision(self.rev())
527 return self._repo.changelog.changelogrevision(self.rev())
528
528
529 @propertycache
529 @propertycache
530 def _manifest(self):
530 def _manifest(self):
531 return self._repo.manifestlog[self._changeset.manifest].read()
531 return self._repo.manifestlog[self._changeset.manifest].read()
532
532
533 @propertycache
533 @propertycache
534 def _manifestdelta(self):
534 def _manifestdelta(self):
535 return self._repo.manifest.readdelta(self._changeset.manifest)
535 mfnode = self._changeset.manifest
536 return self._repo.manifestlog[mfnode].readdelta()
536
537
537 @propertycache
538 @propertycache
538 def _parents(self):
539 def _parents(self):
539 repo = self._repo
540 repo = self._repo
540 p1, p2 = repo.changelog.parentrevs(self._rev)
541 p1, p2 = repo.changelog.parentrevs(self._rev)
541 if p2 == nullrev:
542 if p2 == nullrev:
542 return [changectx(repo, p1)]
543 return [changectx(repo, p1)]
543 return [changectx(repo, p1), changectx(repo, p2)]
544 return [changectx(repo, p1), changectx(repo, p2)]
544
545
545 def changeset(self):
546 def changeset(self):
546 c = self._changeset
547 c = self._changeset
547 return (
548 return (
548 c.manifest,
549 c.manifest,
549 c.user,
550 c.user,
550 c.date,
551 c.date,
551 c.files,
552 c.files,
552 c.description,
553 c.description,
553 c.extra,
554 c.extra,
554 )
555 )
555 def manifestnode(self):
556 def manifestnode(self):
556 return self._changeset.manifest
557 return self._changeset.manifest
557
558
558 def user(self):
559 def user(self):
559 return self._changeset.user
560 return self._changeset.user
560 def date(self):
561 def date(self):
561 return self._changeset.date
562 return self._changeset.date
562 def files(self):
563 def files(self):
563 return self._changeset.files
564 return self._changeset.files
564 def description(self):
565 def description(self):
565 return self._changeset.description
566 return self._changeset.description
566 def branch(self):
567 def branch(self):
567 return encoding.tolocal(self._changeset.extra.get("branch"))
568 return encoding.tolocal(self._changeset.extra.get("branch"))
568 def closesbranch(self):
569 def closesbranch(self):
569 return 'close' in self._changeset.extra
570 return 'close' in self._changeset.extra
570 def extra(self):
571 def extra(self):
571 return self._changeset.extra
572 return self._changeset.extra
572 def tags(self):
573 def tags(self):
573 return self._repo.nodetags(self._node)
574 return self._repo.nodetags(self._node)
574 def bookmarks(self):
575 def bookmarks(self):
575 return self._repo.nodebookmarks(self._node)
576 return self._repo.nodebookmarks(self._node)
576 def phase(self):
577 def phase(self):
577 return self._repo._phasecache.phase(self._repo, self._rev)
578 return self._repo._phasecache.phase(self._repo, self._rev)
578 def hidden(self):
579 def hidden(self):
579 return self._rev in repoview.filterrevs(self._repo, 'visible')
580 return self._rev in repoview.filterrevs(self._repo, 'visible')
580
581
581 def children(self):
582 def children(self):
582 """return contexts for each child changeset"""
583 """return contexts for each child changeset"""
583 c = self._repo.changelog.children(self._node)
584 c = self._repo.changelog.children(self._node)
584 return [changectx(self._repo, x) for x in c]
585 return [changectx(self._repo, x) for x in c]
585
586
586 def ancestors(self):
587 def ancestors(self):
587 for a in self._repo.changelog.ancestors([self._rev]):
588 for a in self._repo.changelog.ancestors([self._rev]):
588 yield changectx(self._repo, a)
589 yield changectx(self._repo, a)
589
590
590 def descendants(self):
591 def descendants(self):
591 for d in self._repo.changelog.descendants([self._rev]):
592 for d in self._repo.changelog.descendants([self._rev]):
592 yield changectx(self._repo, d)
593 yield changectx(self._repo, d)
593
594
594 def filectx(self, path, fileid=None, filelog=None):
595 def filectx(self, path, fileid=None, filelog=None):
595 """get a file context from this changeset"""
596 """get a file context from this changeset"""
596 if fileid is None:
597 if fileid is None:
597 fileid = self.filenode(path)
598 fileid = self.filenode(path)
598 return filectx(self._repo, path, fileid=fileid,
599 return filectx(self._repo, path, fileid=fileid,
599 changectx=self, filelog=filelog)
600 changectx=self, filelog=filelog)
600
601
601 def ancestor(self, c2, warn=False):
602 def ancestor(self, c2, warn=False):
602 """return the "best" ancestor context of self and c2
603 """return the "best" ancestor context of self and c2
603
604
604 If there are multiple candidates, it will show a message and check
605 If there are multiple candidates, it will show a message and check
605 merge.preferancestor configuration before falling back to the
606 merge.preferancestor configuration before falling back to the
606 revlog ancestor."""
607 revlog ancestor."""
607 # deal with workingctxs
608 # deal with workingctxs
608 n2 = c2._node
609 n2 = c2._node
609 if n2 is None:
610 if n2 is None:
610 n2 = c2._parents[0]._node
611 n2 = c2._parents[0]._node
611 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
612 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
612 if not cahs:
613 if not cahs:
613 anc = nullid
614 anc = nullid
614 elif len(cahs) == 1:
615 elif len(cahs) == 1:
615 anc = cahs[0]
616 anc = cahs[0]
616 else:
617 else:
617 # experimental config: merge.preferancestor
618 # experimental config: merge.preferancestor
618 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
619 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
619 try:
620 try:
620 ctx = changectx(self._repo, r)
621 ctx = changectx(self._repo, r)
621 except error.RepoLookupError:
622 except error.RepoLookupError:
622 continue
623 continue
623 anc = ctx.node()
624 anc = ctx.node()
624 if anc in cahs:
625 if anc in cahs:
625 break
626 break
626 else:
627 else:
627 anc = self._repo.changelog.ancestor(self._node, n2)
628 anc = self._repo.changelog.ancestor(self._node, n2)
628 if warn:
629 if warn:
629 self._repo.ui.status(
630 self._repo.ui.status(
630 (_("note: using %s as ancestor of %s and %s\n") %
631 (_("note: using %s as ancestor of %s and %s\n") %
631 (short(anc), short(self._node), short(n2))) +
632 (short(anc), short(self._node), short(n2))) +
632 ''.join(_(" alternatively, use --config "
633 ''.join(_(" alternatively, use --config "
633 "merge.preferancestor=%s\n") %
634 "merge.preferancestor=%s\n") %
634 short(n) for n in sorted(cahs) if n != anc))
635 short(n) for n in sorted(cahs) if n != anc))
635 return changectx(self._repo, anc)
636 return changectx(self._repo, anc)
636
637
637 def descendant(self, other):
638 def descendant(self, other):
638 """True if other is descendant of this changeset"""
639 """True if other is descendant of this changeset"""
639 return self._repo.changelog.descendant(self._rev, other._rev)
640 return self._repo.changelog.descendant(self._rev, other._rev)
640
641
641 def walk(self, match):
642 def walk(self, match):
642 '''Generates matching file names.'''
643 '''Generates matching file names.'''
643
644
644 # Wrap match.bad method to have message with nodeid
645 # Wrap match.bad method to have message with nodeid
645 def bad(fn, msg):
646 def bad(fn, msg):
646 # The manifest doesn't know about subrepos, so don't complain about
647 # The manifest doesn't know about subrepos, so don't complain about
647 # paths into valid subrepos.
648 # paths into valid subrepos.
648 if any(fn == s or fn.startswith(s + '/')
649 if any(fn == s or fn.startswith(s + '/')
649 for s in self.substate):
650 for s in self.substate):
650 return
651 return
651 match.bad(fn, _('no such file in rev %s') % self)
652 match.bad(fn, _('no such file in rev %s') % self)
652
653
653 m = matchmod.badmatch(match, bad)
654 m = matchmod.badmatch(match, bad)
654 return self._manifest.walk(m)
655 return self._manifest.walk(m)
655
656
656 def matches(self, match):
657 def matches(self, match):
657 return self.walk(match)
658 return self.walk(match)
658
659
659 class basefilectx(object):
660 class basefilectx(object):
660 """A filecontext object represents the common logic for its children:
661 """A filecontext object represents the common logic for its children:
661 filectx: read-only access to a filerevision that is already present
662 filectx: read-only access to a filerevision that is already present
662 in the repo,
663 in the repo,
663 workingfilectx: a filecontext that represents files from the working
664 workingfilectx: a filecontext that represents files from the working
664 directory,
665 directory,
665 memfilectx: a filecontext that represents files in-memory."""
666 memfilectx: a filecontext that represents files in-memory."""
666 def __new__(cls, repo, path, *args, **kwargs):
667 def __new__(cls, repo, path, *args, **kwargs):
667 return super(basefilectx, cls).__new__(cls)
668 return super(basefilectx, cls).__new__(cls)
668
669
669 @propertycache
670 @propertycache
670 def _filelog(self):
671 def _filelog(self):
671 return self._repo.file(self._path)
672 return self._repo.file(self._path)
672
673
673 @propertycache
674 @propertycache
674 def _changeid(self):
675 def _changeid(self):
675 if '_changeid' in self.__dict__:
676 if '_changeid' in self.__dict__:
676 return self._changeid
677 return self._changeid
677 elif '_changectx' in self.__dict__:
678 elif '_changectx' in self.__dict__:
678 return self._changectx.rev()
679 return self._changectx.rev()
679 elif '_descendantrev' in self.__dict__:
680 elif '_descendantrev' in self.__dict__:
680 # this file context was created from a revision with a known
681 # this file context was created from a revision with a known
681 # descendant, we can (lazily) correct for linkrev aliases
682 # descendant, we can (lazily) correct for linkrev aliases
682 return self._adjustlinkrev(self._path, self._filelog,
683 return self._adjustlinkrev(self._path, self._filelog,
683 self._filenode, self._descendantrev)
684 self._filenode, self._descendantrev)
684 else:
685 else:
685 return self._filelog.linkrev(self._filerev)
686 return self._filelog.linkrev(self._filerev)
686
687
687 @propertycache
688 @propertycache
688 def _filenode(self):
689 def _filenode(self):
689 if '_fileid' in self.__dict__:
690 if '_fileid' in self.__dict__:
690 return self._filelog.lookup(self._fileid)
691 return self._filelog.lookup(self._fileid)
691 else:
692 else:
692 return self._changectx.filenode(self._path)
693 return self._changectx.filenode(self._path)
693
694
694 @propertycache
695 @propertycache
695 def _filerev(self):
696 def _filerev(self):
696 return self._filelog.rev(self._filenode)
697 return self._filelog.rev(self._filenode)
697
698
698 @propertycache
699 @propertycache
699 def _repopath(self):
700 def _repopath(self):
700 return self._path
701 return self._path
701
702
702 def __nonzero__(self):
703 def __nonzero__(self):
703 try:
704 try:
704 self._filenode
705 self._filenode
705 return True
706 return True
706 except error.LookupError:
707 except error.LookupError:
707 # file is missing
708 # file is missing
708 return False
709 return False
709
710
710 def __str__(self):
711 def __str__(self):
711 return "%s@%s" % (self.path(), self._changectx)
712 return "%s@%s" % (self.path(), self._changectx)
712
713
713 def __repr__(self):
714 def __repr__(self):
714 return "<%s %s>" % (type(self).__name__, str(self))
715 return "<%s %s>" % (type(self).__name__, str(self))
715
716
716 def __hash__(self):
717 def __hash__(self):
717 try:
718 try:
718 return hash((self._path, self._filenode))
719 return hash((self._path, self._filenode))
719 except AttributeError:
720 except AttributeError:
720 return id(self)
721 return id(self)
721
722
722 def __eq__(self, other):
723 def __eq__(self, other):
723 try:
724 try:
724 return (type(self) == type(other) and self._path == other._path
725 return (type(self) == type(other) and self._path == other._path
725 and self._filenode == other._filenode)
726 and self._filenode == other._filenode)
726 except AttributeError:
727 except AttributeError:
727 return False
728 return False
728
729
729 def __ne__(self, other):
730 def __ne__(self, other):
730 return not (self == other)
731 return not (self == other)
731
732
732 def filerev(self):
733 def filerev(self):
733 return self._filerev
734 return self._filerev
734 def filenode(self):
735 def filenode(self):
735 return self._filenode
736 return self._filenode
736 def flags(self):
737 def flags(self):
737 return self._changectx.flags(self._path)
738 return self._changectx.flags(self._path)
738 def filelog(self):
739 def filelog(self):
739 return self._filelog
740 return self._filelog
740 def rev(self):
741 def rev(self):
741 return self._changeid
742 return self._changeid
742 def linkrev(self):
743 def linkrev(self):
743 return self._filelog.linkrev(self._filerev)
744 return self._filelog.linkrev(self._filerev)
744 def node(self):
745 def node(self):
745 return self._changectx.node()
746 return self._changectx.node()
746 def hex(self):
747 def hex(self):
747 return self._changectx.hex()
748 return self._changectx.hex()
748 def user(self):
749 def user(self):
749 return self._changectx.user()
750 return self._changectx.user()
750 def date(self):
751 def date(self):
751 return self._changectx.date()
752 return self._changectx.date()
752 def files(self):
753 def files(self):
753 return self._changectx.files()
754 return self._changectx.files()
754 def description(self):
755 def description(self):
755 return self._changectx.description()
756 return self._changectx.description()
756 def branch(self):
757 def branch(self):
757 return self._changectx.branch()
758 return self._changectx.branch()
758 def extra(self):
759 def extra(self):
759 return self._changectx.extra()
760 return self._changectx.extra()
760 def phase(self):
761 def phase(self):
761 return self._changectx.phase()
762 return self._changectx.phase()
762 def phasestr(self):
763 def phasestr(self):
763 return self._changectx.phasestr()
764 return self._changectx.phasestr()
764 def manifest(self):
765 def manifest(self):
765 return self._changectx.manifest()
766 return self._changectx.manifest()
766 def changectx(self):
767 def changectx(self):
767 return self._changectx
768 return self._changectx
768 def repo(self):
769 def repo(self):
769 return self._repo
770 return self._repo
770
771
771 def path(self):
772 def path(self):
772 return self._path
773 return self._path
773
774
774 def isbinary(self):
775 def isbinary(self):
775 try:
776 try:
776 return util.binary(self.data())
777 return util.binary(self.data())
777 except IOError:
778 except IOError:
778 return False
779 return False
779 def isexec(self):
780 def isexec(self):
780 return 'x' in self.flags()
781 return 'x' in self.flags()
781 def islink(self):
782 def islink(self):
782 return 'l' in self.flags()
783 return 'l' in self.flags()
783
784
784 def isabsent(self):
785 def isabsent(self):
785 """whether this filectx represents a file not in self._changectx
786 """whether this filectx represents a file not in self._changectx
786
787
787 This is mainly for merge code to detect change/delete conflicts. This is
788 This is mainly for merge code to detect change/delete conflicts. This is
788 expected to be True for all subclasses of basectx."""
789 expected to be True for all subclasses of basectx."""
789 return False
790 return False
790
791
791 _customcmp = False
792 _customcmp = False
792 def cmp(self, fctx):
793 def cmp(self, fctx):
793 """compare with other file context
794 """compare with other file context
794
795
795 returns True if different than fctx.
796 returns True if different than fctx.
796 """
797 """
797 if fctx._customcmp:
798 if fctx._customcmp:
798 return fctx.cmp(self)
799 return fctx.cmp(self)
799
800
800 if (fctx._filenode is None
801 if (fctx._filenode is None
801 and (self._repo._encodefilterpats
802 and (self._repo._encodefilterpats
802 # if file data starts with '\1\n', empty metadata block is
803 # if file data starts with '\1\n', empty metadata block is
803 # prepended, which adds 4 bytes to filelog.size().
804 # prepended, which adds 4 bytes to filelog.size().
804 or self.size() - 4 == fctx.size())
805 or self.size() - 4 == fctx.size())
805 or self.size() == fctx.size()):
806 or self.size() == fctx.size()):
806 return self._filelog.cmp(self._filenode, fctx.data())
807 return self._filelog.cmp(self._filenode, fctx.data())
807
808
808 return True
809 return True
809
810
810 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
811 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
811 """return the first ancestor of <srcrev> introducing <fnode>
812 """return the first ancestor of <srcrev> introducing <fnode>
812
813
813 If the linkrev of the file revision does not point to an ancestor of
814 If the linkrev of the file revision does not point to an ancestor of
814 srcrev, we'll walk down the ancestors until we find one introducing
815 srcrev, we'll walk down the ancestors until we find one introducing
815 this file revision.
816 this file revision.
816
817
817 :repo: a localrepository object (used to access changelog and manifest)
818 :repo: a localrepository object (used to access changelog and manifest)
818 :path: the file path
819 :path: the file path
819 :fnode: the nodeid of the file revision
820 :fnode: the nodeid of the file revision
820 :filelog: the filelog of this path
821 :filelog: the filelog of this path
821 :srcrev: the changeset revision we search ancestors from
822 :srcrev: the changeset revision we search ancestors from
822 :inclusive: if true, the src revision will also be checked
823 :inclusive: if true, the src revision will also be checked
823 """
824 """
824 repo = self._repo
825 repo = self._repo
825 cl = repo.unfiltered().changelog
826 cl = repo.unfiltered().changelog
826 ma = repo.manifest
827 ma = repo.manifest
827 # fetch the linkrev
828 # fetch the linkrev
828 fr = filelog.rev(fnode)
829 fr = filelog.rev(fnode)
829 lkr = filelog.linkrev(fr)
830 lkr = filelog.linkrev(fr)
830 # hack to reuse ancestor computation when searching for renames
831 # hack to reuse ancestor computation when searching for renames
831 memberanc = getattr(self, '_ancestrycontext', None)
832 memberanc = getattr(self, '_ancestrycontext', None)
832 iteranc = None
833 iteranc = None
833 if srcrev is None:
834 if srcrev is None:
834 # wctx case, used by workingfilectx during mergecopy
835 # wctx case, used by workingfilectx during mergecopy
835 revs = [p.rev() for p in self._repo[None].parents()]
836 revs = [p.rev() for p in self._repo[None].parents()]
836 inclusive = True # we skipped the real (revless) source
837 inclusive = True # we skipped the real (revless) source
837 else:
838 else:
838 revs = [srcrev]
839 revs = [srcrev]
839 if memberanc is None:
840 if memberanc is None:
840 memberanc = iteranc = cl.ancestors(revs, lkr,
841 memberanc = iteranc = cl.ancestors(revs, lkr,
841 inclusive=inclusive)
842 inclusive=inclusive)
842 # check if this linkrev is an ancestor of srcrev
843 # check if this linkrev is an ancestor of srcrev
843 if lkr not in memberanc:
844 if lkr not in memberanc:
844 if iteranc is None:
845 if iteranc is None:
845 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
846 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
846 for a in iteranc:
847 for a in iteranc:
847 ac = cl.read(a) # get changeset data (we avoid object creation)
848 ac = cl.read(a) # get changeset data (we avoid object creation)
848 if path in ac[3]: # checking the 'files' field.
849 if path in ac[3]: # checking the 'files' field.
849 # The file has been touched, check if the content is
850 # The file has been touched, check if the content is
850 # similar to the one we search for.
851 # similar to the one we search for.
851 if fnode == ma.readfast(ac[0]).get(path):
852 if fnode == ma.readfast(ac[0]).get(path):
852 return a
853 return a
853 # In theory, we should never get out of that loop without a result.
854 # In theory, we should never get out of that loop without a result.
854 # But if manifest uses a buggy file revision (not children of the
855 # But if manifest uses a buggy file revision (not children of the
855 # one it replaces) we could. Such a buggy situation will likely
856 # one it replaces) we could. Such a buggy situation will likely
856 # result is crash somewhere else at to some point.
857 # result is crash somewhere else at to some point.
857 return lkr
858 return lkr
858
859
859 def introrev(self):
860 def introrev(self):
860 """return the rev of the changeset which introduced this file revision
861 """return the rev of the changeset which introduced this file revision
861
862
862 This method is different from linkrev because it take into account the
863 This method is different from linkrev because it take into account the
863 changeset the filectx was created from. It ensures the returned
864 changeset the filectx was created from. It ensures the returned
864 revision is one of its ancestors. This prevents bugs from
865 revision is one of its ancestors. This prevents bugs from
865 'linkrev-shadowing' when a file revision is used by multiple
866 'linkrev-shadowing' when a file revision is used by multiple
866 changesets.
867 changesets.
867 """
868 """
868 lkr = self.linkrev()
869 lkr = self.linkrev()
869 attrs = vars(self)
870 attrs = vars(self)
870 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
871 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
871 if noctx or self.rev() == lkr:
872 if noctx or self.rev() == lkr:
872 return self.linkrev()
873 return self.linkrev()
873 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
874 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
874 self.rev(), inclusive=True)
875 self.rev(), inclusive=True)
875
876
876 def _parentfilectx(self, path, fileid, filelog):
877 def _parentfilectx(self, path, fileid, filelog):
877 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
878 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
878 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
879 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
879 if '_changeid' in vars(self) or '_changectx' in vars(self):
880 if '_changeid' in vars(self) or '_changectx' in vars(self):
880 # If self is associated with a changeset (probably explicitly
881 # If self is associated with a changeset (probably explicitly
881 # fed), ensure the created filectx is associated with a
882 # fed), ensure the created filectx is associated with a
882 # changeset that is an ancestor of self.changectx.
883 # changeset that is an ancestor of self.changectx.
883 # This lets us later use _adjustlinkrev to get a correct link.
884 # This lets us later use _adjustlinkrev to get a correct link.
884 fctx._descendantrev = self.rev()
885 fctx._descendantrev = self.rev()
885 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
886 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
886 elif '_descendantrev' in vars(self):
887 elif '_descendantrev' in vars(self):
887 # Otherwise propagate _descendantrev if we have one associated.
888 # Otherwise propagate _descendantrev if we have one associated.
888 fctx._descendantrev = self._descendantrev
889 fctx._descendantrev = self._descendantrev
889 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
890 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
890 return fctx
891 return fctx
891
892
892 def parents(self):
893 def parents(self):
893 _path = self._path
894 _path = self._path
894 fl = self._filelog
895 fl = self._filelog
895 parents = self._filelog.parents(self._filenode)
896 parents = self._filelog.parents(self._filenode)
896 pl = [(_path, node, fl) for node in parents if node != nullid]
897 pl = [(_path, node, fl) for node in parents if node != nullid]
897
898
898 r = fl.renamed(self._filenode)
899 r = fl.renamed(self._filenode)
899 if r:
900 if r:
900 # - In the simple rename case, both parent are nullid, pl is empty.
901 # - In the simple rename case, both parent are nullid, pl is empty.
901 # - In case of merge, only one of the parent is null id and should
902 # - In case of merge, only one of the parent is null id and should
902 # be replaced with the rename information. This parent is -always-
903 # be replaced with the rename information. This parent is -always-
903 # the first one.
904 # the first one.
904 #
905 #
905 # As null id have always been filtered out in the previous list
906 # As null id have always been filtered out in the previous list
906 # comprehension, inserting to 0 will always result in "replacing
907 # comprehension, inserting to 0 will always result in "replacing
907 # first nullid parent with rename information.
908 # first nullid parent with rename information.
908 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
909 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
909
910
910 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
911 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
911
912
912 def p1(self):
913 def p1(self):
913 return self.parents()[0]
914 return self.parents()[0]
914
915
915 def p2(self):
916 def p2(self):
916 p = self.parents()
917 p = self.parents()
917 if len(p) == 2:
918 if len(p) == 2:
918 return p[1]
919 return p[1]
919 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
920 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
920
921
921 def annotate(self, follow=False, linenumber=False, diffopts=None):
922 def annotate(self, follow=False, linenumber=False, diffopts=None):
922 '''returns a list of tuples of ((ctx, number), line) for each line
923 '''returns a list of tuples of ((ctx, number), line) for each line
923 in the file, where ctx is the filectx of the node where
924 in the file, where ctx is the filectx of the node where
924 that line was last changed; if linenumber parameter is true, number is
925 that line was last changed; if linenumber parameter is true, number is
925 the line number at the first appearance in the managed file, otherwise,
926 the line number at the first appearance in the managed file, otherwise,
926 number has a fixed value of False.
927 number has a fixed value of False.
927 '''
928 '''
928
929
929 def lines(text):
930 def lines(text):
930 if text.endswith("\n"):
931 if text.endswith("\n"):
931 return text.count("\n")
932 return text.count("\n")
932 return text.count("\n") + 1
933 return text.count("\n") + 1
933
934
934 if linenumber:
935 if linenumber:
935 def decorate(text, rev):
936 def decorate(text, rev):
936 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
937 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
937 else:
938 else:
938 def decorate(text, rev):
939 def decorate(text, rev):
939 return ([(rev, False)] * lines(text), text)
940 return ([(rev, False)] * lines(text), text)
940
941
941 def pair(parent, child):
942 def pair(parent, child):
942 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
943 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
943 refine=True)
944 refine=True)
944 for (a1, a2, b1, b2), t in blocks:
945 for (a1, a2, b1, b2), t in blocks:
945 # Changed blocks ('!') or blocks made only of blank lines ('~')
946 # Changed blocks ('!') or blocks made only of blank lines ('~')
946 # belong to the child.
947 # belong to the child.
947 if t == '=':
948 if t == '=':
948 child[0][b1:b2] = parent[0][a1:a2]
949 child[0][b1:b2] = parent[0][a1:a2]
949 return child
950 return child
950
951
951 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
952 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
952
953
953 def parents(f):
954 def parents(f):
954 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
955 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
955 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
956 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
956 # from the topmost introrev (= srcrev) down to p.linkrev() if it
957 # from the topmost introrev (= srcrev) down to p.linkrev() if it
957 # isn't an ancestor of the srcrev.
958 # isn't an ancestor of the srcrev.
958 f._changeid
959 f._changeid
959 pl = f.parents()
960 pl = f.parents()
960
961
961 # Don't return renamed parents if we aren't following.
962 # Don't return renamed parents if we aren't following.
962 if not follow:
963 if not follow:
963 pl = [p for p in pl if p.path() == f.path()]
964 pl = [p for p in pl if p.path() == f.path()]
964
965
965 # renamed filectx won't have a filelog yet, so set it
966 # renamed filectx won't have a filelog yet, so set it
966 # from the cache to save time
967 # from the cache to save time
967 for p in pl:
968 for p in pl:
968 if not '_filelog' in p.__dict__:
969 if not '_filelog' in p.__dict__:
969 p._filelog = getlog(p.path())
970 p._filelog = getlog(p.path())
970
971
971 return pl
972 return pl
972
973
973 # use linkrev to find the first changeset where self appeared
974 # use linkrev to find the first changeset where self appeared
974 base = self
975 base = self
975 introrev = self.introrev()
976 introrev = self.introrev()
976 if self.rev() != introrev:
977 if self.rev() != introrev:
977 base = self.filectx(self.filenode(), changeid=introrev)
978 base = self.filectx(self.filenode(), changeid=introrev)
978 if getattr(base, '_ancestrycontext', None) is None:
979 if getattr(base, '_ancestrycontext', None) is None:
979 cl = self._repo.changelog
980 cl = self._repo.changelog
980 if introrev is None:
981 if introrev is None:
981 # wctx is not inclusive, but works because _ancestrycontext
982 # wctx is not inclusive, but works because _ancestrycontext
982 # is used to test filelog revisions
983 # is used to test filelog revisions
983 ac = cl.ancestors([p.rev() for p in base.parents()],
984 ac = cl.ancestors([p.rev() for p in base.parents()],
984 inclusive=True)
985 inclusive=True)
985 else:
986 else:
986 ac = cl.ancestors([introrev], inclusive=True)
987 ac = cl.ancestors([introrev], inclusive=True)
987 base._ancestrycontext = ac
988 base._ancestrycontext = ac
988
989
989 # This algorithm would prefer to be recursive, but Python is a
990 # This algorithm would prefer to be recursive, but Python is a
990 # bit recursion-hostile. Instead we do an iterative
991 # bit recursion-hostile. Instead we do an iterative
991 # depth-first search.
992 # depth-first search.
992
993
993 # 1st DFS pre-calculates pcache and needed
994 # 1st DFS pre-calculates pcache and needed
994 visit = [base]
995 visit = [base]
995 pcache = {}
996 pcache = {}
996 needed = {base: 1}
997 needed = {base: 1}
997 while visit:
998 while visit:
998 f = visit.pop()
999 f = visit.pop()
999 if f in pcache:
1000 if f in pcache:
1000 continue
1001 continue
1001 pl = parents(f)
1002 pl = parents(f)
1002 pcache[f] = pl
1003 pcache[f] = pl
1003 for p in pl:
1004 for p in pl:
1004 needed[p] = needed.get(p, 0) + 1
1005 needed[p] = needed.get(p, 0) + 1
1005 if p not in pcache:
1006 if p not in pcache:
1006 visit.append(p)
1007 visit.append(p)
1007
1008
1008 # 2nd DFS does the actual annotate
1009 # 2nd DFS does the actual annotate
1009 visit[:] = [base]
1010 visit[:] = [base]
1010 hist = {}
1011 hist = {}
1011 while visit:
1012 while visit:
1012 f = visit[-1]
1013 f = visit[-1]
1013 if f in hist:
1014 if f in hist:
1014 visit.pop()
1015 visit.pop()
1015 continue
1016 continue
1016
1017
1017 ready = True
1018 ready = True
1018 pl = pcache[f]
1019 pl = pcache[f]
1019 for p in pl:
1020 for p in pl:
1020 if p not in hist:
1021 if p not in hist:
1021 ready = False
1022 ready = False
1022 visit.append(p)
1023 visit.append(p)
1023 if ready:
1024 if ready:
1024 visit.pop()
1025 visit.pop()
1025 curr = decorate(f.data(), f)
1026 curr = decorate(f.data(), f)
1026 for p in pl:
1027 for p in pl:
1027 curr = pair(hist[p], curr)
1028 curr = pair(hist[p], curr)
1028 if needed[p] == 1:
1029 if needed[p] == 1:
1029 del hist[p]
1030 del hist[p]
1030 del needed[p]
1031 del needed[p]
1031 else:
1032 else:
1032 needed[p] -= 1
1033 needed[p] -= 1
1033
1034
1034 hist[f] = curr
1035 hist[f] = curr
1035 del pcache[f]
1036 del pcache[f]
1036
1037
1037 return zip(hist[base][0], hist[base][1].splitlines(True))
1038 return zip(hist[base][0], hist[base][1].splitlines(True))
1038
1039
1039 def ancestors(self, followfirst=False):
1040 def ancestors(self, followfirst=False):
1040 visit = {}
1041 visit = {}
1041 c = self
1042 c = self
1042 if followfirst:
1043 if followfirst:
1043 cut = 1
1044 cut = 1
1044 else:
1045 else:
1045 cut = None
1046 cut = None
1046
1047
1047 while True:
1048 while True:
1048 for parent in c.parents()[:cut]:
1049 for parent in c.parents()[:cut]:
1049 visit[(parent.linkrev(), parent.filenode())] = parent
1050 visit[(parent.linkrev(), parent.filenode())] = parent
1050 if not visit:
1051 if not visit:
1051 break
1052 break
1052 c = visit.pop(max(visit))
1053 c = visit.pop(max(visit))
1053 yield c
1054 yield c
1054
1055
1055 class filectx(basefilectx):
1056 class filectx(basefilectx):
1056 """A filecontext object makes access to data related to a particular
1057 """A filecontext object makes access to data related to a particular
1057 filerevision convenient."""
1058 filerevision convenient."""
1058 def __init__(self, repo, path, changeid=None, fileid=None,
1059 def __init__(self, repo, path, changeid=None, fileid=None,
1059 filelog=None, changectx=None):
1060 filelog=None, changectx=None):
1060 """changeid can be a changeset revision, node, or tag.
1061 """changeid can be a changeset revision, node, or tag.
1061 fileid can be a file revision or node."""
1062 fileid can be a file revision or node."""
1062 self._repo = repo
1063 self._repo = repo
1063 self._path = path
1064 self._path = path
1064
1065
1065 assert (changeid is not None
1066 assert (changeid is not None
1066 or fileid is not None
1067 or fileid is not None
1067 or changectx is not None), \
1068 or changectx is not None), \
1068 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1069 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1069 % (changeid, fileid, changectx))
1070 % (changeid, fileid, changectx))
1070
1071
1071 if filelog is not None:
1072 if filelog is not None:
1072 self._filelog = filelog
1073 self._filelog = filelog
1073
1074
1074 if changeid is not None:
1075 if changeid is not None:
1075 self._changeid = changeid
1076 self._changeid = changeid
1076 if changectx is not None:
1077 if changectx is not None:
1077 self._changectx = changectx
1078 self._changectx = changectx
1078 if fileid is not None:
1079 if fileid is not None:
1079 self._fileid = fileid
1080 self._fileid = fileid
1080
1081
1081 @propertycache
1082 @propertycache
1082 def _changectx(self):
1083 def _changectx(self):
1083 try:
1084 try:
1084 return changectx(self._repo, self._changeid)
1085 return changectx(self._repo, self._changeid)
1085 except error.FilteredRepoLookupError:
1086 except error.FilteredRepoLookupError:
1086 # Linkrev may point to any revision in the repository. When the
1087 # Linkrev may point to any revision in the repository. When the
1087 # repository is filtered this may lead to `filectx` trying to build
1088 # repository is filtered this may lead to `filectx` trying to build
1088 # `changectx` for filtered revision. In such case we fallback to
1089 # `changectx` for filtered revision. In such case we fallback to
1089 # creating `changectx` on the unfiltered version of the reposition.
1090 # creating `changectx` on the unfiltered version of the reposition.
1090 # This fallback should not be an issue because `changectx` from
1091 # This fallback should not be an issue because `changectx` from
1091 # `filectx` are not used in complex operations that care about
1092 # `filectx` are not used in complex operations that care about
1092 # filtering.
1093 # filtering.
1093 #
1094 #
1094 # This fallback is a cheap and dirty fix that prevent several
1095 # This fallback is a cheap and dirty fix that prevent several
1095 # crashes. It does not ensure the behavior is correct. However the
1096 # crashes. It does not ensure the behavior is correct. However the
1096 # behavior was not correct before filtering either and "incorrect
1097 # behavior was not correct before filtering either and "incorrect
1097 # behavior" is seen as better as "crash"
1098 # behavior" is seen as better as "crash"
1098 #
1099 #
1099 # Linkrevs have several serious troubles with filtering that are
1100 # Linkrevs have several serious troubles with filtering that are
1100 # complicated to solve. Proper handling of the issue here should be
1101 # complicated to solve. Proper handling of the issue here should be
1101 # considered when solving linkrev issue are on the table.
1102 # considered when solving linkrev issue are on the table.
1102 return changectx(self._repo.unfiltered(), self._changeid)
1103 return changectx(self._repo.unfiltered(), self._changeid)
1103
1104
1104 def filectx(self, fileid, changeid=None):
1105 def filectx(self, fileid, changeid=None):
1105 '''opens an arbitrary revision of the file without
1106 '''opens an arbitrary revision of the file without
1106 opening a new filelog'''
1107 opening a new filelog'''
1107 return filectx(self._repo, self._path, fileid=fileid,
1108 return filectx(self._repo, self._path, fileid=fileid,
1108 filelog=self._filelog, changeid=changeid)
1109 filelog=self._filelog, changeid=changeid)
1109
1110
1110 def data(self):
1111 def data(self):
1111 try:
1112 try:
1112 return self._filelog.read(self._filenode)
1113 return self._filelog.read(self._filenode)
1113 except error.CensoredNodeError:
1114 except error.CensoredNodeError:
1114 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1115 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1115 return ""
1116 return ""
1116 raise error.Abort(_("censored node: %s") % short(self._filenode),
1117 raise error.Abort(_("censored node: %s") % short(self._filenode),
1117 hint=_("set censor.policy to ignore errors"))
1118 hint=_("set censor.policy to ignore errors"))
1118
1119
1119 def size(self):
1120 def size(self):
1120 return self._filelog.size(self._filerev)
1121 return self._filelog.size(self._filerev)
1121
1122
1122 def renamed(self):
1123 def renamed(self):
1123 """check if file was actually renamed in this changeset revision
1124 """check if file was actually renamed in this changeset revision
1124
1125
1125 If rename logged in file revision, we report copy for changeset only
1126 If rename logged in file revision, we report copy for changeset only
1126 if file revisions linkrev points back to the changeset in question
1127 if file revisions linkrev points back to the changeset in question
1127 or both changeset parents contain different file revisions.
1128 or both changeset parents contain different file revisions.
1128 """
1129 """
1129
1130
1130 renamed = self._filelog.renamed(self._filenode)
1131 renamed = self._filelog.renamed(self._filenode)
1131 if not renamed:
1132 if not renamed:
1132 return renamed
1133 return renamed
1133
1134
1134 if self.rev() == self.linkrev():
1135 if self.rev() == self.linkrev():
1135 return renamed
1136 return renamed
1136
1137
1137 name = self.path()
1138 name = self.path()
1138 fnode = self._filenode
1139 fnode = self._filenode
1139 for p in self._changectx.parents():
1140 for p in self._changectx.parents():
1140 try:
1141 try:
1141 if fnode == p.filenode(name):
1142 if fnode == p.filenode(name):
1142 return None
1143 return None
1143 except error.LookupError:
1144 except error.LookupError:
1144 pass
1145 pass
1145 return renamed
1146 return renamed
1146
1147
1147 def children(self):
1148 def children(self):
1148 # hard for renames
1149 # hard for renames
1149 c = self._filelog.children(self._filenode)
1150 c = self._filelog.children(self._filenode)
1150 return [filectx(self._repo, self._path, fileid=x,
1151 return [filectx(self._repo, self._path, fileid=x,
1151 filelog=self._filelog) for x in c]
1152 filelog=self._filelog) for x in c]
1152
1153
1153 class committablectx(basectx):
1154 class committablectx(basectx):
1154 """A committablectx object provides common functionality for a context that
1155 """A committablectx object provides common functionality for a context that
1155 wants the ability to commit, e.g. workingctx or memctx."""
1156 wants the ability to commit, e.g. workingctx or memctx."""
1156 def __init__(self, repo, text="", user=None, date=None, extra=None,
1157 def __init__(self, repo, text="", user=None, date=None, extra=None,
1157 changes=None):
1158 changes=None):
1158 self._repo = repo
1159 self._repo = repo
1159 self._rev = None
1160 self._rev = None
1160 self._node = None
1161 self._node = None
1161 self._text = text
1162 self._text = text
1162 if date:
1163 if date:
1163 self._date = util.parsedate(date)
1164 self._date = util.parsedate(date)
1164 if user:
1165 if user:
1165 self._user = user
1166 self._user = user
1166 if changes:
1167 if changes:
1167 self._status = changes
1168 self._status = changes
1168
1169
1169 self._extra = {}
1170 self._extra = {}
1170 if extra:
1171 if extra:
1171 self._extra = extra.copy()
1172 self._extra = extra.copy()
1172 if 'branch' not in self._extra:
1173 if 'branch' not in self._extra:
1173 try:
1174 try:
1174 branch = encoding.fromlocal(self._repo.dirstate.branch())
1175 branch = encoding.fromlocal(self._repo.dirstate.branch())
1175 except UnicodeDecodeError:
1176 except UnicodeDecodeError:
1176 raise error.Abort(_('branch name not in UTF-8!'))
1177 raise error.Abort(_('branch name not in UTF-8!'))
1177 self._extra['branch'] = branch
1178 self._extra['branch'] = branch
1178 if self._extra['branch'] == '':
1179 if self._extra['branch'] == '':
1179 self._extra['branch'] = 'default'
1180 self._extra['branch'] = 'default'
1180
1181
1181 def __str__(self):
1182 def __str__(self):
1182 return str(self._parents[0]) + "+"
1183 return str(self._parents[0]) + "+"
1183
1184
1184 def __nonzero__(self):
1185 def __nonzero__(self):
1185 return True
1186 return True
1186
1187
1187 def _buildflagfunc(self):
1188 def _buildflagfunc(self):
1188 # Create a fallback function for getting file flags when the
1189 # Create a fallback function for getting file flags when the
1189 # filesystem doesn't support them
1190 # filesystem doesn't support them
1190
1191
1191 copiesget = self._repo.dirstate.copies().get
1192 copiesget = self._repo.dirstate.copies().get
1192 parents = self.parents()
1193 parents = self.parents()
1193 if len(parents) < 2:
1194 if len(parents) < 2:
1194 # when we have one parent, it's easy: copy from parent
1195 # when we have one parent, it's easy: copy from parent
1195 man = parents[0].manifest()
1196 man = parents[0].manifest()
1196 def func(f):
1197 def func(f):
1197 f = copiesget(f, f)
1198 f = copiesget(f, f)
1198 return man.flags(f)
1199 return man.flags(f)
1199 else:
1200 else:
1200 # merges are tricky: we try to reconstruct the unstored
1201 # merges are tricky: we try to reconstruct the unstored
1201 # result from the merge (issue1802)
1202 # result from the merge (issue1802)
1202 p1, p2 = parents
1203 p1, p2 = parents
1203 pa = p1.ancestor(p2)
1204 pa = p1.ancestor(p2)
1204 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1205 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1205
1206
1206 def func(f):
1207 def func(f):
1207 f = copiesget(f, f) # may be wrong for merges with copies
1208 f = copiesget(f, f) # may be wrong for merges with copies
1208 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1209 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1209 if fl1 == fl2:
1210 if fl1 == fl2:
1210 return fl1
1211 return fl1
1211 if fl1 == fla:
1212 if fl1 == fla:
1212 return fl2
1213 return fl2
1213 if fl2 == fla:
1214 if fl2 == fla:
1214 return fl1
1215 return fl1
1215 return '' # punt for conflicts
1216 return '' # punt for conflicts
1216
1217
1217 return func
1218 return func
1218
1219
1219 @propertycache
1220 @propertycache
1220 def _flagfunc(self):
1221 def _flagfunc(self):
1221 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1222 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1222
1223
1223 @propertycache
1224 @propertycache
1224 def _manifest(self):
1225 def _manifest(self):
1225 """generate a manifest corresponding to the values in self._status
1226 """generate a manifest corresponding to the values in self._status
1226
1227
1227 This reuse the file nodeid from parent, but we append an extra letter
1228 This reuse the file nodeid from parent, but we append an extra letter
1228 when modified. Modified files get an extra 'm' while added files get
1229 when modified. Modified files get an extra 'm' while added files get
1229 an extra 'a'. This is used by manifests merge to see that files
1230 an extra 'a'. This is used by manifests merge to see that files
1230 are different and by update logic to avoid deleting newly added files.
1231 are different and by update logic to avoid deleting newly added files.
1231 """
1232 """
1232 parents = self.parents()
1233 parents = self.parents()
1233
1234
1234 man1 = parents[0].manifest()
1235 man1 = parents[0].manifest()
1235 man = man1.copy()
1236 man = man1.copy()
1236 if len(parents) > 1:
1237 if len(parents) > 1:
1237 man2 = self.p2().manifest()
1238 man2 = self.p2().manifest()
1238 def getman(f):
1239 def getman(f):
1239 if f in man1:
1240 if f in man1:
1240 return man1
1241 return man1
1241 return man2
1242 return man2
1242 else:
1243 else:
1243 getman = lambda f: man1
1244 getman = lambda f: man1
1244
1245
1245 copied = self._repo.dirstate.copies()
1246 copied = self._repo.dirstate.copies()
1246 ff = self._flagfunc
1247 ff = self._flagfunc
1247 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1248 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1248 for f in l:
1249 for f in l:
1249 orig = copied.get(f, f)
1250 orig = copied.get(f, f)
1250 man[f] = getman(orig).get(orig, nullid) + i
1251 man[f] = getman(orig).get(orig, nullid) + i
1251 try:
1252 try:
1252 man.setflag(f, ff(f))
1253 man.setflag(f, ff(f))
1253 except OSError:
1254 except OSError:
1254 pass
1255 pass
1255
1256
1256 for f in self._status.deleted + self._status.removed:
1257 for f in self._status.deleted + self._status.removed:
1257 if f in man:
1258 if f in man:
1258 del man[f]
1259 del man[f]
1259
1260
1260 return man
1261 return man
1261
1262
1262 @propertycache
1263 @propertycache
1263 def _status(self):
1264 def _status(self):
1264 return self._repo.status()
1265 return self._repo.status()
1265
1266
1266 @propertycache
1267 @propertycache
1267 def _user(self):
1268 def _user(self):
1268 return self._repo.ui.username()
1269 return self._repo.ui.username()
1269
1270
1270 @propertycache
1271 @propertycache
1271 def _date(self):
1272 def _date(self):
1272 return util.makedate()
1273 return util.makedate()
1273
1274
1274 def subrev(self, subpath):
1275 def subrev(self, subpath):
1275 return None
1276 return None
1276
1277
1277 def manifestnode(self):
1278 def manifestnode(self):
1278 return None
1279 return None
1279 def user(self):
1280 def user(self):
1280 return self._user or self._repo.ui.username()
1281 return self._user or self._repo.ui.username()
1281 def date(self):
1282 def date(self):
1282 return self._date
1283 return self._date
1283 def description(self):
1284 def description(self):
1284 return self._text
1285 return self._text
1285 def files(self):
1286 def files(self):
1286 return sorted(self._status.modified + self._status.added +
1287 return sorted(self._status.modified + self._status.added +
1287 self._status.removed)
1288 self._status.removed)
1288
1289
1289 def modified(self):
1290 def modified(self):
1290 return self._status.modified
1291 return self._status.modified
1291 def added(self):
1292 def added(self):
1292 return self._status.added
1293 return self._status.added
1293 def removed(self):
1294 def removed(self):
1294 return self._status.removed
1295 return self._status.removed
1295 def deleted(self):
1296 def deleted(self):
1296 return self._status.deleted
1297 return self._status.deleted
1297 def branch(self):
1298 def branch(self):
1298 return encoding.tolocal(self._extra['branch'])
1299 return encoding.tolocal(self._extra['branch'])
1299 def closesbranch(self):
1300 def closesbranch(self):
1300 return 'close' in self._extra
1301 return 'close' in self._extra
1301 def extra(self):
1302 def extra(self):
1302 return self._extra
1303 return self._extra
1303
1304
1304 def tags(self):
1305 def tags(self):
1305 return []
1306 return []
1306
1307
1307 def bookmarks(self):
1308 def bookmarks(self):
1308 b = []
1309 b = []
1309 for p in self.parents():
1310 for p in self.parents():
1310 b.extend(p.bookmarks())
1311 b.extend(p.bookmarks())
1311 return b
1312 return b
1312
1313
1313 def phase(self):
1314 def phase(self):
1314 phase = phases.draft # default phase to draft
1315 phase = phases.draft # default phase to draft
1315 for p in self.parents():
1316 for p in self.parents():
1316 phase = max(phase, p.phase())
1317 phase = max(phase, p.phase())
1317 return phase
1318 return phase
1318
1319
1319 def hidden(self):
1320 def hidden(self):
1320 return False
1321 return False
1321
1322
1322 def children(self):
1323 def children(self):
1323 return []
1324 return []
1324
1325
1325 def flags(self, path):
1326 def flags(self, path):
1326 if '_manifest' in self.__dict__:
1327 if '_manifest' in self.__dict__:
1327 try:
1328 try:
1328 return self._manifest.flags(path)
1329 return self._manifest.flags(path)
1329 except KeyError:
1330 except KeyError:
1330 return ''
1331 return ''
1331
1332
1332 try:
1333 try:
1333 return self._flagfunc(path)
1334 return self._flagfunc(path)
1334 except OSError:
1335 except OSError:
1335 return ''
1336 return ''
1336
1337
1337 def ancestor(self, c2):
1338 def ancestor(self, c2):
1338 """return the "best" ancestor context of self and c2"""
1339 """return the "best" ancestor context of self and c2"""
1339 return self._parents[0].ancestor(c2) # punt on two parents for now
1340 return self._parents[0].ancestor(c2) # punt on two parents for now
1340
1341
1341 def walk(self, match):
1342 def walk(self, match):
1342 '''Generates matching file names.'''
1343 '''Generates matching file names.'''
1343 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1344 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1344 True, False))
1345 True, False))
1345
1346
1346 def matches(self, match):
1347 def matches(self, match):
1347 return sorted(self._repo.dirstate.matches(match))
1348 return sorted(self._repo.dirstate.matches(match))
1348
1349
1349 def ancestors(self):
1350 def ancestors(self):
1350 for p in self._parents:
1351 for p in self._parents:
1351 yield p
1352 yield p
1352 for a in self._repo.changelog.ancestors(
1353 for a in self._repo.changelog.ancestors(
1353 [p.rev() for p in self._parents]):
1354 [p.rev() for p in self._parents]):
1354 yield changectx(self._repo, a)
1355 yield changectx(self._repo, a)
1355
1356
1356 def markcommitted(self, node):
1357 def markcommitted(self, node):
1357 """Perform post-commit cleanup necessary after committing this ctx
1358 """Perform post-commit cleanup necessary after committing this ctx
1358
1359
1359 Specifically, this updates backing stores this working context
1360 Specifically, this updates backing stores this working context
1360 wraps to reflect the fact that the changes reflected by this
1361 wraps to reflect the fact that the changes reflected by this
1361 workingctx have been committed. For example, it marks
1362 workingctx have been committed. For example, it marks
1362 modified and added files as normal in the dirstate.
1363 modified and added files as normal in the dirstate.
1363
1364
1364 """
1365 """
1365
1366
1366 self._repo.dirstate.beginparentchange()
1367 self._repo.dirstate.beginparentchange()
1367 for f in self.modified() + self.added():
1368 for f in self.modified() + self.added():
1368 self._repo.dirstate.normal(f)
1369 self._repo.dirstate.normal(f)
1369 for f in self.removed():
1370 for f in self.removed():
1370 self._repo.dirstate.drop(f)
1371 self._repo.dirstate.drop(f)
1371 self._repo.dirstate.setparents(node)
1372 self._repo.dirstate.setparents(node)
1372 self._repo.dirstate.endparentchange()
1373 self._repo.dirstate.endparentchange()
1373
1374
1374 # write changes out explicitly, because nesting wlock at
1375 # write changes out explicitly, because nesting wlock at
1375 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1376 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1376 # from immediately doing so for subsequent changing files
1377 # from immediately doing so for subsequent changing files
1377 self._repo.dirstate.write(self._repo.currenttransaction())
1378 self._repo.dirstate.write(self._repo.currenttransaction())
1378
1379
1379 class workingctx(committablectx):
1380 class workingctx(committablectx):
1380 """A workingctx object makes access to data related to
1381 """A workingctx object makes access to data related to
1381 the current working directory convenient.
1382 the current working directory convenient.
1382 date - any valid date string or (unixtime, offset), or None.
1383 date - any valid date string or (unixtime, offset), or None.
1383 user - username string, or None.
1384 user - username string, or None.
1384 extra - a dictionary of extra values, or None.
1385 extra - a dictionary of extra values, or None.
1385 changes - a list of file lists as returned by localrepo.status()
1386 changes - a list of file lists as returned by localrepo.status()
1386 or None to use the repository status.
1387 or None to use the repository status.
1387 """
1388 """
1388 def __init__(self, repo, text="", user=None, date=None, extra=None,
1389 def __init__(self, repo, text="", user=None, date=None, extra=None,
1389 changes=None):
1390 changes=None):
1390 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1391 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1391
1392
1392 def __iter__(self):
1393 def __iter__(self):
1393 d = self._repo.dirstate
1394 d = self._repo.dirstate
1394 for f in d:
1395 for f in d:
1395 if d[f] != 'r':
1396 if d[f] != 'r':
1396 yield f
1397 yield f
1397
1398
1398 def __contains__(self, key):
1399 def __contains__(self, key):
1399 return self._repo.dirstate[key] not in "?r"
1400 return self._repo.dirstate[key] not in "?r"
1400
1401
1401 def hex(self):
1402 def hex(self):
1402 return hex(wdirid)
1403 return hex(wdirid)
1403
1404
1404 @propertycache
1405 @propertycache
1405 def _parents(self):
1406 def _parents(self):
1406 p = self._repo.dirstate.parents()
1407 p = self._repo.dirstate.parents()
1407 if p[1] == nullid:
1408 if p[1] == nullid:
1408 p = p[:-1]
1409 p = p[:-1]
1409 return [changectx(self._repo, x) for x in p]
1410 return [changectx(self._repo, x) for x in p]
1410
1411
1411 def filectx(self, path, filelog=None):
1412 def filectx(self, path, filelog=None):
1412 """get a file context from the working directory"""
1413 """get a file context from the working directory"""
1413 return workingfilectx(self._repo, path, workingctx=self,
1414 return workingfilectx(self._repo, path, workingctx=self,
1414 filelog=filelog)
1415 filelog=filelog)
1415
1416
1416 def dirty(self, missing=False, merge=True, branch=True):
1417 def dirty(self, missing=False, merge=True, branch=True):
1417 "check whether a working directory is modified"
1418 "check whether a working directory is modified"
1418 # check subrepos first
1419 # check subrepos first
1419 for s in sorted(self.substate):
1420 for s in sorted(self.substate):
1420 if self.sub(s).dirty():
1421 if self.sub(s).dirty():
1421 return True
1422 return True
1422 # check current working dir
1423 # check current working dir
1423 return ((merge and self.p2()) or
1424 return ((merge and self.p2()) or
1424 (branch and self.branch() != self.p1().branch()) or
1425 (branch and self.branch() != self.p1().branch()) or
1425 self.modified() or self.added() or self.removed() or
1426 self.modified() or self.added() or self.removed() or
1426 (missing and self.deleted()))
1427 (missing and self.deleted()))
1427
1428
1428 def add(self, list, prefix=""):
1429 def add(self, list, prefix=""):
1429 join = lambda f: os.path.join(prefix, f)
1430 join = lambda f: os.path.join(prefix, f)
1430 with self._repo.wlock():
1431 with self._repo.wlock():
1431 ui, ds = self._repo.ui, self._repo.dirstate
1432 ui, ds = self._repo.ui, self._repo.dirstate
1432 rejected = []
1433 rejected = []
1433 lstat = self._repo.wvfs.lstat
1434 lstat = self._repo.wvfs.lstat
1434 for f in list:
1435 for f in list:
1435 scmutil.checkportable(ui, join(f))
1436 scmutil.checkportable(ui, join(f))
1436 try:
1437 try:
1437 st = lstat(f)
1438 st = lstat(f)
1438 except OSError:
1439 except OSError:
1439 ui.warn(_("%s does not exist!\n") % join(f))
1440 ui.warn(_("%s does not exist!\n") % join(f))
1440 rejected.append(f)
1441 rejected.append(f)
1441 continue
1442 continue
1442 if st.st_size > 10000000:
1443 if st.st_size > 10000000:
1443 ui.warn(_("%s: up to %d MB of RAM may be required "
1444 ui.warn(_("%s: up to %d MB of RAM may be required "
1444 "to manage this file\n"
1445 "to manage this file\n"
1445 "(use 'hg revert %s' to cancel the "
1446 "(use 'hg revert %s' to cancel the "
1446 "pending addition)\n")
1447 "pending addition)\n")
1447 % (f, 3 * st.st_size // 1000000, join(f)))
1448 % (f, 3 * st.st_size // 1000000, join(f)))
1448 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1449 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1449 ui.warn(_("%s not added: only files and symlinks "
1450 ui.warn(_("%s not added: only files and symlinks "
1450 "supported currently\n") % join(f))
1451 "supported currently\n") % join(f))
1451 rejected.append(f)
1452 rejected.append(f)
1452 elif ds[f] in 'amn':
1453 elif ds[f] in 'amn':
1453 ui.warn(_("%s already tracked!\n") % join(f))
1454 ui.warn(_("%s already tracked!\n") % join(f))
1454 elif ds[f] == 'r':
1455 elif ds[f] == 'r':
1455 ds.normallookup(f)
1456 ds.normallookup(f)
1456 else:
1457 else:
1457 ds.add(f)
1458 ds.add(f)
1458 return rejected
1459 return rejected
1459
1460
1460 def forget(self, files, prefix=""):
1461 def forget(self, files, prefix=""):
1461 join = lambda f: os.path.join(prefix, f)
1462 join = lambda f: os.path.join(prefix, f)
1462 with self._repo.wlock():
1463 with self._repo.wlock():
1463 rejected = []
1464 rejected = []
1464 for f in files:
1465 for f in files:
1465 if f not in self._repo.dirstate:
1466 if f not in self._repo.dirstate:
1466 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1467 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1467 rejected.append(f)
1468 rejected.append(f)
1468 elif self._repo.dirstate[f] != 'a':
1469 elif self._repo.dirstate[f] != 'a':
1469 self._repo.dirstate.remove(f)
1470 self._repo.dirstate.remove(f)
1470 else:
1471 else:
1471 self._repo.dirstate.drop(f)
1472 self._repo.dirstate.drop(f)
1472 return rejected
1473 return rejected
1473
1474
1474 def undelete(self, list):
1475 def undelete(self, list):
1475 pctxs = self.parents()
1476 pctxs = self.parents()
1476 with self._repo.wlock():
1477 with self._repo.wlock():
1477 for f in list:
1478 for f in list:
1478 if self._repo.dirstate[f] != 'r':
1479 if self._repo.dirstate[f] != 'r':
1479 self._repo.ui.warn(_("%s not removed!\n") % f)
1480 self._repo.ui.warn(_("%s not removed!\n") % f)
1480 else:
1481 else:
1481 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1482 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1482 t = fctx.data()
1483 t = fctx.data()
1483 self._repo.wwrite(f, t, fctx.flags())
1484 self._repo.wwrite(f, t, fctx.flags())
1484 self._repo.dirstate.normal(f)
1485 self._repo.dirstate.normal(f)
1485
1486
1486 def copy(self, source, dest):
1487 def copy(self, source, dest):
1487 try:
1488 try:
1488 st = self._repo.wvfs.lstat(dest)
1489 st = self._repo.wvfs.lstat(dest)
1489 except OSError as err:
1490 except OSError as err:
1490 if err.errno != errno.ENOENT:
1491 if err.errno != errno.ENOENT:
1491 raise
1492 raise
1492 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1493 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1493 return
1494 return
1494 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1495 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1495 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1496 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1496 "symbolic link\n") % dest)
1497 "symbolic link\n") % dest)
1497 else:
1498 else:
1498 with self._repo.wlock():
1499 with self._repo.wlock():
1499 if self._repo.dirstate[dest] in '?':
1500 if self._repo.dirstate[dest] in '?':
1500 self._repo.dirstate.add(dest)
1501 self._repo.dirstate.add(dest)
1501 elif self._repo.dirstate[dest] in 'r':
1502 elif self._repo.dirstate[dest] in 'r':
1502 self._repo.dirstate.normallookup(dest)
1503 self._repo.dirstate.normallookup(dest)
1503 self._repo.dirstate.copy(source, dest)
1504 self._repo.dirstate.copy(source, dest)
1504
1505
1505 def match(self, pats=[], include=None, exclude=None, default='glob',
1506 def match(self, pats=[], include=None, exclude=None, default='glob',
1506 listsubrepos=False, badfn=None):
1507 listsubrepos=False, badfn=None):
1507 r = self._repo
1508 r = self._repo
1508
1509
1509 # Only a case insensitive filesystem needs magic to translate user input
1510 # Only a case insensitive filesystem needs magic to translate user input
1510 # to actual case in the filesystem.
1511 # to actual case in the filesystem.
1511 if not util.fscasesensitive(r.root):
1512 if not util.fscasesensitive(r.root):
1512 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1513 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1513 exclude, default, r.auditor, self,
1514 exclude, default, r.auditor, self,
1514 listsubrepos=listsubrepos,
1515 listsubrepos=listsubrepos,
1515 badfn=badfn)
1516 badfn=badfn)
1516 return matchmod.match(r.root, r.getcwd(), pats,
1517 return matchmod.match(r.root, r.getcwd(), pats,
1517 include, exclude, default,
1518 include, exclude, default,
1518 auditor=r.auditor, ctx=self,
1519 auditor=r.auditor, ctx=self,
1519 listsubrepos=listsubrepos, badfn=badfn)
1520 listsubrepos=listsubrepos, badfn=badfn)
1520
1521
1521 def _filtersuspectsymlink(self, files):
1522 def _filtersuspectsymlink(self, files):
1522 if not files or self._repo.dirstate._checklink:
1523 if not files or self._repo.dirstate._checklink:
1523 return files
1524 return files
1524
1525
1525 # Symlink placeholders may get non-symlink-like contents
1526 # Symlink placeholders may get non-symlink-like contents
1526 # via user error or dereferencing by NFS or Samba servers,
1527 # via user error or dereferencing by NFS or Samba servers,
1527 # so we filter out any placeholders that don't look like a
1528 # so we filter out any placeholders that don't look like a
1528 # symlink
1529 # symlink
1529 sane = []
1530 sane = []
1530 for f in files:
1531 for f in files:
1531 if self.flags(f) == 'l':
1532 if self.flags(f) == 'l':
1532 d = self[f].data()
1533 d = self[f].data()
1533 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1534 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1534 self._repo.ui.debug('ignoring suspect symlink placeholder'
1535 self._repo.ui.debug('ignoring suspect symlink placeholder'
1535 ' "%s"\n' % f)
1536 ' "%s"\n' % f)
1536 continue
1537 continue
1537 sane.append(f)
1538 sane.append(f)
1538 return sane
1539 return sane
1539
1540
1540 def _checklookup(self, files):
1541 def _checklookup(self, files):
1541 # check for any possibly clean files
1542 # check for any possibly clean files
1542 if not files:
1543 if not files:
1543 return [], []
1544 return [], []
1544
1545
1545 modified = []
1546 modified = []
1546 fixup = []
1547 fixup = []
1547 pctx = self._parents[0]
1548 pctx = self._parents[0]
1548 # do a full compare of any files that might have changed
1549 # do a full compare of any files that might have changed
1549 for f in sorted(files):
1550 for f in sorted(files):
1550 if (f not in pctx or self.flags(f) != pctx.flags(f)
1551 if (f not in pctx or self.flags(f) != pctx.flags(f)
1551 or pctx[f].cmp(self[f])):
1552 or pctx[f].cmp(self[f])):
1552 modified.append(f)
1553 modified.append(f)
1553 else:
1554 else:
1554 fixup.append(f)
1555 fixup.append(f)
1555
1556
1556 # update dirstate for files that are actually clean
1557 # update dirstate for files that are actually clean
1557 if fixup:
1558 if fixup:
1558 try:
1559 try:
1559 # updating the dirstate is optional
1560 # updating the dirstate is optional
1560 # so we don't wait on the lock
1561 # so we don't wait on the lock
1561 # wlock can invalidate the dirstate, so cache normal _after_
1562 # wlock can invalidate the dirstate, so cache normal _after_
1562 # taking the lock
1563 # taking the lock
1563 with self._repo.wlock(False):
1564 with self._repo.wlock(False):
1564 normal = self._repo.dirstate.normal
1565 normal = self._repo.dirstate.normal
1565 for f in fixup:
1566 for f in fixup:
1566 normal(f)
1567 normal(f)
1567 # write changes out explicitly, because nesting
1568 # write changes out explicitly, because nesting
1568 # wlock at runtime may prevent 'wlock.release()'
1569 # wlock at runtime may prevent 'wlock.release()'
1569 # after this block from doing so for subsequent
1570 # after this block from doing so for subsequent
1570 # changing files
1571 # changing files
1571 self._repo.dirstate.write(self._repo.currenttransaction())
1572 self._repo.dirstate.write(self._repo.currenttransaction())
1572 except error.LockError:
1573 except error.LockError:
1573 pass
1574 pass
1574 return modified, fixup
1575 return modified, fixup
1575
1576
1576 def _manifestmatches(self, match, s):
1577 def _manifestmatches(self, match, s):
1577 """Slow path for workingctx
1578 """Slow path for workingctx
1578
1579
1579 The fast path is when we compare the working directory to its parent
1580 The fast path is when we compare the working directory to its parent
1580 which means this function is comparing with a non-parent; therefore we
1581 which means this function is comparing with a non-parent; therefore we
1581 need to build a manifest and return what matches.
1582 need to build a manifest and return what matches.
1582 """
1583 """
1583 mf = self._repo['.']._manifestmatches(match, s)
1584 mf = self._repo['.']._manifestmatches(match, s)
1584 for f in s.modified + s.added:
1585 for f in s.modified + s.added:
1585 mf[f] = _newnode
1586 mf[f] = _newnode
1586 mf.setflag(f, self.flags(f))
1587 mf.setflag(f, self.flags(f))
1587 for f in s.removed:
1588 for f in s.removed:
1588 if f in mf:
1589 if f in mf:
1589 del mf[f]
1590 del mf[f]
1590 return mf
1591 return mf
1591
1592
1592 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1593 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1593 unknown=False):
1594 unknown=False):
1594 '''Gets the status from the dirstate -- internal use only.'''
1595 '''Gets the status from the dirstate -- internal use only.'''
1595 listignored, listclean, listunknown = ignored, clean, unknown
1596 listignored, listclean, listunknown = ignored, clean, unknown
1596 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1597 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1597 subrepos = []
1598 subrepos = []
1598 if '.hgsub' in self:
1599 if '.hgsub' in self:
1599 subrepos = sorted(self.substate)
1600 subrepos = sorted(self.substate)
1600 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1601 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1601 listclean, listunknown)
1602 listclean, listunknown)
1602
1603
1603 # check for any possibly clean files
1604 # check for any possibly clean files
1604 if cmp:
1605 if cmp:
1605 modified2, fixup = self._checklookup(cmp)
1606 modified2, fixup = self._checklookup(cmp)
1606 s.modified.extend(modified2)
1607 s.modified.extend(modified2)
1607
1608
1608 # update dirstate for files that are actually clean
1609 # update dirstate for files that are actually clean
1609 if fixup and listclean:
1610 if fixup and listclean:
1610 s.clean.extend(fixup)
1611 s.clean.extend(fixup)
1611
1612
1612 if match.always():
1613 if match.always():
1613 # cache for performance
1614 # cache for performance
1614 if s.unknown or s.ignored or s.clean:
1615 if s.unknown or s.ignored or s.clean:
1615 # "_status" is cached with list*=False in the normal route
1616 # "_status" is cached with list*=False in the normal route
1616 self._status = scmutil.status(s.modified, s.added, s.removed,
1617 self._status = scmutil.status(s.modified, s.added, s.removed,
1617 s.deleted, [], [], [])
1618 s.deleted, [], [], [])
1618 else:
1619 else:
1619 self._status = s
1620 self._status = s
1620
1621
1621 return s
1622 return s
1622
1623
1623 def _buildstatus(self, other, s, match, listignored, listclean,
1624 def _buildstatus(self, other, s, match, listignored, listclean,
1624 listunknown):
1625 listunknown):
1625 """build a status with respect to another context
1626 """build a status with respect to another context
1626
1627
1627 This includes logic for maintaining the fast path of status when
1628 This includes logic for maintaining the fast path of status when
1628 comparing the working directory against its parent, which is to skip
1629 comparing the working directory against its parent, which is to skip
1629 building a new manifest if self (working directory) is not comparing
1630 building a new manifest if self (working directory) is not comparing
1630 against its parent (repo['.']).
1631 against its parent (repo['.']).
1631 """
1632 """
1632 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1633 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1633 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1634 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1634 # might have accidentally ended up with the entire contents of the file
1635 # might have accidentally ended up with the entire contents of the file
1635 # they are supposed to be linking to.
1636 # they are supposed to be linking to.
1636 s.modified[:] = self._filtersuspectsymlink(s.modified)
1637 s.modified[:] = self._filtersuspectsymlink(s.modified)
1637 if other != self._repo['.']:
1638 if other != self._repo['.']:
1638 s = super(workingctx, self)._buildstatus(other, s, match,
1639 s = super(workingctx, self)._buildstatus(other, s, match,
1639 listignored, listclean,
1640 listignored, listclean,
1640 listunknown)
1641 listunknown)
1641 return s
1642 return s
1642
1643
1643 def _matchstatus(self, other, match):
1644 def _matchstatus(self, other, match):
1644 """override the match method with a filter for directory patterns
1645 """override the match method with a filter for directory patterns
1645
1646
1646 We use inheritance to customize the match.bad method only in cases of
1647 We use inheritance to customize the match.bad method only in cases of
1647 workingctx since it belongs only to the working directory when
1648 workingctx since it belongs only to the working directory when
1648 comparing against the parent changeset.
1649 comparing against the parent changeset.
1649
1650
1650 If we aren't comparing against the working directory's parent, then we
1651 If we aren't comparing against the working directory's parent, then we
1651 just use the default match object sent to us.
1652 just use the default match object sent to us.
1652 """
1653 """
1653 superself = super(workingctx, self)
1654 superself = super(workingctx, self)
1654 match = superself._matchstatus(other, match)
1655 match = superself._matchstatus(other, match)
1655 if other != self._repo['.']:
1656 if other != self._repo['.']:
1656 def bad(f, msg):
1657 def bad(f, msg):
1657 # 'f' may be a directory pattern from 'match.files()',
1658 # 'f' may be a directory pattern from 'match.files()',
1658 # so 'f not in ctx1' is not enough
1659 # so 'f not in ctx1' is not enough
1659 if f not in other and not other.hasdir(f):
1660 if f not in other and not other.hasdir(f):
1660 self._repo.ui.warn('%s: %s\n' %
1661 self._repo.ui.warn('%s: %s\n' %
1661 (self._repo.dirstate.pathto(f), msg))
1662 (self._repo.dirstate.pathto(f), msg))
1662 match.bad = bad
1663 match.bad = bad
1663 return match
1664 return match
1664
1665
1665 class committablefilectx(basefilectx):
1666 class committablefilectx(basefilectx):
1666 """A committablefilectx provides common functionality for a file context
1667 """A committablefilectx provides common functionality for a file context
1667 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1668 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1668 def __init__(self, repo, path, filelog=None, ctx=None):
1669 def __init__(self, repo, path, filelog=None, ctx=None):
1669 self._repo = repo
1670 self._repo = repo
1670 self._path = path
1671 self._path = path
1671 self._changeid = None
1672 self._changeid = None
1672 self._filerev = self._filenode = None
1673 self._filerev = self._filenode = None
1673
1674
1674 if filelog is not None:
1675 if filelog is not None:
1675 self._filelog = filelog
1676 self._filelog = filelog
1676 if ctx:
1677 if ctx:
1677 self._changectx = ctx
1678 self._changectx = ctx
1678
1679
1679 def __nonzero__(self):
1680 def __nonzero__(self):
1680 return True
1681 return True
1681
1682
1682 def linkrev(self):
1683 def linkrev(self):
1683 # linked to self._changectx no matter if file is modified or not
1684 # linked to self._changectx no matter if file is modified or not
1684 return self.rev()
1685 return self.rev()
1685
1686
1686 def parents(self):
1687 def parents(self):
1687 '''return parent filectxs, following copies if necessary'''
1688 '''return parent filectxs, following copies if necessary'''
1688 def filenode(ctx, path):
1689 def filenode(ctx, path):
1689 return ctx._manifest.get(path, nullid)
1690 return ctx._manifest.get(path, nullid)
1690
1691
1691 path = self._path
1692 path = self._path
1692 fl = self._filelog
1693 fl = self._filelog
1693 pcl = self._changectx._parents
1694 pcl = self._changectx._parents
1694 renamed = self.renamed()
1695 renamed = self.renamed()
1695
1696
1696 if renamed:
1697 if renamed:
1697 pl = [renamed + (None,)]
1698 pl = [renamed + (None,)]
1698 else:
1699 else:
1699 pl = [(path, filenode(pcl[0], path), fl)]
1700 pl = [(path, filenode(pcl[0], path), fl)]
1700
1701
1701 for pc in pcl[1:]:
1702 for pc in pcl[1:]:
1702 pl.append((path, filenode(pc, path), fl))
1703 pl.append((path, filenode(pc, path), fl))
1703
1704
1704 return [self._parentfilectx(p, fileid=n, filelog=l)
1705 return [self._parentfilectx(p, fileid=n, filelog=l)
1705 for p, n, l in pl if n != nullid]
1706 for p, n, l in pl if n != nullid]
1706
1707
1707 def children(self):
1708 def children(self):
1708 return []
1709 return []
1709
1710
1710 class workingfilectx(committablefilectx):
1711 class workingfilectx(committablefilectx):
1711 """A workingfilectx object makes access to data related to a particular
1712 """A workingfilectx object makes access to data related to a particular
1712 file in the working directory convenient."""
1713 file in the working directory convenient."""
1713 def __init__(self, repo, path, filelog=None, workingctx=None):
1714 def __init__(self, repo, path, filelog=None, workingctx=None):
1714 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1715 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1715
1716
1716 @propertycache
1717 @propertycache
1717 def _changectx(self):
1718 def _changectx(self):
1718 return workingctx(self._repo)
1719 return workingctx(self._repo)
1719
1720
1720 def data(self):
1721 def data(self):
1721 return self._repo.wread(self._path)
1722 return self._repo.wread(self._path)
1722 def renamed(self):
1723 def renamed(self):
1723 rp = self._repo.dirstate.copied(self._path)
1724 rp = self._repo.dirstate.copied(self._path)
1724 if not rp:
1725 if not rp:
1725 return None
1726 return None
1726 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1727 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1727
1728
1728 def size(self):
1729 def size(self):
1729 return self._repo.wvfs.lstat(self._path).st_size
1730 return self._repo.wvfs.lstat(self._path).st_size
1730 def date(self):
1731 def date(self):
1731 t, tz = self._changectx.date()
1732 t, tz = self._changectx.date()
1732 try:
1733 try:
1733 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1734 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1734 except OSError as err:
1735 except OSError as err:
1735 if err.errno != errno.ENOENT:
1736 if err.errno != errno.ENOENT:
1736 raise
1737 raise
1737 return (t, tz)
1738 return (t, tz)
1738
1739
1739 def cmp(self, fctx):
1740 def cmp(self, fctx):
1740 """compare with other file context
1741 """compare with other file context
1741
1742
1742 returns True if different than fctx.
1743 returns True if different than fctx.
1743 """
1744 """
1744 # fctx should be a filectx (not a workingfilectx)
1745 # fctx should be a filectx (not a workingfilectx)
1745 # invert comparison to reuse the same code path
1746 # invert comparison to reuse the same code path
1746 return fctx.cmp(self)
1747 return fctx.cmp(self)
1747
1748
1748 def remove(self, ignoremissing=False):
1749 def remove(self, ignoremissing=False):
1749 """wraps unlink for a repo's working directory"""
1750 """wraps unlink for a repo's working directory"""
1750 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1751 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1751
1752
1752 def write(self, data, flags):
1753 def write(self, data, flags):
1753 """wraps repo.wwrite"""
1754 """wraps repo.wwrite"""
1754 self._repo.wwrite(self._path, data, flags)
1755 self._repo.wwrite(self._path, data, flags)
1755
1756
1756 class workingcommitctx(workingctx):
1757 class workingcommitctx(workingctx):
1757 """A workingcommitctx object makes access to data related to
1758 """A workingcommitctx object makes access to data related to
1758 the revision being committed convenient.
1759 the revision being committed convenient.
1759
1760
1760 This hides changes in the working directory, if they aren't
1761 This hides changes in the working directory, if they aren't
1761 committed in this context.
1762 committed in this context.
1762 """
1763 """
1763 def __init__(self, repo, changes,
1764 def __init__(self, repo, changes,
1764 text="", user=None, date=None, extra=None):
1765 text="", user=None, date=None, extra=None):
1765 super(workingctx, self).__init__(repo, text, user, date, extra,
1766 super(workingctx, self).__init__(repo, text, user, date, extra,
1766 changes)
1767 changes)
1767
1768
1768 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1769 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1769 unknown=False):
1770 unknown=False):
1770 """Return matched files only in ``self._status``
1771 """Return matched files only in ``self._status``
1771
1772
1772 Uncommitted files appear "clean" via this context, even if
1773 Uncommitted files appear "clean" via this context, even if
1773 they aren't actually so in the working directory.
1774 they aren't actually so in the working directory.
1774 """
1775 """
1775 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1776 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1776 if clean:
1777 if clean:
1777 clean = [f for f in self._manifest if f not in self._changedset]
1778 clean = [f for f in self._manifest if f not in self._changedset]
1778 else:
1779 else:
1779 clean = []
1780 clean = []
1780 return scmutil.status([f for f in self._status.modified if match(f)],
1781 return scmutil.status([f for f in self._status.modified if match(f)],
1781 [f for f in self._status.added if match(f)],
1782 [f for f in self._status.added if match(f)],
1782 [f for f in self._status.removed if match(f)],
1783 [f for f in self._status.removed if match(f)],
1783 [], [], [], clean)
1784 [], [], [], clean)
1784
1785
1785 @propertycache
1786 @propertycache
1786 def _changedset(self):
1787 def _changedset(self):
1787 """Return the set of files changed in this context
1788 """Return the set of files changed in this context
1788 """
1789 """
1789 changed = set(self._status.modified)
1790 changed = set(self._status.modified)
1790 changed.update(self._status.added)
1791 changed.update(self._status.added)
1791 changed.update(self._status.removed)
1792 changed.update(self._status.removed)
1792 return changed
1793 return changed
1793
1794
1794 def makecachingfilectxfn(func):
1795 def makecachingfilectxfn(func):
1795 """Create a filectxfn that caches based on the path.
1796 """Create a filectxfn that caches based on the path.
1796
1797
1797 We can't use util.cachefunc because it uses all arguments as the cache
1798 We can't use util.cachefunc because it uses all arguments as the cache
1798 key and this creates a cycle since the arguments include the repo and
1799 key and this creates a cycle since the arguments include the repo and
1799 memctx.
1800 memctx.
1800 """
1801 """
1801 cache = {}
1802 cache = {}
1802
1803
1803 def getfilectx(repo, memctx, path):
1804 def getfilectx(repo, memctx, path):
1804 if path not in cache:
1805 if path not in cache:
1805 cache[path] = func(repo, memctx, path)
1806 cache[path] = func(repo, memctx, path)
1806 return cache[path]
1807 return cache[path]
1807
1808
1808 return getfilectx
1809 return getfilectx
1809
1810
1810 class memctx(committablectx):
1811 class memctx(committablectx):
1811 """Use memctx to perform in-memory commits via localrepo.commitctx().
1812 """Use memctx to perform in-memory commits via localrepo.commitctx().
1812
1813
1813 Revision information is supplied at initialization time while
1814 Revision information is supplied at initialization time while
1814 related files data and is made available through a callback
1815 related files data and is made available through a callback
1815 mechanism. 'repo' is the current localrepo, 'parents' is a
1816 mechanism. 'repo' is the current localrepo, 'parents' is a
1816 sequence of two parent revisions identifiers (pass None for every
1817 sequence of two parent revisions identifiers (pass None for every
1817 missing parent), 'text' is the commit message and 'files' lists
1818 missing parent), 'text' is the commit message and 'files' lists
1818 names of files touched by the revision (normalized and relative to
1819 names of files touched by the revision (normalized and relative to
1819 repository root).
1820 repository root).
1820
1821
1821 filectxfn(repo, memctx, path) is a callable receiving the
1822 filectxfn(repo, memctx, path) is a callable receiving the
1822 repository, the current memctx object and the normalized path of
1823 repository, the current memctx object and the normalized path of
1823 requested file, relative to repository root. It is fired by the
1824 requested file, relative to repository root. It is fired by the
1824 commit function for every file in 'files', but calls order is
1825 commit function for every file in 'files', but calls order is
1825 undefined. If the file is available in the revision being
1826 undefined. If the file is available in the revision being
1826 committed (updated or added), filectxfn returns a memfilectx
1827 committed (updated or added), filectxfn returns a memfilectx
1827 object. If the file was removed, filectxfn raises an
1828 object. If the file was removed, filectxfn raises an
1828 IOError. Moved files are represented by marking the source file
1829 IOError. Moved files are represented by marking the source file
1829 removed and the new file added with copy information (see
1830 removed and the new file added with copy information (see
1830 memfilectx).
1831 memfilectx).
1831
1832
1832 user receives the committer name and defaults to current
1833 user receives the committer name and defaults to current
1833 repository username, date is the commit date in any format
1834 repository username, date is the commit date in any format
1834 supported by util.parsedate() and defaults to current date, extra
1835 supported by util.parsedate() and defaults to current date, extra
1835 is a dictionary of metadata or is left empty.
1836 is a dictionary of metadata or is left empty.
1836 """
1837 """
1837
1838
1838 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1839 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1839 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1840 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1840 # this field to determine what to do in filectxfn.
1841 # this field to determine what to do in filectxfn.
1841 _returnnoneformissingfiles = True
1842 _returnnoneformissingfiles = True
1842
1843
1843 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1844 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1844 date=None, extra=None, editor=False):
1845 date=None, extra=None, editor=False):
1845 super(memctx, self).__init__(repo, text, user, date, extra)
1846 super(memctx, self).__init__(repo, text, user, date, extra)
1846 self._rev = None
1847 self._rev = None
1847 self._node = None
1848 self._node = None
1848 parents = [(p or nullid) for p in parents]
1849 parents = [(p or nullid) for p in parents]
1849 p1, p2 = parents
1850 p1, p2 = parents
1850 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1851 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1851 files = sorted(set(files))
1852 files = sorted(set(files))
1852 self._files = files
1853 self._files = files
1853 self.substate = {}
1854 self.substate = {}
1854
1855
1855 # if store is not callable, wrap it in a function
1856 # if store is not callable, wrap it in a function
1856 if not callable(filectxfn):
1857 if not callable(filectxfn):
1857 def getfilectx(repo, memctx, path):
1858 def getfilectx(repo, memctx, path):
1858 fctx = filectxfn[path]
1859 fctx = filectxfn[path]
1859 # this is weird but apparently we only keep track of one parent
1860 # this is weird but apparently we only keep track of one parent
1860 # (why not only store that instead of a tuple?)
1861 # (why not only store that instead of a tuple?)
1861 copied = fctx.renamed()
1862 copied = fctx.renamed()
1862 if copied:
1863 if copied:
1863 copied = copied[0]
1864 copied = copied[0]
1864 return memfilectx(repo, path, fctx.data(),
1865 return memfilectx(repo, path, fctx.data(),
1865 islink=fctx.islink(), isexec=fctx.isexec(),
1866 islink=fctx.islink(), isexec=fctx.isexec(),
1866 copied=copied, memctx=memctx)
1867 copied=copied, memctx=memctx)
1867 self._filectxfn = getfilectx
1868 self._filectxfn = getfilectx
1868 else:
1869 else:
1869 # memoizing increases performance for e.g. vcs convert scenarios.
1870 # memoizing increases performance for e.g. vcs convert scenarios.
1870 self._filectxfn = makecachingfilectxfn(filectxfn)
1871 self._filectxfn = makecachingfilectxfn(filectxfn)
1871
1872
1872 if extra:
1873 if extra:
1873 self._extra = extra.copy()
1874 self._extra = extra.copy()
1874 else:
1875 else:
1875 self._extra = {}
1876 self._extra = {}
1876
1877
1877 if self._extra.get('branch', '') == '':
1878 if self._extra.get('branch', '') == '':
1878 self._extra['branch'] = 'default'
1879 self._extra['branch'] = 'default'
1879
1880
1880 if editor:
1881 if editor:
1881 self._text = editor(self._repo, self, [])
1882 self._text = editor(self._repo, self, [])
1882 self._repo.savecommitmessage(self._text)
1883 self._repo.savecommitmessage(self._text)
1883
1884
1884 def filectx(self, path, filelog=None):
1885 def filectx(self, path, filelog=None):
1885 """get a file context from the working directory
1886 """get a file context from the working directory
1886
1887
1887 Returns None if file doesn't exist and should be removed."""
1888 Returns None if file doesn't exist and should be removed."""
1888 return self._filectxfn(self._repo, self, path)
1889 return self._filectxfn(self._repo, self, path)
1889
1890
1890 def commit(self):
1891 def commit(self):
1891 """commit context to the repo"""
1892 """commit context to the repo"""
1892 return self._repo.commitctx(self)
1893 return self._repo.commitctx(self)
1893
1894
1894 @propertycache
1895 @propertycache
1895 def _manifest(self):
1896 def _manifest(self):
1896 """generate a manifest based on the return values of filectxfn"""
1897 """generate a manifest based on the return values of filectxfn"""
1897
1898
1898 # keep this simple for now; just worry about p1
1899 # keep this simple for now; just worry about p1
1899 pctx = self._parents[0]
1900 pctx = self._parents[0]
1900 man = pctx.manifest().copy()
1901 man = pctx.manifest().copy()
1901
1902
1902 for f in self._status.modified:
1903 for f in self._status.modified:
1903 p1node = nullid
1904 p1node = nullid
1904 p2node = nullid
1905 p2node = nullid
1905 p = pctx[f].parents() # if file isn't in pctx, check p2?
1906 p = pctx[f].parents() # if file isn't in pctx, check p2?
1906 if len(p) > 0:
1907 if len(p) > 0:
1907 p1node = p[0].filenode()
1908 p1node = p[0].filenode()
1908 if len(p) > 1:
1909 if len(p) > 1:
1909 p2node = p[1].filenode()
1910 p2node = p[1].filenode()
1910 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1911 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1911
1912
1912 for f in self._status.added:
1913 for f in self._status.added:
1913 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1914 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1914
1915
1915 for f in self._status.removed:
1916 for f in self._status.removed:
1916 if f in man:
1917 if f in man:
1917 del man[f]
1918 del man[f]
1918
1919
1919 return man
1920 return man
1920
1921
1921 @propertycache
1922 @propertycache
1922 def _status(self):
1923 def _status(self):
1923 """Calculate exact status from ``files`` specified at construction
1924 """Calculate exact status from ``files`` specified at construction
1924 """
1925 """
1925 man1 = self.p1().manifest()
1926 man1 = self.p1().manifest()
1926 p2 = self._parents[1]
1927 p2 = self._parents[1]
1927 # "1 < len(self._parents)" can't be used for checking
1928 # "1 < len(self._parents)" can't be used for checking
1928 # existence of the 2nd parent, because "memctx._parents" is
1929 # existence of the 2nd parent, because "memctx._parents" is
1929 # explicitly initialized by the list, of which length is 2.
1930 # explicitly initialized by the list, of which length is 2.
1930 if p2.node() != nullid:
1931 if p2.node() != nullid:
1931 man2 = p2.manifest()
1932 man2 = p2.manifest()
1932 managing = lambda f: f in man1 or f in man2
1933 managing = lambda f: f in man1 or f in man2
1933 else:
1934 else:
1934 managing = lambda f: f in man1
1935 managing = lambda f: f in man1
1935
1936
1936 modified, added, removed = [], [], []
1937 modified, added, removed = [], [], []
1937 for f in self._files:
1938 for f in self._files:
1938 if not managing(f):
1939 if not managing(f):
1939 added.append(f)
1940 added.append(f)
1940 elif self[f]:
1941 elif self[f]:
1941 modified.append(f)
1942 modified.append(f)
1942 else:
1943 else:
1943 removed.append(f)
1944 removed.append(f)
1944
1945
1945 return scmutil.status(modified, added, removed, [], [], [], [])
1946 return scmutil.status(modified, added, removed, [], [], [], [])
1946
1947
1947 class memfilectx(committablefilectx):
1948 class memfilectx(committablefilectx):
1948 """memfilectx represents an in-memory file to commit.
1949 """memfilectx represents an in-memory file to commit.
1949
1950
1950 See memctx and committablefilectx for more details.
1951 See memctx and committablefilectx for more details.
1951 """
1952 """
1952 def __init__(self, repo, path, data, islink=False,
1953 def __init__(self, repo, path, data, islink=False,
1953 isexec=False, copied=None, memctx=None):
1954 isexec=False, copied=None, memctx=None):
1954 """
1955 """
1955 path is the normalized file path relative to repository root.
1956 path is the normalized file path relative to repository root.
1956 data is the file content as a string.
1957 data is the file content as a string.
1957 islink is True if the file is a symbolic link.
1958 islink is True if the file is a symbolic link.
1958 isexec is True if the file is executable.
1959 isexec is True if the file is executable.
1959 copied is the source file path if current file was copied in the
1960 copied is the source file path if current file was copied in the
1960 revision being committed, or None."""
1961 revision being committed, or None."""
1961 super(memfilectx, self).__init__(repo, path, None, memctx)
1962 super(memfilectx, self).__init__(repo, path, None, memctx)
1962 self._data = data
1963 self._data = data
1963 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1964 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1964 self._copied = None
1965 self._copied = None
1965 if copied:
1966 if copied:
1966 self._copied = (copied, nullid)
1967 self._copied = (copied, nullid)
1967
1968
1968 def data(self):
1969 def data(self):
1969 return self._data
1970 return self._data
1970 def size(self):
1971 def size(self):
1971 return len(self.data())
1972 return len(self.data())
1972 def flags(self):
1973 def flags(self):
1973 return self._flags
1974 return self._flags
1974 def renamed(self):
1975 def renamed(self):
1975 return self._copied
1976 return self._copied
1976
1977
1977 def remove(self, ignoremissing=False):
1978 def remove(self, ignoremissing=False):
1978 """wraps unlink for a repo's working directory"""
1979 """wraps unlink for a repo's working directory"""
1979 # need to figure out what to do here
1980 # need to figure out what to do here
1980 del self._changectx[self._path]
1981 del self._changectx[self._path]
1981
1982
1982 def write(self, data, flags):
1983 def write(self, data, flags):
1983 """wraps repo.wwrite"""
1984 """wraps repo.wwrite"""
1984 self._data = data
1985 self._data = data
@@ -1,1245 +1,1278 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class _lazymanifest(dict):
107 class _lazymanifest(dict):
108 """This is the pure implementation of lazymanifest.
108 """This is the pure implementation of lazymanifest.
109
109
110 It has not been optimized *at all* and is not lazy.
110 It has not been optimized *at all* and is not lazy.
111 """
111 """
112
112
113 def __init__(self, data):
113 def __init__(self, data):
114 dict.__init__(self)
114 dict.__init__(self)
115 for f, n, fl in _parse(data):
115 for f, n, fl in _parse(data):
116 self[f] = n, fl
116 self[f] = n, fl
117
117
118 def __setitem__(self, k, v):
118 def __setitem__(self, k, v):
119 node, flag = v
119 node, flag = v
120 assert node is not None
120 assert node is not None
121 if len(node) > 21:
121 if len(node) > 21:
122 node = node[:21] # match c implementation behavior
122 node = node[:21] # match c implementation behavior
123 dict.__setitem__(self, k, (node, flag))
123 dict.__setitem__(self, k, (node, flag))
124
124
125 def __iter__(self):
125 def __iter__(self):
126 return iter(sorted(dict.keys(self)))
126 return iter(sorted(dict.keys(self)))
127
127
128 def iterkeys(self):
128 def iterkeys(self):
129 return iter(sorted(dict.keys(self)))
129 return iter(sorted(dict.keys(self)))
130
130
131 def iterentries(self):
131 def iterentries(self):
132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
133
133
134 def copy(self):
134 def copy(self):
135 c = _lazymanifest('')
135 c = _lazymanifest('')
136 c.update(self)
136 c.update(self)
137 return c
137 return c
138
138
139 def diff(self, m2, clean=False):
139 def diff(self, m2, clean=False):
140 '''Finds changes between the current manifest and m2.'''
140 '''Finds changes between the current manifest and m2.'''
141 diff = {}
141 diff = {}
142
142
143 for fn, e1 in self.iteritems():
143 for fn, e1 in self.iteritems():
144 if fn not in m2:
144 if fn not in m2:
145 diff[fn] = e1, (None, '')
145 diff[fn] = e1, (None, '')
146 else:
146 else:
147 e2 = m2[fn]
147 e2 = m2[fn]
148 if e1 != e2:
148 if e1 != e2:
149 diff[fn] = e1, e2
149 diff[fn] = e1, e2
150 elif clean:
150 elif clean:
151 diff[fn] = None
151 diff[fn] = None
152
152
153 for fn, e2 in m2.iteritems():
153 for fn, e2 in m2.iteritems():
154 if fn not in self:
154 if fn not in self:
155 diff[fn] = (None, ''), e2
155 diff[fn] = (None, ''), e2
156
156
157 return diff
157 return diff
158
158
159 def filtercopy(self, filterfn):
159 def filtercopy(self, filterfn):
160 c = _lazymanifest('')
160 c = _lazymanifest('')
161 for f, n, fl in self.iterentries():
161 for f, n, fl in self.iterentries():
162 if filterfn(f):
162 if filterfn(f):
163 c[f] = n, fl
163 c[f] = n, fl
164 return c
164 return c
165
165
166 def text(self):
166 def text(self):
167 """Get the full data of this manifest as a bytestring."""
167 """Get the full data of this manifest as a bytestring."""
168 return _textv1(self.iterentries())
168 return _textv1(self.iterentries())
169
169
170 try:
170 try:
171 _lazymanifest = parsers.lazymanifest
171 _lazymanifest = parsers.lazymanifest
172 except AttributeError:
172 except AttributeError:
173 pass
173 pass
174
174
175 class manifestdict(object):
175 class manifestdict(object):
176 def __init__(self, data=''):
176 def __init__(self, data=''):
177 if data.startswith('\0'):
177 if data.startswith('\0'):
178 #_lazymanifest can not parse v2
178 #_lazymanifest can not parse v2
179 self._lm = _lazymanifest('')
179 self._lm = _lazymanifest('')
180 for f, n, fl in _parsev2(data):
180 for f, n, fl in _parsev2(data):
181 self._lm[f] = n, fl
181 self._lm[f] = n, fl
182 else:
182 else:
183 self._lm = _lazymanifest(data)
183 self._lm = _lazymanifest(data)
184
184
185 def __getitem__(self, key):
185 def __getitem__(self, key):
186 return self._lm[key][0]
186 return self._lm[key][0]
187
187
188 def find(self, key):
188 def find(self, key):
189 return self._lm[key]
189 return self._lm[key]
190
190
191 def __len__(self):
191 def __len__(self):
192 return len(self._lm)
192 return len(self._lm)
193
193
194 def __setitem__(self, key, node):
194 def __setitem__(self, key, node):
195 self._lm[key] = node, self.flags(key, '')
195 self._lm[key] = node, self.flags(key, '')
196
196
197 def __contains__(self, key):
197 def __contains__(self, key):
198 return key in self._lm
198 return key in self._lm
199
199
200 def __delitem__(self, key):
200 def __delitem__(self, key):
201 del self._lm[key]
201 del self._lm[key]
202
202
203 def __iter__(self):
203 def __iter__(self):
204 return self._lm.__iter__()
204 return self._lm.__iter__()
205
205
206 def iterkeys(self):
206 def iterkeys(self):
207 return self._lm.iterkeys()
207 return self._lm.iterkeys()
208
208
209 def keys(self):
209 def keys(self):
210 return list(self.iterkeys())
210 return list(self.iterkeys())
211
211
212 def filesnotin(self, m2):
212 def filesnotin(self, m2):
213 '''Set of files in this manifest that are not in the other'''
213 '''Set of files in this manifest that are not in the other'''
214 diff = self.diff(m2)
214 diff = self.diff(m2)
215 files = set(filepath
215 files = set(filepath
216 for filepath, hashflags in diff.iteritems()
216 for filepath, hashflags in diff.iteritems()
217 if hashflags[1][0] is None)
217 if hashflags[1][0] is None)
218 return files
218 return files
219
219
220 @propertycache
220 @propertycache
221 def _dirs(self):
221 def _dirs(self):
222 return util.dirs(self)
222 return util.dirs(self)
223
223
224 def dirs(self):
224 def dirs(self):
225 return self._dirs
225 return self._dirs
226
226
227 def hasdir(self, dir):
227 def hasdir(self, dir):
228 return dir in self._dirs
228 return dir in self._dirs
229
229
230 def _filesfastpath(self, match):
230 def _filesfastpath(self, match):
231 '''Checks whether we can correctly and quickly iterate over matcher
231 '''Checks whether we can correctly and quickly iterate over matcher
232 files instead of over manifest files.'''
232 files instead of over manifest files.'''
233 files = match.files()
233 files = match.files()
234 return (len(files) < 100 and (match.isexact() or
234 return (len(files) < 100 and (match.isexact() or
235 (match.prefix() and all(fn in self for fn in files))))
235 (match.prefix() and all(fn in self for fn in files))))
236
236
237 def walk(self, match):
237 def walk(self, match):
238 '''Generates matching file names.
238 '''Generates matching file names.
239
239
240 Equivalent to manifest.matches(match).iterkeys(), but without creating
240 Equivalent to manifest.matches(match).iterkeys(), but without creating
241 an entirely new manifest.
241 an entirely new manifest.
242
242
243 It also reports nonexistent files by marking them bad with match.bad().
243 It also reports nonexistent files by marking them bad with match.bad().
244 '''
244 '''
245 if match.always():
245 if match.always():
246 for f in iter(self):
246 for f in iter(self):
247 yield f
247 yield f
248 return
248 return
249
249
250 fset = set(match.files())
250 fset = set(match.files())
251
251
252 # avoid the entire walk if we're only looking for specific files
252 # avoid the entire walk if we're only looking for specific files
253 if self._filesfastpath(match):
253 if self._filesfastpath(match):
254 for fn in sorted(fset):
254 for fn in sorted(fset):
255 yield fn
255 yield fn
256 return
256 return
257
257
258 for fn in self:
258 for fn in self:
259 if fn in fset:
259 if fn in fset:
260 # specified pattern is the exact name
260 # specified pattern is the exact name
261 fset.remove(fn)
261 fset.remove(fn)
262 if match(fn):
262 if match(fn):
263 yield fn
263 yield fn
264
264
265 # for dirstate.walk, files=['.'] means "walk the whole tree".
265 # for dirstate.walk, files=['.'] means "walk the whole tree".
266 # follow that here, too
266 # follow that here, too
267 fset.discard('.')
267 fset.discard('.')
268
268
269 for fn in sorted(fset):
269 for fn in sorted(fset):
270 if not self.hasdir(fn):
270 if not self.hasdir(fn):
271 match.bad(fn, None)
271 match.bad(fn, None)
272
272
273 def matches(self, match):
273 def matches(self, match):
274 '''generate a new manifest filtered by the match argument'''
274 '''generate a new manifest filtered by the match argument'''
275 if match.always():
275 if match.always():
276 return self.copy()
276 return self.copy()
277
277
278 if self._filesfastpath(match):
278 if self._filesfastpath(match):
279 m = manifestdict()
279 m = manifestdict()
280 lm = self._lm
280 lm = self._lm
281 for fn in match.files():
281 for fn in match.files():
282 if fn in lm:
282 if fn in lm:
283 m._lm[fn] = lm[fn]
283 m._lm[fn] = lm[fn]
284 return m
284 return m
285
285
286 m = manifestdict()
286 m = manifestdict()
287 m._lm = self._lm.filtercopy(match)
287 m._lm = self._lm.filtercopy(match)
288 return m
288 return m
289
289
290 def diff(self, m2, clean=False):
290 def diff(self, m2, clean=False):
291 '''Finds changes between the current manifest and m2.
291 '''Finds changes between the current manifest and m2.
292
292
293 Args:
293 Args:
294 m2: the manifest to which this manifest should be compared.
294 m2: the manifest to which this manifest should be compared.
295 clean: if true, include files unchanged between these manifests
295 clean: if true, include files unchanged between these manifests
296 with a None value in the returned dictionary.
296 with a None value in the returned dictionary.
297
297
298 The result is returned as a dict with filename as key and
298 The result is returned as a dict with filename as key and
299 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
299 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
300 nodeid in the current/other manifest and fl1/fl2 is the flag
300 nodeid in the current/other manifest and fl1/fl2 is the flag
301 in the current/other manifest. Where the file does not exist,
301 in the current/other manifest. Where the file does not exist,
302 the nodeid will be None and the flags will be the empty
302 the nodeid will be None and the flags will be the empty
303 string.
303 string.
304 '''
304 '''
305 return self._lm.diff(m2._lm, clean)
305 return self._lm.diff(m2._lm, clean)
306
306
307 def setflag(self, key, flag):
307 def setflag(self, key, flag):
308 self._lm[key] = self[key], flag
308 self._lm[key] = self[key], flag
309
309
310 def get(self, key, default=None):
310 def get(self, key, default=None):
311 try:
311 try:
312 return self._lm[key][0]
312 return self._lm[key][0]
313 except KeyError:
313 except KeyError:
314 return default
314 return default
315
315
316 def flags(self, key, default=''):
316 def flags(self, key, default=''):
317 try:
317 try:
318 return self._lm[key][1]
318 return self._lm[key][1]
319 except KeyError:
319 except KeyError:
320 return default
320 return default
321
321
322 def copy(self):
322 def copy(self):
323 c = manifestdict()
323 c = manifestdict()
324 c._lm = self._lm.copy()
324 c._lm = self._lm.copy()
325 return c
325 return c
326
326
327 def iteritems(self):
327 def iteritems(self):
328 return (x[:2] for x in self._lm.iterentries())
328 return (x[:2] for x in self._lm.iterentries())
329
329
330 def iterentries(self):
330 def iterentries(self):
331 return self._lm.iterentries()
331 return self._lm.iterentries()
332
332
333 def text(self, usemanifestv2=False):
333 def text(self, usemanifestv2=False):
334 if usemanifestv2:
334 if usemanifestv2:
335 return _textv2(self._lm.iterentries())
335 return _textv2(self._lm.iterentries())
336 else:
336 else:
337 # use (probably) native version for v1
337 # use (probably) native version for v1
338 return self._lm.text()
338 return self._lm.text()
339
339
340 def fastdelta(self, base, changes):
340 def fastdelta(self, base, changes):
341 """Given a base manifest text as an array.array and a list of changes
341 """Given a base manifest text as an array.array and a list of changes
342 relative to that text, compute a delta that can be used by revlog.
342 relative to that text, compute a delta that can be used by revlog.
343 """
343 """
344 delta = []
344 delta = []
345 dstart = None
345 dstart = None
346 dend = None
346 dend = None
347 dline = [""]
347 dline = [""]
348 start = 0
348 start = 0
349 # zero copy representation of base as a buffer
349 # zero copy representation of base as a buffer
350 addbuf = util.buffer(base)
350 addbuf = util.buffer(base)
351
351
352 changes = list(changes)
352 changes = list(changes)
353 if len(changes) < 1000:
353 if len(changes) < 1000:
354 # start with a readonly loop that finds the offset of
354 # start with a readonly loop that finds the offset of
355 # each line and creates the deltas
355 # each line and creates the deltas
356 for f, todelete in changes:
356 for f, todelete in changes:
357 # bs will either be the index of the item or the insert point
357 # bs will either be the index of the item or the insert point
358 start, end = _msearch(addbuf, f, start)
358 start, end = _msearch(addbuf, f, start)
359 if not todelete:
359 if not todelete:
360 h, fl = self._lm[f]
360 h, fl = self._lm[f]
361 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
361 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
362 else:
362 else:
363 if start == end:
363 if start == end:
364 # item we want to delete was not found, error out
364 # item we want to delete was not found, error out
365 raise AssertionError(
365 raise AssertionError(
366 _("failed to remove %s from manifest") % f)
366 _("failed to remove %s from manifest") % f)
367 l = ""
367 l = ""
368 if dstart is not None and dstart <= start and dend >= start:
368 if dstart is not None and dstart <= start and dend >= start:
369 if dend < end:
369 if dend < end:
370 dend = end
370 dend = end
371 if l:
371 if l:
372 dline.append(l)
372 dline.append(l)
373 else:
373 else:
374 if dstart is not None:
374 if dstart is not None:
375 delta.append([dstart, dend, "".join(dline)])
375 delta.append([dstart, dend, "".join(dline)])
376 dstart = start
376 dstart = start
377 dend = end
377 dend = end
378 dline = [l]
378 dline = [l]
379
379
380 if dstart is not None:
380 if dstart is not None:
381 delta.append([dstart, dend, "".join(dline)])
381 delta.append([dstart, dend, "".join(dline)])
382 # apply the delta to the base, and get a delta for addrevision
382 # apply the delta to the base, and get a delta for addrevision
383 deltatext, arraytext = _addlistdelta(base, delta)
383 deltatext, arraytext = _addlistdelta(base, delta)
384 else:
384 else:
385 # For large changes, it's much cheaper to just build the text and
385 # For large changes, it's much cheaper to just build the text and
386 # diff it.
386 # diff it.
387 arraytext = array.array('c', self.text())
387 arraytext = array.array('c', self.text())
388 deltatext = mdiff.textdiff(base, arraytext)
388 deltatext = mdiff.textdiff(base, arraytext)
389
389
390 return arraytext, deltatext
390 return arraytext, deltatext
391
391
392 def _msearch(m, s, lo=0, hi=None):
392 def _msearch(m, s, lo=0, hi=None):
393 '''return a tuple (start, end) that says where to find s within m.
393 '''return a tuple (start, end) that says where to find s within m.
394
394
395 If the string is found m[start:end] are the line containing
395 If the string is found m[start:end] are the line containing
396 that string. If start == end the string was not found and
396 that string. If start == end the string was not found and
397 they indicate the proper sorted insertion point.
397 they indicate the proper sorted insertion point.
398
398
399 m should be a buffer or a string
399 m should be a buffer or a string
400 s is a string'''
400 s is a string'''
401 def advance(i, c):
401 def advance(i, c):
402 while i < lenm and m[i] != c:
402 while i < lenm and m[i] != c:
403 i += 1
403 i += 1
404 return i
404 return i
405 if not s:
405 if not s:
406 return (lo, lo)
406 return (lo, lo)
407 lenm = len(m)
407 lenm = len(m)
408 if not hi:
408 if not hi:
409 hi = lenm
409 hi = lenm
410 while lo < hi:
410 while lo < hi:
411 mid = (lo + hi) // 2
411 mid = (lo + hi) // 2
412 start = mid
412 start = mid
413 while start > 0 and m[start - 1] != '\n':
413 while start > 0 and m[start - 1] != '\n':
414 start -= 1
414 start -= 1
415 end = advance(start, '\0')
415 end = advance(start, '\0')
416 if m[start:end] < s:
416 if m[start:end] < s:
417 # we know that after the null there are 40 bytes of sha1
417 # we know that after the null there are 40 bytes of sha1
418 # this translates to the bisect lo = mid + 1
418 # this translates to the bisect lo = mid + 1
419 lo = advance(end + 40, '\n') + 1
419 lo = advance(end + 40, '\n') + 1
420 else:
420 else:
421 # this translates to the bisect hi = mid
421 # this translates to the bisect hi = mid
422 hi = start
422 hi = start
423 end = advance(lo, '\0')
423 end = advance(lo, '\0')
424 found = m[lo:end]
424 found = m[lo:end]
425 if s == found:
425 if s == found:
426 # we know that after the null there are 40 bytes of sha1
426 # we know that after the null there are 40 bytes of sha1
427 end = advance(end + 40, '\n')
427 end = advance(end + 40, '\n')
428 return (lo, end + 1)
428 return (lo, end + 1)
429 else:
429 else:
430 return (lo, lo)
430 return (lo, lo)
431
431
432 def _checkforbidden(l):
432 def _checkforbidden(l):
433 """Check filenames for illegal characters."""
433 """Check filenames for illegal characters."""
434 for f in l:
434 for f in l:
435 if '\n' in f or '\r' in f:
435 if '\n' in f or '\r' in f:
436 raise error.RevlogError(
436 raise error.RevlogError(
437 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
437 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
438
438
439
439
440 # apply the changes collected during the bisect loop to our addlist
440 # apply the changes collected during the bisect loop to our addlist
441 # return a delta suitable for addrevision
441 # return a delta suitable for addrevision
442 def _addlistdelta(addlist, x):
442 def _addlistdelta(addlist, x):
443 # for large addlist arrays, building a new array is cheaper
443 # for large addlist arrays, building a new array is cheaper
444 # than repeatedly modifying the existing one
444 # than repeatedly modifying the existing one
445 currentposition = 0
445 currentposition = 0
446 newaddlist = array.array('c')
446 newaddlist = array.array('c')
447
447
448 for start, end, content in x:
448 for start, end, content in x:
449 newaddlist += addlist[currentposition:start]
449 newaddlist += addlist[currentposition:start]
450 if content:
450 if content:
451 newaddlist += array.array('c', content)
451 newaddlist += array.array('c', content)
452
452
453 currentposition = end
453 currentposition = end
454
454
455 newaddlist += addlist[currentposition:]
455 newaddlist += addlist[currentposition:]
456
456
457 deltatext = "".join(struct.pack(">lll", start, end, len(content))
457 deltatext = "".join(struct.pack(">lll", start, end, len(content))
458 + content for start, end, content in x)
458 + content for start, end, content in x)
459 return deltatext, newaddlist
459 return deltatext, newaddlist
460
460
461 def _splittopdir(f):
461 def _splittopdir(f):
462 if '/' in f:
462 if '/' in f:
463 dir, subpath = f.split('/', 1)
463 dir, subpath = f.split('/', 1)
464 return dir + '/', subpath
464 return dir + '/', subpath
465 else:
465 else:
466 return '', f
466 return '', f
467
467
468 _noop = lambda s: None
468 _noop = lambda s: None
469
469
470 class treemanifest(object):
470 class treemanifest(object):
471 def __init__(self, dir='', text=''):
471 def __init__(self, dir='', text=''):
472 self._dir = dir
472 self._dir = dir
473 self._node = revlog.nullid
473 self._node = revlog.nullid
474 self._loadfunc = _noop
474 self._loadfunc = _noop
475 self._copyfunc = _noop
475 self._copyfunc = _noop
476 self._dirty = False
476 self._dirty = False
477 self._dirs = {}
477 self._dirs = {}
478 # Using _lazymanifest here is a little slower than plain old dicts
478 # Using _lazymanifest here is a little slower than plain old dicts
479 self._files = {}
479 self._files = {}
480 self._flags = {}
480 self._flags = {}
481 if text:
481 if text:
482 def readsubtree(subdir, subm):
482 def readsubtree(subdir, subm):
483 raise AssertionError('treemanifest constructor only accepts '
483 raise AssertionError('treemanifest constructor only accepts '
484 'flat manifests')
484 'flat manifests')
485 self.parse(text, readsubtree)
485 self.parse(text, readsubtree)
486 self._dirty = True # Mark flat manifest dirty after parsing
486 self._dirty = True # Mark flat manifest dirty after parsing
487
487
488 def _subpath(self, path):
488 def _subpath(self, path):
489 return self._dir + path
489 return self._dir + path
490
490
491 def __len__(self):
491 def __len__(self):
492 self._load()
492 self._load()
493 size = len(self._files)
493 size = len(self._files)
494 for m in self._dirs.values():
494 for m in self._dirs.values():
495 size += m.__len__()
495 size += m.__len__()
496 return size
496 return size
497
497
498 def _isempty(self):
498 def _isempty(self):
499 self._load() # for consistency; already loaded by all callers
499 self._load() # for consistency; already loaded by all callers
500 return (not self._files and (not self._dirs or
500 return (not self._files and (not self._dirs or
501 all(m._isempty() for m in self._dirs.values())))
501 all(m._isempty() for m in self._dirs.values())))
502
502
503 def __repr__(self):
503 def __repr__(self):
504 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
504 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
505 (self._dir, revlog.hex(self._node),
505 (self._dir, revlog.hex(self._node),
506 bool(self._loadfunc is _noop),
506 bool(self._loadfunc is _noop),
507 self._dirty, id(self)))
507 self._dirty, id(self)))
508
508
509 def dir(self):
509 def dir(self):
510 '''The directory that this tree manifest represents, including a
510 '''The directory that this tree manifest represents, including a
511 trailing '/'. Empty string for the repo root directory.'''
511 trailing '/'. Empty string for the repo root directory.'''
512 return self._dir
512 return self._dir
513
513
514 def node(self):
514 def node(self):
515 '''This node of this instance. nullid for unsaved instances. Should
515 '''This node of this instance. nullid for unsaved instances. Should
516 be updated when the instance is read or written from a revlog.
516 be updated when the instance is read or written from a revlog.
517 '''
517 '''
518 assert not self._dirty
518 assert not self._dirty
519 return self._node
519 return self._node
520
520
521 def setnode(self, node):
521 def setnode(self, node):
522 self._node = node
522 self._node = node
523 self._dirty = False
523 self._dirty = False
524
524
525 def iterentries(self):
525 def iterentries(self):
526 self._load()
526 self._load()
527 for p, n in sorted(self._dirs.items() + self._files.items()):
527 for p, n in sorted(self._dirs.items() + self._files.items()):
528 if p in self._files:
528 if p in self._files:
529 yield self._subpath(p), n, self._flags.get(p, '')
529 yield self._subpath(p), n, self._flags.get(p, '')
530 else:
530 else:
531 for x in n.iterentries():
531 for x in n.iterentries():
532 yield x
532 yield x
533
533
534 def iteritems(self):
534 def iteritems(self):
535 self._load()
535 self._load()
536 for p, n in sorted(self._dirs.items() + self._files.items()):
536 for p, n in sorted(self._dirs.items() + self._files.items()):
537 if p in self._files:
537 if p in self._files:
538 yield self._subpath(p), n
538 yield self._subpath(p), n
539 else:
539 else:
540 for f, sn in n.iteritems():
540 for f, sn in n.iteritems():
541 yield f, sn
541 yield f, sn
542
542
543 def iterkeys(self):
543 def iterkeys(self):
544 self._load()
544 self._load()
545 for p in sorted(self._dirs.keys() + self._files.keys()):
545 for p in sorted(self._dirs.keys() + self._files.keys()):
546 if p in self._files:
546 if p in self._files:
547 yield self._subpath(p)
547 yield self._subpath(p)
548 else:
548 else:
549 for f in self._dirs[p].iterkeys():
549 for f in self._dirs[p].iterkeys():
550 yield f
550 yield f
551
551
552 def keys(self):
552 def keys(self):
553 return list(self.iterkeys())
553 return list(self.iterkeys())
554
554
555 def __iter__(self):
555 def __iter__(self):
556 return self.iterkeys()
556 return self.iterkeys()
557
557
558 def __contains__(self, f):
558 def __contains__(self, f):
559 if f is None:
559 if f is None:
560 return False
560 return False
561 self._load()
561 self._load()
562 dir, subpath = _splittopdir(f)
562 dir, subpath = _splittopdir(f)
563 if dir:
563 if dir:
564 if dir not in self._dirs:
564 if dir not in self._dirs:
565 return False
565 return False
566 return self._dirs[dir].__contains__(subpath)
566 return self._dirs[dir].__contains__(subpath)
567 else:
567 else:
568 return f in self._files
568 return f in self._files
569
569
570 def get(self, f, default=None):
570 def get(self, f, default=None):
571 self._load()
571 self._load()
572 dir, subpath = _splittopdir(f)
572 dir, subpath = _splittopdir(f)
573 if dir:
573 if dir:
574 if dir not in self._dirs:
574 if dir not in self._dirs:
575 return default
575 return default
576 return self._dirs[dir].get(subpath, default)
576 return self._dirs[dir].get(subpath, default)
577 else:
577 else:
578 return self._files.get(f, default)
578 return self._files.get(f, default)
579
579
580 def __getitem__(self, f):
580 def __getitem__(self, f):
581 self._load()
581 self._load()
582 dir, subpath = _splittopdir(f)
582 dir, subpath = _splittopdir(f)
583 if dir:
583 if dir:
584 return self._dirs[dir].__getitem__(subpath)
584 return self._dirs[dir].__getitem__(subpath)
585 else:
585 else:
586 return self._files[f]
586 return self._files[f]
587
587
588 def flags(self, f):
588 def flags(self, f):
589 self._load()
589 self._load()
590 dir, subpath = _splittopdir(f)
590 dir, subpath = _splittopdir(f)
591 if dir:
591 if dir:
592 if dir not in self._dirs:
592 if dir not in self._dirs:
593 return ''
593 return ''
594 return self._dirs[dir].flags(subpath)
594 return self._dirs[dir].flags(subpath)
595 else:
595 else:
596 if f in self._dirs:
596 if f in self._dirs:
597 return ''
597 return ''
598 return self._flags.get(f, '')
598 return self._flags.get(f, '')
599
599
600 def find(self, f):
600 def find(self, f):
601 self._load()
601 self._load()
602 dir, subpath = _splittopdir(f)
602 dir, subpath = _splittopdir(f)
603 if dir:
603 if dir:
604 return self._dirs[dir].find(subpath)
604 return self._dirs[dir].find(subpath)
605 else:
605 else:
606 return self._files[f], self._flags.get(f, '')
606 return self._files[f], self._flags.get(f, '')
607
607
608 def __delitem__(self, f):
608 def __delitem__(self, f):
609 self._load()
609 self._load()
610 dir, subpath = _splittopdir(f)
610 dir, subpath = _splittopdir(f)
611 if dir:
611 if dir:
612 self._dirs[dir].__delitem__(subpath)
612 self._dirs[dir].__delitem__(subpath)
613 # If the directory is now empty, remove it
613 # If the directory is now empty, remove it
614 if self._dirs[dir]._isempty():
614 if self._dirs[dir]._isempty():
615 del self._dirs[dir]
615 del self._dirs[dir]
616 else:
616 else:
617 del self._files[f]
617 del self._files[f]
618 if f in self._flags:
618 if f in self._flags:
619 del self._flags[f]
619 del self._flags[f]
620 self._dirty = True
620 self._dirty = True
621
621
622 def __setitem__(self, f, n):
622 def __setitem__(self, f, n):
623 assert n is not None
623 assert n is not None
624 self._load()
624 self._load()
625 dir, subpath = _splittopdir(f)
625 dir, subpath = _splittopdir(f)
626 if dir:
626 if dir:
627 if dir not in self._dirs:
627 if dir not in self._dirs:
628 self._dirs[dir] = treemanifest(self._subpath(dir))
628 self._dirs[dir] = treemanifest(self._subpath(dir))
629 self._dirs[dir].__setitem__(subpath, n)
629 self._dirs[dir].__setitem__(subpath, n)
630 else:
630 else:
631 self._files[f] = n[:21] # to match manifestdict's behavior
631 self._files[f] = n[:21] # to match manifestdict's behavior
632 self._dirty = True
632 self._dirty = True
633
633
634 def _load(self):
634 def _load(self):
635 if self._loadfunc is not _noop:
635 if self._loadfunc is not _noop:
636 lf, self._loadfunc = self._loadfunc, _noop
636 lf, self._loadfunc = self._loadfunc, _noop
637 lf(self)
637 lf(self)
638 elif self._copyfunc is not _noop:
638 elif self._copyfunc is not _noop:
639 cf, self._copyfunc = self._copyfunc, _noop
639 cf, self._copyfunc = self._copyfunc, _noop
640 cf(self)
640 cf(self)
641
641
642 def setflag(self, f, flags):
642 def setflag(self, f, flags):
643 """Set the flags (symlink, executable) for path f."""
643 """Set the flags (symlink, executable) for path f."""
644 self._load()
644 self._load()
645 dir, subpath = _splittopdir(f)
645 dir, subpath = _splittopdir(f)
646 if dir:
646 if dir:
647 if dir not in self._dirs:
647 if dir not in self._dirs:
648 self._dirs[dir] = treemanifest(self._subpath(dir))
648 self._dirs[dir] = treemanifest(self._subpath(dir))
649 self._dirs[dir].setflag(subpath, flags)
649 self._dirs[dir].setflag(subpath, flags)
650 else:
650 else:
651 self._flags[f] = flags
651 self._flags[f] = flags
652 self._dirty = True
652 self._dirty = True
653
653
654 def copy(self):
654 def copy(self):
655 copy = treemanifest(self._dir)
655 copy = treemanifest(self._dir)
656 copy._node = self._node
656 copy._node = self._node
657 copy._dirty = self._dirty
657 copy._dirty = self._dirty
658 if self._copyfunc is _noop:
658 if self._copyfunc is _noop:
659 def _copyfunc(s):
659 def _copyfunc(s):
660 self._load()
660 self._load()
661 for d in self._dirs:
661 for d in self._dirs:
662 s._dirs[d] = self._dirs[d].copy()
662 s._dirs[d] = self._dirs[d].copy()
663 s._files = dict.copy(self._files)
663 s._files = dict.copy(self._files)
664 s._flags = dict.copy(self._flags)
664 s._flags = dict.copy(self._flags)
665 if self._loadfunc is _noop:
665 if self._loadfunc is _noop:
666 _copyfunc(copy)
666 _copyfunc(copy)
667 else:
667 else:
668 copy._copyfunc = _copyfunc
668 copy._copyfunc = _copyfunc
669 else:
669 else:
670 copy._copyfunc = self._copyfunc
670 copy._copyfunc = self._copyfunc
671 return copy
671 return copy
672
672
673 def filesnotin(self, m2):
673 def filesnotin(self, m2):
674 '''Set of files in this manifest that are not in the other'''
674 '''Set of files in this manifest that are not in the other'''
675 files = set()
675 files = set()
676 def _filesnotin(t1, t2):
676 def _filesnotin(t1, t2):
677 if t1._node == t2._node and not t1._dirty and not t2._dirty:
677 if t1._node == t2._node and not t1._dirty and not t2._dirty:
678 return
678 return
679 t1._load()
679 t1._load()
680 t2._load()
680 t2._load()
681 for d, m1 in t1._dirs.iteritems():
681 for d, m1 in t1._dirs.iteritems():
682 if d in t2._dirs:
682 if d in t2._dirs:
683 m2 = t2._dirs[d]
683 m2 = t2._dirs[d]
684 _filesnotin(m1, m2)
684 _filesnotin(m1, m2)
685 else:
685 else:
686 files.update(m1.iterkeys())
686 files.update(m1.iterkeys())
687
687
688 for fn in t1._files.iterkeys():
688 for fn in t1._files.iterkeys():
689 if fn not in t2._files:
689 if fn not in t2._files:
690 files.add(t1._subpath(fn))
690 files.add(t1._subpath(fn))
691
691
692 _filesnotin(self, m2)
692 _filesnotin(self, m2)
693 return files
693 return files
694
694
695 @propertycache
695 @propertycache
696 def _alldirs(self):
696 def _alldirs(self):
697 return util.dirs(self)
697 return util.dirs(self)
698
698
699 def dirs(self):
699 def dirs(self):
700 return self._alldirs
700 return self._alldirs
701
701
702 def hasdir(self, dir):
702 def hasdir(self, dir):
703 self._load()
703 self._load()
704 topdir, subdir = _splittopdir(dir)
704 topdir, subdir = _splittopdir(dir)
705 if topdir:
705 if topdir:
706 if topdir in self._dirs:
706 if topdir in self._dirs:
707 return self._dirs[topdir].hasdir(subdir)
707 return self._dirs[topdir].hasdir(subdir)
708 return False
708 return False
709 return (dir + '/') in self._dirs
709 return (dir + '/') in self._dirs
710
710
711 def walk(self, match):
711 def walk(self, match):
712 '''Generates matching file names.
712 '''Generates matching file names.
713
713
714 Equivalent to manifest.matches(match).iterkeys(), but without creating
714 Equivalent to manifest.matches(match).iterkeys(), but without creating
715 an entirely new manifest.
715 an entirely new manifest.
716
716
717 It also reports nonexistent files by marking them bad with match.bad().
717 It also reports nonexistent files by marking them bad with match.bad().
718 '''
718 '''
719 if match.always():
719 if match.always():
720 for f in iter(self):
720 for f in iter(self):
721 yield f
721 yield f
722 return
722 return
723
723
724 fset = set(match.files())
724 fset = set(match.files())
725
725
726 for fn in self._walk(match):
726 for fn in self._walk(match):
727 if fn in fset:
727 if fn in fset:
728 # specified pattern is the exact name
728 # specified pattern is the exact name
729 fset.remove(fn)
729 fset.remove(fn)
730 yield fn
730 yield fn
731
731
732 # for dirstate.walk, files=['.'] means "walk the whole tree".
732 # for dirstate.walk, files=['.'] means "walk the whole tree".
733 # follow that here, too
733 # follow that here, too
734 fset.discard('.')
734 fset.discard('.')
735
735
736 for fn in sorted(fset):
736 for fn in sorted(fset):
737 if not self.hasdir(fn):
737 if not self.hasdir(fn):
738 match.bad(fn, None)
738 match.bad(fn, None)
739
739
740 def _walk(self, match):
740 def _walk(self, match):
741 '''Recursively generates matching file names for walk().'''
741 '''Recursively generates matching file names for walk().'''
742 if not match.visitdir(self._dir[:-1] or '.'):
742 if not match.visitdir(self._dir[:-1] or '.'):
743 return
743 return
744
744
745 # yield this dir's files and walk its submanifests
745 # yield this dir's files and walk its submanifests
746 self._load()
746 self._load()
747 for p in sorted(self._dirs.keys() + self._files.keys()):
747 for p in sorted(self._dirs.keys() + self._files.keys()):
748 if p in self._files:
748 if p in self._files:
749 fullp = self._subpath(p)
749 fullp = self._subpath(p)
750 if match(fullp):
750 if match(fullp):
751 yield fullp
751 yield fullp
752 else:
752 else:
753 for f in self._dirs[p]._walk(match):
753 for f in self._dirs[p]._walk(match):
754 yield f
754 yield f
755
755
756 def matches(self, match):
756 def matches(self, match):
757 '''generate a new manifest filtered by the match argument'''
757 '''generate a new manifest filtered by the match argument'''
758 if match.always():
758 if match.always():
759 return self.copy()
759 return self.copy()
760
760
761 return self._matches(match)
761 return self._matches(match)
762
762
763 def _matches(self, match):
763 def _matches(self, match):
764 '''recursively generate a new manifest filtered by the match argument.
764 '''recursively generate a new manifest filtered by the match argument.
765 '''
765 '''
766
766
767 visit = match.visitdir(self._dir[:-1] or '.')
767 visit = match.visitdir(self._dir[:-1] or '.')
768 if visit == 'all':
768 if visit == 'all':
769 return self.copy()
769 return self.copy()
770 ret = treemanifest(self._dir)
770 ret = treemanifest(self._dir)
771 if not visit:
771 if not visit:
772 return ret
772 return ret
773
773
774 self._load()
774 self._load()
775 for fn in self._files:
775 for fn in self._files:
776 fullp = self._subpath(fn)
776 fullp = self._subpath(fn)
777 if not match(fullp):
777 if not match(fullp):
778 continue
778 continue
779 ret._files[fn] = self._files[fn]
779 ret._files[fn] = self._files[fn]
780 if fn in self._flags:
780 if fn in self._flags:
781 ret._flags[fn] = self._flags[fn]
781 ret._flags[fn] = self._flags[fn]
782
782
783 for dir, subm in self._dirs.iteritems():
783 for dir, subm in self._dirs.iteritems():
784 m = subm._matches(match)
784 m = subm._matches(match)
785 if not m._isempty():
785 if not m._isempty():
786 ret._dirs[dir] = m
786 ret._dirs[dir] = m
787
787
788 if not ret._isempty():
788 if not ret._isempty():
789 ret._dirty = True
789 ret._dirty = True
790 return ret
790 return ret
791
791
792 def diff(self, m2, clean=False):
792 def diff(self, m2, clean=False):
793 '''Finds changes between the current manifest and m2.
793 '''Finds changes between the current manifest and m2.
794
794
795 Args:
795 Args:
796 m2: the manifest to which this manifest should be compared.
796 m2: the manifest to which this manifest should be compared.
797 clean: if true, include files unchanged between these manifests
797 clean: if true, include files unchanged between these manifests
798 with a None value in the returned dictionary.
798 with a None value in the returned dictionary.
799
799
800 The result is returned as a dict with filename as key and
800 The result is returned as a dict with filename as key and
801 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
801 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
802 nodeid in the current/other manifest and fl1/fl2 is the flag
802 nodeid in the current/other manifest and fl1/fl2 is the flag
803 in the current/other manifest. Where the file does not exist,
803 in the current/other manifest. Where the file does not exist,
804 the nodeid will be None and the flags will be the empty
804 the nodeid will be None and the flags will be the empty
805 string.
805 string.
806 '''
806 '''
807 result = {}
807 result = {}
808 emptytree = treemanifest()
808 emptytree = treemanifest()
809 def _diff(t1, t2):
809 def _diff(t1, t2):
810 if t1._node == t2._node and not t1._dirty and not t2._dirty:
810 if t1._node == t2._node and not t1._dirty and not t2._dirty:
811 return
811 return
812 t1._load()
812 t1._load()
813 t2._load()
813 t2._load()
814 for d, m1 in t1._dirs.iteritems():
814 for d, m1 in t1._dirs.iteritems():
815 m2 = t2._dirs.get(d, emptytree)
815 m2 = t2._dirs.get(d, emptytree)
816 _diff(m1, m2)
816 _diff(m1, m2)
817
817
818 for d, m2 in t2._dirs.iteritems():
818 for d, m2 in t2._dirs.iteritems():
819 if d not in t1._dirs:
819 if d not in t1._dirs:
820 _diff(emptytree, m2)
820 _diff(emptytree, m2)
821
821
822 for fn, n1 in t1._files.iteritems():
822 for fn, n1 in t1._files.iteritems():
823 fl1 = t1._flags.get(fn, '')
823 fl1 = t1._flags.get(fn, '')
824 n2 = t2._files.get(fn, None)
824 n2 = t2._files.get(fn, None)
825 fl2 = t2._flags.get(fn, '')
825 fl2 = t2._flags.get(fn, '')
826 if n1 != n2 or fl1 != fl2:
826 if n1 != n2 or fl1 != fl2:
827 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
827 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
828 elif clean:
828 elif clean:
829 result[t1._subpath(fn)] = None
829 result[t1._subpath(fn)] = None
830
830
831 for fn, n2 in t2._files.iteritems():
831 for fn, n2 in t2._files.iteritems():
832 if fn not in t1._files:
832 if fn not in t1._files:
833 fl2 = t2._flags.get(fn, '')
833 fl2 = t2._flags.get(fn, '')
834 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
834 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
835
835
836 _diff(self, m2)
836 _diff(self, m2)
837 return result
837 return result
838
838
839 def unmodifiedsince(self, m2):
839 def unmodifiedsince(self, m2):
840 return not self._dirty and not m2._dirty and self._node == m2._node
840 return not self._dirty and not m2._dirty and self._node == m2._node
841
841
842 def parse(self, text, readsubtree):
842 def parse(self, text, readsubtree):
843 for f, n, fl in _parse(text):
843 for f, n, fl in _parse(text):
844 if fl == 't':
844 if fl == 't':
845 f = f + '/'
845 f = f + '/'
846 self._dirs[f] = readsubtree(self._subpath(f), n)
846 self._dirs[f] = readsubtree(self._subpath(f), n)
847 elif '/' in f:
847 elif '/' in f:
848 # This is a flat manifest, so use __setitem__ and setflag rather
848 # This is a flat manifest, so use __setitem__ and setflag rather
849 # than assigning directly to _files and _flags, so we can
849 # than assigning directly to _files and _flags, so we can
850 # assign a path in a subdirectory, and to mark dirty (compared
850 # assign a path in a subdirectory, and to mark dirty (compared
851 # to nullid).
851 # to nullid).
852 self[f] = n
852 self[f] = n
853 if fl:
853 if fl:
854 self.setflag(f, fl)
854 self.setflag(f, fl)
855 else:
855 else:
856 # Assigning to _files and _flags avoids marking as dirty,
856 # Assigning to _files and _flags avoids marking as dirty,
857 # and should be a little faster.
857 # and should be a little faster.
858 self._files[f] = n
858 self._files[f] = n
859 if fl:
859 if fl:
860 self._flags[f] = fl
860 self._flags[f] = fl
861
861
862 def text(self, usemanifestv2=False):
862 def text(self, usemanifestv2=False):
863 """Get the full data of this manifest as a bytestring."""
863 """Get the full data of this manifest as a bytestring."""
864 self._load()
864 self._load()
865 return _text(self.iterentries(), usemanifestv2)
865 return _text(self.iterentries(), usemanifestv2)
866
866
867 def dirtext(self, usemanifestv2=False):
867 def dirtext(self, usemanifestv2=False):
868 """Get the full data of this directory as a bytestring. Make sure that
868 """Get the full data of this directory as a bytestring. Make sure that
869 any submanifests have been written first, so their nodeids are correct.
869 any submanifests have been written first, so their nodeids are correct.
870 """
870 """
871 self._load()
871 self._load()
872 flags = self.flags
872 flags = self.flags
873 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
873 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
874 files = [(f, self._files[f], flags(f)) for f in self._files]
874 files = [(f, self._files[f], flags(f)) for f in self._files]
875 return _text(sorted(dirs + files), usemanifestv2)
875 return _text(sorted(dirs + files), usemanifestv2)
876
876
877 def read(self, gettext, readsubtree):
877 def read(self, gettext, readsubtree):
878 def _load_for_read(s):
878 def _load_for_read(s):
879 s.parse(gettext(), readsubtree)
879 s.parse(gettext(), readsubtree)
880 s._dirty = False
880 s._dirty = False
881 self._loadfunc = _load_for_read
881 self._loadfunc = _load_for_read
882
882
883 def writesubtrees(self, m1, m2, writesubtree):
883 def writesubtrees(self, m1, m2, writesubtree):
884 self._load() # for consistency; should never have any effect here
884 self._load() # for consistency; should never have any effect here
885 m1._load()
885 m1._load()
886 m2._load()
886 m2._load()
887 emptytree = treemanifest()
887 emptytree = treemanifest()
888 for d, subm in self._dirs.iteritems():
888 for d, subm in self._dirs.iteritems():
889 subp1 = m1._dirs.get(d, emptytree)._node
889 subp1 = m1._dirs.get(d, emptytree)._node
890 subp2 = m2._dirs.get(d, emptytree)._node
890 subp2 = m2._dirs.get(d, emptytree)._node
891 if subp1 == revlog.nullid:
891 if subp1 == revlog.nullid:
892 subp1, subp2 = subp2, subp1
892 subp1, subp2 = subp2, subp1
893 writesubtree(subm, subp1, subp2)
893 writesubtree(subm, subp1, subp2)
894
894
895 class manifestrevlog(revlog.revlog):
895 class manifestrevlog(revlog.revlog):
896 '''A revlog that stores manifest texts. This is responsible for caching the
896 '''A revlog that stores manifest texts. This is responsible for caching the
897 full-text manifest contents.
897 full-text manifest contents.
898 '''
898 '''
899 def __init__(self, opener, indexfile):
899 def __init__(self, opener, indexfile):
900 super(manifestrevlog, self).__init__(opener, indexfile)
900 super(manifestrevlog, self).__init__(opener, indexfile)
901
901
902 # During normal operations, we expect to deal with not more than four
902 # During normal operations, we expect to deal with not more than four
903 # revs at a time (such as during commit --amend). When rebasing large
903 # revs at a time (such as during commit --amend). When rebasing large
904 # stacks of commits, the number can go up, hence the config knob below.
904 # stacks of commits, the number can go up, hence the config knob below.
905 cachesize = 4
905 cachesize = 4
906 opts = getattr(opener, 'options', None)
906 opts = getattr(opener, 'options', None)
907 if opts is not None:
907 if opts is not None:
908 cachesize = opts.get('manifestcachesize', cachesize)
908 cachesize = opts.get('manifestcachesize', cachesize)
909 self._fulltextcache = util.lrucachedict(cachesize)
909 self._fulltextcache = util.lrucachedict(cachesize)
910
910
911 @property
911 @property
912 def fulltextcache(self):
912 def fulltextcache(self):
913 return self._fulltextcache
913 return self._fulltextcache
914
914
915 def clearcaches(self):
915 def clearcaches(self):
916 super(manifestrevlog, self).clearcaches()
916 super(manifestrevlog, self).clearcaches()
917 self._fulltextcache.clear()
917 self._fulltextcache.clear()
918
918
919 class manifestlog(object):
919 class manifestlog(object):
920 """A collection class representing the collection of manifest snapshots
920 """A collection class representing the collection of manifest snapshots
921 referenced by commits in the repository.
921 referenced by commits in the repository.
922
922
923 In this situation, 'manifest' refers to the abstract concept of a snapshot
923 In this situation, 'manifest' refers to the abstract concept of a snapshot
924 of the list of files in the given commit. Consumers of the output of this
924 of the list of files in the given commit. Consumers of the output of this
925 class do not care about the implementation details of the actual manifests
925 class do not care about the implementation details of the actual manifests
926 they receive (i.e. tree or flat or lazily loaded, etc)."""
926 they receive (i.e. tree or flat or lazily loaded, etc)."""
927 def __init__(self, opener, repo):
927 def __init__(self, opener, repo):
928 self._repo = repo
928 self._repo = repo
929
929
930 # We'll separate this into it's own cache once oldmanifest is no longer
930 # We'll separate this into it's own cache once oldmanifest is no longer
931 # used
931 # used
932 self._mancache = repo.manifest._mancache
932 self._mancache = repo.manifest._mancache
933
933
934 @property
934 @property
935 def _revlog(self):
935 def _revlog(self):
936 return self._repo.manifest
936 return self._repo.manifest
937
937
938 @property
938 @property
939 def _oldmanifest(self):
939 def _oldmanifest(self):
940 # _revlog is the same as _oldmanifest right now, but we eventually want
940 # _revlog is the same as _oldmanifest right now, but we eventually want
941 # to delete _oldmanifest while still allowing manifestlog to access the
941 # to delete _oldmanifest while still allowing manifestlog to access the
942 # revlog specific apis.
942 # revlog specific apis.
943 return self._repo.manifest
943 return self._repo.manifest
944
944
945 def __getitem__(self, node):
945 def __getitem__(self, node):
946 """Retrieves the manifest instance for the given node. Throws a KeyError
946 """Retrieves the manifest instance for the given node. Throws a KeyError
947 if not found.
947 if not found.
948 """
948 """
949 if node in self._mancache:
949 if node in self._mancache:
950 cachemf = self._mancache[node]
950 cachemf = self._mancache[node]
951 # The old manifest may put non-ctx manifests in the cache, so skip
951 # The old manifest may put non-ctx manifests in the cache, so skip
952 # those since they don't implement the full api.
952 # those since they don't implement the full api.
953 if (isinstance(cachemf, manifestctx) or
953 if (isinstance(cachemf, manifestctx) or
954 isinstance(cachemf, treemanifestctx)):
954 isinstance(cachemf, treemanifestctx)):
955 return cachemf
955 return cachemf
956
956
957 if self._oldmanifest._treeinmem:
957 if self._oldmanifest._treeinmem:
958 m = treemanifestctx(self._revlog, '', node)
958 m = treemanifestctx(self._revlog, '', node)
959 else:
959 else:
960 m = manifestctx(self._revlog, node)
960 m = manifestctx(self._revlog, node)
961 if node != revlog.nullid:
961 if node != revlog.nullid:
962 self._mancache[node] = m
962 self._mancache[node] = m
963 return m
963 return m
964
964
965 class manifestctx(object):
965 class manifestctx(object):
966 """A class representing a single revision of a manifest, including its
966 """A class representing a single revision of a manifest, including its
967 contents, its parent revs, and its linkrev.
967 contents, its parent revs, and its linkrev.
968 """
968 """
969 def __init__(self, revlog, node):
969 def __init__(self, revlog, node):
970 self._revlog = revlog
970 self._revlog = revlog
971 self._data = None
971 self._data = None
972
972
973 self._node = node
973 self._node = node
974
974
975 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
975 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
976 # but let's add it later when something needs it and we can load it
976 # but let's add it later when something needs it and we can load it
977 # lazily.
977 # lazily.
978 #self.p1, self.p2 = revlog.parents(node)
978 #self.p1, self.p2 = revlog.parents(node)
979 #rev = revlog.rev(node)
979 #rev = revlog.rev(node)
980 #self.linkrev = revlog.linkrev(rev)
980 #self.linkrev = revlog.linkrev(rev)
981
981
982 def node(self):
982 def node(self):
983 return self._node
983 return self._node
984
984
985 def read(self):
985 def read(self):
986 if not self._data:
986 if not self._data:
987 if self._node == revlog.nullid:
987 if self._node == revlog.nullid:
988 self._data = manifestdict()
988 self._data = manifestdict()
989 else:
989 else:
990 text = self._revlog.revision(self._node)
990 text = self._revlog.revision(self._node)
991 arraytext = array.array('c', text)
991 arraytext = array.array('c', text)
992 self._revlog._fulltextcache[self._node] = arraytext
992 self._revlog._fulltextcache[self._node] = arraytext
993 self._data = manifestdict(text)
993 self._data = manifestdict(text)
994 return self._data
994 return self._data
995
995
996 def readdelta(self):
997 revlog = self._revlog
998 if revlog._usemanifestv2:
999 # Need to perform a slow delta
1000 r0 = revlog.deltaparent(revlog.rev(self._node))
1001 m0 = manifestctx(revlog, revlog.node(r0)).read()
1002 m1 = self.read()
1003 md = manifestdict()
1004 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1005 if n1:
1006 md[f] = n1
1007 if fl1:
1008 md.setflag(f, fl1)
1009 return md
1010
1011 r = revlog.rev(self._node)
1012 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1013 return manifestdict(d)
1014
996 class treemanifestctx(object):
1015 class treemanifestctx(object):
997 def __init__(self, revlog, dir, node):
1016 def __init__(self, revlog, dir, node):
998 revlog = revlog.dirlog(dir)
1017 revlog = revlog.dirlog(dir)
999 self._revlog = revlog
1018 self._revlog = revlog
1000 self._dir = dir
1019 self._dir = dir
1001 self._data = None
1020 self._data = None
1002
1021
1003 self._node = node
1022 self._node = node
1004
1023
1005 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1024 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1006 # we can instantiate treemanifestctx objects for directories we don't
1025 # we can instantiate treemanifestctx objects for directories we don't
1007 # have on disk.
1026 # have on disk.
1008 #self.p1, self.p2 = revlog.parents(node)
1027 #self.p1, self.p2 = revlog.parents(node)
1009 #rev = revlog.rev(node)
1028 #rev = revlog.rev(node)
1010 #self.linkrev = revlog.linkrev(rev)
1029 #self.linkrev = revlog.linkrev(rev)
1011
1030
1012 def read(self):
1031 def read(self):
1013 if not self._data:
1032 if not self._data:
1014 if self._node == revlog.nullid:
1033 if self._node == revlog.nullid:
1015 self._data = treemanifest()
1034 self._data = treemanifest()
1016 elif self._revlog._treeondisk:
1035 elif self._revlog._treeondisk:
1017 m = treemanifest(dir=self._dir)
1036 m = treemanifest(dir=self._dir)
1018 def gettext():
1037 def gettext():
1019 return self._revlog.revision(self._node)
1038 return self._revlog.revision(self._node)
1020 def readsubtree(dir, subm):
1039 def readsubtree(dir, subm):
1021 return treemanifestctx(self._revlog, dir, subm).read()
1040 return treemanifestctx(self._revlog, dir, subm).read()
1022 m.read(gettext, readsubtree)
1041 m.read(gettext, readsubtree)
1023 m.setnode(self._node)
1042 m.setnode(self._node)
1024 self._data = m
1043 self._data = m
1025 else:
1044 else:
1026 text = self._revlog.revision(self._node)
1045 text = self._revlog.revision(self._node)
1027 arraytext = array.array('c', text)
1046 arraytext = array.array('c', text)
1028 self._revlog.fulltextcache[self._node] = arraytext
1047 self._revlog.fulltextcache[self._node] = arraytext
1029 self._data = treemanifest(dir=self._dir, text=text)
1048 self._data = treemanifest(dir=self._dir, text=text)
1030
1049
1031 return self._data
1050 return self._data
1032
1051
1033 def node(self):
1052 def node(self):
1034 return self._node
1053 return self._node
1035
1054
1055 def readdelta(self):
1056 # Need to perform a slow delta
1057 revlog = self._revlog
1058 r0 = revlog.deltaparent(revlog.rev(self._node))
1059 m0 = treemanifestctx(revlog, revlog.node(r0), dir=self._dir).read()
1060 m1 = self.read()
1061 md = treemanifest(dir=self._dir)
1062 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1063 if n1:
1064 md[f] = n1
1065 if fl1:
1066 md.setflag(f, fl1)
1067 return md
1068
1036 class manifest(manifestrevlog):
1069 class manifest(manifestrevlog):
1037 def __init__(self, opener, dir='', dirlogcache=None):
1070 def __init__(self, opener, dir='', dirlogcache=None):
1038 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1071 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1039 manifest.manifest only. External users should create a root manifest
1072 manifest.manifest only. External users should create a root manifest
1040 log with manifest.manifest(opener) and call dirlog() on it.
1073 log with manifest.manifest(opener) and call dirlog() on it.
1041 '''
1074 '''
1042 # During normal operations, we expect to deal with not more than four
1075 # During normal operations, we expect to deal with not more than four
1043 # revs at a time (such as during commit --amend). When rebasing large
1076 # revs at a time (such as during commit --amend). When rebasing large
1044 # stacks of commits, the number can go up, hence the config knob below.
1077 # stacks of commits, the number can go up, hence the config knob below.
1045 cachesize = 4
1078 cachesize = 4
1046 usetreemanifest = False
1079 usetreemanifest = False
1047 usemanifestv2 = False
1080 usemanifestv2 = False
1048 opts = getattr(opener, 'options', None)
1081 opts = getattr(opener, 'options', None)
1049 if opts is not None:
1082 if opts is not None:
1050 cachesize = opts.get('manifestcachesize', cachesize)
1083 cachesize = opts.get('manifestcachesize', cachesize)
1051 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1084 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1052 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1085 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1053 self._mancache = util.lrucachedict(cachesize)
1086 self._mancache = util.lrucachedict(cachesize)
1054 self._treeinmem = usetreemanifest
1087 self._treeinmem = usetreemanifest
1055 self._treeondisk = usetreemanifest
1088 self._treeondisk = usetreemanifest
1056 self._usemanifestv2 = usemanifestv2
1089 self._usemanifestv2 = usemanifestv2
1057 indexfile = "00manifest.i"
1090 indexfile = "00manifest.i"
1058 if dir:
1091 if dir:
1059 assert self._treeondisk, 'opts is %r' % opts
1092 assert self._treeondisk, 'opts is %r' % opts
1060 if not dir.endswith('/'):
1093 if not dir.endswith('/'):
1061 dir = dir + '/'
1094 dir = dir + '/'
1062 indexfile = "meta/" + dir + "00manifest.i"
1095 indexfile = "meta/" + dir + "00manifest.i"
1063 super(manifest, self).__init__(opener, indexfile)
1096 super(manifest, self).__init__(opener, indexfile)
1064 self._dir = dir
1097 self._dir = dir
1065 # The dirlogcache is kept on the root manifest log
1098 # The dirlogcache is kept on the root manifest log
1066 if dir:
1099 if dir:
1067 self._dirlogcache = dirlogcache
1100 self._dirlogcache = dirlogcache
1068 else:
1101 else:
1069 self._dirlogcache = {'': self}
1102 self._dirlogcache = {'': self}
1070
1103
1071 def _newmanifest(self, data=''):
1104 def _newmanifest(self, data=''):
1072 if self._treeinmem:
1105 if self._treeinmem:
1073 return treemanifest(self._dir, data)
1106 return treemanifest(self._dir, data)
1074 return manifestdict(data)
1107 return manifestdict(data)
1075
1108
1076 def dirlog(self, dir):
1109 def dirlog(self, dir):
1077 if dir:
1110 if dir:
1078 assert self._treeondisk
1111 assert self._treeondisk
1079 if dir not in self._dirlogcache:
1112 if dir not in self._dirlogcache:
1080 self._dirlogcache[dir] = manifest(self.opener, dir,
1113 self._dirlogcache[dir] = manifest(self.opener, dir,
1081 self._dirlogcache)
1114 self._dirlogcache)
1082 return self._dirlogcache[dir]
1115 return self._dirlogcache[dir]
1083
1116
1084 def _slowreaddelta(self, node):
1117 def _slowreaddelta(self, node):
1085 r0 = self.deltaparent(self.rev(node))
1118 r0 = self.deltaparent(self.rev(node))
1086 m0 = self.read(self.node(r0))
1119 m0 = self.read(self.node(r0))
1087 m1 = self.read(node)
1120 m1 = self.read(node)
1088 md = self._newmanifest()
1121 md = self._newmanifest()
1089 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1122 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1090 if n1:
1123 if n1:
1091 md[f] = n1
1124 md[f] = n1
1092 if fl1:
1125 if fl1:
1093 md.setflag(f, fl1)
1126 md.setflag(f, fl1)
1094 return md
1127 return md
1095
1128
1096 def readdelta(self, node):
1129 def readdelta(self, node):
1097 if self._usemanifestv2 or self._treeondisk:
1130 if self._usemanifestv2 or self._treeondisk:
1098 return self._slowreaddelta(node)
1131 return self._slowreaddelta(node)
1099 r = self.rev(node)
1132 r = self.rev(node)
1100 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1133 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1101 return self._newmanifest(d)
1134 return self._newmanifest(d)
1102
1135
1103 def readshallowdelta(self, node):
1136 def readshallowdelta(self, node):
1104 '''For flat manifests, this is the same as readdelta(). For
1137 '''For flat manifests, this is the same as readdelta(). For
1105 treemanifests, this will read the delta for this revlog's directory,
1138 treemanifests, this will read the delta for this revlog's directory,
1106 without recursively reading subdirectory manifests. Instead, any
1139 without recursively reading subdirectory manifests. Instead, any
1107 subdirectory entry will be reported as it appears in the manifests, i.e.
1140 subdirectory entry will be reported as it appears in the manifests, i.e.
1108 the subdirectory will be reported among files and distinguished only by
1141 the subdirectory will be reported among files and distinguished only by
1109 its 't' flag.'''
1142 its 't' flag.'''
1110 if not self._treeondisk:
1143 if not self._treeondisk:
1111 return self.readdelta(node)
1144 return self.readdelta(node)
1112 if self._usemanifestv2:
1145 if self._usemanifestv2:
1113 raise error.Abort(
1146 raise error.Abort(
1114 _("readshallowdelta() not implemented for manifestv2"))
1147 _("readshallowdelta() not implemented for manifestv2"))
1115 r = self.rev(node)
1148 r = self.rev(node)
1116 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1149 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1117 return manifestdict(d)
1150 return manifestdict(d)
1118
1151
1119 def readfast(self, node):
1152 def readfast(self, node):
1120 '''use the faster of readdelta or read
1153 '''use the faster of readdelta or read
1121
1154
1122 This will return a manifest which is either only the files
1155 This will return a manifest which is either only the files
1123 added/modified relative to p1, or all files in the
1156 added/modified relative to p1, or all files in the
1124 manifest. Which one is returned depends on the codepath used
1157 manifest. Which one is returned depends on the codepath used
1125 to retrieve the data.
1158 to retrieve the data.
1126 '''
1159 '''
1127 r = self.rev(node)
1160 r = self.rev(node)
1128 deltaparent = self.deltaparent(r)
1161 deltaparent = self.deltaparent(r)
1129 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1162 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1130 return self.readdelta(node)
1163 return self.readdelta(node)
1131 return self.read(node)
1164 return self.read(node)
1132
1165
1133 def readshallowfast(self, node):
1166 def readshallowfast(self, node):
1134 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1167 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1135 '''
1168 '''
1136 r = self.rev(node)
1169 r = self.rev(node)
1137 deltaparent = self.deltaparent(r)
1170 deltaparent = self.deltaparent(r)
1138 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1171 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1139 return self.readshallowdelta(node)
1172 return self.readshallowdelta(node)
1140 return self.readshallow(node)
1173 return self.readshallow(node)
1141
1174
1142 def read(self, node):
1175 def read(self, node):
1143 if node == revlog.nullid:
1176 if node == revlog.nullid:
1144 return self._newmanifest() # don't upset local cache
1177 return self._newmanifest() # don't upset local cache
1145 if node in self._mancache:
1178 if node in self._mancache:
1146 cached = self._mancache[node]
1179 cached = self._mancache[node]
1147 if (isinstance(cached, manifestctx) or
1180 if (isinstance(cached, manifestctx) or
1148 isinstance(cached, treemanifestctx)):
1181 isinstance(cached, treemanifestctx)):
1149 cached = cached.read()
1182 cached = cached.read()
1150 return cached
1183 return cached
1151 if self._treeondisk:
1184 if self._treeondisk:
1152 def gettext():
1185 def gettext():
1153 return self.revision(node)
1186 return self.revision(node)
1154 def readsubtree(dir, subm):
1187 def readsubtree(dir, subm):
1155 return self.dirlog(dir).read(subm)
1188 return self.dirlog(dir).read(subm)
1156 m = self._newmanifest()
1189 m = self._newmanifest()
1157 m.read(gettext, readsubtree)
1190 m.read(gettext, readsubtree)
1158 m.setnode(node)
1191 m.setnode(node)
1159 arraytext = None
1192 arraytext = None
1160 else:
1193 else:
1161 text = self.revision(node)
1194 text = self.revision(node)
1162 m = self._newmanifest(text)
1195 m = self._newmanifest(text)
1163 arraytext = array.array('c', text)
1196 arraytext = array.array('c', text)
1164 self._mancache[node] = m
1197 self._mancache[node] = m
1165 self.fulltextcache[node] = arraytext
1198 self.fulltextcache[node] = arraytext
1166 return m
1199 return m
1167
1200
1168 def readshallow(self, node):
1201 def readshallow(self, node):
1169 '''Reads the manifest in this directory. When using flat manifests,
1202 '''Reads the manifest in this directory. When using flat manifests,
1170 this manifest will generally have files in subdirectories in it. Does
1203 this manifest will generally have files in subdirectories in it. Does
1171 not cache the manifest as the callers generally do not read the same
1204 not cache the manifest as the callers generally do not read the same
1172 version twice.'''
1205 version twice.'''
1173 return manifestdict(self.revision(node))
1206 return manifestdict(self.revision(node))
1174
1207
1175 def find(self, node, f):
1208 def find(self, node, f):
1176 '''look up entry for a single file efficiently.
1209 '''look up entry for a single file efficiently.
1177 return (node, flags) pair if found, (None, None) if not.'''
1210 return (node, flags) pair if found, (None, None) if not.'''
1178 m = self.read(node)
1211 m = self.read(node)
1179 try:
1212 try:
1180 return m.find(f)
1213 return m.find(f)
1181 except KeyError:
1214 except KeyError:
1182 return None, None
1215 return None, None
1183
1216
1184 def add(self, m, transaction, link, p1, p2, added, removed):
1217 def add(self, m, transaction, link, p1, p2, added, removed):
1185 if (p1 in self.fulltextcache and not self._treeinmem
1218 if (p1 in self.fulltextcache and not self._treeinmem
1186 and not self._usemanifestv2):
1219 and not self._usemanifestv2):
1187 # If our first parent is in the manifest cache, we can
1220 # If our first parent is in the manifest cache, we can
1188 # compute a delta here using properties we know about the
1221 # compute a delta here using properties we know about the
1189 # manifest up-front, which may save time later for the
1222 # manifest up-front, which may save time later for the
1190 # revlog layer.
1223 # revlog layer.
1191
1224
1192 _checkforbidden(added)
1225 _checkforbidden(added)
1193 # combine the changed lists into one sorted iterator
1226 # combine the changed lists into one sorted iterator
1194 work = heapq.merge([(x, False) for x in added],
1227 work = heapq.merge([(x, False) for x in added],
1195 [(x, True) for x in removed])
1228 [(x, True) for x in removed])
1196
1229
1197 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1230 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1198 cachedelta = self.rev(p1), deltatext
1231 cachedelta = self.rev(p1), deltatext
1199 text = util.buffer(arraytext)
1232 text = util.buffer(arraytext)
1200 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1233 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1201 else:
1234 else:
1202 # The first parent manifest isn't already loaded, so we'll
1235 # The first parent manifest isn't already loaded, so we'll
1203 # just encode a fulltext of the manifest and pass that
1236 # just encode a fulltext of the manifest and pass that
1204 # through to the revlog layer, and let it handle the delta
1237 # through to the revlog layer, and let it handle the delta
1205 # process.
1238 # process.
1206 if self._treeondisk:
1239 if self._treeondisk:
1207 m1 = self.read(p1)
1240 m1 = self.read(p1)
1208 m2 = self.read(p2)
1241 m2 = self.read(p2)
1209 n = self._addtree(m, transaction, link, m1, m2)
1242 n = self._addtree(m, transaction, link, m1, m2)
1210 arraytext = None
1243 arraytext = None
1211 else:
1244 else:
1212 text = m.text(self._usemanifestv2)
1245 text = m.text(self._usemanifestv2)
1213 n = self.addrevision(text, transaction, link, p1, p2)
1246 n = self.addrevision(text, transaction, link, p1, p2)
1214 arraytext = array.array('c', text)
1247 arraytext = array.array('c', text)
1215
1248
1216 self._mancache[n] = m
1249 self._mancache[n] = m
1217 self.fulltextcache[n] = arraytext
1250 self.fulltextcache[n] = arraytext
1218
1251
1219 return n
1252 return n
1220
1253
1221 def _addtree(self, m, transaction, link, m1, m2):
1254 def _addtree(self, m, transaction, link, m1, m2):
1222 # If the manifest is unchanged compared to one parent,
1255 # If the manifest is unchanged compared to one parent,
1223 # don't write a new revision
1256 # don't write a new revision
1224 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1257 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1225 return m.node()
1258 return m.node()
1226 def writesubtree(subm, subp1, subp2):
1259 def writesubtree(subm, subp1, subp2):
1227 sublog = self.dirlog(subm.dir())
1260 sublog = self.dirlog(subm.dir())
1228 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1261 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1229 m.writesubtrees(m1, m2, writesubtree)
1262 m.writesubtrees(m1, m2, writesubtree)
1230 text = m.dirtext(self._usemanifestv2)
1263 text = m.dirtext(self._usemanifestv2)
1231 # Double-check whether contents are unchanged to one parent
1264 # Double-check whether contents are unchanged to one parent
1232 if text == m1.dirtext(self._usemanifestv2):
1265 if text == m1.dirtext(self._usemanifestv2):
1233 n = m1.node()
1266 n = m1.node()
1234 elif text == m2.dirtext(self._usemanifestv2):
1267 elif text == m2.dirtext(self._usemanifestv2):
1235 n = m2.node()
1268 n = m2.node()
1236 else:
1269 else:
1237 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1270 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1238 # Save nodeid so parent manifest can calculate its nodeid
1271 # Save nodeid so parent manifest can calculate its nodeid
1239 m.setnode(n)
1272 m.setnode(n)
1240 return n
1273 return n
1241
1274
1242 def clearcaches(self):
1275 def clearcaches(self):
1243 super(manifest, self).clearcaches()
1276 super(manifest, self).clearcaches()
1244 self._mancache.clear()
1277 self._mancache.clear()
1245 self._dirlogcache = {'': self}
1278 self._dirlogcache = {'': self}
General Comments 0
You need to be logged in to leave comments. Login now