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