comparison mercurial/merge.py @ 6269:ffdf70e74623

merge: privatize some functions, unnest some others
author Matt Mackall <mpm@selenic.com>
date Sat, 15 Mar 2008 10:02:31 -0500
parents 7e4f66fe964b
children 14f0fe2e2db7
comparison
equal deleted inserted replaced
6268:7e4f66fe964b 6269:ffdf70e74623
7 7
8 from node import nullid, nullrev 8 from node import nullid, nullrev
9 from i18n import _ 9 from i18n import _
10 import errno, util, os, heapq, filemerge 10 import errno, util, os, heapq, filemerge
11 11
12 def checkunknown(wctx, mctx): 12 def _checkunknown(wctx, mctx):
13 "check for collisions between unknown files and files in mctx" 13 "check for collisions between unknown files and files in mctx"
14 man = mctx.manifest() 14 man = mctx.manifest()
15 for f in wctx.unknown(): 15 for f in wctx.unknown():
16 if f in man: 16 if f in man:
17 if mctx.filectx(f).cmp(wctx.filectx(f).data()): 17 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
18 raise util.Abort(_("untracked file in working directory differs" 18 raise util.Abort(_("untracked file in working directory differs"
19 " from file in requested revision: '%s'") 19 " from file in requested revision: '%s'")
20 % f) 20 % f)
21 21
22 def checkcollision(mctx): 22 def _checkcollision(mctx):
23 "check for case folding collisions in the destination context" 23 "check for case folding collisions in the destination context"
24 folded = {} 24 folded = {}
25 for fn in mctx.manifest(): 25 for fn in mctx.manifest():
26 fold = fn.lower() 26 fold = fn.lower()
27 if fold in folded: 27 if fold in folded:
28 raise util.Abort(_("case-folding collision between %s and %s") 28 raise util.Abort(_("case-folding collision between %s and %s")
29 % (fn, folded[fold])) 29 % (fn, folded[fold]))
30 folded[fold] = fn 30 folded[fold] = fn
31 31
32 def forgetremoved(wctx, mctx, branchmerge): 32 def _forgetremoved(wctx, mctx, branchmerge):
33 """ 33 """
34 Forget removed files 34 Forget removed files
35 35
36 If we're jumping between revisions (as opposed to merging), and if 36 If we're jumping between revisions (as opposed to merging), and if
37 neither the working directory nor the target rev has the file, 37 neither the working directory nor the target rev has the file,
56 if f not in man: 56 if f not in man:
57 action.append((f, "f")) 57 action.append((f, "f"))
58 58
59 return action 59 return action
60 60
61 def _nonoverlap(d1, d2, d3):
62 "Return list of elements in d1 not in d2 or d3"
63 l = [d for d in d1 if d not in d3 and d not in d2]
64 l.sort()
65 return l
66
67 def _dirname(f):
68 s = f.rfind("/")
69 if s == -1:
70 return ""
71 return f[:s]
72
73 def _dirs(files):
74 d = {}
75 for f in files:
76 f = _dirname(f)
77 while f not in d:
78 d[f] = True
79 f = _dirname(f)
80 return d
81
82 def _findoldnames(fctx, limit):
83 "find files that path was copied from, back to linkrev limit"
84 old = {}
85 seen = {}
86 orig = fctx.path()
87 visit = [fctx]
88 while visit:
89 fc = visit.pop()
90 s = str(fc)
91 if s in seen:
92 continue
93 seen[s] = 1
94 if fc.path() != orig and fc.path() not in old:
95 old[fc.path()] = 1
96 if fc.rev() < limit:
97 continue
98 visit += fc.parents()
99
100 old = old.keys()
101 old.sort()
102 return old
103
61 def findcopies(repo, m1, m2, ma, limit): 104 def findcopies(repo, m1, m2, ma, limit):
62 """ 105 """
63 Find moves and copies between m1 and m2 back to limit linkrev 106 Find moves and copies between m1 and m2 back to limit linkrev
64 """ 107 """
65
66 def nonoverlap(d1, d2, d3):
67 "Return list of elements in d1 not in d2 or d3"
68 l = [d for d in d1 if d not in d3 and d not in d2]
69 l.sort()
70 return l
71
72 def dirname(f):
73 s = f.rfind("/")
74 if s == -1:
75 return ""
76 return f[:s]
77
78 def dirs(files):
79 d = {}
80 for f in files:
81 f = dirname(f)
82 while f not in d:
83 d[f] = True
84 f = dirname(f)
85 return d
86 108
87 wctx = repo.workingctx() 109 wctx = repo.workingctx()
88 110
89 def makectx(f, n): 111 def makectx(f, n):
90 if len(n) == 20: 112 if len(n) == 20:
91 return repo.filectx(f, fileid=n) 113 return repo.filectx(f, fileid=n)
92 return wctx.filectx(f) 114 return wctx.filectx(f)
93 ctx = util.cachefunc(makectx) 115 ctx = util.cachefunc(makectx)
94 116
95 def findold(fctx):
96 "find files that path was copied from, back to linkrev limit"
97 old = {}
98 seen = {}
99 orig = fctx.path()
100 visit = [fctx]
101 while visit:
102 fc = visit.pop()
103 s = str(fc)
104 if s in seen:
105 continue
106 seen[s] = 1
107 if fc.path() != orig and fc.path() not in old:
108 old[fc.path()] = 1
109 if fc.rev() < limit and fc.rev() is not None:
110 continue
111 visit += fc.parents()
112
113 old = old.keys()
114 old.sort()
115 return old
116
117 copy = {} 117 copy = {}
118 fullcopy = {} 118 fullcopy = {}
119 diverge = {} 119 diverge = {}
120 120
121 def checkcopies(c, man, aman): 121 def checkcopies(c, man, aman):
122 '''check possible copies for filectx c''' 122 '''check possible copies for filectx c'''
123 for of in findold(c): 123 for of in _findoldnames(c, limit):
124 fullcopy[c.path()] = of # remember for dir rename detection 124 fullcopy[c.path()] = of # remember for dir rename detection
125 if of not in man: # original file not in other manifest? 125 if of not in man: # original file not in other manifest?
126 if of in ma: 126 if of in ma:
127 diverge.setdefault(of, []).append(c.path()) 127 diverge.setdefault(of, []).append(c.path())
128 continue 128 continue
147 if not m1 or not m2 or not ma: 147 if not m1 or not m2 or not ma:
148 return {}, {} 148 return {}, {}
149 149
150 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit) 150 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
151 151
152 u1 = nonoverlap(m1, m2, ma) 152 u1 = _nonoverlap(m1, m2, ma)
153 u2 = nonoverlap(m2, m1, ma) 153 u2 = _nonoverlap(m2, m1, ma)
154 154
155 if u1: 155 if u1:
156 repo.ui.debug(_(" unmatched files in local:\n %s\n") 156 repo.ui.debug(_(" unmatched files in local:\n %s\n")
157 % "\n ".join(u1)) 157 % "\n ".join(u1))
158 if u2: 158 if u2:
186 return copy, diverge 186 return copy, diverge
187 187
188 repo.ui.debug(_(" checking for directory renames\n")) 188 repo.ui.debug(_(" checking for directory renames\n"))
189 189
190 # generate a directory move map 190 # generate a directory move map
191 d1, d2 = dirs(m1), dirs(m2) 191 d1, d2 = _dirs(m1), _dirs(m2)
192 invalid = {} 192 invalid = {}
193 dirmove = {} 193 dirmove = {}
194 194
195 # examine each file copy for a potential directory move, which is 195 # examine each file copy for a potential directory move, which is
196 # when all the files in a directory are moved to a new directory 196 # when all the files in a directory are moved to a new directory
197 for dst, src in fullcopy.items(): 197 for dst, src in fullcopy.items():
198 dsrc, ddst = dirname(src), dirname(dst) 198 dsrc, ddst = _dirname(src), _dirname(dst)
199 if dsrc in invalid: 199 if dsrc in invalid:
200 # already seen to be uninteresting 200 # already seen to be uninteresting
201 continue 201 continue
202 elif dsrc in d1 and ddst in d1: 202 elif dsrc in d1 and ddst in d1:
203 # directory wasn't entirely moved locally 203 # directory wasn't entirely moved locally
234 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f])) 234 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f]))
235 break 235 break
236 236
237 return copy, diverge 237 return copy, diverge
238 238
239 def symmetricdifference(repo, rev1, rev2): 239 def _symmetricdifference(repo, rev1, rev2):
240 """symmetric difference of the sets of ancestors of rev1 and rev2 240 """symmetric difference of the sets of ancestors of rev1 and rev2
241 241
242 I.e. revisions that are ancestors of rev1 or rev2, but not both. 242 I.e. revisions that are ancestors of rev1 or rev2, but not both.
243 """ 243 """
244 # basic idea: 244 # basic idea:
338 if not (backwards or overwrite): 338 if not (backwards or overwrite):
339 rev1 = p1.rev() 339 rev1 = p1.rev()
340 if rev1 is None: 340 if rev1 is None:
341 # p1 is a workingctx 341 # p1 is a workingctx
342 rev1 = p1.parents()[0].rev() 342 rev1 = p1.parents()[0].rev()
343 limit = min(symmetricdifference(repo, rev1, p2.rev())) 343 limit = min(_symmetricdifference(repo, rev1, p2.rev()))
344 copy, diverge = findcopies(repo, m1, m2, ma, limit) 344 copy, diverge = findcopies(repo, m1, m2, ma, limit)
345 345
346 for of, fl in diverge.items(): 346 for of, fl in diverge.items():
347 act("divergent renames", "dr", of, fl) 347 act("divergent renames", "dr", of, fl)
348 348
613 raise util.Abort(_("outstanding uncommitted changes")) 613 raise util.Abort(_("outstanding uncommitted changes"))
614 614
615 ### calculate phase 615 ### calculate phase
616 action = [] 616 action = []
617 if not force: 617 if not force:
618 checkunknown(wc, p2) 618 _checkunknown(wc, p2)
619 if not util.checkfolding(repo.path): 619 if not util.checkfolding(repo.path):
620 checkcollision(p2) 620 _checkcollision(p2)
621 action += forgetremoved(wc, p2, branchmerge) 621 action += _forgetremoved(wc, p2, branchmerge)
622 action += manifestmerge(repo, wc, p2, pa, overwrite, partial) 622 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
623 623
624 ### apply phase 624 ### apply phase
625 if not branchmerge: # just jump to the new rev 625 if not branchmerge: # just jump to the new rev
626 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' 626 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''