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