##// END OF EJS Templates
Replace difflib with bdiff for annotate...
mpm@selenic.com -
r434:08f00b64 default
parent child Browse files
Show More
@@ -1,1465 +1,1464 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import sys, struct, os
8 import sys, struct, os
9 import util
9 import util
10 from revlog import *
10 from revlog import *
11 from demandload import *
11 from demandload import *
12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
13 demandload(globals(), "tempfile httprangereader difflib")
13 demandload(globals(), "tempfile httprangereader bdiff")
14
14
15 def is_exec(f):
15 def is_exec(f):
16 return (os.stat(f).st_mode & 0100 != 0)
16 return (os.stat(f).st_mode & 0100 != 0)
17
17
18 def set_exec(f, mode):
18 def set_exec(f, mode):
19 s = os.stat(f).st_mode
19 s = os.stat(f).st_mode
20 if (s & 0100 != 0) == mode:
20 if (s & 0100 != 0) == mode:
21 return
21 return
22 if mode:
22 if mode:
23 # Turn on +x for every +r bit when making a file executable
23 # Turn on +x for every +r bit when making a file executable
24 # and obey umask.
24 # and obey umask.
25 umask = os.umask(0)
25 umask = os.umask(0)
26 os.umask(umask)
26 os.umask(umask)
27 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
27 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
28 else:
28 else:
29 os.chmod(f, s & 0666)
29 os.chmod(f, s & 0666)
30
30
31 class filelog(revlog):
31 class filelog(revlog):
32 def __init__(self, opener, path):
32 def __init__(self, opener, path):
33 revlog.__init__(self, opener,
33 revlog.__init__(self, opener,
34 os.path.join("data", path + ".i"),
34 os.path.join("data", path + ".i"),
35 os.path.join("data", path + ".d"))
35 os.path.join("data", path + ".d"))
36
36
37 def read(self, node):
37 def read(self, node):
38 t = self.revision(node)
38 t = self.revision(node)
39 if t[:2] != '\1\n':
39 if t[:2] != '\1\n':
40 return t
40 return t
41 s = t.find('\1\n', 2)
41 s = t.find('\1\n', 2)
42 return t[s+2:]
42 return t[s+2:]
43
43
44 def readmeta(self, node):
44 def readmeta(self, node):
45 t = self.revision(node)
45 t = self.revision(node)
46 if t[:2] != '\1\n':
46 if t[:2] != '\1\n':
47 return t
47 return t
48 s = t.find('\1\n', 2)
48 s = t.find('\1\n', 2)
49 mt = t[2:s]
49 mt = t[2:s]
50 for l in mt.splitlines():
50 for l in mt.splitlines():
51 k, v = l.split(": ", 1)
51 k, v = l.split(": ", 1)
52 m[k] = v
52 m[k] = v
53 return m
53 return m
54
54
55 def add(self, text, meta, transaction, link, p1=None, p2=None):
55 def add(self, text, meta, transaction, link, p1=None, p2=None):
56 if meta or text[:2] == '\1\n':
56 if meta or text[:2] == '\1\n':
57 mt = ""
57 mt = ""
58 if meta:
58 if meta:
59 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
59 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
60 text = "\1\n" + "".join(mt) + "\1\n" + text
60 text = "\1\n" + "".join(mt) + "\1\n" + text
61 return self.addrevision(text, transaction, link, p1, p2)
61 return self.addrevision(text, transaction, link, p1, p2)
62
62
63 def annotate(self, node):
63 def annotate(self, node):
64
64
65 def decorate(text, rev):
65 def decorate(text, rev):
66 return [(rev, l) for l in text.splitlines(1)]
66 return [(rev, l) for l in text.splitlines(1)]
67
67
68 def strip(annotation):
68 def strip(annotation):
69 return [e[1] for e in annotation]
69 return "".join([e[1] for e in annotation])
70
70
71 def pair(parent, child):
71 def pair(parent, child):
72 new = []
72 new = []
73 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
73 lb = 0
74 for o, m, n, s, t in sm.get_opcodes():
74 for a1, a2, b1, b2 in bdiff.blocks(strip(parent), strip(child)):
75 if o == 'equal':
75 new += child[lb:b1]
76 new += parent[m:n]
76 new += parent[a1:a2]
77 else:
77 lb = b2
78 new += child[s:t]
79 return new
78 return new
80
79
81 # find all ancestors
80 # find all ancestors
82 needed = {node:1}
81 needed = {node:1}
83 visit = [node]
82 visit = [node]
84 while visit:
83 while visit:
85 n = visit.pop(0)
84 n = visit.pop(0)
86 for p in self.parents(n):
85 for p in self.parents(n):
87 if p not in needed:
86 if p not in needed:
88 needed[p] = 1
87 needed[p] = 1
89 visit.append(p)
88 visit.append(p)
90 else:
89 else:
91 # count how many times we'll use this
90 # count how many times we'll use this
92 needed[p] += 1
91 needed[p] += 1
93
92
94 # sort by revision which is a topological order
93 # sort by revision which is a topological order
95 visit = needed.keys()
94 visit = needed.keys()
96 visit = [ (self.rev(n), n) for n in visit ]
95 visit = [ (self.rev(n), n) for n in visit ]
97 visit.sort()
96 visit.sort()
98 visit = [ p[1] for p in visit ]
97 visit = [ p[1] for p in visit ]
99 hist = {}
98 hist = {}
100
99
101 for n in visit:
100 for n in visit:
102 curr = decorate(self.read(n), self.linkrev(n))
101 curr = decorate(self.read(n), self.linkrev(n))
103 for p in self.parents(n):
102 for p in self.parents(n):
104 if p != nullid:
103 if p != nullid:
105 curr = pair(hist[p], curr)
104 curr = pair(hist[p], curr)
106 # trim the history of unneeded revs
105 # trim the history of unneeded revs
107 needed[p] -= 1
106 needed[p] -= 1
108 if not needed[p]:
107 if not needed[p]:
109 del hist[p]
108 del hist[p]
110 hist[n] = curr
109 hist[n] = curr
111
110
112 return hist[n]
111 return hist[n]
113
112
114 class manifest(revlog):
113 class manifest(revlog):
115 def __init__(self, opener):
114 def __init__(self, opener):
116 self.mapcache = None
115 self.mapcache = None
117 self.listcache = None
116 self.listcache = None
118 self.addlist = None
117 self.addlist = None
119 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
118 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
120
119
121 def read(self, node):
120 def read(self, node):
122 if node == nullid: return {} # don't upset local cache
121 if node == nullid: return {} # don't upset local cache
123 if self.mapcache and self.mapcache[0] == node:
122 if self.mapcache and self.mapcache[0] == node:
124 return self.mapcache[1].copy()
123 return self.mapcache[1].copy()
125 text = self.revision(node)
124 text = self.revision(node)
126 map = {}
125 map = {}
127 flag = {}
126 flag = {}
128 self.listcache = (text, text.splitlines(1))
127 self.listcache = (text, text.splitlines(1))
129 for l in self.listcache[1]:
128 for l in self.listcache[1]:
130 (f, n) = l.split('\0')
129 (f, n) = l.split('\0')
131 map[f] = bin(n[:40])
130 map[f] = bin(n[:40])
132 flag[f] = (n[40:-1] == "x")
131 flag[f] = (n[40:-1] == "x")
133 self.mapcache = (node, map, flag)
132 self.mapcache = (node, map, flag)
134 return map
133 return map
135
134
136 def readflags(self, node):
135 def readflags(self, node):
137 if node == nullid: return {} # don't upset local cache
136 if node == nullid: return {} # don't upset local cache
138 if not self.mapcache or self.mapcache[0] != node:
137 if not self.mapcache or self.mapcache[0] != node:
139 self.read(node)
138 self.read(node)
140 return self.mapcache[2]
139 return self.mapcache[2]
141
140
142 def diff(self, a, b):
141 def diff(self, a, b):
143 # this is sneaky, as we're not actually using a and b
142 # this is sneaky, as we're not actually using a and b
144 if self.listcache and self.addlist and self.listcache[0] == a:
143 if self.listcache and self.addlist and self.listcache[0] == a:
145 d = mdiff.diff(self.listcache[1], self.addlist, 1)
144 d = mdiff.diff(self.listcache[1], self.addlist, 1)
146 if mdiff.patch(a, d) != b:
145 if mdiff.patch(a, d) != b:
147 sys.stderr.write("*** sortdiff failed, falling back ***\n")
146 sys.stderr.write("*** sortdiff failed, falling back ***\n")
148 return mdiff.textdiff(a, b)
147 return mdiff.textdiff(a, b)
149 return d
148 return d
150 else:
149 else:
151 return mdiff.textdiff(a, b)
150 return mdiff.textdiff(a, b)
152
151
153 def add(self, map, flags, transaction, link, p1=None, p2=None):
152 def add(self, map, flags, transaction, link, p1=None, p2=None):
154 files = map.keys()
153 files = map.keys()
155 files.sort()
154 files.sort()
156
155
157 self.addlist = ["%s\000%s%s\n" %
156 self.addlist = ["%s\000%s%s\n" %
158 (f, hex(map[f]), flags[f] and "x" or '')
157 (f, hex(map[f]), flags[f] and "x" or '')
159 for f in files]
158 for f in files]
160 text = "".join(self.addlist)
159 text = "".join(self.addlist)
161
160
162 n = self.addrevision(text, transaction, link, p1, p2)
161 n = self.addrevision(text, transaction, link, p1, p2)
163 self.mapcache = (n, map, flags)
162 self.mapcache = (n, map, flags)
164 self.listcache = (text, self.addlist)
163 self.listcache = (text, self.addlist)
165 self.addlist = None
164 self.addlist = None
166
165
167 return n
166 return n
168
167
169 class changelog(revlog):
168 class changelog(revlog):
170 def __init__(self, opener):
169 def __init__(self, opener):
171 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
170 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
172
171
173 def extract(self, text):
172 def extract(self, text):
174 if not text:
173 if not text:
175 return (nullid, "", "0", [], "")
174 return (nullid, "", "0", [], "")
176 last = text.index("\n\n")
175 last = text.index("\n\n")
177 desc = text[last + 2:]
176 desc = text[last + 2:]
178 l = text[:last].splitlines()
177 l = text[:last].splitlines()
179 manifest = bin(l[0])
178 manifest = bin(l[0])
180 user = l[1]
179 user = l[1]
181 date = l[2]
180 date = l[2]
182 files = l[3:]
181 files = l[3:]
183 return (manifest, user, date, files, desc)
182 return (manifest, user, date, files, desc)
184
183
185 def read(self, node):
184 def read(self, node):
186 return self.extract(self.revision(node))
185 return self.extract(self.revision(node))
187
186
188 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
187 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
189 user=None, date=None):
188 user=None, date=None):
190 user = (user or
189 user = (user or
191 os.environ.get("HGUSER") or
190 os.environ.get("HGUSER") or
192 os.environ.get("EMAIL") or
191 os.environ.get("EMAIL") or
193 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
192 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
194 date = date or "%d %d" % (time.time(), time.timezone)
193 date = date or "%d %d" % (time.time(), time.timezone)
195 list.sort()
194 list.sort()
196 l = [hex(manifest), user, date] + list + ["", desc]
195 l = [hex(manifest), user, date] + list + ["", desc]
197 text = "\n".join(l)
196 text = "\n".join(l)
198 return self.addrevision(text, transaction, self.count(), p1, p2)
197 return self.addrevision(text, transaction, self.count(), p1, p2)
199
198
200 class dirstate:
199 class dirstate:
201 def __init__(self, opener, ui, root):
200 def __init__(self, opener, ui, root):
202 self.opener = opener
201 self.opener = opener
203 self.root = root
202 self.root = root
204 self.dirty = 0
203 self.dirty = 0
205 self.ui = ui
204 self.ui = ui
206 self.map = None
205 self.map = None
207 self.pl = None
206 self.pl = None
208 self.copies = {}
207 self.copies = {}
209
208
210 def __del__(self):
209 def __del__(self):
211 if self.dirty:
210 if self.dirty:
212 self.write()
211 self.write()
213
212
214 def __getitem__(self, key):
213 def __getitem__(self, key):
215 try:
214 try:
216 return self.map[key]
215 return self.map[key]
217 except TypeError:
216 except TypeError:
218 self.read()
217 self.read()
219 return self[key]
218 return self[key]
220
219
221 def __contains__(self, key):
220 def __contains__(self, key):
222 if not self.map: self.read()
221 if not self.map: self.read()
223 return key in self.map
222 return key in self.map
224
223
225 def parents(self):
224 def parents(self):
226 if not self.pl:
225 if not self.pl:
227 self.read()
226 self.read()
228 return self.pl
227 return self.pl
229
228
230 def setparents(self, p1, p2 = nullid):
229 def setparents(self, p1, p2 = nullid):
231 self.dirty = 1
230 self.dirty = 1
232 self.pl = p1, p2
231 self.pl = p1, p2
233
232
234 def state(self, key):
233 def state(self, key):
235 try:
234 try:
236 return self[key][0]
235 return self[key][0]
237 except KeyError:
236 except KeyError:
238 return "?"
237 return "?"
239
238
240 def read(self):
239 def read(self):
241 if self.map is not None: return self.map
240 if self.map is not None: return self.map
242
241
243 self.map = {}
242 self.map = {}
244 self.pl = [nullid, nullid]
243 self.pl = [nullid, nullid]
245 try:
244 try:
246 st = self.opener("dirstate").read()
245 st = self.opener("dirstate").read()
247 if not st: return
246 if not st: return
248 except: return
247 except: return
249
248
250 self.pl = [st[:20], st[20: 40]]
249 self.pl = [st[:20], st[20: 40]]
251
250
252 pos = 40
251 pos = 40
253 while pos < len(st):
252 while pos < len(st):
254 e = struct.unpack(">cllll", st[pos:pos+17])
253 e = struct.unpack(">cllll", st[pos:pos+17])
255 l = e[4]
254 l = e[4]
256 pos += 17
255 pos += 17
257 f = st[pos:pos + l]
256 f = st[pos:pos + l]
258 if '\0' in f:
257 if '\0' in f:
259 f, c = f.split('\0')
258 f, c = f.split('\0')
260 self.copies[f] = c
259 self.copies[f] = c
261 self.map[f] = e[:4]
260 self.map[f] = e[:4]
262 pos += l
261 pos += l
263
262
264 def copy(self, source, dest):
263 def copy(self, source, dest):
265 self.read()
264 self.read()
266 self.dirty = 1
265 self.dirty = 1
267 self.copies[dest] = source
266 self.copies[dest] = source
268
267
269 def copied(self, file):
268 def copied(self, file):
270 return self.copies.get(file, None)
269 return self.copies.get(file, None)
271
270
272 def update(self, files, state):
271 def update(self, files, state):
273 ''' current states:
272 ''' current states:
274 n normal
273 n normal
275 m needs merging
274 m needs merging
276 r marked for removal
275 r marked for removal
277 a marked for addition'''
276 a marked for addition'''
278
277
279 if not files: return
278 if not files: return
280 self.read()
279 self.read()
281 self.dirty = 1
280 self.dirty = 1
282 for f in files:
281 for f in files:
283 if state == "r":
282 if state == "r":
284 self.map[f] = ('r', 0, 0, 0)
283 self.map[f] = ('r', 0, 0, 0)
285 else:
284 else:
286 s = os.stat(os.path.join(self.root, f))
285 s = os.stat(os.path.join(self.root, f))
287 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
286 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
288
287
289 def forget(self, files):
288 def forget(self, files):
290 if not files: return
289 if not files: return
291 self.read()
290 self.read()
292 self.dirty = 1
291 self.dirty = 1
293 for f in files:
292 for f in files:
294 try:
293 try:
295 del self.map[f]
294 del self.map[f]
296 except KeyError:
295 except KeyError:
297 self.ui.warn("not in dirstate: %s!\n" % f)
296 self.ui.warn("not in dirstate: %s!\n" % f)
298 pass
297 pass
299
298
300 def clear(self):
299 def clear(self):
301 self.map = {}
300 self.map = {}
302 self.dirty = 1
301 self.dirty = 1
303
302
304 def write(self):
303 def write(self):
305 st = self.opener("dirstate", "w")
304 st = self.opener("dirstate", "w")
306 st.write("".join(self.pl))
305 st.write("".join(self.pl))
307 for f, e in self.map.items():
306 for f, e in self.map.items():
308 c = self.copied(f)
307 c = self.copied(f)
309 if c:
308 if c:
310 f = f + "\0" + c
309 f = f + "\0" + c
311 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
310 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
312 st.write(e + f)
311 st.write(e + f)
313 self.dirty = 0
312 self.dirty = 0
314
313
315 def dup(self):
314 def dup(self):
316 self.read()
315 self.read()
317 return self.map.copy()
316 return self.map.copy()
318
317
319 # used to avoid circular references so destructors work
318 # used to avoid circular references so destructors work
320 def opener(base):
319 def opener(base):
321 p = base
320 p = base
322 def o(path, mode="r"):
321 def o(path, mode="r"):
323 if p[:7] == "http://":
322 if p[:7] == "http://":
324 f = os.path.join(p, urllib.quote(path))
323 f = os.path.join(p, urllib.quote(path))
325 return httprangereader.httprangereader(f)
324 return httprangereader.httprangereader(f)
326
325
327 f = os.path.join(p, path)
326 f = os.path.join(p, path)
328
327
329 mode += "b" # for that other OS
328 mode += "b" # for that other OS
330
329
331 if mode[0] != "r":
330 if mode[0] != "r":
332 try:
331 try:
333 s = os.stat(f)
332 s = os.stat(f)
334 except OSError:
333 except OSError:
335 d = os.path.dirname(f)
334 d = os.path.dirname(f)
336 if not os.path.isdir(d):
335 if not os.path.isdir(d):
337 os.makedirs(d)
336 os.makedirs(d)
338 else:
337 else:
339 if s.st_nlink > 1:
338 if s.st_nlink > 1:
340 file(f + ".tmp", "wb").write(file(f, "rb").read())
339 file(f + ".tmp", "wb").write(file(f, "rb").read())
341 util.rename(f+".tmp", f)
340 util.rename(f+".tmp", f)
342
341
343 return file(f, mode)
342 return file(f, mode)
344
343
345 return o
344 return o
346
345
347 class localrepository:
346 class localrepository:
348 def __init__(self, ui, path=None, create=0):
347 def __init__(self, ui, path=None, create=0):
349 self.remote = 0
348 self.remote = 0
350 if path and path[:7] == "http://":
349 if path and path[:7] == "http://":
351 self.remote = 1
350 self.remote = 1
352 self.path = path
351 self.path = path
353 else:
352 else:
354 if not path:
353 if not path:
355 p = os.getcwd()
354 p = os.getcwd()
356 while not os.path.isdir(os.path.join(p, ".hg")):
355 while not os.path.isdir(os.path.join(p, ".hg")):
357 oldp = p
356 oldp = p
358 p = os.path.dirname(p)
357 p = os.path.dirname(p)
359 if p == oldp: raise "No repo found"
358 if p == oldp: raise "No repo found"
360 path = p
359 path = p
361 self.path = os.path.join(path, ".hg")
360 self.path = os.path.join(path, ".hg")
362
361
363 if not create and not os.path.isdir(self.path):
362 if not create and not os.path.isdir(self.path):
364 raise "repository %s not found" % self.path
363 raise "repository %s not found" % self.path
365
364
366 self.root = path
365 self.root = path
367 self.ui = ui
366 self.ui = ui
368
367
369 if create:
368 if create:
370 os.mkdir(self.path)
369 os.mkdir(self.path)
371 os.mkdir(self.join("data"))
370 os.mkdir(self.join("data"))
372
371
373 self.opener = opener(self.path)
372 self.opener = opener(self.path)
374 self.wopener = opener(self.root)
373 self.wopener = opener(self.root)
375 self.manifest = manifest(self.opener)
374 self.manifest = manifest(self.opener)
376 self.changelog = changelog(self.opener)
375 self.changelog = changelog(self.opener)
377 self.ignorelist = None
376 self.ignorelist = None
378 self.tagscache = None
377 self.tagscache = None
379 self.nodetagscache = None
378 self.nodetagscache = None
380
379
381 if not self.remote:
380 if not self.remote:
382 self.dirstate = dirstate(self.opener, ui, self.root)
381 self.dirstate = dirstate(self.opener, ui, self.root)
383 try:
382 try:
384 self.ui.readconfig(self.opener("hgrc"))
383 self.ui.readconfig(self.opener("hgrc"))
385 except IOError: pass
384 except IOError: pass
386
385
387 def ignore(self, f):
386 def ignore(self, f):
388 if self.ignorelist is None:
387 if self.ignorelist is None:
389 self.ignorelist = []
388 self.ignorelist = []
390 try:
389 try:
391 l = file(self.wjoin(".hgignore"))
390 l = file(self.wjoin(".hgignore"))
392 for pat in l:
391 for pat in l:
393 if pat != "\n":
392 if pat != "\n":
394 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
393 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
395 except IOError: pass
394 except IOError: pass
396 for pat in self.ignorelist:
395 for pat in self.ignorelist:
397 if pat.search(f): return True
396 if pat.search(f): return True
398 return False
397 return False
399
398
400 def tags(self):
399 def tags(self):
401 '''return a mapping of tag to node'''
400 '''return a mapping of tag to node'''
402 if not self.tagscache:
401 if not self.tagscache:
403 self.tagscache = {}
402 self.tagscache = {}
404 try:
403 try:
405 # read each head of the tags file, ending with the tip
404 # read each head of the tags file, ending with the tip
406 # and add each tag found to the map, with "newer" ones
405 # and add each tag found to the map, with "newer" ones
407 # taking precedence
406 # taking precedence
408 fl = self.file(".hgtags")
407 fl = self.file(".hgtags")
409 h = fl.heads()
408 h = fl.heads()
410 h.reverse()
409 h.reverse()
411 for r in h:
410 for r in h:
412 for l in fl.revision(r).splitlines():
411 for l in fl.revision(r).splitlines():
413 if l:
412 if l:
414 n, k = l.split(" ", 1)
413 n, k = l.split(" ", 1)
415 self.tagscache[k.strip()] = bin(n)
414 self.tagscache[k.strip()] = bin(n)
416 except KeyError: pass
415 except KeyError: pass
417 self.tagscache['tip'] = self.changelog.tip()
416 self.tagscache['tip'] = self.changelog.tip()
418
417
419 return self.tagscache
418 return self.tagscache
420
419
421 def tagslist(self):
420 def tagslist(self):
422 '''return a list of tags ordered by revision'''
421 '''return a list of tags ordered by revision'''
423 l = []
422 l = []
424 for t,n in self.tags().items():
423 for t,n in self.tags().items():
425 try:
424 try:
426 r = self.changelog.rev(n)
425 r = self.changelog.rev(n)
427 except:
426 except:
428 r = -2 # sort to the beginning of the list if unknown
427 r = -2 # sort to the beginning of the list if unknown
429 l.append((r,t,n))
428 l.append((r,t,n))
430 l.sort()
429 l.sort()
431 return [(t,n) for r,t,n in l]
430 return [(t,n) for r,t,n in l]
432
431
433 def nodetags(self, node):
432 def nodetags(self, node):
434 '''return the tags associated with a node'''
433 '''return the tags associated with a node'''
435 if not self.nodetagscache:
434 if not self.nodetagscache:
436 self.nodetagscache = {}
435 self.nodetagscache = {}
437 for t,n in self.tags().items():
436 for t,n in self.tags().items():
438 self.nodetagscache.setdefault(n,[]).append(t)
437 self.nodetagscache.setdefault(n,[]).append(t)
439 return self.nodetagscache.get(node, [])
438 return self.nodetagscache.get(node, [])
440
439
441 def lookup(self, key):
440 def lookup(self, key):
442 try:
441 try:
443 return self.tags()[key]
442 return self.tags()[key]
444 except KeyError:
443 except KeyError:
445 return self.changelog.lookup(key)
444 return self.changelog.lookup(key)
446
445
447 def join(self, f):
446 def join(self, f):
448 return os.path.join(self.path, f)
447 return os.path.join(self.path, f)
449
448
450 def wjoin(self, f):
449 def wjoin(self, f):
451 return os.path.join(self.root, f)
450 return os.path.join(self.root, f)
452
451
453 def file(self, f):
452 def file(self, f):
454 if f[0] == '/': f = f[1:]
453 if f[0] == '/': f = f[1:]
455 return filelog(self.opener, f)
454 return filelog(self.opener, f)
456
455
457 def wfile(self, f, mode='r'):
456 def wfile(self, f, mode='r'):
458 return self.wopener(f, mode)
457 return self.wopener(f, mode)
459
458
460 def transaction(self):
459 def transaction(self):
461 # save dirstate for undo
460 # save dirstate for undo
462 try:
461 try:
463 ds = self.opener("dirstate").read()
462 ds = self.opener("dirstate").read()
464 except IOError:
463 except IOError:
465 ds = ""
464 ds = ""
466 self.opener("undo.dirstate", "w").write(ds)
465 self.opener("undo.dirstate", "w").write(ds)
467
466
468 return transaction.transaction(self.opener, self.join("journal"),
467 return transaction.transaction(self.opener, self.join("journal"),
469 self.join("undo"))
468 self.join("undo"))
470
469
471 def recover(self):
470 def recover(self):
472 lock = self.lock()
471 lock = self.lock()
473 if os.path.exists(self.join("recover")):
472 if os.path.exists(self.join("recover")):
474 self.ui.status("attempting to rollback interrupted transaction\n")
473 self.ui.status("attempting to rollback interrupted transaction\n")
475 return transaction.rollback(self.opener, self.join("recover"))
474 return transaction.rollback(self.opener, self.join("recover"))
476 else:
475 else:
477 self.ui.warn("no interrupted transaction available\n")
476 self.ui.warn("no interrupted transaction available\n")
478
477
479 def undo(self):
478 def undo(self):
480 lock = self.lock()
479 lock = self.lock()
481 if os.path.exists(self.join("undo")):
480 if os.path.exists(self.join("undo")):
482 self.ui.status("attempting to rollback last transaction\n")
481 self.ui.status("attempting to rollback last transaction\n")
483 transaction.rollback(self.opener, self.join("undo"))
482 transaction.rollback(self.opener, self.join("undo"))
484 self.dirstate = None
483 self.dirstate = None
485 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
484 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
486 self.dirstate = dirstate(self.opener, self.ui, self.root)
485 self.dirstate = dirstate(self.opener, self.ui, self.root)
487 else:
486 else:
488 self.ui.warn("no undo information available\n")
487 self.ui.warn("no undo information available\n")
489
488
490 def lock(self, wait = 1):
489 def lock(self, wait = 1):
491 try:
490 try:
492 return lock.lock(self.join("lock"), 0)
491 return lock.lock(self.join("lock"), 0)
493 except lock.LockHeld, inst:
492 except lock.LockHeld, inst:
494 if wait:
493 if wait:
495 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
494 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
496 return lock.lock(self.join("lock"), wait)
495 return lock.lock(self.join("lock"), wait)
497 raise inst
496 raise inst
498
497
499 def rawcommit(self, files, text, user, date, p1=None, p2=None):
498 def rawcommit(self, files, text, user, date, p1=None, p2=None):
500 p1 = p1 or self.dirstate.parents()[0] or nullid
499 p1 = p1 or self.dirstate.parents()[0] or nullid
501 p2 = p2 or self.dirstate.parents()[1] or nullid
500 p2 = p2 or self.dirstate.parents()[1] or nullid
502 c1 = self.changelog.read(p1)
501 c1 = self.changelog.read(p1)
503 c2 = self.changelog.read(p2)
502 c2 = self.changelog.read(p2)
504 m1 = self.manifest.read(c1[0])
503 m1 = self.manifest.read(c1[0])
505 mf1 = self.manifest.readflags(c1[0])
504 mf1 = self.manifest.readflags(c1[0])
506 m2 = self.manifest.read(c2[0])
505 m2 = self.manifest.read(c2[0])
507
506
508 tr = self.transaction()
507 tr = self.transaction()
509 mm = m1.copy()
508 mm = m1.copy()
510 mfm = mf1.copy()
509 mfm = mf1.copy()
511 linkrev = self.changelog.count()
510 linkrev = self.changelog.count()
512 self.dirstate.setparents(p1, p2)
511 self.dirstate.setparents(p1, p2)
513 for f in files:
512 for f in files:
514 try:
513 try:
515 t = self.wfile(f).read()
514 t = self.wfile(f).read()
516 tm = is_exec(self.wjoin(f))
515 tm = is_exec(self.wjoin(f))
517 r = self.file(f)
516 r = self.file(f)
518 mfm[f] = tm
517 mfm[f] = tm
519 mm[f] = r.add(t, {}, tr, linkrev,
518 mm[f] = r.add(t, {}, tr, linkrev,
520 m1.get(f, nullid), m2.get(f, nullid))
519 m1.get(f, nullid), m2.get(f, nullid))
521 self.dirstate.update([f], "n")
520 self.dirstate.update([f], "n")
522 except IOError:
521 except IOError:
523 try:
522 try:
524 del mm[f]
523 del mm[f]
525 del mfm[f]
524 del mfm[f]
526 self.dirstate.forget([f])
525 self.dirstate.forget([f])
527 except:
526 except:
528 # deleted from p2?
527 # deleted from p2?
529 pass
528 pass
530
529
531 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
530 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
532 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
531 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
533 tr.close()
532 tr.close()
534
533
535 def commit(self, files = None, text = "", user = None, date = None):
534 def commit(self, files = None, text = "", user = None, date = None):
536 commit = []
535 commit = []
537 remove = []
536 remove = []
538 if files:
537 if files:
539 for f in files:
538 for f in files:
540 s = self.dirstate.state(f)
539 s = self.dirstate.state(f)
541 if s in 'nmai':
540 if s in 'nmai':
542 commit.append(f)
541 commit.append(f)
543 elif s == 'r':
542 elif s == 'r':
544 remove.append(f)
543 remove.append(f)
545 else:
544 else:
546 self.ui.warn("%s not tracked!\n" % f)
545 self.ui.warn("%s not tracked!\n" % f)
547 else:
546 else:
548 (c, a, d, u) = self.diffdir(self.root)
547 (c, a, d, u) = self.diffdir(self.root)
549 commit = c + a
548 commit = c + a
550 remove = d
549 remove = d
551
550
552 if not commit and not remove:
551 if not commit and not remove:
553 self.ui.status("nothing changed\n")
552 self.ui.status("nothing changed\n")
554 return
553 return
555
554
556 p1, p2 = self.dirstate.parents()
555 p1, p2 = self.dirstate.parents()
557 c1 = self.changelog.read(p1)
556 c1 = self.changelog.read(p1)
558 c2 = self.changelog.read(p2)
557 c2 = self.changelog.read(p2)
559 m1 = self.manifest.read(c1[0])
558 m1 = self.manifest.read(c1[0])
560 mf1 = self.manifest.readflags(c1[0])
559 mf1 = self.manifest.readflags(c1[0])
561 m2 = self.manifest.read(c2[0])
560 m2 = self.manifest.read(c2[0])
562 lock = self.lock()
561 lock = self.lock()
563 tr = self.transaction()
562 tr = self.transaction()
564
563
565 # check in files
564 # check in files
566 new = {}
565 new = {}
567 linkrev = self.changelog.count()
566 linkrev = self.changelog.count()
568 commit.sort()
567 commit.sort()
569 for f in commit:
568 for f in commit:
570 self.ui.note(f + "\n")
569 self.ui.note(f + "\n")
571 try:
570 try:
572 fp = self.wjoin(f)
571 fp = self.wjoin(f)
573 mf1[f] = is_exec(fp)
572 mf1[f] = is_exec(fp)
574 t = self.wfile(f).read()
573 t = self.wfile(f).read()
575 except IOError:
574 except IOError:
576 self.warn("trouble committing %s!\n" % f)
575 self.warn("trouble committing %s!\n" % f)
577 raise
576 raise
578
577
579 meta = {}
578 meta = {}
580 cp = self.dirstate.copied(f)
579 cp = self.dirstate.copied(f)
581 if cp:
580 if cp:
582 meta["copy"] = cp
581 meta["copy"] = cp
583 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
582 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
584 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
583 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
585
584
586 r = self.file(f)
585 r = self.file(f)
587 fp1 = m1.get(f, nullid)
586 fp1 = m1.get(f, nullid)
588 fp2 = m2.get(f, nullid)
587 fp2 = m2.get(f, nullid)
589 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
588 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
590
589
591 # update manifest
590 # update manifest
592 m1.update(new)
591 m1.update(new)
593 for f in remove:
592 for f in remove:
594 if f in m1:
593 if f in m1:
595 del m1[f]
594 del m1[f]
596 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
595 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
597
596
598 # add changeset
597 # add changeset
599 new = new.keys()
598 new = new.keys()
600 new.sort()
599 new.sort()
601
600
602 if not text:
601 if not text:
603 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
602 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
604 edittext += "".join(["HG: changed %s\n" % f for f in new])
603 edittext += "".join(["HG: changed %s\n" % f for f in new])
605 edittext += "".join(["HG: removed %s\n" % f for f in remove])
604 edittext += "".join(["HG: removed %s\n" % f for f in remove])
606 edittext = self.ui.edit(edittext)
605 edittext = self.ui.edit(edittext)
607 if not edittext.rstrip():
606 if not edittext.rstrip():
608 return 1
607 return 1
609 text = edittext
608 text = edittext
610
609
611 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
610 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
612 tr.close()
611 tr.close()
613
612
614 self.dirstate.setparents(n)
613 self.dirstate.setparents(n)
615 self.dirstate.update(new, "n")
614 self.dirstate.update(new, "n")
616 self.dirstate.forget(remove)
615 self.dirstate.forget(remove)
617
616
618 def diffdir(self, path, changeset = None):
617 def diffdir(self, path, changeset = None):
619 changed = []
618 changed = []
620 added = []
619 added = []
621 unknown = []
620 unknown = []
622 mf = {}
621 mf = {}
623
622
624 if changeset:
623 if changeset:
625 change = self.changelog.read(changeset)
624 change = self.changelog.read(changeset)
626 mf = self.manifest.read(change[0])
625 mf = self.manifest.read(change[0])
627 dc = dict.fromkeys(mf)
626 dc = dict.fromkeys(mf)
628 else:
627 else:
629 changeset = self.dirstate.parents()[0]
628 changeset = self.dirstate.parents()[0]
630 change = self.changelog.read(changeset)
629 change = self.changelog.read(changeset)
631 mf = self.manifest.read(change[0])
630 mf = self.manifest.read(change[0])
632 dc = self.dirstate.dup()
631 dc = self.dirstate.dup()
633
632
634 def fcmp(fn):
633 def fcmp(fn):
635 t1 = self.wfile(fn).read()
634 t1 = self.wfile(fn).read()
636 t2 = self.file(fn).revision(mf[fn])
635 t2 = self.file(fn).revision(mf[fn])
637 return cmp(t1, t2)
636 return cmp(t1, t2)
638
637
639 for dir, subdirs, files in os.walk(path):
638 for dir, subdirs, files in os.walk(path):
640 d = dir[len(self.root)+1:]
639 d = dir[len(self.root)+1:]
641 if ".hg" in subdirs: subdirs.remove(".hg")
640 if ".hg" in subdirs: subdirs.remove(".hg")
642
641
643 for f in files:
642 for f in files:
644 fn = util.pconvert(os.path.join(d, f))
643 fn = util.pconvert(os.path.join(d, f))
645 try: s = os.stat(os.path.join(self.root, fn))
644 try: s = os.stat(os.path.join(self.root, fn))
646 except: continue
645 except: continue
647 if fn in dc:
646 if fn in dc:
648 c = dc[fn]
647 c = dc[fn]
649 del dc[fn]
648 del dc[fn]
650 if not c:
649 if not c:
651 if fcmp(fn):
650 if fcmp(fn):
652 changed.append(fn)
651 changed.append(fn)
653 elif c[0] == 'm':
652 elif c[0] == 'm':
654 changed.append(fn)
653 changed.append(fn)
655 elif c[0] == 'a':
654 elif c[0] == 'a':
656 added.append(fn)
655 added.append(fn)
657 elif c[0] == 'r':
656 elif c[0] == 'r':
658 unknown.append(fn)
657 unknown.append(fn)
659 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
658 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
660 changed.append(fn)
659 changed.append(fn)
661 elif c[1] != s.st_mode or c[3] != s.st_mtime:
660 elif c[1] != s.st_mode or c[3] != s.st_mtime:
662 if fcmp(fn):
661 if fcmp(fn):
663 changed.append(fn)
662 changed.append(fn)
664 else:
663 else:
665 if self.ignore(fn): continue
664 if self.ignore(fn): continue
666 unknown.append(fn)
665 unknown.append(fn)
667
666
668 deleted = dc.keys()
667 deleted = dc.keys()
669 deleted.sort()
668 deleted.sort()
670
669
671 return (changed, added, deleted, unknown)
670 return (changed, added, deleted, unknown)
672
671
673 def diffrevs(self, node1, node2):
672 def diffrevs(self, node1, node2):
674 changed, added = [], []
673 changed, added = [], []
675
674
676 change = self.changelog.read(node1)
675 change = self.changelog.read(node1)
677 mf1 = self.manifest.read(change[0])
676 mf1 = self.manifest.read(change[0])
678 change = self.changelog.read(node2)
677 change = self.changelog.read(node2)
679 mf2 = self.manifest.read(change[0])
678 mf2 = self.manifest.read(change[0])
680
679
681 for fn in mf2:
680 for fn in mf2:
682 if mf1.has_key(fn):
681 if mf1.has_key(fn):
683 if mf1[fn] != mf2[fn]:
682 if mf1[fn] != mf2[fn]:
684 changed.append(fn)
683 changed.append(fn)
685 del mf1[fn]
684 del mf1[fn]
686 else:
685 else:
687 added.append(fn)
686 added.append(fn)
688
687
689 deleted = mf1.keys()
688 deleted = mf1.keys()
690 deleted.sort()
689 deleted.sort()
691
690
692 return (changed, added, deleted)
691 return (changed, added, deleted)
693
692
694 def add(self, list):
693 def add(self, list):
695 for f in list:
694 for f in list:
696 p = self.wjoin(f)
695 p = self.wjoin(f)
697 if not os.path.isfile(p):
696 if not os.path.isfile(p):
698 self.ui.warn("%s does not exist!\n" % f)
697 self.ui.warn("%s does not exist!\n" % f)
699 elif self.dirstate.state(f) == 'n':
698 elif self.dirstate.state(f) == 'n':
700 self.ui.warn("%s already tracked!\n" % f)
699 self.ui.warn("%s already tracked!\n" % f)
701 else:
700 else:
702 self.dirstate.update([f], "a")
701 self.dirstate.update([f], "a")
703
702
704 def forget(self, list):
703 def forget(self, list):
705 for f in list:
704 for f in list:
706 if self.dirstate.state(f) not in 'ai':
705 if self.dirstate.state(f) not in 'ai':
707 self.ui.warn("%s not added!\n" % f)
706 self.ui.warn("%s not added!\n" % f)
708 else:
707 else:
709 self.dirstate.forget([f])
708 self.dirstate.forget([f])
710
709
711 def remove(self, list):
710 def remove(self, list):
712 for f in list:
711 for f in list:
713 p = self.wjoin(f)
712 p = self.wjoin(f)
714 if os.path.isfile(p):
713 if os.path.isfile(p):
715 self.ui.warn("%s still exists!\n" % f)
714 self.ui.warn("%s still exists!\n" % f)
716 elif self.dirstate.state(f) == 'a':
715 elif self.dirstate.state(f) == 'a':
717 self.ui.warn("%s never committed!\n" % f)
716 self.ui.warn("%s never committed!\n" % f)
718 self.dirstate.forget(f)
717 self.dirstate.forget(f)
719 elif f not in self.dirstate:
718 elif f not in self.dirstate:
720 self.ui.warn("%s not tracked!\n" % f)
719 self.ui.warn("%s not tracked!\n" % f)
721 else:
720 else:
722 self.dirstate.update([f], "r")
721 self.dirstate.update([f], "r")
723
722
724 def copy(self, source, dest):
723 def copy(self, source, dest):
725 p = self.wjoin(dest)
724 p = self.wjoin(dest)
726 if not os.path.isfile(dest):
725 if not os.path.isfile(dest):
727 self.ui.warn("%s does not exist!\n" % dest)
726 self.ui.warn("%s does not exist!\n" % dest)
728 else:
727 else:
729 if self.dirstate.state(dest) == '?':
728 if self.dirstate.state(dest) == '?':
730 self.dirstate.update([dest], "a")
729 self.dirstate.update([dest], "a")
731 self.dirstate.copy(source, dest)
730 self.dirstate.copy(source, dest)
732
731
733 def heads(self):
732 def heads(self):
734 return self.changelog.heads()
733 return self.changelog.heads()
735
734
736 def branches(self, nodes):
735 def branches(self, nodes):
737 if not nodes: nodes = [self.changelog.tip()]
736 if not nodes: nodes = [self.changelog.tip()]
738 b = []
737 b = []
739 for n in nodes:
738 for n in nodes:
740 t = n
739 t = n
741 while n:
740 while n:
742 p = self.changelog.parents(n)
741 p = self.changelog.parents(n)
743 if p[1] != nullid or p[0] == nullid:
742 if p[1] != nullid or p[0] == nullid:
744 b.append((t, n, p[0], p[1]))
743 b.append((t, n, p[0], p[1]))
745 break
744 break
746 n = p[0]
745 n = p[0]
747 return b
746 return b
748
747
749 def between(self, pairs):
748 def between(self, pairs):
750 r = []
749 r = []
751
750
752 for top, bottom in pairs:
751 for top, bottom in pairs:
753 n, l, i = top, [], 0
752 n, l, i = top, [], 0
754 f = 1
753 f = 1
755
754
756 while n != bottom:
755 while n != bottom:
757 p = self.changelog.parents(n)[0]
756 p = self.changelog.parents(n)[0]
758 if i == f:
757 if i == f:
759 l.append(n)
758 l.append(n)
760 f = f * 2
759 f = f * 2
761 n = p
760 n = p
762 i += 1
761 i += 1
763
762
764 r.append(l)
763 r.append(l)
765
764
766 return r
765 return r
767
766
768 def newer(self, nodes):
767 def newer(self, nodes):
769 m = {}
768 m = {}
770 nl = []
769 nl = []
771 pm = {}
770 pm = {}
772 cl = self.changelog
771 cl = self.changelog
773 t = l = cl.count()
772 t = l = cl.count()
774
773
775 # find the lowest numbered node
774 # find the lowest numbered node
776 for n in nodes:
775 for n in nodes:
777 l = min(l, cl.rev(n))
776 l = min(l, cl.rev(n))
778 m[n] = 1
777 m[n] = 1
779
778
780 for i in xrange(l, t):
779 for i in xrange(l, t):
781 n = cl.node(i)
780 n = cl.node(i)
782 if n in m: # explicitly listed
781 if n in m: # explicitly listed
783 pm[n] = 1
782 pm[n] = 1
784 nl.append(n)
783 nl.append(n)
785 continue
784 continue
786 for p in cl.parents(n):
785 for p in cl.parents(n):
787 if p in pm: # parent listed
786 if p in pm: # parent listed
788 pm[n] = 1
787 pm[n] = 1
789 nl.append(n)
788 nl.append(n)
790 break
789 break
791
790
792 return nl
791 return nl
793
792
794 def getchangegroup(self, remote):
793 def getchangegroup(self, remote):
795 m = self.changelog.nodemap
794 m = self.changelog.nodemap
796 search = []
795 search = []
797 fetch = []
796 fetch = []
798 seen = {}
797 seen = {}
799 seenbranch = {}
798 seenbranch = {}
800
799
801 # if we have an empty repo, fetch everything
800 # if we have an empty repo, fetch everything
802 if self.changelog.tip() == nullid:
801 if self.changelog.tip() == nullid:
803 self.ui.status("requesting all changes\n")
802 self.ui.status("requesting all changes\n")
804 return remote.changegroup([nullid])
803 return remote.changegroup([nullid])
805
804
806 # otherwise, assume we're closer to the tip than the root
805 # otherwise, assume we're closer to the tip than the root
807 self.ui.status("searching for changes\n")
806 self.ui.status("searching for changes\n")
808 heads = remote.heads()
807 heads = remote.heads()
809 unknown = []
808 unknown = []
810 for h in heads:
809 for h in heads:
811 if h not in m:
810 if h not in m:
812 unknown.append(h)
811 unknown.append(h)
813
812
814 if not unknown:
813 if not unknown:
815 self.ui.status("nothing to do!\n")
814 self.ui.status("nothing to do!\n")
816 return None
815 return None
817
816
818 rep = {}
817 rep = {}
819 reqcnt = 0
818 reqcnt = 0
820
819
821 unknown = remote.branches(unknown)
820 unknown = remote.branches(unknown)
822 while unknown:
821 while unknown:
823 r = []
822 r = []
824 while unknown:
823 while unknown:
825 n = unknown.pop(0)
824 n = unknown.pop(0)
826 if n[0] in seen:
825 if n[0] in seen:
827 continue
826 continue
828
827
829 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
828 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
830 if n[0] == nullid:
829 if n[0] == nullid:
831 break
830 break
832 if n in seenbranch:
831 if n in seenbranch:
833 self.ui.debug("branch already found\n")
832 self.ui.debug("branch already found\n")
834 continue
833 continue
835 if n[1] and n[1] in m: # do we know the base?
834 if n[1] and n[1] in m: # do we know the base?
836 self.ui.debug("found incomplete branch %s:%s\n"
835 self.ui.debug("found incomplete branch %s:%s\n"
837 % (short(n[0]), short(n[1])))
836 % (short(n[0]), short(n[1])))
838 search.append(n) # schedule branch range for scanning
837 search.append(n) # schedule branch range for scanning
839 seenbranch[n] = 1
838 seenbranch[n] = 1
840 else:
839 else:
841 if n[1] not in seen and n[1] not in fetch:
840 if n[1] not in seen and n[1] not in fetch:
842 if n[2] in m and n[3] in m:
841 if n[2] in m and n[3] in m:
843 self.ui.debug("found new changeset %s\n" %
842 self.ui.debug("found new changeset %s\n" %
844 short(n[1]))
843 short(n[1]))
845 fetch.append(n[1]) # earliest unknown
844 fetch.append(n[1]) # earliest unknown
846 continue
845 continue
847
846
848 for a in n[2:4]:
847 for a in n[2:4]:
849 if a not in rep:
848 if a not in rep:
850 r.append(a)
849 r.append(a)
851 rep[a] = 1
850 rep[a] = 1
852
851
853 seen[n[0]] = 1
852 seen[n[0]] = 1
854
853
855 if r:
854 if r:
856 reqcnt += 1
855 reqcnt += 1
857 self.ui.debug("request %d: %s\n" %
856 self.ui.debug("request %d: %s\n" %
858 (reqcnt, " ".join(map(short, r))))
857 (reqcnt, " ".join(map(short, r))))
859 for p in range(0, len(r), 10):
858 for p in range(0, len(r), 10):
860 for b in remote.branches(r[p:p+10]):
859 for b in remote.branches(r[p:p+10]):
861 self.ui.debug("received %s:%s\n" %
860 self.ui.debug("received %s:%s\n" %
862 (short(b[0]), short(b[1])))
861 (short(b[0]), short(b[1])))
863 if b[0] not in m and b[0] not in seen:
862 if b[0] not in m and b[0] not in seen:
864 unknown.append(b)
863 unknown.append(b)
865
864
866 while search:
865 while search:
867 n = search.pop(0)
866 n = search.pop(0)
868 reqcnt += 1
867 reqcnt += 1
869 l = remote.between([(n[0], n[1])])[0]
868 l = remote.between([(n[0], n[1])])[0]
870 l.append(n[1])
869 l.append(n[1])
871 p = n[0]
870 p = n[0]
872 f = 1
871 f = 1
873 for i in l:
872 for i in l:
874 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
873 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
875 if i in m:
874 if i in m:
876 if f <= 2:
875 if f <= 2:
877 self.ui.debug("found new branch changeset %s\n" %
876 self.ui.debug("found new branch changeset %s\n" %
878 short(p))
877 short(p))
879 fetch.append(p)
878 fetch.append(p)
880 else:
879 else:
881 self.ui.debug("narrowed branch search to %s:%s\n"
880 self.ui.debug("narrowed branch search to %s:%s\n"
882 % (short(p), short(i)))
881 % (short(p), short(i)))
883 search.append((p, i))
882 search.append((p, i))
884 break
883 break
885 p, f = i, f * 2
884 p, f = i, f * 2
886
885
887 for f in fetch:
886 for f in fetch:
888 if f in m:
887 if f in m:
889 raise "already have", short(f[:4])
888 raise "already have", short(f[:4])
890
889
891 self.ui.note("adding new changesets starting at " +
890 self.ui.note("adding new changesets starting at " +
892 " ".join([short(f) for f in fetch]) + "\n")
891 " ".join([short(f) for f in fetch]) + "\n")
893
892
894 self.ui.debug("%d total queries\n" % reqcnt)
893 self.ui.debug("%d total queries\n" % reqcnt)
895
894
896 return remote.changegroup(fetch)
895 return remote.changegroup(fetch)
897
896
898 def changegroup(self, basenodes):
897 def changegroup(self, basenodes):
899 nodes = self.newer(basenodes)
898 nodes = self.newer(basenodes)
900
899
901 # construct the link map
900 # construct the link map
902 linkmap = {}
901 linkmap = {}
903 for n in nodes:
902 for n in nodes:
904 linkmap[self.changelog.rev(n)] = n
903 linkmap[self.changelog.rev(n)] = n
905
904
906 # construct a list of all changed files
905 # construct a list of all changed files
907 changed = {}
906 changed = {}
908 for n in nodes:
907 for n in nodes:
909 c = self.changelog.read(n)
908 c = self.changelog.read(n)
910 for f in c[3]:
909 for f in c[3]:
911 changed[f] = 1
910 changed[f] = 1
912 changed = changed.keys()
911 changed = changed.keys()
913 changed.sort()
912 changed.sort()
914
913
915 # the changegroup is changesets + manifests + all file revs
914 # the changegroup is changesets + manifests + all file revs
916 revs = [ self.changelog.rev(n) for n in nodes ]
915 revs = [ self.changelog.rev(n) for n in nodes ]
917
916
918 for y in self.changelog.group(linkmap): yield y
917 for y in self.changelog.group(linkmap): yield y
919 for y in self.manifest.group(linkmap): yield y
918 for y in self.manifest.group(linkmap): yield y
920 for f in changed:
919 for f in changed:
921 yield struct.pack(">l", len(f) + 4) + f
920 yield struct.pack(">l", len(f) + 4) + f
922 g = self.file(f).group(linkmap)
921 g = self.file(f).group(linkmap)
923 for y in g:
922 for y in g:
924 yield y
923 yield y
925
924
926 def addchangegroup(self, generator):
925 def addchangegroup(self, generator):
927
926
928 class genread:
927 class genread:
929 def __init__(self, generator):
928 def __init__(self, generator):
930 self.g = generator
929 self.g = generator
931 self.buf = ""
930 self.buf = ""
932 def read(self, l):
931 def read(self, l):
933 while l > len(self.buf):
932 while l > len(self.buf):
934 try:
933 try:
935 self.buf += self.g.next()
934 self.buf += self.g.next()
936 except StopIteration:
935 except StopIteration:
937 break
936 break
938 d, self.buf = self.buf[:l], self.buf[l:]
937 d, self.buf = self.buf[:l], self.buf[l:]
939 return d
938 return d
940
939
941 def getchunk():
940 def getchunk():
942 d = source.read(4)
941 d = source.read(4)
943 if not d: return ""
942 if not d: return ""
944 l = struct.unpack(">l", d)[0]
943 l = struct.unpack(">l", d)[0]
945 if l <= 4: return ""
944 if l <= 4: return ""
946 return source.read(l - 4)
945 return source.read(l - 4)
947
946
948 def getgroup():
947 def getgroup():
949 while 1:
948 while 1:
950 c = getchunk()
949 c = getchunk()
951 if not c: break
950 if not c: break
952 yield c
951 yield c
953
952
954 def csmap(x):
953 def csmap(x):
955 self.ui.debug("add changeset %s\n" % short(x))
954 self.ui.debug("add changeset %s\n" % short(x))
956 return self.changelog.count()
955 return self.changelog.count()
957
956
958 def revmap(x):
957 def revmap(x):
959 return self.changelog.rev(x)
958 return self.changelog.rev(x)
960
959
961 if not generator: return
960 if not generator: return
962 changesets = files = revisions = 0
961 changesets = files = revisions = 0
963
962
964 source = genread(generator)
963 source = genread(generator)
965 lock = self.lock()
964 lock = self.lock()
966 tr = self.transaction()
965 tr = self.transaction()
967
966
968 # pull off the changeset group
967 # pull off the changeset group
969 self.ui.status("adding changesets\n")
968 self.ui.status("adding changesets\n")
970 co = self.changelog.tip()
969 co = self.changelog.tip()
971 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
970 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
972 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
971 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
973
972
974 # pull off the manifest group
973 # pull off the manifest group
975 self.ui.status("adding manifests\n")
974 self.ui.status("adding manifests\n")
976 mm = self.manifest.tip()
975 mm = self.manifest.tip()
977 mo = self.manifest.addgroup(getgroup(), revmap, tr)
976 mo = self.manifest.addgroup(getgroup(), revmap, tr)
978
977
979 # process the files
978 # process the files
980 self.ui.status("adding file revisions\n")
979 self.ui.status("adding file revisions\n")
981 while 1:
980 while 1:
982 f = getchunk()
981 f = getchunk()
983 if not f: break
982 if not f: break
984 self.ui.debug("adding %s revisions\n" % f)
983 self.ui.debug("adding %s revisions\n" % f)
985 fl = self.file(f)
984 fl = self.file(f)
986 o = fl.tip()
985 o = fl.tip()
987 n = fl.addgroup(getgroup(), revmap, tr)
986 n = fl.addgroup(getgroup(), revmap, tr)
988 revisions += fl.rev(n) - fl.rev(o)
987 revisions += fl.rev(n) - fl.rev(o)
989 files += 1
988 files += 1
990
989
991 self.ui.status(("modified %d files, added %d changesets" +
990 self.ui.status(("modified %d files, added %d changesets" +
992 " and %d new revisions\n")
991 " and %d new revisions\n")
993 % (files, changesets, revisions))
992 % (files, changesets, revisions))
994
993
995 tr.close()
994 tr.close()
996 return
995 return
997
996
998 def update(self, node, allow=False, force=False):
997 def update(self, node, allow=False, force=False):
999 pl = self.dirstate.parents()
998 pl = self.dirstate.parents()
1000 if not force and pl[1] != nullid:
999 if not force and pl[1] != nullid:
1001 self.ui.warn("aborting: outstanding uncommitted merges\n")
1000 self.ui.warn("aborting: outstanding uncommitted merges\n")
1002 return
1001 return
1003
1002
1004 p1, p2 = pl[0], node
1003 p1, p2 = pl[0], node
1005 pa = self.changelog.ancestor(p1, p2)
1004 pa = self.changelog.ancestor(p1, p2)
1006 m1n = self.changelog.read(p1)[0]
1005 m1n = self.changelog.read(p1)[0]
1007 m2n = self.changelog.read(p2)[0]
1006 m2n = self.changelog.read(p2)[0]
1008 man = self.manifest.ancestor(m1n, m2n)
1007 man = self.manifest.ancestor(m1n, m2n)
1009 m1 = self.manifest.read(m1n)
1008 m1 = self.manifest.read(m1n)
1010 mf1 = self.manifest.readflags(m1n)
1009 mf1 = self.manifest.readflags(m1n)
1011 m2 = self.manifest.read(m2n)
1010 m2 = self.manifest.read(m2n)
1012 mf2 = self.manifest.readflags(m2n)
1011 mf2 = self.manifest.readflags(m2n)
1013 ma = self.manifest.read(man)
1012 ma = self.manifest.read(man)
1014 mfa = self.manifest.readflags(man)
1013 mfa = self.manifest.readflags(man)
1015
1014
1016 (c, a, d, u) = self.diffdir(self.root)
1015 (c, a, d, u) = self.diffdir(self.root)
1017
1016
1018 # is this a jump, or a merge? i.e. is there a linear path
1017 # is this a jump, or a merge? i.e. is there a linear path
1019 # from p1 to p2?
1018 # from p1 to p2?
1020 linear_path = (pa == p1 or pa == p2)
1019 linear_path = (pa == p1 or pa == p2)
1021
1020
1022 # resolve the manifest to determine which files
1021 # resolve the manifest to determine which files
1023 # we care about merging
1022 # we care about merging
1024 self.ui.note("resolving manifests\n")
1023 self.ui.note("resolving manifests\n")
1025 self.ui.debug(" ancestor %s local %s remote %s\n" %
1024 self.ui.debug(" ancestor %s local %s remote %s\n" %
1026 (short(man), short(m1n), short(m2n)))
1025 (short(man), short(m1n), short(m2n)))
1027
1026
1028 merge = {}
1027 merge = {}
1029 get = {}
1028 get = {}
1030 remove = []
1029 remove = []
1031 mark = {}
1030 mark = {}
1032
1031
1033 # construct a working dir manifest
1032 # construct a working dir manifest
1034 mw = m1.copy()
1033 mw = m1.copy()
1035 mfw = mf1.copy()
1034 mfw = mf1.copy()
1036 for f in a + c + u:
1035 for f in a + c + u:
1037 mw[f] = ""
1036 mw[f] = ""
1038 mfw[f] = is_exec(self.wjoin(f))
1037 mfw[f] = is_exec(self.wjoin(f))
1039 for f in d:
1038 for f in d:
1040 if f in mw: del mw[f]
1039 if f in mw: del mw[f]
1041
1040
1042 # If we're jumping between revisions (as opposed to merging),
1041 # If we're jumping between revisions (as opposed to merging),
1043 # and if neither the working directory nor the target rev has
1042 # and if neither the working directory nor the target rev has
1044 # the file, then we need to remove it from the dirstate, to
1043 # the file, then we need to remove it from the dirstate, to
1045 # prevent the dirstate from listing the file when it is no
1044 # prevent the dirstate from listing the file when it is no
1046 # longer in the manifest.
1045 # longer in the manifest.
1047 if linear_path and f not in m2:
1046 if linear_path and f not in m2:
1048 self.dirstate.forget((f,))
1047 self.dirstate.forget((f,))
1049
1048
1050 for f, n in mw.iteritems():
1049 for f, n in mw.iteritems():
1051 if f in m2:
1050 if f in m2:
1052 s = 0
1051 s = 0
1053
1052
1054 # is the wfile new since m1, and match m2?
1053 # is the wfile new since m1, and match m2?
1055 if f not in m1:
1054 if f not in m1:
1056 t1 = self.wfile(f).read()
1055 t1 = self.wfile(f).read()
1057 t2 = self.file(f).revision(m2[f])
1056 t2 = self.file(f).revision(m2[f])
1058 if cmp(t1, t2) == 0:
1057 if cmp(t1, t2) == 0:
1059 mark[f] = 1
1058 mark[f] = 1
1060 n = m2[f]
1059 n = m2[f]
1061 del t1, t2
1060 del t1, t2
1062
1061
1063 # are files different?
1062 # are files different?
1064 if n != m2[f]:
1063 if n != m2[f]:
1065 a = ma.get(f, nullid)
1064 a = ma.get(f, nullid)
1066 # are both different from the ancestor?
1065 # are both different from the ancestor?
1067 if n != a and m2[f] != a:
1066 if n != a and m2[f] != a:
1068 self.ui.debug(" %s versions differ, resolve\n" % f)
1067 self.ui.debug(" %s versions differ, resolve\n" % f)
1069 # merge executable bits
1068 # merge executable bits
1070 # "if we changed or they changed, change in merge"
1069 # "if we changed or they changed, change in merge"
1071 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1070 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1072 mode = ((a^b) | (a^c)) ^ a
1071 mode = ((a^b) | (a^c)) ^ a
1073 merge[f] = (m1.get(f, nullid), m2[f], mode)
1072 merge[f] = (m1.get(f, nullid), m2[f], mode)
1074 s = 1
1073 s = 1
1075 # are we clobbering?
1074 # are we clobbering?
1076 # is remote's version newer?
1075 # is remote's version newer?
1077 # or are we going back in time?
1076 # or are we going back in time?
1078 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1077 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1079 self.ui.debug(" remote %s is newer, get\n" % f)
1078 self.ui.debug(" remote %s is newer, get\n" % f)
1080 get[f] = m2[f]
1079 get[f] = m2[f]
1081 s = 1
1080 s = 1
1082 else:
1081 else:
1083 mark[f] = 1
1082 mark[f] = 1
1084
1083
1085 if not s and mfw[f] != mf2[f]:
1084 if not s and mfw[f] != mf2[f]:
1086 if force:
1085 if force:
1087 self.ui.debug(" updating permissions for %s\n" % f)
1086 self.ui.debug(" updating permissions for %s\n" % f)
1088 set_exec(self.wjoin(f), mf2[f])
1087 set_exec(self.wjoin(f), mf2[f])
1089 else:
1088 else:
1090 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1089 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1091 mode = ((a^b) | (a^c)) ^ a
1090 mode = ((a^b) | (a^c)) ^ a
1092 if mode != b:
1091 if mode != b:
1093 self.ui.debug(" updating permissions for %s\n" % f)
1092 self.ui.debug(" updating permissions for %s\n" % f)
1094 set_exec(self.wjoin(f), mode)
1093 set_exec(self.wjoin(f), mode)
1095 mark[f] = 1
1094 mark[f] = 1
1096 del m2[f]
1095 del m2[f]
1097 elif f in ma:
1096 elif f in ma:
1098 if not force and n != ma[f]:
1097 if not force and n != ma[f]:
1099 r = ""
1098 r = ""
1100 if linear_path or allow:
1099 if linear_path or allow:
1101 r = self.ui.prompt(
1100 r = self.ui.prompt(
1102 (" local changed %s which remote deleted\n" % f) +
1101 (" local changed %s which remote deleted\n" % f) +
1103 "(k)eep or (d)elete?", "[kd]", "k")
1102 "(k)eep or (d)elete?", "[kd]", "k")
1104 if r == "d":
1103 if r == "d":
1105 remove.append(f)
1104 remove.append(f)
1106 else:
1105 else:
1107 self.ui.debug("other deleted %s\n" % f)
1106 self.ui.debug("other deleted %s\n" % f)
1108 remove.append(f) # other deleted it
1107 remove.append(f) # other deleted it
1109 else:
1108 else:
1110 if n == m1.get(f, nullid): # same as parent
1109 if n == m1.get(f, nullid): # same as parent
1111 if p2 == pa: # going backwards?
1110 if p2 == pa: # going backwards?
1112 self.ui.debug("remote deleted %s\n" % f)
1111 self.ui.debug("remote deleted %s\n" % f)
1113 remove.append(f)
1112 remove.append(f)
1114 else:
1113 else:
1115 self.ui.debug("local created %s, keeping\n" % f)
1114 self.ui.debug("local created %s, keeping\n" % f)
1116 else:
1115 else:
1117 self.ui.debug("working dir created %s, keeping\n" % f)
1116 self.ui.debug("working dir created %s, keeping\n" % f)
1118
1117
1119 for f, n in m2.iteritems():
1118 for f, n in m2.iteritems():
1120 if f[0] == "/": continue
1119 if f[0] == "/": continue
1121 if not force and f in ma and n != ma[f]:
1120 if not force and f in ma and n != ma[f]:
1122 r = ""
1121 r = ""
1123 if linear_path or allow:
1122 if linear_path or allow:
1124 r = self.ui.prompt(
1123 r = self.ui.prompt(
1125 ("remote changed %s which local deleted\n" % f) +
1124 ("remote changed %s which local deleted\n" % f) +
1126 "(k)eep or (d)elete?", "[kd]", "k")
1125 "(k)eep or (d)elete?", "[kd]", "k")
1127 if r == "d": remove.append(f)
1126 if r == "d": remove.append(f)
1128 else:
1127 else:
1129 self.ui.debug("remote created %s\n" % f)
1128 self.ui.debug("remote created %s\n" % f)
1130 get[f] = n
1129 get[f] = n
1131
1130
1132 del mw, m1, m2, ma
1131 del mw, m1, m2, ma
1133
1132
1134 if force:
1133 if force:
1135 for f in merge:
1134 for f in merge:
1136 get[f] = merge[f][1]
1135 get[f] = merge[f][1]
1137 merge = {}
1136 merge = {}
1138
1137
1139 if linear_path:
1138 if linear_path:
1140 # we don't need to do any magic, just jump to the new rev
1139 # we don't need to do any magic, just jump to the new rev
1141 mode = 'n'
1140 mode = 'n'
1142 p1, p2 = p2, nullid
1141 p1, p2 = p2, nullid
1143 else:
1142 else:
1144 if not allow:
1143 if not allow:
1145 self.ui.status("this update spans a branch" +
1144 self.ui.status("this update spans a branch" +
1146 " affecting the following files:\n")
1145 " affecting the following files:\n")
1147 fl = merge.keys() + get.keys()
1146 fl = merge.keys() + get.keys()
1148 fl.sort()
1147 fl.sort()
1149 for f in fl:
1148 for f in fl:
1150 cf = ""
1149 cf = ""
1151 if f in merge: cf = " (resolve)"
1150 if f in merge: cf = " (resolve)"
1152 self.ui.status(" %s%s\n" % (f, cf))
1151 self.ui.status(" %s%s\n" % (f, cf))
1153 self.ui.warn("aborting update spanning branches!\n")
1152 self.ui.warn("aborting update spanning branches!\n")
1154 self.ui.status("(use update -m to perform a branch merge)\n")
1153 self.ui.status("(use update -m to perform a branch merge)\n")
1155 return 1
1154 return 1
1156 # we have to remember what files we needed to get/change
1155 # we have to remember what files we needed to get/change
1157 # because any file that's different from either one of its
1156 # because any file that's different from either one of its
1158 # parents must be in the changeset
1157 # parents must be in the changeset
1159 mode = 'm'
1158 mode = 'm'
1160 self.dirstate.update(mark.keys(), "m")
1159 self.dirstate.update(mark.keys(), "m")
1161
1160
1162 self.dirstate.setparents(p1, p2)
1161 self.dirstate.setparents(p1, p2)
1163
1162
1164 # get the files we don't need to change
1163 # get the files we don't need to change
1165 files = get.keys()
1164 files = get.keys()
1166 files.sort()
1165 files.sort()
1167 for f in files:
1166 for f in files:
1168 if f[0] == "/": continue
1167 if f[0] == "/": continue
1169 self.ui.note("getting %s\n" % f)
1168 self.ui.note("getting %s\n" % f)
1170 t = self.file(f).read(get[f])
1169 t = self.file(f).read(get[f])
1171 try:
1170 try:
1172 self.wfile(f, "w").write(t)
1171 self.wfile(f, "w").write(t)
1173 except IOError:
1172 except IOError:
1174 os.makedirs(os.path.dirname(self.wjoin(f)))
1173 os.makedirs(os.path.dirname(self.wjoin(f)))
1175 self.wfile(f, "w").write(t)
1174 self.wfile(f, "w").write(t)
1176 set_exec(self.wjoin(f), mf2[f])
1175 set_exec(self.wjoin(f), mf2[f])
1177 self.dirstate.update([f], mode)
1176 self.dirstate.update([f], mode)
1178
1177
1179 # merge the tricky bits
1178 # merge the tricky bits
1180 files = merge.keys()
1179 files = merge.keys()
1181 files.sort()
1180 files.sort()
1182 for f in files:
1181 for f in files:
1183 self.ui.status("merging %s\n" % f)
1182 self.ui.status("merging %s\n" % f)
1184 m, o, flag = merge[f]
1183 m, o, flag = merge[f]
1185 self.merge3(f, m, o)
1184 self.merge3(f, m, o)
1186 set_exec(self.wjoin(f), flag)
1185 set_exec(self.wjoin(f), flag)
1187 self.dirstate.update([f], 'm')
1186 self.dirstate.update([f], 'm')
1188
1187
1189 for f in remove:
1188 for f in remove:
1190 self.ui.note("removing %s\n" % f)
1189 self.ui.note("removing %s\n" % f)
1191 os.unlink(f)
1190 os.unlink(f)
1192 if mode == 'n':
1191 if mode == 'n':
1193 self.dirstate.forget(remove)
1192 self.dirstate.forget(remove)
1194 else:
1193 else:
1195 self.dirstate.update(remove, 'r')
1194 self.dirstate.update(remove, 'r')
1196
1195
1197 def merge3(self, fn, my, other):
1196 def merge3(self, fn, my, other):
1198 """perform a 3-way merge in the working directory"""
1197 """perform a 3-way merge in the working directory"""
1199
1198
1200 def temp(prefix, node):
1199 def temp(prefix, node):
1201 pre = "%s~%s." % (os.path.basename(fn), prefix)
1200 pre = "%s~%s." % (os.path.basename(fn), prefix)
1202 (fd, name) = tempfile.mkstemp("", pre)
1201 (fd, name) = tempfile.mkstemp("", pre)
1203 f = os.fdopen(fd, "wb")
1202 f = os.fdopen(fd, "wb")
1204 f.write(fl.revision(node))
1203 f.write(fl.revision(node))
1205 f.close()
1204 f.close()
1206 return name
1205 return name
1207
1206
1208 fl = self.file(fn)
1207 fl = self.file(fn)
1209 base = fl.ancestor(my, other)
1208 base = fl.ancestor(my, other)
1210 a = self.wjoin(fn)
1209 a = self.wjoin(fn)
1211 b = temp("base", base)
1210 b = temp("base", base)
1212 c = temp("other", other)
1211 c = temp("other", other)
1213
1212
1214 self.ui.note("resolving %s\n" % fn)
1213 self.ui.note("resolving %s\n" % fn)
1215 self.ui.debug("file %s: other %s ancestor %s\n" %
1214 self.ui.debug("file %s: other %s ancestor %s\n" %
1216 (fn, short(other), short(base)))
1215 (fn, short(other), short(base)))
1217
1216
1218 cmd = os.environ.get("HGMERGE", "hgmerge")
1217 cmd = os.environ.get("HGMERGE", "hgmerge")
1219 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1218 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1220 if r:
1219 if r:
1221 self.ui.warn("merging %s failed!\n" % fn)
1220 self.ui.warn("merging %s failed!\n" % fn)
1222
1221
1223 os.unlink(b)
1222 os.unlink(b)
1224 os.unlink(c)
1223 os.unlink(c)
1225
1224
1226 def verify(self):
1225 def verify(self):
1227 filelinkrevs = {}
1226 filelinkrevs = {}
1228 filenodes = {}
1227 filenodes = {}
1229 changesets = revisions = files = 0
1228 changesets = revisions = files = 0
1230 errors = 0
1229 errors = 0
1231
1230
1232 seen = {}
1231 seen = {}
1233 self.ui.status("checking changesets\n")
1232 self.ui.status("checking changesets\n")
1234 for i in range(self.changelog.count()):
1233 for i in range(self.changelog.count()):
1235 changesets += 1
1234 changesets += 1
1236 n = self.changelog.node(i)
1235 n = self.changelog.node(i)
1237 if n in seen:
1236 if n in seen:
1238 self.ui.warn("duplicate changeset at revision %d\n" % i)
1237 self.ui.warn("duplicate changeset at revision %d\n" % i)
1239 errors += 1
1238 errors += 1
1240 seen[n] = 1
1239 seen[n] = 1
1241
1240
1242 for p in self.changelog.parents(n):
1241 for p in self.changelog.parents(n):
1243 if p not in self.changelog.nodemap:
1242 if p not in self.changelog.nodemap:
1244 self.ui.warn("changeset %s has unknown parent %s\n" %
1243 self.ui.warn("changeset %s has unknown parent %s\n" %
1245 (short(n), short(p)))
1244 (short(n), short(p)))
1246 errors += 1
1245 errors += 1
1247 try:
1246 try:
1248 changes = self.changelog.read(n)
1247 changes = self.changelog.read(n)
1249 except Exception, inst:
1248 except Exception, inst:
1250 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1249 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1251 errors += 1
1250 errors += 1
1252
1251
1253 for f in changes[3]:
1252 for f in changes[3]:
1254 filelinkrevs.setdefault(f, []).append(i)
1253 filelinkrevs.setdefault(f, []).append(i)
1255
1254
1256 seen = {}
1255 seen = {}
1257 self.ui.status("checking manifests\n")
1256 self.ui.status("checking manifests\n")
1258 for i in range(self.manifest.count()):
1257 for i in range(self.manifest.count()):
1259 n = self.manifest.node(i)
1258 n = self.manifest.node(i)
1260 if n in seen:
1259 if n in seen:
1261 self.ui.warn("duplicate manifest at revision %d\n" % i)
1260 self.ui.warn("duplicate manifest at revision %d\n" % i)
1262 errors += 1
1261 errors += 1
1263 seen[n] = 1
1262 seen[n] = 1
1264
1263
1265 for p in self.manifest.parents(n):
1264 for p in self.manifest.parents(n):
1266 if p not in self.manifest.nodemap:
1265 if p not in self.manifest.nodemap:
1267 self.ui.warn("manifest %s has unknown parent %s\n" %
1266 self.ui.warn("manifest %s has unknown parent %s\n" %
1268 (short(n), short(p)))
1267 (short(n), short(p)))
1269 errors += 1
1268 errors += 1
1270
1269
1271 try:
1270 try:
1272 delta = mdiff.patchtext(self.manifest.delta(n))
1271 delta = mdiff.patchtext(self.manifest.delta(n))
1273 except KeyboardInterrupt:
1272 except KeyboardInterrupt:
1274 print "aborted"
1273 print "aborted"
1275 sys.exit(0)
1274 sys.exit(0)
1276 except Exception, inst:
1275 except Exception, inst:
1277 self.ui.warn("unpacking manifest %s: %s\n"
1276 self.ui.warn("unpacking manifest %s: %s\n"
1278 % (short(n), inst))
1277 % (short(n), inst))
1279 errors += 1
1278 errors += 1
1280
1279
1281 ff = [ l.split('\0') for l in delta.splitlines() ]
1280 ff = [ l.split('\0') for l in delta.splitlines() ]
1282 for f, fn in ff:
1281 for f, fn in ff:
1283 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1282 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1284
1283
1285 self.ui.status("crosschecking files in changesets and manifests\n")
1284 self.ui.status("crosschecking files in changesets and manifests\n")
1286 for f in filenodes:
1285 for f in filenodes:
1287 if f not in filelinkrevs:
1286 if f not in filelinkrevs:
1288 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1287 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1289 errors += 1
1288 errors += 1
1290
1289
1291 for f in filelinkrevs:
1290 for f in filelinkrevs:
1292 if f not in filenodes:
1291 if f not in filenodes:
1293 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1292 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1294 errors += 1
1293 errors += 1
1295
1294
1296 self.ui.status("checking files\n")
1295 self.ui.status("checking files\n")
1297 ff = filenodes.keys()
1296 ff = filenodes.keys()
1298 ff.sort()
1297 ff.sort()
1299 for f in ff:
1298 for f in ff:
1300 if f == "/dev/null": continue
1299 if f == "/dev/null": continue
1301 files += 1
1300 files += 1
1302 fl = self.file(f)
1301 fl = self.file(f)
1303 nodes = { nullid: 1 }
1302 nodes = { nullid: 1 }
1304 seen = {}
1303 seen = {}
1305 for i in range(fl.count()):
1304 for i in range(fl.count()):
1306 revisions += 1
1305 revisions += 1
1307 n = fl.node(i)
1306 n = fl.node(i)
1308
1307
1309 if n in seen:
1308 if n in seen:
1310 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1309 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1311 errors += 1
1310 errors += 1
1312
1311
1313 if n not in filenodes[f]:
1312 if n not in filenodes[f]:
1314 self.ui.warn("%s: %d:%s not in manifests\n"
1313 self.ui.warn("%s: %d:%s not in manifests\n"
1315 % (f, i, short(n)))
1314 % (f, i, short(n)))
1316 print len(filenodes[f].keys()), fl.count(), f
1315 print len(filenodes[f].keys()), fl.count(), f
1317 errors += 1
1316 errors += 1
1318 else:
1317 else:
1319 del filenodes[f][n]
1318 del filenodes[f][n]
1320
1319
1321 flr = fl.linkrev(n)
1320 flr = fl.linkrev(n)
1322 if flr not in filelinkrevs[f]:
1321 if flr not in filelinkrevs[f]:
1323 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1322 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1324 % (f, short(n), fl.linkrev(n)))
1323 % (f, short(n), fl.linkrev(n)))
1325 errors += 1
1324 errors += 1
1326 else:
1325 else:
1327 filelinkrevs[f].remove(flr)
1326 filelinkrevs[f].remove(flr)
1328
1327
1329 # verify contents
1328 # verify contents
1330 try:
1329 try:
1331 t = fl.read(n)
1330 t = fl.read(n)
1332 except Exception, inst:
1331 except Exception, inst:
1333 self.ui.warn("unpacking file %s %s: %s\n"
1332 self.ui.warn("unpacking file %s %s: %s\n"
1334 % (f, short(n), inst))
1333 % (f, short(n), inst))
1335 errors += 1
1334 errors += 1
1336
1335
1337 # verify parents
1336 # verify parents
1338 (p1, p2) = fl.parents(n)
1337 (p1, p2) = fl.parents(n)
1339 if p1 not in nodes:
1338 if p1 not in nodes:
1340 self.ui.warn("file %s:%s unknown parent 1 %s" %
1339 self.ui.warn("file %s:%s unknown parent 1 %s" %
1341 (f, short(n), short(p1)))
1340 (f, short(n), short(p1)))
1342 errors += 1
1341 errors += 1
1343 if p2 not in nodes:
1342 if p2 not in nodes:
1344 self.ui.warn("file %s:%s unknown parent 2 %s" %
1343 self.ui.warn("file %s:%s unknown parent 2 %s" %
1345 (f, short(n), short(p1)))
1344 (f, short(n), short(p1)))
1346 errors += 1
1345 errors += 1
1347 nodes[n] = 1
1346 nodes[n] = 1
1348
1347
1349 # cross-check
1348 # cross-check
1350 for node in filenodes[f]:
1349 for node in filenodes[f]:
1351 self.ui.warn("node %s in manifests not in %s\n"
1350 self.ui.warn("node %s in manifests not in %s\n"
1352 % (hex(n), f))
1351 % (hex(n), f))
1353 errors += 1
1352 errors += 1
1354
1353
1355 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1354 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1356 (files, changesets, revisions))
1355 (files, changesets, revisions))
1357
1356
1358 if errors:
1357 if errors:
1359 self.ui.warn("%d integrity errors encountered!\n" % errors)
1358 self.ui.warn("%d integrity errors encountered!\n" % errors)
1360 return 1
1359 return 1
1361
1360
1362 class remoterepository:
1361 class remoterepository:
1363 def __init__(self, ui, path):
1362 def __init__(self, ui, path):
1364 self.url = path
1363 self.url = path
1365 self.ui = ui
1364 self.ui = ui
1366 no_list = [ "localhost", "127.0.0.1" ]
1365 no_list = [ "localhost", "127.0.0.1" ]
1367 host = ui.config("http_proxy", "host")
1366 host = ui.config("http_proxy", "host")
1368 if host is None:
1367 if host is None:
1369 host = os.environ.get("http_proxy")
1368 host = os.environ.get("http_proxy")
1370 if host and host.startswith('http://'):
1369 if host and host.startswith('http://'):
1371 host = host[7:]
1370 host = host[7:]
1372 user = ui.config("http_proxy", "user")
1371 user = ui.config("http_proxy", "user")
1373 passwd = ui.config("http_proxy", "passwd")
1372 passwd = ui.config("http_proxy", "passwd")
1374 no = ui.config("http_proxy", "no")
1373 no = ui.config("http_proxy", "no")
1375 if no is None:
1374 if no is None:
1376 no = os.environ.get("no_proxy")
1375 no = os.environ.get("no_proxy")
1377 if no:
1376 if no:
1378 no_list = no_list + no.split(",")
1377 no_list = no_list + no.split(",")
1379
1378
1380 no_proxy = 0
1379 no_proxy = 0
1381 for h in no_list:
1380 for h in no_list:
1382 if (path.startswith("http://" + h + "/") or
1381 if (path.startswith("http://" + h + "/") or
1383 path.startswith("http://" + h + ":") or
1382 path.startswith("http://" + h + ":") or
1384 path == "http://" + h):
1383 path == "http://" + h):
1385 no_proxy = 1
1384 no_proxy = 1
1386
1385
1387 # Note: urllib2 takes proxy values from the environment and those will
1386 # Note: urllib2 takes proxy values from the environment and those will
1388 # take precedence
1387 # take precedence
1389 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1388 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1390 if os.environ.has_key(env):
1389 if os.environ.has_key(env):
1391 del os.environ[env]
1390 del os.environ[env]
1392
1391
1393 proxy_handler = urllib2.BaseHandler()
1392 proxy_handler = urllib2.BaseHandler()
1394 if host and not no_proxy:
1393 if host and not no_proxy:
1395 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1394 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1396
1395
1397 authinfo = None
1396 authinfo = None
1398 if user and passwd:
1397 if user and passwd:
1399 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1398 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1400 passmgr.add_password(None, host, user, passwd)
1399 passmgr.add_password(None, host, user, passwd)
1401 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1400 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1402
1401
1403 opener = urllib2.build_opener(proxy_handler, authinfo)
1402 opener = urllib2.build_opener(proxy_handler, authinfo)
1404 urllib2.install_opener(opener)
1403 urllib2.install_opener(opener)
1405
1404
1406 def do_cmd(self, cmd, **args):
1405 def do_cmd(self, cmd, **args):
1407 self.ui.debug("sending %s command\n" % cmd)
1406 self.ui.debug("sending %s command\n" % cmd)
1408 q = {"cmd": cmd}
1407 q = {"cmd": cmd}
1409 q.update(args)
1408 q.update(args)
1410 qs = urllib.urlencode(q)
1409 qs = urllib.urlencode(q)
1411 cu = "%s?%s" % (self.url, qs)
1410 cu = "%s?%s" % (self.url, qs)
1412 return urllib2.urlopen(cu)
1411 return urllib2.urlopen(cu)
1413
1412
1414 def heads(self):
1413 def heads(self):
1415 d = self.do_cmd("heads").read()
1414 d = self.do_cmd("heads").read()
1416 try:
1415 try:
1417 return map(bin, d[:-1].split(" "))
1416 return map(bin, d[:-1].split(" "))
1418 except:
1417 except:
1419 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1418 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1420 raise
1419 raise
1421
1420
1422 def branches(self, nodes):
1421 def branches(self, nodes):
1423 n = " ".join(map(hex, nodes))
1422 n = " ".join(map(hex, nodes))
1424 d = self.do_cmd("branches", nodes=n).read()
1423 d = self.do_cmd("branches", nodes=n).read()
1425 try:
1424 try:
1426 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1425 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1427 return br
1426 return br
1428 except:
1427 except:
1429 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1428 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1430 raise
1429 raise
1431
1430
1432 def between(self, pairs):
1431 def between(self, pairs):
1433 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1432 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1434 d = self.do_cmd("between", pairs=n).read()
1433 d = self.do_cmd("between", pairs=n).read()
1435 try:
1434 try:
1436 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1435 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1437 return p
1436 return p
1438 except:
1437 except:
1439 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1438 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1440 raise
1439 raise
1441
1440
1442 def changegroup(self, nodes):
1441 def changegroup(self, nodes):
1443 n = " ".join(map(hex, nodes))
1442 n = " ".join(map(hex, nodes))
1444 zd = zlib.decompressobj()
1443 zd = zlib.decompressobj()
1445 f = self.do_cmd("changegroup", roots=n)
1444 f = self.do_cmd("changegroup", roots=n)
1446 bytes = 0
1445 bytes = 0
1447 while 1:
1446 while 1:
1448 d = f.read(4096)
1447 d = f.read(4096)
1449 bytes += len(d)
1448 bytes += len(d)
1450 if not d:
1449 if not d:
1451 yield zd.flush()
1450 yield zd.flush()
1452 break
1451 break
1453 yield zd.decompress(d)
1452 yield zd.decompress(d)
1454 self.ui.note("%d bytes of data transfered\n" % bytes)
1453 self.ui.note("%d bytes of data transfered\n" % bytes)
1455
1454
1456 def repository(ui, path=None, create=0):
1455 def repository(ui, path=None, create=0):
1457 if path and path[:7] == "http://":
1456 if path and path[:7] == "http://":
1458 return remoterepository(ui, path)
1457 return remoterepository(ui, path)
1459 if path and path[:5] == "hg://":
1458 if path and path[:5] == "hg://":
1460 return remoterepository(ui, path.replace("hg://", "http://"))
1459 return remoterepository(ui, path.replace("hg://", "http://"))
1461 if path and path[:11] == "old-http://":
1460 if path and path[:11] == "old-http://":
1462 return localrepository(ui, path.replace("old-http://", "http://"))
1461 return localrepository(ui, path.replace("old-http://", "http://"))
1463 else:
1462 else:
1464 return localrepository(ui, path, create)
1463 return localrepository(ui, path, create)
1465
1464
General Comments 0
You need to be logged in to leave comments. Login now