##// 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 return short(self.node())
33 return short(self.node())
34
34
35 def __repr__(self):
35 def __repr__(self):
36 return "<changectx %s>" % short(self.node())
36 return "<changectx %s>" % str(self)
37
37
38 def __eq__(self, other):
38 def __eq__(self, other):
39 return self._rev == other._rev
39 return self._rev == other._rev
@@ -41,26 +41,25 b' class changectx(object):'
41 def __nonzero__(self):
41 def __nonzero__(self):
42 return self._rev != -1
42 return self._rev != -1
43
43
44 def changeset(self):
44 def __getattr__(self, name):
45 try:
45 if name == '_changeset':
46 return self._changeset
47 except AttributeError:
48 self._changeset = self._repo.changelog.read(self.node())
46 self._changeset = self._repo.changelog.read(self.node())
49 return self._changeset
47 return self._changeset
50
48 elif name == '_manifest':
51 def manifest(self):
49 self._manifest = self._repo.manifest.read(self._changeset[0])
52 try:
53 return self._manifest
50 return self._manifest
54 except AttributeError:
51 else:
55 self._manifest = self._repo.manifest.read(self.changeset()[0])
52 raise AttributeError, name
56 return self._manifest
53
54 def changeset(self): return self._changeset
55 def manifest(self): return self._manifest
57
56
58 def rev(self): return self._rev
57 def rev(self): return self._rev
59 def node(self): return self._node
58 def node(self): return self._node
60 def user(self): return self.changeset()[1]
59 def user(self): return self._changeset[1]
61 def date(self): return self.changeset()[2]
60 def date(self): return self._changeset[2]
62 def files(self): return self.changeset()[3]
61 def files(self): return self._changeset[3]
63 def description(self): return self.changeset()[4]
62 def description(self): return self._changeset[4]
64
63
65 def parents(self):
64 def parents(self):
66 """return contexts for each parent changeset"""
65 """return contexts for each parent changeset"""
@@ -73,17 +72,16 b' class changectx(object):'
73 return [ changectx(self._repo, x) for x in c ]
72 return [ changectx(self._repo, x) for x in c ]
74
73
75 def filenode(self, path):
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 return node
78 return node
78
79
79 def filectx(self, path, fileid=None):
80 def filectx(self, path, fileid=None):
80 """get a file context from this changeset"""
81 """get a file context from this changeset"""
81 if fileid is None:
82 if fileid is None:
82 fileid = self.filenode(path)
83 fileid = self.filenode(path)
83 if not fileid:
84 return filectx(self._repo, path, fileid=fileid, changectx=self)
84 raise repo.LookupError(_("'%s' does not exist in changeset %s") %
85 (path, hex(self.node())))
86 return filectx(self._repo, path, fileid=fileid)
87
85
88 def filectxs(self):
86 def filectxs(self):
89 """generate a file context for each file in this changeset's
87 """generate a file context for each file in this changeset's
@@ -104,34 +102,44 b' class changectx(object):'
104 class filectx(object):
102 class filectx(object):
105 """A filecontext object makes access to data related to a particular
103 """A filecontext object makes access to data related to a particular
106 filerevision convenient."""
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 """changeid can be a changeset revision, node, or tag.
107 """changeid can be a changeset revision, node, or tag.
109 fileid can be a file revision or node."""
108 fileid can be a file revision or node."""
110 self._repo = repo_
109 self._repo = repo
111 self._path = path
110 self._path = path
112
111
113 assert changeid is not None or fileid is not None
112 assert changeid is not None or fileid is not None
114
113
115 if filelog:
114 if filelog:
116 self._filelog = filelog
115 self._filelog = filelog
117 else:
116 if changectx:
118 self._filelog = self._repo.file(self._path)
117 self._changectx = changectx
118 self._changeid = changectx.node()
119
119
120 if fileid is None:
120 if fileid is None:
121 self._changeid = changeid
121 self._changeid = changeid
122 else:
122 else:
123 try:
123 self._fileid = fileid
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)
128
124
129 def __getattr__(self, name):
125 def __getattr__(self, name):
130 if name == '_changectx':
126 if name == '_changectx':
131 self._changectx = changectx(self._repo, self._changeid)
127 self._changectx = changectx(self._repo, self._changeid)
132 return self._changectx
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 elif name == '_filenode':
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 return self._filenode
143 return self._filenode
136 elif name == '_filerev':
144 elif name == '_filerev':
137 self._filerev = self._filelog.rev(self._filenode)
145 self._filerev = self._filelog.rev(self._filenode)
@@ -146,7 +154,7 b' class filectx(object):'
146 return "%s@%s" % (self.path(), short(self.node()))
154 return "%s@%s" % (self.path(), short(self.node()))
147
155
148 def __repr__(self):
156 def __repr__(self):
149 return "<filectx %s@%s>" % (self.path(), short(self.node()))
157 return "<filectx %s>" % str(self)
150
158
151 def __eq__(self, other):
159 def __eq__(self, other):
152 return self._path == other._path and self._changeid == other._changeid
160 return self._path == other._path and self._changeid == other._changeid
@@ -218,7 +226,10 b' class filectx(object):'
218 def parents(f):
226 def parents(f):
219 # we want to reuse filectx objects as much as possible
227 # we want to reuse filectx objects as much as possible
220 p = f._path
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 if follow:
234 if follow:
224 r = f.renamed()
235 r = f.renamed()
@@ -271,6 +282,13 b' class filectx(object):'
271 """
282 """
272
283
273 acache = {}
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 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
292 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
275 def parents(vertex):
293 def parents(vertex):
276 if vertex in acache:
294 if vertex in acache:
@@ -293,3 +311,149 b' class filectx(object):'
293 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
311 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
294
312
295 return None
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 def changectx(self, changeid=None):
321 def changectx(self, changeid=None):
322 return context.changectx(self, changeid)
322 return context.changectx(self, changeid)
323
323
324 def workingctx(self):
325 return context.workingctx(self)
326
324 def parents(self, changeid=None):
327 def parents(self, changeid=None):
325 '''
328 '''
326 get list of changectxs for parents of changeid or working directory
329 get list of changectxs for parents of changeid or working directory
@@ -10,72 +10,66 b' from i18n import gettext as _'
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "errno util os tempfile")
11 demandload(globals(), "errno util os tempfile")
12
12
13 def merge3(repo, fn, my, other, p1, p2):
13 def filemerge(repo, fw, fo, fd, my, other, p1, p2, move):
14 """perform a 3-way merge in the working directory"""
14 """perform a 3-way merge in the working directory
15
15
16 def temp(prefix, node):
16 fw = filename in the working directory and first parent
17 pre = "%s~%s." % (os.path.basename(fn), prefix)
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 (fd, name) = tempfile.mkstemp(prefix=pre)
31 (fd, name) = tempfile.mkstemp(prefix=pre)
19 f = os.fdopen(fd, "wb")
32 f = os.fdopen(fd, "wb")
20 repo.wwrite(fn, fl.read(node), f)
33 repo.wwrite(ctx.path(), ctx.data(), f)
21 f.close()
34 f.close()
22 return name
35 return name
23
36
24 fl = repo.file(fn)
37 fcm = repo.filectx(fw, fileid=my)
25 base = fl.ancestor(my, other)
38 fco = repo.filectx(fo, fileid=other)
26 a = repo.wjoin(fn)
39 fca = fcm.ancestor(fco)
27 b = temp("base", base)
40 if not fca:
28 c = temp("other", other)
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)
46 repo.ui.note(_("resolving %s\n") % fw)
31 repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
32 (fn, short(my), short(other), short(base)))
33
48
34 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
35 or "hgmerge")
50 or "hgmerge")
36 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
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 'HG_MY_NODE': p1,
53 'HG_MY_NODE': p1,
39 'HG_OTHER_NODE': p2,
54 '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)})
43 if r:
55 if r:
44 repo.ui.warn(_("merging %s failed!\n") % fn)
56 repo.ui.warn(_("merging %s failed!\n") % fw)
45
57
46 os.unlink(b)
58 os.unlink(b)
47 os.unlink(c)
59 os.unlink(c)
48 return r
60 return r
49
61
50 def checkunknown(repo, m2, status):
62 def checkunknown(repo, m2, wctx):
51 """
63 """
52 check for collisions between unknown files and files in m2
64 check for collisions between unknown files and files in m2
53 """
65 """
54 modified, added, removed, deleted, unknown = status[:5]
66 for f in wctx.unknown():
55 for f in unknown:
56 if f in m2:
67 if f in m2:
57 if repo.file(f).cmp(m2[f], repo.wread(f)):
68 if repo.file(f).cmp(m2[f], repo.wread(f)):
58 raise util.Abort(_("'%s' already exists in the working"
69 raise util.Abort(_("'%s' already exists in the working"
59 " dir and differs from remote") % f)
70 " dir and differs from remote") % f)
60
71
61 def workingmanifest(repo, man, status):
72 def forgetremoved(m2, wctx):
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):
79 """
73 """
80 Forget removed files
74 Forget removed files
81
75
@@ -86,10 +80,9 b' def forgetremoved(m2, status):'
86 manifest.
80 manifest.
87 """
81 """
88
82
89 modified, added, removed, deleted, unknown = status[:5]
90 action = []
83 action = []
91
84
92 for f in deleted + removed:
85 for f in wctx.deleted() + wctx.removed():
93 if f not in m2:
86 if f not in m2:
94 action.append((f, "f"))
87 action.append((f, "f"))
95
88
@@ -261,7 +254,7 b' def applyupdates(repo, action, xp1, xp2)'
261 elif m == "m": # merge
254 elif m == "m": # merge
262 flag, my, other = a[2:]
255 flag, my, other = a[2:]
263 repo.ui.status(_("merging %s\n") % f)
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 unresolved += 1
258 unresolved += 1
266 util.set_exec(repo.wjoin(f), flag)
259 util.set_exec(repo.wjoin(f), flag)
267 merged += 1
260 merged += 1
@@ -320,7 +313,8 b' def update(repo, node, branchmerge=False'
320
313
321 ### check phase
314 ### check phase
322
315
323 pl = repo.parents()
316 wc = repo.workingctx()
317 pl = wc.parents()
324 if not overwrite and len(pl) > 1:
318 if not overwrite and len(pl) > 1:
325 raise util.Abort(_("outstanding uncommitted merges"))
319 raise util.Abort(_("outstanding uncommitted merges"))
326
320
@@ -339,13 +333,11 b' def update(repo, node, branchmerge=False'
339 raise util.Abort(_("update spans branches, use 'hg merge' "
333 raise util.Abort(_("update spans branches, use 'hg merge' "
340 "or 'hg update -C' to lose changes"))
334 "or 'hg update -C' to lose changes"))
341
335
342 status = repo.status()
343 modified, added, removed, deleted, unknown = status[:5]
344 if branchmerge and not forcemerge:
336 if branchmerge and not forcemerge:
345 if modified or added or removed:
337 if wc.modified() or wc.added() or wc.removed():
346 raise util.Abort(_("outstanding uncommitted changes"))
338 raise util.Abort(_("outstanding uncommitted changes"))
347
339
348 m1 = p1.manifest().copy()
340 m1 = wc.manifest().copy()
349 m2 = p2.manifest().copy()
341 m2 = p2.manifest().copy()
350 ma = pa.manifest()
342 ma = pa.manifest()
351
343
@@ -359,14 +351,13 b' def update(repo, node, branchmerge=False'
359 action = []
351 action = []
360 copy = {}
352 copy = {}
361
353
362 m1 = workingmanifest(repo, m1, status)
363 filtermanifest(m1, partial)
354 filtermanifest(m1, partial)
364 filtermanifest(m2, partial)
355 filtermanifest(m2, partial)
365
356
366 if not force:
357 if not force:
367 checkunknown(repo, m2, status)
358 checkunknown(repo, m2, wc)
368 if not branchmerge:
359 if not branchmerge:
369 action += forgetremoved(m2, status)
360 action += forgetremoved(m2, wc)
370 if not (backwards or overwrite):
361 if not (backwards or overwrite):
371 copy = findcopies(repo, m1, m2, pa.rev())
362 copy = findcopies(repo, m1, m2, pa.rev())
372
363
@@ -27,7 +27,7 b' resolving manifests'
27 test.txt: versions differ -> m
27 test.txt: versions differ -> m
28 merging test.txt
28 merging test.txt
29 resolving test.txt
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 merging test.txt failed!
31 merging test.txt failed!
32 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
32 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
33 There are unresolved merges, you can redo the full merge using:
33 There are unresolved merges, you can redo the full merge using:
@@ -21,7 +21,7 b' resolving manifests'
21 b: remote created -> g
21 b: remote created -> g
22 merging a
22 merging a
23 resolving a
23 resolving a
24 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
24 my a@33aaa84a386b other a@802f095af299 ancestor a@33aaa84a386b
25 getting b
25 getting b
26 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
26 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
27 changeset: 1:802f095af299
27 changeset: 1:802f095af299
@@ -55,7 +55,7 b' resolving manifests'
55 b: remote created -> g
55 b: remote created -> g
56 merging a
56 merging a
57 resolving a
57 resolving a
58 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
58 my a@33aaa84a386b other a@802f095af299 ancestor a@33aaa84a386b
59 getting b
59 getting b
60 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
60 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
61 changeset: 1:802f095af299
61 changeset: 1:802f095af299
@@ -106,10 +106,10 b' resolving manifests'
106 b: versions differ -> m
106 b: versions differ -> m
107 merging a
107 merging a
108 resolving a
108 resolving a
109 file a: my d730145abbf9 other 13e0d5f949fa ancestor b789fdd96dc2
109 my a@802f095af299 other a@030602aee63d ancestor a@33aaa84a386b
110 merging b
110 merging b
111 resolving b
111 resolving b
112 file b: my 1e88685f5dde other 61de8c7723ca ancestor 000000000000
112 my b@802f095af299 other b@030602aee63d ancestor b@000000000000
113 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
113 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
114 (branch merge, don't forget to commit)
114 (branch merge, don't forget to commit)
115 changeset: 1:802f095af299
115 changeset: 1:802f095af299
General Comments 0
You need to be logged in to leave comments. Login now