##// END OF EJS Templates
Attempt to read all remaining remote output at close...
Matt Mackall -
r648:8c89408a default
parent child Browse files
Show More
@@ -1,1858 +1,1859 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.ignorelist = None
490 self.ignorelist = 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 self.ignorelist is None:
501 if self.ignorelist is None:
502 self.ignorelist = []
502 self.ignorelist = []
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 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
507 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
508 except IOError: pass
508 except IOError: pass
509 for pat in self.ignorelist:
509 for pat in self.ignorelist:
510 if pat.search(f): return True
510 if pat.search(f): return True
511 return False
511 return False
512
512
513 def hook(self, name, **args):
513 def hook(self, name, **args):
514 s = self.ui.config("hooks", name)
514 s = self.ui.config("hooks", name)
515 if s:
515 if s:
516 self.ui.note("running hook %s: %s\n" % (name, s))
516 self.ui.note("running hook %s: %s\n" % (name, s))
517 old = {}
517 old = {}
518 for k, v in args.items():
518 for k, v in args.items():
519 k = k.upper()
519 k = k.upper()
520 old[k] = os.environ.get(k, None)
520 old[k] = os.environ.get(k, None)
521 os.environ[k] = v
521 os.environ[k] = v
522
522
523 r = os.system(s)
523 r = os.system(s)
524
524
525 for k, v in old.items():
525 for k, v in old.items():
526 if v != None:
526 if v != None:
527 os.environ[k] = v
527 os.environ[k] = v
528 else:
528 else:
529 del os.environ[k]
529 del os.environ[k]
530
530
531 if r:
531 if r:
532 self.ui.warn("abort: %s hook failed with status %d!\n" %
532 self.ui.warn("abort: %s hook failed with status %d!\n" %
533 (name, r))
533 (name, r))
534 return False
534 return False
535 return True
535 return True
536
536
537 def tags(self):
537 def tags(self):
538 '''return a mapping of tag to node'''
538 '''return a mapping of tag to node'''
539 if not self.tagscache:
539 if not self.tagscache:
540 self.tagscache = {}
540 self.tagscache = {}
541 def addtag(self, k, n):
541 def addtag(self, k, n):
542 try:
542 try:
543 bin_n = bin(n)
543 bin_n = bin(n)
544 except TypeError:
544 except TypeError:
545 bin_n = ''
545 bin_n = ''
546 self.tagscache[k.strip()] = bin_n
546 self.tagscache[k.strip()] = bin_n
547
547
548 try:
548 try:
549 # read each head of the tags file, ending with the tip
549 # read each head of the tags file, ending with the tip
550 # and add each tag found to the map, with "newer" ones
550 # and add each tag found to the map, with "newer" ones
551 # taking precedence
551 # taking precedence
552 fl = self.file(".hgtags")
552 fl = self.file(".hgtags")
553 h = fl.heads()
553 h = fl.heads()
554 h.reverse()
554 h.reverse()
555 for r in h:
555 for r in h:
556 for l in fl.revision(r).splitlines():
556 for l in fl.revision(r).splitlines():
557 if l:
557 if l:
558 n, k = l.split(" ", 1)
558 n, k = l.split(" ", 1)
559 addtag(self, k, n)
559 addtag(self, k, n)
560 except KeyError:
560 except KeyError:
561 pass
561 pass
562
562
563 try:
563 try:
564 f = self.opener("localtags")
564 f = self.opener("localtags")
565 for l in f:
565 for l in f:
566 n, k = l.split(" ", 1)
566 n, k = l.split(" ", 1)
567 addtag(self, k, n)
567 addtag(self, k, n)
568 except IOError:
568 except IOError:
569 pass
569 pass
570
570
571 self.tagscache['tip'] = self.changelog.tip()
571 self.tagscache['tip'] = self.changelog.tip()
572
572
573 return self.tagscache
573 return self.tagscache
574
574
575 def tagslist(self):
575 def tagslist(self):
576 '''return a list of tags ordered by revision'''
576 '''return a list of tags ordered by revision'''
577 l = []
577 l = []
578 for t, n in self.tags().items():
578 for t, n in self.tags().items():
579 try:
579 try:
580 r = self.changelog.rev(n)
580 r = self.changelog.rev(n)
581 except:
581 except:
582 r = -2 # sort to the beginning of the list if unknown
582 r = -2 # sort to the beginning of the list if unknown
583 l.append((r,t,n))
583 l.append((r,t,n))
584 l.sort()
584 l.sort()
585 return [(t,n) for r,t,n in l]
585 return [(t,n) for r,t,n in l]
586
586
587 def nodetags(self, node):
587 def nodetags(self, node):
588 '''return the tags associated with a node'''
588 '''return the tags associated with a node'''
589 if not self.nodetagscache:
589 if not self.nodetagscache:
590 self.nodetagscache = {}
590 self.nodetagscache = {}
591 for t,n in self.tags().items():
591 for t,n in self.tags().items():
592 self.nodetagscache.setdefault(n,[]).append(t)
592 self.nodetagscache.setdefault(n,[]).append(t)
593 return self.nodetagscache.get(node, [])
593 return self.nodetagscache.get(node, [])
594
594
595 def lookup(self, key):
595 def lookup(self, key):
596 try:
596 try:
597 return self.tags()[key]
597 return self.tags()[key]
598 except KeyError:
598 except KeyError:
599 return self.changelog.lookup(key)
599 return self.changelog.lookup(key)
600
600
601 def dev(self):
601 def dev(self):
602 if self.remote: return -1
602 if self.remote: return -1
603 return os.stat(self.path).st_dev
603 return os.stat(self.path).st_dev
604
604
605 def join(self, f):
605 def join(self, f):
606 return os.path.join(self.path, f)
606 return os.path.join(self.path, f)
607
607
608 def wjoin(self, f):
608 def wjoin(self, f):
609 return os.path.join(self.root, f)
609 return os.path.join(self.root, f)
610
610
611 def file(self, f):
611 def file(self, f):
612 if f[0] == '/': f = f[1:]
612 if f[0] == '/': f = f[1:]
613 return filelog(self.opener, f)
613 return filelog(self.opener, f)
614
614
615 def getcwd(self):
615 def getcwd(self):
616 cwd = os.getcwd()
616 cwd = os.getcwd()
617 if cwd == self.root: return ''
617 if cwd == self.root: return ''
618 return cwd[len(self.root) + 1:]
618 return cwd[len(self.root) + 1:]
619
619
620 def wfile(self, f, mode='r'):
620 def wfile(self, f, mode='r'):
621 return self.wopener(f, mode)
621 return self.wopener(f, mode)
622
622
623 def transaction(self):
623 def transaction(self):
624 # save dirstate for undo
624 # save dirstate for undo
625 try:
625 try:
626 ds = self.opener("dirstate").read()
626 ds = self.opener("dirstate").read()
627 except IOError:
627 except IOError:
628 ds = ""
628 ds = ""
629 self.opener("undo.dirstate", "w").write(ds)
629 self.opener("undo.dirstate", "w").write(ds)
630
630
631 return transaction.transaction(self.ui.warn,
631 return transaction.transaction(self.ui.warn,
632 self.opener, self.join("journal"),
632 self.opener, self.join("journal"),
633 self.join("undo"))
633 self.join("undo"))
634
634
635 def recover(self):
635 def recover(self):
636 lock = self.lock()
636 lock = self.lock()
637 if os.path.exists(self.join("journal")):
637 if os.path.exists(self.join("journal")):
638 self.ui.status("rolling back interrupted transaction\n")
638 self.ui.status("rolling back interrupted transaction\n")
639 return transaction.rollback(self.opener, self.join("journal"))
639 return transaction.rollback(self.opener, self.join("journal"))
640 else:
640 else:
641 self.ui.warn("no interrupted transaction available\n")
641 self.ui.warn("no interrupted transaction available\n")
642
642
643 def undo(self):
643 def undo(self):
644 lock = self.lock()
644 lock = self.lock()
645 if os.path.exists(self.join("undo")):
645 if os.path.exists(self.join("undo")):
646 self.ui.status("rolling back last transaction\n")
646 self.ui.status("rolling back last transaction\n")
647 transaction.rollback(self.opener, self.join("undo"))
647 transaction.rollback(self.opener, self.join("undo"))
648 self.dirstate = None
648 self.dirstate = None
649 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
649 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
650 self.dirstate = dirstate(self.opener, self.ui, self.root)
650 self.dirstate = dirstate(self.opener, self.ui, self.root)
651 else:
651 else:
652 self.ui.warn("no undo information available\n")
652 self.ui.warn("no undo information available\n")
653
653
654 def lock(self, wait = 1):
654 def lock(self, wait = 1):
655 try:
655 try:
656 return lock.lock(self.join("lock"), 0)
656 return lock.lock(self.join("lock"), 0)
657 except lock.LockHeld, inst:
657 except lock.LockHeld, inst:
658 if wait:
658 if wait:
659 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
659 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
660 return lock.lock(self.join("lock"), wait)
660 return lock.lock(self.join("lock"), wait)
661 raise inst
661 raise inst
662
662
663 def rawcommit(self, files, text, user, date, p1=None, p2=None):
663 def rawcommit(self, files, text, user, date, p1=None, p2=None):
664 orig_parent = self.dirstate.parents()[0] or nullid
664 orig_parent = self.dirstate.parents()[0] or nullid
665 p1 = p1 or self.dirstate.parents()[0] or nullid
665 p1 = p1 or self.dirstate.parents()[0] or nullid
666 p2 = p2 or self.dirstate.parents()[1] or nullid
666 p2 = p2 or self.dirstate.parents()[1] or nullid
667 c1 = self.changelog.read(p1)
667 c1 = self.changelog.read(p1)
668 c2 = self.changelog.read(p2)
668 c2 = self.changelog.read(p2)
669 m1 = self.manifest.read(c1[0])
669 m1 = self.manifest.read(c1[0])
670 mf1 = self.manifest.readflags(c1[0])
670 mf1 = self.manifest.readflags(c1[0])
671 m2 = self.manifest.read(c2[0])
671 m2 = self.manifest.read(c2[0])
672
672
673 if orig_parent == p1:
673 if orig_parent == p1:
674 update_dirstate = 1
674 update_dirstate = 1
675 else:
675 else:
676 update_dirstate = 0
676 update_dirstate = 0
677
677
678 tr = self.transaction()
678 tr = self.transaction()
679 mm = m1.copy()
679 mm = m1.copy()
680 mfm = mf1.copy()
680 mfm = mf1.copy()
681 linkrev = self.changelog.count()
681 linkrev = self.changelog.count()
682 for f in files:
682 for f in files:
683 try:
683 try:
684 t = self.wfile(f).read()
684 t = self.wfile(f).read()
685 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
685 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
686 r = self.file(f)
686 r = self.file(f)
687 mfm[f] = tm
687 mfm[f] = tm
688 mm[f] = r.add(t, {}, tr, linkrev,
688 mm[f] = r.add(t, {}, tr, linkrev,
689 m1.get(f, nullid), m2.get(f, nullid))
689 m1.get(f, nullid), m2.get(f, nullid))
690 if update_dirstate:
690 if update_dirstate:
691 self.dirstate.update([f], "n")
691 self.dirstate.update([f], "n")
692 except IOError:
692 except IOError:
693 try:
693 try:
694 del mm[f]
694 del mm[f]
695 del mfm[f]
695 del mfm[f]
696 if update_dirstate:
696 if update_dirstate:
697 self.dirstate.forget([f])
697 self.dirstate.forget([f])
698 except:
698 except:
699 # deleted from p2?
699 # deleted from p2?
700 pass
700 pass
701
701
702 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
702 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
703 user = user or self.ui.username()
703 user = user or self.ui.username()
704 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
704 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
705 tr.close()
705 tr.close()
706 if update_dirstate:
706 if update_dirstate:
707 self.dirstate.setparents(n, nullid)
707 self.dirstate.setparents(n, nullid)
708
708
709 def commit(self, files = None, text = "", user = None, date = None):
709 def commit(self, files = None, text = "", user = None, date = None):
710 commit = []
710 commit = []
711 remove = []
711 remove = []
712 if files:
712 if files:
713 for f in files:
713 for f in files:
714 s = self.dirstate.state(f)
714 s = self.dirstate.state(f)
715 if s in 'nmai':
715 if s in 'nmai':
716 commit.append(f)
716 commit.append(f)
717 elif s == 'r':
717 elif s == 'r':
718 remove.append(f)
718 remove.append(f)
719 else:
719 else:
720 self.ui.warn("%s not tracked!\n" % f)
720 self.ui.warn("%s not tracked!\n" % f)
721 else:
721 else:
722 (c, a, d, u) = self.changes(None, None)
722 (c, a, d, u) = self.changes(None, None)
723 commit = c + a
723 commit = c + a
724 remove = d
724 remove = d
725
725
726 if not commit and not remove:
726 if not commit and not remove:
727 self.ui.status("nothing changed\n")
727 self.ui.status("nothing changed\n")
728 return
728 return
729
729
730 if not self.hook("precommit"):
730 if not self.hook("precommit"):
731 return 1
731 return 1
732
732
733 p1, p2 = self.dirstate.parents()
733 p1, p2 = self.dirstate.parents()
734 c1 = self.changelog.read(p1)
734 c1 = self.changelog.read(p1)
735 c2 = self.changelog.read(p2)
735 c2 = self.changelog.read(p2)
736 m1 = self.manifest.read(c1[0])
736 m1 = self.manifest.read(c1[0])
737 mf1 = self.manifest.readflags(c1[0])
737 mf1 = self.manifest.readflags(c1[0])
738 m2 = self.manifest.read(c2[0])
738 m2 = self.manifest.read(c2[0])
739 lock = self.lock()
739 lock = self.lock()
740 tr = self.transaction()
740 tr = self.transaction()
741
741
742 # check in files
742 # check in files
743 new = {}
743 new = {}
744 linkrev = self.changelog.count()
744 linkrev = self.changelog.count()
745 commit.sort()
745 commit.sort()
746 for f in commit:
746 for f in commit:
747 self.ui.note(f + "\n")
747 self.ui.note(f + "\n")
748 try:
748 try:
749 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
749 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
750 t = self.wfile(f).read()
750 t = self.wfile(f).read()
751 except IOError:
751 except IOError:
752 self.warn("trouble committing %s!\n" % f)
752 self.warn("trouble committing %s!\n" % f)
753 raise
753 raise
754
754
755 meta = {}
755 meta = {}
756 cp = self.dirstate.copied(f)
756 cp = self.dirstate.copied(f)
757 if cp:
757 if cp:
758 meta["copy"] = cp
758 meta["copy"] = cp
759 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
759 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
760 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
760 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
761
761
762 r = self.file(f)
762 r = self.file(f)
763 fp1 = m1.get(f, nullid)
763 fp1 = m1.get(f, nullid)
764 fp2 = m2.get(f, nullid)
764 fp2 = m2.get(f, nullid)
765 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
765 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
766
766
767 # update manifest
767 # update manifest
768 m1.update(new)
768 m1.update(new)
769 for f in remove:
769 for f in remove:
770 if f in m1:
770 if f in m1:
771 del m1[f]
771 del m1[f]
772 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], (new,remove))
772 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], (new,remove))
773
773
774 # add changeset
774 # add changeset
775 new = new.keys()
775 new = new.keys()
776 new.sort()
776 new.sort()
777
777
778 if not text:
778 if not text:
779 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
779 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
780 edittext += "".join(["HG: changed %s\n" % f for f in new])
780 edittext += "".join(["HG: changed %s\n" % f for f in new])
781 edittext += "".join(["HG: removed %s\n" % f for f in remove])
781 edittext += "".join(["HG: removed %s\n" % f for f in remove])
782 edittext = self.ui.edit(edittext)
782 edittext = self.ui.edit(edittext)
783 if not edittext.rstrip():
783 if not edittext.rstrip():
784 return 1
784 return 1
785 text = edittext
785 text = edittext
786
786
787 user = user or self.ui.username()
787 user = user or self.ui.username()
788 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
788 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
789
789
790 if not self.hook("commit", node=hex(n)):
790 if not self.hook("commit", node=hex(n)):
791 return 1
791 return 1
792
792
793 tr.close()
793 tr.close()
794
794
795 self.dirstate.setparents(n)
795 self.dirstate.setparents(n)
796 self.dirstate.update(new, "n")
796 self.dirstate.update(new, "n")
797 self.dirstate.forget(remove)
797 self.dirstate.forget(remove)
798
798
799 def changes(self, node1, node2, files=None):
799 def changes(self, node1, node2, files=None):
800 mf2, u = None, []
800 mf2, u = None, []
801
801
802 def fcmp(fn, mf):
802 def fcmp(fn, mf):
803 t1 = self.wfile(fn).read()
803 t1 = self.wfile(fn).read()
804 t2 = self.file(fn).revision(mf[fn])
804 t2 = self.file(fn).revision(mf[fn])
805 return cmp(t1, t2)
805 return cmp(t1, t2)
806
806
807 # are we comparing the working directory?
807 # are we comparing the working directory?
808 if not node2:
808 if not node2:
809 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
809 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
810
810
811 # are we comparing working dir against its parent?
811 # are we comparing working dir against its parent?
812 if not node1:
812 if not node1:
813 if l:
813 if l:
814 # do a full compare of any files that might have changed
814 # do a full compare of any files that might have changed
815 change = self.changelog.read(self.dirstate.parents()[0])
815 change = self.changelog.read(self.dirstate.parents()[0])
816 mf2 = self.manifest.read(change[0])
816 mf2 = self.manifest.read(change[0])
817 for f in l:
817 for f in l:
818 if fcmp(f, mf2):
818 if fcmp(f, mf2):
819 c.append(f)
819 c.append(f)
820
820
821 for l in c, a, d, u:
821 for l in c, a, d, u:
822 l.sort()
822 l.sort()
823
823
824 return (c, a, d, u)
824 return (c, a, d, u)
825
825
826 # are we comparing working dir against non-tip?
826 # are we comparing working dir against non-tip?
827 # generate a pseudo-manifest for the working dir
827 # generate a pseudo-manifest for the working dir
828 if not node2:
828 if not node2:
829 if not mf2:
829 if not mf2:
830 change = self.changelog.read(self.dirstate.parents()[0])
830 change = self.changelog.read(self.dirstate.parents()[0])
831 mf2 = self.manifest.read(change[0]).copy()
831 mf2 = self.manifest.read(change[0]).copy()
832 for f in a + c + l:
832 for f in a + c + l:
833 mf2[f] = ""
833 mf2[f] = ""
834 for f in d:
834 for f in d:
835 if f in mf2: del mf2[f]
835 if f in mf2: del mf2[f]
836 else:
836 else:
837 change = self.changelog.read(node2)
837 change = self.changelog.read(node2)
838 mf2 = self.manifest.read(change[0])
838 mf2 = self.manifest.read(change[0])
839
839
840 # flush lists from dirstate before comparing manifests
840 # flush lists from dirstate before comparing manifests
841 c, a = [], []
841 c, a = [], []
842
842
843 change = self.changelog.read(node1)
843 change = self.changelog.read(node1)
844 mf1 = self.manifest.read(change[0]).copy()
844 mf1 = self.manifest.read(change[0]).copy()
845
845
846 for fn in mf2:
846 for fn in mf2:
847 if mf1.has_key(fn):
847 if mf1.has_key(fn):
848 if mf1[fn] != mf2[fn]:
848 if mf1[fn] != mf2[fn]:
849 if mf2[fn] != "" or fcmp(fn, mf1):
849 if mf2[fn] != "" or fcmp(fn, mf1):
850 c.append(fn)
850 c.append(fn)
851 del mf1[fn]
851 del mf1[fn]
852 else:
852 else:
853 a.append(fn)
853 a.append(fn)
854
854
855 d = mf1.keys()
855 d = mf1.keys()
856
856
857 for l in c, a, d, u:
857 for l in c, a, d, u:
858 l.sort()
858 l.sort()
859
859
860 return (c, a, d, u)
860 return (c, a, d, u)
861
861
862 def add(self, list):
862 def add(self, list):
863 for f in list:
863 for f in list:
864 p = self.wjoin(f)
864 p = self.wjoin(f)
865 if not os.path.exists(p):
865 if not os.path.exists(p):
866 self.ui.warn("%s does not exist!\n" % f)
866 self.ui.warn("%s does not exist!\n" % f)
867 elif not os.path.isfile(p):
867 elif not os.path.isfile(p):
868 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
868 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
869 elif self.dirstate.state(f) == 'n':
869 elif self.dirstate.state(f) == 'n':
870 self.ui.warn("%s already tracked!\n" % f)
870 self.ui.warn("%s already tracked!\n" % f)
871 else:
871 else:
872 self.dirstate.update([f], "a")
872 self.dirstate.update([f], "a")
873
873
874 def forget(self, list):
874 def forget(self, list):
875 for f in list:
875 for f in list:
876 if self.dirstate.state(f) not in 'ai':
876 if self.dirstate.state(f) not in 'ai':
877 self.ui.warn("%s not added!\n" % f)
877 self.ui.warn("%s not added!\n" % f)
878 else:
878 else:
879 self.dirstate.forget([f])
879 self.dirstate.forget([f])
880
880
881 def remove(self, list):
881 def remove(self, list):
882 for f in list:
882 for f in list:
883 p = self.wjoin(f)
883 p = self.wjoin(f)
884 if os.path.exists(p):
884 if os.path.exists(p):
885 self.ui.warn("%s still exists!\n" % f)
885 self.ui.warn("%s still exists!\n" % f)
886 elif self.dirstate.state(f) == 'a':
886 elif self.dirstate.state(f) == 'a':
887 self.ui.warn("%s never committed!\n" % f)
887 self.ui.warn("%s never committed!\n" % f)
888 self.dirstate.forget(f)
888 self.dirstate.forget(f)
889 elif f not in self.dirstate:
889 elif f not in self.dirstate:
890 self.ui.warn("%s not tracked!\n" % f)
890 self.ui.warn("%s not tracked!\n" % f)
891 else:
891 else:
892 self.dirstate.update([f], "r")
892 self.dirstate.update([f], "r")
893
893
894 def copy(self, source, dest):
894 def copy(self, source, dest):
895 p = self.wjoin(dest)
895 p = self.wjoin(dest)
896 if not os.path.exists(dest):
896 if not os.path.exists(dest):
897 self.ui.warn("%s does not exist!\n" % dest)
897 self.ui.warn("%s does not exist!\n" % dest)
898 elif not os.path.isfile(dest):
898 elif not os.path.isfile(dest):
899 self.ui.warn("copy failed: %s is not a file\n" % dest)
899 self.ui.warn("copy failed: %s is not a file\n" % dest)
900 else:
900 else:
901 if self.dirstate.state(dest) == '?':
901 if self.dirstate.state(dest) == '?':
902 self.dirstate.update([dest], "a")
902 self.dirstate.update([dest], "a")
903 self.dirstate.copy(source, dest)
903 self.dirstate.copy(source, dest)
904
904
905 def heads(self):
905 def heads(self):
906 return self.changelog.heads()
906 return self.changelog.heads()
907
907
908 def branches(self, nodes):
908 def branches(self, nodes):
909 if not nodes: nodes = [self.changelog.tip()]
909 if not nodes: nodes = [self.changelog.tip()]
910 b = []
910 b = []
911 for n in nodes:
911 for n in nodes:
912 t = n
912 t = n
913 while n:
913 while n:
914 p = self.changelog.parents(n)
914 p = self.changelog.parents(n)
915 if p[1] != nullid or p[0] == nullid:
915 if p[1] != nullid or p[0] == nullid:
916 b.append((t, n, p[0], p[1]))
916 b.append((t, n, p[0], p[1]))
917 break
917 break
918 n = p[0]
918 n = p[0]
919 return b
919 return b
920
920
921 def between(self, pairs):
921 def between(self, pairs):
922 r = []
922 r = []
923
923
924 for top, bottom in pairs:
924 for top, bottom in pairs:
925 n, l, i = top, [], 0
925 n, l, i = top, [], 0
926 f = 1
926 f = 1
927
927
928 while n != bottom:
928 while n != bottom:
929 p = self.changelog.parents(n)[0]
929 p = self.changelog.parents(n)[0]
930 if i == f:
930 if i == f:
931 l.append(n)
931 l.append(n)
932 f = f * 2
932 f = f * 2
933 n = p
933 n = p
934 i += 1
934 i += 1
935
935
936 r.append(l)
936 r.append(l)
937
937
938 return r
938 return r
939
939
940 def newer(self, nodes):
940 def newer(self, nodes):
941 m = {}
941 m = {}
942 nl = []
942 nl = []
943 pm = {}
943 pm = {}
944 cl = self.changelog
944 cl = self.changelog
945 t = l = cl.count()
945 t = l = cl.count()
946
946
947 # find the lowest numbered node
947 # find the lowest numbered node
948 for n in nodes:
948 for n in nodes:
949 l = min(l, cl.rev(n))
949 l = min(l, cl.rev(n))
950 m[n] = 1
950 m[n] = 1
951
951
952 for i in xrange(l, t):
952 for i in xrange(l, t):
953 n = cl.node(i)
953 n = cl.node(i)
954 if n in m: # explicitly listed
954 if n in m: # explicitly listed
955 pm[n] = 1
955 pm[n] = 1
956 nl.append(n)
956 nl.append(n)
957 continue
957 continue
958 for p in cl.parents(n):
958 for p in cl.parents(n):
959 if p in pm: # parent listed
959 if p in pm: # parent listed
960 pm[n] = 1
960 pm[n] = 1
961 nl.append(n)
961 nl.append(n)
962 break
962 break
963
963
964 return nl
964 return nl
965
965
966 def findincoming(self, remote, base={}):
966 def findincoming(self, remote, base={}):
967 m = self.changelog.nodemap
967 m = self.changelog.nodemap
968 search = []
968 search = []
969 fetch = []
969 fetch = []
970 seen = {}
970 seen = {}
971 seenbranch = {}
971 seenbranch = {}
972
972
973 # assume we're closer to the tip than the root
973 # assume we're closer to the tip than the root
974 # and start by examining the heads
974 # and start by examining the heads
975 self.ui.status("searching for changes\n")
975 self.ui.status("searching for changes\n")
976 heads = remote.heads()
976 heads = remote.heads()
977 unknown = []
977 unknown = []
978 for h in heads:
978 for h in heads:
979 if h not in m:
979 if h not in m:
980 unknown.append(h)
980 unknown.append(h)
981 else:
981 else:
982 base[h] = 1
982 base[h] = 1
983
983
984 if not unknown:
984 if not unknown:
985 return None
985 return None
986
986
987 rep = {}
987 rep = {}
988 reqcnt = 0
988 reqcnt = 0
989
989
990 # search through remote branches
990 # search through remote branches
991 # a 'branch' here is a linear segment of history, with four parts:
991 # a 'branch' here is a linear segment of history, with four parts:
992 # head, root, first parent, second parent
992 # head, root, first parent, second parent
993 # (a branch always has two parents (or none) by definition)
993 # (a branch always has two parents (or none) by definition)
994 unknown = remote.branches(unknown)
994 unknown = remote.branches(unknown)
995 while unknown:
995 while unknown:
996 r = []
996 r = []
997 while unknown:
997 while unknown:
998 n = unknown.pop(0)
998 n = unknown.pop(0)
999 if n[0] in seen:
999 if n[0] in seen:
1000 continue
1000 continue
1001
1001
1002 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1002 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1003 if n[0] == nullid:
1003 if n[0] == nullid:
1004 break
1004 break
1005 if n in seenbranch:
1005 if n in seenbranch:
1006 self.ui.debug("branch already found\n")
1006 self.ui.debug("branch already found\n")
1007 continue
1007 continue
1008 if n[1] and n[1] in m: # do we know the base?
1008 if n[1] and n[1] in m: # do we know the base?
1009 self.ui.debug("found incomplete branch %s:%s\n"
1009 self.ui.debug("found incomplete branch %s:%s\n"
1010 % (short(n[0]), short(n[1])))
1010 % (short(n[0]), short(n[1])))
1011 search.append(n) # schedule branch range for scanning
1011 search.append(n) # schedule branch range for scanning
1012 seenbranch[n] = 1
1012 seenbranch[n] = 1
1013 else:
1013 else:
1014 if n[1] not in seen and n[1] not in fetch:
1014 if n[1] not in seen and n[1] not in fetch:
1015 if n[2] in m and n[3] in m:
1015 if n[2] in m and n[3] in m:
1016 self.ui.debug("found new changeset %s\n" %
1016 self.ui.debug("found new changeset %s\n" %
1017 short(n[1]))
1017 short(n[1]))
1018 fetch.append(n[1]) # earliest unknown
1018 fetch.append(n[1]) # earliest unknown
1019 base[n[2]] = 1 # latest known
1019 base[n[2]] = 1 # latest known
1020 continue
1020 continue
1021
1021
1022 for a in n[2:4]:
1022 for a in n[2:4]:
1023 if a not in rep:
1023 if a not in rep:
1024 r.append(a)
1024 r.append(a)
1025 rep[a] = 1
1025 rep[a] = 1
1026
1026
1027 seen[n[0]] = 1
1027 seen[n[0]] = 1
1028
1028
1029 if r:
1029 if r:
1030 reqcnt += 1
1030 reqcnt += 1
1031 self.ui.debug("request %d: %s\n" %
1031 self.ui.debug("request %d: %s\n" %
1032 (reqcnt, " ".join(map(short, r))))
1032 (reqcnt, " ".join(map(short, r))))
1033 for p in range(0, len(r), 10):
1033 for p in range(0, len(r), 10):
1034 for b in remote.branches(r[p:p+10]):
1034 for b in remote.branches(r[p:p+10]):
1035 self.ui.debug("received %s:%s\n" %
1035 self.ui.debug("received %s:%s\n" %
1036 (short(b[0]), short(b[1])))
1036 (short(b[0]), short(b[1])))
1037 if b[0] not in m and b[0] not in seen:
1037 if b[0] not in m and b[0] not in seen:
1038 unknown.append(b)
1038 unknown.append(b)
1039
1039
1040 # do binary search on the branches we found
1040 # do binary search on the branches we found
1041 while search:
1041 while search:
1042 n = search.pop(0)
1042 n = search.pop(0)
1043 reqcnt += 1
1043 reqcnt += 1
1044 l = remote.between([(n[0], n[1])])[0]
1044 l = remote.between([(n[0], n[1])])[0]
1045 l.append(n[1])
1045 l.append(n[1])
1046 p = n[0]
1046 p = n[0]
1047 f = 1
1047 f = 1
1048 for i in l:
1048 for i in l:
1049 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1049 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1050 if i in m:
1050 if i in m:
1051 if f <= 2:
1051 if f <= 2:
1052 self.ui.debug("found new branch changeset %s\n" %
1052 self.ui.debug("found new branch changeset %s\n" %
1053 short(p))
1053 short(p))
1054 fetch.append(p)
1054 fetch.append(p)
1055 base[i] = 1
1055 base[i] = 1
1056 else:
1056 else:
1057 self.ui.debug("narrowed branch search to %s:%s\n"
1057 self.ui.debug("narrowed branch search to %s:%s\n"
1058 % (short(p), short(i)))
1058 % (short(p), short(i)))
1059 search.append((p, i))
1059 search.append((p, i))
1060 break
1060 break
1061 p, f = i, f * 2
1061 p, f = i, f * 2
1062
1062
1063 # sanity check our fetch list
1063 # sanity check our fetch list
1064 for f in fetch:
1064 for f in fetch:
1065 if f in m:
1065 if f in m:
1066 raise RepoError("already have changeset " + short(f[:4]))
1066 raise RepoError("already have changeset " + short(f[:4]))
1067
1067
1068 if base.keys() == [nullid]:
1068 if base.keys() == [nullid]:
1069 self.ui.warn("warning: pulling from an unrelated repository!\n")
1069 self.ui.warn("warning: pulling from an unrelated repository!\n")
1070
1070
1071 self.ui.note("adding new changesets starting at " +
1071 self.ui.note("adding new changesets starting at " +
1072 " ".join([short(f) for f in fetch]) + "\n")
1072 " ".join([short(f) for f in fetch]) + "\n")
1073
1073
1074 self.ui.debug("%d total queries\n" % reqcnt)
1074 self.ui.debug("%d total queries\n" % reqcnt)
1075
1075
1076 return fetch
1076 return fetch
1077
1077
1078 def findoutgoing(self, remote):
1078 def findoutgoing(self, remote):
1079 base = {}
1079 base = {}
1080 self.findincoming(remote, base)
1080 self.findincoming(remote, base)
1081 remain = dict.fromkeys(self.changelog.nodemap)
1081 remain = dict.fromkeys(self.changelog.nodemap)
1082
1082
1083 # prune everything remote has from the tree
1083 # prune everything remote has from the tree
1084 del remain[nullid]
1084 del remain[nullid]
1085 remove = base.keys()
1085 remove = base.keys()
1086 while remove:
1086 while remove:
1087 n = remove.pop(0)
1087 n = remove.pop(0)
1088 if n in remain:
1088 if n in remain:
1089 del remain[n]
1089 del remain[n]
1090 for p in self.changelog.parents(n):
1090 for p in self.changelog.parents(n):
1091 remove.append(p)
1091 remove.append(p)
1092
1092
1093 # find every node whose parents have been pruned
1093 # find every node whose parents have been pruned
1094 subset = []
1094 subset = []
1095 for n in remain:
1095 for n in remain:
1096 p1, p2 = self.changelog.parents(n)
1096 p1, p2 = self.changelog.parents(n)
1097 if p1 not in remain and p2 not in remain:
1097 if p1 not in remain and p2 not in remain:
1098 subset.append(n)
1098 subset.append(n)
1099
1099
1100 # this is the set of all roots we have to push
1100 # this is the set of all roots we have to push
1101 return subset
1101 return subset
1102
1102
1103 def pull(self, remote):
1103 def pull(self, remote):
1104 lock = self.lock()
1104 lock = self.lock()
1105
1105
1106 # if we have an empty repo, fetch everything
1106 # if we have an empty repo, fetch everything
1107 if self.changelog.tip() == nullid:
1107 if self.changelog.tip() == nullid:
1108 self.ui.status("requesting all changes\n")
1108 self.ui.status("requesting all changes\n")
1109 fetch = [nullid]
1109 fetch = [nullid]
1110 else:
1110 else:
1111 fetch = self.findincoming(remote)
1111 fetch = self.findincoming(remote)
1112
1112
1113 if not fetch:
1113 if not fetch:
1114 self.ui.status("no changes found\n")
1114 self.ui.status("no changes found\n")
1115 return 1
1115 return 1
1116
1116
1117 cg = remote.changegroup(fetch)
1117 cg = remote.changegroup(fetch)
1118 return self.addchangegroup(cg)
1118 return self.addchangegroup(cg)
1119
1119
1120 def push(self, remote):
1120 def push(self, remote):
1121 lock = remote.lock()
1121 lock = remote.lock()
1122 update = self.findoutgoing(remote)
1122 update = self.findoutgoing(remote)
1123 if not update:
1123 if not update:
1124 self.ui.status("no changes found\n")
1124 self.ui.status("no changes found\n")
1125 return 1
1125 return 1
1126
1126
1127 cg = self.changegroup(update)
1127 cg = self.changegroup(update)
1128 return remote.addchangegroup(cg)
1128 return remote.addchangegroup(cg)
1129
1129
1130 def changegroup(self, basenodes):
1130 def changegroup(self, basenodes):
1131 class genread:
1131 class genread:
1132 def __init__(self, generator):
1132 def __init__(self, generator):
1133 self.g = generator
1133 self.g = generator
1134 self.buf = ""
1134 self.buf = ""
1135 def read(self, l):
1135 def read(self, l):
1136 while l > len(self.buf):
1136 while l > len(self.buf):
1137 try:
1137 try:
1138 self.buf += self.g.next()
1138 self.buf += self.g.next()
1139 except StopIteration:
1139 except StopIteration:
1140 break
1140 break
1141 d, self.buf = self.buf[:l], self.buf[l:]
1141 d, self.buf = self.buf[:l], self.buf[l:]
1142 return d
1142 return d
1143
1143
1144 def gengroup():
1144 def gengroup():
1145 nodes = self.newer(basenodes)
1145 nodes = self.newer(basenodes)
1146
1146
1147 # construct the link map
1147 # construct the link map
1148 linkmap = {}
1148 linkmap = {}
1149 for n in nodes:
1149 for n in nodes:
1150 linkmap[self.changelog.rev(n)] = n
1150 linkmap[self.changelog.rev(n)] = n
1151
1151
1152 # construct a list of all changed files
1152 # construct a list of all changed files
1153 changed = {}
1153 changed = {}
1154 for n in nodes:
1154 for n in nodes:
1155 c = self.changelog.read(n)
1155 c = self.changelog.read(n)
1156 for f in c[3]:
1156 for f in c[3]:
1157 changed[f] = 1
1157 changed[f] = 1
1158 changed = changed.keys()
1158 changed = changed.keys()
1159 changed.sort()
1159 changed.sort()
1160
1160
1161 # the changegroup is changesets + manifests + all file revs
1161 # the changegroup is changesets + manifests + all file revs
1162 revs = [ self.changelog.rev(n) for n in nodes ]
1162 revs = [ self.changelog.rev(n) for n in nodes ]
1163
1163
1164 for y in self.changelog.group(linkmap): yield y
1164 for y in self.changelog.group(linkmap): yield y
1165 for y in self.manifest.group(linkmap): yield y
1165 for y in self.manifest.group(linkmap): yield y
1166 for f in changed:
1166 for f in changed:
1167 yield struct.pack(">l", len(f) + 4) + f
1167 yield struct.pack(">l", len(f) + 4) + f
1168 g = self.file(f).group(linkmap)
1168 g = self.file(f).group(linkmap)
1169 for y in g:
1169 for y in g:
1170 yield y
1170 yield y
1171
1171
1172 yield struct.pack(">l", 0)
1172 yield struct.pack(">l", 0)
1173
1173
1174 return genread(gengroup())
1174 return genread(gengroup())
1175
1175
1176 def addchangegroup(self, source):
1176 def addchangegroup(self, source):
1177
1177
1178 def getchunk():
1178 def getchunk():
1179 d = source.read(4)
1179 d = source.read(4)
1180 if not d: return ""
1180 if not d: return ""
1181 l = struct.unpack(">l", d)[0]
1181 l = struct.unpack(">l", d)[0]
1182 if l <= 4: return ""
1182 if l <= 4: return ""
1183 return source.read(l - 4)
1183 return source.read(l - 4)
1184
1184
1185 def getgroup():
1185 def getgroup():
1186 while 1:
1186 while 1:
1187 c = getchunk()
1187 c = getchunk()
1188 if not c: break
1188 if not c: break
1189 yield c
1189 yield c
1190
1190
1191 def csmap(x):
1191 def csmap(x):
1192 self.ui.debug("add changeset %s\n" % short(x))
1192 self.ui.debug("add changeset %s\n" % short(x))
1193 return self.changelog.count()
1193 return self.changelog.count()
1194
1194
1195 def revmap(x):
1195 def revmap(x):
1196 return self.changelog.rev(x)
1196 return self.changelog.rev(x)
1197
1197
1198 if not source: return
1198 if not source: return
1199 changesets = files = revisions = 0
1199 changesets = files = revisions = 0
1200
1200
1201 tr = self.transaction()
1201 tr = self.transaction()
1202
1202
1203 # pull off the changeset group
1203 # pull off the changeset group
1204 self.ui.status("adding changesets\n")
1204 self.ui.status("adding changesets\n")
1205 co = self.changelog.tip()
1205 co = self.changelog.tip()
1206 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1206 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1207 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1207 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1208
1208
1209 # pull off the manifest group
1209 # pull off the manifest group
1210 self.ui.status("adding manifests\n")
1210 self.ui.status("adding manifests\n")
1211 mm = self.manifest.tip()
1211 mm = self.manifest.tip()
1212 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1212 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1213
1213
1214 # process the files
1214 # process the files
1215 self.ui.status("adding file revisions\n")
1215 self.ui.status("adding file revisions\n")
1216 while 1:
1216 while 1:
1217 f = getchunk()
1217 f = getchunk()
1218 if not f: break
1218 if not f: break
1219 self.ui.debug("adding %s revisions\n" % f)
1219 self.ui.debug("adding %s revisions\n" % f)
1220 fl = self.file(f)
1220 fl = self.file(f)
1221 o = fl.count()
1221 o = fl.count()
1222 n = fl.addgroup(getgroup(), revmap, tr)
1222 n = fl.addgroup(getgroup(), revmap, tr)
1223 revisions += fl.count() - o
1223 revisions += fl.count() - o
1224 files += 1
1224 files += 1
1225
1225
1226 self.ui.status(("modified %d files, added %d changesets" +
1226 self.ui.status(("modified %d files, added %d changesets" +
1227 " and %d new revisions\n")
1227 " and %d new revisions\n")
1228 % (files, changesets, revisions))
1228 % (files, changesets, revisions))
1229
1229
1230 tr.close()
1230 tr.close()
1231 return
1231 return
1232
1232
1233 def update(self, node, allow=False, force=False, choose=None,
1233 def update(self, node, allow=False, force=False, choose=None,
1234 moddirstate=True):
1234 moddirstate=True):
1235 pl = self.dirstate.parents()
1235 pl = self.dirstate.parents()
1236 if not force and pl[1] != nullid:
1236 if not force and pl[1] != nullid:
1237 self.ui.warn("aborting: outstanding uncommitted merges\n")
1237 self.ui.warn("aborting: outstanding uncommitted merges\n")
1238 return
1238 return
1239
1239
1240 p1, p2 = pl[0], node
1240 p1, p2 = pl[0], node
1241 pa = self.changelog.ancestor(p1, p2)
1241 pa = self.changelog.ancestor(p1, p2)
1242 m1n = self.changelog.read(p1)[0]
1242 m1n = self.changelog.read(p1)[0]
1243 m2n = self.changelog.read(p2)[0]
1243 m2n = self.changelog.read(p2)[0]
1244 man = self.manifest.ancestor(m1n, m2n)
1244 man = self.manifest.ancestor(m1n, m2n)
1245 m1 = self.manifest.read(m1n)
1245 m1 = self.manifest.read(m1n)
1246 mf1 = self.manifest.readflags(m1n)
1246 mf1 = self.manifest.readflags(m1n)
1247 m2 = self.manifest.read(m2n)
1247 m2 = self.manifest.read(m2n)
1248 mf2 = self.manifest.readflags(m2n)
1248 mf2 = self.manifest.readflags(m2n)
1249 ma = self.manifest.read(man)
1249 ma = self.manifest.read(man)
1250 mfa = self.manifest.readflags(man)
1250 mfa = self.manifest.readflags(man)
1251
1251
1252 (c, a, d, u) = self.changes(None, None)
1252 (c, a, d, u) = self.changes(None, None)
1253
1253
1254 # is this a jump, or a merge? i.e. is there a linear path
1254 # is this a jump, or a merge? i.e. is there a linear path
1255 # from p1 to p2?
1255 # from p1 to p2?
1256 linear_path = (pa == p1 or pa == p2)
1256 linear_path = (pa == p1 or pa == p2)
1257
1257
1258 # resolve the manifest to determine which files
1258 # resolve the manifest to determine which files
1259 # we care about merging
1259 # we care about merging
1260 self.ui.note("resolving manifests\n")
1260 self.ui.note("resolving manifests\n")
1261 self.ui.debug(" ancestor %s local %s remote %s\n" %
1261 self.ui.debug(" ancestor %s local %s remote %s\n" %
1262 (short(man), short(m1n), short(m2n)))
1262 (short(man), short(m1n), short(m2n)))
1263
1263
1264 merge = {}
1264 merge = {}
1265 get = {}
1265 get = {}
1266 remove = []
1266 remove = []
1267 mark = {}
1267 mark = {}
1268
1268
1269 # construct a working dir manifest
1269 # construct a working dir manifest
1270 mw = m1.copy()
1270 mw = m1.copy()
1271 mfw = mf1.copy()
1271 mfw = mf1.copy()
1272 umap = dict.fromkeys(u)
1272 umap = dict.fromkeys(u)
1273
1273
1274 for f in a + c + u:
1274 for f in a + c + u:
1275 mw[f] = ""
1275 mw[f] = ""
1276 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1276 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1277
1277
1278 for f in d:
1278 for f in d:
1279 if f in mw: del mw[f]
1279 if f in mw: del mw[f]
1280
1280
1281 # If we're jumping between revisions (as opposed to merging),
1281 # If we're jumping between revisions (as opposed to merging),
1282 # and if neither the working directory nor the target rev has
1282 # and if neither the working directory nor the target rev has
1283 # the file, then we need to remove it from the dirstate, to
1283 # the file, then we need to remove it from the dirstate, to
1284 # prevent the dirstate from listing the file when it is no
1284 # prevent the dirstate from listing the file when it is no
1285 # longer in the manifest.
1285 # longer in the manifest.
1286 if moddirstate and linear_path and f not in m2:
1286 if moddirstate and linear_path and f not in m2:
1287 self.dirstate.forget((f,))
1287 self.dirstate.forget((f,))
1288
1288
1289 # Compare manifests
1289 # Compare manifests
1290 for f, n in mw.iteritems():
1290 for f, n in mw.iteritems():
1291 if choose and not choose(f): continue
1291 if choose and not choose(f): continue
1292 if f in m2:
1292 if f in m2:
1293 s = 0
1293 s = 0
1294
1294
1295 # is the wfile new since m1, and match m2?
1295 # is the wfile new since m1, and match m2?
1296 if f not in m1:
1296 if f not in m1:
1297 t1 = self.wfile(f).read()
1297 t1 = self.wfile(f).read()
1298 t2 = self.file(f).revision(m2[f])
1298 t2 = self.file(f).revision(m2[f])
1299 if cmp(t1, t2) == 0:
1299 if cmp(t1, t2) == 0:
1300 mark[f] = 1
1300 mark[f] = 1
1301 n = m2[f]
1301 n = m2[f]
1302 del t1, t2
1302 del t1, t2
1303
1303
1304 # are files different?
1304 # are files different?
1305 if n != m2[f]:
1305 if n != m2[f]:
1306 a = ma.get(f, nullid)
1306 a = ma.get(f, nullid)
1307 # are both different from the ancestor?
1307 # are both different from the ancestor?
1308 if n != a and m2[f] != a:
1308 if n != a and m2[f] != a:
1309 self.ui.debug(" %s versions differ, resolve\n" % f)
1309 self.ui.debug(" %s versions differ, resolve\n" % f)
1310 # merge executable bits
1310 # merge executable bits
1311 # "if we changed or they changed, change in merge"
1311 # "if we changed or they changed, change in merge"
1312 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1312 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1313 mode = ((a^b) | (a^c)) ^ a
1313 mode = ((a^b) | (a^c)) ^ a
1314 merge[f] = (m1.get(f, nullid), m2[f], mode)
1314 merge[f] = (m1.get(f, nullid), m2[f], mode)
1315 s = 1
1315 s = 1
1316 # are we clobbering?
1316 # are we clobbering?
1317 # is remote's version newer?
1317 # is remote's version newer?
1318 # or are we going back in time?
1318 # or are we going back in time?
1319 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1319 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1320 self.ui.debug(" remote %s is newer, get\n" % f)
1320 self.ui.debug(" remote %s is newer, get\n" % f)
1321 get[f] = m2[f]
1321 get[f] = m2[f]
1322 s = 1
1322 s = 1
1323 else:
1323 else:
1324 mark[f] = 1
1324 mark[f] = 1
1325 elif f in umap:
1325 elif f in umap:
1326 # this unknown file is the same as the checkout
1326 # this unknown file is the same as the checkout
1327 get[f] = m2[f]
1327 get[f] = m2[f]
1328
1328
1329 if not s and mfw[f] != mf2[f]:
1329 if not s and mfw[f] != mf2[f]:
1330 if force:
1330 if force:
1331 self.ui.debug(" updating permissions for %s\n" % f)
1331 self.ui.debug(" updating permissions for %s\n" % f)
1332 util.set_exec(self.wjoin(f), mf2[f])
1332 util.set_exec(self.wjoin(f), mf2[f])
1333 else:
1333 else:
1334 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1334 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1335 mode = ((a^b) | (a^c)) ^ a
1335 mode = ((a^b) | (a^c)) ^ a
1336 if mode != b:
1336 if mode != b:
1337 self.ui.debug(" updating permissions for %s\n" % f)
1337 self.ui.debug(" updating permissions for %s\n" % f)
1338 util.set_exec(self.wjoin(f), mode)
1338 util.set_exec(self.wjoin(f), mode)
1339 mark[f] = 1
1339 mark[f] = 1
1340 del m2[f]
1340 del m2[f]
1341 elif f in ma:
1341 elif f in ma:
1342 if n != ma[f]:
1342 if n != ma[f]:
1343 r = "d"
1343 r = "d"
1344 if not force and (linear_path or allow):
1344 if not force and (linear_path or allow):
1345 r = self.ui.prompt(
1345 r = self.ui.prompt(
1346 (" local changed %s which remote deleted\n" % f) +
1346 (" local changed %s which remote deleted\n" % f) +
1347 "(k)eep or (d)elete?", "[kd]", "k")
1347 "(k)eep or (d)elete?", "[kd]", "k")
1348 if r == "d":
1348 if r == "d":
1349 remove.append(f)
1349 remove.append(f)
1350 else:
1350 else:
1351 self.ui.debug("other deleted %s\n" % f)
1351 self.ui.debug("other deleted %s\n" % f)
1352 remove.append(f) # other deleted it
1352 remove.append(f) # other deleted it
1353 else:
1353 else:
1354 if n == m1.get(f, nullid): # same as parent
1354 if n == m1.get(f, nullid): # same as parent
1355 if p2 == pa: # going backwards?
1355 if p2 == pa: # going backwards?
1356 self.ui.debug("remote deleted %s\n" % f)
1356 self.ui.debug("remote deleted %s\n" % f)
1357 remove.append(f)
1357 remove.append(f)
1358 else:
1358 else:
1359 self.ui.debug("local created %s, keeping\n" % f)
1359 self.ui.debug("local created %s, keeping\n" % f)
1360 else:
1360 else:
1361 self.ui.debug("working dir created %s, keeping\n" % f)
1361 self.ui.debug("working dir created %s, keeping\n" % f)
1362
1362
1363 for f, n in m2.iteritems():
1363 for f, n in m2.iteritems():
1364 if choose and not choose(f): continue
1364 if choose and not choose(f): continue
1365 if f[0] == "/": continue
1365 if f[0] == "/": continue
1366 if f in ma and n != ma[f]:
1366 if f in ma and n != ma[f]:
1367 r = "k"
1367 r = "k"
1368 if not force and (linear_path or allow):
1368 if not force and (linear_path or allow):
1369 r = self.ui.prompt(
1369 r = self.ui.prompt(
1370 ("remote changed %s which local deleted\n" % f) +
1370 ("remote changed %s which local deleted\n" % f) +
1371 "(k)eep or (d)elete?", "[kd]", "k")
1371 "(k)eep or (d)elete?", "[kd]", "k")
1372 if r == "k": get[f] = n
1372 if r == "k": get[f] = n
1373 elif f not in ma:
1373 elif f not in ma:
1374 self.ui.debug("remote created %s\n" % f)
1374 self.ui.debug("remote created %s\n" % f)
1375 get[f] = n
1375 get[f] = n
1376 else:
1376 else:
1377 self.ui.debug("local deleted %s\n" % f)
1377 self.ui.debug("local deleted %s\n" % f)
1378
1378
1379 del mw, m1, m2, ma
1379 del mw, m1, m2, ma
1380
1380
1381 if force:
1381 if force:
1382 for f in merge:
1382 for f in merge:
1383 get[f] = merge[f][1]
1383 get[f] = merge[f][1]
1384 merge = {}
1384 merge = {}
1385
1385
1386 if linear_path:
1386 if linear_path:
1387 # we don't need to do any magic, just jump to the new rev
1387 # we don't need to do any magic, just jump to the new rev
1388 mode = 'n'
1388 mode = 'n'
1389 p1, p2 = p2, nullid
1389 p1, p2 = p2, nullid
1390 else:
1390 else:
1391 if not allow:
1391 if not allow:
1392 self.ui.status("this update spans a branch" +
1392 self.ui.status("this update spans a branch" +
1393 " affecting the following files:\n")
1393 " affecting the following files:\n")
1394 fl = merge.keys() + get.keys()
1394 fl = merge.keys() + get.keys()
1395 fl.sort()
1395 fl.sort()
1396 for f in fl:
1396 for f in fl:
1397 cf = ""
1397 cf = ""
1398 if f in merge: cf = " (resolve)"
1398 if f in merge: cf = " (resolve)"
1399 self.ui.status(" %s%s\n" % (f, cf))
1399 self.ui.status(" %s%s\n" % (f, cf))
1400 self.ui.warn("aborting update spanning branches!\n")
1400 self.ui.warn("aborting update spanning branches!\n")
1401 self.ui.status("(use update -m to perform a branch merge)\n")
1401 self.ui.status("(use update -m to perform a branch merge)\n")
1402 return 1
1402 return 1
1403 # we have to remember what files we needed to get/change
1403 # we have to remember what files we needed to get/change
1404 # because any file that's different from either one of its
1404 # because any file that's different from either one of its
1405 # parents must be in the changeset
1405 # parents must be in the changeset
1406 mode = 'm'
1406 mode = 'm'
1407 if moddirstate:
1407 if moddirstate:
1408 self.dirstate.update(mark.keys(), "m")
1408 self.dirstate.update(mark.keys(), "m")
1409
1409
1410 if moddirstate:
1410 if moddirstate:
1411 self.dirstate.setparents(p1, p2)
1411 self.dirstate.setparents(p1, p2)
1412
1412
1413 # get the files we don't need to change
1413 # get the files we don't need to change
1414 files = get.keys()
1414 files = get.keys()
1415 files.sort()
1415 files.sort()
1416 for f in files:
1416 for f in files:
1417 if f[0] == "/": continue
1417 if f[0] == "/": continue
1418 self.ui.note("getting %s\n" % f)
1418 self.ui.note("getting %s\n" % f)
1419 t = self.file(f).read(get[f])
1419 t = self.file(f).read(get[f])
1420 try:
1420 try:
1421 self.wfile(f, "w").write(t)
1421 self.wfile(f, "w").write(t)
1422 except IOError:
1422 except IOError:
1423 os.makedirs(os.path.dirname(self.wjoin(f)))
1423 os.makedirs(os.path.dirname(self.wjoin(f)))
1424 self.wfile(f, "w").write(t)
1424 self.wfile(f, "w").write(t)
1425 util.set_exec(self.wjoin(f), mf2[f])
1425 util.set_exec(self.wjoin(f), mf2[f])
1426 if moddirstate:
1426 if moddirstate:
1427 self.dirstate.update([f], mode)
1427 self.dirstate.update([f], mode)
1428
1428
1429 # merge the tricky bits
1429 # merge the tricky bits
1430 files = merge.keys()
1430 files = merge.keys()
1431 files.sort()
1431 files.sort()
1432 for f in files:
1432 for f in files:
1433 self.ui.status("merging %s\n" % f)
1433 self.ui.status("merging %s\n" % f)
1434 m, o, flag = merge[f]
1434 m, o, flag = merge[f]
1435 self.merge3(f, m, o)
1435 self.merge3(f, m, o)
1436 util.set_exec(self.wjoin(f), flag)
1436 util.set_exec(self.wjoin(f), flag)
1437 if moddirstate:
1437 if moddirstate:
1438 self.dirstate.update([f], 'm')
1438 self.dirstate.update([f], 'm')
1439
1439
1440 for f in remove:
1440 for f in remove:
1441 self.ui.note("removing %s\n" % f)
1441 self.ui.note("removing %s\n" % f)
1442 os.unlink(f)
1442 os.unlink(f)
1443 # try removing directories that might now be empty
1443 # try removing directories that might now be empty
1444 try: os.removedirs(os.path.dirname(f))
1444 try: os.removedirs(os.path.dirname(f))
1445 except: pass
1445 except: pass
1446 if moddirstate:
1446 if moddirstate:
1447 if mode == 'n':
1447 if mode == 'n':
1448 self.dirstate.forget(remove)
1448 self.dirstate.forget(remove)
1449 else:
1449 else:
1450 self.dirstate.update(remove, 'r')
1450 self.dirstate.update(remove, 'r')
1451
1451
1452 def merge3(self, fn, my, other):
1452 def merge3(self, fn, my, other):
1453 """perform a 3-way merge in the working directory"""
1453 """perform a 3-way merge in the working directory"""
1454
1454
1455 def temp(prefix, node):
1455 def temp(prefix, node):
1456 pre = "%s~%s." % (os.path.basename(fn), prefix)
1456 pre = "%s~%s." % (os.path.basename(fn), prefix)
1457 (fd, name) = tempfile.mkstemp("", pre)
1457 (fd, name) = tempfile.mkstemp("", pre)
1458 f = os.fdopen(fd, "wb")
1458 f = os.fdopen(fd, "wb")
1459 f.write(fl.revision(node))
1459 f.write(fl.revision(node))
1460 f.close()
1460 f.close()
1461 return name
1461 return name
1462
1462
1463 fl = self.file(fn)
1463 fl = self.file(fn)
1464 base = fl.ancestor(my, other)
1464 base = fl.ancestor(my, other)
1465 a = self.wjoin(fn)
1465 a = self.wjoin(fn)
1466 b = temp("base", base)
1466 b = temp("base", base)
1467 c = temp("other", other)
1467 c = temp("other", other)
1468
1468
1469 self.ui.note("resolving %s\n" % fn)
1469 self.ui.note("resolving %s\n" % fn)
1470 self.ui.debug("file %s: other %s ancestor %s\n" %
1470 self.ui.debug("file %s: other %s ancestor %s\n" %
1471 (fn, short(other), short(base)))
1471 (fn, short(other), short(base)))
1472
1472
1473 cmd = self.ui.config("ui", "merge") or \
1473 cmd = self.ui.config("ui", "merge") or \
1474 os.environ.get("HGMERGE", "hgmerge")
1474 os.environ.get("HGMERGE", "hgmerge")
1475 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1475 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1476 if r:
1476 if r:
1477 self.ui.warn("merging %s failed!\n" % fn)
1477 self.ui.warn("merging %s failed!\n" % fn)
1478
1478
1479 os.unlink(b)
1479 os.unlink(b)
1480 os.unlink(c)
1480 os.unlink(c)
1481
1481
1482 def verify(self):
1482 def verify(self):
1483 filelinkrevs = {}
1483 filelinkrevs = {}
1484 filenodes = {}
1484 filenodes = {}
1485 changesets = revisions = files = 0
1485 changesets = revisions = files = 0
1486 errors = 0
1486 errors = 0
1487
1487
1488 seen = {}
1488 seen = {}
1489 self.ui.status("checking changesets\n")
1489 self.ui.status("checking changesets\n")
1490 for i in range(self.changelog.count()):
1490 for i in range(self.changelog.count()):
1491 changesets += 1
1491 changesets += 1
1492 n = self.changelog.node(i)
1492 n = self.changelog.node(i)
1493 if n in seen:
1493 if n in seen:
1494 self.ui.warn("duplicate changeset at revision %d\n" % i)
1494 self.ui.warn("duplicate changeset at revision %d\n" % i)
1495 errors += 1
1495 errors += 1
1496 seen[n] = 1
1496 seen[n] = 1
1497
1497
1498 for p in self.changelog.parents(n):
1498 for p in self.changelog.parents(n):
1499 if p not in self.changelog.nodemap:
1499 if p not in self.changelog.nodemap:
1500 self.ui.warn("changeset %s has unknown parent %s\n" %
1500 self.ui.warn("changeset %s has unknown parent %s\n" %
1501 (short(n), short(p)))
1501 (short(n), short(p)))
1502 errors += 1
1502 errors += 1
1503 try:
1503 try:
1504 changes = self.changelog.read(n)
1504 changes = self.changelog.read(n)
1505 except Exception, inst:
1505 except Exception, inst:
1506 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1506 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1507 errors += 1
1507 errors += 1
1508
1508
1509 for f in changes[3]:
1509 for f in changes[3]:
1510 filelinkrevs.setdefault(f, []).append(i)
1510 filelinkrevs.setdefault(f, []).append(i)
1511
1511
1512 seen = {}
1512 seen = {}
1513 self.ui.status("checking manifests\n")
1513 self.ui.status("checking manifests\n")
1514 for i in range(self.manifest.count()):
1514 for i in range(self.manifest.count()):
1515 n = self.manifest.node(i)
1515 n = self.manifest.node(i)
1516 if n in seen:
1516 if n in seen:
1517 self.ui.warn("duplicate manifest at revision %d\n" % i)
1517 self.ui.warn("duplicate manifest at revision %d\n" % i)
1518 errors += 1
1518 errors += 1
1519 seen[n] = 1
1519 seen[n] = 1
1520
1520
1521 for p in self.manifest.parents(n):
1521 for p in self.manifest.parents(n):
1522 if p not in self.manifest.nodemap:
1522 if p not in self.manifest.nodemap:
1523 self.ui.warn("manifest %s has unknown parent %s\n" %
1523 self.ui.warn("manifest %s has unknown parent %s\n" %
1524 (short(n), short(p)))
1524 (short(n), short(p)))
1525 errors += 1
1525 errors += 1
1526
1526
1527 try:
1527 try:
1528 delta = mdiff.patchtext(self.manifest.delta(n))
1528 delta = mdiff.patchtext(self.manifest.delta(n))
1529 except KeyboardInterrupt:
1529 except KeyboardInterrupt:
1530 self.ui.warn("aborted")
1530 self.ui.warn("aborted")
1531 sys.exit(0)
1531 sys.exit(0)
1532 except Exception, inst:
1532 except Exception, inst:
1533 self.ui.warn("unpacking manifest %s: %s\n"
1533 self.ui.warn("unpacking manifest %s: %s\n"
1534 % (short(n), inst))
1534 % (short(n), inst))
1535 errors += 1
1535 errors += 1
1536
1536
1537 ff = [ l.split('\0') for l in delta.splitlines() ]
1537 ff = [ l.split('\0') for l in delta.splitlines() ]
1538 for f, fn in ff:
1538 for f, fn in ff:
1539 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1539 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1540
1540
1541 self.ui.status("crosschecking files in changesets and manifests\n")
1541 self.ui.status("crosschecking files in changesets and manifests\n")
1542 for f in filenodes:
1542 for f in filenodes:
1543 if f not in filelinkrevs:
1543 if f not in filelinkrevs:
1544 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1544 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1545 errors += 1
1545 errors += 1
1546
1546
1547 for f in filelinkrevs:
1547 for f in filelinkrevs:
1548 if f not in filenodes:
1548 if f not in filenodes:
1549 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1549 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1550 errors += 1
1550 errors += 1
1551
1551
1552 self.ui.status("checking files\n")
1552 self.ui.status("checking files\n")
1553 ff = filenodes.keys()
1553 ff = filenodes.keys()
1554 ff.sort()
1554 ff.sort()
1555 for f in ff:
1555 for f in ff:
1556 if f == "/dev/null": continue
1556 if f == "/dev/null": continue
1557 files += 1
1557 files += 1
1558 fl = self.file(f)
1558 fl = self.file(f)
1559 nodes = { nullid: 1 }
1559 nodes = { nullid: 1 }
1560 seen = {}
1560 seen = {}
1561 for i in range(fl.count()):
1561 for i in range(fl.count()):
1562 revisions += 1
1562 revisions += 1
1563 n = fl.node(i)
1563 n = fl.node(i)
1564
1564
1565 if n in seen:
1565 if n in seen:
1566 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1566 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1567 errors += 1
1567 errors += 1
1568
1568
1569 if n not in filenodes[f]:
1569 if n not in filenodes[f]:
1570 self.ui.warn("%s: %d:%s not in manifests\n"
1570 self.ui.warn("%s: %d:%s not in manifests\n"
1571 % (f, i, short(n)))
1571 % (f, i, short(n)))
1572 errors += 1
1572 errors += 1
1573 else:
1573 else:
1574 del filenodes[f][n]
1574 del filenodes[f][n]
1575
1575
1576 flr = fl.linkrev(n)
1576 flr = fl.linkrev(n)
1577 if flr not in filelinkrevs[f]:
1577 if flr not in filelinkrevs[f]:
1578 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1578 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1579 % (f, short(n), fl.linkrev(n)))
1579 % (f, short(n), fl.linkrev(n)))
1580 errors += 1
1580 errors += 1
1581 else:
1581 else:
1582 filelinkrevs[f].remove(flr)
1582 filelinkrevs[f].remove(flr)
1583
1583
1584 # verify contents
1584 # verify contents
1585 try:
1585 try:
1586 t = fl.read(n)
1586 t = fl.read(n)
1587 except Exception, inst:
1587 except Exception, inst:
1588 self.ui.warn("unpacking file %s %s: %s\n"
1588 self.ui.warn("unpacking file %s %s: %s\n"
1589 % (f, short(n), inst))
1589 % (f, short(n), inst))
1590 errors += 1
1590 errors += 1
1591
1591
1592 # verify parents
1592 # verify parents
1593 (p1, p2) = fl.parents(n)
1593 (p1, p2) = fl.parents(n)
1594 if p1 not in nodes:
1594 if p1 not in nodes:
1595 self.ui.warn("file %s:%s unknown parent 1 %s" %
1595 self.ui.warn("file %s:%s unknown parent 1 %s" %
1596 (f, short(n), short(p1)))
1596 (f, short(n), short(p1)))
1597 errors += 1
1597 errors += 1
1598 if p2 not in nodes:
1598 if p2 not in nodes:
1599 self.ui.warn("file %s:%s unknown parent 2 %s" %
1599 self.ui.warn("file %s:%s unknown parent 2 %s" %
1600 (f, short(n), short(p1)))
1600 (f, short(n), short(p1)))
1601 errors += 1
1601 errors += 1
1602 nodes[n] = 1
1602 nodes[n] = 1
1603
1603
1604 # cross-check
1604 # cross-check
1605 for node in filenodes[f]:
1605 for node in filenodes[f]:
1606 self.ui.warn("node %s in manifests not in %s\n"
1606 self.ui.warn("node %s in manifests not in %s\n"
1607 % (hex(n), f))
1607 % (hex(n), f))
1608 errors += 1
1608 errors += 1
1609
1609
1610 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1610 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1611 (files, changesets, revisions))
1611 (files, changesets, revisions))
1612
1612
1613 if errors:
1613 if errors:
1614 self.ui.warn("%d integrity errors encountered!\n" % errors)
1614 self.ui.warn("%d integrity errors encountered!\n" % errors)
1615 return 1
1615 return 1
1616
1616
1617 class httprepository:
1617 class httprepository:
1618 def __init__(self, ui, path):
1618 def __init__(self, ui, path):
1619 self.url = path
1619 self.url = path
1620 self.ui = ui
1620 self.ui = ui
1621 no_list = [ "localhost", "127.0.0.1" ]
1621 no_list = [ "localhost", "127.0.0.1" ]
1622 host = ui.config("http_proxy", "host")
1622 host = ui.config("http_proxy", "host")
1623 if host is None:
1623 if host is None:
1624 host = os.environ.get("http_proxy")
1624 host = os.environ.get("http_proxy")
1625 if host and host.startswith('http://'):
1625 if host and host.startswith('http://'):
1626 host = host[7:]
1626 host = host[7:]
1627 user = ui.config("http_proxy", "user")
1627 user = ui.config("http_proxy", "user")
1628 passwd = ui.config("http_proxy", "passwd")
1628 passwd = ui.config("http_proxy", "passwd")
1629 no = ui.config("http_proxy", "no")
1629 no = ui.config("http_proxy", "no")
1630 if no is None:
1630 if no is None:
1631 no = os.environ.get("no_proxy")
1631 no = os.environ.get("no_proxy")
1632 if no:
1632 if no:
1633 no_list = no_list + no.split(",")
1633 no_list = no_list + no.split(",")
1634
1634
1635 no_proxy = 0
1635 no_proxy = 0
1636 for h in no_list:
1636 for h in no_list:
1637 if (path.startswith("http://" + h + "/") or
1637 if (path.startswith("http://" + h + "/") or
1638 path.startswith("http://" + h + ":") or
1638 path.startswith("http://" + h + ":") or
1639 path == "http://" + h):
1639 path == "http://" + h):
1640 no_proxy = 1
1640 no_proxy = 1
1641
1641
1642 # Note: urllib2 takes proxy values from the environment and those will
1642 # Note: urllib2 takes proxy values from the environment and those will
1643 # take precedence
1643 # take precedence
1644 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1644 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1645 if os.environ.has_key(env):
1645 if os.environ.has_key(env):
1646 del os.environ[env]
1646 del os.environ[env]
1647
1647
1648 proxy_handler = urllib2.BaseHandler()
1648 proxy_handler = urllib2.BaseHandler()
1649 if host and not no_proxy:
1649 if host and not no_proxy:
1650 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1650 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1651
1651
1652 authinfo = None
1652 authinfo = None
1653 if user and passwd:
1653 if user and passwd:
1654 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1654 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1655 passmgr.add_password(None, host, user, passwd)
1655 passmgr.add_password(None, host, user, passwd)
1656 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1656 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1657
1657
1658 opener = urllib2.build_opener(proxy_handler, authinfo)
1658 opener = urllib2.build_opener(proxy_handler, authinfo)
1659 urllib2.install_opener(opener)
1659 urllib2.install_opener(opener)
1660
1660
1661 def dev(self):
1661 def dev(self):
1662 return -1
1662 return -1
1663
1663
1664 def do_cmd(self, cmd, **args):
1664 def do_cmd(self, cmd, **args):
1665 self.ui.debug("sending %s command\n" % cmd)
1665 self.ui.debug("sending %s command\n" % cmd)
1666 q = {"cmd": cmd}
1666 q = {"cmd": cmd}
1667 q.update(args)
1667 q.update(args)
1668 qs = urllib.urlencode(q)
1668 qs = urllib.urlencode(q)
1669 cu = "%s?%s" % (self.url, qs)
1669 cu = "%s?%s" % (self.url, qs)
1670 return urllib2.urlopen(cu)
1670 return urllib2.urlopen(cu)
1671
1671
1672 def heads(self):
1672 def heads(self):
1673 d = self.do_cmd("heads").read()
1673 d = self.do_cmd("heads").read()
1674 try:
1674 try:
1675 return map(bin, d[:-1].split(" "))
1675 return map(bin, d[:-1].split(" "))
1676 except:
1676 except:
1677 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1677 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1678 raise
1678 raise
1679
1679
1680 def branches(self, nodes):
1680 def branches(self, nodes):
1681 n = " ".join(map(hex, nodes))
1681 n = " ".join(map(hex, nodes))
1682 d = self.do_cmd("branches", nodes=n).read()
1682 d = self.do_cmd("branches", nodes=n).read()
1683 try:
1683 try:
1684 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1684 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1685 return br
1685 return br
1686 except:
1686 except:
1687 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1687 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1688 raise
1688 raise
1689
1689
1690 def between(self, pairs):
1690 def between(self, pairs):
1691 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1691 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1692 d = self.do_cmd("between", pairs=n).read()
1692 d = self.do_cmd("between", pairs=n).read()
1693 try:
1693 try:
1694 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1694 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1695 return p
1695 return p
1696 except:
1696 except:
1697 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1697 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1698 raise
1698 raise
1699
1699
1700 def changegroup(self, nodes):
1700 def changegroup(self, nodes):
1701 n = " ".join(map(hex, nodes))
1701 n = " ".join(map(hex, nodes))
1702 f = self.do_cmd("changegroup", roots=n)
1702 f = self.do_cmd("changegroup", roots=n)
1703 bytes = 0
1703 bytes = 0
1704
1704
1705 class zread:
1705 class zread:
1706 def __init__(self, f):
1706 def __init__(self, f):
1707 self.zd = zlib.decompressobj()
1707 self.zd = zlib.decompressobj()
1708 self.f = f
1708 self.f = f
1709 self.buf = ""
1709 self.buf = ""
1710 def read(self, l):
1710 def read(self, l):
1711 while l > len(self.buf):
1711 while l > len(self.buf):
1712 r = f.read(4096)
1712 r = f.read(4096)
1713 if r:
1713 if r:
1714 self.buf += self.zd.decompress(r)
1714 self.buf += self.zd.decompress(r)
1715 else:
1715 else:
1716 self.buf += self.zd.flush()
1716 self.buf += self.zd.flush()
1717 break
1717 break
1718 d, self.buf = self.buf[:l], self.buf[l:]
1718 d, self.buf = self.buf[:l], self.buf[l:]
1719 return d
1719 return d
1720
1720
1721 return zread(f)
1721 return zread(f)
1722
1722
1723 class remotelock:
1723 class remotelock:
1724 def __init__(self, repo):
1724 def __init__(self, repo):
1725 self.repo = repo
1725 self.repo = repo
1726 def release(self):
1726 def release(self):
1727 self.repo.unlock()
1727 self.repo.unlock()
1728 self.repo = None
1728 self.repo = None
1729 def __del__(self):
1729 def __del__(self):
1730 if self.repo:
1730 if self.repo:
1731 self.release()
1731 self.release()
1732
1732
1733 class sshrepository:
1733 class sshrepository:
1734 def __init__(self, ui, path):
1734 def __init__(self, ui, path):
1735 self.url = path
1735 self.url = path
1736 self.ui = ui
1736 self.ui = ui
1737
1737
1738 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1738 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1739 if not m:
1739 if not m:
1740 raise RepoError("couldn't parse destination %s\n" % path)
1740 raise RepoError("couldn't parse destination %s\n" % path)
1741
1741
1742 self.user = m.group(2)
1742 self.user = m.group(2)
1743 self.host = m.group(3)
1743 self.host = m.group(3)
1744 self.port = m.group(5)
1744 self.port = m.group(5)
1745 self.path = m.group(7)
1745 self.path = m.group(7)
1746
1746
1747 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1747 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1748 args = self.port and ("%s -p %s") % (args, self.port) or args
1748 args = self.port and ("%s -p %s") % (args, self.port) or args
1749 path = self.path or ""
1749 path = self.path or ""
1750
1750
1751 cmd = "ssh %s 'hg -R %s serve --stdio'"
1751 cmd = "ssh %s 'hg -R %s serve --stdio'"
1752 cmd = cmd % (args, path)
1752 cmd = cmd % (args, path)
1753
1753
1754 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
1754 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
1755
1755
1756 def readerr(self):
1756 def readerr(self):
1757 while 1:
1757 while 1:
1758 r,w,x = select.select([self.pipee], [], [], 0)
1758 r,w,x = select.select([self.pipee], [], [], 0)
1759 if not r: break
1759 if not r: break
1760 l = self.pipee.readline()
1760 l = self.pipee.readline()
1761 if not l: break
1761 if not l: break
1762 self.ui.status("remote: ", l)
1762 self.ui.status("remote: ", l)
1763
1763
1764 def __del__(self):
1764 def __del__(self):
1765 self.readerr()
1766 self.pipeo.close()
1765 self.pipeo.close()
1767 self.pipei.close()
1766 self.pipei.close()
1767 for l in self.pipee:
1768 self.ui.status("remote: ", l)
1768 self.pipee.close()
1769 self.pipee.close()
1769
1770
1770 def dev(self):
1771 def dev(self):
1771 return -1
1772 return -1
1772
1773
1773 def do_cmd(self, cmd, **args):
1774 def do_cmd(self, cmd, **args):
1774 self.ui.debug("sending %s command\n" % cmd)
1775 self.ui.debug("sending %s command\n" % cmd)
1775 self.pipeo.write("%s\n" % cmd)
1776 self.pipeo.write("%s\n" % cmd)
1776 for k, v in args.items():
1777 for k, v in args.items():
1777 self.pipeo.write("%s %d\n" % (k, len(v)))
1778 self.pipeo.write("%s %d\n" % (k, len(v)))
1778 self.pipeo.write(v)
1779 self.pipeo.write(v)
1779 self.pipeo.flush()
1780 self.pipeo.flush()
1780
1781
1781 return self.pipei
1782 return self.pipei
1782
1783
1783 def call(self, cmd, **args):
1784 def call(self, cmd, **args):
1784 r = self.do_cmd(cmd, **args)
1785 r = self.do_cmd(cmd, **args)
1785 l = r.readline()
1786 l = r.readline()
1786 self.readerr()
1787 self.readerr()
1787 try:
1788 try:
1788 l = int(l)
1789 l = int(l)
1789 except:
1790 except:
1790 raise RepoError("unexpected response '%s'" % l)
1791 raise RepoError("unexpected response '%s'" % l)
1791 return r.read(l)
1792 return r.read(l)
1792
1793
1793 def lock(self):
1794 def lock(self):
1794 self.call("lock")
1795 self.call("lock")
1795 return remotelock(self)
1796 return remotelock(self)
1796
1797
1797 def unlock(self):
1798 def unlock(self):
1798 self.call("unlock")
1799 self.call("unlock")
1799
1800
1800 def heads(self):
1801 def heads(self):
1801 d = self.call("heads")
1802 d = self.call("heads")
1802 try:
1803 try:
1803 return map(bin, d[:-1].split(" "))
1804 return map(bin, d[:-1].split(" "))
1804 except:
1805 except:
1805 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1806 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1806
1807
1807 def branches(self, nodes):
1808 def branches(self, nodes):
1808 n = " ".join(map(hex, nodes))
1809 n = " ".join(map(hex, nodes))
1809 d = self.call("branches", nodes=n)
1810 d = self.call("branches", nodes=n)
1810 try:
1811 try:
1811 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1812 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1812 return br
1813 return br
1813 except:
1814 except:
1814 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1815 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1815
1816
1816 def between(self, pairs):
1817 def between(self, pairs):
1817 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1818 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1818 d = self.call("between", pairs=n)
1819 d = self.call("between", pairs=n)
1819 try:
1820 try:
1820 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1821 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1821 return p
1822 return p
1822 except:
1823 except:
1823 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1824 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1824
1825
1825 def changegroup(self, nodes):
1826 def changegroup(self, nodes):
1826 n = " ".join(map(hex, nodes))
1827 n = " ".join(map(hex, nodes))
1827 f = self.do_cmd("changegroup", roots=n)
1828 f = self.do_cmd("changegroup", roots=n)
1828 return self.pipei
1829 return self.pipei
1829
1830
1830 def addchangegroup(self, cg):
1831 def addchangegroup(self, cg):
1831 d = self.call("addchangegroup")
1832 d = self.call("addchangegroup")
1832 if d:
1833 if d:
1833 raise RepoError("push refused: %s", d)
1834 raise RepoError("push refused: %s", d)
1834
1835
1835 while 1:
1836 while 1:
1836 d = cg.read(4096)
1837 d = cg.read(4096)
1837 if not d: break
1838 if not d: break
1838 self.pipeo.write(d)
1839 self.pipeo.write(d)
1839 self.readerr()
1840 self.readerr()
1840
1841
1841 self.pipeo.flush()
1842 self.pipeo.flush()
1842
1843
1843 self.readerr()
1844 self.readerr()
1844 l = int(self.pipei.readline())
1845 l = int(self.pipei.readline())
1845 return self.pipei.read(l) != ""
1846 return self.pipei.read(l) != ""
1846
1847
1847 def repository(ui, path=None, create=0):
1848 def repository(ui, path=None, create=0):
1848 if path:
1849 if path:
1849 if path.startswith("http://"):
1850 if path.startswith("http://"):
1850 return httprepository(ui, path)
1851 return httprepository(ui, path)
1851 if path.startswith("hg://"):
1852 if path.startswith("hg://"):
1852 return httprepository(ui, path.replace("hg://", "http://"))
1853 return httprepository(ui, path.replace("hg://", "http://"))
1853 if path.startswith("old-http://"):
1854 if path.startswith("old-http://"):
1854 return localrepository(ui, path.replace("old-http://", "http://"))
1855 return localrepository(ui, path.replace("old-http://", "http://"))
1855 if path.startswith("ssh://"):
1856 if path.startswith("ssh://"):
1856 return sshrepository(ui, path)
1857 return sshrepository(ui, path)
1857
1858
1858 return localrepository(ui, path, create)
1859 return localrepository(ui, path, create)
General Comments 0
You need to be logged in to leave comments. Login now