##// END OF EJS Templates
merge with crew-stable
Alexis S. L. Carvalho -
r5161:4ed58fe4 merge default
parent child Browse files
Show More
@@ -0,0 +1,35 b''
1 #!/bin/sh
2
3 # 0-2-4
4 # \ \ \
5 # 1-3-5
6 #
7 # rename in #1, content change in #4.
8
9 hg init t
10 cd t
11
12 touch 1
13 touch 2
14 hg commit -Am init -d "0 0" # 0
15
16 hg rename 1 1a
17 hg commit -m rename -d "0 0" # 1
18
19 hg co -C 0
20 echo unrelated >> 2
21 hg ci -m unrelated1 -d "0 0" # 2
22
23 hg merge --debug 1
24 hg ci -m merge1 -d "0 0" # 3
25
26 hg co -C 2
27 echo hello >> 1
28 hg ci -m unrelated2 -d "0 0" # 4
29
30 hg co -C 3
31 hg merge -y --debug 4
32
33 hg co -C 4
34 hg merge -y --debug 3
35
@@ -0,0 +1,33 b''
1 adding 1
2 adding 2
3 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
4 resolving manifests
5 overwrite None partial False
6 ancestor 81f4b099af3d local c64f439569a9+ remote 2f8037f47a5c
7 1: other deleted -> r
8 1a: remote created -> g
9 removing 1
10 getting 1a
11 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
12 (branch merge, don't forget to commit)
13 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
14 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
15 resolving manifests
16 overwrite None partial False
17 ancestor c64f439569a9 local ac7575e3c052+ remote 746e9549ea96
18 1a: local moved to 1 -> m
19 merging 1a and 1
20 my 1a@ac7575e3c052+ other 1@746e9549ea96 ancestor 1@81f4b099af3d
21 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
22 (branch merge, don't forget to commit)
23 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
24 resolving manifests
25 overwrite None partial False
26 ancestor c64f439569a9 local 746e9549ea96+ remote ac7575e3c052
27 1: remote moved to 1a -> m
28 copying 1 to 1a
29 merging 1 and 1a
30 my 1@746e9549ea96+ other 1a@2f8037f47a5c ancestor 1@81f4b099af3d
31 removing 1
32 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
33 (branch merge, don't forget to commit)
@@ -1,579 +1,636 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 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 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import errno, util, os, tempfile, context
10 import errno, util, os, tempfile, context, heapq
11
11
12 def filemerge(repo, fw, fd, fo, wctx, mctx):
12 def filemerge(repo, fw, fd, fo, wctx, mctx):
13 """perform a 3-way merge in the working directory
13 """perform a 3-way merge in the working directory
14
14
15 fw = original filename in the working directory
15 fw = original filename in the working directory
16 fd = destination filename in the working directory
16 fd = destination filename in the working directory
17 fo = filename in other parent
17 fo = filename in other parent
18 wctx, mctx = working and merge changecontexts
18 wctx, mctx = working and merge changecontexts
19 """
19 """
20
20
21 def temp(prefix, ctx):
21 def temp(prefix, ctx):
22 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
22 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
23 (fd, name) = tempfile.mkstemp(prefix=pre)
23 (fd, name) = tempfile.mkstemp(prefix=pre)
24 data = repo.wwritedata(ctx.path(), ctx.data())
24 data = repo.wwritedata(ctx.path(), ctx.data())
25 f = os.fdopen(fd, "wb")
25 f = os.fdopen(fd, "wb")
26 f.write(data)
26 f.write(data)
27 f.close()
27 f.close()
28 return name
28 return name
29
29
30 fcm = wctx.filectx(fw)
30 fcm = wctx.filectx(fw)
31 fcmdata = wctx.filectx(fd).data()
31 fcmdata = wctx.filectx(fd).data()
32 fco = mctx.filectx(fo)
32 fco = mctx.filectx(fo)
33
33
34 if not fco.cmp(fcmdata): # files identical?
34 if not fco.cmp(fcmdata): # files identical?
35 return None
35 return None
36
36
37 fca = fcm.ancestor(fco)
37 fca = fcm.ancestor(fco)
38 if not fca:
38 if not fca:
39 fca = repo.filectx(fw, fileid=nullrev)
39 fca = repo.filectx(fw, fileid=nullrev)
40 a = repo.wjoin(fd)
40 a = repo.wjoin(fd)
41 b = temp("base", fca)
41 b = temp("base", fca)
42 c = temp("other", fco)
42 c = temp("other", fco)
43
43
44 if fw != fo:
44 if fw != fo:
45 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
45 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
46 else:
46 else:
47 repo.ui.status(_("merging %s\n") % fw)
47 repo.ui.status(_("merging %s\n") % fw)
48
48
49 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
49 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
50
50
51 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
51 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
52 or "hgmerge")
52 or "hgmerge")
53 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
53 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
54 environ={'HG_FILE': fd,
54 environ={'HG_FILE': fd,
55 'HG_MY_NODE': str(wctx.parents()[0]),
55 'HG_MY_NODE': str(wctx.parents()[0]),
56 'HG_OTHER_NODE': str(mctx)})
56 'HG_OTHER_NODE': str(mctx)})
57 if r:
57 if r:
58 repo.ui.warn(_("merging %s failed!\n") % fd)
58 repo.ui.warn(_("merging %s failed!\n") % fd)
59
59
60 os.unlink(b)
60 os.unlink(b)
61 os.unlink(c)
61 os.unlink(c)
62 return r
62 return r
63
63
64 def checkunknown(wctx, mctx):
64 def checkunknown(wctx, mctx):
65 "check for collisions between unknown files and files in mctx"
65 "check for collisions between unknown files and files in mctx"
66 man = mctx.manifest()
66 man = mctx.manifest()
67 for f in wctx.unknown():
67 for f in wctx.unknown():
68 if f in man:
68 if f in man:
69 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
69 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
70 raise util.Abort(_("untracked local file '%s' differs"
70 raise util.Abort(_("untracked local file '%s' differs"
71 " from remote version") % f)
71 " from remote version") % f)
72
72
73 def checkcollision(mctx):
73 def checkcollision(mctx):
74 "check for case folding collisions in the destination context"
74 "check for case folding collisions in the destination context"
75 folded = {}
75 folded = {}
76 for fn in mctx.manifest():
76 for fn in mctx.manifest():
77 fold = fn.lower()
77 fold = fn.lower()
78 if fold in folded:
78 if fold in folded:
79 raise util.Abort(_("case-folding collision between %s and %s")
79 raise util.Abort(_("case-folding collision between %s and %s")
80 % (fn, folded[fold]))
80 % (fn, folded[fold]))
81 folded[fold] = fn
81 folded[fold] = fn
82
82
83 def forgetremoved(wctx, mctx):
83 def forgetremoved(wctx, mctx):
84 """
84 """
85 Forget removed files
85 Forget removed files
86
86
87 If we're jumping between revisions (as opposed to merging), and if
87 If we're jumping between revisions (as opposed to merging), and if
88 neither the working directory nor the target rev has the file,
88 neither the working directory nor the target rev has the file,
89 then we need to remove it from the dirstate, to prevent the
89 then we need to remove it from the dirstate, to prevent the
90 dirstate from listing the file when it is no longer in the
90 dirstate from listing the file when it is no longer in the
91 manifest.
91 manifest.
92 """
92 """
93
93
94 action = []
94 action = []
95 man = mctx.manifest()
95 man = mctx.manifest()
96 for f in wctx.deleted() + wctx.removed():
96 for f in wctx.deleted() + wctx.removed():
97 if f not in man:
97 if f not in man:
98 action.append((f, "f"))
98 action.append((f, "f"))
99
99
100 return action
100 return action
101
101
102 def findcopies(repo, m1, m2, ma, limit):
102 def findcopies(repo, m1, m2, ma, limit):
103 """
103 """
104 Find moves and copies between m1 and m2 back to limit linkrev
104 Find moves and copies between m1 and m2 back to limit linkrev
105 """
105 """
106
106
107 def nonoverlap(d1, d2, d3):
107 def nonoverlap(d1, d2, d3):
108 "Return list of elements in d1 not in d2 or d3"
108 "Return list of elements in d1 not in d2 or d3"
109 l = [d for d in d1 if d not in d3 and d not in d2]
109 l = [d for d in d1 if d not in d3 and d not in d2]
110 l.sort()
110 l.sort()
111 return l
111 return l
112
112
113 def dirname(f):
113 def dirname(f):
114 s = f.rfind("/")
114 s = f.rfind("/")
115 if s == -1:
115 if s == -1:
116 return ""
116 return ""
117 return f[:s]
117 return f[:s]
118
118
119 def dirs(files):
119 def dirs(files):
120 d = {}
120 d = {}
121 for f in files:
121 for f in files:
122 f = dirname(f)
122 f = dirname(f)
123 while f not in d:
123 while f not in d:
124 d[f] = True
124 d[f] = True
125 f = dirname(f)
125 f = dirname(f)
126 return d
126 return d
127
127
128 wctx = repo.workingctx()
128 wctx = repo.workingctx()
129
129
130 def makectx(f, n):
130 def makectx(f, n):
131 if len(n) == 20:
131 if len(n) == 20:
132 return repo.filectx(f, fileid=n)
132 return repo.filectx(f, fileid=n)
133 return wctx.filectx(f)
133 return wctx.filectx(f)
134 ctx = util.cachefunc(makectx)
134 ctx = util.cachefunc(makectx)
135
135
136 def findold(fctx):
136 def findold(fctx):
137 "find files that path was copied from, back to linkrev limit"
137 "find files that path was copied from, back to linkrev limit"
138 old = {}
138 old = {}
139 seen = {}
139 seen = {}
140 orig = fctx.path()
140 orig = fctx.path()
141 visit = [fctx]
141 visit = [fctx]
142 while visit:
142 while visit:
143 fc = visit.pop()
143 fc = visit.pop()
144 s = str(fc)
144 s = str(fc)
145 if s in seen:
145 if s in seen:
146 continue
146 continue
147 seen[s] = 1
147 seen[s] = 1
148 if fc.path() != orig and fc.path() not in old:
148 if fc.path() != orig and fc.path() not in old:
149 old[fc.path()] = 1
149 old[fc.path()] = 1
150 if fc.rev() < limit:
150 if fc.rev() < limit:
151 continue
151 continue
152 visit += fc.parents()
152 visit += fc.parents()
153
153
154 old = old.keys()
154 old = old.keys()
155 old.sort()
155 old.sort()
156 return old
156 return old
157
157
158 copy = {}
158 copy = {}
159 fullcopy = {}
159 fullcopy = {}
160 diverge = {}
160 diverge = {}
161
161
162 def checkcopies(c, man, aman):
162 def checkcopies(c, man, aman):
163 '''check possible copies for filectx c'''
163 '''check possible copies for filectx c'''
164 for of in findold(c):
164 for of in findold(c):
165 fullcopy[c.path()] = of # remember for dir rename detection
165 fullcopy[c.path()] = of # remember for dir rename detection
166 if of not in man: # original file not in other manifest?
166 if of not in man: # original file not in other manifest?
167 if of in ma:
167 if of in ma:
168 diverge.setdefault(of, []).append(c.path())
168 diverge.setdefault(of, []).append(c.path())
169 continue
169 continue
170 # if the original file is unchanged on the other branch,
170 # if the original file is unchanged on the other branch,
171 # no merge needed
171 # no merge needed
172 if man[of] == aman.get(of):
172 if man[of] == aman.get(of):
173 continue
173 continue
174 c2 = ctx(of, man[of])
174 c2 = ctx(of, man[of])
175 ca = c.ancestor(c2)
175 ca = c.ancestor(c2)
176 if not ca: # unrelated?
176 if not ca: # unrelated?
177 continue
177 continue
178 # named changed on only one side?
178 # named changed on only one side?
179 if ca.path() == c.path() or ca.path() == c2.path():
179 if ca.path() == c.path() or ca.path() == c2.path():
180 if c == ca or c2 == ca: # no merge needed, ignore copy
180 if c == ca or c2 == ca: # no merge needed, ignore copy
181 continue
181 continue
182 copy[c.path()] = of
182 copy[c.path()] = of
183
183
184 if not repo.ui.configbool("merge", "followcopies", True):
184 if not repo.ui.configbool("merge", "followcopies", True):
185 return {}, {}
185 return {}, {}
186
186
187 # avoid silly behavior for update from empty dir
187 # avoid silly behavior for update from empty dir
188 if not m1 or not m2 or not ma:
188 if not m1 or not m2 or not ma:
189 return {}, {}
189 return {}, {}
190
190
191 u1 = nonoverlap(m1, m2, ma)
191 u1 = nonoverlap(m1, m2, ma)
192 u2 = nonoverlap(m2, m1, ma)
192 u2 = nonoverlap(m2, m1, ma)
193
193
194 for f in u1:
194 for f in u1:
195 checkcopies(ctx(f, m1[f]), m2, ma)
195 checkcopies(ctx(f, m1[f]), m2, ma)
196
196
197 for f in u2:
197 for f in u2:
198 checkcopies(ctx(f, m2[f]), m1, ma)
198 checkcopies(ctx(f, m2[f]), m1, ma)
199
199
200 d2 = {}
200 d2 = {}
201 for of, fl in diverge.items():
201 for of, fl in diverge.items():
202 for f in fl:
202 for f in fl:
203 fo = list(fl)
203 fo = list(fl)
204 fo.remove(f)
204 fo.remove(f)
205 d2[f] = (of, fo)
205 d2[f] = (of, fo)
206
206
207 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
207 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
208 return copy, diverge
208 return copy, diverge
209
209
210 # generate a directory move map
210 # generate a directory move map
211 d1, d2 = dirs(m1), dirs(m2)
211 d1, d2 = dirs(m1), dirs(m2)
212 invalid = {}
212 invalid = {}
213 dirmove = {}
213 dirmove = {}
214
214
215 # examine each file copy for a potential directory move, which is
215 # examine each file copy for a potential directory move, which is
216 # when all the files in a directory are moved to a new directory
216 # when all the files in a directory are moved to a new directory
217 for dst, src in fullcopy.items():
217 for dst, src in fullcopy.items():
218 dsrc, ddst = dirname(src), dirname(dst)
218 dsrc, ddst = dirname(src), dirname(dst)
219 if dsrc in invalid:
219 if dsrc in invalid:
220 # already seen to be uninteresting
220 # already seen to be uninteresting
221 continue
221 continue
222 elif dsrc in d1 and ddst in d1:
222 elif dsrc in d1 and ddst in d1:
223 # directory wasn't entirely moved locally
223 # directory wasn't entirely moved locally
224 invalid[dsrc] = True
224 invalid[dsrc] = True
225 elif dsrc in d2 and ddst in d2:
225 elif dsrc in d2 and ddst in d2:
226 # directory wasn't entirely moved remotely
226 # directory wasn't entirely moved remotely
227 invalid[dsrc] = True
227 invalid[dsrc] = True
228 elif dsrc in dirmove and dirmove[dsrc] != ddst:
228 elif dsrc in dirmove and dirmove[dsrc] != ddst:
229 # files from the same directory moved to two different places
229 # files from the same directory moved to two different places
230 invalid[dsrc] = True
230 invalid[dsrc] = True
231 else:
231 else:
232 # looks good so far
232 # looks good so far
233 dirmove[dsrc + "/"] = ddst + "/"
233 dirmove[dsrc + "/"] = ddst + "/"
234
234
235 for i in invalid:
235 for i in invalid:
236 if i in dirmove:
236 if i in dirmove:
237 del dirmove[i]
237 del dirmove[i]
238
238
239 del d1, d2, invalid
239 del d1, d2, invalid
240
240
241 if not dirmove:
241 if not dirmove:
242 return copy, diverge
242 return copy, diverge
243
243
244 # check unaccounted nonoverlapping files against directory moves
244 # check unaccounted nonoverlapping files against directory moves
245 for f in u1 + u2:
245 for f in u1 + u2:
246 if f not in fullcopy:
246 if f not in fullcopy:
247 for d in dirmove:
247 for d in dirmove:
248 if f.startswith(d):
248 if f.startswith(d):
249 # new file added in a directory that was moved, move it
249 # new file added in a directory that was moved, move it
250 copy[f] = dirmove[d] + f[len(d):]
250 copy[f] = dirmove[d] + f[len(d):]
251 break
251 break
252
252
253 return copy, diverge
253 return copy, diverge
254
254
255 def symmetricdifference(repo, rev1, rev2):
256 """symmetric difference of the sets of ancestors of rev1 and rev2
257
258 I.e. revisions that are ancestors of rev1 or rev2, but not both.
259 """
260 # basic idea:
261 # - mark rev1 and rev2 with different colors
262 # - walk the graph in topological order with the help of a heap;
263 # for each revision r:
264 # - if r has only one color, we want to return it
265 # - add colors[r] to its parents
266 #
267 # We keep track of the number of revisions in the heap that
268 # we may be interested in. We stop walking the graph as soon
269 # as this number reaches 0.
270 WHITE = 1
271 BLACK = 2
272 ALLCOLORS = WHITE | BLACK
273 colors = {rev1: WHITE, rev2: BLACK}
274
275 cl = repo.changelog
276
277 visit = [-rev1, -rev2]
278 heapq.heapify(visit)
279 n_wanted = len(visit)
280 ret = []
281
282 while n_wanted:
283 r = -heapq.heappop(visit)
284 wanted = colors[r] != ALLCOLORS
285 n_wanted -= wanted
286 if wanted:
287 ret.append(r)
288
289 for p in cl.parentrevs(r):
290 if p == nullrev:
291 continue
292 if p not in colors:
293 # first time we see p; add it to visit
294 n_wanted += wanted
295 colors[p] = colors[r]
296 heapq.heappush(visit, -p)
297 elif colors[p] != ALLCOLORS and colors[p] != colors[r]:
298 # at first we thought we wanted p, but now
299 # we know we don't really want it
300 n_wanted -= 1
301 colors[p] |= colors[r]
302
303 del colors[r]
304
305 return ret
306
255 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
307 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
256 """
308 """
257 Merge p1 and p2 with ancestor ma and generate merge action list
309 Merge p1 and p2 with ancestor ma and generate merge action list
258
310
259 overwrite = whether we clobber working files
311 overwrite = whether we clobber working files
260 partial = function to filter file lists
312 partial = function to filter file lists
261 """
313 """
262
314
263 repo.ui.note(_("resolving manifests\n"))
315 repo.ui.note(_("resolving manifests\n"))
264 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
316 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
265 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
317 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
266
318
267 m1 = p1.manifest()
319 m1 = p1.manifest()
268 m2 = p2.manifest()
320 m2 = p2.manifest()
269 ma = pa.manifest()
321 ma = pa.manifest()
270 backwards = (pa == p2)
322 backwards = (pa == p2)
271 action = []
323 action = []
272 copy = {}
324 copy = {}
273 diverge = {}
325 diverge = {}
274
326
275 def fmerge(f, f2=None, fa=None):
327 def fmerge(f, f2=None, fa=None):
276 """merge flags"""
328 """merge flags"""
277 if not f2:
329 if not f2:
278 f2 = f
330 f2 = f
279 fa = f
331 fa = f
280 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
332 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
281 if ((a^b) | (a^c)) ^ a:
333 if ((a^b) | (a^c)) ^ a:
282 return 'x'
334 return 'x'
283 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
335 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
284 if ((a^b) | (a^c)) ^ a:
336 if ((a^b) | (a^c)) ^ a:
285 return 'l'
337 return 'l'
286 return ''
338 return ''
287
339
288 def act(msg, m, f, *args):
340 def act(msg, m, f, *args):
289 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
341 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
290 action.append((f, m) + args)
342 action.append((f, m) + args)
291
343
292 if not (backwards or overwrite):
344 if not (backwards or overwrite):
293 copy, diverge = findcopies(repo, m1, m2, ma, pa.rev())
345 rev1 = p1.rev()
346 if rev1 is None:
347 # p1 is a workingctx
348 rev1 = p1.parents()[0].rev()
349 limit = min(symmetricdifference(repo, rev1, p2.rev()))
350 copy, diverge = findcopies(repo, m1, m2, ma, limit)
294
351
295 for of, fl in diverge.items():
352 for of, fl in diverge.items():
296 act("divergent renames", "dr", of, fl)
353 act("divergent renames", "dr", of, fl)
297
354
298 copied = dict.fromkeys(copy.values())
355 copied = dict.fromkeys(copy.values())
299
356
300 # Compare manifests
357 # Compare manifests
301 for f, n in m1.iteritems():
358 for f, n in m1.iteritems():
302 if partial and not partial(f):
359 if partial and not partial(f):
303 continue
360 continue
304 if f in m2:
361 if f in m2:
305 # are files different?
362 # are files different?
306 if n != m2[f]:
363 if n != m2[f]:
307 a = ma.get(f, nullid)
364 a = ma.get(f, nullid)
308 # are both different from the ancestor?
365 # are both different from the ancestor?
309 if not overwrite and n != a and m2[f] != a:
366 if not overwrite and n != a and m2[f] != a:
310 act("versions differ", "m", f, f, f, fmerge(f), False)
367 act("versions differ", "m", f, f, f, fmerge(f), False)
311 # are we clobbering?
368 # are we clobbering?
312 # is remote's version newer?
369 # is remote's version newer?
313 # or are we going back in time and clean?
370 # or are we going back in time and clean?
314 elif overwrite or m2[f] != a or (backwards and not n[20:]):
371 elif overwrite or m2[f] != a or (backwards and not n[20:]):
315 act("remote is newer", "g", f, m2.flags(f))
372 act("remote is newer", "g", f, m2.flags(f))
316 # local is newer, not overwrite, check mode bits
373 # local is newer, not overwrite, check mode bits
317 elif fmerge(f) != m1.flags(f):
374 elif fmerge(f) != m1.flags(f):
318 act("update permissions", "e", f, m2.flags(f))
375 act("update permissions", "e", f, m2.flags(f))
319 # contents same, check mode bits
376 # contents same, check mode bits
320 elif m1.flags(f) != m2.flags(f):
377 elif m1.flags(f) != m2.flags(f):
321 if overwrite or fmerge(f) != m1.flags(f):
378 if overwrite or fmerge(f) != m1.flags(f):
322 act("update permissions", "e", f, m2.flags(f))
379 act("update permissions", "e", f, m2.flags(f))
323 elif f in copied:
380 elif f in copied:
324 continue
381 continue
325 elif f in copy:
382 elif f in copy:
326 f2 = copy[f]
383 f2 = copy[f]
327 if f2 not in m2: # directory rename
384 if f2 not in m2: # directory rename
328 act("remote renamed directory to " + f2, "d",
385 act("remote renamed directory to " + f2, "d",
329 f, None, f2, m1.flags(f))
386 f, None, f2, m1.flags(f))
330 elif f2 in m1: # case 2 A,B/B/B
387 elif f2 in m1: # case 2 A,B/B/B
331 act("local copied to " + f2, "m",
388 act("local copied to " + f2, "m",
332 f, f2, f, fmerge(f, f2, f2), False)
389 f, f2, f, fmerge(f, f2, f2), False)
333 else: # case 4,21 A/B/B
390 else: # case 4,21 A/B/B
334 act("local moved to " + f2, "m",
391 act("local moved to " + f2, "m",
335 f, f2, f, fmerge(f, f2, f2), False)
392 f, f2, f, fmerge(f, f2, f2), False)
336 elif f in ma:
393 elif f in ma:
337 if n != ma[f] and not overwrite:
394 if n != ma[f] and not overwrite:
338 if repo.ui.prompt(
395 if repo.ui.prompt(
339 (_(" local changed %s which remote deleted\n") % f) +
396 (_(" local changed %s which remote deleted\n") % f) +
340 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
397 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
341 act("prompt delete", "r", f)
398 act("prompt delete", "r", f)
342 else:
399 else:
343 act("other deleted", "r", f)
400 act("other deleted", "r", f)
344 else:
401 else:
345 # file is created on branch or in working directory
402 # file is created on branch or in working directory
346 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
403 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
347 act("remote deleted", "r", f)
404 act("remote deleted", "r", f)
348
405
349 for f, n in m2.iteritems():
406 for f, n in m2.iteritems():
350 if partial and not partial(f):
407 if partial and not partial(f):
351 continue
408 continue
352 if f in m1:
409 if f in m1:
353 continue
410 continue
354 if f in copied:
411 if f in copied:
355 continue
412 continue
356 if f in copy:
413 if f in copy:
357 f2 = copy[f]
414 f2 = copy[f]
358 if f2 not in m1: # directory rename
415 if f2 not in m1: # directory rename
359 act("local renamed directory to " + f2, "d",
416 act("local renamed directory to " + f2, "d",
360 None, f, f2, m2.flags(f))
417 None, f, f2, m2.flags(f))
361 elif f2 in m2: # rename case 1, A/A,B/A
418 elif f2 in m2: # rename case 1, A/A,B/A
362 act("remote copied to " + f, "m",
419 act("remote copied to " + f, "m",
363 f2, f, f, fmerge(f2, f, f2), False)
420 f2, f, f, fmerge(f2, f, f2), False)
364 else: # case 3,20 A/B/A
421 else: # case 3,20 A/B/A
365 act("remote moved to " + f, "m",
422 act("remote moved to " + f, "m",
366 f2, f, f, fmerge(f2, f, f2), True)
423 f2, f, f, fmerge(f2, f, f2), True)
367 elif f in ma:
424 elif f in ma:
368 if overwrite or backwards:
425 if overwrite or backwards:
369 act("recreating", "g", f, m2.flags(f))
426 act("recreating", "g", f, m2.flags(f))
370 elif n != ma[f]:
427 elif n != ma[f]:
371 if repo.ui.prompt(
428 if repo.ui.prompt(
372 (_("remote changed %s which local deleted\n") % f) +
429 (_("remote changed %s which local deleted\n") % f) +
373 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
430 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
374 act("prompt recreating", "g", f, m2.flags(f))
431 act("prompt recreating", "g", f, m2.flags(f))
375 else:
432 else:
376 act("remote created", "g", f, m2.flags(f))
433 act("remote created", "g", f, m2.flags(f))
377
434
378 return action
435 return action
379
436
380 def applyupdates(repo, action, wctx, mctx):
437 def applyupdates(repo, action, wctx, mctx):
381 "apply the merge action list to the working directory"
438 "apply the merge action list to the working directory"
382
439
383 updated, merged, removed, unresolved = 0, 0, 0, 0
440 updated, merged, removed, unresolved = 0, 0, 0, 0
384 action.sort()
441 action.sort()
385 # prescan for copy/renames
442 # prescan for copy/renames
386 for a in action:
443 for a in action:
387 f, m = a[:2]
444 f, m = a[:2]
388 if m == 'm': # merge
445 if m == 'm': # merge
389 f2, fd, flags, move = a[2:]
446 f2, fd, flags, move = a[2:]
390 if f != fd:
447 if f != fd:
391 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
448 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
392 repo.wwrite(fd, repo.wread(f), flags)
449 repo.wwrite(fd, repo.wread(f), flags)
393
450
394 audit_path = util.path_auditor(repo.root)
451 audit_path = util.path_auditor(repo.root)
395
452
396 for a in action:
453 for a in action:
397 f, m = a[:2]
454 f, m = a[:2]
398 if f and f[0] == "/":
455 if f and f[0] == "/":
399 continue
456 continue
400 if m == "r": # remove
457 if m == "r": # remove
401 repo.ui.note(_("removing %s\n") % f)
458 repo.ui.note(_("removing %s\n") % f)
402 audit_path(f)
459 audit_path(f)
403 try:
460 try:
404 util.unlink(repo.wjoin(f))
461 util.unlink(repo.wjoin(f))
405 except OSError, inst:
462 except OSError, inst:
406 if inst.errno != errno.ENOENT:
463 if inst.errno != errno.ENOENT:
407 repo.ui.warn(_("update failed to remove %s: %s!\n") %
464 repo.ui.warn(_("update failed to remove %s: %s!\n") %
408 (f, inst.strerror))
465 (f, inst.strerror))
409 removed += 1
466 removed += 1
410 elif m == "m": # merge
467 elif m == "m": # merge
411 f2, fd, flags, move = a[2:]
468 f2, fd, flags, move = a[2:]
412 r = filemerge(repo, f, fd, f2, wctx, mctx)
469 r = filemerge(repo, f, fd, f2, wctx, mctx)
413 if r > 0:
470 if r > 0:
414 unresolved += 1
471 unresolved += 1
415 else:
472 else:
416 if r is None:
473 if r is None:
417 updated += 1
474 updated += 1
418 else:
475 else:
419 merged += 1
476 merged += 1
420 util.set_exec(repo.wjoin(fd), "x" in flags)
477 util.set_exec(repo.wjoin(fd), "x" in flags)
421 if f != fd and move and util.lexists(repo.wjoin(f)):
478 if f != fd and move and util.lexists(repo.wjoin(f)):
422 repo.ui.debug(_("removing %s\n") % f)
479 repo.ui.debug(_("removing %s\n") % f)
423 os.unlink(repo.wjoin(f))
480 os.unlink(repo.wjoin(f))
424 elif m == "g": # get
481 elif m == "g": # get
425 flags = a[2]
482 flags = a[2]
426 repo.ui.note(_("getting %s\n") % f)
483 repo.ui.note(_("getting %s\n") % f)
427 t = mctx.filectx(f).data()
484 t = mctx.filectx(f).data()
428 repo.wwrite(f, t, flags)
485 repo.wwrite(f, t, flags)
429 updated += 1
486 updated += 1
430 elif m == "d": # directory rename
487 elif m == "d": # directory rename
431 f2, fd, flags = a[2:]
488 f2, fd, flags = a[2:]
432 if f:
489 if f:
433 repo.ui.note(_("moving %s to %s\n") % (f, fd))
490 repo.ui.note(_("moving %s to %s\n") % (f, fd))
434 t = wctx.filectx(f).data()
491 t = wctx.filectx(f).data()
435 repo.wwrite(fd, t, flags)
492 repo.wwrite(fd, t, flags)
436 util.unlink(repo.wjoin(f))
493 util.unlink(repo.wjoin(f))
437 if f2:
494 if f2:
438 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
495 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
439 t = mctx.filectx(f2).data()
496 t = mctx.filectx(f2).data()
440 repo.wwrite(fd, t, flags)
497 repo.wwrite(fd, t, flags)
441 updated += 1
498 updated += 1
442 elif m == "dr": # divergent renames
499 elif m == "dr": # divergent renames
443 fl = a[2]
500 fl = a[2]
444 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
501 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
445 for nf in fl:
502 for nf in fl:
446 repo.ui.warn(" %s\n" % nf)
503 repo.ui.warn(" %s\n" % nf)
447 elif m == "e": # exec
504 elif m == "e": # exec
448 flags = a[2]
505 flags = a[2]
449 util.set_exec(repo.wjoin(f), flags)
506 util.set_exec(repo.wjoin(f), flags)
450
507
451 return updated, merged, removed, unresolved
508 return updated, merged, removed, unresolved
452
509
453 def recordupdates(repo, action, branchmerge):
510 def recordupdates(repo, action, branchmerge):
454 "record merge actions to the dirstate"
511 "record merge actions to the dirstate"
455
512
456 for a in action:
513 for a in action:
457 f, m = a[:2]
514 f, m = a[:2]
458 if m == "r": # remove
515 if m == "r": # remove
459 if branchmerge:
516 if branchmerge:
460 repo.dirstate.remove(f)
517 repo.dirstate.remove(f)
461 else:
518 else:
462 repo.dirstate.forget(f)
519 repo.dirstate.forget(f)
463 elif m == "f": # forget
520 elif m == "f": # forget
464 repo.dirstate.forget(f)
521 repo.dirstate.forget(f)
465 elif m in "ge": # get or exec change
522 elif m in "ge": # get or exec change
466 if branchmerge:
523 if branchmerge:
467 repo.dirstate.normaldirty(f)
524 repo.dirstate.normaldirty(f)
468 else:
525 else:
469 repo.dirstate.normal(f)
526 repo.dirstate.normal(f)
470 elif m == "m": # merge
527 elif m == "m": # merge
471 f2, fd, flag, move = a[2:]
528 f2, fd, flag, move = a[2:]
472 if branchmerge:
529 if branchmerge:
473 # We've done a branch merge, mark this file as merged
530 # We've done a branch merge, mark this file as merged
474 # so that we properly record the merger later
531 # so that we properly record the merger later
475 repo.dirstate.merge(fd)
532 repo.dirstate.merge(fd)
476 if f != f2: # copy/rename
533 if f != f2: # copy/rename
477 if move:
534 if move:
478 repo.dirstate.remove(f)
535 repo.dirstate.remove(f)
479 if f != fd:
536 if f != fd:
480 repo.dirstate.copy(f, fd)
537 repo.dirstate.copy(f, fd)
481 else:
538 else:
482 repo.dirstate.copy(f2, fd)
539 repo.dirstate.copy(f2, fd)
483 else:
540 else:
484 # We've update-merged a locally modified file, so
541 # We've update-merged a locally modified file, so
485 # we set the dirstate to emulate a normal checkout
542 # we set the dirstate to emulate a normal checkout
486 # of that file some time in the past. Thus our
543 # of that file some time in the past. Thus our
487 # merge will appear as a normal local file
544 # merge will appear as a normal local file
488 # modification.
545 # modification.
489 repo.dirstate.normaldirty(fd)
546 repo.dirstate.normaldirty(fd)
490 if move:
547 if move:
491 repo.dirstate.forget(f)
548 repo.dirstate.forget(f)
492 elif m == "d": # directory rename
549 elif m == "d": # directory rename
493 f2, fd, flag = a[2:]
550 f2, fd, flag = a[2:]
494 if not f2 and f not in repo.dirstate:
551 if not f2 and f not in repo.dirstate:
495 # untracked file moved
552 # untracked file moved
496 continue
553 continue
497 if branchmerge:
554 if branchmerge:
498 repo.dirstate.add(fd)
555 repo.dirstate.add(fd)
499 if f:
556 if f:
500 repo.dirstate.remove(f)
557 repo.dirstate.remove(f)
501 repo.dirstate.copy(f, fd)
558 repo.dirstate.copy(f, fd)
502 if f2:
559 if f2:
503 repo.dirstate.copy(f2, fd)
560 repo.dirstate.copy(f2, fd)
504 else:
561 else:
505 repo.dirstate.normal(fd)
562 repo.dirstate.normal(fd)
506 if f:
563 if f:
507 repo.dirstate.forget(f)
564 repo.dirstate.forget(f)
508
565
509 def update(repo, node, branchmerge, force, partial):
566 def update(repo, node, branchmerge, force, partial):
510 """
567 """
511 Perform a merge between the working directory and the given node
568 Perform a merge between the working directory and the given node
512
569
513 branchmerge = whether to merge between branches
570 branchmerge = whether to merge between branches
514 force = whether to force branch merging or file overwriting
571 force = whether to force branch merging or file overwriting
515 partial = a function to filter file lists (dirstate not updated)
572 partial = a function to filter file lists (dirstate not updated)
516 """
573 """
517
574
518 wlock = repo.wlock()
575 wlock = repo.wlock()
519 try:
576 try:
520 wc = repo.workingctx()
577 wc = repo.workingctx()
521 if node is None:
578 if node is None:
522 # tip of current branch
579 # tip of current branch
523 try:
580 try:
524 node = repo.branchtags()[wc.branch()]
581 node = repo.branchtags()[wc.branch()]
525 except KeyError:
582 except KeyError:
526 raise util.Abort(_("branch %s not found") % wc.branch())
583 raise util.Abort(_("branch %s not found") % wc.branch())
527 overwrite = force and not branchmerge
584 overwrite = force and not branchmerge
528 forcemerge = force and branchmerge
585 forcemerge = force and branchmerge
529 pl = wc.parents()
586 pl = wc.parents()
530 p1, p2 = pl[0], repo.changectx(node)
587 p1, p2 = pl[0], repo.changectx(node)
531 pa = p1.ancestor(p2)
588 pa = p1.ancestor(p2)
532 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
589 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
533 fastforward = False
590 fastforward = False
534
591
535 ### check phase
592 ### check phase
536 if not overwrite and len(pl) > 1:
593 if not overwrite and len(pl) > 1:
537 raise util.Abort(_("outstanding uncommitted merges"))
594 raise util.Abort(_("outstanding uncommitted merges"))
538 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
595 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
539 if branchmerge:
596 if branchmerge:
540 if p1.branch() != p2.branch() and pa != p2:
597 if p1.branch() != p2.branch() and pa != p2:
541 fastforward = True
598 fastforward = True
542 else:
599 else:
543 raise util.Abort(_("there is nothing to merge, just use "
600 raise util.Abort(_("there is nothing to merge, just use "
544 "'hg update' or look at 'hg heads'"))
601 "'hg update' or look at 'hg heads'"))
545 elif not (overwrite or branchmerge):
602 elif not (overwrite or branchmerge):
546 raise util.Abort(_("update spans branches, use 'hg merge' "
603 raise util.Abort(_("update spans branches, use 'hg merge' "
547 "or 'hg update -C' to lose changes"))
604 "or 'hg update -C' to lose changes"))
548 if branchmerge and not forcemerge:
605 if branchmerge and not forcemerge:
549 if wc.files():
606 if wc.files():
550 raise util.Abort(_("outstanding uncommitted changes"))
607 raise util.Abort(_("outstanding uncommitted changes"))
551
608
552 ### calculate phase
609 ### calculate phase
553 action = []
610 action = []
554 if not force:
611 if not force:
555 checkunknown(wc, p2)
612 checkunknown(wc, p2)
556 if not util.checkfolding(repo.path):
613 if not util.checkfolding(repo.path):
557 checkcollision(p2)
614 checkcollision(p2)
558 if not branchmerge:
615 if not branchmerge:
559 action += forgetremoved(wc, p2)
616 action += forgetremoved(wc, p2)
560 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
617 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
561
618
562 ### apply phase
619 ### apply phase
563 if not branchmerge: # just jump to the new rev
620 if not branchmerge: # just jump to the new rev
564 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
621 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
565 if not partial:
622 if not partial:
566 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
623 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
567
624
568 stats = applyupdates(repo, action, wc, p2)
625 stats = applyupdates(repo, action, wc, p2)
569
626
570 if not partial:
627 if not partial:
571 recordupdates(repo, action, branchmerge)
628 recordupdates(repo, action, branchmerge)
572 repo.dirstate.setparents(fp1, fp2)
629 repo.dirstate.setparents(fp1, fp2)
573 if not branchmerge and not fastforward:
630 if not branchmerge and not fastforward:
574 repo.dirstate.setbranch(p2.branch())
631 repo.dirstate.setbranch(p2.branch())
575 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
632 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
576
633
577 return stats
634 return stats
578 finally:
635 finally:
579 del wlock
636 del wlock
General Comments 0
You need to be logged in to leave comments. Login now