##// END OF EJS Templates
merge: fix prompt keep
Matt Mackall -
r8736:7619b16e default
parent child Browse files
Show More
@@ -1,498 +1,499
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 we clobbering?
181 # are we clobbering?
182 if overwrite:
182 if overwrite:
183 act("clobbering", "g", f, rflags)
183 act("clobbering", "g", f, rflags)
184 # or are we going back in time and clean?
184 # or are we going back in time and clean?
185 elif backwards:
185 elif backwards:
186 if not n[20:] or not p2[f].cmp(p1[f].data()):
186 if not n[20:] or not p2[f].cmp(p1[f].data()):
187 act("reverting", "g", f, rflags)
187 act("reverting", "g", f, rflags)
188 # are both different from the ancestor?
188 # are both different from the ancestor?
189 elif n != a and m2[f] != a:
189 elif n != a and m2[f] != a:
190 act("versions differ", "m", f, f, f, rflags, False)
190 act("versions differ", "m", f, f, f, rflags, False)
191 # is remote's version newer?
191 # is remote's version newer?
192 elif m2[f] != a:
192 elif m2[f] != a:
193 act("remote is newer", "g", f, rflags)
193 act("remote is newer", "g", f, rflags)
194 # local is newer, not overwrite, check mode bits
194 # local is newer, not overwrite, check mode bits
195 elif m1.flags(f) != rflags:
195 elif m1.flags(f) != rflags:
196 act("update permissions", "e", f, rflags)
196 act("update permissions", "e", f, rflags)
197 # contents same, check mode bits
197 # contents same, check mode bits
198 elif m1.flags(f) != rflags:
198 elif m1.flags(f) != rflags:
199 act("update permissions", "e", f, rflags)
199 act("update permissions", "e", f, rflags)
200 elif f in copied:
200 elif f in copied:
201 continue
201 continue
202 elif f in copy:
202 elif f in copy:
203 f2 = copy[f]
203 f2 = copy[f]
204 if f2 not in m2: # directory rename
204 if f2 not in m2: # directory rename
205 act("remote renamed directory to " + f2, "d",
205 act("remote renamed directory to " + f2, "d",
206 f, None, f2, m1.flags(f))
206 f, None, f2, m1.flags(f))
207 elif f2 in m1: # case 2 A,B/B/B
207 elif f2 in m1: # case 2 A,B/B/B
208 act("local copied to " + f2, "m",
208 act("local copied to " + f2, "m",
209 f, f2, f, fmerge(f, f2, f2), False)
209 f, f2, f, fmerge(f, f2, f2), False)
210 else: # case 4,21 A/B/B
210 else: # case 4,21 A/B/B
211 act("local moved to " + f2, "m",
211 act("local moved to " + f2, "m",
212 f, f2, f, fmerge(f, f2, f2), False)
212 f, f2, f, fmerge(f, f2, f2), False)
213 elif f in ma and not n[20:]:
213 elif f in ma and not n[20:]:
214 if n != ma[f] and not overwrite:
214 if n != ma[f] and not overwrite:
215 if repo.ui.prompt(
215 if repo.ui.prompt(
216 _(" local changed %s which remote deleted\n"
216 _(" local changed %s which remote deleted\n"
217 "use (c)hanged version or (d)elete?") % f,
217 "use (c)hanged version or (d)elete?") % f,
218 (_("&Changed"), _("&Delete")), _("c")) == _("d"):
218 (_("&Changed"), _("&Delete")), _("c")) == _("d"):
219 act("prompt delete", "r", f)
219 act("prompt delete", "r", f)
220 act("prompt keep", "a", f)
220 else:
221 act("prompt keep", "a", f)
221 else:
222 else:
222 act("other deleted", "r", f)
223 act("other deleted", "r", f)
223 elif overwrite and n[20:] == "a": # do not erase the working copy
224 elif overwrite and n[20:] == "a": # do not erase the working copy
224 act("remote deleted", "f", f)
225 act("remote deleted", "f", f)
225 else:
226 else:
226 # file is created on branch or in working directory
227 # file is created on branch or in working directory
227 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
228 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
228 act("remote deleted", "r", f)
229 act("remote deleted", "r", f)
229
230
230 for f, n in m2.iteritems():
231 for f, n in m2.iteritems():
231 if partial and not partial(f):
232 if partial and not partial(f):
232 continue
233 continue
233 if f in m1:
234 if f in m1:
234 continue
235 continue
235 if f in copied:
236 if f in copied:
236 continue
237 continue
237 if f in copy:
238 if f in copy:
238 f2 = copy[f]
239 f2 = copy[f]
239 if f2 not in m1: # directory rename
240 if f2 not in m1: # directory rename
240 act("local renamed directory to " + f2, "d",
241 act("local renamed directory to " + f2, "d",
241 None, f, f2, m2.flags(f))
242 None, f, f2, m2.flags(f))
242 elif f2 in m2: # rename case 1, A/A,B/A
243 elif f2 in m2: # rename case 1, A/A,B/A
243 act("remote copied to " + f, "m",
244 act("remote copied to " + f, "m",
244 f2, f, f, fmerge(f2, f, f2), False)
245 f2, f, f, fmerge(f2, f, f2), False)
245 else: # case 3,20 A/B/A
246 else: # case 3,20 A/B/A
246 act("remote moved to " + f, "m",
247 act("remote moved to " + f, "m",
247 f2, f, f, fmerge(f2, f, f2), True)
248 f2, f, f, fmerge(f2, f, f2), True)
248 elif f in ma:
249 elif f in ma:
249 if overwrite or backwards:
250 if overwrite or backwards:
250 act("recreating", "g", f, m2.flags(f))
251 act("recreating", "g", f, m2.flags(f))
251 elif n != ma[f]:
252 elif n != ma[f]:
252 if repo.ui.prompt(
253 if repo.ui.prompt(
253 _("remote changed %s which local deleted\n"
254 _("remote changed %s which local deleted\n"
254 "use (c)hanged version or leave (d)eleted?") % f,
255 "use (c)hanged version or leave (d)eleted?") % f,
255 (_("&Changed"), _("&Deleted")), _("c")) == _("c"):
256 (_("&Changed"), _("&Deleted")), _("c")) == _("c"):
256 act("prompt recreating", "g", f, m2.flags(f))
257 act("prompt recreating", "g", f, m2.flags(f))
257 else:
258 else:
258 act("remote created", "g", f, m2.flags(f))
259 act("remote created", "g", f, m2.flags(f))
259
260
260 return action
261 return action
261
262
262 def actionkey(a):
263 def actionkey(a):
263 return a[1] == 'r' and -1 or 0, a
264 return a[1] == 'r' and -1 or 0, a
264
265
265 def applyupdates(repo, action, wctx, mctx):
266 def applyupdates(repo, action, wctx, mctx):
266 "apply the merge action list to the working directory"
267 "apply the merge action list to the working directory"
267
268
268 updated, merged, removed, unresolved = 0, 0, 0, 0
269 updated, merged, removed, unresolved = 0, 0, 0, 0
269 ms = mergestate(repo)
270 ms = mergestate(repo)
270 ms.reset(wctx.parents()[0].node())
271 ms.reset(wctx.parents()[0].node())
271 moves = []
272 moves = []
272 action.sort(key=actionkey)
273 action.sort(key=actionkey)
273
274
274 # prescan for merges
275 # prescan for merges
275 for a in action:
276 for a in action:
276 f, m = a[:2]
277 f, m = a[:2]
277 if m == 'm': # merge
278 if m == 'm': # merge
278 f2, fd, flags, move = a[2:]
279 f2, fd, flags, move = a[2:]
279 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
280 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
280 fcl = wctx[f]
281 fcl = wctx[f]
281 fco = mctx[f2]
282 fco = mctx[f2]
282 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
283 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
283 ms.add(fcl, fco, fca, fd, flags)
284 ms.add(fcl, fco, fca, fd, flags)
284 if f != fd and move:
285 if f != fd and move:
285 moves.append(f)
286 moves.append(f)
286
287
287 # remove renamed files after safely stored
288 # remove renamed files after safely stored
288 for f in moves:
289 for f in moves:
289 if util.lexists(repo.wjoin(f)):
290 if util.lexists(repo.wjoin(f)):
290 repo.ui.debug(_("removing %s\n") % f)
291 repo.ui.debug(_("removing %s\n") % f)
291 os.unlink(repo.wjoin(f))
292 os.unlink(repo.wjoin(f))
292
293
293 audit_path = util.path_auditor(repo.root)
294 audit_path = util.path_auditor(repo.root)
294
295
295 for a in action:
296 for a in action:
296 f, m = a[:2]
297 f, m = a[:2]
297 if f and f[0] == "/":
298 if f and f[0] == "/":
298 continue
299 continue
299 if m == "r": # remove
300 if m == "r": # remove
300 repo.ui.note(_("removing %s\n") % f)
301 repo.ui.note(_("removing %s\n") % f)
301 audit_path(f)
302 audit_path(f)
302 try:
303 try:
303 util.unlink(repo.wjoin(f))
304 util.unlink(repo.wjoin(f))
304 except OSError, inst:
305 except OSError, inst:
305 if inst.errno != errno.ENOENT:
306 if inst.errno != errno.ENOENT:
306 repo.ui.warn(_("update failed to remove %s: %s!\n") %
307 repo.ui.warn(_("update failed to remove %s: %s!\n") %
307 (f, inst.strerror))
308 (f, inst.strerror))
308 removed += 1
309 removed += 1
309 elif m == "m": # merge
310 elif m == "m": # merge
310 f2, fd, flags, move = a[2:]
311 f2, fd, flags, move = a[2:]
311 r = ms.resolve(fd, wctx, mctx)
312 r = ms.resolve(fd, wctx, mctx)
312 if r > 0:
313 if r > 0:
313 unresolved += 1
314 unresolved += 1
314 else:
315 else:
315 if r is None:
316 if r is None:
316 updated += 1
317 updated += 1
317 else:
318 else:
318 merged += 1
319 merged += 1
319 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
320 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
320 if f != fd and move and util.lexists(repo.wjoin(f)):
321 if f != fd and move and util.lexists(repo.wjoin(f)):
321 repo.ui.debug(_("removing %s\n") % f)
322 repo.ui.debug(_("removing %s\n") % f)
322 os.unlink(repo.wjoin(f))
323 os.unlink(repo.wjoin(f))
323 elif m == "g": # get
324 elif m == "g": # get
324 flags = a[2]
325 flags = a[2]
325 repo.ui.note(_("getting %s\n") % f)
326 repo.ui.note(_("getting %s\n") % f)
326 t = mctx.filectx(f).data()
327 t = mctx.filectx(f).data()
327 repo.wwrite(f, t, flags)
328 repo.wwrite(f, t, flags)
328 updated += 1
329 updated += 1
329 elif m == "d": # directory rename
330 elif m == "d": # directory rename
330 f2, fd, flags = a[2:]
331 f2, fd, flags = a[2:]
331 if f:
332 if f:
332 repo.ui.note(_("moving %s to %s\n") % (f, fd))
333 repo.ui.note(_("moving %s to %s\n") % (f, fd))
333 t = wctx.filectx(f).data()
334 t = wctx.filectx(f).data()
334 repo.wwrite(fd, t, flags)
335 repo.wwrite(fd, t, flags)
335 util.unlink(repo.wjoin(f))
336 util.unlink(repo.wjoin(f))
336 if f2:
337 if f2:
337 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
338 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
338 t = mctx.filectx(f2).data()
339 t = mctx.filectx(f2).data()
339 repo.wwrite(fd, t, flags)
340 repo.wwrite(fd, t, flags)
340 updated += 1
341 updated += 1
341 elif m == "dr": # divergent renames
342 elif m == "dr": # divergent renames
342 fl = a[2]
343 fl = a[2]
343 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
344 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
344 for nf in fl:
345 for nf in fl:
345 repo.ui.warn(" %s\n" % nf)
346 repo.ui.warn(" %s\n" % nf)
346 elif m == "e": # exec
347 elif m == "e": # exec
347 flags = a[2]
348 flags = a[2]
348 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
349 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
349
350
350 return updated, merged, removed, unresolved
351 return updated, merged, removed, unresolved
351
352
352 def recordupdates(repo, action, branchmerge):
353 def recordupdates(repo, action, branchmerge):
353 "record merge actions to the dirstate"
354 "record merge actions to the dirstate"
354
355
355 for a in action:
356 for a in action:
356 f, m = a[:2]
357 f, m = a[:2]
357 if m == "r": # remove
358 if m == "r": # remove
358 if branchmerge:
359 if branchmerge:
359 repo.dirstate.remove(f)
360 repo.dirstate.remove(f)
360 else:
361 else:
361 repo.dirstate.forget(f)
362 repo.dirstate.forget(f)
362 elif m == "a": # re-add
363 elif m == "a": # re-add
363 if not branchmerge:
364 if not branchmerge:
364 repo.dirstate.add(f)
365 repo.dirstate.add(f)
365 elif m == "f": # forget
366 elif m == "f": # forget
366 repo.dirstate.forget(f)
367 repo.dirstate.forget(f)
367 elif m == "e": # exec change
368 elif m == "e": # exec change
368 repo.dirstate.normallookup(f)
369 repo.dirstate.normallookup(f)
369 elif m == "g": # get
370 elif m == "g": # get
370 if branchmerge:
371 if branchmerge:
371 repo.dirstate.normaldirty(f)
372 repo.dirstate.normaldirty(f)
372 else:
373 else:
373 repo.dirstate.normal(f)
374 repo.dirstate.normal(f)
374 elif m == "m": # merge
375 elif m == "m": # merge
375 f2, fd, flag, move = a[2:]
376 f2, fd, flag, move = a[2:]
376 if branchmerge:
377 if branchmerge:
377 # We've done a branch merge, mark this file as merged
378 # We've done a branch merge, mark this file as merged
378 # so that we properly record the merger later
379 # so that we properly record the merger later
379 repo.dirstate.merge(fd)
380 repo.dirstate.merge(fd)
380 if f != f2: # copy/rename
381 if f != f2: # copy/rename
381 if move:
382 if move:
382 repo.dirstate.remove(f)
383 repo.dirstate.remove(f)
383 if f != fd:
384 if f != fd:
384 repo.dirstate.copy(f, fd)
385 repo.dirstate.copy(f, fd)
385 else:
386 else:
386 repo.dirstate.copy(f2, fd)
387 repo.dirstate.copy(f2, fd)
387 else:
388 else:
388 # We've update-merged a locally modified file, so
389 # We've update-merged a locally modified file, so
389 # we set the dirstate to emulate a normal checkout
390 # we set the dirstate to emulate a normal checkout
390 # of that file some time in the past. Thus our
391 # of that file some time in the past. Thus our
391 # merge will appear as a normal local file
392 # merge will appear as a normal local file
392 # modification.
393 # modification.
393 repo.dirstate.normallookup(fd)
394 repo.dirstate.normallookup(fd)
394 if move:
395 if move:
395 repo.dirstate.forget(f)
396 repo.dirstate.forget(f)
396 elif m == "d": # directory rename
397 elif m == "d": # directory rename
397 f2, fd, flag = a[2:]
398 f2, fd, flag = a[2:]
398 if not f2 and f not in repo.dirstate:
399 if not f2 and f not in repo.dirstate:
399 # untracked file moved
400 # untracked file moved
400 continue
401 continue
401 if branchmerge:
402 if branchmerge:
402 repo.dirstate.add(fd)
403 repo.dirstate.add(fd)
403 if f:
404 if f:
404 repo.dirstate.remove(f)
405 repo.dirstate.remove(f)
405 repo.dirstate.copy(f, fd)
406 repo.dirstate.copy(f, fd)
406 if f2:
407 if f2:
407 repo.dirstate.copy(f2, fd)
408 repo.dirstate.copy(f2, fd)
408 else:
409 else:
409 repo.dirstate.normal(fd)
410 repo.dirstate.normal(fd)
410 if f:
411 if f:
411 repo.dirstate.forget(f)
412 repo.dirstate.forget(f)
412
413
413 def update(repo, node, branchmerge, force, partial):
414 def update(repo, node, branchmerge, force, partial):
414 """
415 """
415 Perform a merge between the working directory and the given node
416 Perform a merge between the working directory and the given node
416
417
417 branchmerge = whether to merge between branches
418 branchmerge = whether to merge between branches
418 force = whether to force branch merging or file overwriting
419 force = whether to force branch merging or file overwriting
419 partial = a function to filter file lists (dirstate not updated)
420 partial = a function to filter file lists (dirstate not updated)
420 """
421 """
421
422
422 wlock = repo.wlock()
423 wlock = repo.wlock()
423 try:
424 try:
424 wc = repo[None]
425 wc = repo[None]
425 if node is None:
426 if node is None:
426 # tip of current branch
427 # tip of current branch
427 try:
428 try:
428 node = repo.branchtags()[wc.branch()]
429 node = repo.branchtags()[wc.branch()]
429 except KeyError:
430 except KeyError:
430 if wc.branch() == "default": # no default branch!
431 if wc.branch() == "default": # no default branch!
431 node = repo.lookup("tip") # update to tip
432 node = repo.lookup("tip") # update to tip
432 else:
433 else:
433 raise util.Abort(_("branch %s not found") % wc.branch())
434 raise util.Abort(_("branch %s not found") % wc.branch())
434 overwrite = force and not branchmerge
435 overwrite = force and not branchmerge
435 pl = wc.parents()
436 pl = wc.parents()
436 p1, p2 = pl[0], repo[node]
437 p1, p2 = pl[0], repo[node]
437 pa = p1.ancestor(p2)
438 pa = p1.ancestor(p2)
438 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
439 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
439 fastforward = False
440 fastforward = False
440
441
441 ### check phase
442 ### check phase
442 if not overwrite and len(pl) > 1:
443 if not overwrite and len(pl) > 1:
443 raise util.Abort(_("outstanding uncommitted merges"))
444 raise util.Abort(_("outstanding uncommitted merges"))
444 if branchmerge:
445 if branchmerge:
445 if pa == p2:
446 if pa == p2:
446 raise util.Abort(_("can't merge with ancestor"))
447 raise util.Abort(_("can't merge with ancestor"))
447 elif pa == p1:
448 elif pa == p1:
448 if p1.branch() != p2.branch():
449 if p1.branch() != p2.branch():
449 fastforward = True
450 fastforward = True
450 else:
451 else:
451 raise util.Abort(_("nothing to merge (use 'hg update'"
452 raise util.Abort(_("nothing to merge (use 'hg update'"
452 " or check 'hg heads')"))
453 " or check 'hg heads')"))
453 if not force and (wc.files() or wc.deleted()):
454 if not force and (wc.files() or wc.deleted()):
454 raise util.Abort(_("outstanding uncommitted changes "
455 raise util.Abort(_("outstanding uncommitted changes "
455 "(use 'hg status' to list changes)"))
456 "(use 'hg status' to list changes)"))
456 elif not overwrite:
457 elif not overwrite:
457 if pa == p1 or pa == p2: # linear
458 if pa == p1 or pa == p2: # linear
458 pass # all good
459 pass # all good
459 elif p1.branch() == p2.branch():
460 elif p1.branch() == p2.branch():
460 if wc.files() or wc.deleted():
461 if wc.files() or wc.deleted():
461 raise util.Abort(_("crosses branches (use 'hg merge' or "
462 raise util.Abort(_("crosses branches (use 'hg merge' or "
462 "'hg update -C' to discard changes)"))
463 "'hg update -C' to discard changes)"))
463 raise util.Abort(_("crosses branches (use 'hg merge' "
464 raise util.Abort(_("crosses branches (use 'hg merge' "
464 "or 'hg update -C')"))
465 "or 'hg update -C')"))
465 elif wc.files() or wc.deleted():
466 elif wc.files() or wc.deleted():
466 raise util.Abort(_("crosses named branches (use "
467 raise util.Abort(_("crosses named branches (use "
467 "'hg update -C' to discard changes)"))
468 "'hg update -C' to discard changes)"))
468 else:
469 else:
469 # Allow jumping branches if there are no changes
470 # Allow jumping branches if there are no changes
470 overwrite = True
471 overwrite = True
471
472
472 ### calculate phase
473 ### calculate phase
473 action = []
474 action = []
474 if not force:
475 if not force:
475 _checkunknown(wc, p2)
476 _checkunknown(wc, p2)
476 if not util.checkcase(repo.path):
477 if not util.checkcase(repo.path):
477 _checkcollision(p2)
478 _checkcollision(p2)
478 action += _forgetremoved(wc, p2, branchmerge)
479 action += _forgetremoved(wc, p2, branchmerge)
479 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
480 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
480
481
481 ### apply phase
482 ### apply phase
482 if not branchmerge: # just jump to the new rev
483 if not branchmerge: # just jump to the new rev
483 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
484 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
484 if not partial:
485 if not partial:
485 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
486 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
486
487
487 stats = applyupdates(repo, action, wc, p2)
488 stats = applyupdates(repo, action, wc, p2)
488
489
489 if not partial:
490 if not partial:
490 recordupdates(repo, action, branchmerge)
491 recordupdates(repo, action, branchmerge)
491 repo.dirstate.setparents(fp1, fp2)
492 repo.dirstate.setparents(fp1, fp2)
492 if not branchmerge and not fastforward:
493 if not branchmerge and not fastforward:
493 repo.dirstate.setbranch(p2.branch())
494 repo.dirstate.setbranch(p2.branch())
494 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
495 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
495
496
496 return stats
497 return stats
497 finally:
498 finally:
498 wlock.release()
499 wlock.release()
General Comments 0
You need to be logged in to leave comments. Login now