##// END OF EJS Templates
Make fast-forward merges full merges
Brendan Cully -
r4623:66ed92ed default
parent child Browse files
Show More
@@ -1,544 +1,543 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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
158
159 def checkcopies(c, man):
159 def checkcopies(c, man):
160 '''check possible copies for filectx c'''
160 '''check possible copies for filectx c'''
161 for of in findold(c):
161 for of in findold(c):
162 if of not in man: # original file not in other manifest?
162 if of not in man: # original file not in other manifest?
163 continue
163 continue
164 c2 = ctx(of, man[of])
164 c2 = ctx(of, man[of])
165 ca = c.ancestor(c2)
165 ca = c.ancestor(c2)
166 if not ca: # unrelated?
166 if not ca: # unrelated?
167 continue
167 continue
168 # named changed on only one side?
168 # named changed on only one side?
169 if ca.path() == c.path() or ca.path() == c2.path():
169 if ca.path() == c.path() or ca.path() == c2.path():
170 fullcopy[c.path()] = of # remember for dir rename detection
170 fullcopy[c.path()] = of # remember for dir rename detection
171 if c == ca or c2 == ca: # no merge needed, ignore copy
171 if c == ca or c2 == ca: # no merge needed, ignore copy
172 continue
172 continue
173 copy[c.path()] = of
173 copy[c.path()] = of
174
174
175 if not repo.ui.configbool("merge", "followcopies", True):
175 if not repo.ui.configbool("merge", "followcopies", True):
176 return {}
176 return {}
177
177
178 # avoid silly behavior for update from empty dir
178 # avoid silly behavior for update from empty dir
179 if not m1 or not m2 or not ma:
179 if not m1 or not m2 or not ma:
180 return {}
180 return {}
181
181
182 u1 = nonoverlap(m1, m2, ma)
182 u1 = nonoverlap(m1, m2, ma)
183 u2 = nonoverlap(m2, m1, ma)
183 u2 = nonoverlap(m2, m1, ma)
184
184
185 for f in u1:
185 for f in u1:
186 checkcopies(ctx(f, m1[f]), m2)
186 checkcopies(ctx(f, m1[f]), m2)
187
187
188 for f in u2:
188 for f in u2:
189 checkcopies(ctx(f, m2[f]), m1)
189 checkcopies(ctx(f, m2[f]), m1)
190
190
191 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
191 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
192 return copy
192 return copy
193
193
194 # generate a directory move map
194 # generate a directory move map
195 d1, d2 = dirs(m1), dirs(m2)
195 d1, d2 = dirs(m1), dirs(m2)
196 invalid = {}
196 invalid = {}
197 dirmove = {}
197 dirmove = {}
198
198
199 # examine each file copy for a potential directory move, which is
199 # examine each file copy for a potential directory move, which is
200 # when all the files in a directory are moved to a new directory
200 # when all the files in a directory are moved to a new directory
201 for dst, src in fullcopy.items():
201 for dst, src in fullcopy.items():
202 dsrc, ddst = dirname(src), dirname(dst)
202 dsrc, ddst = dirname(src), dirname(dst)
203 if dsrc in invalid:
203 if dsrc in invalid:
204 # already seen to be uninteresting
204 # already seen to be uninteresting
205 continue
205 continue
206 elif dsrc in d1 and ddst in d1:
206 elif dsrc in d1 and ddst in d1:
207 # directory wasn't entirely moved locally
207 # directory wasn't entirely moved locally
208 invalid[dsrc] = True
208 invalid[dsrc] = True
209 elif dsrc in d2 and ddst in d2:
209 elif dsrc in d2 and ddst in d2:
210 # directory wasn't entirely moved remotely
210 # directory wasn't entirely moved remotely
211 invalid[dsrc] = True
211 invalid[dsrc] = True
212 elif dsrc in dirmove and dirmove[dsrc] != ddst:
212 elif dsrc in dirmove and dirmove[dsrc] != ddst:
213 # files from the same directory moved to two different places
213 # files from the same directory moved to two different places
214 invalid[dsrc] = True
214 invalid[dsrc] = True
215 else:
215 else:
216 # looks good so far
216 # looks good so far
217 dirmove[dsrc + "/"] = ddst + "/"
217 dirmove[dsrc + "/"] = ddst + "/"
218
218
219 for i in invalid:
219 for i in invalid:
220 if i in dirmove:
220 if i in dirmove:
221 del dirmove[i]
221 del dirmove[i]
222
222
223 del d1, d2, invalid
223 del d1, d2, invalid
224
224
225 if not dirmove:
225 if not dirmove:
226 return copy
226 return copy
227
227
228 # check unaccounted nonoverlapping files against directory moves
228 # check unaccounted nonoverlapping files against directory moves
229 for f in u1 + u2:
229 for f in u1 + u2:
230 if f not in fullcopy:
230 if f not in fullcopy:
231 for d in dirmove:
231 for d in dirmove:
232 if f.startswith(d):
232 if f.startswith(d):
233 # new file added in a directory that was moved, move it
233 # new file added in a directory that was moved, move it
234 copy[f] = dirmove[d] + f[len(d):]
234 copy[f] = dirmove[d] + f[len(d):]
235 break
235 break
236
236
237 return copy
237 return copy
238
238
239 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
239 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
240 """
240 """
241 Merge p1 and p2 with ancestor ma and generate merge action list
241 Merge p1 and p2 with ancestor ma and generate merge action list
242
242
243 overwrite = whether we clobber working files
243 overwrite = whether we clobber working files
244 partial = function to filter file lists
244 partial = function to filter file lists
245 """
245 """
246
246
247 repo.ui.note(_("resolving manifests\n"))
247 repo.ui.note(_("resolving manifests\n"))
248 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
248 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
249 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
249 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
250
250
251 m1 = p1.manifest()
251 m1 = p1.manifest()
252 m2 = p2.manifest()
252 m2 = p2.manifest()
253 ma = pa.manifest()
253 ma = pa.manifest()
254 backwards = (pa == p2)
254 backwards = (pa == p2)
255 action = []
255 action = []
256 copy = {}
256 copy = {}
257
257
258 def fmerge(f, f2=None, fa=None):
258 def fmerge(f, f2=None, fa=None):
259 """merge flags"""
259 """merge flags"""
260 if not f2:
260 if not f2:
261 f2 = f
261 f2 = f
262 fa = f
262 fa = f
263 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
263 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
264 if ((a^b) | (a^c)) ^ a:
264 if ((a^b) | (a^c)) ^ a:
265 return 'x'
265 return 'x'
266 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
266 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
267 if ((a^b) | (a^c)) ^ a:
267 if ((a^b) | (a^c)) ^ a:
268 return 'l'
268 return 'l'
269 return ''
269 return ''
270
270
271 def act(msg, m, f, *args):
271 def act(msg, m, f, *args):
272 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
272 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
273 action.append((f, m) + args)
273 action.append((f, m) + args)
274
274
275 if not (backwards or overwrite):
275 if not (backwards or overwrite):
276 copy = findcopies(repo, m1, m2, ma, pa.rev())
276 copy = findcopies(repo, m1, m2, ma, pa.rev())
277 copied = dict.fromkeys(copy.values())
277 copied = dict.fromkeys(copy.values())
278
278
279 # Compare manifests
279 # Compare manifests
280 for f, n in m1.iteritems():
280 for f, n in m1.iteritems():
281 if partial and not partial(f):
281 if partial and not partial(f):
282 continue
282 continue
283 if f in m2:
283 if f in m2:
284 # are files different?
284 # are files different?
285 if n != m2[f]:
285 if n != m2[f]:
286 a = ma.get(f, nullid)
286 a = ma.get(f, nullid)
287 # are both different from the ancestor?
287 # are both different from the ancestor?
288 if not overwrite and n != a and m2[f] != a:
288 if not overwrite and n != a and m2[f] != a:
289 act("versions differ", "m", f, f, f, fmerge(f), False)
289 act("versions differ", "m", f, f, f, fmerge(f), False)
290 # are we clobbering?
290 # are we clobbering?
291 # is remote's version newer?
291 # is remote's version newer?
292 # or are we going back in time and clean?
292 # or are we going back in time and clean?
293 elif overwrite or m2[f] != a or (backwards and not n[20:]):
293 elif overwrite or m2[f] != a or (backwards and not n[20:]):
294 act("remote is newer", "g", f, m2.flags(f))
294 act("remote is newer", "g", f, m2.flags(f))
295 # local is newer, not overwrite, check mode bits
295 # local is newer, not overwrite, check mode bits
296 elif fmerge(f) != m1.flags(f):
296 elif fmerge(f) != m1.flags(f):
297 act("update permissions", "e", f, m2.flags(f))
297 act("update permissions", "e", f, m2.flags(f))
298 # contents same, check mode bits
298 # contents same, check mode bits
299 elif m1.flags(f) != m2.flags(f):
299 elif m1.flags(f) != m2.flags(f):
300 if overwrite or fmerge(f) != m1.flags(f):
300 if overwrite or fmerge(f) != m1.flags(f):
301 act("update permissions", "e", f, m2.flags(f))
301 act("update permissions", "e", f, m2.flags(f))
302 elif f in copied:
302 elif f in copied:
303 continue
303 continue
304 elif f in copy:
304 elif f in copy:
305 f2 = copy[f]
305 f2 = copy[f]
306 if f2 not in m2: # directory rename
306 if f2 not in m2: # directory rename
307 act("remote renamed directory to " + f2, "d",
307 act("remote renamed directory to " + f2, "d",
308 f, None, f2, m1.flags(f))
308 f, None, f2, m1.flags(f))
309 elif f2 in m1: # case 2 A,B/B/B
309 elif f2 in m1: # case 2 A,B/B/B
310 act("local copied to " + f2, "m",
310 act("local copied to " + f2, "m",
311 f, f2, f, fmerge(f, f2, f2), False)
311 f, f2, f, fmerge(f, f2, f2), False)
312 else: # case 4,21 A/B/B
312 else: # case 4,21 A/B/B
313 act("local moved to " + f2, "m",
313 act("local moved to " + f2, "m",
314 f, f2, f, fmerge(f, f2, f2), False)
314 f, f2, f, fmerge(f, f2, f2), False)
315 elif f in ma:
315 elif f in ma:
316 if n != ma[f] and not overwrite:
316 if n != ma[f] and not overwrite:
317 if repo.ui.prompt(
317 if repo.ui.prompt(
318 (_(" local changed %s which remote deleted\n") % f) +
318 (_(" local changed %s which remote deleted\n") % f) +
319 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
319 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
320 act("prompt delete", "r", f)
320 act("prompt delete", "r", f)
321 else:
321 else:
322 act("other deleted", "r", f)
322 act("other deleted", "r", f)
323 else:
323 else:
324 # file is created on branch or in working directory
324 # file is created on branch or in working directory
325 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
325 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
326 act("remote deleted", "r", f)
326 act("remote deleted", "r", f)
327
327
328 for f, n in m2.iteritems():
328 for f, n in m2.iteritems():
329 if partial and not partial(f):
329 if partial and not partial(f):
330 continue
330 continue
331 if f in m1:
331 if f in m1:
332 continue
332 continue
333 if f in copied:
333 if f in copied:
334 continue
334 continue
335 if f in copy:
335 if f in copy:
336 f2 = copy[f]
336 f2 = copy[f]
337 if f2 not in m1: # directory rename
337 if f2 not in m1: # directory rename
338 act("local renamed directory to " + f2, "d",
338 act("local renamed directory to " + f2, "d",
339 None, f, f2, m2.flags(f))
339 None, f, f2, m2.flags(f))
340 elif f2 in m2: # rename case 1, A/A,B/A
340 elif f2 in m2: # rename case 1, A/A,B/A
341 act("remote copied to " + f, "m",
341 act("remote copied to " + f, "m",
342 f2, f, f, fmerge(f2, f, f2), False)
342 f2, f, f, fmerge(f2, f, f2), False)
343 else: # case 3,20 A/B/A
343 else: # case 3,20 A/B/A
344 act("remote moved to " + f, "m",
344 act("remote moved to " + f, "m",
345 f2, f, f, fmerge(f2, f, f2), True)
345 f2, f, f, fmerge(f2, f, f2), True)
346 elif f in ma:
346 elif f in ma:
347 if overwrite or backwards:
347 if overwrite or backwards:
348 act("recreating", "g", f, m2.flags(f))
348 act("recreating", "g", f, m2.flags(f))
349 elif n != ma[f]:
349 elif n != ma[f]:
350 if repo.ui.prompt(
350 if repo.ui.prompt(
351 (_("remote changed %s which local deleted\n") % f) +
351 (_("remote changed %s which local deleted\n") % f) +
352 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
352 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
353 act("prompt recreating", "g", f, m2.flags(f))
353 act("prompt recreating", "g", f, m2.flags(f))
354 else:
354 else:
355 act("remote created", "g", f, m2.flags(f))
355 act("remote created", "g", f, m2.flags(f))
356
356
357 return action
357 return action
358
358
359 def applyupdates(repo, action, wctx, mctx):
359 def applyupdates(repo, action, wctx, mctx):
360 "apply the merge action list to the working directory"
360 "apply the merge action list to the working directory"
361
361
362 updated, merged, removed, unresolved = 0, 0, 0, 0
362 updated, merged, removed, unresolved = 0, 0, 0, 0
363 action.sort()
363 action.sort()
364 for a in action:
364 for a in action:
365 f, m = a[:2]
365 f, m = a[:2]
366 if f and f[0] == "/":
366 if f and f[0] == "/":
367 continue
367 continue
368 if m == "r": # remove
368 if m == "r": # remove
369 repo.ui.note(_("removing %s\n") % f)
369 repo.ui.note(_("removing %s\n") % f)
370 util.audit_path(f)
370 util.audit_path(f)
371 try:
371 try:
372 util.unlink(repo.wjoin(f))
372 util.unlink(repo.wjoin(f))
373 except OSError, inst:
373 except OSError, inst:
374 if inst.errno != errno.ENOENT:
374 if inst.errno != errno.ENOENT:
375 repo.ui.warn(_("update failed to remove %s: %s!\n") %
375 repo.ui.warn(_("update failed to remove %s: %s!\n") %
376 (f, inst.strerror))
376 (f, inst.strerror))
377 removed += 1
377 removed += 1
378 elif m == "m": # merge
378 elif m == "m": # merge
379 f2, fd, flags, move = a[2:]
379 f2, fd, flags, move = a[2:]
380 r = filemerge(repo, f, f2, wctx, mctx)
380 r = filemerge(repo, f, f2, wctx, mctx)
381 if r > 0:
381 if r > 0:
382 unresolved += 1
382 unresolved += 1
383 else:
383 else:
384 if r is None:
384 if r is None:
385 updated += 1
385 updated += 1
386 else:
386 else:
387 merged += 1
387 merged += 1
388 if f != fd:
388 if f != fd:
389 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
389 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
390 repo.wwrite(fd, repo.wread(f), flags)
390 repo.wwrite(fd, repo.wread(f), flags)
391 if move:
391 if move:
392 repo.ui.debug(_("removing %s\n") % f)
392 repo.ui.debug(_("removing %s\n") % f)
393 os.unlink(repo.wjoin(f))
393 os.unlink(repo.wjoin(f))
394 util.set_exec(repo.wjoin(fd), "x" in flags)
394 util.set_exec(repo.wjoin(fd), "x" in flags)
395 elif m == "g": # get
395 elif m == "g": # get
396 flags = a[2]
396 flags = a[2]
397 repo.ui.note(_("getting %s\n") % f)
397 repo.ui.note(_("getting %s\n") % f)
398 t = mctx.filectx(f).data()
398 t = mctx.filectx(f).data()
399 repo.wwrite(f, t, flags)
399 repo.wwrite(f, t, flags)
400 updated += 1
400 updated += 1
401 elif m == "d": # directory rename
401 elif m == "d": # directory rename
402 f2, fd, flags = a[2:]
402 f2, fd, flags = a[2:]
403 if f:
403 if f:
404 repo.ui.note(_("moving %s to %s\n") % (f, fd))
404 repo.ui.note(_("moving %s to %s\n") % (f, fd))
405 t = wctx.filectx(f).data()
405 t = wctx.filectx(f).data()
406 repo.wwrite(fd, t, flags)
406 repo.wwrite(fd, t, flags)
407 util.unlink(repo.wjoin(f))
407 util.unlink(repo.wjoin(f))
408 if f2:
408 if f2:
409 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
409 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
410 t = mctx.filectx(f2).data()
410 t = mctx.filectx(f2).data()
411 repo.wwrite(fd, t, flags)
411 repo.wwrite(fd, t, flags)
412 updated += 1
412 updated += 1
413 elif m == "e": # exec
413 elif m == "e": # exec
414 flags = a[2]
414 flags = a[2]
415 util.set_exec(repo.wjoin(f), flags)
415 util.set_exec(repo.wjoin(f), flags)
416
416
417 return updated, merged, removed, unresolved
417 return updated, merged, removed, unresolved
418
418
419 def recordupdates(repo, action, branchmerge):
419 def recordupdates(repo, action, branchmerge):
420 "record merge actions to the dirstate"
420 "record merge actions to the dirstate"
421
421
422 for a in action:
422 for a in action:
423 f, m = a[:2]
423 f, m = a[:2]
424 if m == "r": # remove
424 if m == "r": # remove
425 if branchmerge:
425 if branchmerge:
426 repo.dirstate.update([f], 'r')
426 repo.dirstate.update([f], 'r')
427 else:
427 else:
428 repo.dirstate.forget([f])
428 repo.dirstate.forget([f])
429 elif m == "f": # forget
429 elif m == "f": # forget
430 repo.dirstate.forget([f])
430 repo.dirstate.forget([f])
431 elif m == "g": # get
431 elif m == "g": # get
432 if branchmerge:
432 if branchmerge:
433 repo.dirstate.update([f], 'n', st_mtime=-1)
433 repo.dirstate.update([f], 'n', st_mtime=-1)
434 else:
434 else:
435 repo.dirstate.update([f], 'n')
435 repo.dirstate.update([f], 'n')
436 elif m == "m": # merge
436 elif m == "m": # merge
437 f2, fd, flag, move = a[2:]
437 f2, fd, flag, move = a[2:]
438 if branchmerge:
438 if branchmerge:
439 # We've done a branch merge, mark this file as merged
439 # We've done a branch merge, mark this file as merged
440 # so that we properly record the merger later
440 # so that we properly record the merger later
441 repo.dirstate.update([fd], 'm')
441 repo.dirstate.update([fd], 'm')
442 if f != f2: # copy/rename
442 if f != f2: # copy/rename
443 if move:
443 if move:
444 repo.dirstate.update([f], 'r')
444 repo.dirstate.update([f], 'r')
445 if f != fd:
445 if f != fd:
446 repo.dirstate.copy(f, fd)
446 repo.dirstate.copy(f, fd)
447 else:
447 else:
448 repo.dirstate.copy(f2, fd)
448 repo.dirstate.copy(f2, fd)
449 else:
449 else:
450 # We've update-merged a locally modified file, so
450 # We've update-merged a locally modified file, so
451 # we set the dirstate to emulate a normal checkout
451 # we set the dirstate to emulate a normal checkout
452 # of that file some time in the past. Thus our
452 # of that file some time in the past. Thus our
453 # merge will appear as a normal local file
453 # merge will appear as a normal local file
454 # modification.
454 # modification.
455 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
455 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
456 if move:
456 if move:
457 repo.dirstate.forget([f])
457 repo.dirstate.forget([f])
458 elif m == "d": # directory rename
458 elif m == "d": # directory rename
459 f2, fd, flag = a[2:]
459 f2, fd, flag = a[2:]
460 if branchmerge:
460 if branchmerge:
461 repo.dirstate.update([fd], 'a')
461 repo.dirstate.update([fd], 'a')
462 if f:
462 if f:
463 repo.dirstate.update([f], 'r')
463 repo.dirstate.update([f], 'r')
464 repo.dirstate.copy(f, fd)
464 repo.dirstate.copy(f, fd)
465 if f2:
465 if f2:
466 repo.dirstate.copy(f2, fd)
466 repo.dirstate.copy(f2, fd)
467 else:
467 else:
468 repo.dirstate.update([fd], 'n')
468 repo.dirstate.update([fd], 'n')
469 if f:
469 if f:
470 repo.dirstate.forget([f])
470 repo.dirstate.forget([f])
471
471
472 def update(repo, node, branchmerge, force, partial, wlock):
472 def update(repo, node, branchmerge, force, partial, wlock):
473 """
473 """
474 Perform a merge between the working directory and the given node
474 Perform a merge between the working directory and the given node
475
475
476 branchmerge = whether to merge between branches
476 branchmerge = whether to merge between branches
477 force = whether to force branch merging or file overwriting
477 force = whether to force branch merging or file overwriting
478 partial = a function to filter file lists (dirstate not updated)
478 partial = a function to filter file lists (dirstate not updated)
479 wlock = working dir lock, if already held
479 wlock = working dir lock, if already held
480 """
480 """
481
481
482 if not wlock:
482 if not wlock:
483 wlock = repo.wlock()
483 wlock = repo.wlock()
484
484
485 wc = repo.workingctx()
485 wc = repo.workingctx()
486 if node is None:
486 if node is None:
487 # tip of current branch
487 # tip of current branch
488 try:
488 try:
489 node = repo.branchtags()[wc.branch()]
489 node = repo.branchtags()[wc.branch()]
490 except KeyError:
490 except KeyError:
491 raise util.Abort(_("branch %s not found") % wc.branch())
491 raise util.Abort(_("branch %s not found") % wc.branch())
492 overwrite = force and not branchmerge
492 overwrite = force and not branchmerge
493 forcemerge = force and branchmerge
493 forcemerge = force and branchmerge
494 pl = wc.parents()
494 pl = wc.parents()
495 p1, p2 = pl[0], repo.changectx(node)
495 p1, p2 = pl[0], repo.changectx(node)
496 pa = p1.ancestor(p2)
496 pa = p1.ancestor(p2)
497 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
497 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
498 fastforward = False
498 fastforward = False
499
499
500 ### check phase
500 ### check phase
501 if not overwrite and len(pl) > 1:
501 if not overwrite and len(pl) > 1:
502 raise util.Abort(_("outstanding uncommitted merges"))
502 raise util.Abort(_("outstanding uncommitted merges"))
503 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
503 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
504 if branchmerge:
504 if branchmerge:
505 if p1.branch() != p2.branch():
505 if p1.branch() != p2.branch():
506 fastforward = True
506 fastforward = True
507 branchmerge = False
508 else:
507 else:
509 raise util.Abort(_("there is nothing to merge, just use "
508 raise util.Abort(_("there is nothing to merge, just use "
510 "'hg update' or look at 'hg heads'"))
509 "'hg update' or look at 'hg heads'"))
511 elif not (overwrite or branchmerge):
510 elif not (overwrite or branchmerge):
512 raise util.Abort(_("update spans branches, use 'hg merge' "
511 raise util.Abort(_("update spans branches, use 'hg merge' "
513 "or 'hg update -C' to lose changes"))
512 "or 'hg update -C' to lose changes"))
514 if branchmerge and not forcemerge:
513 if branchmerge and not forcemerge:
515 if wc.files():
514 if wc.files():
516 raise util.Abort(_("outstanding uncommitted changes"))
515 raise util.Abort(_("outstanding uncommitted changes"))
517
516
518 ### calculate phase
517 ### calculate phase
519 action = []
518 action = []
520 if not force:
519 if not force:
521 checkunknown(wc, p2)
520 checkunknown(wc, p2)
522 if not util.checkfolding(repo.path):
521 if not util.checkfolding(repo.path):
523 checkcollision(p2)
522 checkcollision(p2)
524 if not branchmerge:
523 if not branchmerge:
525 action += forgetremoved(wc, p2)
524 action += forgetremoved(wc, p2)
526 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
525 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
527
526
528 ### apply phase
527 ### apply phase
529 if not branchmerge: # just jump to the new rev
528 if not branchmerge: # just jump to the new rev
530 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
529 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
531 if not partial:
530 if not partial:
532 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
531 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
533
532
534 stats = applyupdates(repo, action, wc, p2)
533 stats = applyupdates(repo, action, wc, p2)
535
534
536 if not partial:
535 if not partial:
537 recordupdates(repo, action, branchmerge)
536 recordupdates(repo, action, branchmerge)
538 repo.dirstate.setparents(fp1, fp2)
537 repo.dirstate.setparents(fp1, fp2)
539 if not branchmerge and not fastforward:
538 if not branchmerge and not fastforward:
540 repo.dirstate.setbranch(p2.branch())
539 repo.dirstate.setbranch(p2.branch())
541 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
540 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
542
541
543 return stats
542 return stats
544
543
@@ -1,106 +1,108 b''
1 marked working directory as branch foo
1 marked working directory as branch foo
2 foo
2 foo
3 marked working directory as branch bar
3 marked working directory as branch bar
4 % branch shadowing
4 % branch shadowing
5 abort: a branch of the same name already exists (use --force to override)
5 abort: a branch of the same name already exists (use --force to override)
6 marked working directory as branch default
6 marked working directory as branch default
7 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
7 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 foo
8 foo
9 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
9 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
10 (branch merge, don't forget to commit)
10 (branch merge, don't forget to commit)
11 foo
11 foo
12 changeset: 5:5f8fb06e083e
12 changeset: 5:5f8fb06e083e
13 branch: foo
13 branch: foo
14 tag: tip
14 tag: tip
15 parent: 4:4909a3732169
15 parent: 4:4909a3732169
16 parent: 3:bf1bc2f45e83
16 parent: 3:bf1bc2f45e83
17 user: test
17 user: test
18 date: Mon Jan 12 13:46:40 1970 +0000
18 date: Mon Jan 12 13:46:40 1970 +0000
19 summary: merge
19 summary: merge
20
20
21 changeset: 4:4909a3732169
21 changeset: 4:4909a3732169
22 branch: foo
22 branch: foo
23 parent: 1:b699b1cec9c2
23 parent: 1:b699b1cec9c2
24 user: test
24 user: test
25 date: Mon Jan 12 13:46:40 1970 +0000
25 date: Mon Jan 12 13:46:40 1970 +0000
26 summary: modify a branch
26 summary: modify a branch
27
27
28 changeset: 3:bf1bc2f45e83
28 changeset: 3:bf1bc2f45e83
29 user: test
29 user: test
30 date: Mon Jan 12 13:46:40 1970 +0000
30 date: Mon Jan 12 13:46:40 1970 +0000
31 summary: clear branch name
31 summary: clear branch name
32
32
33 changeset: 2:67ec16bde7f1
33 changeset: 2:67ec16bde7f1
34 branch: bar
34 branch: bar
35 user: test
35 user: test
36 date: Mon Jan 12 13:46:40 1970 +0000
36 date: Mon Jan 12 13:46:40 1970 +0000
37 summary: change branch name
37 summary: change branch name
38
38
39 changeset: 1:b699b1cec9c2
39 changeset: 1:b699b1cec9c2
40 branch: foo
40 branch: foo
41 user: test
41 user: test
42 date: Mon Jan 12 13:46:40 1970 +0000
42 date: Mon Jan 12 13:46:40 1970 +0000
43 summary: add branch name
43 summary: add branch name
44
44
45 changeset: 0:be8523e69bf8
45 changeset: 0:be8523e69bf8
46 user: test
46 user: test
47 date: Mon Jan 12 13:46:40 1970 +0000
47 date: Mon Jan 12 13:46:40 1970 +0000
48 summary: initial
48 summary: initial
49
49
50 foo 5:5f8fb06e083e
50 foo 5:5f8fb06e083e
51 default 3:bf1bc2f45e83
51 default 3:bf1bc2f45e83
52 bar 2:67ec16bde7f1
52 bar 2:67ec16bde7f1
53 foo
53 foo
54 default
54 default
55 bar
55 bar
56 % test for invalid branch cache
56 % test for invalid branch cache
57 rolling back last transaction
57 rolling back last transaction
58 changeset: 4:4909a3732169
58 changeset: 4:4909a3732169
59 branch: foo
59 branch: foo
60 tag: tip
60 tag: tip
61 parent: 1:b699b1cec9c2
61 parent: 1:b699b1cec9c2
62 user: test
62 user: test
63 date: Mon Jan 12 13:46:40 1970 +0000
63 date: Mon Jan 12 13:46:40 1970 +0000
64 summary: modify a branch
64 summary: modify a branch
65
65
66 Invalid branch cache: unknown tip
66 Invalid branch cache: unknown tip
67 changeset: 4:4909a3732169c0c20011c4f4b8fdff4e3d89b23f
67 changeset: 4:4909a3732169c0c20011c4f4b8fdff4e3d89b23f
68 branch: foo
68 branch: foo
69 tag: tip
69 tag: tip
70 parent: 1:b699b1cec9c2966b3700de4fef0dc123cd754c31
70 parent: 1:b699b1cec9c2966b3700de4fef0dc123cd754c31
71 parent: -1:0000000000000000000000000000000000000000
71 parent: -1:0000000000000000000000000000000000000000
72 manifest: 4:d01b250baaa05909152f7ae07d7a649deea0df9a
72 manifest: 4:d01b250baaa05909152f7ae07d7a649deea0df9a
73 user: test
73 user: test
74 date: Mon Jan 12 13:46:40 1970 +0000
74 date: Mon Jan 12 13:46:40 1970 +0000
75 files: a
75 files: a
76 extra: branch=foo
76 extra: branch=foo
77 description:
77 description:
78 modify a branch
78 modify a branch
79
79
80
80
81 4:4909a3732169
81 4:4909a3732169
82 4909a3732169c0c20011c4f4b8fdff4e3d89b23f 4
82 4909a3732169c0c20011c4f4b8fdff4e3d89b23f 4
83 bf1bc2f45e834c75404d0ddab57d53beab56e2f8 default
83 bf1bc2f45e834c75404d0ddab57d53beab56e2f8 default
84 4909a3732169c0c20011c4f4b8fdff4e3d89b23f foo
84 4909a3732169c0c20011c4f4b8fdff4e3d89b23f foo
85 67ec16bde7f1575d523313b9bca000f6a6f12dca bar
85 67ec16bde7f1575d523313b9bca000f6a6f12dca bar
86 % update with no arguments: tipmost revision of the current branch
86 % update with no arguments: tipmost revision of the current branch
87 bf1bc2f45e83
87 bf1bc2f45e83
88 4909a3732169 (foo) tip
88 4909a3732169 (foo) tip
89 marked working directory as branch foobar
89 marked working directory as branch foobar
90 abort: branch foobar not found
90 abort: branch foobar not found
91 % fastforward merge
91 % fastforward merge
92 marked working directory as branch ff
92 marked working directory as branch ff
93 adding ff
93 adding ff
94 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
94 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 (branch merge, don't forget to commit)
96 (branch merge, don't forget to commit)
97 foo
97 foo
98 changeset: 6:9cc105113eeb
98 changeset: 6:f0c74f92a385
99 branch: foo
99 branch: foo
100 tag: tip
100 tag: tip
101 parent: 4:4909a3732169
102 parent: 5:c420d2121b71
101 user: test
103 user: test
102 date: Mon Jan 12 13:46:40 1970 +0000
104 date: Mon Jan 12 13:46:40 1970 +0000
103 summary: Merge ff into foo
105 summary: Merge ff into foo
104
106
105 a
107 a
106 ff
108 ff
General Comments 0
You need to be logged in to leave comments. Login now