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