##// END OF EJS Templates
cleanup of revlog.group when repository is local...
Benoit Boissinot -
r1677:11d12bd6 default
parent child Browse files
Show More
@@ -1,57 +1,58 b''
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from revlog import *
9 9 from i18n import gettext as _
10 10 from demandload import demandload
11 11 demandload(globals(), "os time util")
12 12
13 13 class changelog(revlog):
14 def __init__(self, opener):
15 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
14 def __init__(self, opener, local=True):
15 revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
16 local=local)
16 17
17 18 def extract(self, text):
18 19 if not text:
19 20 return (nullid, "", (0, 0), [], "")
20 21 last = text.index("\n\n")
21 22 desc = text[last + 2:]
22 23 l = text[:last].splitlines()
23 24 manifest = bin(l[0])
24 25 user = l[1]
25 26 date = l[2].split(' ')
26 27 time = float(date.pop(0))
27 28 try:
28 29 # various tools did silly things with the time zone field.
29 30 timezone = int(date[0])
30 31 except:
31 32 timezone = 0
32 33 files = l[3:]
33 34 return (manifest, user, (time, timezone), files, desc)
34 35
35 36 def read(self, node):
36 37 return self.extract(self.revision(node))
37 38
38 39 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
39 40 user=None, date=None):
40 41 if date:
41 42 # validate explicit (probably user-specified) date and
42 43 # time zone offset. values must fit in signed 32 bits for
43 44 # current 32-bit linux runtimes.
44 45 try:
45 46 when, offset = map(int, date.split(' '))
46 47 except ValueError:
47 48 raise ValueError(_('invalid date: %r') % date)
48 49 if abs(when) > 0x7fffffff:
49 50 raise ValueError(_('date exceeds 32 bits: %d') % when)
50 51 if abs(offset) >= 43200:
51 52 raise ValueError(_('impossible time zone offset: %d') % offset)
52 53 else:
53 54 date = "%d %d" % util.makedate()
54 55 list.sort()
55 56 l = [hex(manifest), user, date] + list + ["", desc]
56 57 text = "\n".join(l)
57 58 return self.addrevision(text, transaction, self.count(), p1, p2)
@@ -1,107 +1,108 b''
1 1 # filelog.py - file history class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os
9 9 from revlog import *
10 10 from demandload import *
11 11 demandload(globals(), "bdiff")
12 12
13 13 class filelog(revlog):
14 def __init__(self, opener, path):
14 def __init__(self, opener, path, local=True):
15 15 revlog.__init__(self, opener,
16 16 os.path.join("data", self.encodedir(path + ".i")),
17 os.path.join("data", self.encodedir(path + ".d")))
17 os.path.join("data", self.encodedir(path + ".d")),
18 local=local)
18 19
19 20 # This avoids a collision between a file named foo and a dir named
20 21 # foo.i or foo.d
21 22 def encodedir(self, path):
22 23 return (path
23 24 .replace(".hg/", ".hg.hg/")
24 25 .replace(".i/", ".i.hg/")
25 26 .replace(".d/", ".d.hg/"))
26 27
27 28 def decodedir(self, path):
28 29 return (path
29 30 .replace(".d.hg/", ".d/")
30 31 .replace(".i.hg/", ".i/")
31 32 .replace(".hg.hg/", ".hg/"))
32 33
33 34 def read(self, node):
34 35 t = self.revision(node)
35 36 if not t.startswith('\1\n'):
36 37 return t
37 38 s = t.find('\1\n', 2)
38 39 return t[s+2:]
39 40
40 41 def readmeta(self, node):
41 42 t = self.revision(node)
42 43 if not t.startswith('\1\n'):
43 44 return {}
44 45 s = t.find('\1\n', 2)
45 46 mt = t[2:s]
46 47 m = {}
47 48 for l in mt.splitlines():
48 49 k, v = l.split(": ", 1)
49 50 m[k] = v
50 51 return m
51 52
52 53 def add(self, text, meta, transaction, link, p1=None, p2=None):
53 54 if meta or text.startswith('\1\n'):
54 55 mt = ""
55 56 if meta:
56 57 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
57 58 text = "\1\n%s\1\n%s" % ("".join(mt), text)
58 59 return self.addrevision(text, transaction, link, p1, p2)
59 60
60 61 def renamed(self, node):
61 62 if 0 and self.parents(node)[0] != nullid: # XXX
62 63 return False
63 64 m = self.readmeta(node)
64 65 if m and m.has_key("copy"):
65 66 return (m["copy"], bin(m["copyrev"]))
66 67 return False
67 68
68 69 def annotate(self, node):
69 70
70 71 def decorate(text, rev):
71 72 return ([rev] * len(text.splitlines()), text)
72 73
73 74 def pair(parent, child):
74 75 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
75 76 child[0][b1:b2] = parent[0][a1:a2]
76 77 return child
77 78
78 79 # find all ancestors
79 80 needed = {node:1}
80 81 visit = [node]
81 82 while visit:
82 83 n = visit.pop(0)
83 84 for p in self.parents(n):
84 85 if p not in needed:
85 86 needed[p] = 1
86 87 visit.append(p)
87 88 else:
88 89 # count how many times we'll use this
89 90 needed[p] += 1
90 91
91 92 # sort by revision which is a topological order
92 93 visit = [ (self.rev(n), n) for n in needed.keys() ]
93 94 visit.sort()
94 95 hist = {}
95 96
96 97 for r,n in visit:
97 98 curr = decorate(self.read(n), self.linkrev(n))
98 99 for p in self.parents(n):
99 100 if p != nullid:
100 101 curr = pair(hist[p], curr)
101 102 # trim the history of unneeded revs
102 103 needed[p] -= 1
103 104 if not needed[p]:
104 105 del hist[p]
105 106 hist[n] = curr
106 107
107 108 return zip(hist[n][0], hist[n][1].splitlines(1))
@@ -1,1786 +1,1786 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import struct, os, util
9 9 import filelog, manifest, changelog, dirstate, repo
10 10 from node import *
11 11 from i18n import gettext as _
12 12 from demandload import *
13 13 demandload(globals(), "re lock transaction tempfile stat mdiff errno")
14 14
15 15 class localrepository(object):
16 16 def __init__(self, ui, path=None, create=0):
17 17 if not path:
18 18 p = os.getcwd()
19 19 while not os.path.isdir(os.path.join(p, ".hg")):
20 20 oldp = p
21 21 p = os.path.dirname(p)
22 22 if p == oldp: raise repo.RepoError(_("no repo found"))
23 23 path = p
24 24 self.path = os.path.join(path, ".hg")
25 25
26 26 if not create and not os.path.isdir(self.path):
27 27 raise repo.RepoError(_("repository %s not found") % self.path)
28 28
29 29 self.root = os.path.abspath(path)
30 30 self.ui = ui
31 31 self.opener = util.opener(self.path)
32 32 self.wopener = util.opener(self.root)
33 self.manifest = manifest.manifest(self.opener)
34 self.changelog = changelog.changelog(self.opener)
33 self.manifest = manifest.manifest(self.opener, local=self.local())
34 self.changelog = changelog.changelog(self.opener, local=self.local())
35 35 self.tagscache = None
36 36 self.nodetagscache = None
37 37 self.encodepats = None
38 38 self.decodepats = None
39 39
40 40 if create:
41 41 os.mkdir(self.path)
42 42 os.mkdir(self.join("data"))
43 43
44 44 self.dirstate = dirstate.dirstate(self.opener, ui, self.root)
45 45 try:
46 46 self.ui.readconfig(self.join("hgrc"))
47 47 except IOError: pass
48 48
49 49 def hook(self, name, **args):
50 50 def runhook(name, cmd):
51 51 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
52 52 old = {}
53 53 for k, v in args.items():
54 54 k = k.upper()
55 55 old[k] = os.environ.get(k, None)
56 56 os.environ[k] = v
57 57
58 58 # Hooks run in the repository root
59 59 olddir = os.getcwd()
60 60 os.chdir(self.root)
61 61 r = os.system(cmd)
62 62 os.chdir(olddir)
63 63
64 64 for k, v in old.items():
65 65 if v != None:
66 66 os.environ[k] = v
67 67 else:
68 68 del os.environ[k]
69 69
70 70 if r:
71 71 self.ui.warn(_("abort: %s hook failed with status %d!\n") %
72 72 (name, r))
73 73 return False
74 74 return True
75 75
76 76 r = True
77 77 for hname, cmd in self.ui.configitems("hooks"):
78 78 s = hname.split(".")
79 79 if s[0] == name and cmd:
80 80 r = runhook(hname, cmd) and r
81 81 return r
82 82
83 83 def tags(self):
84 84 '''return a mapping of tag to node'''
85 85 if not self.tagscache:
86 86 self.tagscache = {}
87 87 def addtag(self, k, n):
88 88 try:
89 89 bin_n = bin(n)
90 90 except TypeError:
91 91 bin_n = ''
92 92 self.tagscache[k.strip()] = bin_n
93 93
94 94 try:
95 95 # read each head of the tags file, ending with the tip
96 96 # and add each tag found to the map, with "newer" ones
97 97 # taking precedence
98 98 fl = self.file(".hgtags")
99 99 h = fl.heads()
100 100 h.reverse()
101 101 for r in h:
102 102 for l in fl.read(r).splitlines():
103 103 if l:
104 104 n, k = l.split(" ", 1)
105 105 addtag(self, k, n)
106 106 except KeyError:
107 107 pass
108 108
109 109 try:
110 110 f = self.opener("localtags")
111 111 for l in f:
112 112 n, k = l.split(" ", 1)
113 113 addtag(self, k, n)
114 114 except IOError:
115 115 pass
116 116
117 117 self.tagscache['tip'] = self.changelog.tip()
118 118
119 119 return self.tagscache
120 120
121 121 def tagslist(self):
122 122 '''return a list of tags ordered by revision'''
123 123 l = []
124 124 for t, n in self.tags().items():
125 125 try:
126 126 r = self.changelog.rev(n)
127 127 except:
128 128 r = -2 # sort to the beginning of the list if unknown
129 129 l.append((r,t,n))
130 130 l.sort()
131 131 return [(t,n) for r,t,n in l]
132 132
133 133 def nodetags(self, node):
134 134 '''return the tags associated with a node'''
135 135 if not self.nodetagscache:
136 136 self.nodetagscache = {}
137 137 for t,n in self.tags().items():
138 138 self.nodetagscache.setdefault(n,[]).append(t)
139 139 return self.nodetagscache.get(node, [])
140 140
141 141 def lookup(self, key):
142 142 try:
143 143 return self.tags()[key]
144 144 except KeyError:
145 145 try:
146 146 return self.changelog.lookup(key)
147 147 except:
148 148 raise repo.RepoError(_("unknown revision '%s'") % key)
149 149
150 150 def dev(self):
151 151 return os.stat(self.path).st_dev
152 152
153 153 def local(self):
154 154 return True
155 155
156 156 def join(self, f):
157 157 return os.path.join(self.path, f)
158 158
159 159 def wjoin(self, f):
160 160 return os.path.join(self.root, f)
161 161
162 162 def file(self, f):
163 163 if f[0] == '/': f = f[1:]
164 return filelog.filelog(self.opener, f)
164 return filelog.filelog(self.opener, f, local=self.local())
165 165
166 166 def getcwd(self):
167 167 return self.dirstate.getcwd()
168 168
169 169 def wfile(self, f, mode='r'):
170 170 return self.wopener(f, mode)
171 171
172 172 def wread(self, filename):
173 173 if self.encodepats == None:
174 174 l = []
175 175 for pat, cmd in self.ui.configitems("encode"):
176 176 mf = util.matcher("", "/", [pat], [], [])[1]
177 177 l.append((mf, cmd))
178 178 self.encodepats = l
179 179
180 180 data = self.wopener(filename, 'r').read()
181 181
182 182 for mf, cmd in self.encodepats:
183 183 if mf(filename):
184 184 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
185 185 data = util.filter(data, cmd)
186 186 break
187 187
188 188 return data
189 189
190 190 def wwrite(self, filename, data, fd=None):
191 191 if self.decodepats == None:
192 192 l = []
193 193 for pat, cmd in self.ui.configitems("decode"):
194 194 mf = util.matcher("", "/", [pat], [], [])[1]
195 195 l.append((mf, cmd))
196 196 self.decodepats = l
197 197
198 198 for mf, cmd in self.decodepats:
199 199 if mf(filename):
200 200 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
201 201 data = util.filter(data, cmd)
202 202 break
203 203
204 204 if fd:
205 205 return fd.write(data)
206 206 return self.wopener(filename, 'w').write(data)
207 207
208 208 def transaction(self):
209 209 # save dirstate for undo
210 210 try:
211 211 ds = self.opener("dirstate").read()
212 212 except IOError:
213 213 ds = ""
214 214 self.opener("journal.dirstate", "w").write(ds)
215 215
216 216 def after():
217 217 util.rename(self.join("journal"), self.join("undo"))
218 218 util.rename(self.join("journal.dirstate"),
219 219 self.join("undo.dirstate"))
220 220
221 221 return transaction.transaction(self.ui.warn, self.opener,
222 222 self.join("journal"), after)
223 223
224 224 def recover(self):
225 225 lock = self.lock()
226 226 if os.path.exists(self.join("journal")):
227 227 self.ui.status(_("rolling back interrupted transaction\n"))
228 228 transaction.rollback(self.opener, self.join("journal"))
229 229 return True
230 230 else:
231 231 self.ui.warn(_("no interrupted transaction available\n"))
232 232 return False
233 233
234 234 def undo(self):
235 235 wlock = self.wlock()
236 236 lock = self.lock()
237 237 if os.path.exists(self.join("undo")):
238 238 self.ui.status(_("rolling back last transaction\n"))
239 239 transaction.rollback(self.opener, self.join("undo"))
240 240 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
241 241 self.dirstate.read()
242 242 else:
243 243 self.ui.warn(_("no undo information available\n"))
244 244
245 245 def lock(self, wait=1):
246 246 try:
247 247 return lock.lock(self.join("lock"), 0)
248 248 except lock.LockHeld, inst:
249 249 if wait:
250 250 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
251 251 return lock.lock(self.join("lock"), wait)
252 252 raise inst
253 253
254 254 def wlock(self, wait=1):
255 255 try:
256 256 wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
257 257 except lock.LockHeld, inst:
258 258 if not wait:
259 259 raise inst
260 260 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
261 261 wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
262 262 self.dirstate.read()
263 263 return wlock
264 264
265 265 def rawcommit(self, files, text, user, date, p1=None, p2=None):
266 266 orig_parent = self.dirstate.parents()[0] or nullid
267 267 p1 = p1 or self.dirstate.parents()[0] or nullid
268 268 p2 = p2 or self.dirstate.parents()[1] or nullid
269 269 c1 = self.changelog.read(p1)
270 270 c2 = self.changelog.read(p2)
271 271 m1 = self.manifest.read(c1[0])
272 272 mf1 = self.manifest.readflags(c1[0])
273 273 m2 = self.manifest.read(c2[0])
274 274 changed = []
275 275
276 276 if orig_parent == p1:
277 277 update_dirstate = 1
278 278 else:
279 279 update_dirstate = 0
280 280
281 281 wlock = self.wlock()
282 282 lock = self.lock()
283 283 tr = self.transaction()
284 284 mm = m1.copy()
285 285 mfm = mf1.copy()
286 286 linkrev = self.changelog.count()
287 287 for f in files:
288 288 try:
289 289 t = self.wread(f)
290 290 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
291 291 r = self.file(f)
292 292 mfm[f] = tm
293 293
294 294 fp1 = m1.get(f, nullid)
295 295 fp2 = m2.get(f, nullid)
296 296
297 297 # is the same revision on two branches of a merge?
298 298 if fp2 == fp1:
299 299 fp2 = nullid
300 300
301 301 if fp2 != nullid:
302 302 # is one parent an ancestor of the other?
303 303 fpa = r.ancestor(fp1, fp2)
304 304 if fpa == fp1:
305 305 fp1, fp2 = fp2, nullid
306 306 elif fpa == fp2:
307 307 fp2 = nullid
308 308
309 309 # is the file unmodified from the parent?
310 310 if t == r.read(fp1):
311 311 # record the proper existing parent in manifest
312 312 # no need to add a revision
313 313 mm[f] = fp1
314 314 continue
315 315
316 316 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
317 317 changed.append(f)
318 318 if update_dirstate:
319 319 self.dirstate.update([f], "n")
320 320 except IOError:
321 321 try:
322 322 del mm[f]
323 323 del mfm[f]
324 324 if update_dirstate:
325 325 self.dirstate.forget([f])
326 326 except:
327 327 # deleted from p2?
328 328 pass
329 329
330 330 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
331 331 user = user or self.ui.username()
332 332 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
333 333 tr.close()
334 334 if update_dirstate:
335 335 self.dirstate.setparents(n, nullid)
336 336
337 337 def commit(self, files = None, text = "", user = None, date = None,
338 338 match = util.always, force=False):
339 339 commit = []
340 340 remove = []
341 341 changed = []
342 342
343 343 if files:
344 344 for f in files:
345 345 s = self.dirstate.state(f)
346 346 if s in 'nmai':
347 347 commit.append(f)
348 348 elif s == 'r':
349 349 remove.append(f)
350 350 else:
351 351 self.ui.warn(_("%s not tracked!\n") % f)
352 352 else:
353 353 (c, a, d, u) = self.changes(match=match)
354 354 commit = c + a
355 355 remove = d
356 356
357 357 p1, p2 = self.dirstate.parents()
358 358 c1 = self.changelog.read(p1)
359 359 c2 = self.changelog.read(p2)
360 360 m1 = self.manifest.read(c1[0])
361 361 mf1 = self.manifest.readflags(c1[0])
362 362 m2 = self.manifest.read(c2[0])
363 363
364 364 if not commit and not remove and not force and p2 == nullid:
365 365 self.ui.status(_("nothing changed\n"))
366 366 return None
367 367
368 368 if not self.hook("precommit"):
369 369 return None
370 370
371 371 wlock = self.wlock()
372 372 lock = self.lock()
373 373 tr = self.transaction()
374 374
375 375 # check in files
376 376 new = {}
377 377 linkrev = self.changelog.count()
378 378 commit.sort()
379 379 for f in commit:
380 380 self.ui.note(f + "\n")
381 381 try:
382 382 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
383 383 t = self.wread(f)
384 384 except IOError:
385 385 self.ui.warn(_("trouble committing %s!\n") % f)
386 386 raise
387 387
388 388 r = self.file(f)
389 389
390 390 meta = {}
391 391 cp = self.dirstate.copied(f)
392 392 if cp:
393 393 meta["copy"] = cp
394 394 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
395 395 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
396 396 fp1, fp2 = nullid, nullid
397 397 else:
398 398 fp1 = m1.get(f, nullid)
399 399 fp2 = m2.get(f, nullid)
400 400
401 401 # is the same revision on two branches of a merge?
402 402 if fp2 == fp1:
403 403 fp2 = nullid
404 404
405 405 if fp2 != nullid:
406 406 # is one parent an ancestor of the other?
407 407 fpa = r.ancestor(fp1, fp2)
408 408 if fpa == fp1:
409 409 fp1, fp2 = fp2, nullid
410 410 elif fpa == fp2:
411 411 fp2 = nullid
412 412
413 413 # is the file unmodified from the parent?
414 414 if not meta and t == r.read(fp1):
415 415 # record the proper existing parent in manifest
416 416 # no need to add a revision
417 417 new[f] = fp1
418 418 continue
419 419
420 420 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
421 421 # remember what we've added so that we can later calculate
422 422 # the files to pull from a set of changesets
423 423 changed.append(f)
424 424
425 425 # update manifest
426 426 m1.update(new)
427 427 for f in remove:
428 428 if f in m1:
429 429 del m1[f]
430 430 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
431 431 (new, remove))
432 432
433 433 # add changeset
434 434 new = new.keys()
435 435 new.sort()
436 436
437 437 if not text:
438 438 edittext = ""
439 439 if p2 != nullid:
440 440 edittext += "HG: branch merge\n"
441 441 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
442 442 edittext += "".join(["HG: changed %s\n" % f for f in changed])
443 443 edittext += "".join(["HG: removed %s\n" % f for f in remove])
444 444 if not changed and not remove:
445 445 edittext += "HG: no files changed\n"
446 446 edittext = self.ui.edit(edittext)
447 447 if not edittext.rstrip():
448 448 return None
449 449 text = edittext
450 450
451 451 user = user or self.ui.username()
452 452 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
453 453 tr.close()
454 454
455 455 self.dirstate.setparents(n)
456 456 self.dirstate.update(new, "n")
457 457 self.dirstate.forget(remove)
458 458
459 459 if not self.hook("commit", node=hex(n)):
460 460 return None
461 461 return n
462 462
463 463 def walk(self, node=None, files=[], match=util.always):
464 464 if node:
465 465 fdict = dict.fromkeys(files)
466 466 for fn in self.manifest.read(self.changelog.read(node)[0]):
467 467 fdict.pop(fn, None)
468 468 if match(fn):
469 469 yield 'm', fn
470 470 for fn in fdict:
471 471 self.ui.warn(_('%s: No such file in rev %s\n') % (
472 472 util.pathto(self.getcwd(), fn), short(node)))
473 473 else:
474 474 for src, fn in self.dirstate.walk(files, match):
475 475 yield src, fn
476 476
477 477 def changes(self, node1 = None, node2 = None, files = [],
478 478 match = util.always):
479 479 mf2, u = None, []
480 480
481 481 def fcmp(fn, mf):
482 482 t1 = self.wread(fn)
483 483 t2 = self.file(fn).read(mf.get(fn, nullid))
484 484 return cmp(t1, t2)
485 485
486 486 def mfmatches(node):
487 487 mf = dict(self.manifest.read(node))
488 488 for fn in mf.keys():
489 489 if not match(fn):
490 490 del mf[fn]
491 491 return mf
492 492
493 493 # are we comparing the working directory?
494 494 if not node2:
495 495 try:
496 496 wlock = self.wlock(wait=0)
497 497 except lock.LockHeld:
498 498 wlock = None
499 499 l, c, a, d, u = self.dirstate.changes(files, match)
500 500
501 501 # are we comparing working dir against its parent?
502 502 if not node1:
503 503 if l:
504 504 # do a full compare of any files that might have changed
505 505 change = self.changelog.read(self.dirstate.parents()[0])
506 506 mf2 = mfmatches(change[0])
507 507 for f in l:
508 508 if fcmp(f, mf2):
509 509 c.append(f)
510 510 elif wlock is not None:
511 511 self.dirstate.update([f], "n")
512 512
513 513 for l in c, a, d, u:
514 514 l.sort()
515 515
516 516 return (c, a, d, u)
517 517
518 518 # are we comparing working dir against non-tip?
519 519 # generate a pseudo-manifest for the working dir
520 520 if not node2:
521 521 if not mf2:
522 522 change = self.changelog.read(self.dirstate.parents()[0])
523 523 mf2 = mfmatches(change[0])
524 524 for f in a + c + l:
525 525 mf2[f] = ""
526 526 for f in d:
527 527 if f in mf2: del mf2[f]
528 528 else:
529 529 change = self.changelog.read(node2)
530 530 mf2 = mfmatches(change[0])
531 531
532 532 # flush lists from dirstate before comparing manifests
533 533 c, a = [], []
534 534
535 535 change = self.changelog.read(node1)
536 536 mf1 = mfmatches(change[0])
537 537
538 538 for fn in mf2:
539 539 if mf1.has_key(fn):
540 540 if mf1[fn] != mf2[fn]:
541 541 if mf2[fn] != "" or fcmp(fn, mf1):
542 542 c.append(fn)
543 543 del mf1[fn]
544 544 else:
545 545 a.append(fn)
546 546
547 547 d = mf1.keys()
548 548
549 549 for l in c, a, d, u:
550 550 l.sort()
551 551
552 552 return (c, a, d, u)
553 553
554 554 def add(self, list):
555 555 wlock = self.wlock()
556 556 for f in list:
557 557 p = self.wjoin(f)
558 558 if not os.path.exists(p):
559 559 self.ui.warn(_("%s does not exist!\n") % f)
560 560 elif not os.path.isfile(p):
561 561 self.ui.warn(_("%s not added: only files supported currently\n") % f)
562 562 elif self.dirstate.state(f) in 'an':
563 563 self.ui.warn(_("%s already tracked!\n") % f)
564 564 else:
565 565 self.dirstate.update([f], "a")
566 566
567 567 def forget(self, list):
568 568 wlock = self.wlock()
569 569 for f in list:
570 570 if self.dirstate.state(f) not in 'ai':
571 571 self.ui.warn(_("%s not added!\n") % f)
572 572 else:
573 573 self.dirstate.forget([f])
574 574
575 575 def remove(self, list, unlink=False):
576 576 if unlink:
577 577 for f in list:
578 578 try:
579 579 util.unlink(self.wjoin(f))
580 580 except OSError, inst:
581 581 if inst.errno != errno.ENOENT: raise
582 582 wlock = self.wlock()
583 583 for f in list:
584 584 p = self.wjoin(f)
585 585 if os.path.exists(p):
586 586 self.ui.warn(_("%s still exists!\n") % f)
587 587 elif self.dirstate.state(f) == 'a':
588 588 self.ui.warn(_("%s never committed!\n") % f)
589 589 self.dirstate.forget([f])
590 590 elif f not in self.dirstate:
591 591 self.ui.warn(_("%s not tracked!\n") % f)
592 592 else:
593 593 self.dirstate.update([f], "r")
594 594
595 595 def undelete(self, list):
596 596 p = self.dirstate.parents()[0]
597 597 mn = self.changelog.read(p)[0]
598 598 mf = self.manifest.readflags(mn)
599 599 m = self.manifest.read(mn)
600 600 wlock = self.wlock()
601 601 for f in list:
602 602 if self.dirstate.state(f) not in "r":
603 603 self.ui.warn("%s not removed!\n" % f)
604 604 else:
605 605 t = self.file(f).read(m[f])
606 606 self.wwrite(f, t)
607 607 util.set_exec(self.wjoin(f), mf[f])
608 608 self.dirstate.update([f], "n")
609 609
610 610 def copy(self, source, dest):
611 611 p = self.wjoin(dest)
612 612 if not os.path.exists(p):
613 613 self.ui.warn(_("%s does not exist!\n") % dest)
614 614 elif not os.path.isfile(p):
615 615 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
616 616 else:
617 617 wlock = self.wlock()
618 618 if self.dirstate.state(dest) == '?':
619 619 self.dirstate.update([dest], "a")
620 620 self.dirstate.copy(source, dest)
621 621
622 622 def heads(self, start=None):
623 623 heads = self.changelog.heads(start)
624 624 # sort the output in rev descending order
625 625 heads = [(-self.changelog.rev(h), h) for h in heads]
626 626 heads.sort()
627 627 return [n for (r, n) in heads]
628 628
629 629 # branchlookup returns a dict giving a list of branches for
630 630 # each head. A branch is defined as the tag of a node or
631 631 # the branch of the node's parents. If a node has multiple
632 632 # branch tags, tags are eliminated if they are visible from other
633 633 # branch tags.
634 634 #
635 635 # So, for this graph: a->b->c->d->e
636 636 # \ /
637 637 # aa -----/
638 638 # a has tag 2.6.12
639 639 # d has tag 2.6.13
640 640 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
641 641 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
642 642 # from the list.
643 643 #
644 644 # It is possible that more than one head will have the same branch tag.
645 645 # callers need to check the result for multiple heads under the same
646 646 # branch tag if that is a problem for them (ie checkout of a specific
647 647 # branch).
648 648 #
649 649 # passing in a specific branch will limit the depth of the search
650 650 # through the parents. It won't limit the branches returned in the
651 651 # result though.
652 652 def branchlookup(self, heads=None, branch=None):
653 653 if not heads:
654 654 heads = self.heads()
655 655 headt = [ h for h in heads ]
656 656 chlog = self.changelog
657 657 branches = {}
658 658 merges = []
659 659 seenmerge = {}
660 660
661 661 # traverse the tree once for each head, recording in the branches
662 662 # dict which tags are visible from this head. The branches
663 663 # dict also records which tags are visible from each tag
664 664 # while we traverse.
665 665 while headt or merges:
666 666 if merges:
667 667 n, found = merges.pop()
668 668 visit = [n]
669 669 else:
670 670 h = headt.pop()
671 671 visit = [h]
672 672 found = [h]
673 673 seen = {}
674 674 while visit:
675 675 n = visit.pop()
676 676 if n in seen:
677 677 continue
678 678 pp = chlog.parents(n)
679 679 tags = self.nodetags(n)
680 680 if tags:
681 681 for x in tags:
682 682 if x == 'tip':
683 683 continue
684 684 for f in found:
685 685 branches.setdefault(f, {})[n] = 1
686 686 branches.setdefault(n, {})[n] = 1
687 687 break
688 688 if n not in found:
689 689 found.append(n)
690 690 if branch in tags:
691 691 continue
692 692 seen[n] = 1
693 693 if pp[1] != nullid and n not in seenmerge:
694 694 merges.append((pp[1], [x for x in found]))
695 695 seenmerge[n] = 1
696 696 if pp[0] != nullid:
697 697 visit.append(pp[0])
698 698 # traverse the branches dict, eliminating branch tags from each
699 699 # head that are visible from another branch tag for that head.
700 700 out = {}
701 701 viscache = {}
702 702 for h in heads:
703 703 def visible(node):
704 704 if node in viscache:
705 705 return viscache[node]
706 706 ret = {}
707 707 visit = [node]
708 708 while visit:
709 709 x = visit.pop()
710 710 if x in viscache:
711 711 ret.update(viscache[x])
712 712 elif x not in ret:
713 713 ret[x] = 1
714 714 if x in branches:
715 715 visit[len(visit):] = branches[x].keys()
716 716 viscache[node] = ret
717 717 return ret
718 718 if h not in branches:
719 719 continue
720 720 # O(n^2), but somewhat limited. This only searches the
721 721 # tags visible from a specific head, not all the tags in the
722 722 # whole repo.
723 723 for b in branches[h]:
724 724 vis = False
725 725 for bb in branches[h].keys():
726 726 if b != bb:
727 727 if b in visible(bb):
728 728 vis = True
729 729 break
730 730 if not vis:
731 731 l = out.setdefault(h, [])
732 732 l[len(l):] = self.nodetags(b)
733 733 return out
734 734
735 735 def branches(self, nodes):
736 736 if not nodes: nodes = [self.changelog.tip()]
737 737 b = []
738 738 for n in nodes:
739 739 t = n
740 740 while n:
741 741 p = self.changelog.parents(n)
742 742 if p[1] != nullid or p[0] == nullid:
743 743 b.append((t, n, p[0], p[1]))
744 744 break
745 745 n = p[0]
746 746 return b
747 747
748 748 def between(self, pairs):
749 749 r = []
750 750
751 751 for top, bottom in pairs:
752 752 n, l, i = top, [], 0
753 753 f = 1
754 754
755 755 while n != bottom:
756 756 p = self.changelog.parents(n)[0]
757 757 if i == f:
758 758 l.append(n)
759 759 f = f * 2
760 760 n = p
761 761 i += 1
762 762
763 763 r.append(l)
764 764
765 765 return r
766 766
767 767 def findincoming(self, remote, base=None, heads=None):
768 768 m = self.changelog.nodemap
769 769 search = []
770 770 fetch = {}
771 771 seen = {}
772 772 seenbranch = {}
773 773 if base == None:
774 774 base = {}
775 775
776 776 # assume we're closer to the tip than the root
777 777 # and start by examining the heads
778 778 self.ui.status(_("searching for changes\n"))
779 779
780 780 if not heads:
781 781 heads = remote.heads()
782 782
783 783 unknown = []
784 784 for h in heads:
785 785 if h not in m:
786 786 unknown.append(h)
787 787 else:
788 788 base[h] = 1
789 789
790 790 if not unknown:
791 791 return None
792 792
793 793 rep = {}
794 794 reqcnt = 0
795 795
796 796 # search through remote branches
797 797 # a 'branch' here is a linear segment of history, with four parts:
798 798 # head, root, first parent, second parent
799 799 # (a branch always has two parents (or none) by definition)
800 800 unknown = remote.branches(unknown)
801 801 while unknown:
802 802 r = []
803 803 while unknown:
804 804 n = unknown.pop(0)
805 805 if n[0] in seen:
806 806 continue
807 807
808 808 self.ui.debug(_("examining %s:%s\n") % (short(n[0]), short(n[1])))
809 809 if n[0] == nullid:
810 810 break
811 811 if n in seenbranch:
812 812 self.ui.debug(_("branch already found\n"))
813 813 continue
814 814 if n[1] and n[1] in m: # do we know the base?
815 815 self.ui.debug(_("found incomplete branch %s:%s\n")
816 816 % (short(n[0]), short(n[1])))
817 817 search.append(n) # schedule branch range for scanning
818 818 seenbranch[n] = 1
819 819 else:
820 820 if n[1] not in seen and n[1] not in fetch:
821 821 if n[2] in m and n[3] in m:
822 822 self.ui.debug(_("found new changeset %s\n") %
823 823 short(n[1]))
824 824 fetch[n[1]] = 1 # earliest unknown
825 825 base[n[2]] = 1 # latest known
826 826 continue
827 827
828 828 for a in n[2:4]:
829 829 if a not in rep:
830 830 r.append(a)
831 831 rep[a] = 1
832 832
833 833 seen[n[0]] = 1
834 834
835 835 if r:
836 836 reqcnt += 1
837 837 self.ui.debug(_("request %d: %s\n") %
838 838 (reqcnt, " ".join(map(short, r))))
839 839 for p in range(0, len(r), 10):
840 840 for b in remote.branches(r[p:p+10]):
841 841 self.ui.debug(_("received %s:%s\n") %
842 842 (short(b[0]), short(b[1])))
843 843 if b[0] in m:
844 844 self.ui.debug(_("found base node %s\n") % short(b[0]))
845 845 base[b[0]] = 1
846 846 elif b[0] not in seen:
847 847 unknown.append(b)
848 848
849 849 # do binary search on the branches we found
850 850 while search:
851 851 n = search.pop(0)
852 852 reqcnt += 1
853 853 l = remote.between([(n[0], n[1])])[0]
854 854 l.append(n[1])
855 855 p = n[0]
856 856 f = 1
857 857 for i in l:
858 858 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
859 859 if i in m:
860 860 if f <= 2:
861 861 self.ui.debug(_("found new branch changeset %s\n") %
862 862 short(p))
863 863 fetch[p] = 1
864 864 base[i] = 1
865 865 else:
866 866 self.ui.debug(_("narrowed branch search to %s:%s\n")
867 867 % (short(p), short(i)))
868 868 search.append((p, i))
869 869 break
870 870 p, f = i, f * 2
871 871
872 872 # sanity check our fetch list
873 873 for f in fetch.keys():
874 874 if f in m:
875 875 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
876 876
877 877 if base.keys() == [nullid]:
878 878 self.ui.warn(_("warning: pulling from an unrelated repository!\n"))
879 879
880 880 self.ui.note(_("found new changesets starting at ") +
881 881 " ".join([short(f) for f in fetch]) + "\n")
882 882
883 883 self.ui.debug(_("%d total queries\n") % reqcnt)
884 884
885 885 return fetch.keys()
886 886
887 887 def findoutgoing(self, remote, base=None, heads=None):
888 888 if base == None:
889 889 base = {}
890 890 self.findincoming(remote, base, heads)
891 891
892 892 self.ui.debug(_("common changesets up to ")
893 893 + " ".join(map(short, base.keys())) + "\n")
894 894
895 895 remain = dict.fromkeys(self.changelog.nodemap)
896 896
897 897 # prune everything remote has from the tree
898 898 del remain[nullid]
899 899 remove = base.keys()
900 900 while remove:
901 901 n = remove.pop(0)
902 902 if n in remain:
903 903 del remain[n]
904 904 for p in self.changelog.parents(n):
905 905 remove.append(p)
906 906
907 907 # find every node whose parents have been pruned
908 908 subset = []
909 909 for n in remain:
910 910 p1, p2 = self.changelog.parents(n)
911 911 if p1 not in remain and p2 not in remain:
912 912 subset.append(n)
913 913
914 914 # this is the set of all roots we have to push
915 915 return subset
916 916
917 917 def pull(self, remote, heads = None):
918 918 lock = self.lock()
919 919
920 920 # if we have an empty repo, fetch everything
921 921 if self.changelog.tip() == nullid:
922 922 self.ui.status(_("requesting all changes\n"))
923 923 fetch = [nullid]
924 924 else:
925 925 fetch = self.findincoming(remote)
926 926
927 927 if not fetch:
928 928 self.ui.status(_("no changes found\n"))
929 929 return 1
930 930
931 931 if heads is None:
932 932 cg = remote.changegroup(fetch)
933 933 else:
934 934 cg = remote.changegroupsubset(fetch, heads)
935 935 return self.addchangegroup(cg)
936 936
937 937 def push(self, remote, force=False):
938 938 lock = remote.lock()
939 939
940 940 base = {}
941 941 heads = remote.heads()
942 942 inc = self.findincoming(remote, base, heads)
943 943 if not force and inc:
944 944 self.ui.warn(_("abort: unsynced remote changes!\n"))
945 945 self.ui.status(_("(did you forget to sync? use push -f to force)\n"))
946 946 return 1
947 947
948 948 update = self.findoutgoing(remote, base)
949 949 if not update:
950 950 self.ui.status(_("no changes found\n"))
951 951 return 1
952 952 elif not force:
953 953 if len(heads) < len(self.changelog.heads()):
954 954 self.ui.warn(_("abort: push creates new remote branches!\n"))
955 955 self.ui.status(_("(did you forget to merge?"
956 956 " use push -f to force)\n"))
957 957 return 1
958 958
959 959 cg = self.changegroup(update)
960 960 return remote.addchangegroup(cg)
961 961
962 962 def changegroupsubset(self, bases, heads):
963 963 """This function generates a changegroup consisting of all the nodes
964 964 that are descendents of any of the bases, and ancestors of any of
965 965 the heads.
966 966
967 967 It is fairly complex as determining which filenodes and which
968 968 manifest nodes need to be included for the changeset to be complete
969 969 is non-trivial.
970 970
971 971 Another wrinkle is doing the reverse, figuring out which changeset in
972 972 the changegroup a particular filenode or manifestnode belongs to."""
973 973
974 974 # Set up some initial variables
975 975 # Make it easy to refer to self.changelog
976 976 cl = self.changelog
977 977 # msng is short for missing - compute the list of changesets in this
978 978 # changegroup.
979 979 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
980 980 # Some bases may turn out to be superfluous, and some heads may be
981 981 # too. nodesbetween will return the minimal set of bases and heads
982 982 # necessary to re-create the changegroup.
983 983
984 984 # Known heads are the list of heads that it is assumed the recipient
985 985 # of this changegroup will know about.
986 986 knownheads = {}
987 987 # We assume that all parents of bases are known heads.
988 988 for n in bases:
989 989 for p in cl.parents(n):
990 990 if p != nullid:
991 991 knownheads[p] = 1
992 992 knownheads = knownheads.keys()
993 993 if knownheads:
994 994 # Now that we know what heads are known, we can compute which
995 995 # changesets are known. The recipient must know about all
996 996 # changesets required to reach the known heads from the null
997 997 # changeset.
998 998 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
999 999 junk = None
1000 1000 # Transform the list into an ersatz set.
1001 1001 has_cl_set = dict.fromkeys(has_cl_set)
1002 1002 else:
1003 1003 # If there were no known heads, the recipient cannot be assumed to
1004 1004 # know about any changesets.
1005 1005 has_cl_set = {}
1006 1006
1007 1007 # Make it easy to refer to self.manifest
1008 1008 mnfst = self.manifest
1009 1009 # We don't know which manifests are missing yet
1010 1010 msng_mnfst_set = {}
1011 1011 # Nor do we know which filenodes are missing.
1012 1012 msng_filenode_set = {}
1013 1013
1014 1014 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1015 1015 junk = None
1016 1016
1017 1017 # A changeset always belongs to itself, so the changenode lookup
1018 1018 # function for a changenode is identity.
1019 1019 def identity(x):
1020 1020 return x
1021 1021
1022 1022 # A function generating function. Sets up an environment for the
1023 1023 # inner function.
1024 1024 def cmp_by_rev_func(revlog):
1025 1025 # Compare two nodes by their revision number in the environment's
1026 1026 # revision history. Since the revision number both represents the
1027 1027 # most efficient order to read the nodes in, and represents a
1028 1028 # topological sorting of the nodes, this function is often useful.
1029 1029 def cmp_by_rev(a, b):
1030 1030 return cmp(revlog.rev(a), revlog.rev(b))
1031 1031 return cmp_by_rev
1032 1032
1033 1033 # If we determine that a particular file or manifest node must be a
1034 1034 # node that the recipient of the changegroup will already have, we can
1035 1035 # also assume the recipient will have all the parents. This function
1036 1036 # prunes them from the set of missing nodes.
1037 1037 def prune_parents(revlog, hasset, msngset):
1038 1038 haslst = hasset.keys()
1039 1039 haslst.sort(cmp_by_rev_func(revlog))
1040 1040 for node in haslst:
1041 1041 parentlst = [p for p in revlog.parents(node) if p != nullid]
1042 1042 while parentlst:
1043 1043 n = parentlst.pop()
1044 1044 if n not in hasset:
1045 1045 hasset[n] = 1
1046 1046 p = [p for p in revlog.parents(n) if p != nullid]
1047 1047 parentlst.extend(p)
1048 1048 for n in hasset:
1049 1049 msngset.pop(n, None)
1050 1050
1051 1051 # This is a function generating function used to set up an environment
1052 1052 # for the inner function to execute in.
1053 1053 def manifest_and_file_collector(changedfileset):
1054 1054 # This is an information gathering function that gathers
1055 1055 # information from each changeset node that goes out as part of
1056 1056 # the changegroup. The information gathered is a list of which
1057 1057 # manifest nodes are potentially required (the recipient may
1058 1058 # already have them) and total list of all files which were
1059 1059 # changed in any changeset in the changegroup.
1060 1060 #
1061 1061 # We also remember the first changenode we saw any manifest
1062 1062 # referenced by so we can later determine which changenode 'owns'
1063 1063 # the manifest.
1064 1064 def collect_manifests_and_files(clnode):
1065 1065 c = cl.read(clnode)
1066 1066 for f in c[3]:
1067 1067 # This is to make sure we only have one instance of each
1068 1068 # filename string for each filename.
1069 1069 changedfileset.setdefault(f, f)
1070 1070 msng_mnfst_set.setdefault(c[0], clnode)
1071 1071 return collect_manifests_and_files
1072 1072
1073 1073 # Figure out which manifest nodes (of the ones we think might be part
1074 1074 # of the changegroup) the recipient must know about and remove them
1075 1075 # from the changegroup.
1076 1076 def prune_manifests():
1077 1077 has_mnfst_set = {}
1078 1078 for n in msng_mnfst_set:
1079 1079 # If a 'missing' manifest thinks it belongs to a changenode
1080 1080 # the recipient is assumed to have, obviously the recipient
1081 1081 # must have that manifest.
1082 1082 linknode = cl.node(mnfst.linkrev(n))
1083 1083 if linknode in has_cl_set:
1084 1084 has_mnfst_set[n] = 1
1085 1085 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1086 1086
1087 1087 # Use the information collected in collect_manifests_and_files to say
1088 1088 # which changenode any manifestnode belongs to.
1089 1089 def lookup_manifest_link(mnfstnode):
1090 1090 return msng_mnfst_set[mnfstnode]
1091 1091
1092 1092 # A function generating function that sets up the initial environment
1093 1093 # the inner function.
1094 1094 def filenode_collector(changedfiles):
1095 1095 next_rev = [0]
1096 1096 # This gathers information from each manifestnode included in the
1097 1097 # changegroup about which filenodes the manifest node references
1098 1098 # so we can include those in the changegroup too.
1099 1099 #
1100 1100 # It also remembers which changenode each filenode belongs to. It
1101 1101 # does this by assuming the a filenode belongs to the changenode
1102 1102 # the first manifest that references it belongs to.
1103 1103 def collect_msng_filenodes(mnfstnode):
1104 1104 r = mnfst.rev(mnfstnode)
1105 1105 if r == next_rev[0]:
1106 1106 # If the last rev we looked at was the one just previous,
1107 1107 # we only need to see a diff.
1108 1108 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1109 1109 # For each line in the delta
1110 1110 for dline in delta.splitlines():
1111 1111 # get the filename and filenode for that line
1112 1112 f, fnode = dline.split('\0')
1113 1113 fnode = bin(fnode[:40])
1114 1114 f = changedfiles.get(f, None)
1115 1115 # And if the file is in the list of files we care
1116 1116 # about.
1117 1117 if f is not None:
1118 1118 # Get the changenode this manifest belongs to
1119 1119 clnode = msng_mnfst_set[mnfstnode]
1120 1120 # Create the set of filenodes for the file if
1121 1121 # there isn't one already.
1122 1122 ndset = msng_filenode_set.setdefault(f, {})
1123 1123 # And set the filenode's changelog node to the
1124 1124 # manifest's if it hasn't been set already.
1125 1125 ndset.setdefault(fnode, clnode)
1126 1126 else:
1127 1127 # Otherwise we need a full manifest.
1128 1128 m = mnfst.read(mnfstnode)
1129 1129 # For every file in we care about.
1130 1130 for f in changedfiles:
1131 1131 fnode = m.get(f, None)
1132 1132 # If it's in the manifest
1133 1133 if fnode is not None:
1134 1134 # See comments above.
1135 1135 clnode = msng_mnfst_set[mnfstnode]
1136 1136 ndset = msng_filenode_set.setdefault(f, {})
1137 1137 ndset.setdefault(fnode, clnode)
1138 1138 # Remember the revision we hope to see next.
1139 1139 next_rev[0] = r + 1
1140 1140 return collect_msng_filenodes
1141 1141
1142 1142 # We have a list of filenodes we think we need for a file, lets remove
1143 1143 # all those we now the recipient must have.
1144 1144 def prune_filenodes(f, filerevlog):
1145 1145 msngset = msng_filenode_set[f]
1146 1146 hasset = {}
1147 1147 # If a 'missing' filenode thinks it belongs to a changenode we
1148 1148 # assume the recipient must have, then the recipient must have
1149 1149 # that filenode.
1150 1150 for n in msngset:
1151 1151 clnode = cl.node(filerevlog.linkrev(n))
1152 1152 if clnode in has_cl_set:
1153 1153 hasset[n] = 1
1154 1154 prune_parents(filerevlog, hasset, msngset)
1155 1155
1156 1156 # A function generator function that sets up the a context for the
1157 1157 # inner function.
1158 1158 def lookup_filenode_link_func(fname):
1159 1159 msngset = msng_filenode_set[fname]
1160 1160 # Lookup the changenode the filenode belongs to.
1161 1161 def lookup_filenode_link(fnode):
1162 1162 return msngset[fnode]
1163 1163 return lookup_filenode_link
1164 1164
1165 1165 # Now that we have all theses utility functions to help out and
1166 1166 # logically divide up the task, generate the group.
1167 1167 def gengroup():
1168 1168 # The set of changed files starts empty.
1169 1169 changedfiles = {}
1170 1170 # Create a changenode group generator that will call our functions
1171 1171 # back to lookup the owning changenode and collect information.
1172 1172 group = cl.group(msng_cl_lst, identity,
1173 1173 manifest_and_file_collector(changedfiles))
1174 1174 for chnk in group:
1175 1175 yield chnk
1176 1176
1177 1177 # The list of manifests has been collected by the generator
1178 1178 # calling our functions back.
1179 1179 prune_manifests()
1180 1180 msng_mnfst_lst = msng_mnfst_set.keys()
1181 1181 # Sort the manifestnodes by revision number.
1182 1182 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1183 1183 # Create a generator for the manifestnodes that calls our lookup
1184 1184 # and data collection functions back.
1185 1185 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1186 1186 filenode_collector(changedfiles))
1187 1187 for chnk in group:
1188 1188 yield chnk
1189 1189
1190 1190 # These are no longer needed, dereference and toss the memory for
1191 1191 # them.
1192 1192 msng_mnfst_lst = None
1193 1193 msng_mnfst_set.clear()
1194 1194
1195 1195 changedfiles = changedfiles.keys()
1196 1196 changedfiles.sort()
1197 1197 # Go through all our files in order sorted by name.
1198 1198 for fname in changedfiles:
1199 1199 filerevlog = self.file(fname)
1200 1200 # Toss out the filenodes that the recipient isn't really
1201 1201 # missing.
1202 1202 prune_filenodes(fname, filerevlog)
1203 1203 msng_filenode_lst = msng_filenode_set[fname].keys()
1204 1204 # If any filenodes are left, generate the group for them,
1205 1205 # otherwise don't bother.
1206 1206 if len(msng_filenode_lst) > 0:
1207 1207 yield struct.pack(">l", len(fname) + 4) + fname
1208 1208 # Sort the filenodes by their revision #
1209 1209 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1210 1210 # Create a group generator and only pass in a changenode
1211 1211 # lookup function as we need to collect no information
1212 1212 # from filenodes.
1213 1213 group = filerevlog.group(msng_filenode_lst,
1214 1214 lookup_filenode_link_func(fname))
1215 1215 for chnk in group:
1216 1216 yield chnk
1217 1217 # Don't need this anymore, toss it to free memory.
1218 1218 del msng_filenode_set[fname]
1219 1219 # Signal that no more groups are left.
1220 1220 yield struct.pack(">l", 0)
1221 1221
1222 1222 return util.chunkbuffer(gengroup())
1223 1223
1224 1224 def changegroup(self, basenodes):
1225 1225 """Generate a changegroup of all nodes that we have that a recipient
1226 1226 doesn't.
1227 1227
1228 1228 This is much easier than the previous function as we can assume that
1229 1229 the recipient has any changenode we aren't sending them."""
1230 1230 cl = self.changelog
1231 1231 nodes = cl.nodesbetween(basenodes, None)[0]
1232 1232 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1233 1233
1234 1234 def identity(x):
1235 1235 return x
1236 1236
1237 1237 def gennodelst(revlog):
1238 1238 for r in xrange(0, revlog.count()):
1239 1239 n = revlog.node(r)
1240 1240 if revlog.linkrev(n) in revset:
1241 1241 yield n
1242 1242
1243 1243 def changed_file_collector(changedfileset):
1244 1244 def collect_changed_files(clnode):
1245 1245 c = cl.read(clnode)
1246 1246 for fname in c[3]:
1247 1247 changedfileset[fname] = 1
1248 1248 return collect_changed_files
1249 1249
1250 1250 def lookuprevlink_func(revlog):
1251 1251 def lookuprevlink(n):
1252 1252 return cl.node(revlog.linkrev(n))
1253 1253 return lookuprevlink
1254 1254
1255 1255 def gengroup():
1256 1256 # construct a list of all changed files
1257 1257 changedfiles = {}
1258 1258
1259 1259 for chnk in cl.group(nodes, identity,
1260 1260 changed_file_collector(changedfiles)):
1261 1261 yield chnk
1262 1262 changedfiles = changedfiles.keys()
1263 1263 changedfiles.sort()
1264 1264
1265 1265 mnfst = self.manifest
1266 1266 nodeiter = gennodelst(mnfst)
1267 1267 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1268 1268 yield chnk
1269 1269
1270 1270 for fname in changedfiles:
1271 1271 filerevlog = self.file(fname)
1272 1272 nodeiter = gennodelst(filerevlog)
1273 1273 nodeiter = list(nodeiter)
1274 1274 if nodeiter:
1275 1275 yield struct.pack(">l", len(fname) + 4) + fname
1276 1276 lookup = lookuprevlink_func(filerevlog)
1277 1277 for chnk in filerevlog.group(nodeiter, lookup):
1278 1278 yield chnk
1279 1279
1280 1280 yield struct.pack(">l", 0)
1281 1281
1282 1282 return util.chunkbuffer(gengroup())
1283 1283
1284 1284 def addchangegroup(self, source):
1285 1285
1286 1286 def getchunk():
1287 1287 d = source.read(4)
1288 1288 if not d: return ""
1289 1289 l = struct.unpack(">l", d)[0]
1290 1290 if l <= 4: return ""
1291 1291 d = source.read(l - 4)
1292 1292 if len(d) < l - 4:
1293 1293 raise repo.RepoError(_("premature EOF reading chunk"
1294 1294 " (got %d bytes, expected %d)")
1295 1295 % (len(d), l - 4))
1296 1296 return d
1297 1297
1298 1298 def getgroup():
1299 1299 while 1:
1300 1300 c = getchunk()
1301 1301 if not c: break
1302 1302 yield c
1303 1303
1304 1304 def csmap(x):
1305 1305 self.ui.debug(_("add changeset %s\n") % short(x))
1306 1306 return self.changelog.count()
1307 1307
1308 1308 def revmap(x):
1309 1309 return self.changelog.rev(x)
1310 1310
1311 1311 if not source: return
1312 1312 changesets = files = revisions = 0
1313 1313
1314 1314 tr = self.transaction()
1315 1315
1316 1316 oldheads = len(self.changelog.heads())
1317 1317
1318 1318 # pull off the changeset group
1319 1319 self.ui.status(_("adding changesets\n"))
1320 1320 co = self.changelog.tip()
1321 1321 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1322 1322 cnr, cor = map(self.changelog.rev, (cn, co))
1323 1323 if cn == nullid:
1324 1324 cnr = cor
1325 1325 changesets = cnr - cor
1326 1326
1327 1327 # pull off the manifest group
1328 1328 self.ui.status(_("adding manifests\n"))
1329 1329 mm = self.manifest.tip()
1330 1330 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1331 1331
1332 1332 # process the files
1333 1333 self.ui.status(_("adding file changes\n"))
1334 1334 while 1:
1335 1335 f = getchunk()
1336 1336 if not f: break
1337 1337 self.ui.debug(_("adding %s revisions\n") % f)
1338 1338 fl = self.file(f)
1339 1339 o = fl.count()
1340 1340 n = fl.addgroup(getgroup(), revmap, tr)
1341 1341 revisions += fl.count() - o
1342 1342 files += 1
1343 1343
1344 1344 newheads = len(self.changelog.heads())
1345 1345 heads = ""
1346 1346 if oldheads and newheads > oldheads:
1347 1347 heads = _(" (+%d heads)") % (newheads - oldheads)
1348 1348
1349 1349 self.ui.status(_("added %d changesets"
1350 1350 " with %d changes to %d files%s\n")
1351 1351 % (changesets, revisions, files, heads))
1352 1352
1353 1353 tr.close()
1354 1354
1355 1355 if changesets > 0:
1356 1356 if not self.hook("changegroup",
1357 1357 node=hex(self.changelog.node(cor+1))):
1358 1358 self.ui.warn(_("abort: changegroup hook returned failure!\n"))
1359 1359 return 1
1360 1360
1361 1361 for i in range(cor + 1, cnr + 1):
1362 1362 self.hook("commit", node=hex(self.changelog.node(i)))
1363 1363
1364 1364 return
1365 1365
1366 1366 def update(self, node, allow=False, force=False, choose=None,
1367 1367 moddirstate=True):
1368 1368 pl = self.dirstate.parents()
1369 1369 if not force and pl[1] != nullid:
1370 1370 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1371 1371 return 1
1372 1372
1373 1373 p1, p2 = pl[0], node
1374 1374 pa = self.changelog.ancestor(p1, p2)
1375 1375 m1n = self.changelog.read(p1)[0]
1376 1376 m2n = self.changelog.read(p2)[0]
1377 1377 man = self.manifest.ancestor(m1n, m2n)
1378 1378 m1 = self.manifest.read(m1n)
1379 1379 mf1 = self.manifest.readflags(m1n)
1380 1380 m2 = self.manifest.read(m2n)
1381 1381 mf2 = self.manifest.readflags(m2n)
1382 1382 ma = self.manifest.read(man)
1383 1383 mfa = self.manifest.readflags(man)
1384 1384
1385 1385 (c, a, d, u) = self.changes()
1386 1386
1387 1387 # is this a jump, or a merge? i.e. is there a linear path
1388 1388 # from p1 to p2?
1389 1389 linear_path = (pa == p1 or pa == p2)
1390 1390
1391 1391 # resolve the manifest to determine which files
1392 1392 # we care about merging
1393 1393 self.ui.note(_("resolving manifests\n"))
1394 1394 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1395 1395 (force, allow, moddirstate, linear_path))
1396 1396 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1397 1397 (short(man), short(m1n), short(m2n)))
1398 1398
1399 1399 merge = {}
1400 1400 get = {}
1401 1401 remove = []
1402 1402
1403 1403 # construct a working dir manifest
1404 1404 mw = m1.copy()
1405 1405 mfw = mf1.copy()
1406 1406 umap = dict.fromkeys(u)
1407 1407
1408 1408 for f in a + c + u:
1409 1409 mw[f] = ""
1410 1410 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1411 1411
1412 1412 if moddirstate:
1413 1413 wlock = self.wlock()
1414 1414
1415 1415 for f in d:
1416 1416 if f in mw: del mw[f]
1417 1417
1418 1418 # If we're jumping between revisions (as opposed to merging),
1419 1419 # and if neither the working directory nor the target rev has
1420 1420 # the file, then we need to remove it from the dirstate, to
1421 1421 # prevent the dirstate from listing the file when it is no
1422 1422 # longer in the manifest.
1423 1423 if moddirstate and linear_path and f not in m2:
1424 1424 self.dirstate.forget((f,))
1425 1425
1426 1426 # Compare manifests
1427 1427 for f, n in mw.iteritems():
1428 1428 if choose and not choose(f): continue
1429 1429 if f in m2:
1430 1430 s = 0
1431 1431
1432 1432 # is the wfile new since m1, and match m2?
1433 1433 if f not in m1:
1434 1434 t1 = self.wread(f)
1435 1435 t2 = self.file(f).read(m2[f])
1436 1436 if cmp(t1, t2) == 0:
1437 1437 n = m2[f]
1438 1438 del t1, t2
1439 1439
1440 1440 # are files different?
1441 1441 if n != m2[f]:
1442 1442 a = ma.get(f, nullid)
1443 1443 # are both different from the ancestor?
1444 1444 if n != a and m2[f] != a:
1445 1445 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1446 1446 # merge executable bits
1447 1447 # "if we changed or they changed, change in merge"
1448 1448 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1449 1449 mode = ((a^b) | (a^c)) ^ a
1450 1450 merge[f] = (m1.get(f, nullid), m2[f], mode)
1451 1451 s = 1
1452 1452 # are we clobbering?
1453 1453 # is remote's version newer?
1454 1454 # or are we going back in time?
1455 1455 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1456 1456 self.ui.debug(_(" remote %s is newer, get\n") % f)
1457 1457 get[f] = m2[f]
1458 1458 s = 1
1459 1459 elif f in umap:
1460 1460 # this unknown file is the same as the checkout
1461 1461 get[f] = m2[f]
1462 1462
1463 1463 if not s and mfw[f] != mf2[f]:
1464 1464 if force:
1465 1465 self.ui.debug(_(" updating permissions for %s\n") % f)
1466 1466 util.set_exec(self.wjoin(f), mf2[f])
1467 1467 else:
1468 1468 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1469 1469 mode = ((a^b) | (a^c)) ^ a
1470 1470 if mode != b:
1471 1471 self.ui.debug(_(" updating permissions for %s\n") % f)
1472 1472 util.set_exec(self.wjoin(f), mode)
1473 1473 del m2[f]
1474 1474 elif f in ma:
1475 1475 if n != ma[f]:
1476 1476 r = _("d")
1477 1477 if not force and (linear_path or allow):
1478 1478 r = self.ui.prompt(
1479 1479 (_(" local changed %s which remote deleted\n") % f) +
1480 1480 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1481 1481 if r == _("d"):
1482 1482 remove.append(f)
1483 1483 else:
1484 1484 self.ui.debug(_("other deleted %s\n") % f)
1485 1485 remove.append(f) # other deleted it
1486 1486 else:
1487 1487 # file is created on branch or in working directory
1488 1488 if force and f not in umap:
1489 1489 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1490 1490 remove.append(f)
1491 1491 elif n == m1.get(f, nullid): # same as parent
1492 1492 if p2 == pa: # going backwards?
1493 1493 self.ui.debug(_("remote deleted %s\n") % f)
1494 1494 remove.append(f)
1495 1495 else:
1496 1496 self.ui.debug(_("local modified %s, keeping\n") % f)
1497 1497 else:
1498 1498 self.ui.debug(_("working dir created %s, keeping\n") % f)
1499 1499
1500 1500 for f, n in m2.iteritems():
1501 1501 if choose and not choose(f): continue
1502 1502 if f[0] == "/": continue
1503 1503 if f in ma and n != ma[f]:
1504 1504 r = _("k")
1505 1505 if not force and (linear_path or allow):
1506 1506 r = self.ui.prompt(
1507 1507 (_("remote changed %s which local deleted\n") % f) +
1508 1508 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1509 1509 if r == _("k"): get[f] = n
1510 1510 elif f not in ma:
1511 1511 self.ui.debug(_("remote created %s\n") % f)
1512 1512 get[f] = n
1513 1513 else:
1514 1514 if force or p2 == pa: # going backwards?
1515 1515 self.ui.debug(_("local deleted %s, recreating\n") % f)
1516 1516 get[f] = n
1517 1517 else:
1518 1518 self.ui.debug(_("local deleted %s\n") % f)
1519 1519
1520 1520 del mw, m1, m2, ma
1521 1521
1522 1522 if force:
1523 1523 for f in merge:
1524 1524 get[f] = merge[f][1]
1525 1525 merge = {}
1526 1526
1527 1527 if linear_path or force:
1528 1528 # we don't need to do any magic, just jump to the new rev
1529 1529 branch_merge = False
1530 1530 p1, p2 = p2, nullid
1531 1531 else:
1532 1532 if not allow:
1533 1533 self.ui.status(_("this update spans a branch"
1534 1534 " affecting the following files:\n"))
1535 1535 fl = merge.keys() + get.keys()
1536 1536 fl.sort()
1537 1537 for f in fl:
1538 1538 cf = ""
1539 1539 if f in merge: cf = _(" (resolve)")
1540 1540 self.ui.status(" %s%s\n" % (f, cf))
1541 1541 self.ui.warn(_("aborting update spanning branches!\n"))
1542 1542 self.ui.status(_("(use update -m to merge across branches"
1543 1543 " or -C to lose changes)\n"))
1544 1544 return 1
1545 1545 branch_merge = True
1546 1546
1547 1547 # get the files we don't need to change
1548 1548 files = get.keys()
1549 1549 files.sort()
1550 1550 for f in files:
1551 1551 if f[0] == "/": continue
1552 1552 self.ui.note(_("getting %s\n") % f)
1553 1553 t = self.file(f).read(get[f])
1554 1554 self.wwrite(f, t)
1555 1555 util.set_exec(self.wjoin(f), mf2[f])
1556 1556 if moddirstate:
1557 1557 if branch_merge:
1558 1558 self.dirstate.update([f], 'n', st_mtime=-1)
1559 1559 else:
1560 1560 self.dirstate.update([f], 'n')
1561 1561
1562 1562 # merge the tricky bits
1563 1563 files = merge.keys()
1564 1564 files.sort()
1565 1565 for f in files:
1566 1566 self.ui.status(_("merging %s\n") % f)
1567 1567 my, other, flag = merge[f]
1568 1568 self.merge3(f, my, other)
1569 1569 util.set_exec(self.wjoin(f), flag)
1570 1570 if moddirstate:
1571 1571 if branch_merge:
1572 1572 # We've done a branch merge, mark this file as merged
1573 1573 # so that we properly record the merger later
1574 1574 self.dirstate.update([f], 'm')
1575 1575 else:
1576 1576 # We've update-merged a locally modified file, so
1577 1577 # we set the dirstate to emulate a normal checkout
1578 1578 # of that file some time in the past. Thus our
1579 1579 # merge will appear as a normal local file
1580 1580 # modification.
1581 1581 f_len = len(self.file(f).read(other))
1582 1582 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1583 1583
1584 1584 remove.sort()
1585 1585 for f in remove:
1586 1586 self.ui.note(_("removing %s\n") % f)
1587 1587 try:
1588 1588 util.unlink(self.wjoin(f))
1589 1589 except OSError, inst:
1590 1590 if inst.errno != errno.ENOENT:
1591 1591 self.ui.warn(_("update failed to remove %s: %s!\n") %
1592 1592 (f, inst.strerror))
1593 1593 if moddirstate:
1594 1594 if branch_merge:
1595 1595 self.dirstate.update(remove, 'r')
1596 1596 else:
1597 1597 self.dirstate.forget(remove)
1598 1598
1599 1599 if moddirstate:
1600 1600 self.dirstate.setparents(p1, p2)
1601 1601
1602 1602 def merge3(self, fn, my, other):
1603 1603 """perform a 3-way merge in the working directory"""
1604 1604
1605 1605 def temp(prefix, node):
1606 1606 pre = "%s~%s." % (os.path.basename(fn), prefix)
1607 1607 (fd, name) = tempfile.mkstemp("", pre)
1608 1608 f = os.fdopen(fd, "wb")
1609 1609 self.wwrite(fn, fl.read(node), f)
1610 1610 f.close()
1611 1611 return name
1612 1612
1613 1613 fl = self.file(fn)
1614 1614 base = fl.ancestor(my, other)
1615 1615 a = self.wjoin(fn)
1616 1616 b = temp("base", base)
1617 1617 c = temp("other", other)
1618 1618
1619 1619 self.ui.note(_("resolving %s\n") % fn)
1620 1620 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1621 1621 (fn, short(my), short(other), short(base)))
1622 1622
1623 1623 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1624 1624 or "hgmerge")
1625 1625 r = os.system('%s "%s" "%s" "%s"' % (cmd, a, b, c))
1626 1626 if r:
1627 1627 self.ui.warn(_("merging %s failed!\n") % fn)
1628 1628
1629 1629 os.unlink(b)
1630 1630 os.unlink(c)
1631 1631
1632 1632 def verify(self):
1633 1633 filelinkrevs = {}
1634 1634 filenodes = {}
1635 1635 changesets = revisions = files = 0
1636 1636 errors = [0]
1637 1637 neededmanifests = {}
1638 1638
1639 1639 def err(msg):
1640 1640 self.ui.warn(msg + "\n")
1641 1641 errors[0] += 1
1642 1642
1643 1643 seen = {}
1644 1644 self.ui.status(_("checking changesets\n"))
1645 1645 d = self.changelog.checksize()
1646 1646 if d:
1647 1647 err(_("changeset data short %d bytes") % d)
1648 1648 for i in range(self.changelog.count()):
1649 1649 changesets += 1
1650 1650 n = self.changelog.node(i)
1651 1651 l = self.changelog.linkrev(n)
1652 1652 if l != i:
1653 1653 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1654 1654 if n in seen:
1655 1655 err(_("duplicate changeset at revision %d") % i)
1656 1656 seen[n] = 1
1657 1657
1658 1658 for p in self.changelog.parents(n):
1659 1659 if p not in self.changelog.nodemap:
1660 1660 err(_("changeset %s has unknown parent %s") %
1661 1661 (short(n), short(p)))
1662 1662 try:
1663 1663 changes = self.changelog.read(n)
1664 1664 except KeyboardInterrupt:
1665 1665 self.ui.warn(_("interrupted"))
1666 1666 raise
1667 1667 except Exception, inst:
1668 1668 err(_("unpacking changeset %s: %s") % (short(n), inst))
1669 1669
1670 1670 neededmanifests[changes[0]] = n
1671 1671
1672 1672 for f in changes[3]:
1673 1673 filelinkrevs.setdefault(f, []).append(i)
1674 1674
1675 1675 seen = {}
1676 1676 self.ui.status(_("checking manifests\n"))
1677 1677 d = self.manifest.checksize()
1678 1678 if d:
1679 1679 err(_("manifest data short %d bytes") % d)
1680 1680 for i in range(self.manifest.count()):
1681 1681 n = self.manifest.node(i)
1682 1682 l = self.manifest.linkrev(n)
1683 1683
1684 1684 if l < 0 or l >= self.changelog.count():
1685 1685 err(_("bad manifest link (%d) at revision %d") % (l, i))
1686 1686
1687 1687 if n in neededmanifests:
1688 1688 del neededmanifests[n]
1689 1689
1690 1690 if n in seen:
1691 1691 err(_("duplicate manifest at revision %d") % i)
1692 1692
1693 1693 seen[n] = 1
1694 1694
1695 1695 for p in self.manifest.parents(n):
1696 1696 if p not in self.manifest.nodemap:
1697 1697 err(_("manifest %s has unknown parent %s") %
1698 1698 (short(n), short(p)))
1699 1699
1700 1700 try:
1701 1701 delta = mdiff.patchtext(self.manifest.delta(n))
1702 1702 except KeyboardInterrupt:
1703 1703 self.ui.warn(_("interrupted"))
1704 1704 raise
1705 1705 except Exception, inst:
1706 1706 err(_("unpacking manifest %s: %s") % (short(n), inst))
1707 1707
1708 1708 ff = [ l.split('\0') for l in delta.splitlines() ]
1709 1709 for f, fn in ff:
1710 1710 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1711 1711
1712 1712 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1713 1713
1714 1714 for m,c in neededmanifests.items():
1715 1715 err(_("Changeset %s refers to unknown manifest %s") %
1716 1716 (short(m), short(c)))
1717 1717 del neededmanifests
1718 1718
1719 1719 for f in filenodes:
1720 1720 if f not in filelinkrevs:
1721 1721 err(_("file %s in manifest but not in changesets") % f)
1722 1722
1723 1723 for f in filelinkrevs:
1724 1724 if f not in filenodes:
1725 1725 err(_("file %s in changeset but not in manifest") % f)
1726 1726
1727 1727 self.ui.status(_("checking files\n"))
1728 1728 ff = filenodes.keys()
1729 1729 ff.sort()
1730 1730 for f in ff:
1731 1731 if f == "/dev/null": continue
1732 1732 files += 1
1733 1733 fl = self.file(f)
1734 1734 d = fl.checksize()
1735 1735 if d:
1736 1736 err(_("%s file data short %d bytes") % (f, d))
1737 1737
1738 1738 nodes = { nullid: 1 }
1739 1739 seen = {}
1740 1740 for i in range(fl.count()):
1741 1741 revisions += 1
1742 1742 n = fl.node(i)
1743 1743
1744 1744 if n in seen:
1745 1745 err(_("%s: duplicate revision %d") % (f, i))
1746 1746 if n not in filenodes[f]:
1747 1747 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1748 1748 else:
1749 1749 del filenodes[f][n]
1750 1750
1751 1751 flr = fl.linkrev(n)
1752 1752 if flr not in filelinkrevs[f]:
1753 1753 err(_("%s:%s points to unexpected changeset %d")
1754 1754 % (f, short(n), flr))
1755 1755 else:
1756 1756 filelinkrevs[f].remove(flr)
1757 1757
1758 1758 # verify contents
1759 1759 try:
1760 1760 t = fl.read(n)
1761 1761 except KeyboardInterrupt:
1762 1762 self.ui.warn(_("interrupted"))
1763 1763 raise
1764 1764 except Exception, inst:
1765 1765 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1766 1766
1767 1767 # verify parents
1768 1768 (p1, p2) = fl.parents(n)
1769 1769 if p1 not in nodes:
1770 1770 err(_("file %s:%s unknown parent 1 %s") %
1771 1771 (f, short(n), short(p1)))
1772 1772 if p2 not in nodes:
1773 1773 err(_("file %s:%s unknown parent 2 %s") %
1774 1774 (f, short(n), short(p1)))
1775 1775 nodes[n] = 1
1776 1776
1777 1777 # cross-check
1778 1778 for node in filenodes[f]:
1779 1779 err(_("node %s in manifests not in %s") % (hex(node), f))
1780 1780
1781 1781 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1782 1782 (files, changesets, revisions))
1783 1783
1784 1784 if errors[0]:
1785 1785 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1786 1786 return 1
@@ -1,173 +1,174 b''
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import struct
9 9 from revlog import *
10 10 from i18n import gettext as _
11 11 from demandload import *
12 12 demandload(globals(), "bisect array")
13 13
14 14 class manifest(revlog):
15 def __init__(self, opener):
15 def __init__(self, opener, local=True):
16 16 self.mapcache = None
17 17 self.listcache = None
18 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
18 revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
19 local=local)
19 20
20 21 def read(self, node):
21 22 if node == nullid: return {} # don't upset local cache
22 23 if self.mapcache and self.mapcache[0] == node:
23 24 return self.mapcache[1]
24 25 text = self.revision(node)
25 26 map = {}
26 27 flag = {}
27 28 self.listcache = array.array('c', text)
28 29 lines = text.splitlines(1)
29 30 for l in lines:
30 31 (f, n) = l.split('\0')
31 32 map[f] = bin(n[:40])
32 33 flag[f] = (n[40:-1] == "x")
33 34 self.mapcache = (node, map, flag)
34 35 return map
35 36
36 37 def readflags(self, node):
37 38 if node == nullid: return {} # don't upset local cache
38 39 if not self.mapcache or self.mapcache[0] != node:
39 40 self.read(node)
40 41 return self.mapcache[2]
41 42
42 43 def diff(self, a, b):
43 44 return mdiff.textdiff(str(a), str(b))
44 45
45 46 def add(self, map, flags, transaction, link, p1=None, p2=None,
46 47 changed=None):
47 48
48 49 # returns a tuple (start, end). If the string is found
49 50 # m[start:end] are the line containing that string. If start == end
50 51 # the string was not found and they indicate the proper sorted
51 52 # insertion point. This was taken from bisect_left, and modified
52 53 # to find line start/end as it goes along.
53 54 #
54 55 # m should be a buffer or a string
55 56 # s is a string
56 57 #
57 58 def manifestsearch(m, s, lo=0, hi=None):
58 59 def advance(i, c):
59 60 while i < lenm and m[i] != c:
60 61 i += 1
61 62 return i
62 63 lenm = len(m)
63 64 if not hi:
64 65 hi = lenm
65 66 while lo < hi:
66 67 mid = (lo + hi) // 2
67 68 start = mid
68 69 while start > 0 and m[start-1] != '\n':
69 70 start -= 1
70 71 end = advance(start, '\0')
71 72 if m[start:end] < s:
72 73 # we know that after the null there are 40 bytes of sha1
73 74 # this translates to the bisect lo = mid + 1
74 75 lo = advance(end + 40, '\n') + 1
75 76 else:
76 77 # this translates to the bisect hi = mid
77 78 hi = start
78 79 end = advance(lo, '\0')
79 80 found = m[lo:end]
80 81 if cmp(s, found) == 0:
81 82 # we know that after the null there are 40 bytes of sha1
82 83 end = advance(end + 40, '\n')
83 84 return (lo, end+1)
84 85 else:
85 86 return (lo, lo)
86 87
87 88 # apply the changes collected during the bisect loop to our addlist
88 89 # return a delta suitable for addrevision
89 90 def addlistdelta(addlist, x):
90 91 # start from the bottom up
91 92 # so changes to the offsets don't mess things up.
92 93 i = len(x)
93 94 while i > 0:
94 95 i -= 1
95 96 start = x[i][0]
96 97 end = x[i][1]
97 98 if x[i][2]:
98 99 addlist[start:end] = array.array('c', x[i][2])
99 100 else:
100 101 del addlist[start:end]
101 102 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \
102 103 for d in x ])
103 104
104 105 # if we're using the listcache, make sure it is valid and
105 106 # parented by the same node we're diffing against
106 107 if not changed or not self.listcache or not p1 or \
107 108 self.mapcache[0] != p1:
108 109 files = map.keys()
109 110 files.sort()
110 111
111 112 text = ["%s\000%s%s\n" %
112 113 (f, hex(map[f]), flags[f] and "x" or '')
113 114 for f in files]
114 115 self.listcache = array.array('c', "".join(text))
115 116 cachedelta = None
116 117 else:
117 118 addlist = self.listcache
118 119
119 120 # combine the changed lists into one list for sorting
120 121 work = [[x, 0] for x in changed[0]]
121 122 work[len(work):] = [[x, 1] for x in changed[1]]
122 123 work.sort()
123 124
124 125 delta = []
125 126 dstart = None
126 127 dend = None
127 128 dline = [""]
128 129 start = 0
129 130 # zero copy representation of addlist as a buffer
130 131 addbuf = buffer(addlist)
131 132
132 133 # start with a readonly loop that finds the offset of
133 134 # each line and creates the deltas
134 135 for w in work:
135 136 f = w[0]
136 137 # bs will either be the index of the item or the insert point
137 138 start, end = manifestsearch(addbuf, f, start)
138 139 if w[1] == 0:
139 140 l = "%s\000%s%s\n" % (f, hex(map[f]),
140 141 flags[f] and "x" or '')
141 142 else:
142 143 l = ""
143 144 if start == end and w[1] == 1:
144 145 # item we want to delete was not found, error out
145 146 raise AssertionError(
146 147 _("failed to remove %s from manifest\n") % f)
147 148 if dstart != None and dstart <= start and dend >= start:
148 149 if dend < end:
149 150 dend = end
150 151 if l:
151 152 dline.append(l)
152 153 else:
153 154 if dstart != None:
154 155 delta.append([dstart, dend, "".join(dline)])
155 156 dstart = start
156 157 dend = end
157 158 dline = [l]
158 159
159 160 if dstart != None:
160 161 delta.append([dstart, dend, "".join(dline)])
161 162 # apply the delta to the addlist, and get a delta for addrevision
162 163 cachedelta = addlistdelta(addlist, delta)
163 164
164 165 # the delta is only valid if we've been processing the tip revision
165 166 if self.mapcache[0] != self.tip():
166 167 cachedelta = None
167 168 self.listcache = addlist
168 169
169 170 n = self.addrevision(buffer(self.listcache), transaction, link, p1, \
170 171 p2, cachedelta)
171 172 self.mapcache = (n, map, flags)
172 173
173 174 return n
@@ -1,893 +1,904 b''
1 1 """
2 2 revlog.py - storage back-end for mercurial
3 3
4 4 This provides efficient delta storage with O(1) retrieve and append
5 5 and O(changes) merge between branches
6 6
7 7 Copyright 2005 Matt Mackall <mpm@selenic.com>
8 8
9 9 This software may be used and distributed according to the terms
10 10 of the GNU General Public License, incorporated herein by reference.
11 11 """
12 12
13 13 from node import *
14 14 from i18n import gettext as _
15 15 from demandload import demandload
16 16 demandload(globals(), "binascii errno heapq mdiff sha struct zlib")
17 17
18 18 def hash(text, p1, p2):
19 19 """generate a hash from the given text and its parent hashes
20 20
21 21 This hash combines both the current file contents and its history
22 22 in a manner that makes it easy to distinguish nodes with the same
23 23 content in the revision graph.
24 24 """
25 25 l = [p1, p2]
26 26 l.sort()
27 27 s = sha.new(l[0])
28 28 s.update(l[1])
29 29 s.update(text)
30 30 return s.digest()
31 31
32 32 def compress(text):
33 33 """ generate a possibly-compressed representation of text """
34 34 if not text: return ("", text)
35 35 if len(text) < 44:
36 36 if text[0] == '\0': return ("", text)
37 37 return ('u', text)
38 38 bin = zlib.compress(text)
39 39 if len(bin) > len(text):
40 40 if text[0] == '\0': return ("", text)
41 41 return ('u', text)
42 42 return ("", bin)
43 43
44 44 def decompress(bin):
45 45 """ decompress the given input """
46 46 if not bin: return bin
47 47 t = bin[0]
48 48 if t == '\0': return bin
49 49 if t == 'x': return zlib.decompress(bin)
50 50 if t == 'u': return bin[1:]
51 51 raise RevlogError(_("unknown compression type %s") % t)
52 52
53 53 indexformat = ">4l20s20s20s"
54 54
55 55 class lazyparser(object):
56 56 """
57 57 this class avoids the need to parse the entirety of large indices
58 58
59 59 By default we parse and load 1000 entries at a time.
60 60
61 61 If no position is specified, we load the whole index, and replace
62 62 the lazy objects in revlog with the underlying objects for
63 63 efficiency in cases where we look at most of the nodes.
64 64 """
65 65 def __init__(self, data, revlog):
66 66 self.data = data
67 67 self.s = struct.calcsize(indexformat)
68 68 self.l = len(data)/self.s
69 69 self.index = [None] * self.l
70 70 self.map = {nullid: -1}
71 71 self.all = 0
72 72 self.revlog = revlog
73 73
74 74 def trunc(self, pos):
75 75 self.l = pos/self.s
76 76
77 77 def load(self, pos=None):
78 78 if self.all: return
79 79 if pos is not None:
80 80 block = pos / 1000
81 81 i = block * 1000
82 82 end = min(self.l, i + 1000)
83 83 else:
84 84 self.all = 1
85 85 i = 0
86 86 end = self.l
87 87 self.revlog.index = self.index
88 88 self.revlog.nodemap = self.map
89 89
90 90 while i < end:
91 91 d = self.data[i * self.s: (i + 1) * self.s]
92 92 e = struct.unpack(indexformat, d)
93 93 self.index[i] = e
94 94 self.map[e[6]] = i
95 95 i += 1
96 96
97 97 class lazyindex(object):
98 98 """a lazy version of the index array"""
99 99 def __init__(self, parser):
100 100 self.p = parser
101 101 def __len__(self):
102 102 return len(self.p.index)
103 103 def load(self, pos):
104 104 if pos < 0:
105 105 pos += len(self.p.index)
106 106 self.p.load(pos)
107 107 return self.p.index[pos]
108 108 def __getitem__(self, pos):
109 109 return self.p.index[pos] or self.load(pos)
110 110 def __delitem__(self, pos):
111 111 del self.p.index[pos]
112 112 def append(self, e):
113 113 self.p.index.append(e)
114 114 def trunc(self, pos):
115 115 self.p.trunc(pos)
116 116
117 117 class lazymap(object):
118 118 """a lazy version of the node map"""
119 119 def __init__(self, parser):
120 120 self.p = parser
121 121 def load(self, key):
122 122 if self.p.all: return
123 123 n = self.p.data.find(key)
124 124 if n < 0:
125 125 raise KeyError(key)
126 126 pos = n / self.p.s
127 127 self.p.load(pos)
128 128 def __contains__(self, key):
129 129 self.p.load()
130 130 return key in self.p.map
131 131 def __iter__(self):
132 132 yield nullid
133 133 for i in xrange(self.p.l):
134 134 try:
135 135 yield self.p.index[i][6]
136 136 except:
137 137 self.p.load(i)
138 138 yield self.p.index[i][6]
139 139 def __getitem__(self, key):
140 140 try:
141 141 return self.p.map[key]
142 142 except KeyError:
143 143 try:
144 144 self.load(key)
145 145 return self.p.map[key]
146 146 except KeyError:
147 147 raise KeyError("node " + hex(key))
148 148 def __setitem__(self, key, val):
149 149 self.p.map[key] = val
150 150 def __delitem__(self, key):
151 151 del self.p.map[key]
152 152
153 153 class RevlogError(Exception): pass
154 154
155 155 class revlog(object):
156 156 """
157 157 the underlying revision storage object
158 158
159 159 A revlog consists of two parts, an index and the revision data.
160 160
161 161 The index is a file with a fixed record size containing
162 162 information on each revision, includings its nodeid (hash), the
163 163 nodeids of its parents, the position and offset of its data within
164 164 the data file, and the revision it's based on. Finally, each entry
165 165 contains a linkrev entry that can serve as a pointer to external
166 166 data.
167 167
168 168 The revision data itself is a linear collection of data chunks.
169 169 Each chunk represents a revision and is usually represented as a
170 170 delta against the previous chunk. To bound lookup time, runs of
171 171 deltas are limited to about 2 times the length of the original
172 172 version data. This makes retrieval of a version proportional to
173 173 its size, or O(1) relative to the number of revisions.
174 174
175 175 Both pieces of the revlog are written to in an append-only
176 176 fashion, which means we never need to rewrite a file to insert or
177 177 remove data, and can use some simple techniques to avoid the need
178 178 for locking while reading.
179 179 """
180 def __init__(self, opener, indexfile, datafile):
180 def __init__(self, opener, indexfile, datafile, local=True):
181 181 """
182 182 create a revlog object
183 183
184 184 opener is a function that abstracts the file opening operation
185 185 and can be used to implement COW semantics or the like.
186 186 """
187 187 self.indexfile = indexfile
188 188 self.datafile = datafile
189 189 self.opener = opener
190 190 self.cache = None
191 self.local = local # XXX only needed because statichttp
191 192
192 193 try:
193 194 i = self.opener(self.indexfile).read()
194 195 except IOError, inst:
195 196 if inst.errno != errno.ENOENT:
196 197 raise
197 198 i = ""
198 199
199 200 if len(i) > 10000:
200 201 # big index, let's parse it on demand
201 202 parser = lazyparser(i, self)
202 203 self.index = lazyindex(parser)
203 204 self.nodemap = lazymap(parser)
204 205 else:
205 206 s = struct.calcsize(indexformat)
206 207 l = len(i) / s
207 208 self.index = [None] * l
208 209 m = [None] * l
209 210
210 211 n = 0
211 212 for f in xrange(0, len(i), s):
212 213 # offset, size, base, linkrev, p1, p2, nodeid
213 214 e = struct.unpack(indexformat, i[f:f + s])
214 215 m[n] = (e[6], n)
215 216 self.index[n] = e
216 217 n += 1
217 218
218 219 self.nodemap = dict(m)
219 220 self.nodemap[nullid] = -1
220 221
221 222 def tip(self): return self.node(len(self.index) - 1)
222 223 def count(self): return len(self.index)
223 224 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
224 225 def rev(self, node):
225 226 try:
226 227 return self.nodemap[node]
227 228 except KeyError:
228 229 raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node)))
229 230 def linkrev(self, node): return self.index[self.rev(node)][3]
230 231 def parents(self, node):
231 232 if node == nullid: return (nullid, nullid)
232 233 return self.index[self.rev(node)][4:6]
233 234
234 235 def start(self, rev): return self.index[rev][0]
235 236 def length(self, rev): return self.index[rev][1]
236 237 def end(self, rev): return self.start(rev) + self.length(rev)
237 238 def base(self, rev): return self.index[rev][2]
238 239
239 240 def reachable(self, rev, stop=None):
240 241 reachable = {}
241 242 visit = [rev]
242 243 reachable[rev] = 1
243 244 if stop:
244 245 stopn = self.rev(stop)
245 246 else:
246 247 stopn = 0
247 248 while visit:
248 249 n = visit.pop(0)
249 250 if n == stop:
250 251 continue
251 252 if n == nullid:
252 253 continue
253 254 for p in self.parents(n):
254 255 if self.rev(p) < stopn:
255 256 continue
256 257 if p not in reachable:
257 258 reachable[p] = 1
258 259 visit.append(p)
259 260 return reachable
260 261
261 262 def nodesbetween(self, roots=None, heads=None):
262 263 """Return a tuple containing three elements. Elements 1 and 2 contain
263 264 a final list bases and heads after all the unreachable ones have been
264 265 pruned. Element 0 contains a topologically sorted list of all
265 266
266 267 nodes that satisfy these constraints:
267 268 1. All nodes must be descended from a node in roots (the nodes on
268 269 roots are considered descended from themselves).
269 270 2. All nodes must also be ancestors of a node in heads (the nodes in
270 271 heads are considered to be their own ancestors).
271 272
272 273 If roots is unspecified, nullid is assumed as the only root.
273 274 If heads is unspecified, it is taken to be the output of the
274 275 heads method (i.e. a list of all nodes in the repository that
275 276 have no children)."""
276 277 nonodes = ([], [], [])
277 278 if roots is not None:
278 279 roots = list(roots)
279 280 if not roots:
280 281 return nonodes
281 282 lowestrev = min([self.rev(n) for n in roots])
282 283 else:
283 284 roots = [nullid] # Everybody's a descendent of nullid
284 285 lowestrev = -1
285 286 if (lowestrev == -1) and (heads is None):
286 287 # We want _all_ the nodes!
287 288 return ([self.node(r) for r in xrange(0, self.count())],
288 289 [nullid], list(self.heads()))
289 290 if heads is None:
290 291 # All nodes are ancestors, so the latest ancestor is the last
291 292 # node.
292 293 highestrev = self.count() - 1
293 294 # Set ancestors to None to signal that every node is an ancestor.
294 295 ancestors = None
295 296 # Set heads to an empty dictionary for later discovery of heads
296 297 heads = {}
297 298 else:
298 299 heads = list(heads)
299 300 if not heads:
300 301 return nonodes
301 302 ancestors = {}
302 303 # Start at the top and keep marking parents until we're done.
303 304 nodestotag = heads[:]
304 305 # Turn heads into a dictionary so we can remove 'fake' heads.
305 306 # Also, later we will be using it to filter out the heads we can't
306 307 # find from roots.
307 308 heads = dict.fromkeys(heads, 0)
308 309 # Remember where the top was so we can use it as a limit later.
309 310 highestrev = max([self.rev(n) for n in nodestotag])
310 311 while nodestotag:
311 312 # grab a node to tag
312 313 n = nodestotag.pop()
313 314 # Never tag nullid
314 315 if n == nullid:
315 316 continue
316 317 # A node's revision number represents its place in a
317 318 # topologically sorted list of nodes.
318 319 r = self.rev(n)
319 320 if r >= lowestrev:
320 321 if n not in ancestors:
321 322 # If we are possibly a descendent of one of the roots
322 323 # and we haven't already been marked as an ancestor
323 324 ancestors[n] = 1 # Mark as ancestor
324 325 # Add non-nullid parents to list of nodes to tag.
325 326 nodestotag.extend([p for p in self.parents(n) if
326 327 p != nullid])
327 328 elif n in heads: # We've seen it before, is it a fake head?
328 329 # So it is, real heads should not be the ancestors of
329 330 # any other heads.
330 331 heads.pop(n)
331 332 if not ancestors:
332 333 return nonodes
333 334 # Now that we have our set of ancestors, we want to remove any
334 335 # roots that are not ancestors.
335 336
336 337 # If one of the roots was nullid, everything is included anyway.
337 338 if lowestrev > -1:
338 339 # But, since we weren't, let's recompute the lowest rev to not
339 340 # include roots that aren't ancestors.
340 341
341 342 # Filter out roots that aren't ancestors of heads
342 343 roots = [n for n in roots if n in ancestors]
343 344 # Recompute the lowest revision
344 345 if roots:
345 346 lowestrev = min([self.rev(n) for n in roots])
346 347 else:
347 348 # No more roots? Return empty list
348 349 return nonodes
349 350 else:
350 351 # We are descending from nullid, and don't need to care about
351 352 # any other roots.
352 353 lowestrev = -1
353 354 roots = [nullid]
354 355 # Transform our roots list into a 'set' (i.e. a dictionary where the
355 356 # values don't matter.
356 357 descendents = dict.fromkeys(roots, 1)
357 358 # Also, keep the original roots so we can filter out roots that aren't
358 359 # 'real' roots (i.e. are descended from other roots).
359 360 roots = descendents.copy()
360 361 # Our topologically sorted list of output nodes.
361 362 orderedout = []
362 363 # Don't start at nullid since we don't want nullid in our output list,
363 364 # and if nullid shows up in descedents, empty parents will look like
364 365 # they're descendents.
365 366 for r in xrange(max(lowestrev, 0), highestrev + 1):
366 367 n = self.node(r)
367 368 isdescendent = False
368 369 if lowestrev == -1: # Everybody is a descendent of nullid
369 370 isdescendent = True
370 371 elif n in descendents:
371 372 # n is already a descendent
372 373 isdescendent = True
373 374 # This check only needs to be done here because all the roots
374 375 # will start being marked is descendents before the loop.
375 376 if n in roots:
376 377 # If n was a root, check if it's a 'real' root.
377 378 p = tuple(self.parents(n))
378 379 # If any of its parents are descendents, it's not a root.
379 380 if (p[0] in descendents) or (p[1] in descendents):
380 381 roots.pop(n)
381 382 else:
382 383 p = tuple(self.parents(n))
383 384 # A node is a descendent if either of its parents are
384 385 # descendents. (We seeded the dependents list with the roots
385 386 # up there, remember?)
386 387 if (p[0] in descendents) or (p[1] in descendents):
387 388 descendents[n] = 1
388 389 isdescendent = True
389 390 if isdescendent and ((ancestors is None) or (n in ancestors)):
390 391 # Only include nodes that are both descendents and ancestors.
391 392 orderedout.append(n)
392 393 if (ancestors is not None) and (n in heads):
393 394 # We're trying to figure out which heads are reachable
394 395 # from roots.
395 396 # Mark this head as having been reached
396 397 heads[n] = 1
397 398 elif ancestors is None:
398 399 # Otherwise, we're trying to discover the heads.
399 400 # Assume this is a head because if it isn't, the next step
400 401 # will eventually remove it.
401 402 heads[n] = 1
402 403 # But, obviously its parents aren't.
403 404 for p in self.parents(n):
404 405 heads.pop(p, None)
405 406 heads = [n for n in heads.iterkeys() if heads[n] != 0]
406 407 roots = roots.keys()
407 408 assert orderedout
408 409 assert roots
409 410 assert heads
410 411 return (orderedout, roots, heads)
411 412
412 413 def heads(self, start=None):
413 414 """return the list of all nodes that have no children
414 415
415 416 if start is specified, only heads that are descendants of
416 417 start will be returned
417 418
418 419 """
419 420 if start is None:
420 421 start = nullid
421 422 reachable = {start: 1}
422 423 heads = {start: 1}
423 424 startrev = self.rev(start)
424 425
425 426 for r in xrange(startrev + 1, self.count()):
426 427 n = self.node(r)
427 428 for pn in self.parents(n):
428 429 if pn in reachable:
429 430 reachable[n] = 1
430 431 heads[n] = 1
431 432 if pn in heads:
432 433 del heads[pn]
433 434 return heads.keys()
434 435
435 436 def children(self, node):
436 437 """find the children of a given node"""
437 438 c = []
438 439 p = self.rev(node)
439 440 for r in range(p + 1, self.count()):
440 441 n = self.node(r)
441 442 for pn in self.parents(n):
442 443 if pn == node:
443 444 c.append(n)
444 445 continue
445 446 elif pn == nullid:
446 447 continue
447 448 return c
448 449
449 450 def lookup(self, id):
450 451 """locate a node based on revision number or subset of hex nodeid"""
451 452 try:
452 453 rev = int(id)
453 454 if str(rev) != id: raise ValueError
454 455 if rev < 0: rev = self.count() + rev
455 456 if rev < 0 or rev >= self.count(): raise ValueError
456 457 return self.node(rev)
457 458 except (ValueError, OverflowError):
458 459 c = []
459 460 for n in self.nodemap:
460 461 if hex(n).startswith(id):
461 462 c.append(n)
462 463 if len(c) > 1: raise RevlogError(_("Ambiguous identifier"))
463 464 if len(c) < 1: raise RevlogError(_("No match found"))
464 465 return c[0]
465 466
466 467 return None
467 468
468 469 def diff(self, a, b):
469 470 """return a delta between two revisions"""
470 471 return mdiff.textdiff(a, b)
471 472
472 473 def patches(self, t, pl):
473 474 """apply a list of patches to a string"""
474 475 return mdiff.patches(t, pl)
475 476
476 477 def delta(self, node):
477 478 """return or calculate a delta between a node and its predecessor"""
478 479 r = self.rev(node)
479 480 b = self.base(r)
480 481 if r == b:
481 482 return self.diff(self.revision(self.node(r - 1)),
482 483 self.revision(node))
483 484 else:
484 485 f = self.opener(self.datafile)
485 486 f.seek(self.start(r))
486 487 data = f.read(self.length(r))
487 488 return decompress(data)
488 489
489 490 def revision(self, node):
490 491 """return an uncompressed revision of a given"""
491 492 if node == nullid: return ""
492 493 if self.cache and self.cache[0] == node: return self.cache[2]
493 494
494 495 # look up what we need to read
495 496 text = None
496 497 rev = self.rev(node)
497 498 start, length, base, link, p1, p2, node = self.index[rev]
498 499 end = start + length
499 500 if base != rev: start = self.start(base)
500 501
501 502 # do we have useful data cached?
502 503 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
503 504 base = self.cache[1]
504 505 start = self.start(base + 1)
505 506 text = self.cache[2]
506 507 last = 0
507 508
508 509 f = self.opener(self.datafile)
509 510 f.seek(start)
510 511 data = f.read(end - start)
511 512
512 513 if text is None:
513 514 last = self.length(base)
514 515 text = decompress(data[:last])
515 516
516 517 bins = []
517 518 for r in xrange(base + 1, rev + 1):
518 519 s = self.length(r)
519 520 bins.append(decompress(data[last:last + s]))
520 521 last = last + s
521 522
522 523 text = mdiff.patches(text, bins)
523 524
524 525 if node != hash(text, p1, p2):
525 526 raise RevlogError(_("integrity check failed on %s:%d")
526 527 % (self.datafile, rev))
527 528
528 529 self.cache = (node, rev, text)
529 530 return text
530 531
531 532 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
532 533 """add a revision to the log
533 534
534 535 text - the revision data to add
535 536 transaction - the transaction object used for rollback
536 537 link - the linkrev data to add
537 538 p1, p2 - the parent nodeids of the revision
538 539 d - an optional precomputed delta
539 540 """
540 541 if text is None: text = ""
541 542 if p1 is None: p1 = self.tip()
542 543 if p2 is None: p2 = nullid
543 544
544 545 node = hash(text, p1, p2)
545 546
546 547 if node in self.nodemap:
547 548 return node
548 549
549 550 n = self.count()
550 551 t = n - 1
551 552
552 553 if n:
553 554 base = self.base(t)
554 555 start = self.start(base)
555 556 end = self.end(t)
556 557 if not d:
557 558 prev = self.revision(self.tip())
558 559 d = self.diff(prev, str(text))
559 560 data = compress(d)
560 561 l = len(data[1]) + len(data[0])
561 562 dist = end - start + l
562 563
563 564 # full versions are inserted when the needed deltas
564 565 # become comparable to the uncompressed text
565 566 if not n or dist > len(text) * 2:
566 567 data = compress(text)
567 568 l = len(data[1]) + len(data[0])
568 569 base = n
569 570 else:
570 571 base = self.base(t)
571 572
572 573 offset = 0
573 574 if t >= 0:
574 575 offset = self.end(t)
575 576
576 577 e = (offset, l, base, link, p1, p2, node)
577 578
578 579 self.index.append(e)
579 580 self.nodemap[node] = n
580 581 entry = struct.pack(indexformat, *e)
581 582
582 583 transaction.add(self.datafile, e[0])
583 584 f = self.opener(self.datafile, "a")
584 585 if data[0]:
585 586 f.write(data[0])
586 587 f.write(data[1])
587 588 transaction.add(self.indexfile, n * len(entry))
588 589 self.opener(self.indexfile, "a").write(entry)
589 590
590 591 self.cache = (node, n, text)
591 592 return node
592 593
593 594 def ancestor(self, a, b):
594 595 """calculate the least common ancestor of nodes a and b"""
595 596 # calculate the distance of every node from root
596 597 dist = {nullid: 0}
597 598 for i in xrange(self.count()):
598 599 n = self.node(i)
599 600 p1, p2 = self.parents(n)
600 601 dist[n] = max(dist[p1], dist[p2]) + 1
601 602
602 603 # traverse ancestors in order of decreasing distance from root
603 604 def ancestors(node):
604 605 # we store negative distances because heap returns smallest member
605 606 h = [(-dist[node], node)]
606 607 seen = {}
607 608 earliest = self.count()
608 609 while h:
609 610 d, n = heapq.heappop(h)
610 611 if n not in seen:
611 612 seen[n] = 1
612 613 r = self.rev(n)
613 614 yield (-d, n)
614 615 for p in self.parents(n):
615 616 heapq.heappush(h, (-dist[p], p))
616 617
617 618 def generations(node):
618 619 sg, s = None, {}
619 620 for g,n in ancestors(node):
620 621 if g != sg:
621 622 if sg:
622 623 yield sg, s
623 624 sg, s = g, {n:1}
624 625 else:
625 626 s[n] = 1
626 627 yield sg, s
627 628
628 629 x = generations(a)
629 630 y = generations(b)
630 631 gx = x.next()
631 632 gy = y.next()
632 633
633 634 # increment each ancestor list until it is closer to root than
634 635 # the other, or they match
635 636 while 1:
636 637 #print "ancestor gen %s %s" % (gx[0], gy[0])
637 638 if gx[0] == gy[0]:
638 639 # find the intersection
639 640 i = [ n for n in gx[1] if n in gy[1] ]
640 641 if i:
641 642 return i[0]
642 643 else:
643 644 #print "next"
644 645 gy = y.next()
645 646 gx = x.next()
646 647 elif gx[0] < gy[0]:
647 648 #print "next y"
648 649 gy = y.next()
649 650 else:
650 651 #print "next x"
651 652 gx = x.next()
652 653
653 def group(self, nodelist, lookup, infocollect = None):
654 def group(self, nodelist, lookup, infocollect=None):
654 655 """calculate a delta group
655 656
656 657 Given a list of changeset revs, return a set of deltas and
657 658 metadata corresponding to nodes. the first delta is
658 659 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
659 660 have this parent as it has all history before these
660 661 changesets. parent is parent[0]
661 662 """
662 663 revs = [self.rev(n) for n in nodelist]
663 needed = dict.fromkeys(revs, 1)
664 664
665 665 # if we don't have any revisions touched by these changesets, bail
666 666 if not revs:
667 667 yield struct.pack(">l", 0)
668 668 return
669 669
670 670 # add the parent of the first rev
671 671 p = self.parents(self.node(revs[0]))[0]
672 672 revs.insert(0, self.rev(p))
673 673
674 # for each delta that isn't contiguous in the log, we need to
675 # reconstruct the base, reconstruct the result, and then
676 # calculate the delta. We also need to do this where we've
677 # stored a full version and not a delta
678 for i in xrange(0, len(revs) - 1):
679 a, b = revs[i], revs[i + 1]
680 if a + 1 != b or self.base(b) == b:
681 for j in xrange(self.base(a), a + 1):
682 needed[j] = 1
683 for j in xrange(self.base(b), b + 1):
684 needed[j] = 1
674 if self.local:
675 mm = self.opener(self.datafile)
676 def chunk(r):
677 o = self.start(r)
678 l = self.length(r)
679 mm.seek(o)
680 return decompress(mm.read(l))
681 else:
682 # XXX: statichttp workaround
683 needed = dict.fromkeys(revs[1:], 1)
684 # for each delta that isn't contiguous in the log, we need to
685 # reconstruct the base, reconstruct the result, and then
686 # calculate the delta. We also need to do this where we've
687 # stored a full version and not a delta
688 for i in xrange(0, len(revs) - 1):
689 a, b = revs[i], revs[i + 1]
690 if a + 1 != b or self.base(b) == b:
691 for j in xrange(self.base(a), a + 1):
692 needed[j] = 1
693 for j in xrange(self.base(b), b + 1):
694 needed[j] = 1
685 695
686 # calculate spans to retrieve from datafile
687 needed = needed.keys()
688 needed.sort()
689 spans = []
690 oo = -1
691 ol = 0
692 for n in needed:
693 if n < 0: continue
694 o = self.start(n)
695 l = self.length(n)
696 if oo + ol == o: # can we merge with the previous?
697 nl = spans[-1][2]
698 nl.append((n, l))
699 ol += l
700 spans[-1] = (oo, ol, nl)
701 else:
702 oo = o
703 ol = l
704 spans.append((oo, ol, [(n, l)]))
696 # calculate spans to retrieve from datafile
697 needed = needed.keys()
698 needed.sort()
699 spans = []
700 oo = -1
701 ol = 0
702 for n in needed:
703 if n < 0: continue
704 o = self.start(n)
705 l = self.length(n)
706 if oo + ol == o: # can we merge with the previous?
707 nl = spans[-1][2]
708 nl.append((n, l))
709 ol += l
710 spans[-1] = (oo, ol, nl)
711 else:
712 oo = o
713 ol = l
714 spans.append((oo, ol, [(n, l)]))
705 715
706 # read spans in, divide up chunks
707 chunks = {}
708 for span in spans:
709 # we reopen the file for each span to make http happy for now
710 f = self.opener(self.datafile)
711 f.seek(span[0])
712 data = f.read(span[1])
716 # read spans in, divide up chunks
717 chunks = {}
718 for span in spans:
719 # we reopen the file for each span to make http happy for now
720 f = self.opener(self.datafile)
721 f.seek(span[0])
722 data = f.read(span[1])
713 723
714 # divide up the span
715 pos = 0
716 for r, l in span[2]:
717 chunks[r] = decompress(data[pos: pos + l])
718 pos += l
724 # divide up the span
725 pos = 0
726 for r, l in span[2]:
727 chunks[r] = decompress(data[pos: pos + l])
728 pos += l
729 def chunk(r):
730 return chunks[r]
719 731
720 732 # helper to reconstruct intermediate versions
721 733 def construct(text, base, rev):
722 bins = [chunks[r] for r in xrange(base + 1, rev + 1)]
734 bins = [chunk(r) for r in xrange(base + 1, rev + 1)]
723 735 return mdiff.patches(text, bins)
724 736
725 737 # build deltas
726 deltas = []
727 738 for d in xrange(0, len(revs) - 1):
728 739 a, b = revs[d], revs[d + 1]
729 740 n = self.node(b)
730 741
731 742 if infocollect is not None:
732 743 infocollect(n)
733 744
734 745 # do we need to construct a new delta?
735 746 if a + 1 != b or self.base(b) == b:
736 747 if a >= 0:
737 748 base = self.base(a)
738 ta = chunks[self.base(a)]
749 ta = chunk(self.base(a))
739 750 ta = construct(ta, base, a)
740 751 else:
741 752 ta = ""
742 753
743 754 base = self.base(b)
744 755 if a > base:
745 756 base = a
746 757 tb = ta
747 758 else:
748 tb = chunks[self.base(b)]
759 tb = chunk(self.base(b))
749 760 tb = construct(tb, base, b)
750 761 d = self.diff(ta, tb)
751 762 else:
752 d = chunks[b]
763 d = chunk(b)
753 764
754 765 p = self.parents(n)
755 766 meta = n + p[0] + p[1] + lookup(n)
756 767 l = struct.pack(">l", len(meta) + len(d) + 4)
757 768 yield l
758 769 yield meta
759 770 yield d
760 771
761 772 yield struct.pack(">l", 0)
762 773
763 774 def addgroup(self, revs, linkmapper, transaction, unique=0):
764 775 """
765 776 add a delta group
766 777
767 778 given a set of deltas, add them to the revision log. the
768 779 first delta is against its parent, which should be in our
769 780 log, the rest are against the previous delta.
770 781 """
771 782
772 783 #track the base of the current delta log
773 784 r = self.count()
774 785 t = r - 1
775 786 node = nullid
776 787
777 788 base = prev = -1
778 789 start = end = measure = 0
779 790 if r:
780 791 start = self.start(self.base(t))
781 792 end = self.end(t)
782 793 measure = self.length(self.base(t))
783 794 base = self.base(t)
784 795 prev = self.tip()
785 796
786 797 transaction.add(self.datafile, end)
787 798 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
788 799 dfh = self.opener(self.datafile, "a")
789 800 ifh = self.opener(self.indexfile, "a")
790 801
791 802 # loop through our set of deltas
792 803 chain = None
793 804 for chunk in revs:
794 805 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
795 806 link = linkmapper(cs)
796 807 if node in self.nodemap:
797 808 # this can happen if two branches make the same change
798 809 # if unique:
799 810 # raise RevlogError(_("already have %s") % hex(node[:4]))
800 811 chain = node
801 812 continue
802 813 delta = chunk[80:]
803 814
804 815 for p in (p1, p2):
805 816 if not p in self.nodemap:
806 817 raise RevlogError(_("unknown parent %s") % short(p1))
807 818
808 819 if not chain:
809 820 # retrieve the parent revision of the delta chain
810 821 chain = p1
811 822 if not chain in self.nodemap:
812 823 raise RevlogError(_("unknown base %s") % short(chain[:4]))
813 824
814 825 # full versions are inserted when the needed deltas become
815 826 # comparable to the uncompressed text or when the previous
816 827 # version is not the one we have a delta against. We use
817 828 # the size of the previous full rev as a proxy for the
818 829 # current size.
819 830
820 831 if chain == prev:
821 832 tempd = compress(delta)
822 833 cdelta = tempd[0] + tempd[1]
823 834
824 835 if chain != prev or (end - start + len(cdelta)) > measure * 2:
825 836 # flush our writes here so we can read it in revision
826 837 dfh.flush()
827 838 ifh.flush()
828 839 text = self.revision(chain)
829 840 text = self.patches(text, [delta])
830 841 chk = self.addrevision(text, transaction, link, p1, p2)
831 842 if chk != node:
832 843 raise RevlogError(_("consistency error adding group"))
833 844 measure = len(text)
834 845 else:
835 846 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
836 847 self.index.append(e)
837 848 self.nodemap[node] = r
838 849 dfh.write(cdelta)
839 850 ifh.write(struct.pack(indexformat, *e))
840 851
841 852 t, r, chain, prev = r, r + 1, node, node
842 853 start = self.start(self.base(t))
843 854 end = self.end(t)
844 855
845 856 dfh.close()
846 857 ifh.close()
847 858 return node
848 859
849 860 def strip(self, rev, minlink):
850 861 if self.count() == 0 or rev >= self.count():
851 862 return
852 863
853 864 # When stripping away a revision, we need to make sure it
854 865 # does not actually belong to an older changeset.
855 866 # The minlink parameter defines the oldest revision
856 867 # we're allowed to strip away.
857 868 while minlink > self.index[rev][3]:
858 869 rev += 1
859 870 if rev >= self.count():
860 871 return
861 872
862 873 # first truncate the files on disk
863 874 end = self.start(rev)
864 875 self.opener(self.datafile, "a").truncate(end)
865 876 end = rev * struct.calcsize(indexformat)
866 877 self.opener(self.indexfile, "a").truncate(end)
867 878
868 879 # then reset internal state in memory to forget those revisions
869 880 self.cache = None
870 881 for p in self.index[rev:]:
871 882 del self.nodemap[p[6]]
872 883 del self.index[rev:]
873 884
874 885 # truncating the lazyindex also truncates the lazymap.
875 886 if isinstance(self.index, lazyindex):
876 887 self.index.trunc(end)
877 888
878 889
879 890 def checksize(self):
880 891 expected = 0
881 892 if self.count():
882 893 expected = self.end(self.count() - 1)
883 894 try:
884 895 f = self.opener(self.datafile)
885 896 f.seek(0, 2)
886 897 actual = f.tell()
887 898 return expected - actual
888 899 except IOError, inst:
889 900 if inst.errno == errno.ENOENT:
890 901 return 0
891 902 raise
892 903
893 904
@@ -1,43 +1,45 b''
1 1 # statichttprepo.py - simple http repository class for mercurial
2 2 #
3 3 # This provides read-only repo access to repositories exported via static http
4 4 #
5 5 # Copyright 2005 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms
8 8 # of the GNU General Public License, incorporated herein by reference.
9 9
10 10 from demandload import demandload
11 11 demandload(globals(), "changelog filelog httprangereader")
12 12 demandload(globals(), "localrepo manifest os urllib urllib2")
13 13
14 14 class rangereader(httprangereader.httprangereader):
15 15 def read(self, size=None):
16 16 try:
17 17 return httprangereader.httprangereader.read(self, size)
18 18 except urllib2.URLError, inst:
19 19 raise IOError(None, str(inst))
20 20
21 21 def opener(base):
22 22 """return a function that opens files over http"""
23 23 p = base
24 24 def o(path, mode="r"):
25 25 f = os.path.join(p, urllib.quote(path))
26 26 return rangereader(f)
27 27 return o
28 28
29 29 class statichttprepository(localrepo.localrepository):
30 30 def __init__(self, ui, path):
31 31 self.path = (path + "/.hg")
32 32 self.ui = ui
33 33 self.opener = opener(self.path)
34 self.manifest = manifest.manifest(self.opener)
35 self.changelog = changelog.changelog(self.opener)
34 self.manifest = manifest.manifest(self.opener, local=self.local())
35 self.changelog = changelog.changelog(self.opener, local=self.local())
36 36 self.tagscache = None
37 37 self.nodetagscache = None
38 self.encodepats = None
39 self.decodepats = None
38 40
39 41 def dev(self):
40 42 return -1
41 43
42 44 def local(self):
43 45 return False
General Comments 0
You need to be logged in to leave comments. Login now