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