##// END OF EJS Templates
Fix copy detection corner case...
Matt Mackall -
r3875:c0a12e64 default
parent child Browse files
Show More
@@ -1,495 +1,495 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 gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "errno util os tempfile")
11 demandload(globals(), "errno util os tempfile")
12
12
13 def filemerge(repo, fw, fo, wctx, mctx):
13 def filemerge(repo, fw, fo, wctx, mctx):
14 """perform a 3-way merge in the working directory
14 """perform a 3-way merge in the working directory
15
15
16 fw = filename in the working directory
16 fw = 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 f = os.fdopen(fd, "wb")
24 f = os.fdopen(fd, "wb")
25 repo.wwrite(ctx.path(), ctx.data(), f)
25 repo.wwrite(ctx.path(), ctx.data(), f)
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 findold(fctx):
105 def findold(fctx):
106 "find files that path was copied from, back to linkrev limit"
106 "find files that path was copied from, back to linkrev limit"
107 old = {}
107 old = {}
108 orig = fctx.path()
108 orig = fctx.path()
109 visit = [fctx]
109 visit = [fctx]
110 while visit:
110 while visit:
111 fc = visit.pop()
111 fc = visit.pop()
112 if fc.path() != orig and fc.path() not in old:
113 old[fc.path()] = 1
112 if fc.rev() < limit:
114 if fc.rev() < limit:
113 continue
115 continue
114 if fc.path() != orig and fc.path() not in old:
115 old[fc.path()] = 1
116 visit += fc.parents()
116 visit += fc.parents()
117
117
118 old = old.keys()
118 old = old.keys()
119 old.sort()
119 old.sort()
120 return old
120 return old
121
121
122 def nonoverlap(d1, d2, d3):
122 def nonoverlap(d1, d2, d3):
123 "Return list of elements in d1 not in d2 or d3"
123 "Return list of elements in d1 not in d2 or d3"
124 l = [d for d in d1 if d not in d3 and d not in d2]
124 l = [d for d in d1 if d not in d3 and d not in d2]
125 l.sort()
125 l.sort()
126 return l
126 return l
127
127
128 def checkcopies(c, man):
128 def checkcopies(c, man):
129 '''check possible copies for filectx c'''
129 '''check possible copies for filectx c'''
130 for of in findold(c):
130 for of in findold(c):
131 if of not in man:
131 if of not in man:
132 return
132 return
133 c2 = ctx(of, man[of])
133 c2 = ctx(of, man[of])
134 ca = c.ancestor(c2)
134 ca = c.ancestor(c2)
135 if not ca: # unrelated
135 if not ca: # unrelated
136 return
136 return
137 if ca.path() == c.path() or ca.path() == c2.path():
137 if ca.path() == c.path() or ca.path() == c2.path():
138 fullcopy[c.path()] = of
138 fullcopy[c.path()] = of
139 if c == ca or c2 == ca: # no merge needed, ignore copy
139 if c == ca or c2 == ca: # no merge needed, ignore copy
140 return
140 return
141 copy[c.path()] = of
141 copy[c.path()] = of
142
142
143 def dirs(files):
143 def dirs(files):
144 d = {}
144 d = {}
145 for f in files:
145 for f in files:
146 d[os.path.dirname(f)] = True
146 d[os.path.dirname(f)] = True
147 return d
147 return d
148
148
149 if not repo.ui.configbool("merge", "followcopies", True):
149 if not repo.ui.configbool("merge", "followcopies", True):
150 return {}
150 return {}
151
151
152 # avoid silly behavior for update from empty dir
152 # avoid silly behavior for update from empty dir
153 if not m1 or not m2 or not ma:
153 if not m1 or not m2 or not ma:
154 return {}
154 return {}
155
155
156 dcopies = repo.dirstate.copies()
156 dcopies = repo.dirstate.copies()
157 copy = {}
157 copy = {}
158 fullcopy = {}
158 fullcopy = {}
159 u1 = nonoverlap(m1, m2, ma)
159 u1 = nonoverlap(m1, m2, ma)
160 u2 = nonoverlap(m2, m1, ma)
160 u2 = nonoverlap(m2, m1, ma)
161 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
161 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
162
162
163 for f in u1:
163 for f in u1:
164 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2)
164 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2)
165
165
166 for f in u2:
166 for f in u2:
167 checkcopies(ctx(f, m2[f]), m1)
167 checkcopies(ctx(f, m2[f]), m1)
168
168
169 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
169 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
170 return copy
170 return copy
171
171
172 # generate a directory move map
172 # generate a directory move map
173 d1, d2 = dirs(m1), dirs(m2)
173 d1, d2 = dirs(m1), dirs(m2)
174 invalid = {}
174 invalid = {}
175 dirmove = {}
175 dirmove = {}
176
176
177 for dst, src in fullcopy.items():
177 for dst, src in fullcopy.items():
178 dsrc, ddst = os.path.dirname(src), os.path.dirname(dst)
178 dsrc, ddst = os.path.dirname(src), os.path.dirname(dst)
179 if dsrc in invalid:
179 if dsrc in invalid:
180 continue
180 continue
181 elif (dsrc in d1 and ddst in d1) or (dsrc in d2 and ddst in d2):
181 elif (dsrc in d1 and ddst in d1) or (dsrc in d2 and ddst in d2):
182 invalid[dsrc] = True
182 invalid[dsrc] = True
183 elif dsrc in dirmove and dirmove[dsrc] != ddst:
183 elif dsrc in dirmove and dirmove[dsrc] != ddst:
184 invalid[dsrc] = True
184 invalid[dsrc] = True
185 del dirmove[dsrc]
185 del dirmove[dsrc]
186 else:
186 else:
187 dirmove[dsrc] = ddst
187 dirmove[dsrc] = ddst
188
188
189 del d1, d2, invalid
189 del d1, d2, invalid
190
190
191 if not dirmove:
191 if not dirmove:
192 return copy
192 return copy
193
193
194 # check unaccounted nonoverlapping files
194 # check unaccounted nonoverlapping files
195 for f in u1 + u2:
195 for f in u1 + u2:
196 if f not in fullcopy:
196 if f not in fullcopy:
197 d = os.path.dirname(f)
197 d = os.path.dirname(f)
198 if d in dirmove:
198 if d in dirmove:
199 copy[f] = dirmove[d] + "/" + os.path.basename(f)
199 copy[f] = dirmove[d] + "/" + os.path.basename(f)
200
200
201 return copy
201 return copy
202
202
203 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
203 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
204 """
204 """
205 Merge p1 and p2 with ancestor ma and generate merge action list
205 Merge p1 and p2 with ancestor ma and generate merge action list
206
206
207 overwrite = whether we clobber working files
207 overwrite = whether we clobber working files
208 partial = function to filter file lists
208 partial = function to filter file lists
209 """
209 """
210
210
211 repo.ui.note(_("resolving manifests\n"))
211 repo.ui.note(_("resolving manifests\n"))
212 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
212 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
213 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
213 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
214
214
215 m1 = p1.manifest()
215 m1 = p1.manifest()
216 m2 = p2.manifest()
216 m2 = p2.manifest()
217 ma = pa.manifest()
217 ma = pa.manifest()
218 backwards = (pa == p2)
218 backwards = (pa == p2)
219 action = []
219 action = []
220 copy = {}
220 copy = {}
221
221
222 def fmerge(f, f2=None, fa=None):
222 def fmerge(f, f2=None, fa=None):
223 """merge executable flags"""
223 """merge executable flags"""
224 if not f2:
224 if not f2:
225 f2 = f
225 f2 = f
226 fa = f
226 fa = f
227 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
227 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
228 return ((a^b) | (a^c)) ^ a
228 return ((a^b) | (a^c)) ^ a
229
229
230 def act(msg, m, f, *args):
230 def act(msg, m, f, *args):
231 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
231 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
232 action.append((f, m) + args)
232 action.append((f, m) + args)
233
233
234 if not (backwards or overwrite):
234 if not (backwards or overwrite):
235 copy = findcopies(repo, m1, m2, ma, pa.rev())
235 copy = findcopies(repo, m1, m2, ma, pa.rev())
236 copied = dict.fromkeys(copy.values())
236 copied = dict.fromkeys(copy.values())
237
237
238 # Compare manifests
238 # Compare manifests
239 for f, n in m1.iteritems():
239 for f, n in m1.iteritems():
240 if partial and not partial(f):
240 if partial and not partial(f):
241 continue
241 continue
242 if f in m2:
242 if f in m2:
243 # are files different?
243 # are files different?
244 if n != m2[f]:
244 if n != m2[f]:
245 a = ma.get(f, nullid)
245 a = ma.get(f, nullid)
246 # are both different from the ancestor?
246 # are both different from the ancestor?
247 if not overwrite and n != a and m2[f] != a:
247 if not overwrite and n != a and m2[f] != a:
248 act("versions differ", "m", f, f, f, fmerge(f), False)
248 act("versions differ", "m", f, f, f, fmerge(f), False)
249 # are we clobbering?
249 # are we clobbering?
250 # is remote's version newer?
250 # is remote's version newer?
251 # or are we going back in time and clean?
251 # or are we going back in time and clean?
252 elif overwrite or m2[f] != a or (backwards and not n[20:]):
252 elif overwrite or m2[f] != a or (backwards and not n[20:]):
253 act("remote is newer", "g", f, m2.execf(f))
253 act("remote is newer", "g", f, m2.execf(f))
254 # local is newer, not overwrite, check mode bits
254 # local is newer, not overwrite, check mode bits
255 elif fmerge(f) != m1.execf(f):
255 elif fmerge(f) != m1.execf(f):
256 act("update permissions", "e", f, m2.execf(f))
256 act("update permissions", "e", f, m2.execf(f))
257 # contents same, check mode bits
257 # contents same, check mode bits
258 elif m1.execf(f) != m2.execf(f):
258 elif m1.execf(f) != m2.execf(f):
259 if overwrite or fmerge(f) != m1.execf(f):
259 if overwrite or fmerge(f) != m1.execf(f):
260 act("update permissions", "e", f, m2.execf(f))
260 act("update permissions", "e", f, m2.execf(f))
261 elif f in copied:
261 elif f in copied:
262 continue
262 continue
263 elif f in copy:
263 elif f in copy:
264 f2 = copy[f]
264 f2 = copy[f]
265 if f2 not in m2: # directory rename
265 if f2 not in m2: # directory rename
266 act("remote renamed directory to " + f2, "d",
266 act("remote renamed directory to " + f2, "d",
267 f, None, f2, m1.execf(f))
267 f, None, f2, m1.execf(f))
268 elif f2 in m1: # case 2 A,B/B/B
268 elif f2 in m1: # case 2 A,B/B/B
269 act("local copied to " + f2, "m",
269 act("local copied to " + f2, "m",
270 f, f2, f, fmerge(f, f2, f2), False)
270 f, f2, f, fmerge(f, f2, f2), False)
271 else: # case 4,21 A/B/B
271 else: # case 4,21 A/B/B
272 act("local moved to " + f2, "m",
272 act("local moved to " + f2, "m",
273 f, f2, f, fmerge(f, f2, f2), False)
273 f, f2, f, fmerge(f, f2, f2), False)
274 elif f in ma:
274 elif f in ma:
275 if n != ma[f] and not overwrite:
275 if n != ma[f] and not overwrite:
276 if repo.ui.prompt(
276 if repo.ui.prompt(
277 (_(" local changed %s which remote deleted\n") % f) +
277 (_(" local changed %s which remote deleted\n") % f) +
278 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
278 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
279 act("prompt delete", "r", f)
279 act("prompt delete", "r", f)
280 else:
280 else:
281 act("other deleted", "r", f)
281 act("other deleted", "r", f)
282 else:
282 else:
283 # file is created on branch or in working directory
283 # file is created on branch or in working directory
284 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
284 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
285 act("remote deleted", "r", f)
285 act("remote deleted", "r", f)
286
286
287 for f, n in m2.iteritems():
287 for f, n in m2.iteritems():
288 if partial and not partial(f):
288 if partial and not partial(f):
289 continue
289 continue
290 if f in m1:
290 if f in m1:
291 continue
291 continue
292 if f in copied:
292 if f in copied:
293 continue
293 continue
294 if f in copy:
294 if f in copy:
295 f2 = copy[f]
295 f2 = copy[f]
296 if f2 not in m1: # directory rename
296 if f2 not in m1: # directory rename
297 act("local renamed directory to " + f2, "d",
297 act("local renamed directory to " + f2, "d",
298 None, f, f2, m2.execf(f))
298 None, f, f2, m2.execf(f))
299 elif f2 in m2: # rename case 1, A/A,B/A
299 elif f2 in m2: # rename case 1, A/A,B/A
300 act("remote copied to " + f, "m",
300 act("remote copied to " + f, "m",
301 f2, f, f, fmerge(f2, f, f2), False)
301 f2, f, f, fmerge(f2, f, f2), False)
302 else: # case 3,20 A/B/A
302 else: # case 3,20 A/B/A
303 act("remote moved to " + f, "m",
303 act("remote moved to " + f, "m",
304 f2, f, f, fmerge(f2, f, f2), True)
304 f2, f, f, fmerge(f2, f, f2), True)
305 elif f in ma:
305 elif f in ma:
306 if overwrite or backwards:
306 if overwrite or backwards:
307 act("recreating", "g", f, m2.execf(f))
307 act("recreating", "g", f, m2.execf(f))
308 elif n != ma[f]:
308 elif n != ma[f]:
309 if repo.ui.prompt(
309 if repo.ui.prompt(
310 (_("remote changed %s which local deleted\n") % f) +
310 (_("remote changed %s which local deleted\n") % f) +
311 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
311 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
312 act("prompt recreating", "g", f, m2.execf(f))
312 act("prompt recreating", "g", f, m2.execf(f))
313 else:
313 else:
314 act("remote created", "g", f, m2.execf(f))
314 act("remote created", "g", f, m2.execf(f))
315
315
316 return action
316 return action
317
317
318 def applyupdates(repo, action, wctx, mctx):
318 def applyupdates(repo, action, wctx, mctx):
319 "apply the merge action list to the working directory"
319 "apply the merge action list to the working directory"
320
320
321 updated, merged, removed, unresolved = 0, 0, 0, 0
321 updated, merged, removed, unresolved = 0, 0, 0, 0
322 action.sort()
322 action.sort()
323 for a in action:
323 for a in action:
324 f, m = a[:2]
324 f, m = a[:2]
325 if f and f[0] == "/":
325 if f and f[0] == "/":
326 continue
326 continue
327 if m == "r": # remove
327 if m == "r": # remove
328 repo.ui.note(_("removing %s\n") % f)
328 repo.ui.note(_("removing %s\n") % f)
329 util.audit_path(f)
329 util.audit_path(f)
330 try:
330 try:
331 util.unlink(repo.wjoin(f))
331 util.unlink(repo.wjoin(f))
332 except OSError, inst:
332 except OSError, inst:
333 if inst.errno != errno.ENOENT:
333 if inst.errno != errno.ENOENT:
334 repo.ui.warn(_("update failed to remove %s: %s!\n") %
334 repo.ui.warn(_("update failed to remove %s: %s!\n") %
335 (f, inst.strerror))
335 (f, inst.strerror))
336 removed += 1
336 removed += 1
337 elif m == "m": # merge
337 elif m == "m": # merge
338 f2, fd, flag, move = a[2:]
338 f2, fd, flag, move = a[2:]
339 r = filemerge(repo, f, f2, wctx, mctx)
339 r = filemerge(repo, f, f2, wctx, mctx)
340 if r > 0:
340 if r > 0:
341 unresolved += 1
341 unresolved += 1
342 else:
342 else:
343 if r is None:
343 if r is None:
344 updated += 1
344 updated += 1
345 else:
345 else:
346 merged += 1
346 merged += 1
347 if f != fd:
347 if f != fd:
348 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
348 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
349 repo.wwrite(fd, repo.wread(f))
349 repo.wwrite(fd, repo.wread(f))
350 if move:
350 if move:
351 repo.ui.debug(_("removing %s\n") % f)
351 repo.ui.debug(_("removing %s\n") % f)
352 os.unlink(repo.wjoin(f))
352 os.unlink(repo.wjoin(f))
353 util.set_exec(repo.wjoin(fd), flag)
353 util.set_exec(repo.wjoin(fd), flag)
354 elif m == "g": # get
354 elif m == "g": # get
355 flag = a[2]
355 flag = a[2]
356 repo.ui.note(_("getting %s\n") % f)
356 repo.ui.note(_("getting %s\n") % f)
357 t = mctx.filectx(f).data()
357 t = mctx.filectx(f).data()
358 repo.wwrite(f, t)
358 repo.wwrite(f, t)
359 util.set_exec(repo.wjoin(f), flag)
359 util.set_exec(repo.wjoin(f), flag)
360 updated += 1
360 updated += 1
361 elif m == "d": # directory rename
361 elif m == "d": # directory rename
362 f2, fd, flag = a[2:]
362 f2, fd, flag = a[2:]
363 if f:
363 if f:
364 repo.ui.note(_("moving %s to %s\n") % (f, fd))
364 repo.ui.note(_("moving %s to %s\n") % (f, fd))
365 t = wctx.filectx(f).data()
365 t = wctx.filectx(f).data()
366 repo.wwrite(fd, t)
366 repo.wwrite(fd, t)
367 util.set_exec(repo.wjoin(fd), flag)
367 util.set_exec(repo.wjoin(fd), flag)
368 util.unlink(repo.wjoin(f))
368 util.unlink(repo.wjoin(f))
369 if f2:
369 if f2:
370 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
370 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
371 t = mctx.filectx(f2).data()
371 t = mctx.filectx(f2).data()
372 repo.wwrite(fd, t)
372 repo.wwrite(fd, t)
373 util.set_exec(repo.wjoin(fd), flag)
373 util.set_exec(repo.wjoin(fd), flag)
374 updated += 1
374 updated += 1
375 elif m == "e": # exec
375 elif m == "e": # exec
376 flag = a[2]
376 flag = a[2]
377 util.set_exec(repo.wjoin(f), flag)
377 util.set_exec(repo.wjoin(f), flag)
378
378
379 return updated, merged, removed, unresolved
379 return updated, merged, removed, unresolved
380
380
381 def recordupdates(repo, action, branchmerge):
381 def recordupdates(repo, action, branchmerge):
382 "record merge actions to the dirstate"
382 "record merge actions to the dirstate"
383
383
384 for a in action:
384 for a in action:
385 f, m = a[:2]
385 f, m = a[:2]
386 if m == "r": # remove
386 if m == "r": # remove
387 if branchmerge:
387 if branchmerge:
388 repo.dirstate.update([f], 'r')
388 repo.dirstate.update([f], 'r')
389 else:
389 else:
390 repo.dirstate.forget([f])
390 repo.dirstate.forget([f])
391 elif m == "f": # forget
391 elif m == "f": # forget
392 repo.dirstate.forget([f])
392 repo.dirstate.forget([f])
393 elif m == "g": # get
393 elif m == "g": # get
394 if branchmerge:
394 if branchmerge:
395 repo.dirstate.update([f], 'n', st_mtime=-1)
395 repo.dirstate.update([f], 'n', st_mtime=-1)
396 else:
396 else:
397 repo.dirstate.update([f], 'n')
397 repo.dirstate.update([f], 'n')
398 elif m == "m": # merge
398 elif m == "m": # merge
399 f2, fd, flag, move = a[2:]
399 f2, fd, flag, move = a[2:]
400 if branchmerge:
400 if branchmerge:
401 # We've done a branch merge, mark this file as merged
401 # We've done a branch merge, mark this file as merged
402 # so that we properly record the merger later
402 # so that we properly record the merger later
403 repo.dirstate.update([fd], 'm')
403 repo.dirstate.update([fd], 'm')
404 if f != f2: # copy/rename
404 if f != f2: # copy/rename
405 if move:
405 if move:
406 repo.dirstate.update([f], 'r')
406 repo.dirstate.update([f], 'r')
407 if f != fd:
407 if f != fd:
408 repo.dirstate.copy(f, fd)
408 repo.dirstate.copy(f, fd)
409 else:
409 else:
410 repo.dirstate.copy(f2, fd)
410 repo.dirstate.copy(f2, fd)
411 else:
411 else:
412 # We've update-merged a locally modified file, so
412 # We've update-merged a locally modified file, so
413 # we set the dirstate to emulate a normal checkout
413 # we set the dirstate to emulate a normal checkout
414 # of that file some time in the past. Thus our
414 # of that file some time in the past. Thus our
415 # merge will appear as a normal local file
415 # merge will appear as a normal local file
416 # modification.
416 # modification.
417 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
417 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
418 if move:
418 if move:
419 repo.dirstate.forget([f])
419 repo.dirstate.forget([f])
420 elif m == "d": # directory rename
420 elif m == "d": # directory rename
421 f2, fd, flag = a[2:]
421 f2, fd, flag = a[2:]
422 if branchmerge:
422 if branchmerge:
423 repo.dirstate.update([fd], 'a')
423 repo.dirstate.update([fd], 'a')
424 if f:
424 if f:
425 repo.dirstate.update([f], 'r')
425 repo.dirstate.update([f], 'r')
426 repo.dirstate.copy(f, fd)
426 repo.dirstate.copy(f, fd)
427 if f2:
427 if f2:
428 repo.dirstate.copy(f2, fd)
428 repo.dirstate.copy(f2, fd)
429 else:
429 else:
430 repo.dirstate.update([fd], 'n')
430 repo.dirstate.update([fd], 'n')
431 if f:
431 if f:
432 repo.dirstate.forget([f])
432 repo.dirstate.forget([f])
433
433
434 def update(repo, node, branchmerge, force, partial, wlock):
434 def update(repo, node, branchmerge, force, partial, wlock):
435 """
435 """
436 Perform a merge between the working directory and the given node
436 Perform a merge between the working directory and the given node
437
437
438 branchmerge = whether to merge between branches
438 branchmerge = whether to merge between branches
439 force = whether to force branch merging or file overwriting
439 force = whether to force branch merging or file overwriting
440 partial = a function to filter file lists (dirstate not updated)
440 partial = a function to filter file lists (dirstate not updated)
441 wlock = working dir lock, if already held
441 wlock = working dir lock, if already held
442 """
442 """
443
443
444 if not wlock:
444 if not wlock:
445 wlock = repo.wlock()
445 wlock = repo.wlock()
446
446
447 overwrite = force and not branchmerge
447 overwrite = force and not branchmerge
448 forcemerge = force and branchmerge
448 forcemerge = force and branchmerge
449 wc = repo.workingctx()
449 wc = repo.workingctx()
450 pl = wc.parents()
450 pl = wc.parents()
451 p1, p2 = pl[0], repo.changectx(node)
451 p1, p2 = pl[0], repo.changectx(node)
452 pa = p1.ancestor(p2)
452 pa = p1.ancestor(p2)
453 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
453 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
454
454
455 ### check phase
455 ### check phase
456 if not overwrite and len(pl) > 1:
456 if not overwrite and len(pl) > 1:
457 raise util.Abort(_("outstanding uncommitted merges"))
457 raise util.Abort(_("outstanding uncommitted merges"))
458 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
458 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
459 if branchmerge:
459 if branchmerge:
460 raise util.Abort(_("there is nothing to merge, just use "
460 raise util.Abort(_("there is nothing to merge, just use "
461 "'hg update' or look at 'hg heads'"))
461 "'hg update' or look at 'hg heads'"))
462 elif not (overwrite or branchmerge):
462 elif not (overwrite or branchmerge):
463 raise util.Abort(_("update spans branches, use 'hg merge' "
463 raise util.Abort(_("update spans branches, use 'hg merge' "
464 "or 'hg update -C' to lose changes"))
464 "or 'hg update -C' to lose changes"))
465 if branchmerge and not forcemerge:
465 if branchmerge and not forcemerge:
466 if wc.files():
466 if wc.files():
467 raise util.Abort(_("outstanding uncommitted changes"))
467 raise util.Abort(_("outstanding uncommitted changes"))
468
468
469 ### calculate phase
469 ### calculate phase
470 action = []
470 action = []
471 if not force:
471 if not force:
472 checkunknown(wc, p2)
472 checkunknown(wc, p2)
473 if not util.checkfolding(repo.path):
473 if not util.checkfolding(repo.path):
474 checkcollision(p2)
474 checkcollision(p2)
475 if not branchmerge:
475 if not branchmerge:
476 action += forgetremoved(wc, p2)
476 action += forgetremoved(wc, p2)
477 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
477 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
478
478
479 ### apply phase
479 ### apply phase
480 if not branchmerge: # just jump to the new rev
480 if not branchmerge: # just jump to the new rev
481 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
481 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
482 if not partial:
482 if not partial:
483 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
483 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
484
484
485 stats = applyupdates(repo, action, wc, p2)
485 stats = applyupdates(repo, action, wc, p2)
486
486
487 if not partial:
487 if not partial:
488 recordupdates(repo, action, branchmerge)
488 recordupdates(repo, action, branchmerge)
489 repo.dirstate.setparents(fp1, fp2)
489 repo.dirstate.setparents(fp1, fp2)
490 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
490 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
491 if not branchmerge:
491 if not branchmerge:
492 repo.opener("branch", "w").write(p2.branch() + "\n")
492 repo.opener("branch", "w").write(p2.branch() + "\n")
493
493
494 return stats
494 return stats
495
495
General Comments 0
You need to be logged in to leave comments. Login now