##// END OF EJS Templates
merge: Fix bug where we overwrote local when local was newer...
mpm@selenic.com -
r273:4f817438 default
parent child Browse files
Show More
@@ -1,1189 +1,1190 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, os
8 import sys, struct, os
9 from revlog import *
9 from revlog import *
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 demandload(globals(), "tempfile byterange difflib")
12 demandload(globals(), "tempfile byterange difflib")
13
13
14 class filelog(revlog):
14 class filelog(revlog):
15 def __init__(self, opener, path):
15 def __init__(self, opener, path):
16 revlog.__init__(self, opener,
16 revlog.__init__(self, opener,
17 os.path.join("data", path + ".i"),
17 os.path.join("data", path + ".i"),
18 os.path.join("data", path + ".d"))
18 os.path.join("data", path + ".d"))
19
19
20 def read(self, node):
20 def read(self, node):
21 return self.revision(node)
21 return self.revision(node)
22 def add(self, text, transaction, link, p1=None, p2=None):
22 def add(self, text, transaction, link, p1=None, p2=None):
23 return self.addrevision(text, transaction, link, p1, p2)
23 return self.addrevision(text, transaction, link, p1, p2)
24
24
25 def annotate(self, node):
25 def annotate(self, node):
26
26
27 def decorate(text, rev):
27 def decorate(text, rev):
28 return [(rev, l) for l in text.splitlines(1)]
28 return [(rev, l) for l in text.splitlines(1)]
29
29
30 def strip(annotation):
30 def strip(annotation):
31 return [e[1] for e in annotation]
31 return [e[1] for e in annotation]
32
32
33 def pair(parent, child):
33 def pair(parent, child):
34 new = []
34 new = []
35 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
35 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
36 for o, m, n, s, t in sm.get_opcodes():
36 for o, m, n, s, t in sm.get_opcodes():
37 if o == 'equal':
37 if o == 'equal':
38 new += parent[m:n]
38 new += parent[m:n]
39 else:
39 else:
40 new += child[s:t]
40 new += child[s:t]
41 return new
41 return new
42
42
43 # find all ancestors
43 # find all ancestors
44 needed = {node:1}
44 needed = {node:1}
45 visit = [node]
45 visit = [node]
46 while visit:
46 while visit:
47 n = visit.pop(0)
47 n = visit.pop(0)
48 for p in self.parents(n):
48 for p in self.parents(n):
49 if p not in needed:
49 if p not in needed:
50 needed[p] = 1
50 needed[p] = 1
51 visit.append(p)
51 visit.append(p)
52 else:
52 else:
53 # count how many times we'll use this
53 # count how many times we'll use this
54 needed[p] += 1
54 needed[p] += 1
55
55
56 # sort by revision which is a topological order
56 # sort by revision which is a topological order
57 visit = needed.keys()
57 visit = needed.keys()
58 visit = [ (self.rev(n), n) for n in visit ]
58 visit = [ (self.rev(n), n) for n in visit ]
59 visit.sort()
59 visit.sort()
60 visit = [ p[1] for p in visit ]
60 visit = [ p[1] for p in visit ]
61 hist = {}
61 hist = {}
62
62
63 for n in visit:
63 for n in visit:
64 curr = decorate(self.read(n), self.linkrev(n))
64 curr = decorate(self.read(n), self.linkrev(n))
65 for p in self.parents(n):
65 for p in self.parents(n):
66 if p != nullid:
66 if p != nullid:
67 curr = pair(hist[p], curr)
67 curr = pair(hist[p], curr)
68 # trim the history of unneeded revs
68 # trim the history of unneeded revs
69 needed[p] -= 1
69 needed[p] -= 1
70 if not needed[p]:
70 if not needed[p]:
71 del hist[p]
71 del hist[p]
72 hist[n] = curr
72 hist[n] = curr
73
73
74 return hist[n]
74 return hist[n]
75
75
76 class manifest(revlog):
76 class manifest(revlog):
77 def __init__(self, opener):
77 def __init__(self, opener):
78 self.mapcache = None
78 self.mapcache = None
79 self.listcache = None
79 self.listcache = None
80 self.addlist = None
80 self.addlist = None
81 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
81 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
82
82
83 def read(self, node):
83 def read(self, node):
84 if self.mapcache and self.mapcache[0] == node:
84 if self.mapcache and self.mapcache[0] == node:
85 return self.mapcache[1].copy()
85 return self.mapcache[1].copy()
86 text = self.revision(node)
86 text = self.revision(node)
87 map = {}
87 map = {}
88 self.listcache = (text, text.splitlines(1))
88 self.listcache = (text, text.splitlines(1))
89 for l in self.listcache[1]:
89 for l in self.listcache[1]:
90 (f, n) = l.split('\0')
90 (f, n) = l.split('\0')
91 map[f] = bin(n[:40])
91 map[f] = bin(n[:40])
92 self.mapcache = (node, map)
92 self.mapcache = (node, map)
93 return map
93 return map
94
94
95 def diff(self, a, b):
95 def diff(self, a, b):
96 # this is sneaky, as we're not actually using a and b
96 # this is sneaky, as we're not actually using a and b
97 if self.listcache and self.addlist and self.listcache[0] == a:
97 if self.listcache and self.addlist and self.listcache[0] == a:
98 d = mdiff.diff(self.listcache[1], self.addlist, 1)
98 d = mdiff.diff(self.listcache[1], self.addlist, 1)
99 if mdiff.patch(a, d) != b:
99 if mdiff.patch(a, d) != b:
100 sys.stderr.write("*** sortdiff failed, falling back ***\n")
100 sys.stderr.write("*** sortdiff failed, falling back ***\n")
101 return mdiff.textdiff(a, b)
101 return mdiff.textdiff(a, b)
102 return d
102 return d
103 else:
103 else:
104 return mdiff.textdiff(a, b)
104 return mdiff.textdiff(a, b)
105
105
106 def add(self, map, transaction, link, p1=None, p2=None):
106 def add(self, map, transaction, link, p1=None, p2=None):
107 files = map.keys()
107 files = map.keys()
108 files.sort()
108 files.sort()
109
109
110 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
110 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
111 text = "".join(self.addlist)
111 text = "".join(self.addlist)
112
112
113 n = self.addrevision(text, transaction, link, p1, p2)
113 n = self.addrevision(text, transaction, link, p1, p2)
114 self.mapcache = (n, map)
114 self.mapcache = (n, map)
115 self.listcache = (text, self.addlist)
115 self.listcache = (text, self.addlist)
116 self.addlist = None
116 self.addlist = None
117
117
118 return n
118 return n
119
119
120 class changelog(revlog):
120 class changelog(revlog):
121 def __init__(self, opener):
121 def __init__(self, opener):
122 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
122 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
123
123
124 def extract(self, text):
124 def extract(self, text):
125 if not text:
125 if not text:
126 return (nullid, "", "0", [], "")
126 return (nullid, "", "0", [], "")
127 last = text.index("\n\n")
127 last = text.index("\n\n")
128 desc = text[last + 2:]
128 desc = text[last + 2:]
129 l = text[:last].splitlines()
129 l = text[:last].splitlines()
130 manifest = bin(l[0])
130 manifest = bin(l[0])
131 user = l[1]
131 user = l[1]
132 date = l[2]
132 date = l[2]
133 files = l[3:]
133 files = l[3:]
134 return (manifest, user, date, files, desc)
134 return (manifest, user, date, files, desc)
135
135
136 def read(self, node):
136 def read(self, node):
137 return self.extract(self.revision(node))
137 return self.extract(self.revision(node))
138
138
139 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
139 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
140 user=None, date=None):
140 user=None, date=None):
141 user = (user or
141 user = (user or
142 os.environ.get("HGUSER") or
142 os.environ.get("HGUSER") or
143 os.environ.get("EMAIL") or
143 os.environ.get("EMAIL") or
144 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
144 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
145 date = date or "%d %d" % (time.time(), time.timezone)
145 date = date or "%d %d" % (time.time(), time.timezone)
146 list.sort()
146 list.sort()
147 l = [hex(manifest), user, date] + list + ["", desc]
147 l = [hex(manifest), user, date] + list + ["", desc]
148 text = "\n".join(l)
148 text = "\n".join(l)
149 return self.addrevision(text, transaction, self.count(), p1, p2)
149 return self.addrevision(text, transaction, self.count(), p1, p2)
150
150
151 class dirstate:
151 class dirstate:
152 def __init__(self, opener, ui, root):
152 def __init__(self, opener, ui, root):
153 self.opener = opener
153 self.opener = opener
154 self.root = root
154 self.root = root
155 self.dirty = 0
155 self.dirty = 0
156 self.ui = ui
156 self.ui = ui
157 self.map = None
157 self.map = None
158 self.pl = None
158 self.pl = None
159
159
160 def __del__(self):
160 def __del__(self):
161 if self.dirty:
161 if self.dirty:
162 self.write()
162 self.write()
163
163
164 def __getitem__(self, key):
164 def __getitem__(self, key):
165 try:
165 try:
166 return self.map[key]
166 return self.map[key]
167 except TypeError:
167 except TypeError:
168 self.read()
168 self.read()
169 return self[key]
169 return self[key]
170
170
171 def __contains__(self, key):
171 def __contains__(self, key):
172 if not self.map: self.read()
172 if not self.map: self.read()
173 return key in self.map
173 return key in self.map
174
174
175 def parents(self):
175 def parents(self):
176 if not self.pl:
176 if not self.pl:
177 self.read()
177 self.read()
178 return self.pl
178 return self.pl
179
179
180 def setparents(self, p1, p2 = nullid):
180 def setparents(self, p1, p2 = nullid):
181 self.dirty = 1
181 self.dirty = 1
182 self.pl = p1, p2
182 self.pl = p1, p2
183
183
184 def state(self, key):
184 def state(self, key):
185 try:
185 try:
186 return self[key][0]
186 return self[key][0]
187 except KeyError:
187 except KeyError:
188 return "?"
188 return "?"
189
189
190 def read(self):
190 def read(self):
191 if self.map is not None: return self.map
191 if self.map is not None: return self.map
192
192
193 self.map = {}
193 self.map = {}
194 self.pl = [nullid, nullid]
194 self.pl = [nullid, nullid]
195 try:
195 try:
196 st = self.opener("dirstate").read()
196 st = self.opener("dirstate").read()
197 except: return
197 except: return
198
198
199 self.pl = [st[:20], st[20: 40]]
199 self.pl = [st[:20], st[20: 40]]
200
200
201 pos = 40
201 pos = 40
202 while pos < len(st):
202 while pos < len(st):
203 e = struct.unpack(">cllll", st[pos:pos+17])
203 e = struct.unpack(">cllll", st[pos:pos+17])
204 l = e[4]
204 l = e[4]
205 pos += 17
205 pos += 17
206 f = st[pos:pos + l]
206 f = st[pos:pos + l]
207 self.map[f] = e[:4]
207 self.map[f] = e[:4]
208 pos += l
208 pos += l
209
209
210 def update(self, files, state):
210 def update(self, files, state):
211 ''' current states:
211 ''' current states:
212 n normal
212 n normal
213 m needs merging
213 m needs merging
214 r marked for removal
214 r marked for removal
215 a marked for addition'''
215 a marked for addition'''
216
216
217 if not files: return
217 if not files: return
218 self.read()
218 self.read()
219 self.dirty = 1
219 self.dirty = 1
220 for f in files:
220 for f in files:
221 if state == "r":
221 if state == "r":
222 self.map[f] = ('r', 0, 0, 0)
222 self.map[f] = ('r', 0, 0, 0)
223 else:
223 else:
224 s = os.stat(os.path.join(self.root, f))
224 s = os.stat(os.path.join(self.root, f))
225 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
225 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
226
226
227 def forget(self, files):
227 def forget(self, files):
228 if not files: return
228 if not files: return
229 self.read()
229 self.read()
230 self.dirty = 1
230 self.dirty = 1
231 for f in files:
231 for f in files:
232 try:
232 try:
233 del self.map[f]
233 del self.map[f]
234 except KeyError:
234 except KeyError:
235 self.ui.warn("not in dirstate: %s!\n" % f)
235 self.ui.warn("not in dirstate: %s!\n" % f)
236 pass
236 pass
237
237
238 def clear(self):
238 def clear(self):
239 self.map = {}
239 self.map = {}
240 self.dirty = 1
240 self.dirty = 1
241
241
242 def write(self):
242 def write(self):
243 st = self.opener("dirstate", "w")
243 st = self.opener("dirstate", "w")
244 st.write("".join(self.pl))
244 st.write("".join(self.pl))
245 for f, e in self.map.items():
245 for f, e in self.map.items():
246 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
246 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
247 st.write(e + f)
247 st.write(e + f)
248 self.dirty = 0
248 self.dirty = 0
249
249
250 def copy(self):
250 def copy(self):
251 self.read()
251 self.read()
252 return self.map.copy()
252 return self.map.copy()
253
253
254 # used to avoid circular references so destructors work
254 # used to avoid circular references so destructors work
255 def opener(base):
255 def opener(base):
256 p = base
256 p = base
257 def o(path, mode="r"):
257 def o(path, mode="r"):
258 if p[:7] == "http://":
258 if p[:7] == "http://":
259 f = os.path.join(p, urllib.quote(path))
259 f = os.path.join(p, urllib.quote(path))
260 return httprangereader(f)
260 return httprangereader(f)
261
261
262 f = os.path.join(p, path)
262 f = os.path.join(p, path)
263
263
264 if mode != "r":
264 if mode != "r":
265 try:
265 try:
266 s = os.stat(f)
266 s = os.stat(f)
267 except OSError:
267 except OSError:
268 d = os.path.dirname(f)
268 d = os.path.dirname(f)
269 if not os.path.isdir(d):
269 if not os.path.isdir(d):
270 os.makedirs(d)
270 os.makedirs(d)
271 else:
271 else:
272 if s.st_nlink > 1:
272 if s.st_nlink > 1:
273 file(f + ".tmp", "w").write(file(f).read())
273 file(f + ".tmp", "w").write(file(f).read())
274 os.rename(f+".tmp", f)
274 os.rename(f+".tmp", f)
275
275
276 return file(f, mode)
276 return file(f, mode)
277
277
278 return o
278 return o
279
279
280 class localrepository:
280 class localrepository:
281 def __init__(self, ui, path=None, create=0):
281 def __init__(self, ui, path=None, create=0):
282 self.remote = 0
282 self.remote = 0
283 if path and path[:7] == "http://":
283 if path and path[:7] == "http://":
284 self.remote = 1
284 self.remote = 1
285 self.path = path
285 self.path = path
286 else:
286 else:
287 if not path:
287 if not path:
288 p = os.getcwd()
288 p = os.getcwd()
289 while not os.path.isdir(os.path.join(p, ".hg")):
289 while not os.path.isdir(os.path.join(p, ".hg")):
290 p = os.path.dirname(p)
290 p = os.path.dirname(p)
291 if p == "/": raise "No repo found"
291 if p == "/": raise "No repo found"
292 path = p
292 path = p
293 self.path = os.path.join(path, ".hg")
293 self.path = os.path.join(path, ".hg")
294
294
295 self.root = path
295 self.root = path
296 self.ui = ui
296 self.ui = ui
297
297
298 if create:
298 if create:
299 os.mkdir(self.path)
299 os.mkdir(self.path)
300 os.mkdir(self.join("data"))
300 os.mkdir(self.join("data"))
301
301
302 self.opener = opener(self.path)
302 self.opener = opener(self.path)
303 self.manifest = manifest(self.opener)
303 self.manifest = manifest(self.opener)
304 self.changelog = changelog(self.opener)
304 self.changelog = changelog(self.opener)
305 self.ignorelist = None
305 self.ignorelist = None
306 self.tags = None
306 self.tags = None
307
307
308 if not self.remote:
308 if not self.remote:
309 self.dirstate = dirstate(self.opener, ui, self.root)
309 self.dirstate = dirstate(self.opener, ui, self.root)
310
310
311 def ignore(self, f):
311 def ignore(self, f):
312 if self.ignorelist is None:
312 if self.ignorelist is None:
313 self.ignorelist = []
313 self.ignorelist = []
314 try:
314 try:
315 l = open(os.path.join(self.root, ".hgignore"))
315 l = open(os.path.join(self.root, ".hgignore"))
316 for pat in l:
316 for pat in l:
317 if pat != "\n":
317 if pat != "\n":
318 self.ignorelist.append(re.compile(pat[:-1]))
318 self.ignorelist.append(re.compile(pat[:-1]))
319 except IOError: pass
319 except IOError: pass
320 for pat in self.ignorelist:
320 for pat in self.ignorelist:
321 if pat.search(f): return True
321 if pat.search(f): return True
322 return False
322 return False
323
323
324 def lookup(self, key):
324 def lookup(self, key):
325 if self.tags is None:
325 if self.tags is None:
326 self.tags = {}
326 self.tags = {}
327 try:
327 try:
328 # read each head of the tags file, ending with the tip
328 # read each head of the tags file, ending with the tip
329 # and add each tag found to the map, with "newer" ones
329 # and add each tag found to the map, with "newer" ones
330 # taking precedence
330 # taking precedence
331 fl = self.file(".hgtags")
331 fl = self.file(".hgtags")
332 h = fl.heads()
332 h = fl.heads()
333 h.reverse()
333 h.reverse()
334 for r in h:
334 for r in h:
335 for l in fl.revision(r).splitlines():
335 for l in fl.revision(r).splitlines():
336 if l:
336 if l:
337 n, k = l.split(" ")
337 n, k = l.split(" ")
338 self.tags[k] = bin(n)
338 self.tags[k] = bin(n)
339 except KeyError: pass
339 except KeyError: pass
340 try:
340 try:
341 return self.tags[key]
341 return self.tags[key]
342 except KeyError:
342 except KeyError:
343 return self.changelog.lookup(key)
343 return self.changelog.lookup(key)
344
344
345 def join(self, f):
345 def join(self, f):
346 return os.path.join(self.path, f)
346 return os.path.join(self.path, f)
347
347
348 def wjoin(self, f):
348 def wjoin(self, f):
349 return os.path.join(self.root, f)
349 return os.path.join(self.root, f)
350
350
351 def file(self, f):
351 def file(self, f):
352 if f[0] == '/': f = f[1:]
352 if f[0] == '/': f = f[1:]
353 return filelog(self.opener, f)
353 return filelog(self.opener, f)
354
354
355 def transaction(self):
355 def transaction(self):
356 # save dirstate for undo
356 # save dirstate for undo
357 try:
357 try:
358 ds = self.opener("dirstate").read()
358 ds = self.opener("dirstate").read()
359 except IOError:
359 except IOError:
360 ds = ""
360 ds = ""
361 self.opener("undo.dirstate", "w").write(ds)
361 self.opener("undo.dirstate", "w").write(ds)
362
362
363 return transaction.transaction(self.opener, self.join("journal"),
363 return transaction.transaction(self.opener, self.join("journal"),
364 self.join("undo"))
364 self.join("undo"))
365
365
366 def recover(self):
366 def recover(self):
367 lock = self.lock()
367 lock = self.lock()
368 if os.path.exists(self.join("recover")):
368 if os.path.exists(self.join("recover")):
369 self.ui.status("attempting to rollback interrupted transaction\n")
369 self.ui.status("attempting to rollback interrupted transaction\n")
370 return transaction.rollback(self.opener, self.join("recover"))
370 return transaction.rollback(self.opener, self.join("recover"))
371 else:
371 else:
372 self.ui.warn("no interrupted transaction available\n")
372 self.ui.warn("no interrupted transaction available\n")
373
373
374 def undo(self):
374 def undo(self):
375 lock = self.lock()
375 lock = self.lock()
376 if os.path.exists(self.join("undo")):
376 if os.path.exists(self.join("undo")):
377 self.ui.status("attempting to rollback last transaction\n")
377 self.ui.status("attempting to rollback last transaction\n")
378 transaction.rollback(self.opener, self.join("undo"))
378 transaction.rollback(self.opener, self.join("undo"))
379 self.dirstate = None
379 self.dirstate = None
380 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
380 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
381 self.dirstate = dirstate(self.opener, self.ui, self.root)
381 self.dirstate = dirstate(self.opener, self.ui, self.root)
382 else:
382 else:
383 self.ui.warn("no undo information available\n")
383 self.ui.warn("no undo information available\n")
384
384
385 def lock(self, wait = 1):
385 def lock(self, wait = 1):
386 try:
386 try:
387 return lock.lock(self.join("lock"), 0)
387 return lock.lock(self.join("lock"), 0)
388 except lock.LockHeld, inst:
388 except lock.LockHeld, inst:
389 if wait:
389 if wait:
390 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
390 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
391 return lock.lock(self.join("lock"), wait)
391 return lock.lock(self.join("lock"), wait)
392 raise inst
392 raise inst
393
393
394 def rawcommit(self, files, text, user, date, p1=None, p2=None):
394 def rawcommit(self, files, text, user, date, p1=None, p2=None):
395 p1 = p1 or self.dirstate.parents()[0] or nullid
395 p1 = p1 or self.dirstate.parents()[0] or nullid
396 p2 = p2 or self.dirstate.parents()[1] or nullid
396 p2 = p2 or self.dirstate.parents()[1] or nullid
397 pchange = self.changelog.read(p1)
397 pchange = self.changelog.read(p1)
398 pmmap = self.manifest.read(pchange[0])
398 pmmap = self.manifest.read(pchange[0])
399 tr = self.transaction()
399 tr = self.transaction()
400 mmap = {}
400 mmap = {}
401 linkrev = self.changelog.count()
401 linkrev = self.changelog.count()
402 for f in files:
402 for f in files:
403 try:
403 try:
404 t = file(f).read()
404 t = file(f).read()
405 except IOError:
405 except IOError:
406 self.ui.warn("Read file %s error, skipped\n" % f)
406 self.ui.warn("Read file %s error, skipped\n" % f)
407 continue
407 continue
408 r = self.file(f)
408 r = self.file(f)
409 # FIXME - need to find both parents properly
409 # FIXME - need to find both parents properly
410 prev = pmmap.get(f, nullid)
410 prev = pmmap.get(f, nullid)
411 mmap[f] = r.add(t, tr, linkrev, prev)
411 mmap[f] = r.add(t, tr, linkrev, prev)
412
412
413 mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
413 mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
414 n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
414 n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
415 tr.close()
415 tr.close()
416 self.dirstate.setparents(p1, p2)
416 self.dirstate.setparents(p1, p2)
417 self.dirstate.clear()
417 self.dirstate.clear()
418 self.dirstate.update(mmap.keys(), "n")
418 self.dirstate.update(mmap.keys(), "n")
419
419
420 def commit(self, files = None, text = ""):
420 def commit(self, files = None, text = ""):
421 commit = []
421 commit = []
422 remove = []
422 remove = []
423 if files:
423 if files:
424 for f in files:
424 for f in files:
425 s = self.dirstate.state(f)
425 s = self.dirstate.state(f)
426 if s in 'nmai':
426 if s in 'nmai':
427 commit.append(f)
427 commit.append(f)
428 elif s == 'r':
428 elif s == 'r':
429 remove.append(f)
429 remove.append(f)
430 else:
430 else:
431 self.ui.warn("%s not tracked!\n" % f)
431 self.ui.warn("%s not tracked!\n" % f)
432 else:
432 else:
433 (c, a, d, u) = self.diffdir(self.root)
433 (c, a, d, u) = self.diffdir(self.root)
434 commit = c + a
434 commit = c + a
435 remove = d
435 remove = d
436
436
437 if not commit and not remove:
437 if not commit and not remove:
438 self.ui.status("nothing changed\n")
438 self.ui.status("nothing changed\n")
439 return
439 return
440
440
441 p1, p2 = self.dirstate.parents()
441 p1, p2 = self.dirstate.parents()
442 c1 = self.changelog.read(p1)
442 c1 = self.changelog.read(p1)
443 c2 = self.changelog.read(p2)
443 c2 = self.changelog.read(p2)
444 m1 = self.manifest.read(c1[0])
444 m1 = self.manifest.read(c1[0])
445 m2 = self.manifest.read(c2[0])
445 m2 = self.manifest.read(c2[0])
446 lock = self.lock()
446 lock = self.lock()
447 tr = self.transaction()
447 tr = self.transaction()
448
448
449 # check in files
449 # check in files
450 new = {}
450 new = {}
451 linkrev = self.changelog.count()
451 linkrev = self.changelog.count()
452 commit.sort()
452 commit.sort()
453 for f in commit:
453 for f in commit:
454 self.ui.note(f + "\n")
454 self.ui.note(f + "\n")
455 try:
455 try:
456 t = file(self.wjoin(f)).read()
456 t = file(self.wjoin(f)).read()
457 except IOError:
457 except IOError:
458 self.warn("trouble committing %s!\n" % f)
458 self.warn("trouble committing %s!\n" % f)
459 raise
459 raise
460
460
461 r = self.file(f)
461 r = self.file(f)
462 fp1 = m1.get(f, nullid)
462 fp1 = m1.get(f, nullid)
463 fp2 = m2.get(f, nullid)
463 fp2 = m2.get(f, nullid)
464 new[f] = r.add(t, tr, linkrev, fp1, fp2)
464 new[f] = r.add(t, tr, linkrev, fp1, fp2)
465
465
466 # update manifest
466 # update manifest
467 m1.update(new)
467 m1.update(new)
468 for f in remove: del m1[f]
468 for f in remove: del m1[f]
469 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
469 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
470
470
471 # add changeset
471 # add changeset
472 new = new.keys()
472 new = new.keys()
473 new.sort()
473 new.sort()
474
474
475 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
475 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
476 edittext += "".join(["HG: changed %s\n" % f for f in new])
476 edittext += "".join(["HG: changed %s\n" % f for f in new])
477 edittext += "".join(["HG: removed %s\n" % f for f in remove])
477 edittext += "".join(["HG: removed %s\n" % f for f in remove])
478 edittext = self.ui.edit(edittext)
478 edittext = self.ui.edit(edittext)
479
479
480 n = self.changelog.add(mn, new, edittext, tr, p1, p2)
480 n = self.changelog.add(mn, new, edittext, tr, p1, p2)
481 tr.close()
481 tr.close()
482
482
483 self.dirstate.setparents(n)
483 self.dirstate.setparents(n)
484 self.dirstate.update(new, "n")
484 self.dirstate.update(new, "n")
485 self.dirstate.forget(remove)
485 self.dirstate.forget(remove)
486
486
487 def diffdir(self, path, changeset = None):
487 def diffdir(self, path, changeset = None):
488 changed = []
488 changed = []
489 added = []
489 added = []
490 unknown = []
490 unknown = []
491 mf = {}
491 mf = {}
492
492
493 if changeset:
493 if changeset:
494 change = self.changelog.read(changeset)
494 change = self.changelog.read(changeset)
495 mf = self.manifest.read(change[0])
495 mf = self.manifest.read(change[0])
496 dc = dict.fromkeys(mf)
496 dc = dict.fromkeys(mf)
497 else:
497 else:
498 changeset = self.dirstate.parents()[0]
498 changeset = self.dirstate.parents()[0]
499 change = self.changelog.read(changeset)
499 change = self.changelog.read(changeset)
500 mf = self.manifest.read(change[0])
500 mf = self.manifest.read(change[0])
501 dc = self.dirstate.copy()
501 dc = self.dirstate.copy()
502
502
503 def fcmp(fn):
503 def fcmp(fn):
504 t1 = file(self.wjoin(fn)).read()
504 t1 = file(self.wjoin(fn)).read()
505 t2 = self.file(fn).revision(mf[fn])
505 t2 = self.file(fn).revision(mf[fn])
506 return cmp(t1, t2)
506 return cmp(t1, t2)
507
507
508 for dir, subdirs, files in os.walk(self.root):
508 for dir, subdirs, files in os.walk(self.root):
509 d = dir[len(self.root)+1:]
509 d = dir[len(self.root)+1:]
510 if ".hg" in subdirs: subdirs.remove(".hg")
510 if ".hg" in subdirs: subdirs.remove(".hg")
511
511
512 for f in files:
512 for f in files:
513 fn = os.path.join(d, f)
513 fn = os.path.join(d, f)
514 try: s = os.stat(os.path.join(self.root, fn))
514 try: s = os.stat(os.path.join(self.root, fn))
515 except: continue
515 except: continue
516 if fn in dc:
516 if fn in dc:
517 c = dc[fn]
517 c = dc[fn]
518 del dc[fn]
518 del dc[fn]
519 if not c:
519 if not c:
520 if fcmp(fn):
520 if fcmp(fn):
521 changed.append(fn)
521 changed.append(fn)
522 elif c[0] == 'm':
522 elif c[0] == 'm':
523 changed.append(fn)
523 changed.append(fn)
524 elif c[0] == 'a':
524 elif c[0] == 'a':
525 added.append(fn)
525 added.append(fn)
526 elif c[0] == 'r':
526 elif c[0] == 'r':
527 unknown.append(fn)
527 unknown.append(fn)
528 elif c[2] != s.st_size:
528 elif c[2] != s.st_size:
529 changed.append(fn)
529 changed.append(fn)
530 elif c[1] != s.st_mode or c[3] != s.st_mtime:
530 elif c[1] != s.st_mode or c[3] != s.st_mtime:
531 if fcmp(fn):
531 if fcmp(fn):
532 changed.append(fn)
532 changed.append(fn)
533 else:
533 else:
534 if self.ignore(fn): continue
534 if self.ignore(fn): continue
535 unknown.append(fn)
535 unknown.append(fn)
536
536
537 deleted = dc.keys()
537 deleted = dc.keys()
538 deleted.sort()
538 deleted.sort()
539
539
540 return (changed, added, deleted, unknown)
540 return (changed, added, deleted, unknown)
541
541
542 def diffrevs(self, node1, node2):
542 def diffrevs(self, node1, node2):
543 changed, added = [], []
543 changed, added = [], []
544
544
545 change = self.changelog.read(node1)
545 change = self.changelog.read(node1)
546 mf1 = self.manifest.read(change[0])
546 mf1 = self.manifest.read(change[0])
547 change = self.changelog.read(node2)
547 change = self.changelog.read(node2)
548 mf2 = self.manifest.read(change[0])
548 mf2 = self.manifest.read(change[0])
549
549
550 for fn in mf2:
550 for fn in mf2:
551 if mf1.has_key(fn):
551 if mf1.has_key(fn):
552 if mf1[fn] != mf2[fn]:
552 if mf1[fn] != mf2[fn]:
553 changed.append(fn)
553 changed.append(fn)
554 del mf1[fn]
554 del mf1[fn]
555 else:
555 else:
556 added.append(fn)
556 added.append(fn)
557
557
558 deleted = mf1.keys()
558 deleted = mf1.keys()
559 deleted.sort()
559 deleted.sort()
560
560
561 return (changed, added, deleted)
561 return (changed, added, deleted)
562
562
563 def add(self, list):
563 def add(self, list):
564 for f in list:
564 for f in list:
565 p = self.wjoin(f)
565 p = self.wjoin(f)
566 if not os.path.isfile(p):
566 if not os.path.isfile(p):
567 self.ui.warn("%s does not exist!\n" % f)
567 self.ui.warn("%s does not exist!\n" % f)
568 elif self.dirstate.state(f) == 'n':
568 elif self.dirstate.state(f) == 'n':
569 self.ui.warn("%s already tracked!\n" % f)
569 self.ui.warn("%s already tracked!\n" % f)
570 else:
570 else:
571 self.dirstate.update([f], "a")
571 self.dirstate.update([f], "a")
572
572
573 def forget(self, list):
573 def forget(self, list):
574 for f in list:
574 for f in list:
575 if self.dirstate.state(f) not in 'ai':
575 if self.dirstate.state(f) not in 'ai':
576 self.ui.warn("%s not added!\n" % f)
576 self.ui.warn("%s not added!\n" % f)
577 else:
577 else:
578 self.dirstate.forget([f])
578 self.dirstate.forget([f])
579
579
580 def remove(self, list):
580 def remove(self, list):
581 for f in list:
581 for f in list:
582 p = self.wjoin(f)
582 p = self.wjoin(f)
583 if os.path.isfile(p):
583 if os.path.isfile(p):
584 self.ui.warn("%s still exists!\n" % f)
584 self.ui.warn("%s still exists!\n" % f)
585 elif f not in self.dirstate:
585 elif f not in self.dirstate:
586 self.ui.warn("%s not tracked!\n" % f)
586 self.ui.warn("%s not tracked!\n" % f)
587 else:
587 else:
588 self.dirstate.update([f], "r")
588 self.dirstate.update([f], "r")
589
589
590 def heads(self):
590 def heads(self):
591 return self.changelog.heads()
591 return self.changelog.heads()
592
592
593 def branches(self, nodes):
593 def branches(self, nodes):
594 if not nodes: nodes = [self.changelog.tip()]
594 if not nodes: nodes = [self.changelog.tip()]
595 b = []
595 b = []
596 for n in nodes:
596 for n in nodes:
597 t = n
597 t = n
598 while n:
598 while n:
599 p = self.changelog.parents(n)
599 p = self.changelog.parents(n)
600 if p[1] != nullid or p[0] == nullid:
600 if p[1] != nullid or p[0] == nullid:
601 b.append((t, n, p[0], p[1]))
601 b.append((t, n, p[0], p[1]))
602 break
602 break
603 n = p[0]
603 n = p[0]
604 return b
604 return b
605
605
606 def between(self, pairs):
606 def between(self, pairs):
607 r = []
607 r = []
608
608
609 for top, bottom in pairs:
609 for top, bottom in pairs:
610 n, l, i = top, [], 0
610 n, l, i = top, [], 0
611 f = 1
611 f = 1
612
612
613 while n != bottom:
613 while n != bottom:
614 p = self.changelog.parents(n)[0]
614 p = self.changelog.parents(n)[0]
615 if i == f:
615 if i == f:
616 l.append(n)
616 l.append(n)
617 f = f * 2
617 f = f * 2
618 n = p
618 n = p
619 i += 1
619 i += 1
620
620
621 r.append(l)
621 r.append(l)
622
622
623 return r
623 return r
624
624
625 def newer(self, nodes):
625 def newer(self, nodes):
626 m = {}
626 m = {}
627 nl = []
627 nl = []
628 pm = {}
628 pm = {}
629 cl = self.changelog
629 cl = self.changelog
630 t = l = cl.count()
630 t = l = cl.count()
631
631
632 # find the lowest numbered node
632 # find the lowest numbered node
633 for n in nodes:
633 for n in nodes:
634 l = min(l, cl.rev(n))
634 l = min(l, cl.rev(n))
635 m[n] = 1
635 m[n] = 1
636
636
637 for i in xrange(l, t):
637 for i in xrange(l, t):
638 n = cl.node(i)
638 n = cl.node(i)
639 if n in m: # explicitly listed
639 if n in m: # explicitly listed
640 pm[n] = 1
640 pm[n] = 1
641 nl.append(n)
641 nl.append(n)
642 continue
642 continue
643 for p in cl.parents(n):
643 for p in cl.parents(n):
644 if p in pm: # parent listed
644 if p in pm: # parent listed
645 pm[n] = 1
645 pm[n] = 1
646 nl.append(n)
646 nl.append(n)
647 break
647 break
648
648
649 return nl
649 return nl
650
650
651 def getchangegroup(self, remote):
651 def getchangegroup(self, remote):
652 m = self.changelog.nodemap
652 m = self.changelog.nodemap
653 search = []
653 search = []
654 fetch = []
654 fetch = []
655 seen = {}
655 seen = {}
656 seenbranch = {}
656 seenbranch = {}
657
657
658 # if we have an empty repo, fetch everything
658 # if we have an empty repo, fetch everything
659 if self.changelog.tip() == nullid:
659 if self.changelog.tip() == nullid:
660 self.ui.status("requesting all changes\n")
660 self.ui.status("requesting all changes\n")
661 return remote.changegroup([nullid])
661 return remote.changegroup([nullid])
662
662
663 # otherwise, assume we're closer to the tip than the root
663 # otherwise, assume we're closer to the tip than the root
664 self.ui.status("searching for changes\n")
664 self.ui.status("searching for changes\n")
665 heads = remote.heads()
665 heads = remote.heads()
666 unknown = []
666 unknown = []
667 for h in heads:
667 for h in heads:
668 if h not in m:
668 if h not in m:
669 unknown.append(h)
669 unknown.append(h)
670
670
671 if not unknown:
671 if not unknown:
672 self.ui.status("nothing to do!\n")
672 self.ui.status("nothing to do!\n")
673 return None
673 return None
674
674
675 unknown = remote.branches(unknown)
675 unknown = remote.branches(unknown)
676 while unknown:
676 while unknown:
677 n = unknown.pop(0)
677 n = unknown.pop(0)
678 seen[n[0]] = 1
678 seen[n[0]] = 1
679
679
680 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
680 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
681 if n == nullid: break
681 if n == nullid: break
682 if n in seenbranch:
682 if n in seenbranch:
683 self.ui.debug("branch already found\n")
683 self.ui.debug("branch already found\n")
684 continue
684 continue
685 if n[1] and n[1] in m: # do we know the base?
685 if n[1] and n[1] in m: # do we know the base?
686 self.ui.debug("found incomplete branch %s:%s\n"
686 self.ui.debug("found incomplete branch %s:%s\n"
687 % (short(n[0]), short(n[1])))
687 % (short(n[0]), short(n[1])))
688 search.append(n) # schedule branch range for scanning
688 search.append(n) # schedule branch range for scanning
689 seenbranch[n] = 1
689 seenbranch[n] = 1
690 else:
690 else:
691 if n[2] in m and n[3] in m:
691 if n[2] in m and n[3] in m:
692 if n[1] not in fetch:
692 if n[1] not in fetch:
693 self.ui.debug("found new changeset %s\n" %
693 self.ui.debug("found new changeset %s\n" %
694 short(n[1]))
694 short(n[1]))
695 fetch.append(n[1]) # earliest unknown
695 fetch.append(n[1]) # earliest unknown
696 continue
696 continue
697
697
698 r = []
698 r = []
699 for a in n[2:4]:
699 for a in n[2:4]:
700 if a not in seen: r.append(a)
700 if a not in seen: r.append(a)
701
701
702 if r:
702 if r:
703 self.ui.debug("requesting %s\n" %
703 self.ui.debug("requesting %s\n" %
704 " ".join(map(short, r)))
704 " ".join(map(short, r)))
705 for b in remote.branches(r):
705 for b in remote.branches(r):
706 self.ui.debug("received %s:%s\n" %
706 self.ui.debug("received %s:%s\n" %
707 (short(b[0]), short(b[1])))
707 (short(b[0]), short(b[1])))
708 if b[0] not in m and b[0] not in seen:
708 if b[0] not in m and b[0] not in seen:
709 unknown.append(b)
709 unknown.append(b)
710
710
711 while search:
711 while search:
712 n = search.pop(0)
712 n = search.pop(0)
713 l = remote.between([(n[0], n[1])])[0]
713 l = remote.between([(n[0], n[1])])[0]
714 p = n[0]
714 p = n[0]
715 f = 1
715 f = 1
716 for i in l + [n[1]]:
716 for i in l + [n[1]]:
717 if i in m:
717 if i in m:
718 if f <= 2:
718 if f <= 2:
719 self.ui.debug("found new branch changeset %s\n" %
719 self.ui.debug("found new branch changeset %s\n" %
720 short(p))
720 short(p))
721 fetch.append(p)
721 fetch.append(p)
722 else:
722 else:
723 self.ui.debug("narrowed branch search to %s:%s\n"
723 self.ui.debug("narrowed branch search to %s:%s\n"
724 % (short(p), short(i)))
724 % (short(p), short(i)))
725 search.append((p, i))
725 search.append((p, i))
726 break
726 break
727 p, f = i, f * 2
727 p, f = i, f * 2
728
728
729 for f in fetch:
729 for f in fetch:
730 if f in m:
730 if f in m:
731 raise "already have", short(f[:4])
731 raise "already have", short(f[:4])
732
732
733 self.ui.note("adding new changesets starting at " +
733 self.ui.note("adding new changesets starting at " +
734 " ".join([short(f) for f in fetch]) + "\n")
734 " ".join([short(f) for f in fetch]) + "\n")
735
735
736 return remote.changegroup(fetch)
736 return remote.changegroup(fetch)
737
737
738 def changegroup(self, basenodes):
738 def changegroup(self, basenodes):
739 nodes = self.newer(basenodes)
739 nodes = self.newer(basenodes)
740
740
741 # construct the link map
741 # construct the link map
742 linkmap = {}
742 linkmap = {}
743 for n in nodes:
743 for n in nodes:
744 linkmap[self.changelog.rev(n)] = n
744 linkmap[self.changelog.rev(n)] = n
745
745
746 # construct a list of all changed files
746 # construct a list of all changed files
747 changed = {}
747 changed = {}
748 for n in nodes:
748 for n in nodes:
749 c = self.changelog.read(n)
749 c = self.changelog.read(n)
750 for f in c[3]:
750 for f in c[3]:
751 changed[f] = 1
751 changed[f] = 1
752 changed = changed.keys()
752 changed = changed.keys()
753 changed.sort()
753 changed.sort()
754
754
755 # the changegroup is changesets + manifests + all file revs
755 # the changegroup is changesets + manifests + all file revs
756 revs = [ self.changelog.rev(n) for n in nodes ]
756 revs = [ self.changelog.rev(n) for n in nodes ]
757
757
758 for y in self.changelog.group(linkmap): yield y
758 for y in self.changelog.group(linkmap): yield y
759 for y in self.manifest.group(linkmap): yield y
759 for y in self.manifest.group(linkmap): yield y
760 for f in changed:
760 for f in changed:
761 yield struct.pack(">l", len(f) + 4) + f
761 yield struct.pack(">l", len(f) + 4) + f
762 g = self.file(f).group(linkmap)
762 g = self.file(f).group(linkmap)
763 for y in g:
763 for y in g:
764 yield y
764 yield y
765
765
766 def addchangegroup(self, generator):
766 def addchangegroup(self, generator):
767
767
768 class genread:
768 class genread:
769 def __init__(self, generator):
769 def __init__(self, generator):
770 self.g = generator
770 self.g = generator
771 self.buf = ""
771 self.buf = ""
772 def read(self, l):
772 def read(self, l):
773 while l > len(self.buf):
773 while l > len(self.buf):
774 try:
774 try:
775 self.buf += self.g.next()
775 self.buf += self.g.next()
776 except StopIteration:
776 except StopIteration:
777 break
777 break
778 d, self.buf = self.buf[:l], self.buf[l:]
778 d, self.buf = self.buf[:l], self.buf[l:]
779 return d
779 return d
780
780
781 def getchunk():
781 def getchunk():
782 d = source.read(4)
782 d = source.read(4)
783 if not d: return ""
783 if not d: return ""
784 l = struct.unpack(">l", d)[0]
784 l = struct.unpack(">l", d)[0]
785 if l <= 4: return ""
785 if l <= 4: return ""
786 return source.read(l - 4)
786 return source.read(l - 4)
787
787
788 def getgroup():
788 def getgroup():
789 while 1:
789 while 1:
790 c = getchunk()
790 c = getchunk()
791 if not c: break
791 if not c: break
792 yield c
792 yield c
793
793
794 def csmap(x):
794 def csmap(x):
795 self.ui.debug("add changeset %s\n" % short(x))
795 self.ui.debug("add changeset %s\n" % short(x))
796 return self.changelog.count()
796 return self.changelog.count()
797
797
798 def revmap(x):
798 def revmap(x):
799 return self.changelog.rev(x)
799 return self.changelog.rev(x)
800
800
801 if not generator: return
801 if not generator: return
802 changesets = files = revisions = 0
802 changesets = files = revisions = 0
803
803
804 source = genread(generator)
804 source = genread(generator)
805 lock = self.lock()
805 lock = self.lock()
806 tr = self.transaction()
806 tr = self.transaction()
807
807
808 # pull off the changeset group
808 # pull off the changeset group
809 self.ui.status("adding changesets\n")
809 self.ui.status("adding changesets\n")
810 co = self.changelog.tip()
810 co = self.changelog.tip()
811 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
811 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
812 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
812 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
813
813
814 # pull off the manifest group
814 # pull off the manifest group
815 self.ui.status("adding manifests\n")
815 self.ui.status("adding manifests\n")
816 mm = self.manifest.tip()
816 mm = self.manifest.tip()
817 mo = self.manifest.addgroup(getgroup(), revmap, tr)
817 mo = self.manifest.addgroup(getgroup(), revmap, tr)
818
818
819 # process the files
819 # process the files
820 self.ui.status("adding file revisions\n")
820 self.ui.status("adding file revisions\n")
821 while 1:
821 while 1:
822 f = getchunk()
822 f = getchunk()
823 if not f: break
823 if not f: break
824 self.ui.debug("adding %s revisions\n" % f)
824 self.ui.debug("adding %s revisions\n" % f)
825 fl = self.file(f)
825 fl = self.file(f)
826 o = fl.tip()
826 o = fl.tip()
827 n = fl.addgroup(getgroup(), revmap, tr)
827 n = fl.addgroup(getgroup(), revmap, tr)
828 revisions += fl.rev(n) - fl.rev(o)
828 revisions += fl.rev(n) - fl.rev(o)
829 files += 1
829 files += 1
830
830
831 self.ui.status(("modified %d files, added %d changesets" +
831 self.ui.status(("modified %d files, added %d changesets" +
832 " and %d new revisions\n")
832 " and %d new revisions\n")
833 % (files, changesets, revisions))
833 % (files, changesets, revisions))
834
834
835 tr.close()
835 tr.close()
836 return
836 return
837
837
838 def update(self, node):
838 def update(self, node):
839 pl = self.dirstate.parents()
839 pl = self.dirstate.parents()
840 if pl[1] != nullid:
840 if pl[1] != nullid:
841 self.ui.warn("aborting: outstanding uncommitted merges\n")
841 self.ui.warn("aborting: outstanding uncommitted merges\n")
842 return
842 return
843
843
844 p1, p2 = pl[0], node
844 p1, p2 = pl[0], node
845 m1n = self.changelog.read(p1)[0]
845 m1n = self.changelog.read(p1)[0]
846 m2n = self.changelog.read(p2)[0]
846 m2n = self.changelog.read(p2)[0]
847 man = self.manifest.ancestor(m1n, m2n)
847 man = self.manifest.ancestor(m1n, m2n)
848 m1 = self.manifest.read(m1n)
848 m1 = self.manifest.read(m1n)
849 m2 = self.manifest.read(m2n)
849 m2 = self.manifest.read(m2n)
850 ma = self.manifest.read(man)
850 ma = self.manifest.read(man)
851
851
852 (c, a, d, u) = self.diffdir(self.root)
852 (c, a, d, u) = self.diffdir(self.root)
853
853
854 # resolve the manifest to determine which files
854 # resolve the manifest to determine which files
855 # we care about merging
855 # we care about merging
856 self.ui.note("resolving manifests\n")
856 self.ui.note("resolving manifests\n")
857 self.ui.debug(" ancestor %s local %s remote %s\n" %
857 self.ui.debug(" ancestor %s local %s remote %s\n" %
858 (short(man), short(m1n), short(m2n)))
858 (short(man), short(m1n), short(m2n)))
859
859
860 merge = {}
860 merge = {}
861 get = {}
861 get = {}
862 remove = []
862 remove = []
863
863
864 # construct a working dir manifest
864 # construct a working dir manifest
865 mw = m1.copy()
865 mw = m1.copy()
866 for f in a + c + u:
866 for f in a + c + u:
867 mw[f] = ""
867 mw[f] = ""
868 for f in d:
868 for f in d:
869 if f in mw: del mw[f]
869 if f in mw: del mw[f]
870
870
871 for f, n in mw.iteritems():
871 for f, n in mw.iteritems():
872 if f in m2:
872 if f in m2:
873 if n != m2[f]:
873 if n != m2[f]:
874 a = ma.get(f, nullid)
874 a = ma.get(f, nullid)
875 if n != a and m2[f] != a:
875 if n != a and m2[f] != a:
876 self.ui.debug(" %s versions differ, do resolve\n" % f)
876 self.ui.debug(" %s versions differ, resolve\n" % f)
877 merge[f] = (m1.get(f, nullid), m2[f])
877 merge[f] = (m1.get(f, nullid), m2[f])
878 else:
878 elif m2[f] != a:
879 self.ui.debug(" remote %s is newer, get\n" % f)
879 get[f] = m2[f]
880 get[f] = m2[f]
880 del m2[f]
881 del m2[f]
881 elif f in ma:
882 elif f in ma:
882 if n != ma[f]:
883 if n != ma[f]:
883 r = self.ui.prompt(
884 r = self.ui.prompt(
884 (" local changed %s which remote deleted\n" % f) +
885 (" local changed %s which remote deleted\n" % f) +
885 "(k)eep or (d)elete?", "[kd]", "k")
886 "(k)eep or (d)elete?", "[kd]", "k")
886 if r == "d":
887 if r == "d":
887 remove.append(f)
888 remove.append(f)
888 else:
889 else:
889 self.ui.debug("other deleted %s\n" % f)
890 self.ui.debug("other deleted %s\n" % f)
890 remove.append(f) # other deleted it
891 remove.append(f) # other deleted it
891 else:
892 else:
892 if n == m1.get(f, nullid): # same as parent
893 if n == m1.get(f, nullid): # same as parent
893 self.ui.debug("remote deleted %s\n" % f)
894 self.ui.debug("remote deleted %s\n" % f)
894 remove.append(f)
895 remove.append(f)
895 else:
896 else:
896 self.ui.debug("working dir created %s, keeping\n" % f)
897 self.ui.debug("working dir created %s, keeping\n" % f)
897
898
898 for f, n in m2.iteritems():
899 for f, n in m2.iteritems():
899 if f[0] == "/": continue
900 if f[0] == "/": continue
900 if f in ma and n != ma[f]:
901 if f in ma and n != ma[f]:
901 r = self.ui.prompt(
902 r = self.ui.prompt(
902 ("remote changed %s which local deleted\n" % f) +
903 ("remote changed %s which local deleted\n" % f) +
903 "(k)eep or (d)elete?", "[kd]", "k")
904 "(k)eep or (d)elete?", "[kd]", "k")
904 if r == "d": remove.append(f)
905 if r == "d": remove.append(f)
905 else:
906 else:
906 self.ui.debug("remote created %s\n" % f)
907 self.ui.debug("remote created %s\n" % f)
907 get[f] = n
908 get[f] = n
908
909
909 del mw, m1, m2, ma
910 del mw, m1, m2, ma
910
911
911 if not merge:
912 if not merge:
912 # we don't need to do any magic, just jump to the new rev
913 # we don't need to do any magic, just jump to the new rev
913 mode = 'n'
914 mode = 'n'
914 p1, p2 = p2, nullid
915 p1, p2 = p2, nullid
915 else:
916 else:
916 # we have to remember what files we needed to get/change
917 # we have to remember what files we needed to get/change
917 # because any file that's different from either one of its
918 # because any file that's different from either one of its
918 # parents must be in the changeset
919 # parents must be in the changeset
919 mode = 'm'
920 mode = 'm'
920
921
921 self.dirstate.setparents(p1, p2)
922 self.dirstate.setparents(p1, p2)
922
923
923 # get the files we don't need to change
924 # get the files we don't need to change
924 files = get.keys()
925 files = get.keys()
925 files.sort()
926 files.sort()
926 for f in files:
927 for f in files:
927 if f[0] == "/": continue
928 if f[0] == "/": continue
928 self.ui.note(f, "\n")
929 self.ui.note("getting %s\n" % f)
929 t = self.file(f).revision(get[f])
930 t = self.file(f).revision(get[f])
930 try:
931 try:
931 file(self.wjoin(f), "w").write(t)
932 file(self.wjoin(f), "w").write(t)
932 except IOError:
933 except IOError:
933 os.makedirs(os.path.dirname(f))
934 os.makedirs(os.path.dirname(f))
934 file(self.wjoin(f), "w").write(t)
935 file(self.wjoin(f), "w").write(t)
935 self.dirstate.update([f], mode)
936 self.dirstate.update([f], mode)
936
937
937 # merge the tricky bits
938 # merge the tricky bits
938 files = merge.keys()
939 files = merge.keys()
939 files.sort()
940 files.sort()
940 for f in files:
941 for f in files:
941 self.ui.status("merging %s\n" % f)
942 self.ui.status("merging %s\n" % f)
942 m, o = merge[f]
943 m, o = merge[f]
943 self.merge3(f, m, o)
944 self.merge3(f, m, o)
944 self.dirstate.update([f], 'm')
945 self.dirstate.update([f], 'm')
945
946
946 for f in remove:
947 for f in remove:
947 self.ui.note("removing %s\n" % f)
948 self.ui.note("removing %s\n" % f)
948 os.unlink(f)
949 os.unlink(f)
949 if mode == 'n':
950 if mode == 'n':
950 self.dirstate.forget(remove)
951 self.dirstate.forget(remove)
951 else:
952 else:
952 self.dirstate.update(remove, 'r')
953 self.dirstate.update(remove, 'r')
953
954
954 def merge3(self, fn, my, other):
955 def merge3(self, fn, my, other):
955 """perform a 3-way merge in the working directory"""
956 """perform a 3-way merge in the working directory"""
956
957
957 def temp(prefix, node):
958 def temp(prefix, node):
958 pre = "%s~%s." % (os.path.basename(fn), prefix)
959 pre = "%s~%s." % (os.path.basename(fn), prefix)
959 (fd, name) = tempfile.mkstemp("", pre)
960 (fd, name) = tempfile.mkstemp("", pre)
960 f = os.fdopen(fd, "w")
961 f = os.fdopen(fd, "w")
961 f.write(fl.revision(node))
962 f.write(fl.revision(node))
962 f.close()
963 f.close()
963 return name
964 return name
964
965
965 fl = self.file(fn)
966 fl = self.file(fn)
966 base = fl.ancestor(my, other)
967 base = fl.ancestor(my, other)
967 a = self.wjoin(fn)
968 a = self.wjoin(fn)
968 b = temp("other", other)
969 b = temp("other", other)
969 c = temp("base", base)
970 c = temp("base", base)
970
971
971 self.ui.note("resolving %s\n" % fn)
972 self.ui.note("resolving %s\n" % fn)
972 self.ui.debug("file %s: other %s ancestor %s\n" %
973 self.ui.debug("file %s: other %s ancestor %s\n" %
973 (fn, short(other), short(base)))
974 (fn, short(other), short(base)))
974
975
975 cmd = os.environ.get("HGMERGE", "hgmerge")
976 cmd = os.environ.get("HGMERGE", "hgmerge")
976 r = os.system("%s %s %s %s" % (cmd, a, b, c))
977 r = os.system("%s %s %s %s" % (cmd, a, b, c))
977 if r:
978 if r:
978 self.ui.warn("merging %s failed!\n" % f)
979 self.ui.warn("merging %s failed!\n" % f)
979
980
980 os.unlink(b)
981 os.unlink(b)
981 os.unlink(c)
982 os.unlink(c)
982
983
983 def verify(self):
984 def verify(self):
984 filelinkrevs = {}
985 filelinkrevs = {}
985 filenodes = {}
986 filenodes = {}
986 manifestchangeset = {}
987 manifestchangeset = {}
987 changesets = revisions = files = 0
988 changesets = revisions = files = 0
988 errors = 0
989 errors = 0
989
990
990 self.ui.status("checking changesets\n")
991 self.ui.status("checking changesets\n")
991 for i in range(self.changelog.count()):
992 for i in range(self.changelog.count()):
992 changesets += 1
993 changesets += 1
993 n = self.changelog.node(i)
994 n = self.changelog.node(i)
994 for p in self.changelog.parents(n):
995 for p in self.changelog.parents(n):
995 if p not in self.changelog.nodemap:
996 if p not in self.changelog.nodemap:
996 self.ui.warn("changeset %s has unknown parent %s\n" %
997 self.ui.warn("changeset %s has unknown parent %s\n" %
997 (short(n), short(p)))
998 (short(n), short(p)))
998 errors += 1
999 errors += 1
999 try:
1000 try:
1000 changes = self.changelog.read(n)
1001 changes = self.changelog.read(n)
1001 except Exception, inst:
1002 except Exception, inst:
1002 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1003 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1003 errors += 1
1004 errors += 1
1004
1005
1005 manifestchangeset[changes[0]] = n
1006 manifestchangeset[changes[0]] = n
1006 for f in changes[3]:
1007 for f in changes[3]:
1007 filelinkrevs.setdefault(f, []).append(i)
1008 filelinkrevs.setdefault(f, []).append(i)
1008
1009
1009 self.ui.status("checking manifests\n")
1010 self.ui.status("checking manifests\n")
1010 for i in range(self.manifest.count()):
1011 for i in range(self.manifest.count()):
1011 n = self.manifest.node(i)
1012 n = self.manifest.node(i)
1012 for p in self.manifest.parents(n):
1013 for p in self.manifest.parents(n):
1013 if p not in self.manifest.nodemap:
1014 if p not in self.manifest.nodemap:
1014 self.ui.warn("manifest %s has unknown parent %s\n" %
1015 self.ui.warn("manifest %s has unknown parent %s\n" %
1015 (short(n), short(p)))
1016 (short(n), short(p)))
1016 errors += 1
1017 errors += 1
1017 ca = self.changelog.node(self.manifest.linkrev(n))
1018 ca = self.changelog.node(self.manifest.linkrev(n))
1018 cc = manifestchangeset[n]
1019 cc = manifestchangeset[n]
1019 if ca != cc:
1020 if ca != cc:
1020 self.ui.warn("manifest %s points to %s, not %s\n" %
1021 self.ui.warn("manifest %s points to %s, not %s\n" %
1021 (hex(n), hex(ca), hex(cc)))
1022 (hex(n), hex(ca), hex(cc)))
1022 errors += 1
1023 errors += 1
1023
1024
1024 try:
1025 try:
1025 delta = mdiff.patchtext(self.manifest.delta(n))
1026 delta = mdiff.patchtext(self.manifest.delta(n))
1026 except KeyboardInterrupt:
1027 except KeyboardInterrupt:
1027 print "aborted"
1028 print "aborted"
1028 sys.exit(0)
1029 sys.exit(0)
1029 except Exception, inst:
1030 except Exception, inst:
1030 self.ui.warn("unpacking manifest %s: %s\n"
1031 self.ui.warn("unpacking manifest %s: %s\n"
1031 % (short(n), inst))
1032 % (short(n), inst))
1032 errors += 1
1033 errors += 1
1033
1034
1034 ff = [ l.split('\0') for l in delta.splitlines() ]
1035 ff = [ l.split('\0') for l in delta.splitlines() ]
1035 for f, fn in ff:
1036 for f, fn in ff:
1036 filenodes.setdefault(f, {})[bin(fn)] = 1
1037 filenodes.setdefault(f, {})[bin(fn)] = 1
1037
1038
1038 self.ui.status("crosschecking files in changesets and manifests\n")
1039 self.ui.status("crosschecking files in changesets and manifests\n")
1039 for f in filenodes:
1040 for f in filenodes:
1040 if f not in filelinkrevs:
1041 if f not in filelinkrevs:
1041 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1042 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1042 errors += 1
1043 errors += 1
1043
1044
1044 for f in filelinkrevs:
1045 for f in filelinkrevs:
1045 if f not in filenodes:
1046 if f not in filenodes:
1046 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1047 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1047 errors += 1
1048 errors += 1
1048
1049
1049 self.ui.status("checking files\n")
1050 self.ui.status("checking files\n")
1050 ff = filenodes.keys()
1051 ff = filenodes.keys()
1051 ff.sort()
1052 ff.sort()
1052 for f in ff:
1053 for f in ff:
1053 if f == "/dev/null": continue
1054 if f == "/dev/null": continue
1054 files += 1
1055 files += 1
1055 fl = self.file(f)
1056 fl = self.file(f)
1056 nodes = { nullid: 1 }
1057 nodes = { nullid: 1 }
1057 for i in range(fl.count()):
1058 for i in range(fl.count()):
1058 revisions += 1
1059 revisions += 1
1059 n = fl.node(i)
1060 n = fl.node(i)
1060
1061
1061 if n not in filenodes[f]:
1062 if n not in filenodes[f]:
1062 self.ui.warn("%s: %d:%s not in manifests\n"
1063 self.ui.warn("%s: %d:%s not in manifests\n"
1063 % (f, i, short(n)))
1064 % (f, i, short(n)))
1064 print len(filenodes[f].keys()), fl.count(), f
1065 print len(filenodes[f].keys()), fl.count(), f
1065 errors += 1
1066 errors += 1
1066 else:
1067 else:
1067 del filenodes[f][n]
1068 del filenodes[f][n]
1068
1069
1069 flr = fl.linkrev(n)
1070 flr = fl.linkrev(n)
1070 if flr not in filelinkrevs[f]:
1071 if flr not in filelinkrevs[f]:
1071 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1072 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1072 % (f, short(n), fl.linkrev(n)))
1073 % (f, short(n), fl.linkrev(n)))
1073 errors += 1
1074 errors += 1
1074 else:
1075 else:
1075 filelinkrevs[f].remove(flr)
1076 filelinkrevs[f].remove(flr)
1076
1077
1077 # verify contents
1078 # verify contents
1078 try:
1079 try:
1079 t = fl.read(n)
1080 t = fl.read(n)
1080 except Exception, inst:
1081 except Exception, inst:
1081 self.ui.warn("unpacking file %s %s: %s\n"
1082 self.ui.warn("unpacking file %s %s: %s\n"
1082 % (f, short(n), inst))
1083 % (f, short(n), inst))
1083 errors += 1
1084 errors += 1
1084
1085
1085 # verify parents
1086 # verify parents
1086 (p1, p2) = fl.parents(n)
1087 (p1, p2) = fl.parents(n)
1087 if p1 not in nodes:
1088 if p1 not in nodes:
1088 self.ui.warn("file %s:%s unknown parent 1 %s" %
1089 self.ui.warn("file %s:%s unknown parent 1 %s" %
1089 (f, short(n), short(p1)))
1090 (f, short(n), short(p1)))
1090 errors += 1
1091 errors += 1
1091 if p2 not in nodes:
1092 if p2 not in nodes:
1092 self.ui.warn("file %s:%s unknown parent 2 %s" %
1093 self.ui.warn("file %s:%s unknown parent 2 %s" %
1093 (f, short(n), short(p1)))
1094 (f, short(n), short(p1)))
1094 errors += 1
1095 errors += 1
1095 nodes[n] = 1
1096 nodes[n] = 1
1096
1097
1097 # cross-check
1098 # cross-check
1098 for node in filenodes[f]:
1099 for node in filenodes[f]:
1099 self.ui.warn("node %s in manifests not in %s\n"
1100 self.ui.warn("node %s in manifests not in %s\n"
1100 % (hex(n), f))
1101 % (hex(n), f))
1101 errors += 1
1102 errors += 1
1102
1103
1103 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1104 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1104 (files, changesets, revisions))
1105 (files, changesets, revisions))
1105
1106
1106 if errors:
1107 if errors:
1107 self.ui.warn("%d integrity errors encountered!\n" % errors)
1108 self.ui.warn("%d integrity errors encountered!\n" % errors)
1108 return 1
1109 return 1
1109
1110
1110 class remoterepository:
1111 class remoterepository:
1111 def __init__(self, ui, path):
1112 def __init__(self, ui, path):
1112 self.url = path
1113 self.url = path
1113 self.ui = ui
1114 self.ui = ui
1114
1115
1115 def do_cmd(self, cmd, **args):
1116 def do_cmd(self, cmd, **args):
1116 self.ui.debug("sending %s command\n" % cmd)
1117 self.ui.debug("sending %s command\n" % cmd)
1117 q = {"cmd": cmd}
1118 q = {"cmd": cmd}
1118 q.update(args)
1119 q.update(args)
1119 qs = urllib.urlencode(q)
1120 qs = urllib.urlencode(q)
1120 cu = "%s?%s" % (self.url, qs)
1121 cu = "%s?%s" % (self.url, qs)
1121 return urllib.urlopen(cu)
1122 return urllib.urlopen(cu)
1122
1123
1123 def heads(self):
1124 def heads(self):
1124 d = self.do_cmd("heads").read()
1125 d = self.do_cmd("heads").read()
1125 try:
1126 try:
1126 return map(bin, d[:-1].split(" "))
1127 return map(bin, d[:-1].split(" "))
1127 except:
1128 except:
1128 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1129 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1129 raise
1130 raise
1130
1131
1131 def branches(self, nodes):
1132 def branches(self, nodes):
1132 n = " ".join(map(hex, nodes))
1133 n = " ".join(map(hex, nodes))
1133 d = self.do_cmd("branches", nodes=n).read()
1134 d = self.do_cmd("branches", nodes=n).read()
1134 try:
1135 try:
1135 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1136 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1136 return br
1137 return br
1137 except:
1138 except:
1138 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1139 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1139 raise
1140 raise
1140
1141
1141 def between(self, pairs):
1142 def between(self, pairs):
1142 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1143 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1143 d = self.do_cmd("between", pairs=n).read()
1144 d = self.do_cmd("between", pairs=n).read()
1144 try:
1145 try:
1145 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1146 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1146 return p
1147 return p
1147 except:
1148 except:
1148 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1149 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1149 raise
1150 raise
1150
1151
1151 def changegroup(self, nodes):
1152 def changegroup(self, nodes):
1152 n = " ".join(map(hex, nodes))
1153 n = " ".join(map(hex, nodes))
1153 zd = zlib.decompressobj()
1154 zd = zlib.decompressobj()
1154 f = self.do_cmd("changegroup", roots=n)
1155 f = self.do_cmd("changegroup", roots=n)
1155 bytes = 0
1156 bytes = 0
1156 while 1:
1157 while 1:
1157 d = f.read(4096)
1158 d = f.read(4096)
1158 bytes += len(d)
1159 bytes += len(d)
1159 if not d:
1160 if not d:
1160 yield zd.flush()
1161 yield zd.flush()
1161 break
1162 break
1162 yield zd.decompress(d)
1163 yield zd.decompress(d)
1163 self.ui.note("%d bytes of data transfered\n" % bytes)
1164 self.ui.note("%d bytes of data transfered\n" % bytes)
1164
1165
1165 def repository(ui, path=None, create=0):
1166 def repository(ui, path=None, create=0):
1166 if path and path[:7] == "http://":
1167 if path and path[:7] == "http://":
1167 return remoterepository(ui, path)
1168 return remoterepository(ui, path)
1168 if path and path[:5] == "hg://":
1169 if path and path[:5] == "hg://":
1169 return remoterepository(ui, path.replace("hg://", "http://"))
1170 return remoterepository(ui, path.replace("hg://", "http://"))
1170 if path and path[:11] == "old-http://":
1171 if path and path[:11] == "old-http://":
1171 return localrepository(ui, path.replace("old-http://", "http://"))
1172 return localrepository(ui, path.replace("old-http://", "http://"))
1172 else:
1173 else:
1173 return localrepository(ui, path, create)
1174 return localrepository(ui, path, create)
1174
1175
1175 class httprangereader:
1176 class httprangereader:
1176 def __init__(self, url):
1177 def __init__(self, url):
1177 self.url = url
1178 self.url = url
1178 self.pos = 0
1179 self.pos = 0
1179 def seek(self, pos):
1180 def seek(self, pos):
1180 self.pos = pos
1181 self.pos = pos
1181 def read(self, bytes=None):
1182 def read(self, bytes=None):
1182 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1183 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1183 urllib2.install_opener(opener)
1184 urllib2.install_opener(opener)
1184 req = urllib2.Request(self.url)
1185 req = urllib2.Request(self.url)
1185 end = ''
1186 end = ''
1186 if bytes: end = self.pos + bytes
1187 if bytes: end = self.pos + bytes
1187 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1188 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1188 f = urllib2.urlopen(req)
1189 f = urllib2.urlopen(req)
1189 return f.read()
1190 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now