##// END OF EJS Templates
merge: drop an overwrite test
Matt Mackall -
r8739:226f1005 default
parent child Browse files
Show More
@@ -1,500 +1,500 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import util, filemerge, copies
10 import util, filemerge, copies
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._read()
17 self._read()
18 def reset(self, node=None):
18 def reset(self, node=None):
19 self._state = {}
19 self._state = {}
20 if node:
20 if node:
21 self._local = node
21 self._local = node
22 shutil.rmtree(self._repo.join("merge"), True)
22 shutil.rmtree(self._repo.join("merge"), True)
23 def _read(self):
23 def _read(self):
24 self._state = {}
24 self._state = {}
25 try:
25 try:
26 localnode = None
26 localnode = None
27 f = self._repo.opener("merge/state")
27 f = self._repo.opener("merge/state")
28 for i, l in enumerate(f):
28 for i, l in enumerate(f):
29 if i == 0:
29 if i == 0:
30 localnode = l[:-1]
30 localnode = l[:-1]
31 else:
31 else:
32 bits = l[:-1].split("\0")
32 bits = l[:-1].split("\0")
33 self._state[bits[0]] = bits[1:]
33 self._state[bits[0]] = bits[1:]
34 self._local = bin(localnode)
34 self._local = bin(localnode)
35 except IOError, err:
35 except IOError, err:
36 if err.errno != errno.ENOENT:
36 if err.errno != errno.ENOENT:
37 raise
37 raise
38 def _write(self):
38 def _write(self):
39 f = self._repo.opener("merge/state", "w")
39 f = self._repo.opener("merge/state", "w")
40 f.write(hex(self._local) + "\n")
40 f.write(hex(self._local) + "\n")
41 for d, v in self._state.iteritems():
41 for d, v in self._state.iteritems():
42 f.write("\0".join([d] + v) + "\n")
42 f.write("\0".join([d] + v) + "\n")
43 def add(self, fcl, fco, fca, fd, flags):
43 def add(self, fcl, fco, fca, fd, flags):
44 hash = util.sha1(fcl.path()).hexdigest()
44 hash = util.sha1(fcl.path()).hexdigest()
45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
47 hex(fca.filenode()), fco.path(), flags]
47 hex(fca.filenode()), fco.path(), flags]
48 self._write()
48 self._write()
49 def __contains__(self, dfile):
49 def __contains__(self, dfile):
50 return dfile in self._state
50 return dfile in self._state
51 def __getitem__(self, dfile):
51 def __getitem__(self, dfile):
52 return self._state[dfile][0]
52 return self._state[dfile][0]
53 def __iter__(self):
53 def __iter__(self):
54 l = self._state.keys()
54 l = self._state.keys()
55 l.sort()
55 l.sort()
56 for f in l:
56 for f in l:
57 yield f
57 yield f
58 def mark(self, dfile, state):
58 def mark(self, dfile, state):
59 self._state[dfile][0] = state
59 self._state[dfile][0] = state
60 self._write()
60 self._write()
61 def resolve(self, dfile, wctx, octx):
61 def resolve(self, dfile, wctx, octx):
62 if self[dfile] == 'r':
62 if self[dfile] == 'r':
63 return 0
63 return 0
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
65 f = self._repo.opener("merge/" + hash)
65 f = self._repo.opener("merge/" + hash)
66 self._repo.wwrite(dfile, f.read(), flags)
66 self._repo.wwrite(dfile, f.read(), flags)
67 fcd = wctx[dfile]
67 fcd = wctx[dfile]
68 fco = octx[ofile]
68 fco = octx[ofile]
69 fca = self._repo.filectx(afile, fileid=anode)
69 fca = self._repo.filectx(afile, fileid=anode)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
71 if not r:
71 if not r:
72 self.mark(dfile, 'r')
72 self.mark(dfile, 'r')
73 return r
73 return r
74
74
75 def _checkunknown(wctx, mctx):
75 def _checkunknown(wctx, mctx):
76 "check for collisions between unknown files and files in mctx"
76 "check for collisions between unknown files and files in mctx"
77 for f in wctx.unknown():
77 for f in wctx.unknown():
78 if f in mctx and mctx[f].cmp(wctx[f].data()):
78 if f in mctx and mctx[f].cmp(wctx[f].data()):
79 raise util.Abort(_("untracked file in working directory differs"
79 raise util.Abort(_("untracked file in working directory differs"
80 " from file in requested revision: '%s'") % f)
80 " from file in requested revision: '%s'") % f)
81
81
82 def _checkcollision(mctx):
82 def _checkcollision(mctx):
83 "check for case folding collisions in the destination context"
83 "check for case folding collisions in the destination context"
84 folded = {}
84 folded = {}
85 for fn in mctx:
85 for fn in mctx:
86 fold = fn.lower()
86 fold = fn.lower()
87 if fold in folded:
87 if fold in folded:
88 raise util.Abort(_("case-folding collision between %s and %s")
88 raise util.Abort(_("case-folding collision between %s and %s")
89 % (fn, folded[fold]))
89 % (fn, folded[fold]))
90 folded[fold] = fn
90 folded[fold] = fn
91
91
92 def _forgetremoved(wctx, mctx, branchmerge):
92 def _forgetremoved(wctx, mctx, branchmerge):
93 """
93 """
94 Forget removed files
94 Forget removed files
95
95
96 If we're jumping between revisions (as opposed to merging), and if
96 If we're jumping between revisions (as opposed to merging), and if
97 neither the working directory nor the target rev has the file,
97 neither the working directory nor the target rev has the file,
98 then we need to remove it from the dirstate, to prevent the
98 then we need to remove it from the dirstate, to prevent the
99 dirstate from listing the file when it is no longer in the
99 dirstate from listing the file when it is no longer in the
100 manifest.
100 manifest.
101
101
102 If we're merging, and the other revision has removed a file
102 If we're merging, and the other revision has removed a file
103 that is not present in the working directory, we need to mark it
103 that is not present in the working directory, we need to mark it
104 as removed.
104 as removed.
105 """
105 """
106
106
107 action = []
107 action = []
108 state = branchmerge and 'r' or 'f'
108 state = branchmerge and 'r' or 'f'
109 for f in wctx.deleted():
109 for f in wctx.deleted():
110 if f not in mctx:
110 if f not in mctx:
111 action.append((f, state))
111 action.append((f, state))
112
112
113 if not branchmerge:
113 if not branchmerge:
114 for f in wctx.removed():
114 for f in wctx.removed():
115 if f not in mctx:
115 if f not in mctx:
116 action.append((f, "f"))
116 action.append((f, "f"))
117
117
118 return action
118 return action
119
119
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
121 """
121 """
122 Merge p1 and p2 with ancestor ma and generate merge action list
122 Merge p1 and p2 with ancestor ma and generate merge action list
123
123
124 overwrite = whether we clobber working files
124 overwrite = whether we clobber working files
125 partial = function to filter file lists
125 partial = function to filter file lists
126 """
126 """
127
127
128 repo.ui.note(_("resolving manifests\n"))
128 repo.ui.note(_("resolving manifests\n"))
129 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
129 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
130 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
130 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
131
131
132 m1 = p1.manifest()
132 m1 = p1.manifest()
133 m2 = p2.manifest()
133 m2 = p2.manifest()
134 ma = pa.manifest()
134 ma = pa.manifest()
135 backwards = (pa == p2)
135 backwards = (pa == p2)
136
136
137 if backwards or overwrite:
137 if backwards or overwrite:
138 ma = m1
138 ma = m1
139
139
140 action = []
140 action = []
141 copy, copied, diverge = {}, {}, {}
141 copy, copied, diverge = {}, {}, {}
142
142
143 def fmerge(f, f2, fa):
143 def fmerge(f, f2, fa):
144 """merge flags"""
144 """merge flags"""
145 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
145 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
146 if m == n: # flags agree
146 if m == n: # flags agree
147 return m # unchanged
147 return m # unchanged
148 if m and n and not a: # flags set, don't agree, differ from parent
148 if m and n and not a: # flags set, don't agree, differ from parent
149 r = repo.ui.prompt(
149 r = repo.ui.prompt(
150 _(" conflicting flags for %s\n"
150 _(" conflicting flags for %s\n"
151 "(n)one, e(x)ec or sym(l)ink?") % f,
151 "(n)one, e(x)ec or sym(l)ink?") % f,
152 (_("&None"), _("E&xec"), _("Sym&link")), _("n"))
152 (_("&None"), _("E&xec"), _("Sym&link")), _("n"))
153 return r != _("n") and r or ''
153 return r != _("n") and r or ''
154 if m and m != a: # changed from a to m
154 if m and m != a: # changed from a to m
155 return m
155 return m
156 if n and n != a: # changed from a to n
156 if n and n != a: # changed from a to n
157 return n
157 return n
158 return '' # flag was cleared
158 return '' # flag was cleared
159
159
160 def act(msg, m, f, *args):
160 def act(msg, m, f, *args):
161 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
161 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
162 action.append((f, m) + args)
162 action.append((f, m) + args)
163
163
164 if pa and not (backwards or overwrite):
164 if pa and not (backwards or overwrite):
165 if repo.ui.configbool("merge", "followcopies", True):
165 if repo.ui.configbool("merge", "followcopies", True):
166 dirs = repo.ui.configbool("merge", "followdirs", True)
166 dirs = repo.ui.configbool("merge", "followdirs", True)
167 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
167 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
168 copied = set(copy.values())
168 copied = set(copy.values())
169 for of, fl in diverge.iteritems():
169 for of, fl in diverge.iteritems():
170 act("divergent renames", "dr", of, fl)
170 act("divergent renames", "dr", of, fl)
171
171
172 # Compare manifests
172 # Compare manifests
173 for f, n in m1.iteritems():
173 for f, n in m1.iteritems():
174 if partial and not partial(f):
174 if partial and not partial(f):
175 continue
175 continue
176 if f in m2:
176 if f in m2:
177 rflags = fmerge(f, f, f)
177 rflags = fmerge(f, f, f)
178 # are files different?
178 # are files different?
179 if n != m2[f]:
179 if n != m2[f]:
180 a = ma.get(f, nullid)
180 a = ma.get(f, nullid)
181 # are both different from the ancestor?
181 # are both different from the ancestor?
182 if n != a and m2[f] != a:
182 if n != a and m2[f] != a:
183 act("versions differ", "m", f, f, f, rflags, False)
183 act("versions differ", "m", f, f, f, rflags, False)
184 # is remote's version newer?
184 # is remote's version newer?
185 elif m2[f] != a:
185 elif m2[f] != a:
186 # are we clobbering?
186 # are we clobbering?
187 if overwrite:
187 if overwrite:
188 act("clobbering", "g", f, rflags)
188 act("clobbering", "g", f, rflags)
189 # or are we going back in time and clean?
189 # or are we going back in time and clean?
190 elif backwards:
190 elif backwards:
191 if not n[20:] or not p2[f].cmp(p1[f].data()):
191 if not n[20:] or not p2[f].cmp(p1[f].data()):
192 act("reverting", "g", f, rflags)
192 act("reverting", "g", f, rflags)
193 else:
193 else:
194 act("remote is newer", "g", f, rflags)
194 act("remote is newer", "g", f, rflags)
195 # local is newer, not overwrite, check mode bits
195 # local is newer, not overwrite, check mode bits
196 elif m1.flags(f) != rflags:
196 elif m1.flags(f) != rflags:
197 act("update permissions", "e", f, rflags)
197 act("update permissions", "e", f, rflags)
198 # contents same, check mode bits
198 # contents same, check mode bits
199 elif m1.flags(f) != rflags:
199 elif m1.flags(f) != rflags:
200 act("update permissions", "e", f, rflags)
200 act("update permissions", "e", f, rflags)
201 elif f in copied:
201 elif f in copied:
202 continue
202 continue
203 elif f in copy:
203 elif f in copy:
204 f2 = copy[f]
204 f2 = copy[f]
205 if f2 not in m2: # directory rename
205 if f2 not in m2: # directory rename
206 act("remote renamed directory to " + f2, "d",
206 act("remote renamed directory to " + f2, "d",
207 f, None, f2, m1.flags(f))
207 f, None, f2, m1.flags(f))
208 elif f2 in m1: # case 2 A,B/B/B
208 elif f2 in m1: # case 2 A,B/B/B
209 act("local copied to " + f2, "m",
209 act("local copied to " + f2, "m",
210 f, f2, f, fmerge(f, f2, f2), False)
210 f, f2, f, fmerge(f, f2, f2), False)
211 else: # case 4,21 A/B/B
211 else: # case 4,21 A/B/B
212 act("local moved to " + f2, "m",
212 act("local moved to " + f2, "m",
213 f, f2, f, fmerge(f, f2, f2), False)
213 f, f2, f, fmerge(f, f2, f2), False)
214 elif f in ma and not n[20:]:
214 elif f in ma and not n[20:]:
215 if n != ma[f] and not overwrite:
215 if n != ma[f]:
216 if repo.ui.prompt(
216 if repo.ui.prompt(
217 _(" local changed %s which remote deleted\n"
217 _(" local changed %s which remote deleted\n"
218 "use (c)hanged version or (d)elete?") % f,
218 "use (c)hanged version or (d)elete?") % f,
219 (_("&Changed"), _("&Delete")), _("c")) == _("d"):
219 (_("&Changed"), _("&Delete")), _("c")) == _("d"):
220 act("prompt delete", "r", f)
220 act("prompt delete", "r", f)
221 else:
221 else:
222 act("prompt keep", "a", f)
222 act("prompt keep", "a", f)
223 else:
223 else:
224 act("other deleted", "r", f)
224 act("other deleted", "r", f)
225 elif n[20:] == "a": # only forget locally-added
225 elif n[20:] == "a": # only forget locally-added
226 act("remote deleted", "f", f)
226 act("remote deleted", "f", f)
227 else:
227 else:
228 # file is created on branch or in working directory
228 # file is created on branch or in working directory
229 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
229 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
230 act("remote deleted", "r", f)
230 act("remote deleted", "r", f)
231
231
232 for f, n in m2.iteritems():
232 for f, n in m2.iteritems():
233 if partial and not partial(f):
233 if partial and not partial(f):
234 continue
234 continue
235 if f in m1:
235 if f in m1:
236 continue
236 continue
237 if f in copied:
237 if f in copied:
238 continue
238 continue
239 if f in copy:
239 if f in copy:
240 f2 = copy[f]
240 f2 = copy[f]
241 if f2 not in m1: # directory rename
241 if f2 not in m1: # directory rename
242 act("local renamed directory to " + f2, "d",
242 act("local renamed directory to " + f2, "d",
243 None, f, f2, m2.flags(f))
243 None, f, f2, m2.flags(f))
244 elif f2 in m2: # rename case 1, A/A,B/A
244 elif f2 in m2: # rename case 1, A/A,B/A
245 act("remote copied to " + f, "m",
245 act("remote copied to " + f, "m",
246 f2, f, f, fmerge(f2, f, f2), False)
246 f2, f, f, fmerge(f2, f, f2), False)
247 else: # case 3,20 A/B/A
247 else: # case 3,20 A/B/A
248 act("remote moved to " + f, "m",
248 act("remote moved to " + f, "m",
249 f2, f, f, fmerge(f2, f, f2), True)
249 f2, f, f, fmerge(f2, f, f2), True)
250 elif f in ma:
250 elif f in ma:
251 if overwrite or backwards:
251 if overwrite or backwards:
252 act("recreating", "g", f, m2.flags(f))
252 act("recreating", "g", f, m2.flags(f))
253 elif n != ma[f]:
253 elif n != ma[f]:
254 if repo.ui.prompt(
254 if repo.ui.prompt(
255 _("remote changed %s which local deleted\n"
255 _("remote changed %s which local deleted\n"
256 "use (c)hanged version or leave (d)eleted?") % f,
256 "use (c)hanged version or leave (d)eleted?") % f,
257 (_("&Changed"), _("&Deleted")), _("c")) == _("c"):
257 (_("&Changed"), _("&Deleted")), _("c")) == _("c"):
258 act("prompt recreating", "g", f, m2.flags(f))
258 act("prompt recreating", "g", f, m2.flags(f))
259 else:
259 else:
260 act("remote created", "g", f, m2.flags(f))
260 act("remote created", "g", f, m2.flags(f))
261
261
262 return action
262 return action
263
263
264 def actionkey(a):
264 def actionkey(a):
265 return a[1] == 'r' and -1 or 0, a
265 return a[1] == 'r' and -1 or 0, a
266
266
267 def applyupdates(repo, action, wctx, mctx):
267 def applyupdates(repo, action, wctx, mctx):
268 "apply the merge action list to the working directory"
268 "apply the merge action list to the working directory"
269
269
270 updated, merged, removed, unresolved = 0, 0, 0, 0
270 updated, merged, removed, unresolved = 0, 0, 0, 0
271 ms = mergestate(repo)
271 ms = mergestate(repo)
272 ms.reset(wctx.parents()[0].node())
272 ms.reset(wctx.parents()[0].node())
273 moves = []
273 moves = []
274 action.sort(key=actionkey)
274 action.sort(key=actionkey)
275
275
276 # prescan for merges
276 # prescan for merges
277 for a in action:
277 for a in action:
278 f, m = a[:2]
278 f, m = a[:2]
279 if m == 'm': # merge
279 if m == 'm': # merge
280 f2, fd, flags, move = a[2:]
280 f2, fd, flags, move = a[2:]
281 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
281 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
282 fcl = wctx[f]
282 fcl = wctx[f]
283 fco = mctx[f2]
283 fco = mctx[f2]
284 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
284 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
285 ms.add(fcl, fco, fca, fd, flags)
285 ms.add(fcl, fco, fca, fd, flags)
286 if f != fd and move:
286 if f != fd and move:
287 moves.append(f)
287 moves.append(f)
288
288
289 # remove renamed files after safely stored
289 # remove renamed files after safely stored
290 for f in moves:
290 for f in moves:
291 if util.lexists(repo.wjoin(f)):
291 if util.lexists(repo.wjoin(f)):
292 repo.ui.debug(_("removing %s\n") % f)
292 repo.ui.debug(_("removing %s\n") % f)
293 os.unlink(repo.wjoin(f))
293 os.unlink(repo.wjoin(f))
294
294
295 audit_path = util.path_auditor(repo.root)
295 audit_path = util.path_auditor(repo.root)
296
296
297 for a in action:
297 for a in action:
298 f, m = a[:2]
298 f, m = a[:2]
299 if f and f[0] == "/":
299 if f and f[0] == "/":
300 continue
300 continue
301 if m == "r": # remove
301 if m == "r": # remove
302 repo.ui.note(_("removing %s\n") % f)
302 repo.ui.note(_("removing %s\n") % f)
303 audit_path(f)
303 audit_path(f)
304 try:
304 try:
305 util.unlink(repo.wjoin(f))
305 util.unlink(repo.wjoin(f))
306 except OSError, inst:
306 except OSError, inst:
307 if inst.errno != errno.ENOENT:
307 if inst.errno != errno.ENOENT:
308 repo.ui.warn(_("update failed to remove %s: %s!\n") %
308 repo.ui.warn(_("update failed to remove %s: %s!\n") %
309 (f, inst.strerror))
309 (f, inst.strerror))
310 removed += 1
310 removed += 1
311 elif m == "m": # merge
311 elif m == "m": # merge
312 f2, fd, flags, move = a[2:]
312 f2, fd, flags, move = a[2:]
313 r = ms.resolve(fd, wctx, mctx)
313 r = ms.resolve(fd, wctx, mctx)
314 if r > 0:
314 if r > 0:
315 unresolved += 1
315 unresolved += 1
316 else:
316 else:
317 if r is None:
317 if r is None:
318 updated += 1
318 updated += 1
319 else:
319 else:
320 merged += 1
320 merged += 1
321 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
321 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
322 if f != fd and move and util.lexists(repo.wjoin(f)):
322 if f != fd and move and util.lexists(repo.wjoin(f)):
323 repo.ui.debug(_("removing %s\n") % f)
323 repo.ui.debug(_("removing %s\n") % f)
324 os.unlink(repo.wjoin(f))
324 os.unlink(repo.wjoin(f))
325 elif m == "g": # get
325 elif m == "g": # get
326 flags = a[2]
326 flags = a[2]
327 repo.ui.note(_("getting %s\n") % f)
327 repo.ui.note(_("getting %s\n") % f)
328 t = mctx.filectx(f).data()
328 t = mctx.filectx(f).data()
329 repo.wwrite(f, t, flags)
329 repo.wwrite(f, t, flags)
330 updated += 1
330 updated += 1
331 elif m == "d": # directory rename
331 elif m == "d": # directory rename
332 f2, fd, flags = a[2:]
332 f2, fd, flags = a[2:]
333 if f:
333 if f:
334 repo.ui.note(_("moving %s to %s\n") % (f, fd))
334 repo.ui.note(_("moving %s to %s\n") % (f, fd))
335 t = wctx.filectx(f).data()
335 t = wctx.filectx(f).data()
336 repo.wwrite(fd, t, flags)
336 repo.wwrite(fd, t, flags)
337 util.unlink(repo.wjoin(f))
337 util.unlink(repo.wjoin(f))
338 if f2:
338 if f2:
339 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
339 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
340 t = mctx.filectx(f2).data()
340 t = mctx.filectx(f2).data()
341 repo.wwrite(fd, t, flags)
341 repo.wwrite(fd, t, flags)
342 updated += 1
342 updated += 1
343 elif m == "dr": # divergent renames
343 elif m == "dr": # divergent renames
344 fl = a[2]
344 fl = a[2]
345 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
345 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
346 for nf in fl:
346 for nf in fl:
347 repo.ui.warn(" %s\n" % nf)
347 repo.ui.warn(" %s\n" % nf)
348 elif m == "e": # exec
348 elif m == "e": # exec
349 flags = a[2]
349 flags = a[2]
350 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
350 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
351
351
352 return updated, merged, removed, unresolved
352 return updated, merged, removed, unresolved
353
353
354 def recordupdates(repo, action, branchmerge):
354 def recordupdates(repo, action, branchmerge):
355 "record merge actions to the dirstate"
355 "record merge actions to the dirstate"
356
356
357 for a in action:
357 for a in action:
358 f, m = a[:2]
358 f, m = a[:2]
359 if m == "r": # remove
359 if m == "r": # remove
360 if branchmerge:
360 if branchmerge:
361 repo.dirstate.remove(f)
361 repo.dirstate.remove(f)
362 else:
362 else:
363 repo.dirstate.forget(f)
363 repo.dirstate.forget(f)
364 elif m == "a": # re-add
364 elif m == "a": # re-add
365 if not branchmerge:
365 if not branchmerge:
366 repo.dirstate.add(f)
366 repo.dirstate.add(f)
367 elif m == "f": # forget
367 elif m == "f": # forget
368 repo.dirstate.forget(f)
368 repo.dirstate.forget(f)
369 elif m == "e": # exec change
369 elif m == "e": # exec change
370 repo.dirstate.normallookup(f)
370 repo.dirstate.normallookup(f)
371 elif m == "g": # get
371 elif m == "g": # get
372 if branchmerge:
372 if branchmerge:
373 repo.dirstate.normaldirty(f)
373 repo.dirstate.normaldirty(f)
374 else:
374 else:
375 repo.dirstate.normal(f)
375 repo.dirstate.normal(f)
376 elif m == "m": # merge
376 elif m == "m": # merge
377 f2, fd, flag, move = a[2:]
377 f2, fd, flag, move = a[2:]
378 if branchmerge:
378 if branchmerge:
379 # We've done a branch merge, mark this file as merged
379 # We've done a branch merge, mark this file as merged
380 # so that we properly record the merger later
380 # so that we properly record the merger later
381 repo.dirstate.merge(fd)
381 repo.dirstate.merge(fd)
382 if f != f2: # copy/rename
382 if f != f2: # copy/rename
383 if move:
383 if move:
384 repo.dirstate.remove(f)
384 repo.dirstate.remove(f)
385 if f != fd:
385 if f != fd:
386 repo.dirstate.copy(f, fd)
386 repo.dirstate.copy(f, fd)
387 else:
387 else:
388 repo.dirstate.copy(f2, fd)
388 repo.dirstate.copy(f2, fd)
389 else:
389 else:
390 # We've update-merged a locally modified file, so
390 # We've update-merged a locally modified file, so
391 # we set the dirstate to emulate a normal checkout
391 # we set the dirstate to emulate a normal checkout
392 # of that file some time in the past. Thus our
392 # of that file some time in the past. Thus our
393 # merge will appear as a normal local file
393 # merge will appear as a normal local file
394 # modification.
394 # modification.
395 repo.dirstate.normallookup(fd)
395 repo.dirstate.normallookup(fd)
396 if move:
396 if move:
397 repo.dirstate.forget(f)
397 repo.dirstate.forget(f)
398 elif m == "d": # directory rename
398 elif m == "d": # directory rename
399 f2, fd, flag = a[2:]
399 f2, fd, flag = a[2:]
400 if not f2 and f not in repo.dirstate:
400 if not f2 and f not in repo.dirstate:
401 # untracked file moved
401 # untracked file moved
402 continue
402 continue
403 if branchmerge:
403 if branchmerge:
404 repo.dirstate.add(fd)
404 repo.dirstate.add(fd)
405 if f:
405 if f:
406 repo.dirstate.remove(f)
406 repo.dirstate.remove(f)
407 repo.dirstate.copy(f, fd)
407 repo.dirstate.copy(f, fd)
408 if f2:
408 if f2:
409 repo.dirstate.copy(f2, fd)
409 repo.dirstate.copy(f2, fd)
410 else:
410 else:
411 repo.dirstate.normal(fd)
411 repo.dirstate.normal(fd)
412 if f:
412 if f:
413 repo.dirstate.forget(f)
413 repo.dirstate.forget(f)
414
414
415 def update(repo, node, branchmerge, force, partial):
415 def update(repo, node, branchmerge, force, partial):
416 """
416 """
417 Perform a merge between the working directory and the given node
417 Perform a merge between the working directory and the given node
418
418
419 branchmerge = whether to merge between branches
419 branchmerge = whether to merge between branches
420 force = whether to force branch merging or file overwriting
420 force = whether to force branch merging or file overwriting
421 partial = a function to filter file lists (dirstate not updated)
421 partial = a function to filter file lists (dirstate not updated)
422 """
422 """
423
423
424 wlock = repo.wlock()
424 wlock = repo.wlock()
425 try:
425 try:
426 wc = repo[None]
426 wc = repo[None]
427 if node is None:
427 if node is None:
428 # tip of current branch
428 # tip of current branch
429 try:
429 try:
430 node = repo.branchtags()[wc.branch()]
430 node = repo.branchtags()[wc.branch()]
431 except KeyError:
431 except KeyError:
432 if wc.branch() == "default": # no default branch!
432 if wc.branch() == "default": # no default branch!
433 node = repo.lookup("tip") # update to tip
433 node = repo.lookup("tip") # update to tip
434 else:
434 else:
435 raise util.Abort(_("branch %s not found") % wc.branch())
435 raise util.Abort(_("branch %s not found") % wc.branch())
436 overwrite = force and not branchmerge
436 overwrite = force and not branchmerge
437 pl = wc.parents()
437 pl = wc.parents()
438 p1, p2 = pl[0], repo[node]
438 p1, p2 = pl[0], repo[node]
439 pa = p1.ancestor(p2)
439 pa = p1.ancestor(p2)
440 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
440 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
441 fastforward = False
441 fastforward = False
442
442
443 ### check phase
443 ### check phase
444 if not overwrite and len(pl) > 1:
444 if not overwrite and len(pl) > 1:
445 raise util.Abort(_("outstanding uncommitted merges"))
445 raise util.Abort(_("outstanding uncommitted merges"))
446 if branchmerge:
446 if branchmerge:
447 if pa == p2:
447 if pa == p2:
448 raise util.Abort(_("can't merge with ancestor"))
448 raise util.Abort(_("can't merge with ancestor"))
449 elif pa == p1:
449 elif pa == p1:
450 if p1.branch() != p2.branch():
450 if p1.branch() != p2.branch():
451 fastforward = True
451 fastforward = True
452 else:
452 else:
453 raise util.Abort(_("nothing to merge (use 'hg update'"
453 raise util.Abort(_("nothing to merge (use 'hg update'"
454 " or check 'hg heads')"))
454 " or check 'hg heads')"))
455 if not force and (wc.files() or wc.deleted()):
455 if not force and (wc.files() or wc.deleted()):
456 raise util.Abort(_("outstanding uncommitted changes "
456 raise util.Abort(_("outstanding uncommitted changes "
457 "(use 'hg status' to list changes)"))
457 "(use 'hg status' to list changes)"))
458 elif not overwrite:
458 elif not overwrite:
459 if pa == p1 or pa == p2: # linear
459 if pa == p1 or pa == p2: # linear
460 pass # all good
460 pass # all good
461 elif p1.branch() == p2.branch():
461 elif p1.branch() == p2.branch():
462 if wc.files() or wc.deleted():
462 if wc.files() or wc.deleted():
463 raise util.Abort(_("crosses branches (use 'hg merge' or "
463 raise util.Abort(_("crosses branches (use 'hg merge' or "
464 "'hg update -C' to discard changes)"))
464 "'hg update -C' to discard changes)"))
465 raise util.Abort(_("crosses branches (use 'hg merge' "
465 raise util.Abort(_("crosses branches (use 'hg merge' "
466 "or 'hg update -C')"))
466 "or 'hg update -C')"))
467 elif wc.files() or wc.deleted():
467 elif wc.files() or wc.deleted():
468 raise util.Abort(_("crosses named branches (use "
468 raise util.Abort(_("crosses named branches (use "
469 "'hg update -C' to discard changes)"))
469 "'hg update -C' to discard changes)"))
470 else:
470 else:
471 # Allow jumping branches if there are no changes
471 # Allow jumping branches if there are no changes
472 overwrite = True
472 overwrite = True
473
473
474 ### calculate phase
474 ### calculate phase
475 action = []
475 action = []
476 if not force:
476 if not force:
477 _checkunknown(wc, p2)
477 _checkunknown(wc, p2)
478 if not util.checkcase(repo.path):
478 if not util.checkcase(repo.path):
479 _checkcollision(p2)
479 _checkcollision(p2)
480 action += _forgetremoved(wc, p2, branchmerge)
480 action += _forgetremoved(wc, p2, branchmerge)
481 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
481 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
482
482
483 ### apply phase
483 ### apply phase
484 if not branchmerge: # just jump to the new rev
484 if not branchmerge: # just jump to the new rev
485 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
485 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
486 if not partial:
486 if not partial:
487 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
487 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
488
488
489 stats = applyupdates(repo, action, wc, p2)
489 stats = applyupdates(repo, action, wc, p2)
490
490
491 if not partial:
491 if not partial:
492 recordupdates(repo, action, branchmerge)
492 recordupdates(repo, action, branchmerge)
493 repo.dirstate.setparents(fp1, fp2)
493 repo.dirstate.setparents(fp1, fp2)
494 if not branchmerge and not fastforward:
494 if not branchmerge and not fastforward:
495 repo.dirstate.setbranch(p2.branch())
495 repo.dirstate.setbranch(p2.branch())
496 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
496 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
497
497
498 return stats
498 return stats
499 finally:
499 finally:
500 wlock.release()
500 wlock.release()
General Comments 0
You need to be logged in to leave comments. Login now