##// END OF EJS Templates
Fix up a bunch of bugs in the new merge code...
mpm@selenic.com -
r65:d40cc5aa 0.4f default
parent child Browse files
Show More
@@ -1,380 +1,380
1 1 #!/usr/bin/env python
2 2 #
3 3 # mercurial - a minimal scalable distributed SCM
4 4 # v0.4e "sabina"
5 5 #
6 6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
7 7 #
8 8 # This software may be used and distributed according to the terms
9 9 # of the GNU General Public License, incorporated herein by reference.
10 10
11 11 # the psyco compiler makes commits a bit faster
12 12 # and makes changegroup merge about 20 times slower!
13 13 # try:
14 14 # import psyco
15 15 # psyco.full()
16 16 # except:
17 17 # pass
18 18
19 19 import sys, os, time
20 20 from mercurial import hg, mdiff, fancyopts
21 21
22 22 def help():
23 23 print """\
24 24 commands:
25 25
26 26 init create a new repository in this directory
27 27 branch <path> create a branch of <path> in this directory
28 28 merge <path> merge changes from <path> into local repository
29 29 checkout [changeset] checkout the latest or given changeset
30 30 status show new, missing, and changed files in working dir
31 31 add [files...] add the given files in the next commit
32 32 remove [files...] remove the given files in the next commit
33 33 addremove add all new files, delete all missing files
34 34 commit commit all changes to the repository
35 35 history show changeset history
36 36 log <file> show revision history of a single file
37 37 dump <file> [rev] dump the latest or given revision of a file
38 38 dumpmanifest [rev] dump the latest or given revision of the manifest
39 39 diff [files...] diff working directory (or selected files)
40 40 """
41 41
42 42 def filterfiles(list, files):
43 43 l = [ x for x in list if x in files ]
44 44
45 45 for f in files:
46 46 if f[-1] != os.sep: f += os.sep
47 47 l += [ x for x in list if x.startswith(f) ]
48 48 return l
49 49
50 50 def diff(files = None, node1 = None, node2 = None):
51 51 def date(c):
52 52 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
53 53
54 54 if node2:
55 55 change = repo.changelog.read(node2)
56 56 mmap2 = repo.manifest.read(change[0])
57 57 (c, a, d) = repo.diffrevs(node1, node2)
58 58 def read(f): return repo.file(f).read(mmap2[f])
59 59 date2 = date(change)
60 60 else:
61 61 date2 = time.asctime()
62 62 if not node1:
63 63 node1 = repo.current
64 64 (c, a, d) = repo.diffdir(repo.root, node1)
65 65 def read(f): return file(os.path.join(repo.root, f)).read()
66 66
67 67 change = repo.changelog.read(node1)
68 68 mmap = repo.manifest.read(change[0])
69 69 date1 = date(change)
70 70
71 71 if files:
72 72 (c, a, d) = map(lambda x: filterfiles(x, files), (c, a, d))
73 73
74 74 for f in c:
75 75 to = repo.file(f).read(mmap[f])
76 76 tn = read(f)
77 77 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
78 78 for f in a:
79 79 to = ""
80 80 tn = read(f)
81 81 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
82 82 for f in d:
83 83 to = repo.file(f).read(mmap[f])
84 84 tn = ""
85 85 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
86 86
87 87 options = {}
88 88 opts = [('v', 'verbose', None, 'verbose'),
89 89 ('d', 'debug', None, 'debug')]
90 90
91 91 args = fancyopts.fancyopts(sys.argv[1:], opts, options,
92 92 'hg [options] <command> [command options] [files]')
93 93
94 94 try:
95 95 cmd = args[0]
96 96 args = args[1:]
97 97 except:
98 98 cmd = ""
99 99
100 100 ui = hg.ui(options["verbose"], options["debug"])
101 101
102 102 if cmd == "init":
103 103 repo = hg.repository(ui, ".", create=1)
104 104 sys.exit(0)
105 105 elif cmd == "branch" or cmd == "clone":
106 106 os.system("cp -al %s/.hg .hg" % args[0])
107 107 sys.exit(0)
108 108 elif cmd == "help":
109 109 help()
110 110 sys.exit(0)
111 111 else:
112 112 try:
113 113 repo = hg.repository(ui=ui)
114 114 except:
115 115 print "Unable to open repository"
116 116 sys.exit(0)
117 117
118 118 if cmd == "checkout" or cmd == "co":
119 119 node = repo.changelog.tip()
120 120 if args:
121 121 node = repo.changelog.lookup(args[0])
122 122 repo.checkout(node)
123 123
124 124 elif cmd == "add":
125 125 repo.add(args)
126 126
127 127 elif cmd == "remove" or cmd == "rm" or cmd == "del" or cmd == "delete":
128 128 repo.remove(args)
129 129
130 130 elif cmd == "commit" or cmd == "checkin" or cmd == "ci":
131 131 if 1:
132 132 if len(args) > 0:
133 133 repo.commit(repo.current, args)
134 134 else:
135 135 repo.commit(repo.current)
136 136
137 137 elif cmd == "import" or cmd == "patch":
138 138 ioptions = {}
139 139 opts = [('p', 'strip', 1, 'path strip'),
140 140 ('b', 'base', "", 'base path'),
141 141 ('q', 'quiet', "", 'silence diff')
142 142 ]
143 143
144 144 args = fancyopts.fancyopts(args, opts, ioptions,
145 145 'hg import [options] <patch names>')
146 146 d = ioptions["base"]
147 147 strip = ioptions["strip"]
148 148 quiet = ioptions["quiet"] and "> /dev/null" or ""
149 149
150 150 for patch in args:
151 151 ui.status("applying %s\n" % patch)
152 152 pf = os.path.join(d, patch)
153 153
154 154 text = ""
155 155 for l in file(pf):
156 156 if l[:3] == "---": break
157 157 text += l
158 158
159 159 if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
160 160 raise "patch failed!"
161 161 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
162 162 files = filter(None, map(lambda x: x.rstrip(), f.read().splitlines()))
163 163 f.close()
164 164 repo.commit(repo.current, files, text)
165 165
166 166 elif cmd == "status":
167 167 (c, a, d) = repo.diffdir(repo.root, repo.current)
168 168 for f in c: print "C", f
169 169 for f in a: print "?", f
170 170 for f in d: print "R", f
171 171
172 172 elif cmd == "diff":
173 173 revs = []
174 174
175 175 if args:
176 176 doptions = {}
177 177 opts = [('r', 'revision', [], 'revision')]
178 178 args = fancyopts.fancyopts(args, opts, doptions,
179 179 'hg diff [options] [files]')
180 180 revs = map(lambda x: repo.changelog.lookup(x), doptions['revision'])
181 181
182 182 if len(revs) > 2:
183 183 print "too many revisions to diff"
184 184 sys.exit(1)
185 185
186 186 if os.getcwd() != repo.root:
187 187 relpath = os.getcwd()[len(repo.root) + 1: ]
188 188 if not args: args = [ relpath ]
189 189 else: args = [ os.path.join(relpath, x) for x in args ]
190 190
191 191 diff(args, *revs)
192 192
193 193 elif cmd == "export":
194 194 node = repo.changelog.lookup(args[0])
195 195 prev = repo.changelog.parents(node)[0]
196 196 diff(None, prev, node)
197 197
198 198 elif cmd == "debugchangegroup":
199 199 newer = repo.newer(map(repo.changelog.lookup, args))
200 200 for chunk in repo.changegroup(newer):
201 201 sys.stdout.write(chunk)
202 202
203 203 elif cmd == "debugaddchangegroup":
204 204 data = sys.stdin.read()
205 205 repo.addchangegroup(data)
206 206
207 207 elif cmd == "addremove":
208 208 (c, a, d) = repo.diffdir(repo.root, repo.current)
209 209 repo.add(a)
210 210 repo.remove(d)
211 211
212 212 elif cmd == "history":
213 213 for i in range(repo.changelog.count()):
214 214 n = repo.changelog.node(i)
215 215 changes = repo.changelog.read(n)
216 216 (p1, p2) = repo.changelog.parents(n)
217 217 (h, h1, h2) = map(hg.hex, (n, p1, p2))
218 218 (i1, i2) = map(repo.changelog.rev, (p1, p2))
219 219 print "rev: %4d:%s" % (i, h)
220 220 print "parents: %4d:%s" % (i1, h1)
221 221 if i2: print " %4d:%s" % (i2, h2)
222 222 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
223 223 hg.hex(changes[0]))
224 224 print "user:", changes[1]
225 225 print "date:", time.asctime(
226 226 time.localtime(float(changes[2].split(' ')[0])))
227 227 print "files:", " ".join(changes[3])
228 228 print "description:"
229 229 print changes[4]
230 230
231 231 elif cmd == "log":
232 232 if args:
233 233 r = repo.file(args[0])
234 234 for i in range(r.count()):
235 235 n = r.node(i)
236 236 (p1, p2) = r.parents(n)
237 237 (h, h1, h2) = map(hg.hex, (n, p1, p2))
238 238 (i1, i2) = map(r.rev, (p1, p2))
239 239 cr = r.linkrev(n)
240 240 cn = hg.hex(repo.changelog.node(cr))
241 241 print "rev: %4d:%s" % (i, h)
242 242 print "changeset: %4d:%s" % (cr, cn)
243 243 print "parents: %4d:%s" % (i1, h1)
244 244 if i2: print " %4d:%s" % (i2, h2)
245 245 else:
246 246 print "missing filename"
247 247
248 248 elif cmd == "dump":
249 249 if args:
250 250 r = repo.file(args[0])
251 251 n = r.tip()
252 252 if len(args) > 1: n = r.lookup(args[1])
253 253 sys.stdout.write(r.read(n))
254 254 else:
255 255 print "missing filename"
256 256
257 257 elif cmd == "dumpmanifest":
258 258 n = repo.manifest.tip()
259 259 if len(args) > 0:
260 260 n = repo.manifest.lookup(args[0])
261 261 m = repo.manifest.read(n)
262 262 files = m.keys()
263 263 files.sort()
264 264
265 265 for f in files:
266 266 print hg.hex(m[f]), f
267 267
268 268 elif cmd == "debughash":
269 269 f = repo.file(args[0])
270 270 print f.encodepath(args[0])
271 271
272 272 elif cmd == "debugindex":
273 273 r = hg.revlog(open, args[0], "")
274 274 print " rev offset length base linkrev"+\
275 275 " p1 p2 nodeid"
276 276 for i in range(r.count()):
277 277 e = r.index[i]
278 278 print "% 6d % 9d % 7d % 5d % 7d %s.. %s.. %s.." % (
279 279 i, e[0], e[1], e[2], e[3],
280 280 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
281 281
282 282 elif cmd == "merge":
283 283 if args:
284 284 other = hg.repository(ui, args[0])
285 print "retrieving changegroup"
285 print "requesting changegroup"
286 286 cg = repo.getchangegroup(other)
287 287 repo.addchangegroup(cg)
288 288 else:
289 289 print "missing source repository"
290 290
291 291 elif cmd == "debugoldmerge":
292 292 if args:
293 293 other = hg.repository(ui, args[0])
294 294 repo.merge(other)
295 295 else:
296 296 print "missing source repository"
297 297
298 298 elif cmd == "verify":
299 299 filelinkrevs = {}
300 300 filenodes = {}
301 301 manifestchangeset = {}
302 302 changesets = revisions = files = 0
303 303
304 304 print "checking changesets"
305 305 for i in range(repo.changelog.count()):
306 306 changesets += 1
307 307 n = repo.changelog.node(i)
308 308 changes = repo.changelog.read(n)
309 309 manifestchangeset[changes[0]] = n
310 310 for f in changes[3]:
311 311 revisions += 1
312 312 filelinkrevs.setdefault(f, []).append(i)
313 313
314 314 print "checking manifests"
315 315 for i in range(repo.manifest.count()):
316 316 n = repo.manifest.node(i)
317 317 ca = repo.changelog.node(repo.manifest.linkrev(n))
318 318 cc = manifestchangeset[n]
319 319 if ca != cc:
320 320 print "manifest %s points to %s, not %s" % \
321 321 (hg.hex(n), hg.hex(ca), hg.hex(cc))
322 322 m = repo.manifest.read(n)
323 323 for f, fn in m.items():
324 324 filenodes.setdefault(f, {})[fn] = 1
325 325
326 326 print "crosschecking files in changesets and manifests"
327 327 for f in filenodes:
328 328 if f not in filelinkrevs:
329 329 print "file %s in manifest but not in changesets"
330 330
331 331 for f in filelinkrevs:
332 332 if f not in filenodes:
333 333 print "file %s in changeset but not in manifest"
334 334
335 335 print "checking files"
336 336 for f in filenodes:
337 337 files += 1
338 338 fl = repo.file(f)
339 339 nodes = {"\0"*20: 1}
340 340 for i in range(fl.count()):
341 341 n = fl.node(i)
342 342
343 343 if n not in filenodes[f]:
344 344 print "%s:%s not in manifests" % (f, hg.hex(n))
345 345 else:
346 346 del filenodes[f][n]
347 347
348 348 flr = fl.linkrev(n)
349 349 if flr not in filelinkrevs[f]:
350 350 print "%s:%s points to unexpected changeset rev %d" \
351 351 % (f, hg.hex(n), fl.linkrev(n))
352 352 else:
353 353 filelinkrevs[f].remove(flr)
354 354
355 355 # verify contents
356 356 t = fl.read(n)
357 357
358 358 # verify parents
359 359 (p1, p2) = fl.parents(n)
360 360 if p1 not in nodes:
361 361 print "%s:%s unknown parent 1 %s" % (f, hg.hex(n), hg.hex(p1))
362 362 if p2 not in nodes:
363 363 print "file %s:%s unknown parent %s" % (f, hg.hex(n), hg.hex(p1))
364 364 nodes[n] = 1
365 365
366 366 # cross-check
367 367 for flr in filelinkrevs[f]:
368 368 print "changeset rev %d not in %s" % (flr, f)
369 369
370 370 for node in filenodes[f]:
371 371 print "node %s in manifests not in %s" % (hg.hex(n), f)
372 372
373 373
374 374 print "%d files, %d changesets, %d total revisions" % (files, changesets,
375 375 revisions)
376 376
377 377 else:
378 378 print "unknown command\n"
379 379 help()
380 380 sys.exit(1)
@@ -1,873 +1,893
1 1 # hg.py - repository classes 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 sys, struct, sha, socket, os, time, base64, re, urllib2
9 9 import urllib
10 10 from mercurial import byterange
11 11 from mercurial.transaction import *
12 12 from mercurial.revlog import *
13 13
14 14 class filelog(revlog):
15 15 def __init__(self, opener, path):
16 16 s = self.encodepath(path)
17 17 revlog.__init__(self, opener, os.path.join("data", s + "i"),
18 18 os.path.join("data", s))
19 19
20 20 def encodepath(self, path):
21 21 s = sha.sha(path).digest()
22 22 s = base64.encodestring(s)[:-3]
23 23 s = re.sub("\+", "%", s)
24 24 s = re.sub("/", "_", s)
25 25 return s
26 26
27 27 def read(self, node):
28 28 return self.revision(node)
29 29 def add(self, text, transaction, link, p1=None, p2=None):
30 30 return self.addrevision(text, transaction, link, p1, p2)
31 31
32 32 def resolvedag(self, old, new, transaction, link):
33 33 """resolve unmerged heads in our DAG"""
34 34 if old == new: return None
35 35 a = self.ancestor(old, new)
36 36 if old == a: return None
37 37 return self.merge3(old, new, a, transaction, link)
38 38
39 39 def merge3(self, my, other, base, transaction, link):
40 40 """perform a 3-way merge and append the result"""
41 41 def temp(prefix, node):
42 42 (fd, name) = tempfile.mkstemp(prefix)
43 43 f = os.fdopen(fd, "w")
44 44 f.write(self.revision(node))
45 45 f.close()
46 46 return name
47 47
48 48 a = temp("local", my)
49 49 b = temp("remote", other)
50 50 c = temp("parent", base)
51 51
52 52 cmd = os.environ["HGMERGE"]
53 53 r = os.system("%s %s %s %s" % (cmd, a, b, c))
54 54 if r:
55 55 raise "Merge failed, implement rollback!"
56 56
57 57 t = open(a).read()
58 58 os.unlink(a)
59 59 os.unlink(b)
60 60 os.unlink(c)
61 61 return self.addrevision(t, transaction, link, my, other)
62 62
63 63 def merge(self, other, transaction, linkseq, link):
64 64 """perform a merge and resolve resulting heads"""
65 65 (o, n) = self.mergedag(other, transaction, linkseq)
66 66 return self.resolvedag(o, n, transaction, link)
67 67
68 68 class manifest(revlog):
69 69 def __init__(self, opener):
70 70 self.mapcache = None
71 71 self.listcache = None
72 72 self.addlist = None
73 73 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
74 74
75 75 def read(self, node):
76 76 if self.mapcache and self.mapcache[0] == node:
77 77 return self.mapcache[1]
78 78 text = self.revision(node)
79 79 map = {}
80 80 self.listcache = (text, text.splitlines(1))
81 81 for l in self.listcache[1]:
82 82 (f, n) = l.split('\0')
83 83 map[f] = bin(n[:40])
84 84 self.mapcache = (node, map)
85 85 return map
86 86
87 87 def diff(self, a, b):
88 88 # this is sneaky, as we're not actually using a and b
89 89 if self.listcache and len(self.listcache[0]) == len(a):
90 90 return mdiff.diff(self.listcache[1], self.addlist, 1)
91 91 else:
92 92 return mdiff.textdiff(a, b)
93 93
94 94 def add(self, map, transaction, link, p1=None, p2=None):
95 95 files = map.keys()
96 96 files.sort()
97 97
98 98 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
99 99 text = "".join(self.addlist)
100 100
101 101 n = self.addrevision(text, transaction, link, p1, p2)
102 102 self.mapcache = (n, map)
103 103 self.listcache = (text, self.addlist)
104 104
105 105 return n
106 106
107 107 class changelog(revlog):
108 108 def __init__(self, opener):
109 109 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
110 110
111 111 def extract(self, text):
112 112 if not text:
113 113 return (nullid, "", "0", [], "")
114 114 last = text.index("\n\n")
115 115 desc = text[last + 2:]
116 116 l = text[:last].splitlines()
117 117 manifest = bin(l[0])
118 118 user = l[1]
119 119 date = l[2]
120 120 files = l[3:]
121 121 return (manifest, user, date, files, desc)
122 122
123 123 def read(self, node):
124 124 return self.extract(self.revision(node))
125 125
126 126 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
127 127 user = (os.environ.get("HGUSER") or
128 128 os.environ.get("EMAIL") or
129 129 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
130 130 date = "%d %d" % (time.time(), time.timezone)
131 131 list.sort()
132 132 l = [hex(manifest), user, date] + list + ["", desc]
133 133 text = "\n".join(l)
134 134 return self.addrevision(text, transaction, self.count(), p1, p2)
135 135
136 136 def merge3(self, my, other, base):
137 137 pass
138 138
139 139 class dircache:
140 140 def __init__(self, opener, ui):
141 141 self.opener = opener
142 142 self.dirty = 0
143 143 self.ui = ui
144 144 self.map = None
145 145 def __del__(self):
146 146 if self.dirty: self.write()
147 147 def __getitem__(self, key):
148 148 try:
149 149 return self.map[key]
150 150 except TypeError:
151 151 self.read()
152 152 return self[key]
153 153
154 154 def read(self):
155 155 if self.map is not None: return self.map
156 156
157 157 self.map = {}
158 158 try:
159 159 st = self.opener("dircache").read()
160 160 except: return
161 161
162 162 pos = 0
163 163 while pos < len(st):
164 164 e = struct.unpack(">llll", st[pos:pos+16])
165 165 l = e[3]
166 166 pos += 16
167 167 f = st[pos:pos + l]
168 168 self.map[f] = e[:3]
169 169 pos += l
170 170
171 171 def update(self, files):
172 172 if not files: return
173 173 self.read()
174 174 self.dirty = 1
175 175 for f in files:
176 176 try:
177 177 s = os.stat(f)
178 178 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
179 179 except IOError:
180 180 self.remove(f)
181 181
182 182 def taint(self, files):
183 183 if not files: return
184 184 self.read()
185 185 self.dirty = 1
186 186 for f in files:
187 187 self.map[f] = (0, -1, 0)
188 188
189 189 def remove(self, files):
190 190 if not files: return
191 191 self.read()
192 192 self.dirty = 1
193 193 for f in files:
194 194 try:
195 195 del self.map[f]
196 196 except KeyError:
197 197 self.ui.warn("Not in dircache: %s\n" % f)
198 198 pass
199 199
200 200 def clear(self):
201 201 self.map = {}
202 202 self.dirty = 1
203 203
204 204 def write(self):
205 205 st = self.opener("dircache", "w")
206 206 for f, e in self.map.items():
207 207 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
208 208 st.write(e + f)
209 209 self.dirty = 0
210 210
211 211 def copy(self):
212 212 self.read()
213 213 return self.map.copy()
214 214
215 215 # used to avoid circular references so destructors work
216 216 def opener(base):
217 217 p = base
218 218 def o(path, mode="r"):
219 219 if p[:7] == "http://":
220 220 f = os.path.join(p, urllib.quote(path))
221 221 return httprangereader(f)
222 222
223 223 f = os.path.join(p, path)
224 224
225 225 if mode != "r" and os.path.isfile(f):
226 226 s = os.stat(f)
227 227 if s.st_nlink > 1:
228 228 file(f + ".tmp", "w").write(file(f).read())
229 229 os.rename(f+".tmp", f)
230 230
231 231 return file(f, mode)
232 232
233 233 return o
234 234
235 235 class localrepository:
236 236 def __init__(self, ui, path=None, create=0):
237 237 self.remote = 0
238 238 if path and path[:7] == "http://":
239 239 self.remote = 1
240 240 self.path = path
241 241 else:
242 242 if not path:
243 243 p = os.getcwd()
244 244 while not os.path.isdir(os.path.join(p, ".hg")):
245 245 p = os.path.dirname(p)
246 246 if p == "/": raise "No repo found"
247 247 path = p
248 248 self.path = os.path.join(path, ".hg")
249 249
250 250 self.root = path
251 251 self.ui = ui
252 252
253 253 if create:
254 254 os.mkdir(self.path)
255 255 os.mkdir(self.join("data"))
256 256
257 257 self.opener = opener(self.path)
258 258 self.manifest = manifest(self.opener)
259 259 self.changelog = changelog(self.opener)
260 260 self.ignorelist = None
261 261
262 262 if not self.remote:
263 263 self.dircache = dircache(self.opener, ui)
264 264 try:
265 265 self.current = bin(self.opener("current").read())
266 266 except IOError:
267 267 self.current = None
268 268
269 269 def setcurrent(self, node):
270 270 self.current = node
271 271 self.opener("current", "w").write(hex(node))
272 272
273 273 def ignore(self, f):
274 274 if self.ignorelist is None:
275 275 self.ignorelist = []
276 276 try:
277 277 l = open(os.path.join(self.root, ".hgignore")).readlines()
278 278 for pat in l:
279 279 if pat != "\n":
280 280 self.ignorelist.append(re.compile(pat[:-1]))
281 281 except IOError: pass
282 282 for pat in self.ignorelist:
283 283 if pat.search(f): return True
284 284 return False
285 285
286 286 def join(self, f):
287 287 return os.path.join(self.path, f)
288 288
289 289 def file(self, f):
290 290 return filelog(self.opener, f)
291 291
292 292 def transaction(self):
293 293 return transaction(self.opener, self.join("journal"))
294 294
295 295 def merge(self, other):
296 296 tr = self.transaction()
297 297 changed = {}
298 298 new = {}
299 299 seqrev = self.changelog.count()
300 300 # some magic to allow fiddling in nested scope
301 301 nextrev = [seqrev]
302 302
303 303 # helpers for back-linking file revisions to local changeset
304 304 # revisions so we can immediately get to changeset from annotate
305 305 def accumulate(text):
306 306 # track which files are added in which changeset and the
307 307 # corresponding _local_ changeset revision
308 308 files = self.changelog.extract(text)[3]
309 309 for f in files:
310 310 changed.setdefault(f, []).append(nextrev[0])
311 311 nextrev[0] += 1
312 312
313 313 def seq(start):
314 314 while 1:
315 315 yield start
316 316 start += 1
317 317
318 318 def lseq(l):
319 319 for r in l:
320 320 yield r
321 321
322 322 # begin the import/merge of changesets
323 323 self.ui.status("merging new changesets\n")
324 324 (co, cn) = self.changelog.mergedag(other.changelog, tr,
325 325 seq(seqrev), accumulate)
326 326 resolverev = self.changelog.count()
327 327
328 328 # is there anything to do?
329 329 if co == cn:
330 330 tr.close()
331 331 return
332 332
333 333 # do we need to resolve?
334 334 simple = (co == self.changelog.ancestor(co, cn))
335 335
336 336 # merge all files changed by the changesets,
337 337 # keeping track of the new tips
338 338 changelist = changed.keys()
339 339 changelist.sort()
340 340 for f in changelist:
341 341 sys.stdout.write(".")
342 342 sys.stdout.flush()
343 343 r = self.file(f)
344 344 node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev)
345 345 if node:
346 346 new[f] = node
347 347 sys.stdout.write("\n")
348 348
349 349 # begin the merge of the manifest
350 350 self.ui.status("merging manifests\n")
351 351 (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev))
352 352
353 353 # For simple merges, we don't need to resolve manifests or changesets
354 354 if simple:
355 355 tr.close()
356 356 return
357 357
358 358 ma = self.manifest.ancestor(mm, mo)
359 359
360 360 # resolve the manifest to point to all the merged files
361 361 self.ui.status("resolving manifests\n")
362 362 mmap = self.manifest.read(mm) # mine
363 363 omap = self.manifest.read(mo) # other
364 364 amap = self.manifest.read(ma) # ancestor
365 365 nmap = {}
366 366
367 367 for f, mid in mmap.iteritems():
368 368 if f in omap:
369 369 if mid != omap[f]:
370 370 nmap[f] = new.get(f, mid) # use merged version
371 371 else:
372 372 nmap[f] = new.get(f, mid) # they're the same
373 373 del omap[f]
374 374 elif f in amap:
375 375 if mid != amap[f]:
376 376 pass # we should prompt here
377 377 else:
378 378 pass # other deleted it
379 379 else:
380 380 nmap[f] = new.get(f, mid) # we created it
381 381
382 382 del mmap
383 383
384 384 for f, oid in omap.iteritems():
385 385 if f in amap:
386 386 if oid != amap[f]:
387 387 pass # this is the nasty case, we should prompt
388 388 else:
389 389 pass # probably safe
390 390 else:
391 391 nmap[f] = new.get(f, oid) # remote created it
392 392
393 393 del omap
394 394 del amap
395 395
396 396 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
397 397
398 398 # Now all files and manifests are merged, we add the changed files
399 399 # and manifest id to the changelog
400 400 self.ui.status("committing merge changeset\n")
401 401 new = new.keys()
402 402 new.sort()
403 403 if co == cn: cn = -1
404 404
405 405 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
406 406 edittext = self.ui.edit(edittext)
407 407 n = self.changelog.add(node, new, edittext, tr, co, cn)
408 408
409 409 tr.close()
410 410
411 411 def commit(self, parent, update = None, text = ""):
412 412 tr = self.transaction()
413 413
414 414 try:
415 415 remove = [ l[:-1] for l in self.opener("to-remove") ]
416 416 os.unlink(self.join("to-remove"))
417 417
418 418 except IOError:
419 419 remove = []
420 420
421 421 if update == None:
422 422 update = self.diffdir(self.root, parent)[0]
423 423
424 424 # check in files
425 425 new = {}
426 426 linkrev = self.changelog.count()
427 427 for f in update:
428 428 try:
429 429 t = file(f).read()
430 430 except IOError:
431 431 remove.append(f)
432 432 continue
433 433 r = self.file(f)
434 434 new[f] = r.add(t, tr, linkrev)
435 435
436 436 # update manifest
437 437 mmap = self.manifest.read(self.manifest.tip())
438 438 mmap.update(new)
439 439 for f in remove:
440 440 del mmap[f]
441 441 mnode = self.manifest.add(mmap, tr, linkrev)
442 442
443 443 # add changeset
444 444 new = new.keys()
445 445 new.sort()
446 446
447 447 edittext = text + "\n"+"".join(["HG: changed %s\n" % f for f in new])
448 448 edittext += "".join(["HG: removed %s\n" % f for f in remove])
449 449 edittext = self.ui.edit(edittext)
450 450
451 451 n = self.changelog.add(mnode, new, edittext, tr)
452 452 tr.close()
453 453
454 454 self.setcurrent(n)
455 455 self.dircache.update(new)
456 456 self.dircache.remove(remove)
457 457
458 458 def checkdir(self, path):
459 459 d = os.path.dirname(path)
460 460 if not d: return
461 461 if not os.path.isdir(d):
462 462 self.checkdir(d)
463 463 os.mkdir(d)
464 464
465 465 def checkout(self, node):
466 466 # checkout is really dumb at the moment
467 467 # it ought to basically merge
468 468 change = self.changelog.read(node)
469 469 mmap = self.manifest.read(change[0])
470 470
471 471 l = mmap.keys()
472 472 l.sort()
473 473 stats = []
474 474 for f in l:
475 475 r = self.file(f)
476 476 t = r.revision(mmap[f])
477 477 try:
478 478 file(f, "w").write(t)
479 479 except:
480 480 self.checkdir(f)
481 481 file(f, "w").write(t)
482 482
483 483 self.setcurrent(node)
484 484 self.dircache.clear()
485 485 self.dircache.update(l)
486 486
487 487 def diffdir(self, path, changeset):
488 488 changed = []
489 489 mf = {}
490 490 added = []
491 491
492 492 if changeset:
493 493 change = self.changelog.read(changeset)
494 494 mf = self.manifest.read(change[0])
495 495
496 496 if changeset == self.current:
497 497 dc = self.dircache.copy()
498 498 else:
499 499 dc = dict.fromkeys(mf)
500 500
501 501 def fcmp(fn):
502 502 t1 = file(os.path.join(self.root, fn)).read()
503 503 t2 = self.file(fn).revision(mf[fn])
504 504 return cmp(t1, t2)
505 505
506 506 for dir, subdirs, files in os.walk(self.root):
507 507 d = dir[len(self.root)+1:]
508 508 if ".hg" in subdirs: subdirs.remove(".hg")
509 509
510 510 for f in files:
511 511 fn = os.path.join(d, f)
512 512 try: s = os.stat(os.path.join(self.root, fn))
513 513 except: continue
514 514 if fn in dc:
515 515 c = dc[fn]
516 516 del dc[fn]
517 517 if not c:
518 518 if fcmp(fn):
519 519 changed.append(fn)
520 520 elif c[1] != s.st_size:
521 521 changed.append(fn)
522 522 elif c[0] != s.st_mode or c[2] != s.st_mtime:
523 523 if fcmp(fn):
524 524 changed.append(fn)
525 525 else:
526 526 if self.ignore(fn): continue
527 527 added.append(fn)
528 528
529 529 deleted = dc.keys()
530 530 deleted.sort()
531 531
532 532 return (changed, added, deleted)
533 533
534 534 def diffrevs(self, node1, node2):
535 535 changed, added = [], []
536 536
537 537 change = self.changelog.read(node1)
538 538 mf1 = self.manifest.read(change[0])
539 539 change = self.changelog.read(node2)
540 540 mf2 = self.manifest.read(change[0])
541 541
542 542 for fn in mf2:
543 543 if mf1.has_key(fn):
544 544 if mf1[fn] != mf2[fn]:
545 545 changed.append(fn)
546 546 del mf1[fn]
547 547 else:
548 548 added.append(fn)
549 549
550 550 deleted = mf1.keys()
551 551 deleted.sort()
552 552
553 553 return (changed, added, deleted)
554 554
555 555 def add(self, list):
556 556 self.dircache.taint(list)
557 557
558 558 def remove(self, list):
559 559 dl = self.opener("to-remove", "a")
560 560 for f in list:
561 561 dl.write(f + "\n")
562 562
563 563 def branches(self, nodes):
564 564 if not nodes: nodes = [self.changelog.tip()]
565 565 b = []
566 566 for n in nodes:
567 567 t = n
568 568 while n:
569 569 p = self.changelog.parents(n)
570 570 if p[1] != nullid or p[0] == nullid:
571 571 b.append((t, n, p[0], p[1]))
572 572 break
573 573 n = p[0]
574 574 return b
575 575
576 576 def between(self, pairs):
577 577 r = []
578 578
579 579 for top, bottom in pairs:
580 580 n, l, i = top, [], 0
581 581 f = 1
582 582
583 583 while n != bottom:
584 584 p = self.changelog.parents(n)[0]
585 585 if i == f:
586 586 l.append(n)
587 587 f = f * 2
588 588 n = p
589 589 i += 1
590 590
591 591 r.append(l)
592 592
593 593 return r
594 594
595 595 def newer(self, nodes):
596 596 m = {}
597 597 nl = []
598 598 cl = self.changelog
599 599 t = l = cl.count()
600 600 for n in nodes:
601 601 l = min(l, cl.rev(n))
602 602 for p in cl.parents(n):
603 603 m[p] = 1
604 604
605 605 for i in xrange(l, t):
606 606 n = cl.node(i)
607 607 for p in cl.parents(n):
608 608 if p in m and n not in m:
609 609 m[n] = 1
610 610 nl.append(n)
611 611
612 612 return nl
613 613
614 614 def getchangegroup(self, remote):
615 615 tip = remote.branches([])[0]
616 cl = self.changelog
616 m = self.changelog.nodemap
617 617 unknown = [tip]
618 618 search = []
619 619 fetch = []
620 620
621 if tip[0] == self.changelog.tip():
621 if tip[0] in m:
622 622 return None
623 623
624 624 while unknown:
625 625 n = unknown.pop(0)
626 626 if n == nullid: break
627 if n[1] and cl.nodemap.has_key(n[1]): # do we know the base?
627 if n[1] and n[1] in m: # do we know the base?
628 628 search.append(n) # schedule branch range for scanning
629 629 else:
630 630 for b in remote.branches([n[2], n[3]]):
631 if cl.nodemap.has_key(b[0]):
631 if b[0] in m:
632 if n[1] not in fetch:
632 633 fetch.append(n[1]) # earliest unknown
633 634 else:
634 635 unknown.append(b)
635 636
636 637 while search:
637 638 n = search.pop(0)
638 639 l = remote.between([(n[0], n[1])])[0]
639 640 p = n[0]
640 641 f = 1
641 642 for i in l + [n[1]]:
642 if self.changelog.nodemap.has_key(i):
643 if i in m:
643 644 if f <= 4:
644 645 fetch.append(p)
645 646 else:
646 647 search.append((p, i))
648 break
647 649 p, f = i, f * 2
648 650
651 for f in fetch:
652 if f in m:
653 raise "already have", hex(f[:4])
654
649 655 return remote.changegroup(fetch)
650 656
651 657 def changegroup(self, basenodes):
652 658 nodes = self.newer(basenodes)
653 659
654 660 # construct the link map
655 661 linkmap = {}
656 662 for n in nodes:
657 663 linkmap[self.changelog.rev(n)] = n
658 664
659 665 # construct a list of all changed files
660 666 changed = {}
661 667 for n in nodes:
662 668 c = self.changelog.read(n)
663 669 for f in c[3]:
664 670 changed[f] = 1
665 671 changed = changed.keys()
666 672 changed.sort()
667 673
668 674 # the changegroup is changesets + manifests + all file revs
669 675 revs = [ self.changelog.rev(n) for n in nodes ]
670 676
671 677 yield self.changelog.group(linkmap)
672 678 yield self.manifest.group(linkmap)
673 679
674 680 for f in changed:
675 681 g = self.file(f).group(linkmap)
676 682 if not g: raise "couldn't find change to %s" % f
677 683 l = struct.pack(">l", len(f))
678 684 yield "".join([l, f, g])
679 685
680 def addchangegroup(self, data):
681 def getlen(data, pos):
682 return struct.unpack(">l", data[pos:pos + 4])[0]
686 def addchangegroup(self, generator):
687 class genread:
688 def __init__(self, generator):
689 self.g = generator
690 self.buf = ""
691 def read(self, l):
692 while l > len(self.buf):
693 try:
694 self.buf += self.g.next()
695 except StopIteration:
696 break
697 d, self.buf = self.buf[:l], self.buf[l:]
698 return d
683 699
684 if not data: return
700 if not generator: return
701 source = genread(generator)
702
703 def getchunk(add = 0):
704 d = source.read(4)
705 if not d: return ""
706 l = struct.unpack(">l", d)[0]
707 return source.read(l - 4 + add)
685 708
686 709 tr = self.transaction()
687 710 simple = True
688 711
689 712 print "merging changesets"
690 713 # pull off the changeset group
691 l = getlen(data, 0)
692 csg = data[0:l]
693 pos = l
714 csg = getchunk()
694 715 co = self.changelog.tip()
695 716 cn = self.changelog.addgroup(csg, lambda x: self.changelog.count(), tr)
696 717
697 718 print "merging manifests"
698 719 # pull off the manifest group
699 l = getlen(data, pos)
700 mfg = data[pos: pos + l]
701 pos += l
720 mfg = getchunk()
702 721 mo = self.manifest.tip()
703 mn = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
722 mm = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
704 723
705 724 # do we need a resolve?
706 725 if self.changelog.ancestor(co, cn) != co:
707 print "NEED RESOLVE"
708 726 simple = False
709 727 resolverev = self.changelog.count()
710 728
711 729 # process the files
712 730 print "merging files"
713 731 new = {}
714 while pos < len(data):
715 l = getlen(data, pos)
716 pos += 4
717 f = data[pos:pos + l]
718 pos += l
719
720 l = getlen(data, pos)
721 fg = data[pos: pos + l]
722 pos += l
732 while 1:
733 f = getchunk(4)
734 if not f: break
735 fg = getchunk()
723 736
724 737 fl = self.file(f)
725 738 o = fl.tip()
726 739 n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
727 740 if not simple:
728 new[fl] = fl.resolvedag(o, n, tr, resolverev)
741 nn = fl.resolvedag(o, n, tr, resolverev)
742 if nn: new[f] = nn
729 743
730 744 # For simple merges, we don't need to resolve manifests or changesets
731 745 if simple:
732 746 tr.close()
733 747 return
734 748
735 749 # resolve the manifest to point to all the merged files
736 750 self.ui.status("resolving manifests\n")
737 751 ma = self.manifest.ancestor(mm, mo)
738 752 mmap = self.manifest.read(mm) # mine
739 753 omap = self.manifest.read(mo) # other
740 754 amap = self.manifest.read(ma) # ancestor
741 755 nmap = {}
742 756
743 757 for f, mid in mmap.iteritems():
744 758 if f in omap:
745 759 if mid != omap[f]:
746 760 nmap[f] = new.get(f, mid) # use merged version
747 761 else:
748 762 nmap[f] = new.get(f, mid) # they're the same
749 763 del omap[f]
750 764 elif f in amap:
751 765 if mid != amap[f]:
752 766 pass # we should prompt here
753 767 else:
754 768 pass # other deleted it
755 769 else:
756 770 nmap[f] = new.get(f, mid) # we created it
757 771
758 772 del mmap
759 773
760 774 for f, oid in omap.iteritems():
761 775 if f in amap:
762 776 if oid != amap[f]:
763 777 pass # this is the nasty case, we should prompt
764 778 else:
765 779 pass # probably safe
766 780 else:
767 781 nmap[f] = new.get(f, oid) # remote created it
768 782
769 783 del omap
770 784 del amap
771 785
772 786 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
773 787
774 788 # Now all files and manifests are merged, we add the changed files
775 789 # and manifest id to the changelog
776 790 self.ui.status("committing merge changeset\n")
777 791 new = new.keys()
778 792 new.sort()
779 793 if co == cn: cn = -1
780 794
781 795 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
782 796 edittext = self.ui.edit(edittext)
783 797 n = self.changelog.add(node, new, edittext, tr, co, cn)
784 798
785 799 tr.close()
786 800
787 801 class remoterepository:
788 802 def __init__(self, ui, path):
789 803 self.url = path.replace("hg://", "http://", 1)
790 804 self.ui = ui
791 805
792 806 def do_cmd(self, cmd, **args):
793 807 q = {"cmd": cmd}
794 808 q.update(args)
795 809 qs = urllib.urlencode(q)
796 810 cu = "%s?%s" % (self.url, qs)
797 return urllib.urlopen(cu).read()
811 return urllib.urlopen(cu)
798 812
799 813 def branches(self, nodes):
800 814 n = " ".join(map(hex, nodes))
801 d = self.do_cmd("branches", nodes=n)
815 d = self.do_cmd("branches", nodes=n).read()
802 816 br = [ map(bin, b.split(" ")) for b in d.splitlines() ]
803 817 return br
804 818
805 819 def between(self, pairs):
806 820 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
807 d = self.do_cmd("between", pairs=n)
821 d = self.do_cmd("between", pairs=n).read()
808 822 p = [ map(bin, l.split(" ")) for l in d.splitlines() ]
809 823 return p
810 824
811 825 def changegroup(self, nodes):
812 826 n = " ".join(map(hex, nodes))
813 d = self.do_cmd("changegroup", roots=n)
814 return zlib.decompress(d)
827 zd = zlib.decompressobj()
828 f = self.do_cmd("changegroup", roots=n)
829 while 1:
830 d = f.read(4096)
831 if not d:
832 yield zd.flush()
833 break
834 yield zd.decompress(d)
815 835
816 836 def repository(ui, path=None, create=0):
817 837 if path and path[:5] == "hg://":
818 838 return remoterepository(ui, path)
819 839 else:
820 840 return localrepository(ui, path, create)
821 841
822 842 class ui:
823 843 def __init__(self, verbose=False, debug=False):
824 844 self.verbose = verbose
825 845 def write(self, *args):
826 846 for a in args:
827 847 sys.stdout.write(str(a))
828 848 def prompt(self, msg, pat):
829 849 while 1:
830 850 sys.stdout.write(msg)
831 851 r = sys.stdin.readline()[:-1]
832 852 if re.match(pat, r):
833 853 return r
834 854 def status(self, *msg):
835 855 self.write(*msg)
836 856 def warn(self, msg):
837 857 self.write(*msg)
838 858 def note(self, msg):
839 859 if self.verbose: self.write(*msg)
840 860 def debug(self, msg):
841 861 if self.debug: self.write(*msg)
842 862 def edit(self, text):
843 863 (fd, name) = tempfile.mkstemp("hg")
844 864 f = os.fdopen(fd, "w")
845 865 f.write(text)
846 866 f.close()
847 867
848 868 editor = os.environ.get("EDITOR", "vi")
849 869 r = os.system("%s %s" % (editor, name))
850 870 if r:
851 871 raise "Edit failed!"
852 872
853 873 t = open(name).read()
854 874 t = re.sub("(?m)^HG:.*\n", "", t)
855 875
856 876 return t
857 877
858 878
859 879 class httprangereader:
860 880 def __init__(self, url):
861 881 self.url = url
862 882 self.pos = 0
863 883 def seek(self, pos):
864 884 self.pos = pos
865 885 def read(self, bytes=None):
866 886 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
867 887 urllib2.install_opener(opener)
868 888 req = urllib2.Request(self.url)
869 889 end = ''
870 890 if bytes: end = self.pos + bytes
871 891 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
872 892 f = urllib2.urlopen(req)
873 893 return f.read()
@@ -1,414 +1,412
1 1 # revlog.py - storage back-end for mercurial
2 2 #
3 3 # This provides efficient delta storage with O(1) retrieve and append
4 4 # and O(changes) merge between branches
5 5 #
6 6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
7 7 #
8 8 # This software may be used and distributed according to the terms
9 9 # of the GNU General Public License, incorporated herein by reference.
10 10
11 11 import zlib, struct, sha, os, tempfile, binascii
12 12 from mercurial import mdiff
13 13
14 14 def hex(node): return binascii.hexlify(node)
15 15 def bin(node): return binascii.unhexlify(node)
16 16
17 17 def compress(text):
18 18 return zlib.compress(text)
19 19
20 20 def decompress(bin):
21 21 return zlib.decompress(bin)
22 22
23 23 def hash(text, p1, p2):
24 24 l = [p1, p2]
25 25 l.sort()
26 26 return sha.sha(l[0] + l[1] + text).digest()
27 27
28 28 nullid = "\0" * 20
29 29 indexformat = ">4l20s20s20s"
30 30
31 31 class revlog:
32 32 def __init__(self, opener, indexfile, datafile):
33 33 self.indexfile = indexfile
34 34 self.datafile = datafile
35 35 self.index = []
36 36 self.opener = opener
37 37 self.cache = None
38 38 self.nodemap = {nullid: -1}
39 39 # read the whole index for now, handle on-demand later
40 40 try:
41 41 n = 0
42 42 i = self.opener(self.indexfile).read()
43 43 s = struct.calcsize(indexformat)
44 44 for f in range(0, len(i), s):
45 45 # offset, size, base, linkrev, p1, p2, nodeid
46 46 e = struct.unpack(indexformat, i[f:f + s])
47 47 self.nodemap[e[6]] = n
48 48 self.index.append(e)
49 49 n += 1
50 50 except IOError: pass
51 51
52 52 def tip(self): return self.node(len(self.index) - 1)
53 53 def count(self): return len(self.index)
54 54 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
55 55 def rev(self, node): return self.nodemap[node]
56 56 def linkrev(self, node): return self.index[self.nodemap[node]][3]
57 57 def parents(self, node):
58 58 if node == nullid: return (nullid, nullid)
59 59 return self.index[self.nodemap[node]][4:6]
60 60
61 61 def start(self, rev): return self.index[rev][0]
62 62 def length(self, rev): return self.index[rev][1]
63 63 def end(self, rev): return self.start(rev) + self.length(rev)
64 64 def base(self, rev): return self.index[rev][2]
65 65
66 66 def lookup(self, id):
67 67 try:
68 68 rev = int(id)
69 69 return self.node(rev)
70 70 except ValueError:
71 71 c = []
72 72 for n in self.nodemap:
73 73 if id in hex(n):
74 74 c.append(n)
75 75 if len(c) > 1: raise KeyError("Ambiguous identifier")
76 76 if len(c) < 1: raise KeyError
77 77 return c[0]
78 78
79 79 return None
80 80
81 81 def revisions(self, list):
82 82 # this can be optimized to do spans, etc
83 83 # be stupid for now
84 84 for node in list:
85 85 yield self.revision(node)
86 86
87 87 def diff(self, a, b):
88 88 return mdiff.textdiff(a, b)
89 89
90 90 def patch(self, text, patch):
91 91 return mdiff.patch(text, patch)
92 92
93 93 def revision(self, node):
94 94 if node == nullid: return ""
95 95 if self.cache and self.cache[0] == node: return self.cache[2]
96 96
97 97 text = None
98 98 rev = self.rev(node)
99 99 base = self.base(rev)
100 100 start = self.start(base)
101 101 end = self.end(rev)
102 102
103 103 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
104 104 base = self.cache[1]
105 105 start = self.start(base + 1)
106 106 text = self.cache[2]
107 107 last = 0
108 108
109 109 f = self.opener(self.datafile)
110 110 f.seek(start)
111 111 data = f.read(end - start)
112 112
113 113 if not text:
114 114 last = self.length(base)
115 115 text = decompress(data[:last])
116 116
117 117 for r in xrange(base + 1, rev + 1):
118 118 s = self.length(r)
119 119 b = decompress(data[last:last + s])
120 120 text = self.patch(text, b)
121 121 last = last + s
122 122
123 123 (p1, p2) = self.parents(node)
124 124 if node != hash(text, p1, p2):
125 125 raise "integrity check failed on %s:%d" % (self.datafile, rev)
126 126
127 127 self.cache = (node, rev, text)
128 128 return text
129 129
130 130 def addrevision(self, text, transaction, link, p1=None, p2=None):
131 131 if text is None: text = ""
132 132 if p1 is None: p1 = self.tip()
133 133 if p2 is None: p2 = nullid
134 134
135 135 node = hash(text, p1, p2)
136 136
137 137 n = self.count()
138 138 t = n - 1
139 139
140 140 if n:
141 141 base = self.base(t)
142 142 start = self.start(base)
143 143 end = self.end(t)
144 144 prev = self.revision(self.tip())
145 145 data = compress(self.diff(prev, text))
146 146 dist = end - start + len(data)
147 147
148 148 # full versions are inserted when the needed deltas
149 149 # become comparable to the uncompressed text
150 150 if not n or dist > len(text) * 2:
151 151 data = compress(text)
152 152 base = n
153 153 else:
154 154 base = self.base(t)
155 155
156 156 offset = 0
157 157 if t >= 0:
158 158 offset = self.end(t)
159 159
160 160 e = (offset, len(data), base, link, p1, p2, node)
161 161
162 162 self.index.append(e)
163 163 self.nodemap[node] = n
164 164 entry = struct.pack(indexformat, *e)
165 165
166 166 transaction.add(self.datafile, e[0])
167 167 self.opener(self.datafile, "a").write(data)
168 168 transaction.add(self.indexfile, n * len(entry))
169 169 self.opener(self.indexfile, "a").write(entry)
170 170
171 171 self.cache = (node, n, text)
172 172 return node
173 173
174 174 def ancestor(self, a, b):
175 175 def expand(list, map):
176 176 a = []
177 177 while list:
178 178 n = list.pop(0)
179 179 map[n] = 1
180 180 yield n
181 181 for p in self.parents(n):
182 182 if p != nullid and p not in map:
183 183 list.append(p)
184 184 yield nullid
185 185
186 186 amap = {}
187 187 bmap = {}
188 188 ag = expand([a], amap)
189 189 bg = expand([b], bmap)
190 190 adone = bdone = 0
191 191
192 192 while not adone or not bdone:
193 193 if not adone:
194 194 an = ag.next()
195 195 if an == nullid:
196 196 adone = 1
197 197 elif an in bmap:
198 198 return an
199 199 if not bdone:
200 200 bn = bg.next()
201 201 if bn == nullid:
202 202 bdone = 1
203 203 elif bn in amap:
204 204 return bn
205 205
206 206 return nullid
207 207
208 208 def mergedag(self, other, transaction, linkseq, accumulate = None):
209 209 """combine the nodes from other's DAG into ours"""
210 210 old = self.tip()
211 211 i = self.count()
212 212 l = []
213 213
214 214 # merge the other revision log into our DAG
215 215 for r in range(other.count()):
216 216 id = other.node(r)
217 217 if id not in self.nodemap:
218 218 (xn, yn) = other.parents(id)
219 219 l.append((id, xn, yn))
220 220 self.nodemap[id] = i
221 221 i += 1
222 222
223 223 # merge node date for new nodes
224 224 r = other.revisions([e[0] for e in l])
225 225 for e in l:
226 226 t = r.next()
227 227 if accumulate: accumulate(t)
228 228 self.addrevision(t, transaction, linkseq.next(), e[1], e[2])
229 229
230 230 # return the unmerged heads for later resolving
231 231 return (old, self.tip())
232 232
233 233 def group(self, linkmap):
234 234 # given a list of changeset revs, return a set of deltas and
235 235 # metadata corresponding to nodes the first delta is
236 236 # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
237 237 # have this parent as it has all history before these
238 238 # changesets. parent is parent[0]
239 239
240 240 revs = []
241 241 needed = {}
242 242
243 243 # find file nodes/revs that match changeset revs
244 244 for i in xrange(0, self.count()):
245 245 if self.index[i][3] in linkmap:
246 246 revs.append(i)
247 247 needed[i] = 1
248 248
249 249 # if we don't have any revisions touched by these changesets, bail
250 250 if not revs: return struct.pack(">l", 0)
251 251
252 252 # add the parent of the first rev
253 253 p = self.parents(self.node(revs[0]))[0]
254 254 revs.insert(0, self.rev(p))
255 255
256 256 # for each delta that isn't contiguous in the log, we need to
257 257 # reconstruct the base, reconstruct the result, and then
258 258 # calculate the delta. We also need to do this where we've
259 259 # stored a full version and not a delta
260 260 for i in xrange(0, len(revs) - 1):
261 261 a, b = revs[i], revs[i + 1]
262 262 if a + 1 != b or self.base(b) == b:
263 263 for j in xrange(self.base(a), a + 1):
264 264 needed[j] = 1
265 265 for j in xrange(self.base(b), b + 1):
266 266 needed[j] = 1
267 267
268 268 # calculate spans to retrieve from datafile
269 269 needed = needed.keys()
270 270 needed.sort()
271 271 spans = []
272 272 for n in needed:
273 273 if n < 0: continue
274 274 o = self.start(n)
275 275 l = self.length(n)
276 276 spans.append((o, l, [(n, l)]))
277 277
278 278 # merge spans
279 279 merge = [spans.pop(0)]
280 280 while spans:
281 281 e = spans.pop(0)
282 282 f = merge[-1]
283 283 if e[0] == f[0] + f[1]:
284 284 merge[-1] = (f[0], f[1] + e[1], f[2] + e[2])
285 285 else:
286 286 merge.append(e)
287 287
288 288 # read spans in, divide up chunks
289 289 chunks = {}
290 290 for span in merge:
291 291 # we reopen the file for each span to make http happy for now
292 292 f = self.opener(self.datafile)
293 293 f.seek(span[0])
294 294 data = f.read(span[1])
295 295
296 296 # divide up the span
297 297 pos = 0
298 298 for r, l in span[2]:
299 299 chunks[r] = data[pos: pos + l]
300 300 pos += l
301 301
302 302 # helper to reconstruct intermediate versions
303 303 def construct(text, base, rev):
304 304 for r in range(base + 1, rev + 1):
305 305 b = decompress(chunks[r])
306 306 text = self.patch(text, b)
307 307 return text
308 308
309 309 # build deltas
310 310 deltas = []
311 311 for d in range(0, len(revs) - 1):
312 312 a, b = revs[d], revs[d + 1]
313 313 n = self.node(b)
314 314
315 315 if a + 1 != b or self.base(b) == b:
316 316 if a >= 0:
317 317 base = self.base(a)
318 318 ta = decompress(chunks[self.base(a)])
319 319 ta = construct(ta, base, a)
320 320 else:
321 321 ta = ""
322 322
323 323 base = self.base(b)
324 324 if a > base:
325 325 base = a
326 326 tb = ta
327 327 else:
328 328 tb = decompress(chunks[self.base(b)])
329 329 tb = construct(tb, base, b)
330 330 d = self.diff(ta, tb)
331 331 else:
332 332 d = decompress(chunks[b])
333 333
334 334 p = self.parents(n)
335 335 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
336 336 l = struct.pack(">l", len(meta) + len(d) + 4)
337 337 deltas.append(l + meta + d)
338 338
339 339 l = struct.pack(">l", sum(map(len, deltas)) + 4)
340 340 deltas.insert(0, l)
341 341 return "".join(deltas)
342 342
343 343 def addgroup(self, data, linkmapper, transaction):
344 344 # given a set of deltas, add them to the revision log. the
345 345 # first delta is against its parent, which should be in our
346 346 # log, the rest are against the previous delta.
347 347
348 if len(data) <= 4: return
348 if not data: return self.tip()
349 349
350 350 # retrieve the parent revision of the delta chain
351 chain = data[28:48]
352 text = self.revision(chain)
351 chain = data[24:44]
353 352
354 353 # track the base of the current delta log
355 354 r = self.count()
356 355 t = r - 1
357 356
358 357 base = prev = -1
359 358 start = end = 0
360 359 if r:
361 360 start = self.start(self.base(t))
362 361 end = self.end(t)
363 362 measure = self.length(self.base(t))
364 363 base = self.base(t)
365 364 prev = self.tip()
366 365
367 366 transaction.add(self.datafile, end)
368 367 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
369 368 dfh = self.opener(self.datafile, "a")
370 369 ifh = self.opener(self.indexfile, "a")
371 370
372 371 # loop through our set of deltas
373 pos = 4
372 pos = 0
374 373 while pos < len(data):
375 374 l, node, p1, p2, cs = struct.unpack(">l20s20s20s20s",
376 375 data[pos:pos+84])
377 376 link = linkmapper(cs)
378 377 delta = data[pos + 84:pos + l]
379 378 pos += l
380 379
381 380 # full versions are inserted when the needed deltas become
382 381 # comparable to the uncompressed text or when the previous
383 382 # version is not the one we have a delta against. We use
384 383 # the size of the previous full rev as a proxy for the
385 384 # current size.
386 385
387 386 if chain == prev:
388 387 cdelta = compress(delta)
389 388
390 389 if chain != prev or (end - start + len(cdelta)) > measure * 2:
391 390 # flush our writes here so we can read it in revision
392 391 dfh.flush()
393 392 ifh.flush()
394 text = self.revision(self.node(t))
393 text = self.revision(chain)
395 394 text = self.patch(text, delta)
396 395 chk = self.addrevision(text, transaction, link, p1, p2)
397 396 if chk != node:
398 397 raise "consistency error adding group"
399 398 measure = len(text)
400 399 else:
401 400 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
402 401 self.index.append(e)
403 402 self.nodemap[node] = r
404 403 dfh.write(cdelta)
405 404 ifh.write(struct.pack(indexformat, *e))
406 405
407 t, r = r, r + 1
408 chain = prev
406 t, r, chain, prev = r, r + 1, node, node
409 407 start = self.start(self.base(t))
410 408 end = self.end(t)
411 409
412 410 dfh.close()
413 411 ifh.close()
414 412 return node
General Comments 0
You need to be logged in to leave comments. Login now