##// END OF EJS Templates
merge: avoid double deletion mentioned in issue636
Matt Mackall -
r5059:8d9bdcbb default
parent child Browse files
Show More
@@ -0,0 +1,30 b''
1 #!/bin/sh
2
3 mkdir t
4 cd t
5 hg init
6
7 echo 1 > a
8 hg ci -qAm "first" -d "1000000 0"
9
10 hg cp a b
11 hg mv a c
12 echo 2 >> b
13 echo 2 >> c
14
15 hg ci -qAm "second" -d "1000000 0"
16
17 hg co -C 0
18
19 echo 0 > a
20 echo 1 >> a
21
22 hg ci -qAm "other" -d "1000000 0"
23
24 hg merge --debug
25
26 echo "-- b --"
27 cat b
28
29 echo "-- c --"
30 cat c
@@ -0,0 +1,23 b''
1 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
2 resolving manifests
3 overwrite None partial False
4 ancestor 583c7b748052 local fb3948d97f07+ remote 40da226db0f0
5 a: remote moved to c -> m
6 a: remote moved to b -> m
7 copying a to b
8 copying a to c
9 merging a and b
10 my a@fb3948d97f07+ other b@40da226db0f0 ancestor a@583c7b748052
11 removing a
12 merging a and c
13 my a@fb3948d97f07+ other c@40da226db0f0 ancestor a@583c7b748052
14 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
15 (branch merge, don't forget to commit)
16 -- b --
17 0
18 1
19 2
20 -- c --
21 0
22 1
23 2
@@ -1,578 +1,578 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
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 manifestmerge(repo, p1, p2, pa, overwrite, partial):
255 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
256 """
256 """
257 Merge p1 and p2 with ancestor ma and generate merge action list
257 Merge p1 and p2 with ancestor ma and generate merge action list
258
258
259 overwrite = whether we clobber working files
259 overwrite = whether we clobber working files
260 partial = function to filter file lists
260 partial = function to filter file lists
261 """
261 """
262
262
263 repo.ui.note(_("resolving manifests\n"))
263 repo.ui.note(_("resolving manifests\n"))
264 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
264 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))
265 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
266
266
267 m1 = p1.manifest()
267 m1 = p1.manifest()
268 m2 = p2.manifest()
268 m2 = p2.manifest()
269 ma = pa.manifest()
269 ma = pa.manifest()
270 backwards = (pa == p2)
270 backwards = (pa == p2)
271 action = []
271 action = []
272 copy = {}
272 copy = {}
273 diverge = {}
273 diverge = {}
274
274
275 def fmerge(f, f2=None, fa=None):
275 def fmerge(f, f2=None, fa=None):
276 """merge flags"""
276 """merge flags"""
277 if not f2:
277 if not f2:
278 f2 = f
278 f2 = f
279 fa = f
279 fa = f
280 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
280 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
281 if ((a^b) | (a^c)) ^ a:
281 if ((a^b) | (a^c)) ^ a:
282 return 'x'
282 return 'x'
283 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
283 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
284 if ((a^b) | (a^c)) ^ a:
284 if ((a^b) | (a^c)) ^ a:
285 return 'l'
285 return 'l'
286 return ''
286 return ''
287
287
288 def act(msg, m, f, *args):
288 def act(msg, m, f, *args):
289 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
289 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
290 action.append((f, m) + args)
290 action.append((f, m) + args)
291
291
292 if not (backwards or overwrite):
292 if not (backwards or overwrite):
293 copy, diverge = findcopies(repo, m1, m2, ma, pa.rev())
293 copy, diverge = findcopies(repo, m1, m2, ma, pa.rev())
294
294
295 for of, fl in diverge.items():
295 for of, fl in diverge.items():
296 act("divergent renames", "dr", of, fl)
296 act("divergent renames", "dr", of, fl)
297
297
298 copied = dict.fromkeys(copy.values())
298 copied = dict.fromkeys(copy.values())
299
299
300 # Compare manifests
300 # Compare manifests
301 for f, n in m1.iteritems():
301 for f, n in m1.iteritems():
302 if partial and not partial(f):
302 if partial and not partial(f):
303 continue
303 continue
304 if f in m2:
304 if f in m2:
305 # are files different?
305 # are files different?
306 if n != m2[f]:
306 if n != m2[f]:
307 a = ma.get(f, nullid)
307 a = ma.get(f, nullid)
308 # are both different from the ancestor?
308 # are both different from the ancestor?
309 if not overwrite and n != a and m2[f] != a:
309 if not overwrite and n != a and m2[f] != a:
310 act("versions differ", "m", f, f, f, fmerge(f), False)
310 act("versions differ", "m", f, f, f, fmerge(f), False)
311 # are we clobbering?
311 # are we clobbering?
312 # is remote's version newer?
312 # is remote's version newer?
313 # or are we going back in time and clean?
313 # or are we going back in time and clean?
314 elif overwrite or m2[f] != a or (backwards and not n[20:]):
314 elif overwrite or m2[f] != a or (backwards and not n[20:]):
315 act("remote is newer", "g", f, m2.flags(f))
315 act("remote is newer", "g", f, m2.flags(f))
316 # local is newer, not overwrite, check mode bits
316 # local is newer, not overwrite, check mode bits
317 elif fmerge(f) != m1.flags(f):
317 elif fmerge(f) != m1.flags(f):
318 act("update permissions", "e", f, m2.flags(f))
318 act("update permissions", "e", f, m2.flags(f))
319 # contents same, check mode bits
319 # contents same, check mode bits
320 elif m1.flags(f) != m2.flags(f):
320 elif m1.flags(f) != m2.flags(f):
321 if overwrite or fmerge(f) != m1.flags(f):
321 if overwrite or fmerge(f) != m1.flags(f):
322 act("update permissions", "e", f, m2.flags(f))
322 act("update permissions", "e", f, m2.flags(f))
323 elif f in copied:
323 elif f in copied:
324 continue
324 continue
325 elif f in copy:
325 elif f in copy:
326 f2 = copy[f]
326 f2 = copy[f]
327 if f2 not in m2: # directory rename
327 if f2 not in m2: # directory rename
328 act("remote renamed directory to " + f2, "d",
328 act("remote renamed directory to " + f2, "d",
329 f, None, f2, m1.flags(f))
329 f, None, f2, m1.flags(f))
330 elif f2 in m1: # case 2 A,B/B/B
330 elif f2 in m1: # case 2 A,B/B/B
331 act("local copied to " + f2, "m",
331 act("local copied to " + f2, "m",
332 f, f2, f, fmerge(f, f2, f2), False)
332 f, f2, f, fmerge(f, f2, f2), False)
333 else: # case 4,21 A/B/B
333 else: # case 4,21 A/B/B
334 act("local moved to " + f2, "m",
334 act("local moved to " + f2, "m",
335 f, f2, f, fmerge(f, f2, f2), False)
335 f, f2, f, fmerge(f, f2, f2), False)
336 elif f in ma:
336 elif f in ma:
337 if n != ma[f] and not overwrite:
337 if n != ma[f] and not overwrite:
338 if repo.ui.prompt(
338 if repo.ui.prompt(
339 (_(" local changed %s which remote deleted\n") % f) +
339 (_(" local changed %s which remote deleted\n") % f) +
340 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
340 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
341 act("prompt delete", "r", f)
341 act("prompt delete", "r", f)
342 else:
342 else:
343 act("other deleted", "r", f)
343 act("other deleted", "r", f)
344 else:
344 else:
345 # file is created on branch or in working directory
345 # file is created on branch or in working directory
346 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
346 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
347 act("remote deleted", "r", f)
347 act("remote deleted", "r", f)
348
348
349 for f, n in m2.iteritems():
349 for f, n in m2.iteritems():
350 if partial and not partial(f):
350 if partial and not partial(f):
351 continue
351 continue
352 if f in m1:
352 if f in m1:
353 continue
353 continue
354 if f in copied:
354 if f in copied:
355 continue
355 continue
356 if f in copy:
356 if f in copy:
357 f2 = copy[f]
357 f2 = copy[f]
358 if f2 not in m1: # directory rename
358 if f2 not in m1: # directory rename
359 act("local renamed directory to " + f2, "d",
359 act("local renamed directory to " + f2, "d",
360 None, f, f2, m2.flags(f))
360 None, f, f2, m2.flags(f))
361 elif f2 in m2: # rename case 1, A/A,B/A
361 elif f2 in m2: # rename case 1, A/A,B/A
362 act("remote copied to " + f, "m",
362 act("remote copied to " + f, "m",
363 f2, f, f, fmerge(f2, f, f2), False)
363 f2, f, f, fmerge(f2, f, f2), False)
364 else: # case 3,20 A/B/A
364 else: # case 3,20 A/B/A
365 act("remote moved to " + f, "m",
365 act("remote moved to " + f, "m",
366 f2, f, f, fmerge(f2, f, f2), True)
366 f2, f, f, fmerge(f2, f, f2), True)
367 elif f in ma:
367 elif f in ma:
368 if overwrite or backwards:
368 if overwrite or backwards:
369 act("recreating", "g", f, m2.flags(f))
369 act("recreating", "g", f, m2.flags(f))
370 elif n != ma[f]:
370 elif n != ma[f]:
371 if repo.ui.prompt(
371 if repo.ui.prompt(
372 (_("remote changed %s which local deleted\n") % f) +
372 (_("remote changed %s which local deleted\n") % f) +
373 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
373 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
374 act("prompt recreating", "g", f, m2.flags(f))
374 act("prompt recreating", "g", f, m2.flags(f))
375 else:
375 else:
376 act("remote created", "g", f, m2.flags(f))
376 act("remote created", "g", f, m2.flags(f))
377
377
378 return action
378 return action
379
379
380 def applyupdates(repo, action, wctx, mctx):
380 def applyupdates(repo, action, wctx, mctx):
381 "apply the merge action list to the working directory"
381 "apply the merge action list to the working directory"
382
382
383 updated, merged, removed, unresolved = 0, 0, 0, 0
383 updated, merged, removed, unresolved = 0, 0, 0, 0
384 action.sort()
384 action.sort()
385 # prescan for copy/renames
385 # prescan for copy/renames
386 for a in action:
386 for a in action:
387 f, m = a[:2]
387 f, m = a[:2]
388 if m == 'm': # merge
388 if m == 'm': # merge
389 f2, fd, flags, move = a[2:]
389 f2, fd, flags, move = a[2:]
390 if f != fd:
390 if f != fd:
391 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
391 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
392 repo.wwrite(fd, repo.wread(f), flags)
392 repo.wwrite(fd, repo.wread(f), flags)
393
393
394 for a in action:
394 for a in action:
395 f, m = a[:2]
395 f, m = a[:2]
396 if f and f[0] == "/":
396 if f and f[0] == "/":
397 continue
397 continue
398 if m == "r": # remove
398 if m == "r": # remove
399 repo.ui.note(_("removing %s\n") % f)
399 repo.ui.note(_("removing %s\n") % f)
400 util.audit_path(f)
400 util.audit_path(f)
401 try:
401 try:
402 util.unlink(repo.wjoin(f))
402 util.unlink(repo.wjoin(f))
403 except OSError, inst:
403 except OSError, inst:
404 if inst.errno != errno.ENOENT:
404 if inst.errno != errno.ENOENT:
405 repo.ui.warn(_("update failed to remove %s: %s!\n") %
405 repo.ui.warn(_("update failed to remove %s: %s!\n") %
406 (f, inst.strerror))
406 (f, inst.strerror))
407 removed += 1
407 removed += 1
408 elif m == "m": # merge
408 elif m == "m": # merge
409 f2, fd, flags, move = a[2:]
409 f2, fd, flags, move = a[2:]
410 r = filemerge(repo, f, fd, f2, wctx, mctx)
410 r = filemerge(repo, f, fd, f2, wctx, mctx)
411 if r > 0:
411 if r > 0:
412 unresolved += 1
412 unresolved += 1
413 else:
413 else:
414 if r is None:
414 if r is None:
415 updated += 1
415 updated += 1
416 else:
416 else:
417 merged += 1
417 merged += 1
418 if f != fd and move:
418 util.set_exec(repo.wjoin(fd), "x" in flags)
419 if f != fd and move and util.lexists(repo.wjoin(f)):
419 repo.ui.debug(_("removing %s\n") % f)
420 repo.ui.debug(_("removing %s\n") % f)
420 os.unlink(repo.wjoin(f))
421 os.unlink(repo.wjoin(f))
421 util.set_exec(repo.wjoin(fd), "x" in flags)
422 elif m == "g": # get
422 elif m == "g": # get
423 flags = a[2]
423 flags = a[2]
424 repo.ui.note(_("getting %s\n") % f)
424 repo.ui.note(_("getting %s\n") % f)
425 t = mctx.filectx(f).data()
425 t = mctx.filectx(f).data()
426 repo.wwrite(f, t, flags)
426 repo.wwrite(f, t, flags)
427 updated += 1
427 updated += 1
428 elif m == "d": # directory rename
428 elif m == "d": # directory rename
429 f2, fd, flags = a[2:]
429 f2, fd, flags = a[2:]
430 if f:
430 if f:
431 repo.ui.note(_("moving %s to %s\n") % (f, fd))
431 repo.ui.note(_("moving %s to %s\n") % (f, fd))
432 t = wctx.filectx(f).data()
432 t = wctx.filectx(f).data()
433 repo.wwrite(fd, t, flags)
433 repo.wwrite(fd, t, flags)
434 util.unlink(repo.wjoin(f))
434 util.unlink(repo.wjoin(f))
435 if f2:
435 if f2:
436 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
436 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
437 t = mctx.filectx(f2).data()
437 t = mctx.filectx(f2).data()
438 repo.wwrite(fd, t, flags)
438 repo.wwrite(fd, t, flags)
439 updated += 1
439 updated += 1
440 elif m == "dr": # divergent renames
440 elif m == "dr": # divergent renames
441 fl = a[2]
441 fl = a[2]
442 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
442 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
443 for nf in fl:
443 for nf in fl:
444 repo.ui.warn(" %s\n" % nf)
444 repo.ui.warn(" %s\n" % nf)
445 elif m == "e": # exec
445 elif m == "e": # exec
446 flags = a[2]
446 flags = a[2]
447 util.set_exec(repo.wjoin(f), flags)
447 util.set_exec(repo.wjoin(f), flags)
448
448
449 return updated, merged, removed, unresolved
449 return updated, merged, removed, unresolved
450
450
451 def recordupdates(repo, action, branchmerge):
451 def recordupdates(repo, action, branchmerge):
452 "record merge actions to the dirstate"
452 "record merge actions to the dirstate"
453
453
454 for a in action:
454 for a in action:
455 f, m = a[:2]
455 f, m = a[:2]
456 if m == "r": # remove
456 if m == "r": # remove
457 if branchmerge:
457 if branchmerge:
458 repo.dirstate.update([f], 'r')
458 repo.dirstate.update([f], 'r')
459 else:
459 else:
460 repo.dirstate.forget([f])
460 repo.dirstate.forget([f])
461 elif m == "f": # forget
461 elif m == "f": # forget
462 repo.dirstate.forget([f])
462 repo.dirstate.forget([f])
463 elif m in "ge": # get or exec change
463 elif m in "ge": # get or exec change
464 if branchmerge:
464 if branchmerge:
465 repo.dirstate.update([f], 'n', st_mtime=-1)
465 repo.dirstate.update([f], 'n', st_mtime=-1)
466 else:
466 else:
467 repo.dirstate.update([f], 'n')
467 repo.dirstate.update([f], 'n')
468 elif m == "m": # merge
468 elif m == "m": # merge
469 f2, fd, flag, move = a[2:]
469 f2, fd, flag, move = a[2:]
470 if branchmerge:
470 if branchmerge:
471 # We've done a branch merge, mark this file as merged
471 # We've done a branch merge, mark this file as merged
472 # so that we properly record the merger later
472 # so that we properly record the merger later
473 repo.dirstate.update([fd], 'm')
473 repo.dirstate.update([fd], 'm')
474 if f != f2: # copy/rename
474 if f != f2: # copy/rename
475 if move:
475 if move:
476 repo.dirstate.update([f], 'r')
476 repo.dirstate.update([f], 'r')
477 if f != fd:
477 if f != fd:
478 repo.dirstate.copy(f, fd)
478 repo.dirstate.copy(f, fd)
479 else:
479 else:
480 repo.dirstate.copy(f2, fd)
480 repo.dirstate.copy(f2, fd)
481 else:
481 else:
482 # We've update-merged a locally modified file, so
482 # We've update-merged a locally modified file, so
483 # we set the dirstate to emulate a normal checkout
483 # we set the dirstate to emulate a normal checkout
484 # of that file some time in the past. Thus our
484 # of that file some time in the past. Thus our
485 # merge will appear as a normal local file
485 # merge will appear as a normal local file
486 # modification.
486 # modification.
487 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
487 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
488 if move:
488 if move:
489 repo.dirstate.forget([f])
489 repo.dirstate.forget([f])
490 elif m == "d": # directory rename
490 elif m == "d": # directory rename
491 f2, fd, flag = a[2:]
491 f2, fd, flag = a[2:]
492 if not f2 and f not in repo.dirstate:
492 if not f2 and f not in repo.dirstate:
493 # untracked file moved
493 # untracked file moved
494 continue
494 continue
495 if branchmerge:
495 if branchmerge:
496 repo.dirstate.update([fd], 'a')
496 repo.dirstate.update([fd], 'a')
497 if f:
497 if f:
498 repo.dirstate.update([f], 'r')
498 repo.dirstate.update([f], 'r')
499 repo.dirstate.copy(f, fd)
499 repo.dirstate.copy(f, fd)
500 if f2:
500 if f2:
501 repo.dirstate.copy(f2, fd)
501 repo.dirstate.copy(f2, fd)
502 else:
502 else:
503 repo.dirstate.update([fd], 'n')
503 repo.dirstate.update([fd], 'n')
504 if f:
504 if f:
505 repo.dirstate.forget([f])
505 repo.dirstate.forget([f])
506
506
507 def update(repo, node, branchmerge, force, partial, wlock):
507 def update(repo, node, branchmerge, force, partial, wlock):
508 """
508 """
509 Perform a merge between the working directory and the given node
509 Perform a merge between the working directory and the given node
510
510
511 branchmerge = whether to merge between branches
511 branchmerge = whether to merge between branches
512 force = whether to force branch merging or file overwriting
512 force = whether to force branch merging or file overwriting
513 partial = a function to filter file lists (dirstate not updated)
513 partial = a function to filter file lists (dirstate not updated)
514 wlock = working dir lock, if already held
514 wlock = working dir lock, if already held
515 """
515 """
516
516
517 if not wlock:
517 if not wlock:
518 wlock = repo.wlock()
518 wlock = repo.wlock()
519
519
520 wc = repo.workingctx()
520 wc = repo.workingctx()
521 if node is None:
521 if node is None:
522 # tip of current branch
522 # tip of current branch
523 try:
523 try:
524 node = repo.branchtags()[wc.branch()]
524 node = repo.branchtags()[wc.branch()]
525 except KeyError:
525 except KeyError:
526 raise util.Abort(_("branch %s not found") % wc.branch())
526 raise util.Abort(_("branch %s not found") % wc.branch())
527 overwrite = force and not branchmerge
527 overwrite = force and not branchmerge
528 forcemerge = force and branchmerge
528 forcemerge = force and branchmerge
529 pl = wc.parents()
529 pl = wc.parents()
530 p1, p2 = pl[0], repo.changectx(node)
530 p1, p2 = pl[0], repo.changectx(node)
531 pa = p1.ancestor(p2)
531 pa = p1.ancestor(p2)
532 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
532 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
533 fastforward = False
533 fastforward = False
534
534
535 ### check phase
535 ### check phase
536 if not overwrite and len(pl) > 1:
536 if not overwrite and len(pl) > 1:
537 raise util.Abort(_("outstanding uncommitted merges"))
537 raise util.Abort(_("outstanding uncommitted merges"))
538 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
538 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
539 if branchmerge:
539 if branchmerge:
540 if p1.branch() != p2.branch() and pa != p2:
540 if p1.branch() != p2.branch() and pa != p2:
541 fastforward = True
541 fastforward = True
542 else:
542 else:
543 raise util.Abort(_("there is nothing to merge, just use "
543 raise util.Abort(_("there is nothing to merge, just use "
544 "'hg update' or look at 'hg heads'"))
544 "'hg update' or look at 'hg heads'"))
545 elif not (overwrite or branchmerge):
545 elif not (overwrite or branchmerge):
546 raise util.Abort(_("update spans branches, use 'hg merge' "
546 raise util.Abort(_("update spans branches, use 'hg merge' "
547 "or 'hg update -C' to lose changes"))
547 "or 'hg update -C' to lose changes"))
548 if branchmerge and not forcemerge:
548 if branchmerge and not forcemerge:
549 if wc.files():
549 if wc.files():
550 raise util.Abort(_("outstanding uncommitted changes"))
550 raise util.Abort(_("outstanding uncommitted changes"))
551
551
552 ### calculate phase
552 ### calculate phase
553 action = []
553 action = []
554 if not force:
554 if not force:
555 checkunknown(wc, p2)
555 checkunknown(wc, p2)
556 if not util.checkfolding(repo.path):
556 if not util.checkfolding(repo.path):
557 checkcollision(p2)
557 checkcollision(p2)
558 if not branchmerge:
558 if not branchmerge:
559 action += forgetremoved(wc, p2)
559 action += forgetremoved(wc, p2)
560 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
560 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
561
561
562 ### apply phase
562 ### apply phase
563 if not branchmerge: # just jump to the new rev
563 if not branchmerge: # just jump to the new rev
564 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
564 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
565 if not partial:
565 if not partial:
566 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
566 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
567
567
568 stats = applyupdates(repo, action, wc, p2)
568 stats = applyupdates(repo, action, wc, p2)
569
569
570 if not partial:
570 if not partial:
571 recordupdates(repo, action, branchmerge)
571 recordupdates(repo, action, branchmerge)
572 repo.dirstate.setparents(fp1, fp2)
572 repo.dirstate.setparents(fp1, fp2)
573 if not branchmerge and not fastforward:
573 if not branchmerge and not fastforward:
574 repo.dirstate.setbranch(p2.branch())
574 repo.dirstate.setbranch(p2.branch())
575 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
575 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
576
576
577 return stats
577 return stats
578
578
General Comments 0
You need to be logged in to leave comments. Login now