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