##// END OF EJS Templates
Add updated merge3 code
mpm@selenic.com -
r96:fce47326 default
parent child Browse files
Show More
@@ -1,826 +1,862 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import sys, struct, sha, socket, os, time, base64, re, urllib2
8 import sys, struct, sha, socket, os, time, base64, re, urllib2
9 import urllib
9 import urllib
10 from mercurial import byterange
10 from mercurial import byterange
11 from mercurial.transaction import *
11 from mercurial.transaction import *
12 from mercurial.revlog import *
12 from mercurial.revlog import *
13 from difflib import SequenceMatcher
13 from difflib import SequenceMatcher
14
14
15 class filelog(revlog):
15 class filelog(revlog):
16 def __init__(self, opener, path):
16 def __init__(self, opener, path):
17 s = self.encodepath(path)
17 s = self.encodepath(path)
18 revlog.__init__(self, opener, os.path.join("data", s + "i"),
18 revlog.__init__(self, opener, os.path.join("data", s + "i"),
19 os.path.join("data", s))
19 os.path.join("data", s))
20
20
21 def encodepath(self, path):
21 def encodepath(self, path):
22 s = sha.sha(path).digest()
22 s = sha.sha(path).digest()
23 s = base64.encodestring(s)[:-3]
23 s = base64.encodestring(s)[:-3]
24 s = re.sub("\+", "%", s)
24 s = re.sub("\+", "%", s)
25 s = re.sub("/", "_", s)
25 s = re.sub("/", "_", s)
26 return s
26 return s
27
27
28 def read(self, node):
28 def read(self, node):
29 return self.revision(node)
29 return self.revision(node)
30 def add(self, text, transaction, link, p1=None, p2=None):
30 def add(self, text, transaction, link, p1=None, p2=None):
31 return self.addrevision(text, transaction, link, p1, p2)
31 return self.addrevision(text, transaction, link, p1, p2)
32
32
33 def annotate(self, node):
33 def annotate(self, node):
34 revs = []
34 revs = []
35 while node != nullid:
35 while node != nullid:
36 revs.append(node)
36 revs.append(node)
37 node = self.parents(node)[0]
37 node = self.parents(node)[0]
38 revs.reverse()
38 revs.reverse()
39 prev = []
39 prev = []
40 annotate = []
40 annotate = []
41 for node in revs:
41 for node in revs:
42 curr = self.read(node).splitlines(1)
42 curr = self.read(node).splitlines(1)
43 linkrev = self.linkrev(node)
43 linkrev = self.linkrev(node)
44 sm = SequenceMatcher(None, prev, curr)
44 sm = SequenceMatcher(None, prev, curr)
45 offset = 0
45 offset = 0
46 for o, m, n, s, t in sm.get_opcodes():
46 for o, m, n, s, t in sm.get_opcodes():
47 if o in ('insert','replace'):
47 if o in ('insert','replace'):
48 annotate[m+offset:n+offset] = \
48 annotate[m+offset:n+offset] = \
49 [ (linkrev, l) for l in curr[s:t]]
49 [ (linkrev, l) for l in curr[s:t]]
50 if o == 'insert':
50 if o == 'insert':
51 offset += m-n
51 offset += m-n
52 elif o == 'delete':
52 elif o == 'delete':
53 del annotate[m+offset:n+offset]
53 del annotate[m+offset:n+offset]
54 offset -= m-n
54 offset -= m-n
55 assert len(annotate) == len(curr)
55 assert len(annotate) == len(curr)
56 prev = curr
56 prev = curr
57 return annotate
57 return annotate
58
58
59 class manifest(revlog):
59 class manifest(revlog):
60 def __init__(self, opener):
60 def __init__(self, opener):
61 self.mapcache = None
61 self.mapcache = None
62 self.listcache = None
62 self.listcache = None
63 self.addlist = None
63 self.addlist = None
64 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
64 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
65
65
66 def read(self, node):
66 def read(self, node):
67 if self.mapcache and self.mapcache[0] == node:
67 if self.mapcache and self.mapcache[0] == node:
68 return self.mapcache[1].copy()
68 return self.mapcache[1].copy()
69 text = self.revision(node)
69 text = self.revision(node)
70 map = {}
70 map = {}
71 self.listcache = (text, text.splitlines(1))
71 self.listcache = (text, text.splitlines(1))
72 for l in self.listcache[1]:
72 for l in self.listcache[1]:
73 (f, n) = l.split('\0')
73 (f, n) = l.split('\0')
74 map[f] = bin(n[:40])
74 map[f] = bin(n[:40])
75 self.mapcache = (node, map)
75 self.mapcache = (node, map)
76 return map
76 return map
77
77
78 def diff(self, a, b):
78 def diff(self, a, b):
79 # this is sneaky, as we're not actually using a and b
79 # this is sneaky, as we're not actually using a and b
80 if self.listcache and len(self.listcache[0]) == len(a):
80 if self.listcache and len(self.listcache[0]) == len(a):
81 return mdiff.diff(self.listcache[1], self.addlist, 1)
81 return mdiff.diff(self.listcache[1], self.addlist, 1)
82 else:
82 else:
83 return mdiff.textdiff(a, b)
83 return mdiff.textdiff(a, b)
84
84
85 def add(self, map, transaction, link, p1=None, p2=None):
85 def add(self, map, transaction, link, p1=None, p2=None):
86 files = map.keys()
86 files = map.keys()
87 files.sort()
87 files.sort()
88
88
89 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
89 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
90 text = "".join(self.addlist)
90 text = "".join(self.addlist)
91
91
92 n = self.addrevision(text, transaction, link, p1, p2)
92 n = self.addrevision(text, transaction, link, p1, p2)
93 self.mapcache = (n, map)
93 self.mapcache = (n, map)
94 self.listcache = (text, self.addlist)
94 self.listcache = (text, self.addlist)
95
95
96 return n
96 return n
97
97
98 class changelog(revlog):
98 class changelog(revlog):
99 def __init__(self, opener):
99 def __init__(self, opener):
100 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
100 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
101
101
102 def extract(self, text):
102 def extract(self, text):
103 if not text:
103 if not text:
104 return (nullid, "", "0", [], "")
104 return (nullid, "", "0", [], "")
105 last = text.index("\n\n")
105 last = text.index("\n\n")
106 desc = text[last + 2:]
106 desc = text[last + 2:]
107 l = text[:last].splitlines()
107 l = text[:last].splitlines()
108 manifest = bin(l[0])
108 manifest = bin(l[0])
109 user = l[1]
109 user = l[1]
110 date = l[2]
110 date = l[2]
111 files = l[3:]
111 files = l[3:]
112 return (manifest, user, date, files, desc)
112 return (manifest, user, date, files, desc)
113
113
114 def read(self, node):
114 def read(self, node):
115 return self.extract(self.revision(node))
115 return self.extract(self.revision(node))
116
116
117 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
117 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
118 user = (os.environ.get("HGUSER") or
118 user = (os.environ.get("HGUSER") or
119 os.environ.get("EMAIL") or
119 os.environ.get("EMAIL") or
120 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
120 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
121 date = "%d %d" % (time.time(), time.timezone)
121 date = "%d %d" % (time.time(), time.timezone)
122 list.sort()
122 list.sort()
123 l = [hex(manifest), user, date] + list + ["", desc]
123 l = [hex(manifest), user, date] + list + ["", desc]
124 text = "\n".join(l)
124 text = "\n".join(l)
125 return self.addrevision(text, transaction, self.count(), p1, p2)
125 return self.addrevision(text, transaction, self.count(), p1, p2)
126
126
127 class dircache:
127 class dircache:
128 def __init__(self, opener, ui):
128 def __init__(self, opener, ui):
129 self.opener = opener
129 self.opener = opener
130 self.dirty = 0
130 self.dirty = 0
131 self.ui = ui
131 self.ui = ui
132 self.map = None
132 self.map = None
133 def __del__(self):
133 def __del__(self):
134 if self.dirty: self.write()
134 if self.dirty: self.write()
135 def __getitem__(self, key):
135 def __getitem__(self, key):
136 try:
136 try:
137 return self.map[key]
137 return self.map[key]
138 except TypeError:
138 except TypeError:
139 self.read()
139 self.read()
140 return self[key]
140 return self[key]
141
141
142 def read(self):
142 def read(self):
143 if self.map is not None: return self.map
143 if self.map is not None: return self.map
144
144
145 self.map = {}
145 self.map = {}
146 try:
146 try:
147 st = self.opener("dircache").read()
147 st = self.opener("dircache").read()
148 except: return
148 except: return
149
149
150 pos = 0
150 pos = 0
151 while pos < len(st):
151 while pos < len(st):
152 e = struct.unpack(">llll", st[pos:pos+16])
152 e = struct.unpack(">llll", st[pos:pos+16])
153 l = e[3]
153 l = e[3]
154 pos += 16
154 pos += 16
155 f = st[pos:pos + l]
155 f = st[pos:pos + l]
156 self.map[f] = e[:3]
156 self.map[f] = e[:3]
157 pos += l
157 pos += l
158
158
159 def update(self, files):
159 def update(self, files):
160 if not files: return
160 if not files: return
161 self.read()
161 self.read()
162 self.dirty = 1
162 self.dirty = 1
163 for f in files:
163 for f in files:
164 try:
164 try:
165 s = os.stat(f)
165 s = os.stat(f)
166 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
166 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
167 except IOError:
167 except IOError:
168 self.remove(f)
168 self.remove(f)
169
169
170 def taint(self, files):
170 def taint(self, files):
171 if not files: return
171 if not files: return
172 self.read()
172 self.read()
173 self.dirty = 1
173 self.dirty = 1
174 for f in files:
174 for f in files:
175 self.map[f] = (0, -1, 0)
175 self.map[f] = (0, -1, 0)
176
176
177 def remove(self, files):
177 def remove(self, files):
178 if not files: return
178 if not files: return
179 self.read()
179 self.read()
180 self.dirty = 1
180 self.dirty = 1
181 for f in files:
181 for f in files:
182 try:
182 try:
183 del self.map[f]
183 del self.map[f]
184 except KeyError:
184 except KeyError:
185 self.ui.warn("Not in dircache: %s\n" % f)
185 self.ui.warn("Not in dircache: %s\n" % f)
186 pass
186 pass
187
187
188 def clear(self):
188 def clear(self):
189 self.map = {}
189 self.map = {}
190 self.dirty = 1
190 self.dirty = 1
191
191
192 def write(self):
192 def write(self):
193 st = self.opener("dircache", "w")
193 st = self.opener("dircache", "w")
194 for f, e in self.map.items():
194 for f, e in self.map.items():
195 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
195 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
196 st.write(e + f)
196 st.write(e + f)
197 self.dirty = 0
197 self.dirty = 0
198
198
199 def copy(self):
199 def copy(self):
200 self.read()
200 self.read()
201 return self.map.copy()
201 return self.map.copy()
202
202
203 # used to avoid circular references so destructors work
203 # used to avoid circular references so destructors work
204 def opener(base):
204 def opener(base):
205 p = base
205 p = base
206 def o(path, mode="r"):
206 def o(path, mode="r"):
207 if p[:7] == "http://":
207 if p[:7] == "http://":
208 f = os.path.join(p, urllib.quote(path))
208 f = os.path.join(p, urllib.quote(path))
209 return httprangereader(f)
209 return httprangereader(f)
210
210
211 f = os.path.join(p, path)
211 f = os.path.join(p, path)
212
212
213 if mode != "r" and os.path.isfile(f):
213 if mode != "r" and os.path.isfile(f):
214 s = os.stat(f)
214 s = os.stat(f)
215 if s.st_nlink > 1:
215 if s.st_nlink > 1:
216 file(f + ".tmp", "w").write(file(f).read())
216 file(f + ".tmp", "w").write(file(f).read())
217 os.rename(f+".tmp", f)
217 os.rename(f+".tmp", f)
218
218
219 return file(f, mode)
219 return file(f, mode)
220
220
221 return o
221 return o
222
222
223 class localrepository:
223 class localrepository:
224 def __init__(self, ui, path=None, create=0):
224 def __init__(self, ui, path=None, create=0):
225 self.remote = 0
225 self.remote = 0
226 if path and path[:7] == "http://":
226 if path and path[:7] == "http://":
227 self.remote = 1
227 self.remote = 1
228 self.path = path
228 self.path = path
229 else:
229 else:
230 if not path:
230 if not path:
231 p = os.getcwd()
231 p = os.getcwd()
232 while not os.path.isdir(os.path.join(p, ".hg")):
232 while not os.path.isdir(os.path.join(p, ".hg")):
233 p = os.path.dirname(p)
233 p = os.path.dirname(p)
234 if p == "/": raise "No repo found"
234 if p == "/": raise "No repo found"
235 path = p
235 path = p
236 self.path = os.path.join(path, ".hg")
236 self.path = os.path.join(path, ".hg")
237
237
238 self.root = path
238 self.root = path
239 self.ui = ui
239 self.ui = ui
240
240
241 if create:
241 if create:
242 os.mkdir(self.path)
242 os.mkdir(self.path)
243 os.mkdir(self.join("data"))
243 os.mkdir(self.join("data"))
244
244
245 self.opener = opener(self.path)
245 self.opener = opener(self.path)
246 self.manifest = manifest(self.opener)
246 self.manifest = manifest(self.opener)
247 self.changelog = changelog(self.opener)
247 self.changelog = changelog(self.opener)
248 self.ignorelist = None
248 self.ignorelist = None
249 self.tags = None
249 self.tags = None
250
250
251 if not self.remote:
251 if not self.remote:
252 self.dircache = dircache(self.opener, ui)
252 self.dircache = dircache(self.opener, ui)
253 try:
253 try:
254 self.current = bin(self.opener("current").read())
254 self.current = bin(self.opener("current").read())
255 except IOError:
255 except IOError:
256 self.current = None
256 self.current = None
257
257
258 def setcurrent(self, node):
258 def setcurrent(self, node):
259 self.current = node
259 self.current = node
260 self.opener("current", "w").write(hex(node))
260 self.opener("current", "w").write(hex(node))
261
261
262 def ignore(self, f):
262 def ignore(self, f):
263 if self.ignorelist is None:
263 if self.ignorelist is None:
264 self.ignorelist = []
264 self.ignorelist = []
265 try:
265 try:
266 l = open(os.path.join(self.root, ".hgignore"))
266 l = open(os.path.join(self.root, ".hgignore"))
267 for pat in l:
267 for pat in l:
268 if pat != "\n":
268 if pat != "\n":
269 self.ignorelist.append(re.compile(pat[:-1]))
269 self.ignorelist.append(re.compile(pat[:-1]))
270 except IOError: pass
270 except IOError: pass
271 for pat in self.ignorelist:
271 for pat in self.ignorelist:
272 if pat.search(f): return True
272 if pat.search(f): return True
273 return False
273 return False
274
274
275 def lookup(self, key):
275 def lookup(self, key):
276 if self.tags is None:
276 if self.tags is None:
277 self.tags = {}
277 self.tags = {}
278 try:
278 try:
279 fl = self.file(".hgtags")
279 fl = self.file(".hgtags")
280 for l in fl.revision(fl.tip()).splitlines():
280 for l in fl.revision(fl.tip()).splitlines():
281 if l:
281 if l:
282 n, k = l.split(" ")
282 n, k = l.split(" ")
283 self.tags[k] = bin(n)
283 self.tags[k] = bin(n)
284 except KeyError: pass
284 except KeyError: pass
285 try:
285 try:
286 return self.tags[key]
286 return self.tags[key]
287 except KeyError:
287 except KeyError:
288 return self.changelog.lookup(key)
288 return self.changelog.lookup(key)
289
289
290 def join(self, f):
290 def join(self, f):
291 return os.path.join(self.path, f)
291 return os.path.join(self.path, f)
292
292
293 def file(self, f):
293 def file(self, f):
294 return filelog(self.opener, f)
294 return filelog(self.opener, f)
295
295
296 def transaction(self):
296 def transaction(self):
297 return transaction(self.opener, self.join("journal"),
297 return transaction(self.opener, self.join("journal"),
298 self.join("undo"))
298 self.join("undo"))
299
299
300 def commit(self, parent, update = None, text = ""):
300 def commit(self, parent, update = None, text = ""):
301 tr = self.transaction()
301 tr = self.transaction()
302
302
303 try:
303 try:
304 remove = [ l[:-1] for l in self.opener("to-remove") ]
304 remove = [ l[:-1] for l in self.opener("to-remove") ]
305 os.unlink(self.join("to-remove"))
305 os.unlink(self.join("to-remove"))
306
306
307 except IOError:
307 except IOError:
308 remove = []
308 remove = []
309
309
310 if update == None:
310 if update == None:
311 update = self.diffdir(self.root, parent)[0]
311 update = self.diffdir(self.root, parent)[0]
312
312
313 # check in files
313 # check in files
314 new = {}
314 new = {}
315 linkrev = self.changelog.count()
315 linkrev = self.changelog.count()
316 for f in update:
316 for f in update:
317 self.ui.note(f + "\n")
317 self.ui.note(f + "\n")
318 try:
318 try:
319 t = file(f).read()
319 t = file(f).read()
320 except IOError:
320 except IOError:
321 remove.append(f)
321 remove.append(f)
322 continue
322 continue
323 r = self.file(f)
323 r = self.file(f)
324 new[f] = r.add(t, tr, linkrev)
324 new[f] = r.add(t, tr, linkrev)
325
325
326 # update manifest
326 # update manifest
327 mmap = self.manifest.read(self.manifest.tip())
327 mmap = self.manifest.read(self.manifest.tip())
328 mmap.update(new)
328 mmap.update(new)
329 for f in remove:
329 for f in remove:
330 del mmap[f]
330 del mmap[f]
331 mnode = self.manifest.add(mmap, tr, linkrev)
331 mnode = self.manifest.add(mmap, tr, linkrev)
332
332
333 # add changeset
333 # add changeset
334 new = new.keys()
334 new = new.keys()
335 new.sort()
335 new.sort()
336
336
337 edittext = text + "\n"+"".join(["HG: changed %s\n" % f for f in new])
337 edittext = text + "\n"+"".join(["HG: changed %s\n" % f for f in new])
338 edittext += "".join(["HG: removed %s\n" % f for f in remove])
338 edittext += "".join(["HG: removed %s\n" % f for f in remove])
339 edittext = self.ui.edit(edittext)
339 edittext = self.ui.edit(edittext)
340
340
341 n = self.changelog.add(mnode, new, edittext, tr)
341 n = self.changelog.add(mnode, new, edittext, tr)
342 tr.close()
342 tr.close()
343
343
344 self.setcurrent(n)
344 self.setcurrent(n)
345 self.dircache.update(new)
345 self.dircache.update(new)
346 self.dircache.remove(remove)
346 self.dircache.remove(remove)
347
347
348 def checkdir(self, path):
348 def checkdir(self, path):
349 d = os.path.dirname(path)
349 d = os.path.dirname(path)
350 if not d: return
350 if not d: return
351 if not os.path.isdir(d):
351 if not os.path.isdir(d):
352 self.checkdir(d)
352 self.checkdir(d)
353 os.mkdir(d)
353 os.mkdir(d)
354
354
355 def checkout(self, node):
355 def checkout(self, node):
356 # checkout is really dumb at the moment
356 # checkout is really dumb at the moment
357 # it ought to basically merge
357 # it ought to basically merge
358 change = self.changelog.read(node)
358 change = self.changelog.read(node)
359 mmap = self.manifest.read(change[0])
359 mmap = self.manifest.read(change[0])
360
360
361 l = mmap.keys()
361 l = mmap.keys()
362 l.sort()
362 l.sort()
363 stats = []
363 stats = []
364 for f in l:
364 for f in l:
365 self.ui.note(f + "\n")
365 self.ui.note(f + "\n")
366 r = self.file(f)
366 r = self.file(f)
367 t = r.revision(mmap[f])
367 t = r.revision(mmap[f])
368 try:
368 try:
369 file(f, "w").write(t)
369 file(f, "w").write(t)
370 except:
370 except:
371 self.checkdir(f)
371 self.checkdir(f)
372 file(f, "w").write(t)
372 file(f, "w").write(t)
373
373
374 self.setcurrent(node)
374 self.setcurrent(node)
375 self.dircache.clear()
375 self.dircache.clear()
376 self.dircache.update(l)
376 self.dircache.update(l)
377
377
378 def diffdir(self, path, changeset):
378 def diffdir(self, path, changeset):
379 changed = []
379 changed = []
380 mf = {}
380 mf = {}
381 added = []
381 added = []
382
382
383 if changeset:
383 if changeset:
384 change = self.changelog.read(changeset)
384 change = self.changelog.read(changeset)
385 mf = self.manifest.read(change[0])
385 mf = self.manifest.read(change[0])
386
386
387 if changeset == self.current:
387 if changeset == self.current:
388 dc = self.dircache.copy()
388 dc = self.dircache.copy()
389 else:
389 else:
390 dc = dict.fromkeys(mf)
390 dc = dict.fromkeys(mf)
391
391
392 def fcmp(fn):
392 def fcmp(fn):
393 t1 = file(os.path.join(self.root, fn)).read()
393 t1 = file(os.path.join(self.root, fn)).read()
394 t2 = self.file(fn).revision(mf[fn])
394 t2 = self.file(fn).revision(mf[fn])
395 return cmp(t1, t2)
395 return cmp(t1, t2)
396
396
397 for dir, subdirs, files in os.walk(self.root):
397 for dir, subdirs, files in os.walk(self.root):
398 d = dir[len(self.root)+1:]
398 d = dir[len(self.root)+1:]
399 if ".hg" in subdirs: subdirs.remove(".hg")
399 if ".hg" in subdirs: subdirs.remove(".hg")
400
400
401 for f in files:
401 for f in files:
402 fn = os.path.join(d, f)
402 fn = os.path.join(d, f)
403 try: s = os.stat(os.path.join(self.root, fn))
403 try: s = os.stat(os.path.join(self.root, fn))
404 except: continue
404 except: continue
405 if fn in dc:
405 if fn in dc:
406 c = dc[fn]
406 c = dc[fn]
407 del dc[fn]
407 del dc[fn]
408 if not c:
408 if not c:
409 if fcmp(fn):
409 if fcmp(fn):
410 changed.append(fn)
410 changed.append(fn)
411 elif c[1] != s.st_size:
411 elif c[1] != s.st_size:
412 changed.append(fn)
412 changed.append(fn)
413 elif c[0] != s.st_mode or c[2] != s.st_mtime:
413 elif c[0] != s.st_mode or c[2] != s.st_mtime:
414 if fcmp(fn):
414 if fcmp(fn):
415 changed.append(fn)
415 changed.append(fn)
416 else:
416 else:
417 if self.ignore(fn): continue
417 if self.ignore(fn): continue
418 added.append(fn)
418 added.append(fn)
419
419
420 deleted = dc.keys()
420 deleted = dc.keys()
421 deleted.sort()
421 deleted.sort()
422
422
423 return (changed, added, deleted)
423 return (changed, added, deleted)
424
424
425 def diffrevs(self, node1, node2):
425 def diffrevs(self, node1, node2):
426 changed, added = [], []
426 changed, added = [], []
427
427
428 change = self.changelog.read(node1)
428 change = self.changelog.read(node1)
429 mf1 = self.manifest.read(change[0])
429 mf1 = self.manifest.read(change[0])
430 change = self.changelog.read(node2)
430 change = self.changelog.read(node2)
431 mf2 = self.manifest.read(change[0])
431 mf2 = self.manifest.read(change[0])
432
432
433 for fn in mf2:
433 for fn in mf2:
434 if mf1.has_key(fn):
434 if mf1.has_key(fn):
435 if mf1[fn] != mf2[fn]:
435 if mf1[fn] != mf2[fn]:
436 changed.append(fn)
436 changed.append(fn)
437 del mf1[fn]
437 del mf1[fn]
438 else:
438 else:
439 added.append(fn)
439 added.append(fn)
440
440
441 deleted = mf1.keys()
441 deleted = mf1.keys()
442 deleted.sort()
442 deleted.sort()
443
443
444 return (changed, added, deleted)
444 return (changed, added, deleted)
445
445
446 def add(self, list):
446 def add(self, list):
447 self.dircache.taint(list)
447 self.dircache.taint(list)
448
448
449 def remove(self, list):
449 def remove(self, list):
450 dl = self.opener("to-remove", "a")
450 dl = self.opener("to-remove", "a")
451 for f in list:
451 for f in list:
452 dl.write(f + "\n")
452 dl.write(f + "\n")
453
453
454 def branches(self, nodes):
454 def branches(self, nodes):
455 if not nodes: nodes = [self.changelog.tip()]
455 if not nodes: nodes = [self.changelog.tip()]
456 b = []
456 b = []
457 for n in nodes:
457 for n in nodes:
458 t = n
458 t = n
459 while n:
459 while n:
460 p = self.changelog.parents(n)
460 p = self.changelog.parents(n)
461 if p[1] != nullid or p[0] == nullid:
461 if p[1] != nullid or p[0] == nullid:
462 b.append((t, n, p[0], p[1]))
462 b.append((t, n, p[0], p[1]))
463 break
463 break
464 n = p[0]
464 n = p[0]
465 return b
465 return b
466
466
467 def between(self, pairs):
467 def between(self, pairs):
468 r = []
468 r = []
469
469
470 for top, bottom in pairs:
470 for top, bottom in pairs:
471 n, l, i = top, [], 0
471 n, l, i = top, [], 0
472 f = 1
472 f = 1
473
473
474 while n != bottom:
474 while n != bottom:
475 p = self.changelog.parents(n)[0]
475 p = self.changelog.parents(n)[0]
476 if i == f:
476 if i == f:
477 l.append(n)
477 l.append(n)
478 f = f * 2
478 f = f * 2
479 n = p
479 n = p
480 i += 1
480 i += 1
481
481
482 r.append(l)
482 r.append(l)
483
483
484 return r
484 return r
485
485
486 def newer(self, nodes):
486 def newer(self, nodes):
487 m = {}
487 m = {}
488 nl = []
488 nl = []
489 pm = {}
489 pm = {}
490 cl = self.changelog
490 cl = self.changelog
491 t = l = cl.count()
491 t = l = cl.count()
492
492
493 # find the lowest numbered node
493 # find the lowest numbered node
494 for n in nodes:
494 for n in nodes:
495 l = min(l, cl.rev(n))
495 l = min(l, cl.rev(n))
496 m[n] = 1
496 m[n] = 1
497
497
498 for i in xrange(l, t):
498 for i in xrange(l, t):
499 n = cl.node(i)
499 n = cl.node(i)
500 if n in m: # explicitly listed
500 if n in m: # explicitly listed
501 pm[n] = 1
501 pm[n] = 1
502 nl.append(n)
502 nl.append(n)
503 continue
503 continue
504 for p in cl.parents(n):
504 for p in cl.parents(n):
505 if p in pm: # parent listed
505 if p in pm: # parent listed
506 pm[n] = 1
506 pm[n] = 1
507 nl.append(n)
507 nl.append(n)
508 break
508 break
509
509
510 return nl
510 return nl
511
511
512 def getchangegroup(self, remote):
512 def getchangegroup(self, remote):
513 tip = remote.branches([])[0]
513 tip = remote.branches([])[0]
514 self.ui.debug("remote tip branch is %s:%s\n" %
514 self.ui.debug("remote tip branch is %s:%s\n" %
515 (short(tip[0]), short(tip[1])))
515 (short(tip[0]), short(tip[1])))
516 m = self.changelog.nodemap
516 m = self.changelog.nodemap
517 unknown = [tip]
517 unknown = [tip]
518 search = []
518 search = []
519 fetch = []
519 fetch = []
520
520
521 if tip[0] in m:
521 if tip[0] in m:
522 self.ui.note("nothing to do!\n")
522 self.ui.note("nothing to do!\n")
523 return None
523 return None
524
524
525 while unknown:
525 while unknown:
526 n = unknown.pop(0)
526 n = unknown.pop(0)
527 if n == nullid: break
527 if n == nullid: break
528 if n[1] and n[1] in m: # do we know the base?
528 if n[1] and n[1] in m: # do we know the base?
529 self.ui.debug("found incomplete branch %s\n" % short(n[1]))
529 self.ui.debug("found incomplete branch %s\n" % short(n[1]))
530 search.append(n) # schedule branch range for scanning
530 search.append(n) # schedule branch range for scanning
531 else:
531 else:
532 if n[2] in m and n[3] in m:
532 if n[2] in m and n[3] in m:
533 if n[1] not in fetch:
533 if n[1] not in fetch:
534 self.ui.debug("found new changeset %s\n" %
534 self.ui.debug("found new changeset %s\n" %
535 short(n[1]))
535 short(n[1]))
536 fetch.append(n[1]) # earliest unknown
536 fetch.append(n[1]) # earliest unknown
537 continue
537 continue
538 for b in remote.branches([n[2], n[3]]):
538 for b in remote.branches([n[2], n[3]]):
539 if b[0] not in m:
539 if b[0] not in m:
540 unknown.append(b)
540 unknown.append(b)
541
541
542 while search:
542 while search:
543 n = search.pop(0)
543 n = search.pop(0)
544 l = remote.between([(n[0], n[1])])[0]
544 l = remote.between([(n[0], n[1])])[0]
545 p = n[0]
545 p = n[0]
546 f = 1
546 f = 1
547 for i in l + [n[1]]:
547 for i in l + [n[1]]:
548 if i in m:
548 if i in m:
549 if f <= 2:
549 if f <= 2:
550 self.ui.debug("found new branch changeset %s\n" %
550 self.ui.debug("found new branch changeset %s\n" %
551 short(p))
551 short(p))
552 fetch.append(p)
552 fetch.append(p)
553 else:
553 else:
554 self.ui.debug("narrowed branch search to %s:%s\n"
554 self.ui.debug("narrowed branch search to %s:%s\n"
555 % (short(p), short(i)))
555 % (short(p), short(i)))
556 search.append((p, i))
556 search.append((p, i))
557 break
557 break
558 p, f = i, f * 2
558 p, f = i, f * 2
559
559
560 for f in fetch:
560 for f in fetch:
561 if f in m:
561 if f in m:
562 raise "already have", short(f[:4])
562 raise "already have", short(f[:4])
563
563
564 self.ui.note("adding new changesets starting at " +
564 self.ui.note("adding new changesets starting at " +
565 " ".join([short(f) for f in fetch]) + "\n")
565 " ".join([short(f) for f in fetch]) + "\n")
566
566
567 return remote.changegroup(fetch)
567 return remote.changegroup(fetch)
568
568
569 def changegroup(self, basenodes):
569 def changegroup(self, basenodes):
570 nodes = self.newer(basenodes)
570 nodes = self.newer(basenodes)
571
571
572 # construct the link map
572 # construct the link map
573 linkmap = {}
573 linkmap = {}
574 for n in nodes:
574 for n in nodes:
575 linkmap[self.changelog.rev(n)] = n
575 linkmap[self.changelog.rev(n)] = n
576
576
577 # construct a list of all changed files
577 # construct a list of all changed files
578 changed = {}
578 changed = {}
579 for n in nodes:
579 for n in nodes:
580 c = self.changelog.read(n)
580 c = self.changelog.read(n)
581 for f in c[3]:
581 for f in c[3]:
582 changed[f] = 1
582 changed[f] = 1
583 changed = changed.keys()
583 changed = changed.keys()
584 changed.sort()
584 changed.sort()
585
585
586 # the changegroup is changesets + manifests + all file revs
586 # the changegroup is changesets + manifests + all file revs
587 revs = [ self.changelog.rev(n) for n in nodes ]
587 revs = [ self.changelog.rev(n) for n in nodes ]
588
588
589 yield self.changelog.group(linkmap)
589 yield self.changelog.group(linkmap)
590 yield self.manifest.group(linkmap)
590 yield self.manifest.group(linkmap)
591
591
592 for f in changed:
592 for f in changed:
593 g = self.file(f).group(linkmap)
593 g = self.file(f).group(linkmap)
594 if not g: raise "couldn't find change to %s" % f
594 if not g: raise "couldn't find change to %s" % f
595 l = struct.pack(">l", len(f))
595 l = struct.pack(">l", len(f))
596 yield "".join([l, f, g])
596 yield "".join([l, f, g])
597
597
598 def addchangegroup(self, generator):
598 def addchangegroup(self, generator):
599 class genread:
599 class genread:
600 def __init__(self, generator):
600 def __init__(self, generator):
601 self.g = generator
601 self.g = generator
602 self.buf = ""
602 self.buf = ""
603 def read(self, l):
603 def read(self, l):
604 while l > len(self.buf):
604 while l > len(self.buf):
605 try:
605 try:
606 self.buf += self.g.next()
606 self.buf += self.g.next()
607 except StopIteration:
607 except StopIteration:
608 break
608 break
609 d, self.buf = self.buf[:l], self.buf[l:]
609 d, self.buf = self.buf[:l], self.buf[l:]
610 return d
610 return d
611
611
612 if not generator: return
612 if not generator: return
613 source = genread(generator)
613 source = genread(generator)
614
614
615 def getchunk(add = 0):
615 def getchunk(add = 0):
616 d = source.read(4)
616 d = source.read(4)
617 if not d: return ""
617 if not d: return ""
618 l = struct.unpack(">l", d)[0]
618 l = struct.unpack(">l", d)[0]
619 return source.read(l - 4 + add)
619 return source.read(l - 4 + add)
620
620
621 tr = self.transaction()
621 tr = self.transaction()
622 simple = True
622 simple = True
623
623
624 self.ui.status("adding changesets\n")
624 self.ui.status("adding changesets\n")
625 # pull off the changeset group
625 # pull off the changeset group
626 def report(x):
626 def report(x):
627 self.ui.debug("add changeset %s\n" % short(x))
627 self.ui.debug("add changeset %s\n" % short(x))
628 return self.changelog.count()
628 return self.changelog.count()
629
629
630 csg = getchunk()
630 csg = getchunk()
631 co = self.changelog.tip()
631 co = self.changelog.tip()
632 cn = self.changelog.addgroup(csg, report, tr)
632 cn = self.changelog.addgroup(csg, report, tr)
633
633
634 self.ui.status("adding manifests\n")
634 self.ui.status("adding manifests\n")
635 # pull off the manifest group
635 # pull off the manifest group
636 mfg = getchunk()
636 mfg = getchunk()
637 mm = self.manifest.tip()
637 mm = self.manifest.tip()
638 mo = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
638 mo = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
639
639
640 # do we need a resolve?
640 # do we need a resolve?
641 if self.changelog.ancestor(co, cn) != co:
641 if self.changelog.ancestor(co, cn) != co:
642 simple = False
642 simple = False
643 resolverev = self.changelog.count()
643 resolverev = self.changelog.count()
644
644
645 # process the files
645 # process the files
646 self.ui.status("adding files\n")
646 self.ui.status("adding files\n")
647 new = {}
647 new = {}
648 while 1:
648 while 1:
649 f = getchunk(4)
649 f = getchunk(4)
650 if not f: break
650 if not f: break
651 fg = getchunk()
651 fg = getchunk()
652 self.ui.debug("adding %s revisions\n" % f)
652 self.ui.debug("adding %s revisions\n" % f)
653 fl = self.file(f)
653 fl = self.file(f)
654 o = fl.tip()
654 o = fl.tip()
655 n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
655 n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
656 if not simple:
656 if not simple:
657 if o == n: continue
657 if o == n: continue
658 # this file has changed between branches, so it must be
658 # this file has changed between branches, so it must be
659 # represented in the merge changeset
659 # represented in the merge changeset
660 new[f] = self.merge3(fl, f, o, n, tr, resolverev)
660 new[f] = self.merge3(fl, f, o, n, tr, resolverev)
661
661
662 # For simple merges, we don't need to resolve manifests or changesets
662 # For simple merges, we don't need to resolve manifests or changesets
663 if simple:
663 if simple:
664 self.ui.debug("simple merge, skipping resolve\n")
664 self.ui.debug("simple merge, skipping resolve\n")
665 tr.close()
665 tr.close()
666 return
666 return
667
667
668 # resolve the manifest to point to all the merged files
668 # resolve the manifest to point to all the merged files
669 self.ui.status("resolving manifests\n")
669 self.ui.status("resolving manifests\n")
670 ma = self.manifest.ancestor(mm, mo)
670 ma = self.manifest.ancestor(mm, mo)
671 omap = self.manifest.read(mo) # other
671 omap = self.manifest.read(mo) # other
672 amap = self.manifest.read(ma) # ancestor
672 amap = self.manifest.read(ma) # ancestor
673 mmap = self.manifest.read(mm) # mine
673 mmap = self.manifest.read(mm) # mine
674 self.ui.debug("ancestor %s local %s other %s\n" %
674 self.ui.debug("ancestor %s local %s other %s\n" %
675 (short(ma), short(mm), short(mo)))
675 (short(ma), short(mm), short(mo)))
676 nmap = {}
676 nmap = {}
677
677
678 for f, mid in mmap.iteritems():
678 for f, mid in mmap.iteritems():
679 if f in omap:
679 if f in omap:
680 if mid != omap[f]:
680 if mid != omap[f]:
681 self.ui.debug("%s versions differ\n" % f)
681 self.ui.debug("%s versions differ\n" % f)
682 if f in new: self.ui.debug("%s updated in resolve\n" % f)
682 if f in new: self.ui.debug("%s updated in resolve\n" % f)
683 # use merged version or local version
683 # use merged version or local version
684 nmap[f] = new.get(f, mid)
684 nmap[f] = new.get(f, mid)
685 else:
685 else:
686 nmap[f] = mid # keep ours
686 nmap[f] = mid # keep ours
687 del omap[f]
687 del omap[f]
688 elif f in amap:
688 elif f in amap:
689 if mid != amap[f]:
689 if mid != amap[f]:
690 self.ui.debug("local changed %s which other deleted\n" % f)
690 self.ui.debug("local changed %s which other deleted\n" % f)
691 pass # we should prompt here
691 pass # we should prompt here
692 else:
692 else:
693 self.ui.debug("other deleted %s\n" % f)
693 self.ui.debug("other deleted %s\n" % f)
694 pass # other deleted it
694 pass # other deleted it
695 else:
695 else:
696 self.ui.debug("local created %s\n" %f)
696 self.ui.debug("local created %s\n" %f)
697 nmap[f] = mid # we created it
697 nmap[f] = mid # we created it
698
698
699 del mmap
699 del mmap
700
700
701 for f, oid in omap.iteritems():
701 for f, oid in omap.iteritems():
702 if f in amap:
702 if f in amap:
703 if oid != amap[f]:
703 if oid != amap[f]:
704 self.ui.debug("other changed %s which we deleted\n" % f)
704 self.ui.debug("other changed %s which we deleted\n" % f)
705 pass # this is the nasty case, we should prompt
705 pass # this is the nasty case, we should prompt
706 else:
706 else:
707 pass # probably safe
707 pass # probably safe
708 else:
708 else:
709 self.ui.debug("remote created %s\n" % f)
709 self.ui.debug("remote created %s\n" % f)
710 nmap[f] = new.get(f, oid) # remote created it
710 nmap[f] = new.get(f, oid) # remote created it
711
711
712 del omap
712 del omap
713 del amap
713 del amap
714
714
715 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
715 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
716
716
717 # Now all files and manifests are merged, we add the changed files
717 # Now all files and manifests are merged, we add the changed files
718 # and manifest id to the changelog
718 # and manifest id to the changelog
719 self.ui.status("committing merge changeset\n")
719 self.ui.status("committing merge changeset\n")
720 new = new.keys()
720 new = new.keys()
721 new.sort()
721 new.sort()
722 if co == cn: cn = -1
722 if co == cn: cn = -1
723
723
724 edittext = "\nHG: merge resolve\n" + \
724 edittext = "\nHG: merge resolve\n" + \
725 "".join(["HG: changed %s\n" % f for f in new])
725 "".join(["HG: changed %s\n" % f for f in new])
726 edittext = self.ui.edit(edittext)
726 edittext = self.ui.edit(edittext)
727 n = self.changelog.add(node, new, edittext, tr, co, cn)
727 n = self.changelog.add(node, new, edittext, tr, co, cn)
728
728
729 tr.close()
729 tr.close()
730
730
731 def merge3(self, fl, fn, my, other, transaction, link):
732 """perform a 3-way merge and append the result"""
733
734 def temp(prefix, node):
735 pre = "%s~%s." % (os.path.basename(fn), prefix)
736 (fd, name) = tempfile.mkstemp("", pre)
737 f = os.fdopen(fd, "w")
738 f.write(fl.revision(node))
739 f.close()
740 return name
741
742 base = fl.ancestor(my, other)
743 self.ui.note("resolving %s\n" % fn)
744 self.ui.debug("local %s remote %s ancestor %s\n" %
745 (short(my), short(other), short(base)))
746
747 if my == base:
748 text = fl.revision(other)
749 else:
750 a = temp("local", my)
751 b = temp("remote", other)
752 c = temp("parent", base)
753
754 cmd = os.environ["HGMERGE"]
755 self.ui.debug("invoking merge with %s\n" % cmd)
756 r = os.system("%s %s %s %s" % (cmd, a, b, c))
757 if r:
758 raise "Merge failed!"
759
760 text = open(a).read()
761 os.unlink(a)
762 os.unlink(b)
763 os.unlink(c)
764
765 return fl.addrevision(text, transaction, link, my, other)
766
731 class remoterepository:
767 class remoterepository:
732 def __init__(self, ui, path):
768 def __init__(self, ui, path):
733 self.url = path.replace("hg://", "http://", 1)
769 self.url = path.replace("hg://", "http://", 1)
734 self.ui = ui
770 self.ui = ui
735
771
736 def do_cmd(self, cmd, **args):
772 def do_cmd(self, cmd, **args):
737 self.ui.debug("sending %s command\n" % cmd)
773 self.ui.debug("sending %s command\n" % cmd)
738 q = {"cmd": cmd}
774 q = {"cmd": cmd}
739 q.update(args)
775 q.update(args)
740 qs = urllib.urlencode(q)
776 qs = urllib.urlencode(q)
741 cu = "%s?%s" % (self.url, qs)
777 cu = "%s?%s" % (self.url, qs)
742 return urllib.urlopen(cu)
778 return urllib.urlopen(cu)
743
779
744 def branches(self, nodes):
780 def branches(self, nodes):
745 n = " ".join(map(hex, nodes))
781 n = " ".join(map(hex, nodes))
746 d = self.do_cmd("branches", nodes=n).read()
782 d = self.do_cmd("branches", nodes=n).read()
747 br = [ map(bin, b.split(" ")) for b in d.splitlines() ]
783 br = [ map(bin, b.split(" ")) for b in d.splitlines() ]
748 return br
784 return br
749
785
750 def between(self, pairs):
786 def between(self, pairs):
751 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
787 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
752 d = self.do_cmd("between", pairs=n).read()
788 d = self.do_cmd("between", pairs=n).read()
753 p = [ map(bin, l.split(" ")) for l in d.splitlines() ]
789 p = [ map(bin, l.split(" ")) for l in d.splitlines() ]
754 return p
790 return p
755
791
756 def changegroup(self, nodes):
792 def changegroup(self, nodes):
757 n = " ".join(map(hex, nodes))
793 n = " ".join(map(hex, nodes))
758 zd = zlib.decompressobj()
794 zd = zlib.decompressobj()
759 f = self.do_cmd("changegroup", roots=n)
795 f = self.do_cmd("changegroup", roots=n)
760 while 1:
796 while 1:
761 d = f.read(4096)
797 d = f.read(4096)
762 if not d:
798 if not d:
763 yield zd.flush()
799 yield zd.flush()
764 break
800 break
765 yield zd.decompress(d)
801 yield zd.decompress(d)
766
802
767 def repository(ui, path=None, create=0):
803 def repository(ui, path=None, create=0):
768 if path and path[:5] == "hg://":
804 if path and path[:5] == "hg://":
769 return remoterepository(ui, path)
805 return remoterepository(ui, path)
770 else:
806 else:
771 return localrepository(ui, path, create)
807 return localrepository(ui, path, create)
772
808
773 class ui:
809 class ui:
774 def __init__(self, verbose=False, debug=False, quiet=False):
810 def __init__(self, verbose=False, debug=False, quiet=False):
775 self.quiet = quiet and not verbose and not debug
811 self.quiet = quiet and not verbose and not debug
776 self.verbose = verbose or debug
812 self.verbose = verbose or debug
777 self.debugflag = debug
813 self.debugflag = debug
778 def write(self, *args):
814 def write(self, *args):
779 for a in args:
815 for a in args:
780 sys.stdout.write(str(a))
816 sys.stdout.write(str(a))
781 def prompt(self, msg, pat):
817 def prompt(self, msg, pat):
782 while 1:
818 while 1:
783 sys.stdout.write(msg)
819 sys.stdout.write(msg)
784 r = sys.stdin.readline()[:-1]
820 r = sys.stdin.readline()[:-1]
785 if re.match(pat, r):
821 if re.match(pat, r):
786 return r
822 return r
787 def status(self, *msg):
823 def status(self, *msg):
788 if not self.quiet: self.write(*msg)
824 if not self.quiet: self.write(*msg)
789 def warn(self, msg):
825 def warn(self, msg):
790 self.write(*msg)
826 self.write(*msg)
791 def note(self, msg):
827 def note(self, msg):
792 if self.verbose: self.write(*msg)
828 if self.verbose: self.write(*msg)
793 def debug(self, msg):
829 def debug(self, msg):
794 if self.debugflag: self.write(*msg)
830 if self.debugflag: self.write(*msg)
795 def edit(self, text):
831 def edit(self, text):
796 (fd, name) = tempfile.mkstemp("hg")
832 (fd, name) = tempfile.mkstemp("hg")
797 f = os.fdopen(fd, "w")
833 f = os.fdopen(fd, "w")
798 f.write(text)
834 f.write(text)
799 f.close()
835 f.close()
800
836
801 editor = os.environ.get("EDITOR", "vi")
837 editor = os.environ.get("EDITOR", "vi")
802 r = os.system("%s %s" % (editor, name))
838 r = os.system("%s %s" % (editor, name))
803 if r:
839 if r:
804 raise "Edit failed!"
840 raise "Edit failed!"
805
841
806 t = open(name).read()
842 t = open(name).read()
807 t = re.sub("(?m)^HG:.*\n", "", t)
843 t = re.sub("(?m)^HG:.*\n", "", t)
808
844
809 return t
845 return t
810
846
811
847
812 class httprangereader:
848 class httprangereader:
813 def __init__(self, url):
849 def __init__(self, url):
814 self.url = url
850 self.url = url
815 self.pos = 0
851 self.pos = 0
816 def seek(self, pos):
852 def seek(self, pos):
817 self.pos = pos
853 self.pos = pos
818 def read(self, bytes=None):
854 def read(self, bytes=None):
819 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
855 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
820 urllib2.install_opener(opener)
856 urllib2.install_opener(opener)
821 req = urllib2.Request(self.url)
857 req = urllib2.Request(self.url)
822 end = ''
858 end = ''
823 if bytes: end = self.pos + bytes
859 if bytes: end = self.pos + bytes
824 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
860 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
825 f = urllib2.urlopen(req)
861 f = urllib2.urlopen(req)
826 return f.read()
862 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now