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