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