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