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