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