##// END OF EJS Templates
Fix bug in reverting deleted files...
Matt Mackall -
r650:2c934c7b default
parent child Browse files
Show More
@@ -1,1864 +1,1868 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 bdiff")
13 demandload(globals(), "tempfile httprangereader bdiff")
14 demandload(globals(), "bisect select")
14 demandload(globals(), "bisect select")
15
15
16 class filelog(revlog):
16 class filelog(revlog):
17 def __init__(self, opener, path):
17 def __init__(self, opener, path):
18 revlog.__init__(self, opener,
18 revlog.__init__(self, opener,
19 os.path.join("data", path + ".i"),
19 os.path.join("data", path + ".i"),
20 os.path.join("data", path + ".d"))
20 os.path.join("data", path + ".d"))
21
21
22 def read(self, node):
22 def read(self, node):
23 t = self.revision(node)
23 t = self.revision(node)
24 if t[:2] != '\1\n':
24 if t[:2] != '\1\n':
25 return t
25 return t
26 s = t.find('\1\n', 2)
26 s = t.find('\1\n', 2)
27 return t[s+2:]
27 return t[s+2:]
28
28
29 def readmeta(self, node):
29 def readmeta(self, node):
30 t = self.revision(node)
30 t = self.revision(node)
31 if t[:2] != '\1\n':
31 if t[:2] != '\1\n':
32 return t
32 return t
33 s = t.find('\1\n', 2)
33 s = t.find('\1\n', 2)
34 mt = t[2:s]
34 mt = t[2:s]
35 for l in mt.splitlines():
35 for l in mt.splitlines():
36 k, v = l.split(": ", 1)
36 k, v = l.split(": ", 1)
37 m[k] = v
37 m[k] = v
38 return m
38 return m
39
39
40 def add(self, text, meta, transaction, link, p1=None, p2=None):
40 def add(self, text, meta, transaction, link, p1=None, p2=None):
41 if meta or text[:2] == '\1\n':
41 if meta or text[:2] == '\1\n':
42 mt = ""
42 mt = ""
43 if meta:
43 if meta:
44 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
44 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
45 text = "\1\n" + "".join(mt) + "\1\n" + text
45 text = "\1\n" + "".join(mt) + "\1\n" + text
46 return self.addrevision(text, transaction, link, p1, p2)
46 return self.addrevision(text, transaction, link, p1, p2)
47
47
48 def annotate(self, node):
48 def annotate(self, node):
49
49
50 def decorate(text, rev):
50 def decorate(text, rev):
51 return ([rev] * len(text.splitlines()), text)
51 return ([rev] * len(text.splitlines()), text)
52
52
53 def pair(parent, child):
53 def pair(parent, child):
54 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
54 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
55 child[0][b1:b2] = parent[0][a1:a2]
55 child[0][b1:b2] = parent[0][a1:a2]
56 return child
56 return child
57
57
58 # find all ancestors
58 # find all ancestors
59 needed = {node:1}
59 needed = {node:1}
60 visit = [node]
60 visit = [node]
61 while visit:
61 while visit:
62 n = visit.pop(0)
62 n = visit.pop(0)
63 for p in self.parents(n):
63 for p in self.parents(n):
64 if p not in needed:
64 if p not in needed:
65 needed[p] = 1
65 needed[p] = 1
66 visit.append(p)
66 visit.append(p)
67 else:
67 else:
68 # count how many times we'll use this
68 # count how many times we'll use this
69 needed[p] += 1
69 needed[p] += 1
70
70
71 # sort by revision which is a topological order
71 # sort by revision which is a topological order
72 visit = [ (self.rev(n), n) for n in needed.keys() ]
72 visit = [ (self.rev(n), n) for n in needed.keys() ]
73 visit.sort()
73 visit.sort()
74 hist = {}
74 hist = {}
75
75
76 for r,n in visit:
76 for r,n in visit:
77 curr = decorate(self.read(n), self.linkrev(n))
77 curr = decorate(self.read(n), self.linkrev(n))
78 for p in self.parents(n):
78 for p in self.parents(n):
79 if p != nullid:
79 if p != nullid:
80 curr = pair(hist[p], curr)
80 curr = pair(hist[p], curr)
81 # trim the history of unneeded revs
81 # trim the history of unneeded revs
82 needed[p] -= 1
82 needed[p] -= 1
83 if not needed[p]:
83 if not needed[p]:
84 del hist[p]
84 del hist[p]
85 hist[n] = curr
85 hist[n] = curr
86
86
87 return zip(hist[n][0], hist[n][1].splitlines(1))
87 return zip(hist[n][0], hist[n][1].splitlines(1))
88
88
89 class manifest(revlog):
89 class manifest(revlog):
90 def __init__(self, opener):
90 def __init__(self, opener):
91 self.mapcache = None
91 self.mapcache = None
92 self.listcache = None
92 self.listcache = None
93 self.addlist = None
93 self.addlist = None
94 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
94 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
95
95
96 def read(self, node):
96 def read(self, node):
97 if node == nullid: return {} # don't upset local cache
97 if node == nullid: return {} # don't upset local cache
98 if self.mapcache and self.mapcache[0] == node:
98 if self.mapcache and self.mapcache[0] == node:
99 return self.mapcache[1]
99 return self.mapcache[1]
100 text = self.revision(node)
100 text = self.revision(node)
101 map = {}
101 map = {}
102 flag = {}
102 flag = {}
103 self.listcache = (text, text.splitlines(1))
103 self.listcache = (text, text.splitlines(1))
104 for l in self.listcache[1]:
104 for l in self.listcache[1]:
105 (f, n) = l.split('\0')
105 (f, n) = l.split('\0')
106 map[f] = bin(n[:40])
106 map[f] = bin(n[:40])
107 flag[f] = (n[40:-1] == "x")
107 flag[f] = (n[40:-1] == "x")
108 self.mapcache = (node, map, flag)
108 self.mapcache = (node, map, flag)
109 return map
109 return map
110
110
111 def readflags(self, node):
111 def readflags(self, node):
112 if node == nullid: return {} # don't upset local cache
112 if node == nullid: return {} # don't upset local cache
113 if not self.mapcache or self.mapcache[0] != node:
113 if not self.mapcache or self.mapcache[0] != node:
114 self.read(node)
114 self.read(node)
115 return self.mapcache[2]
115 return self.mapcache[2]
116
116
117 def diff(self, a, b):
117 def diff(self, a, b):
118 # this is sneaky, as we're not actually using a and b
118 # this is sneaky, as we're not actually using a and b
119 if self.listcache and self.addlist and self.listcache[0] == a:
119 if self.listcache and self.addlist and self.listcache[0] == a:
120 d = mdiff.diff(self.listcache[1], self.addlist, 1)
120 d = mdiff.diff(self.listcache[1], self.addlist, 1)
121 if mdiff.patch(a, d) != b:
121 if mdiff.patch(a, d) != b:
122 sys.stderr.write("*** sortdiff failed, falling back ***\n")
122 sys.stderr.write("*** sortdiff failed, falling back ***\n")
123 return mdiff.textdiff(a, b)
123 return mdiff.textdiff(a, b)
124 return d
124 return d
125 else:
125 else:
126 return mdiff.textdiff(a, b)
126 return mdiff.textdiff(a, b)
127
127
128 def add(self, map, flags, transaction, link, p1=None, p2=None,changed=None):
128 def add(self, map, flags, transaction, link, p1=None, p2=None,changed=None):
129 # directly generate the mdiff delta from the data collected during
129 # directly generate the mdiff delta from the data collected during
130 # the bisect loop below
130 # the bisect loop below
131 def gendelta(delta):
131 def gendelta(delta):
132 i = 0
132 i = 0
133 result = []
133 result = []
134 while i < len(delta):
134 while i < len(delta):
135 start = delta[i][2]
135 start = delta[i][2]
136 end = delta[i][3]
136 end = delta[i][3]
137 l = delta[i][4]
137 l = delta[i][4]
138 if l == None:
138 if l == None:
139 l = ""
139 l = ""
140 while i < len(delta) - 1 and start <= delta[i+1][2] and end >= delta[i+1][2]:
140 while i < len(delta) - 1 and start <= delta[i+1][2] and end >= delta[i+1][2]:
141 if delta[i+1][3] > end:
141 if delta[i+1][3] > end:
142 end = delta[i+1][3]
142 end = delta[i+1][3]
143 if delta[i+1][4]:
143 if delta[i+1][4]:
144 l += delta[i+1][4]
144 l += delta[i+1][4]
145 i += 1
145 i += 1
146 result.append(struct.pack(">lll", start, end, len(l)) + l)
146 result.append(struct.pack(">lll", start, end, len(l)) + l)
147 i += 1
147 i += 1
148 return result
148 return result
149
149
150 # apply the changes collected during the bisect loop to our addlist
150 # apply the changes collected during the bisect loop to our addlist
151 def addlistdelta(addlist, delta):
151 def addlistdelta(addlist, delta):
152 # apply the deltas to the addlist. start from the bottom up
152 # apply the deltas to the addlist. start from the bottom up
153 # so changes to the offsets don't mess things up.
153 # so changes to the offsets don't mess things up.
154 i = len(delta)
154 i = len(delta)
155 while i > 0:
155 while i > 0:
156 i -= 1
156 i -= 1
157 start = delta[i][0]
157 start = delta[i][0]
158 end = delta[i][1]
158 end = delta[i][1]
159 if delta[i][4]:
159 if delta[i][4]:
160 addlist[start:end] = [delta[i][4]]
160 addlist[start:end] = [delta[i][4]]
161 else:
161 else:
162 del addlist[start:end]
162 del addlist[start:end]
163 return addlist
163 return addlist
164
164
165 # calculate the byte offset of the start of each line in the
165 # calculate the byte offset of the start of each line in the
166 # manifest
166 # manifest
167 def calcoffsets(addlist):
167 def calcoffsets(addlist):
168 offsets = [0] * (len(addlist) + 1)
168 offsets = [0] * (len(addlist) + 1)
169 offset = 0
169 offset = 0
170 i = 0
170 i = 0
171 while i < len(addlist):
171 while i < len(addlist):
172 offsets[i] = offset
172 offsets[i] = offset
173 offset += len(addlist[i])
173 offset += len(addlist[i])
174 i += 1
174 i += 1
175 offsets[i] = offset
175 offsets[i] = offset
176 return offsets
176 return offsets
177
177
178 # if we're using the listcache, make sure it is valid and
178 # if we're using the listcache, make sure it is valid and
179 # parented by the same node we're diffing against
179 # parented by the same node we're diffing against
180 if not changed or not self.listcache or not p1 or self.mapcache[0] != p1:
180 if not changed or not self.listcache or not p1 or self.mapcache[0] != p1:
181 files = map.keys()
181 files = map.keys()
182 files.sort()
182 files.sort()
183
183
184 self.addlist = ["%s\000%s%s\n" %
184 self.addlist = ["%s\000%s%s\n" %
185 (f, hex(map[f]), flags[f] and "x" or '')
185 (f, hex(map[f]), flags[f] and "x" or '')
186 for f in files]
186 for f in files]
187 cachedelta = None
187 cachedelta = None
188 else:
188 else:
189 addlist = self.listcache[1]
189 addlist = self.listcache[1]
190
190
191 # find the starting offset for each line in the add list
191 # find the starting offset for each line in the add list
192 offsets = calcoffsets(addlist)
192 offsets = calcoffsets(addlist)
193
193
194 # combine the changed lists into one list for sorting
194 # combine the changed lists into one list for sorting
195 work = [[x, 0] for x in changed[0]]
195 work = [[x, 0] for x in changed[0]]
196 work[len(work):] = [[x, 1] for x in changed[1]]
196 work[len(work):] = [[x, 1] for x in changed[1]]
197 work.sort()
197 work.sort()
198
198
199 delta = []
199 delta = []
200 bs = 0
200 bs = 0
201
201
202 for w in work:
202 for w in work:
203 f = w[0]
203 f = w[0]
204 # bs will either be the index of the item or the insertion point
204 # bs will either be the index of the item or the insertion point
205 bs = bisect.bisect(addlist, f, bs)
205 bs = bisect.bisect(addlist, f, bs)
206 if bs < len(addlist):
206 if bs < len(addlist):
207 fn = addlist[bs][:addlist[bs].index('\0')]
207 fn = addlist[bs][:addlist[bs].index('\0')]
208 else:
208 else:
209 fn = None
209 fn = None
210 if w[1] == 0:
210 if w[1] == 0:
211 l = "%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '')
211 l = "%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '')
212 else:
212 else:
213 l = None
213 l = None
214 start = bs
214 start = bs
215 if fn != f:
215 if fn != f:
216 # item not found, insert a new one
216 # item not found, insert a new one
217 end = bs
217 end = bs
218 if w[1] == 1:
218 if w[1] == 1:
219 sys.stderr.write("failed to remove %s from manifest" % f)
219 sys.stderr.write("failed to remove %s from manifest" % f)
220 sys.exit(1)
220 sys.exit(1)
221 else:
221 else:
222 # item is found, replace/delete the existing line
222 # item is found, replace/delete the existing line
223 end = bs + 1
223 end = bs + 1
224 delta.append([start, end, offsets[start], offsets[end], l])
224 delta.append([start, end, offsets[start], offsets[end], l])
225
225
226 self.addlist = addlistdelta(addlist, delta)
226 self.addlist = addlistdelta(addlist, delta)
227 if self.mapcache[0] == self.tip():
227 if self.mapcache[0] == self.tip():
228 cachedelta = "".join(gendelta(delta))
228 cachedelta = "".join(gendelta(delta))
229 else:
229 else:
230 cachedelta = None
230 cachedelta = None
231
231
232 text = "".join(self.addlist)
232 text = "".join(self.addlist)
233 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
233 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
234 sys.stderr.write("manifest delta failure")
234 sys.stderr.write("manifest delta failure")
235 sys.exit(1)
235 sys.exit(1)
236 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
236 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
237 self.mapcache = (n, map, flags)
237 self.mapcache = (n, map, flags)
238 self.listcache = (text, self.addlist)
238 self.listcache = (text, self.addlist)
239 self.addlist = None
239 self.addlist = None
240
240
241 return n
241 return n
242
242
243 class changelog(revlog):
243 class changelog(revlog):
244 def __init__(self, opener):
244 def __init__(self, opener):
245 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
245 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
246
246
247 def extract(self, text):
247 def extract(self, text):
248 if not text:
248 if not text:
249 return (nullid, "", "0", [], "")
249 return (nullid, "", "0", [], "")
250 last = text.index("\n\n")
250 last = text.index("\n\n")
251 desc = text[last + 2:]
251 desc = text[last + 2:]
252 l = text[:last].splitlines()
252 l = text[:last].splitlines()
253 manifest = bin(l[0])
253 manifest = bin(l[0])
254 user = l[1]
254 user = l[1]
255 date = l[2]
255 date = l[2]
256 files = l[3:]
256 files = l[3:]
257 return (manifest, user, date, files, desc)
257 return (manifest, user, date, files, desc)
258
258
259 def read(self, node):
259 def read(self, node):
260 return self.extract(self.revision(node))
260 return self.extract(self.revision(node))
261
261
262 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
262 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
263 user=None, date=None):
263 user=None, date=None):
264 date = date or "%d %d" % (time.time(), time.timezone)
264 date = date or "%d %d" % (time.time(), time.timezone)
265 list.sort()
265 list.sort()
266 l = [hex(manifest), user, date] + list + ["", desc]
266 l = [hex(manifest), user, date] + list + ["", desc]
267 text = "\n".join(l)
267 text = "\n".join(l)
268 return self.addrevision(text, transaction, self.count(), p1, p2)
268 return self.addrevision(text, transaction, self.count(), p1, p2)
269
269
270 class dirstate:
270 class dirstate:
271 def __init__(self, opener, ui, root):
271 def __init__(self, opener, ui, root):
272 self.opener = opener
272 self.opener = opener
273 self.root = root
273 self.root = root
274 self.dirty = 0
274 self.dirty = 0
275 self.ui = ui
275 self.ui = ui
276 self.map = None
276 self.map = None
277 self.pl = None
277 self.pl = None
278 self.copies = {}
278 self.copies = {}
279
279
280 def __del__(self):
280 def __del__(self):
281 if self.dirty:
281 if self.dirty:
282 self.write()
282 self.write()
283
283
284 def __getitem__(self, key):
284 def __getitem__(self, key):
285 try:
285 try:
286 return self.map[key]
286 return self.map[key]
287 except TypeError:
287 except TypeError:
288 self.read()
288 self.read()
289 return self[key]
289 return self[key]
290
290
291 def __contains__(self, key):
291 def __contains__(self, key):
292 if not self.map: self.read()
292 if not self.map: self.read()
293 return key in self.map
293 return key in self.map
294
294
295 def parents(self):
295 def parents(self):
296 if not self.pl:
296 if not self.pl:
297 self.read()
297 self.read()
298 return self.pl
298 return self.pl
299
299
300 def setparents(self, p1, p2 = nullid):
300 def setparents(self, p1, p2 = nullid):
301 self.dirty = 1
301 self.dirty = 1
302 self.pl = p1, p2
302 self.pl = p1, p2
303
303
304 def state(self, key):
304 def state(self, key):
305 try:
305 try:
306 return self[key][0]
306 return self[key][0]
307 except KeyError:
307 except KeyError:
308 return "?"
308 return "?"
309
309
310 def read(self):
310 def read(self):
311 if self.map is not None: return self.map
311 if self.map is not None: return self.map
312
312
313 self.map = {}
313 self.map = {}
314 self.pl = [nullid, nullid]
314 self.pl = [nullid, nullid]
315 try:
315 try:
316 st = self.opener("dirstate").read()
316 st = self.opener("dirstate").read()
317 if not st: return
317 if not st: return
318 except: return
318 except: return
319
319
320 self.pl = [st[:20], st[20: 40]]
320 self.pl = [st[:20], st[20: 40]]
321
321
322 pos = 40
322 pos = 40
323 while pos < len(st):
323 while pos < len(st):
324 e = struct.unpack(">cllll", st[pos:pos+17])
324 e = struct.unpack(">cllll", st[pos:pos+17])
325 l = e[4]
325 l = e[4]
326 pos += 17
326 pos += 17
327 f = st[pos:pos + l]
327 f = st[pos:pos + l]
328 if '\0' in f:
328 if '\0' in f:
329 f, c = f.split('\0')
329 f, c = f.split('\0')
330 self.copies[f] = c
330 self.copies[f] = c
331 self.map[f] = e[:4]
331 self.map[f] = e[:4]
332 pos += l
332 pos += l
333
333
334 def copy(self, source, dest):
334 def copy(self, source, dest):
335 self.read()
335 self.read()
336 self.dirty = 1
336 self.dirty = 1
337 self.copies[dest] = source
337 self.copies[dest] = source
338
338
339 def copied(self, file):
339 def copied(self, file):
340 return self.copies.get(file, None)
340 return self.copies.get(file, None)
341
341
342 def update(self, files, state):
342 def update(self, files, state):
343 ''' current states:
343 ''' current states:
344 n normal
344 n normal
345 m needs merging
345 m needs merging
346 r marked for removal
346 r marked for removal
347 a marked for addition'''
347 a marked for addition'''
348
348
349 if not files: return
349 if not files: return
350 self.read()
350 self.read()
351 self.dirty = 1
351 self.dirty = 1
352 for f in files:
352 for f in files:
353 if state == "r":
353 if state == "r":
354 self.map[f] = ('r', 0, 0, 0)
354 self.map[f] = ('r', 0, 0, 0)
355 else:
355 else:
356 s = os.stat(os.path.join(self.root, f))
356 s = os.stat(os.path.join(self.root, f))
357 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
357 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
358
358
359 def forget(self, files):
359 def forget(self, files):
360 if not files: return
360 if not files: return
361 self.read()
361 self.read()
362 self.dirty = 1
362 self.dirty = 1
363 for f in files:
363 for f in files:
364 try:
364 try:
365 del self.map[f]
365 del self.map[f]
366 except KeyError:
366 except KeyError:
367 self.ui.warn("not in dirstate: %s!\n" % f)
367 self.ui.warn("not in dirstate: %s!\n" % f)
368 pass
368 pass
369
369
370 def clear(self):
370 def clear(self):
371 self.map = {}
371 self.map = {}
372 self.dirty = 1
372 self.dirty = 1
373
373
374 def write(self):
374 def write(self):
375 st = self.opener("dirstate", "w")
375 st = self.opener("dirstate", "w")
376 st.write("".join(self.pl))
376 st.write("".join(self.pl))
377 for f, e in self.map.items():
377 for f, e in self.map.items():
378 c = self.copied(f)
378 c = self.copied(f)
379 if c:
379 if c:
380 f = f + "\0" + c
380 f = f + "\0" + c
381 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
381 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
382 st.write(e + f)
382 st.write(e + f)
383 self.dirty = 0
383 self.dirty = 0
384
384
385 def changes(self, files, ignore):
385 def changes(self, files, ignore):
386 self.read()
386 self.read()
387 dc = self.map.copy()
387 dc = self.map.copy()
388 lookup, changed, added, unknown = [], [], [], []
388 lookup, changed, added, unknown = [], [], [], []
389
389
390 # compare all files by default
390 # compare all files by default
391 if not files: files = [self.root]
391 if not files: files = [self.root]
392
392
393 # recursive generator of all files listed
393 # recursive generator of all files listed
394 def walk(files):
394 def walk(files):
395 for f in util.unique(files):
395 for f in util.unique(files):
396 f = os.path.join(self.root, f)
396 f = os.path.join(self.root, f)
397 if os.path.isdir(f):
397 if os.path.isdir(f):
398 for dir, subdirs, fl in os.walk(f):
398 for dir, subdirs, fl in os.walk(f):
399 d = dir[len(self.root) + 1:]
399 d = dir[len(self.root) + 1:]
400 if ".hg" in subdirs: subdirs.remove(".hg")
400 if ".hg" in subdirs: subdirs.remove(".hg")
401 for fn in fl:
401 for fn in fl:
402 fn = util.pconvert(os.path.join(d, fn))
402 fn = util.pconvert(os.path.join(d, fn))
403 yield fn
403 yield fn
404 else:
404 else:
405 yield f[len(self.root) + 1:]
405 yield f[len(self.root) + 1:]
406
406
407 for fn in util.unique(walk(files)):
407 for fn in util.unique(walk(files)):
408 try: s = os.stat(os.path.join(self.root, fn))
408 try: s = os.stat(os.path.join(self.root, fn))
409 except: continue
409 except: continue
410
410
411 if fn in dc:
411 if fn in dc:
412 c = dc[fn]
412 c = dc[fn]
413 del dc[fn]
413 del dc[fn]
414
414
415 if c[0] == 'm':
415 if c[0] == 'm':
416 changed.append(fn)
416 changed.append(fn)
417 elif c[0] == 'a':
417 elif c[0] == 'a':
418 added.append(fn)
418 added.append(fn)
419 elif c[0] == 'r':
419 elif c[0] == 'r':
420 unknown.append(fn)
420 unknown.append(fn)
421 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
421 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
422 changed.append(fn)
422 changed.append(fn)
423 elif c[1] != s.st_mode or c[3] != s.st_mtime:
423 elif c[1] != s.st_mode or c[3] != s.st_mtime:
424 lookup.append(fn)
424 lookup.append(fn)
425 else:
425 else:
426 if not ignore(fn): unknown.append(fn)
426 if not ignore(fn): unknown.append(fn)
427
427
428 return (lookup, changed, added, dc.keys(), unknown)
428 return (lookup, changed, added, dc.keys(), unknown)
429
429
430 # used to avoid circular references so destructors work
430 # used to avoid circular references so destructors work
431 def opener(base):
431 def opener(base):
432 p = base
432 p = base
433 def o(path, mode="r"):
433 def o(path, mode="r"):
434 if p[:7] == "http://":
434 if p[:7] == "http://":
435 f = os.path.join(p, urllib.quote(path))
435 f = os.path.join(p, urllib.quote(path))
436 return httprangereader.httprangereader(f)
436 return httprangereader.httprangereader(f)
437
437
438 f = os.path.join(p, path)
438 f = os.path.join(p, path)
439
439
440 mode += "b" # for that other OS
440 mode += "b" # for that other OS
441
441
442 if mode[0] != "r":
442 if mode[0] != "r":
443 try:
443 try:
444 s = os.stat(f)
444 s = os.stat(f)
445 except OSError:
445 except OSError:
446 d = os.path.dirname(f)
446 d = os.path.dirname(f)
447 if not os.path.isdir(d):
447 if not os.path.isdir(d):
448 os.makedirs(d)
448 os.makedirs(d)
449 else:
449 else:
450 if s.st_nlink > 1:
450 if s.st_nlink > 1:
451 file(f + ".tmp", "wb").write(file(f, "rb").read())
451 file(f + ".tmp", "wb").write(file(f, "rb").read())
452 util.rename(f+".tmp", f)
452 util.rename(f+".tmp", f)
453
453
454 return file(f, mode)
454 return file(f, mode)
455
455
456 return o
456 return o
457
457
458 class RepoError(Exception): pass
458 class RepoError(Exception): pass
459
459
460 class localrepository:
460 class localrepository:
461 def __init__(self, ui, path=None, create=0):
461 def __init__(self, ui, path=None, create=0):
462 self.remote = 0
462 self.remote = 0
463 if path and path[:7] == "http://":
463 if path and path[:7] == "http://":
464 self.remote = 1
464 self.remote = 1
465 self.path = path
465 self.path = path
466 else:
466 else:
467 if not path:
467 if not path:
468 p = os.getcwd()
468 p = os.getcwd()
469 while not os.path.isdir(os.path.join(p, ".hg")):
469 while not os.path.isdir(os.path.join(p, ".hg")):
470 oldp = p
470 oldp = p
471 p = os.path.dirname(p)
471 p = os.path.dirname(p)
472 if p == oldp: raise RepoError("no repo found")
472 if p == oldp: raise RepoError("no repo found")
473 path = p
473 path = p
474 self.path = os.path.join(path, ".hg")
474 self.path = os.path.join(path, ".hg")
475
475
476 if not create and not os.path.isdir(self.path):
476 if not create and not os.path.isdir(self.path):
477 raise RepoError("repository %s not found" % self.path)
477 raise RepoError("repository %s not found" % self.path)
478
478
479 self.root = path
479 self.root = path
480 self.ui = ui
480 self.ui = ui
481
481
482 if create:
482 if create:
483 os.mkdir(self.path)
483 os.mkdir(self.path)
484 os.mkdir(self.join("data"))
484 os.mkdir(self.join("data"))
485
485
486 self.opener = opener(self.path)
486 self.opener = opener(self.path)
487 self.wopener = opener(self.root)
487 self.wopener = opener(self.root)
488 self.manifest = manifest(self.opener)
488 self.manifest = manifest(self.opener)
489 self.changelog = changelog(self.opener)
489 self.changelog = changelog(self.opener)
490 self.ignorefunc = None
490 self.ignorefunc = None
491 self.tagscache = None
491 self.tagscache = None
492 self.nodetagscache = None
492 self.nodetagscache = None
493
493
494 if not self.remote:
494 if not self.remote:
495 self.dirstate = dirstate(self.opener, ui, self.root)
495 self.dirstate = dirstate(self.opener, ui, self.root)
496 try:
496 try:
497 self.ui.readconfig(self.opener("hgrc"))
497 self.ui.readconfig(self.opener("hgrc"))
498 except IOError: pass
498 except IOError: pass
499
499
500 def ignore(self, f):
500 def ignore(self, f):
501 if not self.ignorefunc:
501 if not self.ignorefunc:
502 bigpat = []
502 bigpat = []
503 try:
503 try:
504 l = file(self.wjoin(".hgignore"))
504 l = file(self.wjoin(".hgignore"))
505 for pat in l:
505 for pat in l:
506 if pat != "\n":
506 if pat != "\n":
507 bigpat.append(util.pconvert(pat[:-1]))
507 bigpat.append(util.pconvert(pat[:-1]))
508 except IOError: pass
508 except IOError: pass
509 if bigpat:
509 if bigpat:
510 s = "(%s)" % (")|(".join(bigpat))
510 s = "(%s)" % (")|(".join(bigpat))
511 r = re.compile(s)
511 r = re.compile(s)
512 self.ignorefunc = r.search
512 self.ignorefunc = r.search
513 else:
513 else:
514 self.ignorefunc = lambda x: False
514 self.ignorefunc = lambda x: False
515
515
516 return self.ignorefunc(f)
516 return self.ignorefunc(f)
517
517
518 def hook(self, name, **args):
518 def hook(self, name, **args):
519 s = self.ui.config("hooks", name)
519 s = self.ui.config("hooks", name)
520 if s:
520 if s:
521 self.ui.note("running hook %s: %s\n" % (name, s))
521 self.ui.note("running hook %s: %s\n" % (name, s))
522 old = {}
522 old = {}
523 for k, v in args.items():
523 for k, v in args.items():
524 k = k.upper()
524 k = k.upper()
525 old[k] = os.environ.get(k, None)
525 old[k] = os.environ.get(k, None)
526 os.environ[k] = v
526 os.environ[k] = v
527
527
528 r = os.system(s)
528 r = os.system(s)
529
529
530 for k, v in old.items():
530 for k, v in old.items():
531 if v != None:
531 if v != None:
532 os.environ[k] = v
532 os.environ[k] = v
533 else:
533 else:
534 del os.environ[k]
534 del os.environ[k]
535
535
536 if r:
536 if r:
537 self.ui.warn("abort: %s hook failed with status %d!\n" %
537 self.ui.warn("abort: %s hook failed with status %d!\n" %
538 (name, r))
538 (name, r))
539 return False
539 return False
540 return True
540 return True
541
541
542 def tags(self):
542 def tags(self):
543 '''return a mapping of tag to node'''
543 '''return a mapping of tag to node'''
544 if not self.tagscache:
544 if not self.tagscache:
545 self.tagscache = {}
545 self.tagscache = {}
546 def addtag(self, k, n):
546 def addtag(self, k, n):
547 try:
547 try:
548 bin_n = bin(n)
548 bin_n = bin(n)
549 except TypeError:
549 except TypeError:
550 bin_n = ''
550 bin_n = ''
551 self.tagscache[k.strip()] = bin_n
551 self.tagscache[k.strip()] = bin_n
552
552
553 try:
553 try:
554 # read each head of the tags file, ending with the tip
554 # read each head of the tags file, ending with the tip
555 # and add each tag found to the map, with "newer" ones
555 # and add each tag found to the map, with "newer" ones
556 # taking precedence
556 # taking precedence
557 fl = self.file(".hgtags")
557 fl = self.file(".hgtags")
558 h = fl.heads()
558 h = fl.heads()
559 h.reverse()
559 h.reverse()
560 for r in h:
560 for r in h:
561 for l in fl.revision(r).splitlines():
561 for l in fl.revision(r).splitlines():
562 if l:
562 if l:
563 n, k = l.split(" ", 1)
563 n, k = l.split(" ", 1)
564 addtag(self, k, n)
564 addtag(self, k, n)
565 except KeyError:
565 except KeyError:
566 pass
566 pass
567
567
568 try:
568 try:
569 f = self.opener("localtags")
569 f = self.opener("localtags")
570 for l in f:
570 for l in f:
571 n, k = l.split(" ", 1)
571 n, k = l.split(" ", 1)
572 addtag(self, k, n)
572 addtag(self, k, n)
573 except IOError:
573 except IOError:
574 pass
574 pass
575
575
576 self.tagscache['tip'] = self.changelog.tip()
576 self.tagscache['tip'] = self.changelog.tip()
577
577
578 return self.tagscache
578 return self.tagscache
579
579
580 def tagslist(self):
580 def tagslist(self):
581 '''return a list of tags ordered by revision'''
581 '''return a list of tags ordered by revision'''
582 l = []
582 l = []
583 for t, n in self.tags().items():
583 for t, n in self.tags().items():
584 try:
584 try:
585 r = self.changelog.rev(n)
585 r = self.changelog.rev(n)
586 except:
586 except:
587 r = -2 # sort to the beginning of the list if unknown
587 r = -2 # sort to the beginning of the list if unknown
588 l.append((r,t,n))
588 l.append((r,t,n))
589 l.sort()
589 l.sort()
590 return [(t,n) for r,t,n in l]
590 return [(t,n) for r,t,n in l]
591
591
592 def nodetags(self, node):
592 def nodetags(self, node):
593 '''return the tags associated with a node'''
593 '''return the tags associated with a node'''
594 if not self.nodetagscache:
594 if not self.nodetagscache:
595 self.nodetagscache = {}
595 self.nodetagscache = {}
596 for t,n in self.tags().items():
596 for t,n in self.tags().items():
597 self.nodetagscache.setdefault(n,[]).append(t)
597 self.nodetagscache.setdefault(n,[]).append(t)
598 return self.nodetagscache.get(node, [])
598 return self.nodetagscache.get(node, [])
599
599
600 def lookup(self, key):
600 def lookup(self, key):
601 try:
601 try:
602 return self.tags()[key]
602 return self.tags()[key]
603 except KeyError:
603 except KeyError:
604 return self.changelog.lookup(key)
604 return self.changelog.lookup(key)
605
605
606 def dev(self):
606 def dev(self):
607 if self.remote: return -1
607 if self.remote: return -1
608 return os.stat(self.path).st_dev
608 return os.stat(self.path).st_dev
609
609
610 def join(self, f):
610 def join(self, f):
611 return os.path.join(self.path, f)
611 return os.path.join(self.path, f)
612
612
613 def wjoin(self, f):
613 def wjoin(self, f):
614 return os.path.join(self.root, f)
614 return os.path.join(self.root, f)
615
615
616 def file(self, f):
616 def file(self, f):
617 if f[0] == '/': f = f[1:]
617 if f[0] == '/': f = f[1:]
618 return filelog(self.opener, f)
618 return filelog(self.opener, f)
619
619
620 def getcwd(self):
620 def getcwd(self):
621 cwd = os.getcwd()
621 cwd = os.getcwd()
622 if cwd == self.root: return ''
622 if cwd == self.root: return ''
623 return cwd[len(self.root) + 1:]
623 return cwd[len(self.root) + 1:]
624
624
625 def wfile(self, f, mode='r'):
625 def wfile(self, f, mode='r'):
626 return self.wopener(f, mode)
626 return self.wopener(f, mode)
627
627
628 def transaction(self):
628 def transaction(self):
629 # save dirstate for undo
629 # save dirstate for undo
630 try:
630 try:
631 ds = self.opener("dirstate").read()
631 ds = self.opener("dirstate").read()
632 except IOError:
632 except IOError:
633 ds = ""
633 ds = ""
634 self.opener("undo.dirstate", "w").write(ds)
634 self.opener("undo.dirstate", "w").write(ds)
635
635
636 return transaction.transaction(self.ui.warn,
636 return transaction.transaction(self.ui.warn,
637 self.opener, self.join("journal"),
637 self.opener, self.join("journal"),
638 self.join("undo"))
638 self.join("undo"))
639
639
640 def recover(self):
640 def recover(self):
641 lock = self.lock()
641 lock = self.lock()
642 if os.path.exists(self.join("journal")):
642 if os.path.exists(self.join("journal")):
643 self.ui.status("rolling back interrupted transaction\n")
643 self.ui.status("rolling back interrupted transaction\n")
644 return transaction.rollback(self.opener, self.join("journal"))
644 return transaction.rollback(self.opener, self.join("journal"))
645 else:
645 else:
646 self.ui.warn("no interrupted transaction available\n")
646 self.ui.warn("no interrupted transaction available\n")
647
647
648 def undo(self):
648 def undo(self):
649 lock = self.lock()
649 lock = self.lock()
650 if os.path.exists(self.join("undo")):
650 if os.path.exists(self.join("undo")):
651 self.ui.status("rolling back last transaction\n")
651 self.ui.status("rolling back last transaction\n")
652 transaction.rollback(self.opener, self.join("undo"))
652 transaction.rollback(self.opener, self.join("undo"))
653 self.dirstate = None
653 self.dirstate = None
654 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
654 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
655 self.dirstate = dirstate(self.opener, self.ui, self.root)
655 self.dirstate = dirstate(self.opener, self.ui, self.root)
656 else:
656 else:
657 self.ui.warn("no undo information available\n")
657 self.ui.warn("no undo information available\n")
658
658
659 def lock(self, wait = 1):
659 def lock(self, wait = 1):
660 try:
660 try:
661 return lock.lock(self.join("lock"), 0)
661 return lock.lock(self.join("lock"), 0)
662 except lock.LockHeld, inst:
662 except lock.LockHeld, inst:
663 if wait:
663 if wait:
664 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
664 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
665 return lock.lock(self.join("lock"), wait)
665 return lock.lock(self.join("lock"), wait)
666 raise inst
666 raise inst
667
667
668 def rawcommit(self, files, text, user, date, p1=None, p2=None):
668 def rawcommit(self, files, text, user, date, p1=None, p2=None):
669 orig_parent = self.dirstate.parents()[0] or nullid
669 orig_parent = self.dirstate.parents()[0] or nullid
670 p1 = p1 or self.dirstate.parents()[0] or nullid
670 p1 = p1 or self.dirstate.parents()[0] or nullid
671 p2 = p2 or self.dirstate.parents()[1] or nullid
671 p2 = p2 or self.dirstate.parents()[1] or nullid
672 c1 = self.changelog.read(p1)
672 c1 = self.changelog.read(p1)
673 c2 = self.changelog.read(p2)
673 c2 = self.changelog.read(p2)
674 m1 = self.manifest.read(c1[0])
674 m1 = self.manifest.read(c1[0])
675 mf1 = self.manifest.readflags(c1[0])
675 mf1 = self.manifest.readflags(c1[0])
676 m2 = self.manifest.read(c2[0])
676 m2 = self.manifest.read(c2[0])
677
677
678 if orig_parent == p1:
678 if orig_parent == p1:
679 update_dirstate = 1
679 update_dirstate = 1
680 else:
680 else:
681 update_dirstate = 0
681 update_dirstate = 0
682
682
683 tr = self.transaction()
683 tr = self.transaction()
684 mm = m1.copy()
684 mm = m1.copy()
685 mfm = mf1.copy()
685 mfm = mf1.copy()
686 linkrev = self.changelog.count()
686 linkrev = self.changelog.count()
687 for f in files:
687 for f in files:
688 try:
688 try:
689 t = self.wfile(f).read()
689 t = self.wfile(f).read()
690 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
690 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
691 r = self.file(f)
691 r = self.file(f)
692 mfm[f] = tm
692 mfm[f] = tm
693 mm[f] = r.add(t, {}, tr, linkrev,
693 mm[f] = r.add(t, {}, tr, linkrev,
694 m1.get(f, nullid), m2.get(f, nullid))
694 m1.get(f, nullid), m2.get(f, nullid))
695 if update_dirstate:
695 if update_dirstate:
696 self.dirstate.update([f], "n")
696 self.dirstate.update([f], "n")
697 except IOError:
697 except IOError:
698 try:
698 try:
699 del mm[f]
699 del mm[f]
700 del mfm[f]
700 del mfm[f]
701 if update_dirstate:
701 if update_dirstate:
702 self.dirstate.forget([f])
702 self.dirstate.forget([f])
703 except:
703 except:
704 # deleted from p2?
704 # deleted from p2?
705 pass
705 pass
706
706
707 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
707 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
708 user = user or self.ui.username()
708 user = user or self.ui.username()
709 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
709 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
710 tr.close()
710 tr.close()
711 if update_dirstate:
711 if update_dirstate:
712 self.dirstate.setparents(n, nullid)
712 self.dirstate.setparents(n, nullid)
713
713
714 def commit(self, files = None, text = "", user = None, date = None):
714 def commit(self, files = None, text = "", user = None, date = None):
715 commit = []
715 commit = []
716 remove = []
716 remove = []
717 if files:
717 if files:
718 for f in files:
718 for f in files:
719 s = self.dirstate.state(f)
719 s = self.dirstate.state(f)
720 if s in 'nmai':
720 if s in 'nmai':
721 commit.append(f)
721 commit.append(f)
722 elif s == 'r':
722 elif s == 'r':
723 remove.append(f)
723 remove.append(f)
724 else:
724 else:
725 self.ui.warn("%s not tracked!\n" % f)
725 self.ui.warn("%s not tracked!\n" % f)
726 else:
726 else:
727 (c, a, d, u) = self.changes(None, None)
727 (c, a, d, u) = self.changes(None, None)
728 commit = c + a
728 commit = c + a
729 remove = d
729 remove = d
730
730
731 if not commit and not remove:
731 if not commit and not remove:
732 self.ui.status("nothing changed\n")
732 self.ui.status("nothing changed\n")
733 return
733 return
734
734
735 if not self.hook("precommit"):
735 if not self.hook("precommit"):
736 return 1
736 return 1
737
737
738 p1, p2 = self.dirstate.parents()
738 p1, p2 = self.dirstate.parents()
739 c1 = self.changelog.read(p1)
739 c1 = self.changelog.read(p1)
740 c2 = self.changelog.read(p2)
740 c2 = self.changelog.read(p2)
741 m1 = self.manifest.read(c1[0])
741 m1 = self.manifest.read(c1[0])
742 mf1 = self.manifest.readflags(c1[0])
742 mf1 = self.manifest.readflags(c1[0])
743 m2 = self.manifest.read(c2[0])
743 m2 = self.manifest.read(c2[0])
744 lock = self.lock()
744 lock = self.lock()
745 tr = self.transaction()
745 tr = self.transaction()
746
746
747 # check in files
747 # check in files
748 new = {}
748 new = {}
749 linkrev = self.changelog.count()
749 linkrev = self.changelog.count()
750 commit.sort()
750 commit.sort()
751 for f in commit:
751 for f in commit:
752 self.ui.note(f + "\n")
752 self.ui.note(f + "\n")
753 try:
753 try:
754 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
754 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
755 t = self.wfile(f).read()
755 t = self.wfile(f).read()
756 except IOError:
756 except IOError:
757 self.warn("trouble committing %s!\n" % f)
757 self.warn("trouble committing %s!\n" % f)
758 raise
758 raise
759
759
760 meta = {}
760 meta = {}
761 cp = self.dirstate.copied(f)
761 cp = self.dirstate.copied(f)
762 if cp:
762 if cp:
763 meta["copy"] = cp
763 meta["copy"] = cp
764 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
764 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
765 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
765 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
766
766
767 r = self.file(f)
767 r = self.file(f)
768 fp1 = m1.get(f, nullid)
768 fp1 = m1.get(f, nullid)
769 fp2 = m2.get(f, nullid)
769 fp2 = m2.get(f, nullid)
770 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
770 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
771
771
772 # update manifest
772 # update manifest
773 m1.update(new)
773 m1.update(new)
774 for f in remove:
774 for f in remove:
775 if f in m1:
775 if f in m1:
776 del m1[f]
776 del m1[f]
777 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], (new,remove))
777 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], (new,remove))
778
778
779 # add changeset
779 # add changeset
780 new = new.keys()
780 new = new.keys()
781 new.sort()
781 new.sort()
782
782
783 if not text:
783 if not text:
784 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
784 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
785 edittext += "".join(["HG: changed %s\n" % f for f in new])
785 edittext += "".join(["HG: changed %s\n" % f for f in new])
786 edittext += "".join(["HG: removed %s\n" % f for f in remove])
786 edittext += "".join(["HG: removed %s\n" % f for f in remove])
787 edittext = self.ui.edit(edittext)
787 edittext = self.ui.edit(edittext)
788 if not edittext.rstrip():
788 if not edittext.rstrip():
789 return 1
789 return 1
790 text = edittext
790 text = edittext
791
791
792 user = user or self.ui.username()
792 user = user or self.ui.username()
793 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
793 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
794
794
795 if not self.hook("commit", node=hex(n)):
795 if not self.hook("commit", node=hex(n)):
796 return 1
796 return 1
797
797
798 tr.close()
798 tr.close()
799
799
800 self.dirstate.setparents(n)
800 self.dirstate.setparents(n)
801 self.dirstate.update(new, "n")
801 self.dirstate.update(new, "n")
802 self.dirstate.forget(remove)
802 self.dirstate.forget(remove)
803
803
804 def changes(self, node1, node2, files=None):
804 def changes(self, node1, node2, files=None):
805 mf2, u = None, []
805 mf2, u = None, []
806
806
807 def fcmp(fn, mf):
807 def fcmp(fn, mf):
808 t1 = self.wfile(fn).read()
808 t1 = self.wfile(fn).read()
809 t2 = self.file(fn).revision(mf[fn])
809 t2 = self.file(fn).revision(mf[fn])
810 return cmp(t1, t2)
810 return cmp(t1, t2)
811
811
812 # are we comparing the working directory?
812 # are we comparing the working directory?
813 if not node2:
813 if not node2:
814 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
814 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
815
815
816 # are we comparing working dir against its parent?
816 # are we comparing working dir against its parent?
817 if not node1:
817 if not node1:
818 if l:
818 if l:
819 # do a full compare of any files that might have changed
819 # do a full compare of any files that might have changed
820 change = self.changelog.read(self.dirstate.parents()[0])
820 change = self.changelog.read(self.dirstate.parents()[0])
821 mf2 = self.manifest.read(change[0])
821 mf2 = self.manifest.read(change[0])
822 for f in l:
822 for f in l:
823 if fcmp(f, mf2):
823 if fcmp(f, mf2):
824 c.append(f)
824 c.append(f)
825
825
826 for l in c, a, d, u:
826 for l in c, a, d, u:
827 l.sort()
827 l.sort()
828
828
829 return (c, a, d, u)
829 return (c, a, d, u)
830
830
831 # are we comparing working dir against non-tip?
831 # are we comparing working dir against non-tip?
832 # generate a pseudo-manifest for the working dir
832 # generate a pseudo-manifest for the working dir
833 if not node2:
833 if not node2:
834 if not mf2:
834 if not mf2:
835 change = self.changelog.read(self.dirstate.parents()[0])
835 change = self.changelog.read(self.dirstate.parents()[0])
836 mf2 = self.manifest.read(change[0]).copy()
836 mf2 = self.manifest.read(change[0]).copy()
837 for f in a + c + l:
837 for f in a + c + l:
838 mf2[f] = ""
838 mf2[f] = ""
839 for f in d:
839 for f in d:
840 if f in mf2: del mf2[f]
840 if f in mf2: del mf2[f]
841 else:
841 else:
842 change = self.changelog.read(node2)
842 change = self.changelog.read(node2)
843 mf2 = self.manifest.read(change[0])
843 mf2 = self.manifest.read(change[0])
844
844
845 # flush lists from dirstate before comparing manifests
845 # flush lists from dirstate before comparing manifests
846 c, a = [], []
846 c, a = [], []
847
847
848 change = self.changelog.read(node1)
848 change = self.changelog.read(node1)
849 mf1 = self.manifest.read(change[0]).copy()
849 mf1 = self.manifest.read(change[0]).copy()
850
850
851 for fn in mf2:
851 for fn in mf2:
852 if mf1.has_key(fn):
852 if mf1.has_key(fn):
853 if mf1[fn] != mf2[fn]:
853 if mf1[fn] != mf2[fn]:
854 if mf2[fn] != "" or fcmp(fn, mf1):
854 if mf2[fn] != "" or fcmp(fn, mf1):
855 c.append(fn)
855 c.append(fn)
856 del mf1[fn]
856 del mf1[fn]
857 else:
857 else:
858 a.append(fn)
858 a.append(fn)
859
859
860 d = mf1.keys()
860 d = mf1.keys()
861
861
862 for l in c, a, d, u:
862 for l in c, a, d, u:
863 l.sort()
863 l.sort()
864
864
865 return (c, a, d, u)
865 return (c, a, d, u)
866
866
867 def add(self, list):
867 def add(self, list):
868 for f in list:
868 for f in list:
869 p = self.wjoin(f)
869 p = self.wjoin(f)
870 if not os.path.exists(p):
870 if not os.path.exists(p):
871 self.ui.warn("%s does not exist!\n" % f)
871 self.ui.warn("%s does not exist!\n" % f)
872 elif not os.path.isfile(p):
872 elif not os.path.isfile(p):
873 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
873 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
874 elif self.dirstate.state(f) == 'n':
874 elif self.dirstate.state(f) == 'n':
875 self.ui.warn("%s already tracked!\n" % f)
875 self.ui.warn("%s already tracked!\n" % f)
876 else:
876 else:
877 self.dirstate.update([f], "a")
877 self.dirstate.update([f], "a")
878
878
879 def forget(self, list):
879 def forget(self, list):
880 for f in list:
880 for f in list:
881 if self.dirstate.state(f) not in 'ai':
881 if self.dirstate.state(f) not in 'ai':
882 self.ui.warn("%s not added!\n" % f)
882 self.ui.warn("%s not added!\n" % f)
883 else:
883 else:
884 self.dirstate.forget([f])
884 self.dirstate.forget([f])
885
885
886 def remove(self, list):
886 def remove(self, list):
887 for f in list:
887 for f in list:
888 p = self.wjoin(f)
888 p = self.wjoin(f)
889 if os.path.exists(p):
889 if os.path.exists(p):
890 self.ui.warn("%s still exists!\n" % f)
890 self.ui.warn("%s still exists!\n" % f)
891 elif self.dirstate.state(f) == 'a':
891 elif self.dirstate.state(f) == 'a':
892 self.ui.warn("%s never committed!\n" % f)
892 self.ui.warn("%s never committed!\n" % f)
893 self.dirstate.forget(f)
893 self.dirstate.forget(f)
894 elif f not in self.dirstate:
894 elif f not in self.dirstate:
895 self.ui.warn("%s not tracked!\n" % f)
895 self.ui.warn("%s not tracked!\n" % f)
896 else:
896 else:
897 self.dirstate.update([f], "r")
897 self.dirstate.update([f], "r")
898
898
899 def copy(self, source, dest):
899 def copy(self, source, dest):
900 p = self.wjoin(dest)
900 p = self.wjoin(dest)
901 if not os.path.exists(dest):
901 if not os.path.exists(dest):
902 self.ui.warn("%s does not exist!\n" % dest)
902 self.ui.warn("%s does not exist!\n" % dest)
903 elif not os.path.isfile(dest):
903 elif not os.path.isfile(dest):
904 self.ui.warn("copy failed: %s is not a file\n" % dest)
904 self.ui.warn("copy failed: %s is not a file\n" % dest)
905 else:
905 else:
906 if self.dirstate.state(dest) == '?':
906 if self.dirstate.state(dest) == '?':
907 self.dirstate.update([dest], "a")
907 self.dirstate.update([dest], "a")
908 self.dirstate.copy(source, dest)
908 self.dirstate.copy(source, dest)
909
909
910 def heads(self):
910 def heads(self):
911 return self.changelog.heads()
911 return self.changelog.heads()
912
912
913 def branches(self, nodes):
913 def branches(self, nodes):
914 if not nodes: nodes = [self.changelog.tip()]
914 if not nodes: nodes = [self.changelog.tip()]
915 b = []
915 b = []
916 for n in nodes:
916 for n in nodes:
917 t = n
917 t = n
918 while n:
918 while n:
919 p = self.changelog.parents(n)
919 p = self.changelog.parents(n)
920 if p[1] != nullid or p[0] == nullid:
920 if p[1] != nullid or p[0] == nullid:
921 b.append((t, n, p[0], p[1]))
921 b.append((t, n, p[0], p[1]))
922 break
922 break
923 n = p[0]
923 n = p[0]
924 return b
924 return b
925
925
926 def between(self, pairs):
926 def between(self, pairs):
927 r = []
927 r = []
928
928
929 for top, bottom in pairs:
929 for top, bottom in pairs:
930 n, l, i = top, [], 0
930 n, l, i = top, [], 0
931 f = 1
931 f = 1
932
932
933 while n != bottom:
933 while n != bottom:
934 p = self.changelog.parents(n)[0]
934 p = self.changelog.parents(n)[0]
935 if i == f:
935 if i == f:
936 l.append(n)
936 l.append(n)
937 f = f * 2
937 f = f * 2
938 n = p
938 n = p
939 i += 1
939 i += 1
940
940
941 r.append(l)
941 r.append(l)
942
942
943 return r
943 return r
944
944
945 def newer(self, nodes):
945 def newer(self, nodes):
946 m = {}
946 m = {}
947 nl = []
947 nl = []
948 pm = {}
948 pm = {}
949 cl = self.changelog
949 cl = self.changelog
950 t = l = cl.count()
950 t = l = cl.count()
951
951
952 # find the lowest numbered node
952 # find the lowest numbered node
953 for n in nodes:
953 for n in nodes:
954 l = min(l, cl.rev(n))
954 l = min(l, cl.rev(n))
955 m[n] = 1
955 m[n] = 1
956
956
957 for i in xrange(l, t):
957 for i in xrange(l, t):
958 n = cl.node(i)
958 n = cl.node(i)
959 if n in m: # explicitly listed
959 if n in m: # explicitly listed
960 pm[n] = 1
960 pm[n] = 1
961 nl.append(n)
961 nl.append(n)
962 continue
962 continue
963 for p in cl.parents(n):
963 for p in cl.parents(n):
964 if p in pm: # parent listed
964 if p in pm: # parent listed
965 pm[n] = 1
965 pm[n] = 1
966 nl.append(n)
966 nl.append(n)
967 break
967 break
968
968
969 return nl
969 return nl
970
970
971 def findincoming(self, remote, base={}):
971 def findincoming(self, remote, base={}):
972 m = self.changelog.nodemap
972 m = self.changelog.nodemap
973 search = []
973 search = []
974 fetch = []
974 fetch = []
975 seen = {}
975 seen = {}
976 seenbranch = {}
976 seenbranch = {}
977
977
978 # assume we're closer to the tip than the root
978 # assume we're closer to the tip than the root
979 # and start by examining the heads
979 # and start by examining the heads
980 self.ui.status("searching for changes\n")
980 self.ui.status("searching for changes\n")
981 heads = remote.heads()
981 heads = remote.heads()
982 unknown = []
982 unknown = []
983 for h in heads:
983 for h in heads:
984 if h not in m:
984 if h not in m:
985 unknown.append(h)
985 unknown.append(h)
986 else:
986 else:
987 base[h] = 1
987 base[h] = 1
988
988
989 if not unknown:
989 if not unknown:
990 return None
990 return None
991
991
992 rep = {}
992 rep = {}
993 reqcnt = 0
993 reqcnt = 0
994
994
995 # search through remote branches
995 # search through remote branches
996 # a 'branch' here is a linear segment of history, with four parts:
996 # a 'branch' here is a linear segment of history, with four parts:
997 # head, root, first parent, second parent
997 # head, root, first parent, second parent
998 # (a branch always has two parents (or none) by definition)
998 # (a branch always has two parents (or none) by definition)
999 unknown = remote.branches(unknown)
999 unknown = remote.branches(unknown)
1000 while unknown:
1000 while unknown:
1001 r = []
1001 r = []
1002 while unknown:
1002 while unknown:
1003 n = unknown.pop(0)
1003 n = unknown.pop(0)
1004 if n[0] in seen:
1004 if n[0] in seen:
1005 continue
1005 continue
1006
1006
1007 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1007 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1008 if n[0] == nullid:
1008 if n[0] == nullid:
1009 break
1009 break
1010 if n in seenbranch:
1010 if n in seenbranch:
1011 self.ui.debug("branch already found\n")
1011 self.ui.debug("branch already found\n")
1012 continue
1012 continue
1013 if n[1] and n[1] in m: # do we know the base?
1013 if n[1] and n[1] in m: # do we know the base?
1014 self.ui.debug("found incomplete branch %s:%s\n"
1014 self.ui.debug("found incomplete branch %s:%s\n"
1015 % (short(n[0]), short(n[1])))
1015 % (short(n[0]), short(n[1])))
1016 search.append(n) # schedule branch range for scanning
1016 search.append(n) # schedule branch range for scanning
1017 seenbranch[n] = 1
1017 seenbranch[n] = 1
1018 else:
1018 else:
1019 if n[1] not in seen and n[1] not in fetch:
1019 if n[1] not in seen and n[1] not in fetch:
1020 if n[2] in m and n[3] in m:
1020 if n[2] in m and n[3] in m:
1021 self.ui.debug("found new changeset %s\n" %
1021 self.ui.debug("found new changeset %s\n" %
1022 short(n[1]))
1022 short(n[1]))
1023 fetch.append(n[1]) # earliest unknown
1023 fetch.append(n[1]) # earliest unknown
1024 base[n[2]] = 1 # latest known
1024 base[n[2]] = 1 # latest known
1025 continue
1025 continue
1026
1026
1027 for a in n[2:4]:
1027 for a in n[2:4]:
1028 if a not in rep:
1028 if a not in rep:
1029 r.append(a)
1029 r.append(a)
1030 rep[a] = 1
1030 rep[a] = 1
1031
1031
1032 seen[n[0]] = 1
1032 seen[n[0]] = 1
1033
1033
1034 if r:
1034 if r:
1035 reqcnt += 1
1035 reqcnt += 1
1036 self.ui.debug("request %d: %s\n" %
1036 self.ui.debug("request %d: %s\n" %
1037 (reqcnt, " ".join(map(short, r))))
1037 (reqcnt, " ".join(map(short, r))))
1038 for p in range(0, len(r), 10):
1038 for p in range(0, len(r), 10):
1039 for b in remote.branches(r[p:p+10]):
1039 for b in remote.branches(r[p:p+10]):
1040 self.ui.debug("received %s:%s\n" %
1040 self.ui.debug("received %s:%s\n" %
1041 (short(b[0]), short(b[1])))
1041 (short(b[0]), short(b[1])))
1042 if b[0] not in m and b[0] not in seen:
1042 if b[0] not in m and b[0] not in seen:
1043 unknown.append(b)
1043 unknown.append(b)
1044
1044
1045 # do binary search on the branches we found
1045 # do binary search on the branches we found
1046 while search:
1046 while search:
1047 n = search.pop(0)
1047 n = search.pop(0)
1048 reqcnt += 1
1048 reqcnt += 1
1049 l = remote.between([(n[0], n[1])])[0]
1049 l = remote.between([(n[0], n[1])])[0]
1050 l.append(n[1])
1050 l.append(n[1])
1051 p = n[0]
1051 p = n[0]
1052 f = 1
1052 f = 1
1053 for i in l:
1053 for i in l:
1054 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1054 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1055 if i in m:
1055 if i in m:
1056 if f <= 2:
1056 if f <= 2:
1057 self.ui.debug("found new branch changeset %s\n" %
1057 self.ui.debug("found new branch changeset %s\n" %
1058 short(p))
1058 short(p))
1059 fetch.append(p)
1059 fetch.append(p)
1060 base[i] = 1
1060 base[i] = 1
1061 else:
1061 else:
1062 self.ui.debug("narrowed branch search to %s:%s\n"
1062 self.ui.debug("narrowed branch search to %s:%s\n"
1063 % (short(p), short(i)))
1063 % (short(p), short(i)))
1064 search.append((p, i))
1064 search.append((p, i))
1065 break
1065 break
1066 p, f = i, f * 2
1066 p, f = i, f * 2
1067
1067
1068 # sanity check our fetch list
1068 # sanity check our fetch list
1069 for f in fetch:
1069 for f in fetch:
1070 if f in m:
1070 if f in m:
1071 raise RepoError("already have changeset " + short(f[:4]))
1071 raise RepoError("already have changeset " + short(f[:4]))
1072
1072
1073 if base.keys() == [nullid]:
1073 if base.keys() == [nullid]:
1074 self.ui.warn("warning: pulling from an unrelated repository!\n")
1074 self.ui.warn("warning: pulling from an unrelated repository!\n")
1075
1075
1076 self.ui.note("adding new changesets starting at " +
1076 self.ui.note("adding new changesets starting at " +
1077 " ".join([short(f) for f in fetch]) + "\n")
1077 " ".join([short(f) for f in fetch]) + "\n")
1078
1078
1079 self.ui.debug("%d total queries\n" % reqcnt)
1079 self.ui.debug("%d total queries\n" % reqcnt)
1080
1080
1081 return fetch
1081 return fetch
1082
1082
1083 def findoutgoing(self, remote):
1083 def findoutgoing(self, remote):
1084 base = {}
1084 base = {}
1085 self.findincoming(remote, base)
1085 self.findincoming(remote, base)
1086 remain = dict.fromkeys(self.changelog.nodemap)
1086 remain = dict.fromkeys(self.changelog.nodemap)
1087
1087
1088 # prune everything remote has from the tree
1088 # prune everything remote has from the tree
1089 del remain[nullid]
1089 del remain[nullid]
1090 remove = base.keys()
1090 remove = base.keys()
1091 while remove:
1091 while remove:
1092 n = remove.pop(0)
1092 n = remove.pop(0)
1093 if n in remain:
1093 if n in remain:
1094 del remain[n]
1094 del remain[n]
1095 for p in self.changelog.parents(n):
1095 for p in self.changelog.parents(n):
1096 remove.append(p)
1096 remove.append(p)
1097
1097
1098 # find every node whose parents have been pruned
1098 # find every node whose parents have been pruned
1099 subset = []
1099 subset = []
1100 for n in remain:
1100 for n in remain:
1101 p1, p2 = self.changelog.parents(n)
1101 p1, p2 = self.changelog.parents(n)
1102 if p1 not in remain and p2 not in remain:
1102 if p1 not in remain and p2 not in remain:
1103 subset.append(n)
1103 subset.append(n)
1104
1104
1105 # this is the set of all roots we have to push
1105 # this is the set of all roots we have to push
1106 return subset
1106 return subset
1107
1107
1108 def pull(self, remote):
1108 def pull(self, remote):
1109 lock = self.lock()
1109 lock = self.lock()
1110
1110
1111 # if we have an empty repo, fetch everything
1111 # if we have an empty repo, fetch everything
1112 if self.changelog.tip() == nullid:
1112 if self.changelog.tip() == nullid:
1113 self.ui.status("requesting all changes\n")
1113 self.ui.status("requesting all changes\n")
1114 fetch = [nullid]
1114 fetch = [nullid]
1115 else:
1115 else:
1116 fetch = self.findincoming(remote)
1116 fetch = self.findincoming(remote)
1117
1117
1118 if not fetch:
1118 if not fetch:
1119 self.ui.status("no changes found\n")
1119 self.ui.status("no changes found\n")
1120 return 1
1120 return 1
1121
1121
1122 cg = remote.changegroup(fetch)
1122 cg = remote.changegroup(fetch)
1123 return self.addchangegroup(cg)
1123 return self.addchangegroup(cg)
1124
1124
1125 def push(self, remote):
1125 def push(self, remote):
1126 lock = remote.lock()
1126 lock = remote.lock()
1127 update = self.findoutgoing(remote)
1127 update = self.findoutgoing(remote)
1128 if not update:
1128 if not update:
1129 self.ui.status("no changes found\n")
1129 self.ui.status("no changes found\n")
1130 return 1
1130 return 1
1131
1131
1132 cg = self.changegroup(update)
1132 cg = self.changegroup(update)
1133 return remote.addchangegroup(cg)
1133 return remote.addchangegroup(cg)
1134
1134
1135 def changegroup(self, basenodes):
1135 def changegroup(self, basenodes):
1136 class genread:
1136 class genread:
1137 def __init__(self, generator):
1137 def __init__(self, generator):
1138 self.g = generator
1138 self.g = generator
1139 self.buf = ""
1139 self.buf = ""
1140 def read(self, l):
1140 def read(self, l):
1141 while l > len(self.buf):
1141 while l > len(self.buf):
1142 try:
1142 try:
1143 self.buf += self.g.next()
1143 self.buf += self.g.next()
1144 except StopIteration:
1144 except StopIteration:
1145 break
1145 break
1146 d, self.buf = self.buf[:l], self.buf[l:]
1146 d, self.buf = self.buf[:l], self.buf[l:]
1147 return d
1147 return d
1148
1148
1149 def gengroup():
1149 def gengroup():
1150 nodes = self.newer(basenodes)
1150 nodes = self.newer(basenodes)
1151
1151
1152 # construct the link map
1152 # construct the link map
1153 linkmap = {}
1153 linkmap = {}
1154 for n in nodes:
1154 for n in nodes:
1155 linkmap[self.changelog.rev(n)] = n
1155 linkmap[self.changelog.rev(n)] = n
1156
1156
1157 # construct a list of all changed files
1157 # construct a list of all changed files
1158 changed = {}
1158 changed = {}
1159 for n in nodes:
1159 for n in nodes:
1160 c = self.changelog.read(n)
1160 c = self.changelog.read(n)
1161 for f in c[3]:
1161 for f in c[3]:
1162 changed[f] = 1
1162 changed[f] = 1
1163 changed = changed.keys()
1163 changed = changed.keys()
1164 changed.sort()
1164 changed.sort()
1165
1165
1166 # the changegroup is changesets + manifests + all file revs
1166 # the changegroup is changesets + manifests + all file revs
1167 revs = [ self.changelog.rev(n) for n in nodes ]
1167 revs = [ self.changelog.rev(n) for n in nodes ]
1168
1168
1169 for y in self.changelog.group(linkmap): yield y
1169 for y in self.changelog.group(linkmap): yield y
1170 for y in self.manifest.group(linkmap): yield y
1170 for y in self.manifest.group(linkmap): yield y
1171 for f in changed:
1171 for f in changed:
1172 yield struct.pack(">l", len(f) + 4) + f
1172 yield struct.pack(">l", len(f) + 4) + f
1173 g = self.file(f).group(linkmap)
1173 g = self.file(f).group(linkmap)
1174 for y in g:
1174 for y in g:
1175 yield y
1175 yield y
1176
1176
1177 yield struct.pack(">l", 0)
1177 yield struct.pack(">l", 0)
1178
1178
1179 return genread(gengroup())
1179 return genread(gengroup())
1180
1180
1181 def addchangegroup(self, source):
1181 def addchangegroup(self, source):
1182
1182
1183 def getchunk():
1183 def getchunk():
1184 d = source.read(4)
1184 d = source.read(4)
1185 if not d: return ""
1185 if not d: return ""
1186 l = struct.unpack(">l", d)[0]
1186 l = struct.unpack(">l", d)[0]
1187 if l <= 4: return ""
1187 if l <= 4: return ""
1188 return source.read(l - 4)
1188 return source.read(l - 4)
1189
1189
1190 def getgroup():
1190 def getgroup():
1191 while 1:
1191 while 1:
1192 c = getchunk()
1192 c = getchunk()
1193 if not c: break
1193 if not c: break
1194 yield c
1194 yield c
1195
1195
1196 def csmap(x):
1196 def csmap(x):
1197 self.ui.debug("add changeset %s\n" % short(x))
1197 self.ui.debug("add changeset %s\n" % short(x))
1198 return self.changelog.count()
1198 return self.changelog.count()
1199
1199
1200 def revmap(x):
1200 def revmap(x):
1201 return self.changelog.rev(x)
1201 return self.changelog.rev(x)
1202
1202
1203 if not source: return
1203 if not source: return
1204 changesets = files = revisions = 0
1204 changesets = files = revisions = 0
1205
1205
1206 tr = self.transaction()
1206 tr = self.transaction()
1207
1207
1208 # pull off the changeset group
1208 # pull off the changeset group
1209 self.ui.status("adding changesets\n")
1209 self.ui.status("adding changesets\n")
1210 co = self.changelog.tip()
1210 co = self.changelog.tip()
1211 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1211 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1212 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1212 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1213
1213
1214 # pull off the manifest group
1214 # pull off the manifest group
1215 self.ui.status("adding manifests\n")
1215 self.ui.status("adding manifests\n")
1216 mm = self.manifest.tip()
1216 mm = self.manifest.tip()
1217 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1217 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1218
1218
1219 # process the files
1219 # process the files
1220 self.ui.status("adding file revisions\n")
1220 self.ui.status("adding file revisions\n")
1221 while 1:
1221 while 1:
1222 f = getchunk()
1222 f = getchunk()
1223 if not f: break
1223 if not f: break
1224 self.ui.debug("adding %s revisions\n" % f)
1224 self.ui.debug("adding %s revisions\n" % f)
1225 fl = self.file(f)
1225 fl = self.file(f)
1226 o = fl.count()
1226 o = fl.count()
1227 n = fl.addgroup(getgroup(), revmap, tr)
1227 n = fl.addgroup(getgroup(), revmap, tr)
1228 revisions += fl.count() - o
1228 revisions += fl.count() - o
1229 files += 1
1229 files += 1
1230
1230
1231 self.ui.status(("modified %d files, added %d changesets" +
1231 self.ui.status(("modified %d files, added %d changesets" +
1232 " and %d new revisions\n")
1232 " and %d new revisions\n")
1233 % (files, changesets, revisions))
1233 % (files, changesets, revisions))
1234
1234
1235 tr.close()
1235 tr.close()
1236 return
1236 return
1237
1237
1238 def update(self, node, allow=False, force=False, choose=None,
1238 def update(self, node, allow=False, force=False, choose=None,
1239 moddirstate=True):
1239 moddirstate=True):
1240 pl = self.dirstate.parents()
1240 pl = self.dirstate.parents()
1241 if not force and pl[1] != nullid:
1241 if not force and pl[1] != nullid:
1242 self.ui.warn("aborting: outstanding uncommitted merges\n")
1242 self.ui.warn("aborting: outstanding uncommitted merges\n")
1243 return
1243 return
1244
1244
1245 p1, p2 = pl[0], node
1245 p1, p2 = pl[0], node
1246 pa = self.changelog.ancestor(p1, p2)
1246 pa = self.changelog.ancestor(p1, p2)
1247 m1n = self.changelog.read(p1)[0]
1247 m1n = self.changelog.read(p1)[0]
1248 m2n = self.changelog.read(p2)[0]
1248 m2n = self.changelog.read(p2)[0]
1249 man = self.manifest.ancestor(m1n, m2n)
1249 man = self.manifest.ancestor(m1n, m2n)
1250 m1 = self.manifest.read(m1n)
1250 m1 = self.manifest.read(m1n)
1251 mf1 = self.manifest.readflags(m1n)
1251 mf1 = self.manifest.readflags(m1n)
1252 m2 = self.manifest.read(m2n)
1252 m2 = self.manifest.read(m2n)
1253 mf2 = self.manifest.readflags(m2n)
1253 mf2 = self.manifest.readflags(m2n)
1254 ma = self.manifest.read(man)
1254 ma = self.manifest.read(man)
1255 mfa = self.manifest.readflags(man)
1255 mfa = self.manifest.readflags(man)
1256
1256
1257 (c, a, d, u) = self.changes(None, None)
1257 (c, a, d, u) = self.changes(None, None)
1258
1258
1259 # is this a jump, or a merge? i.e. is there a linear path
1259 # is this a jump, or a merge? i.e. is there a linear path
1260 # from p1 to p2?
1260 # from p1 to p2?
1261 linear_path = (pa == p1 or pa == p2)
1261 linear_path = (pa == p1 or pa == p2)
1262
1262
1263 # resolve the manifest to determine which files
1263 # resolve the manifest to determine which files
1264 # we care about merging
1264 # we care about merging
1265 self.ui.note("resolving manifests\n")
1265 self.ui.note("resolving manifests\n")
1266 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1267 (force, allow, moddirstate, linear_path))
1266 self.ui.debug(" ancestor %s local %s remote %s\n" %
1268 self.ui.debug(" ancestor %s local %s remote %s\n" %
1267 (short(man), short(m1n), short(m2n)))
1269 (short(man), short(m1n), short(m2n)))
1268
1270
1269 merge = {}
1271 merge = {}
1270 get = {}
1272 get = {}
1271 remove = []
1273 remove = []
1272 mark = {}
1274 mark = {}
1273
1275
1274 # construct a working dir manifest
1276 # construct a working dir manifest
1275 mw = m1.copy()
1277 mw = m1.copy()
1276 mfw = mf1.copy()
1278 mfw = mf1.copy()
1277 umap = dict.fromkeys(u)
1279 umap = dict.fromkeys(u)
1278
1280
1279 for f in a + c + u:
1281 for f in a + c + u:
1280 mw[f] = ""
1282 mw[f] = ""
1281 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1283 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1282
1284
1283 for f in d:
1285 for f in d:
1284 if f in mw: del mw[f]
1286 if f in mw: del mw[f]
1285
1287
1286 # If we're jumping between revisions (as opposed to merging),
1288 # If we're jumping between revisions (as opposed to merging),
1287 # and if neither the working directory nor the target rev has
1289 # and if neither the working directory nor the target rev has
1288 # the file, then we need to remove it from the dirstate, to
1290 # the file, then we need to remove it from the dirstate, to
1289 # prevent the dirstate from listing the file when it is no
1291 # prevent the dirstate from listing the file when it is no
1290 # longer in the manifest.
1292 # longer in the manifest.
1291 if moddirstate and linear_path and f not in m2:
1293 if moddirstate and linear_path and f not in m2:
1292 self.dirstate.forget((f,))
1294 self.dirstate.forget((f,))
1293
1295
1294 # Compare manifests
1296 # Compare manifests
1295 for f, n in mw.iteritems():
1297 for f, n in mw.iteritems():
1296 if choose and not choose(f): continue
1298 if choose and not choose(f): continue
1297 if f in m2:
1299 if f in m2:
1298 s = 0
1300 s = 0
1299
1301
1300 # is the wfile new since m1, and match m2?
1302 # is the wfile new since m1, and match m2?
1301 if f not in m1:
1303 if f not in m1:
1302 t1 = self.wfile(f).read()
1304 t1 = self.wfile(f).read()
1303 t2 = self.file(f).revision(m2[f])
1305 t2 = self.file(f).revision(m2[f])
1304 if cmp(t1, t2) == 0:
1306 if cmp(t1, t2) == 0:
1305 mark[f] = 1
1307 mark[f] = 1
1306 n = m2[f]
1308 n = m2[f]
1307 del t1, t2
1309 del t1, t2
1308
1310
1309 # are files different?
1311 # are files different?
1310 if n != m2[f]:
1312 if n != m2[f]:
1311 a = ma.get(f, nullid)
1313 a = ma.get(f, nullid)
1312 # are both different from the ancestor?
1314 # are both different from the ancestor?
1313 if n != a and m2[f] != a:
1315 if n != a and m2[f] != a:
1314 self.ui.debug(" %s versions differ, resolve\n" % f)
1316 self.ui.debug(" %s versions differ, resolve\n" % f)
1315 # merge executable bits
1317 # merge executable bits
1316 # "if we changed or they changed, change in merge"
1318 # "if we changed or they changed, change in merge"
1317 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1319 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1318 mode = ((a^b) | (a^c)) ^ a
1320 mode = ((a^b) | (a^c)) ^ a
1319 merge[f] = (m1.get(f, nullid), m2[f], mode)
1321 merge[f] = (m1.get(f, nullid), m2[f], mode)
1320 s = 1
1322 s = 1
1321 # are we clobbering?
1323 # are we clobbering?
1322 # is remote's version newer?
1324 # is remote's version newer?
1323 # or are we going back in time?
1325 # or are we going back in time?
1324 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1326 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1325 self.ui.debug(" remote %s is newer, get\n" % f)
1327 self.ui.debug(" remote %s is newer, get\n" % f)
1326 get[f] = m2[f]
1328 get[f] = m2[f]
1327 s = 1
1329 s = 1
1328 else:
1330 else:
1329 mark[f] = 1
1331 mark[f] = 1
1330 elif f in umap:
1332 elif f in umap:
1331 # this unknown file is the same as the checkout
1333 # this unknown file is the same as the checkout
1332 get[f] = m2[f]
1334 get[f] = m2[f]
1333
1335
1334 if not s and mfw[f] != mf2[f]:
1336 if not s and mfw[f] != mf2[f]:
1335 if force:
1337 if force:
1336 self.ui.debug(" updating permissions for %s\n" % f)
1338 self.ui.debug(" updating permissions for %s\n" % f)
1337 util.set_exec(self.wjoin(f), mf2[f])
1339 util.set_exec(self.wjoin(f), mf2[f])
1338 else:
1340 else:
1339 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1341 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1340 mode = ((a^b) | (a^c)) ^ a
1342 mode = ((a^b) | (a^c)) ^ a
1341 if mode != b:
1343 if mode != b:
1342 self.ui.debug(" updating permissions for %s\n" % f)
1344 self.ui.debug(" updating permissions for %s\n" % f)
1343 util.set_exec(self.wjoin(f), mode)
1345 util.set_exec(self.wjoin(f), mode)
1344 mark[f] = 1
1346 mark[f] = 1
1345 del m2[f]
1347 del m2[f]
1346 elif f in ma:
1348 elif f in ma:
1347 if n != ma[f]:
1349 if n != ma[f]:
1348 r = "d"
1350 r = "d"
1349 if not force and (linear_path or allow):
1351 if not force and (linear_path or allow):
1350 r = self.ui.prompt(
1352 r = self.ui.prompt(
1351 (" local changed %s which remote deleted\n" % f) +
1353 (" local changed %s which remote deleted\n" % f) +
1352 "(k)eep or (d)elete?", "[kd]", "k")
1354 "(k)eep or (d)elete?", "[kd]", "k")
1353 if r == "d":
1355 if r == "d":
1354 remove.append(f)
1356 remove.append(f)
1355 else:
1357 else:
1356 self.ui.debug("other deleted %s\n" % f)
1358 self.ui.debug("other deleted %s\n" % f)
1357 remove.append(f) # other deleted it
1359 remove.append(f) # other deleted it
1358 else:
1360 else:
1359 if n == m1.get(f, nullid): # same as parent
1361 if n == m1.get(f, nullid): # same as parent
1360 if p2 == pa: # going backwards?
1362 if p2 == pa: # going backwards?
1361 self.ui.debug("remote deleted %s\n" % f)
1363 self.ui.debug("remote deleted %s\n" % f)
1362 remove.append(f)
1364 remove.append(f)
1363 else:
1365 else:
1364 self.ui.debug("local created %s, keeping\n" % f)
1366 self.ui.debug("local created %s, keeping\n" % f)
1365 else:
1367 else:
1366 self.ui.debug("working dir created %s, keeping\n" % f)
1368 self.ui.debug("working dir created %s, keeping\n" % f)
1367
1369
1368 for f, n in m2.iteritems():
1370 for f, n in m2.iteritems():
1369 if choose and not choose(f): continue
1371 if choose and not choose(f): continue
1370 if f[0] == "/": continue
1372 if f[0] == "/": continue
1371 if f in ma and n != ma[f]:
1373 if f in ma and n != ma[f]:
1372 r = "k"
1374 r = "k"
1373 if not force and (linear_path or allow):
1375 if not force and (linear_path or allow):
1374 r = self.ui.prompt(
1376 r = self.ui.prompt(
1375 ("remote changed %s which local deleted\n" % f) +
1377 ("remote changed %s which local deleted\n" % f) +
1376 "(k)eep or (d)elete?", "[kd]", "k")
1378 "(k)eep or (d)elete?", "[kd]", "k")
1377 if r == "k": get[f] = n
1379 if r == "k": get[f] = n
1378 elif f not in ma:
1380 elif f not in ma:
1379 self.ui.debug("remote created %s\n" % f)
1381 self.ui.debug("remote created %s\n" % f)
1380 get[f] = n
1382 get[f] = n
1381 else:
1383 else:
1382 self.ui.debug("local deleted %s\n" % f)
1384 self.ui.debug("local deleted %s\n" % f)
1385 if force:
1386 get[f] = n
1383
1387
1384 del mw, m1, m2, ma
1388 del mw, m1, m2, ma
1385
1389
1386 if force:
1390 if force:
1387 for f in merge:
1391 for f in merge:
1388 get[f] = merge[f][1]
1392 get[f] = merge[f][1]
1389 merge = {}
1393 merge = {}
1390
1394
1391 if linear_path:
1395 if linear_path:
1392 # we don't need to do any magic, just jump to the new rev
1396 # we don't need to do any magic, just jump to the new rev
1393 mode = 'n'
1397 mode = 'n'
1394 p1, p2 = p2, nullid
1398 p1, p2 = p2, nullid
1395 else:
1399 else:
1396 if not allow:
1400 if not allow:
1397 self.ui.status("this update spans a branch" +
1401 self.ui.status("this update spans a branch" +
1398 " affecting the following files:\n")
1402 " affecting the following files:\n")
1399 fl = merge.keys() + get.keys()
1403 fl = merge.keys() + get.keys()
1400 fl.sort()
1404 fl.sort()
1401 for f in fl:
1405 for f in fl:
1402 cf = ""
1406 cf = ""
1403 if f in merge: cf = " (resolve)"
1407 if f in merge: cf = " (resolve)"
1404 self.ui.status(" %s%s\n" % (f, cf))
1408 self.ui.status(" %s%s\n" % (f, cf))
1405 self.ui.warn("aborting update spanning branches!\n")
1409 self.ui.warn("aborting update spanning branches!\n")
1406 self.ui.status("(use update -m to perform a branch merge)\n")
1410 self.ui.status("(use update -m to perform a branch merge)\n")
1407 return 1
1411 return 1
1408 # we have to remember what files we needed to get/change
1412 # we have to remember what files we needed to get/change
1409 # because any file that's different from either one of its
1413 # because any file that's different from either one of its
1410 # parents must be in the changeset
1414 # parents must be in the changeset
1411 mode = 'm'
1415 mode = 'm'
1412 if moddirstate:
1416 if moddirstate:
1413 self.dirstate.update(mark.keys(), "m")
1417 self.dirstate.update(mark.keys(), "m")
1414
1418
1415 if moddirstate:
1419 if moddirstate:
1416 self.dirstate.setparents(p1, p2)
1420 self.dirstate.setparents(p1, p2)
1417
1421
1418 # get the files we don't need to change
1422 # get the files we don't need to change
1419 files = get.keys()
1423 files = get.keys()
1420 files.sort()
1424 files.sort()
1421 for f in files:
1425 for f in files:
1422 if f[0] == "/": continue
1426 if f[0] == "/": continue
1423 self.ui.note("getting %s\n" % f)
1427 self.ui.note("getting %s\n" % f)
1424 t = self.file(f).read(get[f])
1428 t = self.file(f).read(get[f])
1425 try:
1429 try:
1426 self.wfile(f, "w").write(t)
1430 self.wfile(f, "w").write(t)
1427 except IOError:
1431 except IOError:
1428 os.makedirs(os.path.dirname(self.wjoin(f)))
1432 os.makedirs(os.path.dirname(self.wjoin(f)))
1429 self.wfile(f, "w").write(t)
1433 self.wfile(f, "w").write(t)
1430 util.set_exec(self.wjoin(f), mf2[f])
1434 util.set_exec(self.wjoin(f), mf2[f])
1431 if moddirstate:
1435 if moddirstate:
1432 self.dirstate.update([f], mode)
1436 self.dirstate.update([f], mode)
1433
1437
1434 # merge the tricky bits
1438 # merge the tricky bits
1435 files = merge.keys()
1439 files = merge.keys()
1436 files.sort()
1440 files.sort()
1437 for f in files:
1441 for f in files:
1438 self.ui.status("merging %s\n" % f)
1442 self.ui.status("merging %s\n" % f)
1439 m, o, flag = merge[f]
1443 m, o, flag = merge[f]
1440 self.merge3(f, m, o)
1444 self.merge3(f, m, o)
1441 util.set_exec(self.wjoin(f), flag)
1445 util.set_exec(self.wjoin(f), flag)
1442 if moddirstate:
1446 if moddirstate:
1443 self.dirstate.update([f], 'm')
1447 self.dirstate.update([f], 'm')
1444
1448
1445 for f in remove:
1449 for f in remove:
1446 self.ui.note("removing %s\n" % f)
1450 self.ui.note("removing %s\n" % f)
1447 os.unlink(f)
1451 os.unlink(f)
1448 # try removing directories that might now be empty
1452 # try removing directories that might now be empty
1449 try: os.removedirs(os.path.dirname(f))
1453 try: os.removedirs(os.path.dirname(f))
1450 except: pass
1454 except: pass
1451 if moddirstate:
1455 if moddirstate:
1452 if mode == 'n':
1456 if mode == 'n':
1453 self.dirstate.forget(remove)
1457 self.dirstate.forget(remove)
1454 else:
1458 else:
1455 self.dirstate.update(remove, 'r')
1459 self.dirstate.update(remove, 'r')
1456
1460
1457 def merge3(self, fn, my, other):
1461 def merge3(self, fn, my, other):
1458 """perform a 3-way merge in the working directory"""
1462 """perform a 3-way merge in the working directory"""
1459
1463
1460 def temp(prefix, node):
1464 def temp(prefix, node):
1461 pre = "%s~%s." % (os.path.basename(fn), prefix)
1465 pre = "%s~%s." % (os.path.basename(fn), prefix)
1462 (fd, name) = tempfile.mkstemp("", pre)
1466 (fd, name) = tempfile.mkstemp("", pre)
1463 f = os.fdopen(fd, "wb")
1467 f = os.fdopen(fd, "wb")
1464 f.write(fl.revision(node))
1468 f.write(fl.revision(node))
1465 f.close()
1469 f.close()
1466 return name
1470 return name
1467
1471
1468 fl = self.file(fn)
1472 fl = self.file(fn)
1469 base = fl.ancestor(my, other)
1473 base = fl.ancestor(my, other)
1470 a = self.wjoin(fn)
1474 a = self.wjoin(fn)
1471 b = temp("base", base)
1475 b = temp("base", base)
1472 c = temp("other", other)
1476 c = temp("other", other)
1473
1477
1474 self.ui.note("resolving %s\n" % fn)
1478 self.ui.note("resolving %s\n" % fn)
1475 self.ui.debug("file %s: other %s ancestor %s\n" %
1479 self.ui.debug("file %s: other %s ancestor %s\n" %
1476 (fn, short(other), short(base)))
1480 (fn, short(other), short(base)))
1477
1481
1478 cmd = self.ui.config("ui", "merge") or \
1482 cmd = self.ui.config("ui", "merge") or \
1479 os.environ.get("HGMERGE", "hgmerge")
1483 os.environ.get("HGMERGE", "hgmerge")
1480 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1484 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1481 if r:
1485 if r:
1482 self.ui.warn("merging %s failed!\n" % fn)
1486 self.ui.warn("merging %s failed!\n" % fn)
1483
1487
1484 os.unlink(b)
1488 os.unlink(b)
1485 os.unlink(c)
1489 os.unlink(c)
1486
1490
1487 def verify(self):
1491 def verify(self):
1488 filelinkrevs = {}
1492 filelinkrevs = {}
1489 filenodes = {}
1493 filenodes = {}
1490 changesets = revisions = files = 0
1494 changesets = revisions = files = 0
1491 errors = 0
1495 errors = 0
1492
1496
1493 seen = {}
1497 seen = {}
1494 self.ui.status("checking changesets\n")
1498 self.ui.status("checking changesets\n")
1495 for i in range(self.changelog.count()):
1499 for i in range(self.changelog.count()):
1496 changesets += 1
1500 changesets += 1
1497 n = self.changelog.node(i)
1501 n = self.changelog.node(i)
1498 if n in seen:
1502 if n in seen:
1499 self.ui.warn("duplicate changeset at revision %d\n" % i)
1503 self.ui.warn("duplicate changeset at revision %d\n" % i)
1500 errors += 1
1504 errors += 1
1501 seen[n] = 1
1505 seen[n] = 1
1502
1506
1503 for p in self.changelog.parents(n):
1507 for p in self.changelog.parents(n):
1504 if p not in self.changelog.nodemap:
1508 if p not in self.changelog.nodemap:
1505 self.ui.warn("changeset %s has unknown parent %s\n" %
1509 self.ui.warn("changeset %s has unknown parent %s\n" %
1506 (short(n), short(p)))
1510 (short(n), short(p)))
1507 errors += 1
1511 errors += 1
1508 try:
1512 try:
1509 changes = self.changelog.read(n)
1513 changes = self.changelog.read(n)
1510 except Exception, inst:
1514 except Exception, inst:
1511 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1515 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1512 errors += 1
1516 errors += 1
1513
1517
1514 for f in changes[3]:
1518 for f in changes[3]:
1515 filelinkrevs.setdefault(f, []).append(i)
1519 filelinkrevs.setdefault(f, []).append(i)
1516
1520
1517 seen = {}
1521 seen = {}
1518 self.ui.status("checking manifests\n")
1522 self.ui.status("checking manifests\n")
1519 for i in range(self.manifest.count()):
1523 for i in range(self.manifest.count()):
1520 n = self.manifest.node(i)
1524 n = self.manifest.node(i)
1521 if n in seen:
1525 if n in seen:
1522 self.ui.warn("duplicate manifest at revision %d\n" % i)
1526 self.ui.warn("duplicate manifest at revision %d\n" % i)
1523 errors += 1
1527 errors += 1
1524 seen[n] = 1
1528 seen[n] = 1
1525
1529
1526 for p in self.manifest.parents(n):
1530 for p in self.manifest.parents(n):
1527 if p not in self.manifest.nodemap:
1531 if p not in self.manifest.nodemap:
1528 self.ui.warn("manifest %s has unknown parent %s\n" %
1532 self.ui.warn("manifest %s has unknown parent %s\n" %
1529 (short(n), short(p)))
1533 (short(n), short(p)))
1530 errors += 1
1534 errors += 1
1531
1535
1532 try:
1536 try:
1533 delta = mdiff.patchtext(self.manifest.delta(n))
1537 delta = mdiff.patchtext(self.manifest.delta(n))
1534 except KeyboardInterrupt:
1538 except KeyboardInterrupt:
1535 self.ui.warn("aborted")
1539 self.ui.warn("aborted")
1536 sys.exit(0)
1540 sys.exit(0)
1537 except Exception, inst:
1541 except Exception, inst:
1538 self.ui.warn("unpacking manifest %s: %s\n"
1542 self.ui.warn("unpacking manifest %s: %s\n"
1539 % (short(n), inst))
1543 % (short(n), inst))
1540 errors += 1
1544 errors += 1
1541
1545
1542 ff = [ l.split('\0') for l in delta.splitlines() ]
1546 ff = [ l.split('\0') for l in delta.splitlines() ]
1543 for f, fn in ff:
1547 for f, fn in ff:
1544 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1548 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1545
1549
1546 self.ui.status("crosschecking files in changesets and manifests\n")
1550 self.ui.status("crosschecking files in changesets and manifests\n")
1547 for f in filenodes:
1551 for f in filenodes:
1548 if f not in filelinkrevs:
1552 if f not in filelinkrevs:
1549 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1553 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1550 errors += 1
1554 errors += 1
1551
1555
1552 for f in filelinkrevs:
1556 for f in filelinkrevs:
1553 if f not in filenodes:
1557 if f not in filenodes:
1554 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1558 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1555 errors += 1
1559 errors += 1
1556
1560
1557 self.ui.status("checking files\n")
1561 self.ui.status("checking files\n")
1558 ff = filenodes.keys()
1562 ff = filenodes.keys()
1559 ff.sort()
1563 ff.sort()
1560 for f in ff:
1564 for f in ff:
1561 if f == "/dev/null": continue
1565 if f == "/dev/null": continue
1562 files += 1
1566 files += 1
1563 fl = self.file(f)
1567 fl = self.file(f)
1564 nodes = { nullid: 1 }
1568 nodes = { nullid: 1 }
1565 seen = {}
1569 seen = {}
1566 for i in range(fl.count()):
1570 for i in range(fl.count()):
1567 revisions += 1
1571 revisions += 1
1568 n = fl.node(i)
1572 n = fl.node(i)
1569
1573
1570 if n in seen:
1574 if n in seen:
1571 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1575 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1572 errors += 1
1576 errors += 1
1573
1577
1574 if n not in filenodes[f]:
1578 if n not in filenodes[f]:
1575 self.ui.warn("%s: %d:%s not in manifests\n"
1579 self.ui.warn("%s: %d:%s not in manifests\n"
1576 % (f, i, short(n)))
1580 % (f, i, short(n)))
1577 errors += 1
1581 errors += 1
1578 else:
1582 else:
1579 del filenodes[f][n]
1583 del filenodes[f][n]
1580
1584
1581 flr = fl.linkrev(n)
1585 flr = fl.linkrev(n)
1582 if flr not in filelinkrevs[f]:
1586 if flr not in filelinkrevs[f]:
1583 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1587 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1584 % (f, short(n), fl.linkrev(n)))
1588 % (f, short(n), fl.linkrev(n)))
1585 errors += 1
1589 errors += 1
1586 else:
1590 else:
1587 filelinkrevs[f].remove(flr)
1591 filelinkrevs[f].remove(flr)
1588
1592
1589 # verify contents
1593 # verify contents
1590 try:
1594 try:
1591 t = fl.read(n)
1595 t = fl.read(n)
1592 except Exception, inst:
1596 except Exception, inst:
1593 self.ui.warn("unpacking file %s %s: %s\n"
1597 self.ui.warn("unpacking file %s %s: %s\n"
1594 % (f, short(n), inst))
1598 % (f, short(n), inst))
1595 errors += 1
1599 errors += 1
1596
1600
1597 # verify parents
1601 # verify parents
1598 (p1, p2) = fl.parents(n)
1602 (p1, p2) = fl.parents(n)
1599 if p1 not in nodes:
1603 if p1 not in nodes:
1600 self.ui.warn("file %s:%s unknown parent 1 %s" %
1604 self.ui.warn("file %s:%s unknown parent 1 %s" %
1601 (f, short(n), short(p1)))
1605 (f, short(n), short(p1)))
1602 errors += 1
1606 errors += 1
1603 if p2 not in nodes:
1607 if p2 not in nodes:
1604 self.ui.warn("file %s:%s unknown parent 2 %s" %
1608 self.ui.warn("file %s:%s unknown parent 2 %s" %
1605 (f, short(n), short(p1)))
1609 (f, short(n), short(p1)))
1606 errors += 1
1610 errors += 1
1607 nodes[n] = 1
1611 nodes[n] = 1
1608
1612
1609 # cross-check
1613 # cross-check
1610 for node in filenodes[f]:
1614 for node in filenodes[f]:
1611 self.ui.warn("node %s in manifests not in %s\n"
1615 self.ui.warn("node %s in manifests not in %s\n"
1612 % (hex(n), f))
1616 % (hex(n), f))
1613 errors += 1
1617 errors += 1
1614
1618
1615 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1619 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1616 (files, changesets, revisions))
1620 (files, changesets, revisions))
1617
1621
1618 if errors:
1622 if errors:
1619 self.ui.warn("%d integrity errors encountered!\n" % errors)
1623 self.ui.warn("%d integrity errors encountered!\n" % errors)
1620 return 1
1624 return 1
1621
1625
1622 class httprepository:
1626 class httprepository:
1623 def __init__(self, ui, path):
1627 def __init__(self, ui, path):
1624 self.url = path
1628 self.url = path
1625 self.ui = ui
1629 self.ui = ui
1626 no_list = [ "localhost", "127.0.0.1" ]
1630 no_list = [ "localhost", "127.0.0.1" ]
1627 host = ui.config("http_proxy", "host")
1631 host = ui.config("http_proxy", "host")
1628 if host is None:
1632 if host is None:
1629 host = os.environ.get("http_proxy")
1633 host = os.environ.get("http_proxy")
1630 if host and host.startswith('http://'):
1634 if host and host.startswith('http://'):
1631 host = host[7:]
1635 host = host[7:]
1632 user = ui.config("http_proxy", "user")
1636 user = ui.config("http_proxy", "user")
1633 passwd = ui.config("http_proxy", "passwd")
1637 passwd = ui.config("http_proxy", "passwd")
1634 no = ui.config("http_proxy", "no")
1638 no = ui.config("http_proxy", "no")
1635 if no is None:
1639 if no is None:
1636 no = os.environ.get("no_proxy")
1640 no = os.environ.get("no_proxy")
1637 if no:
1641 if no:
1638 no_list = no_list + no.split(",")
1642 no_list = no_list + no.split(",")
1639
1643
1640 no_proxy = 0
1644 no_proxy = 0
1641 for h in no_list:
1645 for h in no_list:
1642 if (path.startswith("http://" + h + "/") or
1646 if (path.startswith("http://" + h + "/") or
1643 path.startswith("http://" + h + ":") or
1647 path.startswith("http://" + h + ":") or
1644 path == "http://" + h):
1648 path == "http://" + h):
1645 no_proxy = 1
1649 no_proxy = 1
1646
1650
1647 # Note: urllib2 takes proxy values from the environment and those will
1651 # Note: urllib2 takes proxy values from the environment and those will
1648 # take precedence
1652 # take precedence
1649 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1653 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1650 if os.environ.has_key(env):
1654 if os.environ.has_key(env):
1651 del os.environ[env]
1655 del os.environ[env]
1652
1656
1653 proxy_handler = urllib2.BaseHandler()
1657 proxy_handler = urllib2.BaseHandler()
1654 if host and not no_proxy:
1658 if host and not no_proxy:
1655 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1659 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1656
1660
1657 authinfo = None
1661 authinfo = None
1658 if user and passwd:
1662 if user and passwd:
1659 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1663 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1660 passmgr.add_password(None, host, user, passwd)
1664 passmgr.add_password(None, host, user, passwd)
1661 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1665 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1662
1666
1663 opener = urllib2.build_opener(proxy_handler, authinfo)
1667 opener = urllib2.build_opener(proxy_handler, authinfo)
1664 urllib2.install_opener(opener)
1668 urllib2.install_opener(opener)
1665
1669
1666 def dev(self):
1670 def dev(self):
1667 return -1
1671 return -1
1668
1672
1669 def do_cmd(self, cmd, **args):
1673 def do_cmd(self, cmd, **args):
1670 self.ui.debug("sending %s command\n" % cmd)
1674 self.ui.debug("sending %s command\n" % cmd)
1671 q = {"cmd": cmd}
1675 q = {"cmd": cmd}
1672 q.update(args)
1676 q.update(args)
1673 qs = urllib.urlencode(q)
1677 qs = urllib.urlencode(q)
1674 cu = "%s?%s" % (self.url, qs)
1678 cu = "%s?%s" % (self.url, qs)
1675 return urllib2.urlopen(cu)
1679 return urllib2.urlopen(cu)
1676
1680
1677 def heads(self):
1681 def heads(self):
1678 d = self.do_cmd("heads").read()
1682 d = self.do_cmd("heads").read()
1679 try:
1683 try:
1680 return map(bin, d[:-1].split(" "))
1684 return map(bin, d[:-1].split(" "))
1681 except:
1685 except:
1682 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1686 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1683 raise
1687 raise
1684
1688
1685 def branches(self, nodes):
1689 def branches(self, nodes):
1686 n = " ".join(map(hex, nodes))
1690 n = " ".join(map(hex, nodes))
1687 d = self.do_cmd("branches", nodes=n).read()
1691 d = self.do_cmd("branches", nodes=n).read()
1688 try:
1692 try:
1689 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1693 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1690 return br
1694 return br
1691 except:
1695 except:
1692 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1696 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1693 raise
1697 raise
1694
1698
1695 def between(self, pairs):
1699 def between(self, pairs):
1696 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1700 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1697 d = self.do_cmd("between", pairs=n).read()
1701 d = self.do_cmd("between", pairs=n).read()
1698 try:
1702 try:
1699 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1703 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1700 return p
1704 return p
1701 except:
1705 except:
1702 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1706 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1703 raise
1707 raise
1704
1708
1705 def changegroup(self, nodes):
1709 def changegroup(self, nodes):
1706 n = " ".join(map(hex, nodes))
1710 n = " ".join(map(hex, nodes))
1707 f = self.do_cmd("changegroup", roots=n)
1711 f = self.do_cmd("changegroup", roots=n)
1708 bytes = 0
1712 bytes = 0
1709
1713
1710 class zread:
1714 class zread:
1711 def __init__(self, f):
1715 def __init__(self, f):
1712 self.zd = zlib.decompressobj()
1716 self.zd = zlib.decompressobj()
1713 self.f = f
1717 self.f = f
1714 self.buf = ""
1718 self.buf = ""
1715 def read(self, l):
1719 def read(self, l):
1716 while l > len(self.buf):
1720 while l > len(self.buf):
1717 r = f.read(4096)
1721 r = f.read(4096)
1718 if r:
1722 if r:
1719 self.buf += self.zd.decompress(r)
1723 self.buf += self.zd.decompress(r)
1720 else:
1724 else:
1721 self.buf += self.zd.flush()
1725 self.buf += self.zd.flush()
1722 break
1726 break
1723 d, self.buf = self.buf[:l], self.buf[l:]
1727 d, self.buf = self.buf[:l], self.buf[l:]
1724 return d
1728 return d
1725
1729
1726 return zread(f)
1730 return zread(f)
1727
1731
1728 class remotelock:
1732 class remotelock:
1729 def __init__(self, repo):
1733 def __init__(self, repo):
1730 self.repo = repo
1734 self.repo = repo
1731 def release(self):
1735 def release(self):
1732 self.repo.unlock()
1736 self.repo.unlock()
1733 self.repo = None
1737 self.repo = None
1734 def __del__(self):
1738 def __del__(self):
1735 if self.repo:
1739 if self.repo:
1736 self.release()
1740 self.release()
1737
1741
1738 class sshrepository:
1742 class sshrepository:
1739 def __init__(self, ui, path):
1743 def __init__(self, ui, path):
1740 self.url = path
1744 self.url = path
1741 self.ui = ui
1745 self.ui = ui
1742
1746
1743 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1747 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1744 if not m:
1748 if not m:
1745 raise RepoError("couldn't parse destination %s\n" % path)
1749 raise RepoError("couldn't parse destination %s\n" % path)
1746
1750
1747 self.user = m.group(2)
1751 self.user = m.group(2)
1748 self.host = m.group(3)
1752 self.host = m.group(3)
1749 self.port = m.group(5)
1753 self.port = m.group(5)
1750 self.path = m.group(7)
1754 self.path = m.group(7)
1751
1755
1752 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1756 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1753 args = self.port and ("%s -p %s") % (args, self.port) or args
1757 args = self.port and ("%s -p %s") % (args, self.port) or args
1754 path = self.path or ""
1758 path = self.path or ""
1755
1759
1756 cmd = "ssh %s 'hg -R %s serve --stdio'"
1760 cmd = "ssh %s 'hg -R %s serve --stdio'"
1757 cmd = cmd % (args, path)
1761 cmd = cmd % (args, path)
1758
1762
1759 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
1763 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
1760
1764
1761 def readerr(self):
1765 def readerr(self):
1762 while 1:
1766 while 1:
1763 r,w,x = select.select([self.pipee], [], [], 0)
1767 r,w,x = select.select([self.pipee], [], [], 0)
1764 if not r: break
1768 if not r: break
1765 l = self.pipee.readline()
1769 l = self.pipee.readline()
1766 if not l: break
1770 if not l: break
1767 self.ui.status("remote: ", l)
1771 self.ui.status("remote: ", l)
1768
1772
1769 def __del__(self):
1773 def __del__(self):
1770 self.pipeo.close()
1774 self.pipeo.close()
1771 self.pipei.close()
1775 self.pipei.close()
1772 for l in self.pipee:
1776 for l in self.pipee:
1773 self.ui.status("remote: ", l)
1777 self.ui.status("remote: ", l)
1774 self.pipee.close()
1778 self.pipee.close()
1775
1779
1776 def dev(self):
1780 def dev(self):
1777 return -1
1781 return -1
1778
1782
1779 def do_cmd(self, cmd, **args):
1783 def do_cmd(self, cmd, **args):
1780 self.ui.debug("sending %s command\n" % cmd)
1784 self.ui.debug("sending %s command\n" % cmd)
1781 self.pipeo.write("%s\n" % cmd)
1785 self.pipeo.write("%s\n" % cmd)
1782 for k, v in args.items():
1786 for k, v in args.items():
1783 self.pipeo.write("%s %d\n" % (k, len(v)))
1787 self.pipeo.write("%s %d\n" % (k, len(v)))
1784 self.pipeo.write(v)
1788 self.pipeo.write(v)
1785 self.pipeo.flush()
1789 self.pipeo.flush()
1786
1790
1787 return self.pipei
1791 return self.pipei
1788
1792
1789 def call(self, cmd, **args):
1793 def call(self, cmd, **args):
1790 r = self.do_cmd(cmd, **args)
1794 r = self.do_cmd(cmd, **args)
1791 l = r.readline()
1795 l = r.readline()
1792 self.readerr()
1796 self.readerr()
1793 try:
1797 try:
1794 l = int(l)
1798 l = int(l)
1795 except:
1799 except:
1796 raise RepoError("unexpected response '%s'" % l)
1800 raise RepoError("unexpected response '%s'" % l)
1797 return r.read(l)
1801 return r.read(l)
1798
1802
1799 def lock(self):
1803 def lock(self):
1800 self.call("lock")
1804 self.call("lock")
1801 return remotelock(self)
1805 return remotelock(self)
1802
1806
1803 def unlock(self):
1807 def unlock(self):
1804 self.call("unlock")
1808 self.call("unlock")
1805
1809
1806 def heads(self):
1810 def heads(self):
1807 d = self.call("heads")
1811 d = self.call("heads")
1808 try:
1812 try:
1809 return map(bin, d[:-1].split(" "))
1813 return map(bin, d[:-1].split(" "))
1810 except:
1814 except:
1811 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1815 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1812
1816
1813 def branches(self, nodes):
1817 def branches(self, nodes):
1814 n = " ".join(map(hex, nodes))
1818 n = " ".join(map(hex, nodes))
1815 d = self.call("branches", nodes=n)
1819 d = self.call("branches", nodes=n)
1816 try:
1820 try:
1817 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1821 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1818 return br
1822 return br
1819 except:
1823 except:
1820 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1824 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1821
1825
1822 def between(self, pairs):
1826 def between(self, pairs):
1823 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1827 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1824 d = self.call("between", pairs=n)
1828 d = self.call("between", pairs=n)
1825 try:
1829 try:
1826 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1830 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1827 return p
1831 return p
1828 except:
1832 except:
1829 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1833 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1830
1834
1831 def changegroup(self, nodes):
1835 def changegroup(self, nodes):
1832 n = " ".join(map(hex, nodes))
1836 n = " ".join(map(hex, nodes))
1833 f = self.do_cmd("changegroup", roots=n)
1837 f = self.do_cmd("changegroup", roots=n)
1834 return self.pipei
1838 return self.pipei
1835
1839
1836 def addchangegroup(self, cg):
1840 def addchangegroup(self, cg):
1837 d = self.call("addchangegroup")
1841 d = self.call("addchangegroup")
1838 if d:
1842 if d:
1839 raise RepoError("push refused: %s", d)
1843 raise RepoError("push refused: %s", d)
1840
1844
1841 while 1:
1845 while 1:
1842 d = cg.read(4096)
1846 d = cg.read(4096)
1843 if not d: break
1847 if not d: break
1844 self.pipeo.write(d)
1848 self.pipeo.write(d)
1845 self.readerr()
1849 self.readerr()
1846
1850
1847 self.pipeo.flush()
1851 self.pipeo.flush()
1848
1852
1849 self.readerr()
1853 self.readerr()
1850 l = int(self.pipei.readline())
1854 l = int(self.pipei.readline())
1851 return self.pipei.read(l) != ""
1855 return self.pipei.read(l) != ""
1852
1856
1853 def repository(ui, path=None, create=0):
1857 def repository(ui, path=None, create=0):
1854 if path:
1858 if path:
1855 if path.startswith("http://"):
1859 if path.startswith("http://"):
1856 return httprepository(ui, path)
1860 return httprepository(ui, path)
1857 if path.startswith("hg://"):
1861 if path.startswith("hg://"):
1858 return httprepository(ui, path.replace("hg://", "http://"))
1862 return httprepository(ui, path.replace("hg://", "http://"))
1859 if path.startswith("old-http://"):
1863 if path.startswith("old-http://"):
1860 return localrepository(ui, path.replace("old-http://", "http://"))
1864 return localrepository(ui, path.replace("old-http://", "http://"))
1861 if path.startswith("ssh://"):
1865 if path.startswith("ssh://"):
1862 return sshrepository(ui, path)
1866 return sshrepository(ui, path)
1863
1867
1864 return localrepository(ui, path, create)
1868 return localrepository(ui, path, create)
General Comments 0
You need to be logged in to leave comments. Login now