##// END OF EJS Templates
Merge with upstream
Brendan Cully -
r3241:a184cd0c merge default
parent child Browse files
Show More
@@ -33,7 +33,7 b' class changectx(object):'
33 33 return short(self.node())
34 34
35 35 def __repr__(self):
36 return "<changectx %s>" % short(self.node())
36 return "<changectx %s>" % str(self)
37 37
38 38 def __eq__(self, other):
39 39 return self._rev == other._rev
@@ -41,26 +41,25 b' class changectx(object):'
41 41 def __nonzero__(self):
42 42 return self._rev != -1
43 43
44 def changeset(self):
45 try:
46 return self._changeset
47 except AttributeError:
44 def __getattr__(self, name):
45 if name == '_changeset':
48 46 self._changeset = self._repo.changelog.read(self.node())
49 47 return self._changeset
50
51 def manifest(self):
52 try:
48 elif name == '_manifest':
49 self._manifest = self._repo.manifest.read(self._changeset[0])
53 50 return self._manifest
54 except AttributeError:
55 self._manifest = self._repo.manifest.read(self.changeset()[0])
56 return self._manifest
51 else:
52 raise AttributeError, name
53
54 def changeset(self): return self._changeset
55 def manifest(self): return self._manifest
57 56
58 57 def rev(self): return self._rev
59 58 def node(self): return self._node
60 def user(self): return self.changeset()[1]
61 def date(self): return self.changeset()[2]
62 def files(self): return self.changeset()[3]
63 def description(self): return self.changeset()[4]
59 def user(self): return self._changeset[1]
60 def date(self): return self._changeset[2]
61 def files(self): return self._changeset[3]
62 def description(self): return self._changeset[4]
64 63
65 64 def parents(self):
66 65 """return contexts for each parent changeset"""
@@ -73,17 +72,16 b' class changectx(object):'
73 72 return [ changectx(self._repo, x) for x in c ]
74 73
75 74 def filenode(self, path):
76 node, flag = self._repo.manifest.find(self.changeset()[0], path)
75 if hasattr(self, "_manifest"):
76 return self._manifest[path]
77 node, flag = self._repo.manifest.find(self._changeset[0], path)
77 78 return node
78 79
79 80 def filectx(self, path, fileid=None):
80 81 """get a file context from this changeset"""
81 82 if fileid is None:
82 83 fileid = self.filenode(path)
83 if not fileid:
84 raise repo.LookupError(_("'%s' does not exist in changeset %s") %
85 (path, hex(self.node())))
86 return filectx(self._repo, path, fileid=fileid)
84 return filectx(self._repo, path, fileid=fileid, changectx=self)
87 85
88 86 def filectxs(self):
89 87 """generate a file context for each file in this changeset's
@@ -104,34 +102,44 b' class changectx(object):'
104 102 class filectx(object):
105 103 """A filecontext object makes access to data related to a particular
106 104 filerevision convenient."""
107 def __init__(self, repo_, path, changeid=None, fileid=None, filelog=None):
105 def __init__(self, repo, path, changeid=None, fileid=None,
106 filelog=None, changectx=None):
108 107 """changeid can be a changeset revision, node, or tag.
109 108 fileid can be a file revision or node."""
110 self._repo = repo_
109 self._repo = repo
111 110 self._path = path
112 111
113 112 assert changeid is not None or fileid is not None
114 113
115 114 if filelog:
116 115 self._filelog = filelog
117 else:
118 self._filelog = self._repo.file(self._path)
116 if changectx:
117 self._changectx = changectx
118 self._changeid = changectx.node()
119 119
120 120 if fileid is None:
121 121 self._changeid = changeid
122 122 else:
123 try:
124 self._filenode = self._filelog.lookup(fileid)
125 except revlog.RevlogError, inst:
126 raise repo.LookupError(str(inst))
127 self._changeid = self._filelog.linkrev(self._filenode)
123 self._fileid = fileid
128 124
129 125 def __getattr__(self, name):
130 126 if name == '_changectx':
131 127 self._changectx = changectx(self._repo, self._changeid)
132 128 return self._changectx
129 elif name == '_filelog':
130 self._filelog = self._repo.file(self._path)
131 return self._filelog
132 elif name == '_changeid':
133 self._changeid = self._filelog.linkrev(self._filenode)
134 return self._changeid
133 135 elif name == '_filenode':
134 self._filenode = self._changectx.filenode(self._path)
136 try:
137 if hasattr(self, "_fileid"):
138 self._filenode = self._filelog.lookup(self._fileid)
139 else:
140 self._filenode = self._changectx.filenode(self._path)
141 except revlog.RevlogError, inst:
142 raise repo.LookupError(str(inst))
135 143 return self._filenode
136 144 elif name == '_filerev':
137 145 self._filerev = self._filelog.rev(self._filenode)
@@ -146,7 +154,7 b' class filectx(object):'
146 154 return "%s@%s" % (self.path(), short(self.node()))
147 155
148 156 def __repr__(self):
149 return "<filectx %s@%s>" % (self.path(), short(self.node()))
157 return "<filectx %s>" % str(self)
150 158
151 159 def __eq__(self, other):
152 160 return self._path == other._path and self._changeid == other._changeid
@@ -218,7 +226,10 b' class filectx(object):'
218 226 def parents(f):
219 227 # we want to reuse filectx objects as much as possible
220 228 p = f._path
221 pl = [ (p, r) for r in f._filelog.parentrevs(f._filerev) ]
229 if f._filerev is None: # working dir
230 pl = [ (n.path(), n.filerev()) for n in f.parents() ]
231 else:
232 pl = [ (p, n) for n in f._filelog.parentrevs(f._filerev) ]
222 233
223 234 if follow:
224 235 r = f.renamed()
@@ -271,6 +282,13 b' class filectx(object):'
271 282 """
272 283
273 284 acache = {}
285
286 # prime the ancestor cache for the working directory
287 for c in (self, fc2):
288 if c._filerev == None:
289 pl = [ (n.path(), n.filenode()) for n in c.parents() ]
290 acache[(c._path, None)] = pl
291
274 292 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
275 293 def parents(vertex):
276 294 if vertex in acache:
@@ -293,3 +311,149 b' class filectx(object):'
293 311 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
294 312
295 313 return None
314
315 class workingctx(changectx):
316 """A workingctx object makes access to data related to
317 the current working directory convenient."""
318 def __init__(self, repo):
319 self._repo = repo
320 self._rev = None
321 self._node = None
322
323 def __str__(self):
324 return "."
325
326 def __nonzero__(self):
327 return True
328
329 def __getattr__(self, name):
330 if name == '_parents':
331 self._parents = self._repo.parents()
332 return self._parents
333 if name == '_status':
334 self._status = self._repo.status()
335 return self._status
336 if name == '_manifest':
337 self._buildmanifest()
338 return self._manifest
339 else:
340 raise AttributeError, name
341
342 def _buildmanifest(self):
343 """generate a manifest corresponding to the working directory"""
344
345 man = self._parents[0].manifest().copy()
346 copied = self._repo.dirstate.copies()
347 modified, added, removed, deleted, unknown = self._status[:5]
348 for i,l in (("a", added), ("m", modified), ("u", unknown)):
349 for f in l:
350 man[f] = man.get(copied.get(f, f), nullid) + i
351 man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f)))
352
353 for f in deleted + removed:
354 del man[f]
355
356 self._manifest = man
357
358 def manifest(self): return self._manifest
359
360 def user(self): return self._repo.ui.username()
361 def date(self): return util.makedate()
362 def description(self): return ""
363 def files(self):
364 f = self.modified() + self.added() + self.removed()
365 f.sort()
366 return f
367
368 def modified(self): return self._status[0]
369 def added(self): return self._status[1]
370 def removed(self): return self._status[2]
371 def deleted(self): return self._status[3]
372 def unknown(self): return self._status[4]
373 def clean(self): return self._status[5]
374
375 def parents(self):
376 """return contexts for each parent changeset"""
377 return self._parents
378
379 def children(self):
380 return []
381
382 def filectx(self, path):
383 """get a file context from the working directory"""
384 return workingfilectx(self._repo, path, workingctx=self)
385
386 def ancestor(self, c2):
387 """return the ancestor context of self and c2"""
388 return self._parents[0].ancestor(c2) # punt on two parents for now
389
390 class workingfilectx(filectx):
391 """A workingfilectx object makes access to data related to a particular
392 file in the working directory convenient."""
393 def __init__(self, repo, path, filelog=None, workingctx=None):
394 """changeid can be a changeset revision, node, or tag.
395 fileid can be a file revision or node."""
396 self._repo = repo
397 self._path = path
398 self._changeid = None
399 self._filerev = self._filenode = None
400
401 if filelog:
402 self._filelog = filelog
403 if workingctx:
404 self._changectx = workingctx
405
406 def __getattr__(self, name):
407 if name == '_changectx':
408 self._changectx = workingctx(repo)
409 return self._changectx
410 elif name == '_repopath':
411 self._repopath = self._repo.dirstate.copied(p) or self._path
412 elif name == '_filelog':
413 self._filelog = self._repo.file(self._repopath)
414 return self._filelog
415 else:
416 raise AttributeError, name
417
418 def __nonzero__(self):
419 return True
420
421 def __str__(self):
422 return "%s@." % self.path()
423
424 def filectx(self, fileid):
425 '''opens an arbitrary revision of the file without
426 opening a new filelog'''
427 return filectx(self._repo, self._repopath, fileid=fileid,
428 filelog=self._filelog)
429
430 def rev(self):
431 if hasattr(self, "_changectx"):
432 return self._changectx.rev()
433 return self._filelog.linkrev(self._filenode)
434
435 def data(self): return self._repo.wread(self._path)
436 def renamed(self):
437 rp = self._repopath
438 if rp == self._path:
439 return None
440 return rp, self._workingctx._parents._manifest.get(rp, nullid)
441
442 def parents(self):
443 '''return parent filectxs, following copies if necessary'''
444 p = self._path
445 rp = self._repopath
446 pcl = self._workingctx._parents
447 fl = self._filelog
448 pl = [ (rp, pcl[0]._manifest.get(rp, nullid), fl) ]
449 if len(pcl) > 1:
450 if rp != p:
451 fl = None
452 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
453
454 return [ filectx(self._repo, p, fileid=n, filelog=l)
455 for p,n,l in pl if n != nullid ]
456
457 def children(self):
458 return []
459
@@ -321,6 +321,9 b' class localrepository(repo.repository):'
321 321 def changectx(self, changeid=None):
322 322 return context.changectx(self, changeid)
323 323
324 def workingctx(self):
325 return context.workingctx(self)
326
324 327 def parents(self, changeid=None):
325 328 '''
326 329 get list of changectxs for parents of changeid or working directory
@@ -10,72 +10,66 b' from i18n import gettext as _'
10 10 from demandload import *
11 11 demandload(globals(), "errno util os tempfile")
12 12
13 def merge3(repo, fn, my, other, p1, p2):
14 """perform a 3-way merge in the working directory"""
13 def filemerge(repo, fw, fo, fd, my, other, p1, p2, move):
14 """perform a 3-way merge in the working directory
15 15
16 def temp(prefix, node):
17 pre = "%s~%s." % (os.path.basename(fn), prefix)
16 fw = filename in the working directory and first parent
17 fo = filename in other parent
18 fd = destination filename
19 my = fileid in first parent
20 other = fileid in second parent
21 p1, p2 = hex changeset ids for merge command
22 move = whether to move or copy the file to the destination
23
24 TODO:
25 if fw is copied in the working directory, we get confused
26 implement move and fd
27 """
28
29 def temp(prefix, ctx):
30 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
18 31 (fd, name) = tempfile.mkstemp(prefix=pre)
19 32 f = os.fdopen(fd, "wb")
20 repo.wwrite(fn, fl.read(node), f)
33 repo.wwrite(ctx.path(), ctx.data(), f)
21 34 f.close()
22 35 return name
23 36
24 fl = repo.file(fn)
25 base = fl.ancestor(my, other)
26 a = repo.wjoin(fn)
27 b = temp("base", base)
28 c = temp("other", other)
37 fcm = repo.filectx(fw, fileid=my)
38 fco = repo.filectx(fo, fileid=other)
39 fca = fcm.ancestor(fco)
40 if not fca:
41 fca = repo.filectx(fw, fileid=-1)
42 a = repo.wjoin(fw)
43 b = temp("base", fca)
44 c = temp("other", fco)
29 45
30 repo.ui.note(_("resolving %s\n") % fn)
31 repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
32 (fn, short(my), short(other), short(base)))
46 repo.ui.note(_("resolving %s\n") % fw)
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
33 48
34 49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
35 50 or "hgmerge")
36 51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
37 environ={'HG_FILE': fn,
52 environ={'HG_FILE': fw,
38 53 'HG_MY_NODE': p1,
39 'HG_OTHER_NODE': p2,
40 'HG_FILE_MY_NODE': hex(my),
41 'HG_FILE_OTHER_NODE': hex(other),
42 'HG_FILE_BASE_NODE': hex(base)})
54 'HG_OTHER_NODE': p2})
43 55 if r:
44 repo.ui.warn(_("merging %s failed!\n") % fn)
56 repo.ui.warn(_("merging %s failed!\n") % fw)
45 57
46 58 os.unlink(b)
47 59 os.unlink(c)
48 60 return r
49 61
50 def checkunknown(repo, m2, status):
62 def checkunknown(repo, m2, wctx):
51 63 """
52 64 check for collisions between unknown files and files in m2
53 65 """
54 modified, added, removed, deleted, unknown = status[:5]
55 for f in unknown:
66 for f in wctx.unknown():
56 67 if f in m2:
57 68 if repo.file(f).cmp(m2[f], repo.wread(f)):
58 69 raise util.Abort(_("'%s' already exists in the working"
59 70 " dir and differs from remote") % f)
60 71
61 def workingmanifest(repo, man, status):
62 """
63 Update manifest to correspond to the working directory
64 """
65
66 copied = repo.dirstate.copies()
67 modified, added, removed, deleted, unknown = status[:5]
68 for i,l in (("a", added), ("m", modified), ("u", unknown)):
69 for f in l:
70 man[f] = man.get(copied.get(f, f), nullid) + i
71 man.set(f, util.is_exec(repo.wjoin(f), man.execf(f)))
72
73 for f in deleted + removed:
74 del man[f]
75
76 return man
77
78 def forgetremoved(m2, status):
72 def forgetremoved(m2, wctx):
79 73 """
80 74 Forget removed files
81 75
@@ -86,10 +80,9 b' def forgetremoved(m2, status):'
86 80 manifest.
87 81 """
88 82
89 modified, added, removed, deleted, unknown = status[:5]
90 83 action = []
91 84
92 for f in deleted + removed:
85 for f in wctx.deleted() + wctx.removed():
93 86 if f not in m2:
94 87 action.append((f, "f"))
95 88
@@ -261,7 +254,7 b' def applyupdates(repo, action, xp1, xp2)'
261 254 elif m == "m": # merge
262 255 flag, my, other = a[2:]
263 256 repo.ui.status(_("merging %s\n") % f)
264 if merge3(repo, f, my, other, xp1, xp2):
257 if filemerge(repo, f, f, f, my, other, xp1, xp2, False):
265 258 unresolved += 1
266 259 util.set_exec(repo.wjoin(f), flag)
267 260 merged += 1
@@ -320,7 +313,8 b' def update(repo, node, branchmerge=False'
320 313
321 314 ### check phase
322 315
323 pl = repo.parents()
316 wc = repo.workingctx()
317 pl = wc.parents()
324 318 if not overwrite and len(pl) > 1:
325 319 raise util.Abort(_("outstanding uncommitted merges"))
326 320
@@ -339,13 +333,11 b' def update(repo, node, branchmerge=False'
339 333 raise util.Abort(_("update spans branches, use 'hg merge' "
340 334 "or 'hg update -C' to lose changes"))
341 335
342 status = repo.status()
343 modified, added, removed, deleted, unknown = status[:5]
344 336 if branchmerge and not forcemerge:
345 if modified or added or removed:
337 if wc.modified() or wc.added() or wc.removed():
346 338 raise util.Abort(_("outstanding uncommitted changes"))
347 339
348 m1 = p1.manifest().copy()
340 m1 = wc.manifest().copy()
349 341 m2 = p2.manifest().copy()
350 342 ma = pa.manifest()
351 343
@@ -359,14 +351,13 b' def update(repo, node, branchmerge=False'
359 351 action = []
360 352 copy = {}
361 353
362 m1 = workingmanifest(repo, m1, status)
363 354 filtermanifest(m1, partial)
364 355 filtermanifest(m2, partial)
365 356
366 357 if not force:
367 checkunknown(repo, m2, status)
358 checkunknown(repo, m2, wc)
368 359 if not branchmerge:
369 action += forgetremoved(m2, status)
360 action += forgetremoved(m2, wc)
370 361 if not (backwards or overwrite):
371 362 copy = findcopies(repo, m1, m2, pa.rev())
372 363
@@ -27,7 +27,7 b' resolving manifests'
27 27 test.txt: versions differ -> m
28 28 merging test.txt
29 29 resolving test.txt
30 file test.txt: my fc3148072371 other d40249267ae3 ancestor 8fe46a3eb557
30 my test.txt@451c744aabcc other test.txt@a070d41e8360 ancestor test.txt@faaea63e63a9
31 31 merging test.txt failed!
32 32 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
33 33 There are unresolved merges, you can redo the full merge using:
@@ -21,7 +21,7 b' resolving manifests'
21 21 b: remote created -> g
22 22 merging a
23 23 resolving a
24 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
24 my a@33aaa84a386b other a@802f095af299 ancestor a@33aaa84a386b
25 25 getting b
26 26 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
27 27 changeset: 1:802f095af299
@@ -55,7 +55,7 b' resolving manifests'
55 55 b: remote created -> g
56 56 merging a
57 57 resolving a
58 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
58 my a@33aaa84a386b other a@802f095af299 ancestor a@33aaa84a386b
59 59 getting b
60 60 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
61 61 changeset: 1:802f095af299
@@ -106,10 +106,10 b' resolving manifests'
106 106 b: versions differ -> m
107 107 merging a
108 108 resolving a
109 file a: my d730145abbf9 other 13e0d5f949fa ancestor b789fdd96dc2
109 my a@802f095af299 other a@030602aee63d ancestor a@33aaa84a386b
110 110 merging b
111 111 resolving b
112 file b: my 1e88685f5dde other 61de8c7723ca ancestor 000000000000
112 my b@802f095af299 other b@030602aee63d ancestor b@000000000000
113 113 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
114 114 (branch merge, don't forget to commit)
115 115 changeset: 1:802f095af299
General Comments 0
You need to be logged in to leave comments. Login now