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