##// END OF EJS Templates
merge with main
Thomas Arendsen Hein -
r4893:44b00315 merge default
parent child Browse files
Show More
@@ -1,543 +1,543
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import ancestor, bdiff, repo, revlog, util, os, errno
11 11
12 12 class changectx(object):
13 13 """A changecontext object makes access to data related to a particular
14 14 changeset convenient."""
15 15 def __init__(self, repo, changeid=None):
16 16 """changeid is a revision number, node, or tag"""
17 17 self._repo = repo
18 18
19 19 if not changeid and changeid != 0:
20 20 p1, p2 = self._repo.dirstate.parents()
21 21 self._rev = self._repo.changelog.rev(p1)
22 22 if self._rev == -1:
23 23 changeid = 'tip'
24 24 else:
25 25 self._node = p1
26 26 return
27 27
28 28 self._node = self._repo.lookup(changeid)
29 29 self._rev = self._repo.changelog.rev(self._node)
30 30
31 31 def __str__(self):
32 32 return short(self.node())
33 33
34 34 def __repr__(self):
35 35 return "<changectx %s>" % str(self)
36 36
37 37 def __eq__(self, other):
38 38 try:
39 39 return self._rev == other._rev
40 40 except AttributeError:
41 41 return False
42 42
43 43 def __ne__(self, other):
44 44 return not (self == other)
45 45
46 46 def __nonzero__(self):
47 47 return self._rev != nullrev
48 48
49 49 def __getattr__(self, name):
50 50 if name == '_changeset':
51 51 self._changeset = self._repo.changelog.read(self.node())
52 52 return self._changeset
53 53 elif name == '_manifest':
54 54 self._manifest = self._repo.manifest.read(self._changeset[0])
55 55 return self._manifest
56 56 elif name == '_manifestdelta':
57 57 md = self._repo.manifest.readdelta(self._changeset[0])
58 58 self._manifestdelta = md
59 59 return self._manifestdelta
60 60 else:
61 61 raise AttributeError, name
62 62
63 63 def changeset(self): return self._changeset
64 64 def manifest(self): return self._manifest
65 65
66 66 def rev(self): return self._rev
67 67 def node(self): return self._node
68 68 def user(self): return self._changeset[1]
69 69 def date(self): return self._changeset[2]
70 70 def files(self): return self._changeset[3]
71 71 def description(self): return self._changeset[4]
72 72 def branch(self): return self._changeset[5].get("branch")
73 73 def tags(self): return self._repo.nodetags(self._node)
74 74
75 75 def parents(self):
76 76 """return contexts for each parent changeset"""
77 77 p = self._repo.changelog.parents(self._node)
78 78 return [changectx(self._repo, x) for x in p]
79 79
80 80 def children(self):
81 81 """return contexts for each child changeset"""
82 82 c = self._repo.changelog.children(self._node)
83 83 return [changectx(self._repo, x) for x in c]
84 84
85 85 def filenode(self, path):
86 86 if '_manifest' in self.__dict__:
87 87 try:
88 88 return self._manifest[path]
89 89 except KeyError:
90 90 raise revlog.LookupError(_("'%s' not found in manifest") % path)
91 91 if '_manifestdelta' in self.__dict__ or path in self.files():
92 92 if path in self._manifestdelta:
93 93 return self._manifestdelta[path]
94 94 node, flag = self._repo.manifest.find(self._changeset[0], path)
95 95 if not node:
96 96 raise revlog.LookupError(_("'%s' not found in manifest") % path)
97 97
98 98 return node
99 99
100 100 def filectx(self, path, fileid=None, filelog=None):
101 101 """get a file context from this changeset"""
102 102 if fileid is None:
103 103 fileid = self.filenode(path)
104 104 return filectx(self._repo, path, fileid=fileid,
105 105 changectx=self, filelog=filelog)
106 106
107 107 def filectxs(self):
108 108 """generate a file context for each file in this changeset's
109 109 manifest"""
110 110 mf = self.manifest()
111 111 m = mf.keys()
112 112 m.sort()
113 113 for f in m:
114 114 yield self.filectx(f, fileid=mf[f])
115 115
116 116 def ancestor(self, c2):
117 117 """
118 118 return the ancestor context of self and c2
119 119 """
120 120 n = self._repo.changelog.ancestor(self._node, c2._node)
121 121 return changectx(self._repo, n)
122 122
123 123 class filectx(object):
124 124 """A filecontext object makes access to data related to a particular
125 125 filerevision convenient."""
126 126 def __init__(self, repo, path, changeid=None, fileid=None,
127 127 filelog=None, changectx=None):
128 128 """changeid can be a changeset revision, node, or tag.
129 129 fileid can be a file revision or node."""
130 130 self._repo = repo
131 131 self._path = path
132 132
133 133 assert (changeid is not None
134 134 or fileid is not None
135 135 or changectx is not None)
136 136
137 137 if filelog:
138 138 self._filelog = filelog
139 139
140 140 if fileid is None:
141 141 if changectx is None:
142 142 self._changeid = changeid
143 143 else:
144 144 self._changectx = changectx
145 145 else:
146 146 self._fileid = fileid
147 147
148 148 def __getattr__(self, name):
149 149 if name == '_changectx':
150 150 self._changectx = changectx(self._repo, self._changeid)
151 151 return self._changectx
152 152 elif name == '_filelog':
153 153 self._filelog = self._repo.file(self._path)
154 154 return self._filelog
155 155 elif name == '_changeid':
156 156 self._changeid = self._filelog.linkrev(self._filenode)
157 157 return self._changeid
158 158 elif name == '_filenode':
159 159 if '_fileid' in self.__dict__:
160 160 self._filenode = self._filelog.lookup(self._fileid)
161 161 else:
162 162 self._filenode = self._changectx.filenode(self._path)
163 163 return self._filenode
164 164 elif name == '_filerev':
165 165 self._filerev = self._filelog.rev(self._filenode)
166 166 return self._filerev
167 167 else:
168 168 raise AttributeError, name
169 169
170 170 def __nonzero__(self):
171 171 try:
172 172 n = self._filenode
173 173 return True
174 174 except revlog.LookupError:
175 175 # file is missing
176 176 return False
177 177
178 178 def __str__(self):
179 179 return "%s@%s" % (self.path(), short(self.node()))
180 180
181 181 def __repr__(self):
182 182 return "<filectx %s>" % str(self)
183 183
184 184 def __eq__(self, other):
185 185 try:
186 186 return (self._path == other._path
187 and self._changeid == other._changeid)
187 and self._fileid == other._fileid)
188 188 except AttributeError:
189 189 return False
190 190
191 191 def __ne__(self, other):
192 192 return not (self == other)
193 193
194 194 def filectx(self, fileid):
195 195 '''opens an arbitrary revision of the file without
196 196 opening a new filelog'''
197 197 return filectx(self._repo, self._path, fileid=fileid,
198 198 filelog=self._filelog)
199 199
200 200 def filerev(self): return self._filerev
201 201 def filenode(self): return self._filenode
202 202 def filelog(self): return self._filelog
203 203
204 204 def rev(self):
205 205 if '_changectx' in self.__dict__:
206 206 return self._changectx.rev()
207 207 return self._filelog.linkrev(self._filenode)
208 208
209 209 def node(self): return self._changectx.node()
210 210 def user(self): return self._changectx.user()
211 211 def date(self): return self._changectx.date()
212 212 def files(self): return self._changectx.files()
213 213 def description(self): return self._changectx.description()
214 214 def branch(self): return self._changectx.branch()
215 215 def manifest(self): return self._changectx.manifest()
216 216 def changectx(self): return self._changectx
217 217
218 218 def data(self): return self._filelog.read(self._filenode)
219 219 def renamed(self): return self._filelog.renamed(self._filenode)
220 220 def path(self): return self._path
221 221 def size(self): return self._filelog.size(self._filerev)
222 222
223 223 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
224 224
225 225 def parents(self):
226 226 p = self._path
227 227 fl = self._filelog
228 228 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
229 229
230 230 r = self.renamed()
231 231 if r:
232 232 pl[0] = (r[0], r[1], None)
233 233
234 234 return [filectx(self._repo, p, fileid=n, filelog=l)
235 235 for p,n,l in pl if n != nullid]
236 236
237 237 def children(self):
238 238 # hard for renames
239 239 c = self._filelog.children(self._filenode)
240 240 return [filectx(self._repo, self._path, fileid=x,
241 241 filelog=self._filelog) for x in c]
242 242
243 243 def annotate(self, follow=False, linenumber=None):
244 244 '''returns a list of tuples of (ctx, line) for each line
245 245 in the file, where ctx is the filectx of the node where
246 246 that line was last changed.
247 247 This returns tuples of ((ctx, linenumber), line) for each line,
248 248 if "linenumber" parameter is NOT "None".
249 249 In such tuples, linenumber means one at the first appearance
250 250 in the managed file.
251 251 To reduce annotation cost,
252 252 this returns fixed value(False is used) as linenumber,
253 253 if "linenumber" parameter is "False".'''
254 254
255 255 def decorate_compat(text, rev):
256 256 return ([rev] * len(text.splitlines()), text)
257 257
258 258 def without_linenumber(text, rev):
259 259 return ([(rev, False)] * len(text.splitlines()), text)
260 260
261 261 def with_linenumber(text, rev):
262 262 size = len(text.splitlines())
263 263 return ([(rev, i) for i in xrange(1, size + 1)], text)
264 264
265 265 decorate = (((linenumber is None) and decorate_compat) or
266 266 (linenumber and with_linenumber) or
267 267 without_linenumber)
268 268
269 269 def pair(parent, child):
270 270 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
271 271 child[0][b1:b2] = parent[0][a1:a2]
272 272 return child
273 273
274 274 getlog = util.cachefunc(lambda x: self._repo.file(x))
275 275 def getctx(path, fileid):
276 276 log = path == self._path and self._filelog or getlog(path)
277 277 return filectx(self._repo, path, fileid=fileid, filelog=log)
278 278 getctx = util.cachefunc(getctx)
279 279
280 280 def parents(f):
281 281 # we want to reuse filectx objects as much as possible
282 282 p = f._path
283 283 if f._filerev is None: # working dir
284 284 pl = [(n.path(), n.filerev()) for n in f.parents()]
285 285 else:
286 286 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
287 287
288 288 if follow:
289 289 r = f.renamed()
290 290 if r:
291 291 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
292 292
293 293 return [getctx(p, n) for p, n in pl if n != nullrev]
294 294
295 295 # use linkrev to find the first changeset where self appeared
296 296 if self.rev() != self._filelog.linkrev(self._filenode):
297 297 base = self.filectx(self.filerev())
298 298 else:
299 299 base = self
300 300
301 301 # find all ancestors
302 302 needed = {base: 1}
303 303 visit = [base]
304 304 files = [base._path]
305 305 while visit:
306 306 f = visit.pop(0)
307 307 for p in parents(f):
308 308 if p not in needed:
309 309 needed[p] = 1
310 310 visit.append(p)
311 311 if p._path not in files:
312 312 files.append(p._path)
313 313 else:
314 314 # count how many times we'll use this
315 315 needed[p] += 1
316 316
317 317 # sort by revision (per file) which is a topological order
318 318 visit = []
319 319 for f in files:
320 320 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
321 321 visit.extend(fn)
322 322 visit.sort()
323 323 hist = {}
324 324
325 325 for r, f in visit:
326 326 curr = decorate(f.data(), f)
327 327 for p in parents(f):
328 328 if p != nullid:
329 329 curr = pair(hist[p], curr)
330 330 # trim the history of unneeded revs
331 331 needed[p] -= 1
332 332 if not needed[p]:
333 333 del hist[p]
334 334 hist[f] = curr
335 335
336 336 return zip(hist[f][0], hist[f][1].splitlines(1))
337 337
338 338 def ancestor(self, fc2):
339 339 """
340 340 find the common ancestor file context, if any, of self, and fc2
341 341 """
342 342
343 343 acache = {}
344 344
345 345 # prime the ancestor cache for the working directory
346 346 for c in (self, fc2):
347 347 if c._filerev == None:
348 348 pl = [(n.path(), n.filenode()) for n in c.parents()]
349 349 acache[(c._path, None)] = pl
350 350
351 351 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
352 352 def parents(vertex):
353 353 if vertex in acache:
354 354 return acache[vertex]
355 355 f, n = vertex
356 356 if f not in flcache:
357 357 flcache[f] = self._repo.file(f)
358 358 fl = flcache[f]
359 359 pl = [(f, p) for p in fl.parents(n) if p != nullid]
360 360 re = fl.renamed(n)
361 361 if re:
362 362 pl.append(re)
363 363 acache[vertex] = pl
364 364 return pl
365 365
366 366 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
367 367 v = ancestor.ancestor(a, b, parents)
368 368 if v:
369 369 f, n = v
370 370 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
371 371
372 372 return None
373 373
374 374 class workingctx(changectx):
375 375 """A workingctx object makes access to data related to
376 376 the current working directory convenient."""
377 377 def __init__(self, repo):
378 378 self._repo = repo
379 379 self._rev = None
380 380 self._node = None
381 381
382 382 def __str__(self):
383 383 return str(self._parents[0]) + "+"
384 384
385 385 def __nonzero__(self):
386 386 return True
387 387
388 388 def __getattr__(self, name):
389 389 if name == '_parents':
390 390 self._parents = self._repo.parents()
391 391 return self._parents
392 392 if name == '_status':
393 393 self._status = self._repo.status()
394 394 return self._status
395 395 if name == '_manifest':
396 396 self._buildmanifest()
397 397 return self._manifest
398 398 else:
399 399 raise AttributeError, name
400 400
401 401 def _buildmanifest(self):
402 402 """generate a manifest corresponding to the working directory"""
403 403
404 404 man = self._parents[0].manifest().copy()
405 405 is_exec = util.execfunc(self._repo.root, man.execf)
406 406 is_link = util.linkfunc(self._repo.root, man.linkf)
407 407 copied = self._repo.dirstate.copies()
408 408 modified, added, removed, deleted, unknown = self._status[:5]
409 409 for i, l in (("a", added), ("m", modified), ("u", unknown)):
410 410 for f in l:
411 411 man[f] = man.get(copied.get(f, f), nullid) + i
412 412 try:
413 413 man.set(f, is_exec(f), is_link(f))
414 414 except OSError:
415 415 pass
416 416
417 417 for f in deleted + removed:
418 418 if f in man:
419 419 del man[f]
420 420
421 421 self._manifest = man
422 422
423 423 def manifest(self): return self._manifest
424 424
425 425 def user(self): return self._repo.ui.username()
426 426 def date(self): return util.makedate()
427 427 def description(self): return ""
428 428 def files(self):
429 429 f = self.modified() + self.added() + self.removed()
430 430 f.sort()
431 431 return f
432 432
433 433 def modified(self): return self._status[0]
434 434 def added(self): return self._status[1]
435 435 def removed(self): return self._status[2]
436 436 def deleted(self): return self._status[3]
437 437 def unknown(self): return self._status[4]
438 438 def clean(self): return self._status[5]
439 439 def branch(self): return self._repo.dirstate.branch()
440 440
441 441 def tags(self):
442 442 t = []
443 443 [t.extend(p.tags()) for p in self.parents()]
444 444 return t
445 445
446 446 def parents(self):
447 447 """return contexts for each parent changeset"""
448 448 return self._parents
449 449
450 450 def children(self):
451 451 return []
452 452
453 453 def filectx(self, path, filelog=None):
454 454 """get a file context from the working directory"""
455 455 return workingfilectx(self._repo, path, workingctx=self,
456 456 filelog=filelog)
457 457
458 458 def ancestor(self, c2):
459 459 """return the ancestor context of self and c2"""
460 460 return self._parents[0].ancestor(c2) # punt on two parents for now
461 461
462 462 class workingfilectx(filectx):
463 463 """A workingfilectx object makes access to data related to a particular
464 464 file in the working directory convenient."""
465 465 def __init__(self, repo, path, filelog=None, workingctx=None):
466 466 """changeid can be a changeset revision, node, or tag.
467 467 fileid can be a file revision or node."""
468 468 self._repo = repo
469 469 self._path = path
470 470 self._changeid = None
471 471 self._filerev = self._filenode = None
472 472
473 473 if filelog:
474 474 self._filelog = filelog
475 475 if workingctx:
476 476 self._changectx = workingctx
477 477
478 478 def __getattr__(self, name):
479 479 if name == '_changectx':
480 480 self._changectx = workingctx(self._repo)
481 481 return self._changectx
482 482 elif name == '_repopath':
483 483 self._repopath = (self._repo.dirstate.copied(self._path)
484 484 or self._path)
485 485 return self._repopath
486 486 elif name == '_filelog':
487 487 self._filelog = self._repo.file(self._repopath)
488 488 return self._filelog
489 489 else:
490 490 raise AttributeError, name
491 491
492 492 def __nonzero__(self):
493 493 return True
494 494
495 495 def __str__(self):
496 496 return "%s@%s" % (self.path(), self._changectx)
497 497
498 498 def filectx(self, fileid):
499 499 '''opens an arbitrary revision of the file without
500 500 opening a new filelog'''
501 501 return filectx(self._repo, self._repopath, fileid=fileid,
502 502 filelog=self._filelog)
503 503
504 504 def rev(self):
505 505 if '_changectx' in self.__dict__:
506 506 return self._changectx.rev()
507 507 return self._filelog.linkrev(self._filenode)
508 508
509 509 def data(self): return self._repo.wread(self._path)
510 510 def renamed(self):
511 511 rp = self._repopath
512 512 if rp == self._path:
513 513 return None
514 514 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
515 515
516 516 def parents(self):
517 517 '''return parent filectxs, following copies if necessary'''
518 518 p = self._path
519 519 rp = self._repopath
520 520 pcl = self._changectx._parents
521 521 fl = self._filelog
522 522 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
523 523 if len(pcl) > 1:
524 524 if rp != p:
525 525 fl = None
526 526 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
527 527
528 528 return [filectx(self._repo, p, fileid=n, filelog=l)
529 529 for p,n,l in pl if n != nullid]
530 530
531 531 def children(self):
532 532 return []
533 533
534 534 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
535 535 def date(self):
536 536 t, tz = self._changectx.date()
537 537 try:
538 538 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
539 539 except OSError, err:
540 540 if err.errno != errno.ENOENT: raise
541 541 return (t, tz)
542 542
543 543 def cmp(self, text): return self._repo.wread(self._path) == text
@@ -1,567 +1,570
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import errno, util, os, tempfile, context
11 11
12 12 def filemerge(repo, fw, fo, wctx, mctx):
13 13 """perform a 3-way merge in the working directory
14 14
15 15 fw = filename in the working directory
16 16 fo = filename in other parent
17 17 wctx, mctx = working and merge changecontexts
18 18 """
19 19
20 20 def temp(prefix, ctx):
21 21 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
22 22 (fd, name) = tempfile.mkstemp(prefix=pre)
23 23 data = repo.wwritedata(ctx.path(), ctx.data())
24 24 f = os.fdopen(fd, "wb")
25 25 f.write(data)
26 26 f.close()
27 27 return name
28 28
29 29 fcm = wctx.filectx(fw)
30 30 fco = mctx.filectx(fo)
31 31
32 32 if not fco.cmp(fcm.data()): # files identical?
33 33 return None
34 34
35 35 fca = fcm.ancestor(fco)
36 36 if not fca:
37 37 fca = repo.filectx(fw, fileid=nullrev)
38 38 a = repo.wjoin(fw)
39 39 b = temp("base", fca)
40 40 c = temp("other", fco)
41 41
42 42 if fw != fo:
43 43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
44 44 else:
45 45 repo.ui.status(_("merging %s\n") % fw)
46 46
47 47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
48 48
49 49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
50 50 or "hgmerge")
51 51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
52 52 environ={'HG_FILE': fw,
53 53 'HG_MY_NODE': str(wctx.parents()[0]),
54 54 'HG_OTHER_NODE': str(mctx)})
55 55 if r:
56 56 repo.ui.warn(_("merging %s failed!\n") % fw)
57 57
58 58 os.unlink(b)
59 59 os.unlink(c)
60 60 return r
61 61
62 62 def checkunknown(wctx, mctx):
63 63 "check for collisions between unknown files and files in mctx"
64 64 man = mctx.manifest()
65 65 for f in wctx.unknown():
66 66 if f in man:
67 67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
68 68 raise util.Abort(_("untracked local file '%s' differs"
69 69 " from remote version") % f)
70 70
71 71 def checkcollision(mctx):
72 72 "check for case folding collisions in the destination context"
73 73 folded = {}
74 74 for fn in mctx.manifest():
75 75 fold = fn.lower()
76 76 if fold in folded:
77 77 raise util.Abort(_("case-folding collision between %s and %s")
78 78 % (fn, folded[fold]))
79 79 folded[fold] = fn
80 80
81 81 def forgetremoved(wctx, mctx):
82 82 """
83 83 Forget removed files
84 84
85 85 If we're jumping between revisions (as opposed to merging), and if
86 86 neither the working directory nor the target rev has the file,
87 87 then we need to remove it from the dirstate, to prevent the
88 88 dirstate from listing the file when it is no longer in the
89 89 manifest.
90 90 """
91 91
92 92 action = []
93 93 man = mctx.manifest()
94 94 for f in wctx.deleted() + wctx.removed():
95 95 if f not in man:
96 96 action.append((f, "f"))
97 97
98 98 return action
99 99
100 100 def findcopies(repo, m1, m2, ma, limit):
101 101 """
102 102 Find moves and copies between m1 and m2 back to limit linkrev
103 103 """
104 104
105 105 def nonoverlap(d1, d2, d3):
106 106 "Return list of elements in d1 not in d2 or d3"
107 107 l = [d for d in d1 if d not in d3 and d not in d2]
108 108 l.sort()
109 109 return l
110 110
111 111 def dirname(f):
112 112 s = f.rfind("/")
113 113 if s == -1:
114 114 return ""
115 115 return f[:s]
116 116
117 117 def dirs(files):
118 118 d = {}
119 119 for f in files:
120 120 f = dirname(f)
121 121 while f not in d:
122 122 d[f] = True
123 123 f = dirname(f)
124 124 return d
125 125
126 126 wctx = repo.workingctx()
127 127
128 128 def makectx(f, n):
129 129 if len(n) == 20:
130 130 return repo.filectx(f, fileid=n)
131 131 return wctx.filectx(f)
132 132 ctx = util.cachefunc(makectx)
133 133
134 134 def findold(fctx):
135 135 "find files that path was copied from, back to linkrev limit"
136 136 old = {}
137 137 seen = {}
138 138 orig = fctx.path()
139 139 visit = [fctx]
140 140 while visit:
141 141 fc = visit.pop()
142 142 s = str(fc)
143 143 if s in seen:
144 144 continue
145 145 seen[s] = 1
146 146 if fc.path() != orig and fc.path() not in old:
147 147 old[fc.path()] = 1
148 148 if fc.rev() < limit:
149 149 continue
150 150 visit += fc.parents()
151 151
152 152 old = old.keys()
153 153 old.sort()
154 154 return old
155 155
156 156 copy = {}
157 157 fullcopy = {}
158 158 diverge = {}
159 159
160 def checkcopies(c, man):
160 def checkcopies(c, man, aman):
161 161 '''check possible copies for filectx c'''
162 162 for of in findold(c):
163 163 fullcopy[c.path()] = of # remember for dir rename detection
164 164 if of not in man: # original file not in other manifest?
165 165 if of in ma:
166 166 diverge.setdefault(of, []).append(c.path())
167 167 continue
168 # if the original file is unchanged on the other branch,
169 # no merge needed
170 if man[of] == aman.get(of):
171 continue
168 172 c2 = ctx(of, man[of])
169 173 ca = c.ancestor(c2)
170 174 if not ca: # unrelated?
171 175 continue
172 176 # named changed on only one side?
173 177 if ca.path() == c.path() or ca.path() == c2.path():
174 178 if c == ca or c2 == ca: # no merge needed, ignore copy
175 179 continue
176 180 copy[c.path()] = of
177 181
178 182 if not repo.ui.configbool("merge", "followcopies", True):
179 183 return {}, {}
180 184
181 185 # avoid silly behavior for update from empty dir
182 186 if not m1 or not m2 or not ma:
183 187 return {}, {}
184 188
185 189 u1 = nonoverlap(m1, m2, ma)
186 190 u2 = nonoverlap(m2, m1, ma)
187 191
188 192 for f in u1:
189 checkcopies(ctx(f, m1[f]), m2)
193 checkcopies(ctx(f, m1[f]), m2, ma)
190 194
191 195 for f in u2:
192 checkcopies(ctx(f, m2[f]), m1)
196 checkcopies(ctx(f, m2[f]), m1, ma)
193 197
194 198 d2 = {}
195 199 for of, fl in diverge.items():
196 200 for f in fl:
197 201 fo = list(fl)
198 202 fo.remove(f)
199 203 d2[f] = (of, fo)
200 #diverge = d2
201 204
202 205 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
203 206 return copy, diverge
204 207
205 208 # generate a directory move map
206 209 d1, d2 = dirs(m1), dirs(m2)
207 210 invalid = {}
208 211 dirmove = {}
209 212
210 213 # examine each file copy for a potential directory move, which is
211 214 # when all the files in a directory are moved to a new directory
212 215 for dst, src in fullcopy.items():
213 216 dsrc, ddst = dirname(src), dirname(dst)
214 217 if dsrc in invalid:
215 218 # already seen to be uninteresting
216 219 continue
217 220 elif dsrc in d1 and ddst in d1:
218 221 # directory wasn't entirely moved locally
219 222 invalid[dsrc] = True
220 223 elif dsrc in d2 and ddst in d2:
221 224 # directory wasn't entirely moved remotely
222 225 invalid[dsrc] = True
223 226 elif dsrc in dirmove and dirmove[dsrc] != ddst:
224 227 # files from the same directory moved to two different places
225 228 invalid[dsrc] = True
226 229 else:
227 230 # looks good so far
228 231 dirmove[dsrc + "/"] = ddst + "/"
229 232
230 233 for i in invalid:
231 234 if i in dirmove:
232 235 del dirmove[i]
233 236
234 237 del d1, d2, invalid
235 238
236 239 if not dirmove:
237 240 return copy, diverge
238 241
239 242 # check unaccounted nonoverlapping files against directory moves
240 243 for f in u1 + u2:
241 244 if f not in fullcopy:
242 245 for d in dirmove:
243 246 if f.startswith(d):
244 247 # new file added in a directory that was moved, move it
245 248 copy[f] = dirmove[d] + f[len(d):]
246 249 break
247 250
248 251 return copy, diverge
249 252
250 253 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
251 254 """
252 255 Merge p1 and p2 with ancestor ma and generate merge action list
253 256
254 257 overwrite = whether we clobber working files
255 258 partial = function to filter file lists
256 259 """
257 260
258 261 repo.ui.note(_("resolving manifests\n"))
259 262 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
260 263 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
261 264
262 265 m1 = p1.manifest()
263 266 m2 = p2.manifest()
264 267 ma = pa.manifest()
265 268 backwards = (pa == p2)
266 269 action = []
267 270 copy = {}
268 271 diverge = {}
269 272
270 273 def fmerge(f, f2=None, fa=None):
271 274 """merge flags"""
272 275 if not f2:
273 276 f2 = f
274 277 fa = f
275 278 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
276 279 if ((a^b) | (a^c)) ^ a:
277 280 return 'x'
278 281 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
279 282 if ((a^b) | (a^c)) ^ a:
280 283 return 'l'
281 284 return ''
282 285
283 286 def act(msg, m, f, *args):
284 287 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
285 288 action.append((f, m) + args)
286 289
287 290 if not (backwards or overwrite):
288 291 copy, diverge = findcopies(repo, m1, m2, ma, pa.rev())
289 292
290 293 for of, fl in diverge.items():
291 294 act("divergent renames", "dr", of, fl)
292 295
293 296 copied = dict.fromkeys(copy.values())
294 297
295 298 # Compare manifests
296 299 for f, n in m1.iteritems():
297 300 if partial and not partial(f):
298 301 continue
299 302 if f in m2:
300 303 # are files different?
301 304 if n != m2[f]:
302 305 a = ma.get(f, nullid)
303 306 # are both different from the ancestor?
304 307 if not overwrite and n != a and m2[f] != a:
305 308 act("versions differ", "m", f, f, f, fmerge(f), False)
306 309 # are we clobbering?
307 310 # is remote's version newer?
308 311 # or are we going back in time and clean?
309 312 elif overwrite or m2[f] != a or (backwards and not n[20:]):
310 313 act("remote is newer", "g", f, m2.flags(f))
311 314 # local is newer, not overwrite, check mode bits
312 315 elif fmerge(f) != m1.flags(f):
313 316 act("update permissions", "e", f, m2.flags(f))
314 317 # contents same, check mode bits
315 318 elif m1.flags(f) != m2.flags(f):
316 319 if overwrite or fmerge(f) != m1.flags(f):
317 320 act("update permissions", "e", f, m2.flags(f))
318 321 elif f in copied:
319 322 continue
320 323 elif f in copy:
321 324 f2 = copy[f]
322 325 if f2 not in m2: # directory rename
323 326 act("remote renamed directory to " + f2, "d",
324 327 f, None, f2, m1.flags(f))
325 328 elif f2 in m1: # case 2 A,B/B/B
326 329 act("local copied to " + f2, "m",
327 330 f, f2, f, fmerge(f, f2, f2), False)
328 331 else: # case 4,21 A/B/B
329 332 act("local moved to " + f2, "m",
330 333 f, f2, f, fmerge(f, f2, f2), False)
331 334 elif f in ma:
332 335 if n != ma[f] and not overwrite:
333 336 if repo.ui.prompt(
334 337 (_(" local changed %s which remote deleted\n") % f) +
335 338 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
336 339 act("prompt delete", "r", f)
337 340 else:
338 341 act("other deleted", "r", f)
339 342 else:
340 343 # file is created on branch or in working directory
341 344 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
342 345 act("remote deleted", "r", f)
343 346
344 347 for f, n in m2.iteritems():
345 348 if partial and not partial(f):
346 349 continue
347 350 if f in m1:
348 351 continue
349 352 if f in copied:
350 353 continue
351 354 if f in copy:
352 355 f2 = copy[f]
353 356 if f2 not in m1: # directory rename
354 357 act("local renamed directory to " + f2, "d",
355 358 None, f, f2, m2.flags(f))
356 359 elif f2 in m2: # rename case 1, A/A,B/A
357 360 act("remote copied to " + f, "m",
358 361 f2, f, f, fmerge(f2, f, f2), False)
359 362 else: # case 3,20 A/B/A
360 363 act("remote moved to " + f, "m",
361 364 f2, f, f, fmerge(f2, f, f2), True)
362 365 elif f in ma:
363 366 if overwrite or backwards:
364 367 act("recreating", "g", f, m2.flags(f))
365 368 elif n != ma[f]:
366 369 if repo.ui.prompt(
367 370 (_("remote changed %s which local deleted\n") % f) +
368 371 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
369 372 act("prompt recreating", "g", f, m2.flags(f))
370 373 else:
371 374 act("remote created", "g", f, m2.flags(f))
372 375
373 376 return action
374 377
375 378 def applyupdates(repo, action, wctx, mctx):
376 379 "apply the merge action list to the working directory"
377 380
378 381 updated, merged, removed, unresolved = 0, 0, 0, 0
379 382 action.sort()
380 383 for a in action:
381 384 f, m = a[:2]
382 385 if f and f[0] == "/":
383 386 continue
384 387 if m == "r": # remove
385 388 repo.ui.note(_("removing %s\n") % f)
386 389 util.audit_path(f)
387 390 try:
388 391 util.unlink(repo.wjoin(f))
389 392 except OSError, inst:
390 393 if inst.errno != errno.ENOENT:
391 394 repo.ui.warn(_("update failed to remove %s: %s!\n") %
392 395 (f, inst.strerror))
393 396 removed += 1
394 397 elif m == "m": # merge
395 398 f2, fd, flags, move = a[2:]
396 399 r = filemerge(repo, f, f2, wctx, mctx)
397 400 if r > 0:
398 401 unresolved += 1
399 402 else:
400 403 if r is None:
401 404 updated += 1
402 405 else:
403 406 merged += 1
404 407 if f != fd:
405 408 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
406 409 repo.wwrite(fd, repo.wread(f), flags)
407 410 if move:
408 411 repo.ui.debug(_("removing %s\n") % f)
409 412 os.unlink(repo.wjoin(f))
410 413 util.set_exec(repo.wjoin(fd), "x" in flags)
411 414 elif m == "g": # get
412 415 flags = a[2]
413 416 repo.ui.note(_("getting %s\n") % f)
414 417 t = mctx.filectx(f).data()
415 418 repo.wwrite(f, t, flags)
416 419 updated += 1
417 420 elif m == "d": # directory rename
418 421 f2, fd, flags = a[2:]
419 422 if f:
420 423 repo.ui.note(_("moving %s to %s\n") % (f, fd))
421 424 t = wctx.filectx(f).data()
422 425 repo.wwrite(fd, t, flags)
423 426 util.unlink(repo.wjoin(f))
424 427 if f2:
425 428 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
426 429 t = mctx.filectx(f2).data()
427 430 repo.wwrite(fd, t, flags)
428 431 updated += 1
429 432 elif m == "dr": # divergent renames
430 433 fl = a[2]
431 434 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
432 435 for nf in fl:
433 436 repo.ui.warn(" %s\n" % nf)
434 437 elif m == "e": # exec
435 438 flags = a[2]
436 439 util.set_exec(repo.wjoin(f), flags)
437 440
438 441 return updated, merged, removed, unresolved
439 442
440 443 def recordupdates(repo, action, branchmerge):
441 444 "record merge actions to the dirstate"
442 445
443 446 for a in action:
444 447 f, m = a[:2]
445 448 if m == "r": # remove
446 449 if branchmerge:
447 450 repo.dirstate.update([f], 'r')
448 451 else:
449 452 repo.dirstate.forget([f])
450 453 elif m == "f": # forget
451 454 repo.dirstate.forget([f])
452 455 elif m == "g": # get
453 456 if branchmerge:
454 457 repo.dirstate.update([f], 'n', st_mtime=-1)
455 458 else:
456 459 repo.dirstate.update([f], 'n')
457 460 elif m == "m": # merge
458 461 f2, fd, flag, move = a[2:]
459 462 if branchmerge:
460 463 # We've done a branch merge, mark this file as merged
461 464 # so that we properly record the merger later
462 465 repo.dirstate.update([fd], 'm')
463 466 if f != f2: # copy/rename
464 467 if move:
465 468 repo.dirstate.update([f], 'r')
466 469 if f != fd:
467 470 repo.dirstate.copy(f, fd)
468 471 else:
469 472 repo.dirstate.copy(f2, fd)
470 473 else:
471 474 # We've update-merged a locally modified file, so
472 475 # we set the dirstate to emulate a normal checkout
473 476 # of that file some time in the past. Thus our
474 477 # merge will appear as a normal local file
475 478 # modification.
476 479 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
477 480 if move:
478 481 repo.dirstate.forget([f])
479 482 elif m == "d": # directory rename
480 483 f2, fd, flag = a[2:]
481 484 if not f2 and f not in repo.dirstate:
482 485 # untracked file moved
483 486 continue
484 487 if branchmerge:
485 488 repo.dirstate.update([fd], 'a')
486 489 if f:
487 490 repo.dirstate.update([f], 'r')
488 491 repo.dirstate.copy(f, fd)
489 492 if f2:
490 493 repo.dirstate.copy(f2, fd)
491 494 else:
492 495 repo.dirstate.update([fd], 'n')
493 496 if f:
494 497 repo.dirstate.forget([f])
495 498
496 499 def update(repo, node, branchmerge, force, partial, wlock):
497 500 """
498 501 Perform a merge between the working directory and the given node
499 502
500 503 branchmerge = whether to merge between branches
501 504 force = whether to force branch merging or file overwriting
502 505 partial = a function to filter file lists (dirstate not updated)
503 506 wlock = working dir lock, if already held
504 507 """
505 508
506 509 if not wlock:
507 510 wlock = repo.wlock()
508 511
509 512 wc = repo.workingctx()
510 513 if node is None:
511 514 # tip of current branch
512 515 try:
513 516 node = repo.branchtags()[wc.branch()]
514 517 except KeyError:
515 518 raise util.Abort(_("branch %s not found") % wc.branch())
516 519 overwrite = force and not branchmerge
517 520 forcemerge = force and branchmerge
518 521 pl = wc.parents()
519 522 p1, p2 = pl[0], repo.changectx(node)
520 523 pa = p1.ancestor(p2)
521 524 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
522 525 fastforward = False
523 526
524 527 ### check phase
525 528 if not overwrite and len(pl) > 1:
526 529 raise util.Abort(_("outstanding uncommitted merges"))
527 530 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
528 531 if branchmerge:
529 532 if p1.branch() != p2.branch() and pa != p2:
530 533 fastforward = True
531 534 else:
532 535 raise util.Abort(_("there is nothing to merge, just use "
533 536 "'hg update' or look at 'hg heads'"))
534 537 elif not (overwrite or branchmerge):
535 538 raise util.Abort(_("update spans branches, use 'hg merge' "
536 539 "or 'hg update -C' to lose changes"))
537 540 if branchmerge and not forcemerge:
538 541 if wc.files():
539 542 raise util.Abort(_("outstanding uncommitted changes"))
540 543
541 544 ### calculate phase
542 545 action = []
543 546 if not force:
544 547 checkunknown(wc, p2)
545 548 if not util.checkfolding(repo.path):
546 549 checkcollision(p2)
547 550 if not branchmerge:
548 551 action += forgetremoved(wc, p2)
549 552 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
550 553
551 554 ### apply phase
552 555 if not branchmerge: # just jump to the new rev
553 556 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
554 557 if not partial:
555 558 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
556 559
557 560 stats = applyupdates(repo, action, wc, p2)
558 561
559 562 if not partial:
560 563 recordupdates(repo, action, branchmerge)
561 564 repo.dirstate.setparents(fp1, fp2)
562 565 if not branchmerge and not fastforward:
563 566 repo.dirstate.setbranch(p2.branch())
564 567 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
565 568
566 569 return stats
567 570
General Comments 0
You need to be logged in to leave comments. Login now