##// END OF EJS Templates
Sort removes first when applying updates (fixes issues 750 and 912)...
Paul Moore -
r6805:48258143 default
parent child Browse files
Show More
@@ -1,484 +1,495 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, 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 errno, util, os, filemerge, copies, shutil
10 import errno, util, os, filemerge, copies, shutil
11
11
12 class mergestate(object):
12 class mergestate(object):
13 '''track 3-way merge state of individual files'''
13 '''track 3-way merge state of individual files'''
14 def __init__(self, repo):
14 def __init__(self, repo):
15 self._repo = repo
15 self._repo = repo
16 self._read()
16 self._read()
17 def reset(self, node):
17 def reset(self, node):
18 self._state = {}
18 self._state = {}
19 self._local = node
19 self._local = node
20 shutil.rmtree(self._repo.join("merge"), True)
20 shutil.rmtree(self._repo.join("merge"), True)
21 def _read(self):
21 def _read(self):
22 self._state = {}
22 self._state = {}
23 try:
23 try:
24 localnode = None
24 localnode = None
25 f = self._repo.opener("merge/state")
25 f = self._repo.opener("merge/state")
26 for i, l in enumerate(f):
26 for i, l in enumerate(f):
27 if i == 0:
27 if i == 0:
28 localnode = l[:-1]
28 localnode = l[:-1]
29 else:
29 else:
30 bits = l[:-1].split("\0")
30 bits = l[:-1].split("\0")
31 self._state[bits[0]] = bits[1:]
31 self._state[bits[0]] = bits[1:]
32 self._local = bin(localnode)
32 self._local = bin(localnode)
33 except IOError, err:
33 except IOError, err:
34 if err.errno != errno.ENOENT:
34 if err.errno != errno.ENOENT:
35 raise
35 raise
36 def _write(self):
36 def _write(self):
37 f = self._repo.opener("merge/state", "w")
37 f = self._repo.opener("merge/state", "w")
38 f.write(hex(self._local) + "\n")
38 f.write(hex(self._local) + "\n")
39 for d, v in self._state.items():
39 for d, v in self._state.items():
40 f.write("\0".join([d] + v) + "\n")
40 f.write("\0".join([d] + v) + "\n")
41 def add(self, fcl, fco, fca, fd, flags):
41 def add(self, fcl, fco, fca, fd, flags):
42 hash = util.sha1(fcl.path()).hexdigest()
42 hash = util.sha1(fcl.path()).hexdigest()
43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
45 hex(fca.filenode()), fco.path(), flags]
45 hex(fca.filenode()), fco.path(), flags]
46 self._write()
46 self._write()
47 def __contains__(self, dfile):
47 def __contains__(self, dfile):
48 return dfile in self._state
48 return dfile in self._state
49 def __getitem__(self, dfile):
49 def __getitem__(self, dfile):
50 return self._state[dfile][0]
50 return self._state[dfile][0]
51 def __iter__(self):
51 def __iter__(self):
52 l = self._state.keys()
52 l = self._state.keys()
53 l.sort()
53 l.sort()
54 for f in l:
54 for f in l:
55 yield f
55 yield f
56 def mark(self, dfile, state):
56 def mark(self, dfile, state):
57 self._state[dfile][0] = state
57 self._state[dfile][0] = state
58 self._write()
58 self._write()
59 def resolve(self, dfile, wctx, octx):
59 def resolve(self, dfile, wctx, octx):
60 if self[dfile] == 'r':
60 if self[dfile] == 'r':
61 return 0
61 return 0
62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
63 f = self._repo.opener("merge/" + hash)
63 f = self._repo.opener("merge/" + hash)
64 self._repo.wwrite(dfile, f.read(), flags)
64 self._repo.wwrite(dfile, f.read(), flags)
65 fcd = wctx[dfile]
65 fcd = wctx[dfile]
66 fco = octx[ofile]
66 fco = octx[ofile]
67 fca = self._repo.filectx(afile, fileid=anode)
67 fca = self._repo.filectx(afile, fileid=anode)
68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
69 if not r:
69 if not r:
70 self.mark(dfile, 'r')
70 self.mark(dfile, 'r')
71 return r
71 return r
72
72
73 def _checkunknown(wctx, mctx):
73 def _checkunknown(wctx, mctx):
74 "check for collisions between unknown files and files in mctx"
74 "check for collisions between unknown files and files in mctx"
75 for f in wctx.unknown():
75 for f in wctx.unknown():
76 if f in mctx and mctx[f].cmp(wctx[f].data()):
76 if f in mctx and mctx[f].cmp(wctx[f].data()):
77 raise util.Abort(_("untracked file in working directory differs"
77 raise util.Abort(_("untracked file in working directory differs"
78 " from file in requested revision: '%s'") % f)
78 " from file in requested revision: '%s'") % f)
79
79
80 def _checkcollision(mctx):
80 def _checkcollision(mctx):
81 "check for case folding collisions in the destination context"
81 "check for case folding collisions in the destination context"
82 folded = {}
82 folded = {}
83 for fn in mctx:
83 for fn in mctx:
84 fold = fn.lower()
84 fold = fn.lower()
85 if fold in folded:
85 if fold in folded:
86 raise util.Abort(_("case-folding collision between %s and %s")
86 raise util.Abort(_("case-folding collision between %s and %s")
87 % (fn, folded[fold]))
87 % (fn, folded[fold]))
88 folded[fold] = fn
88 folded[fold] = fn
89
89
90 def _forgetremoved(wctx, mctx, branchmerge):
90 def _forgetremoved(wctx, mctx, branchmerge):
91 """
91 """
92 Forget removed files
92 Forget removed files
93
93
94 If we're jumping between revisions (as opposed to merging), and if
94 If we're jumping between revisions (as opposed to merging), and if
95 neither the working directory nor the target rev has the file,
95 neither the working directory nor the target rev has the file,
96 then we need to remove it from the dirstate, to prevent the
96 then we need to remove it from the dirstate, to prevent the
97 dirstate from listing the file when it is no longer in the
97 dirstate from listing the file when it is no longer in the
98 manifest.
98 manifest.
99
99
100 If we're merging, and the other revision has removed a file
100 If we're merging, and the other revision has removed a file
101 that is not present in the working directory, we need to mark it
101 that is not present in the working directory, we need to mark it
102 as removed.
102 as removed.
103 """
103 """
104
104
105 action = []
105 action = []
106 state = branchmerge and 'r' or 'f'
106 state = branchmerge and 'r' or 'f'
107 for f in wctx.deleted():
107 for f in wctx.deleted():
108 if f not in mctx:
108 if f not in mctx:
109 action.append((f, state))
109 action.append((f, state))
110
110
111 if not branchmerge:
111 if not branchmerge:
112 for f in wctx.removed():
112 for f in wctx.removed():
113 if f not in mctx:
113 if f not in mctx:
114 action.append((f, "f"))
114 action.append((f, "f"))
115
115
116 return action
116 return action
117
117
118 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
118 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
119 """
119 """
120 Merge p1 and p2 with ancestor ma and generate merge action list
120 Merge p1 and p2 with ancestor ma and generate merge action list
121
121
122 overwrite = whether we clobber working files
122 overwrite = whether we clobber working files
123 partial = function to filter file lists
123 partial = function to filter file lists
124 """
124 """
125
125
126 repo.ui.note(_("resolving manifests\n"))
126 repo.ui.note(_("resolving manifests\n"))
127 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
127 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
128 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
128 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
129
129
130 m1 = p1.manifest()
130 m1 = p1.manifest()
131 m2 = p2.manifest()
131 m2 = p2.manifest()
132 ma = pa.manifest()
132 ma = pa.manifest()
133 backwards = (pa == p2)
133 backwards = (pa == p2)
134 action = []
134 action = []
135 copy, copied, diverge = {}, {}, {}
135 copy, copied, diverge = {}, {}, {}
136
136
137 def fmerge(f, f2=None, fa=None):
137 def fmerge(f, f2=None, fa=None):
138 """merge flags"""
138 """merge flags"""
139 if not f2:
139 if not f2:
140 f2 = f
140 f2 = f
141 fa = f
141 fa = f
142 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
142 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
143 if m == n: # flags agree
143 if m == n: # flags agree
144 return m # unchanged
144 return m # unchanged
145 if m and n: # flags are set but don't agree
145 if m and n: # flags are set but don't agree
146 if not a: # both differ from parent
146 if not a: # both differ from parent
147 r = repo.ui.prompt(
147 r = repo.ui.prompt(
148 _(" conflicting flags for %s\n"
148 _(" conflicting flags for %s\n"
149 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
149 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
150 return r != "n" and r or ''
150 return r != "n" and r or ''
151 if m == a:
151 if m == a:
152 return n # changed from m to n
152 return n # changed from m to n
153 return m # changed from n to m
153 return m # changed from n to m
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 = dict.fromkeys(copy.values())
168 copied = dict.fromkeys(copy.values())
169 for of, fl in diverge.items():
169 for of, fl in diverge.items():
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 if overwrite or backwards:
178 rflags = m2.flags(f)
178 rflags = m2.flags(f)
179 else:
179 else:
180 rflags = fmerge(f)
180 rflags = fmerge(f)
181 # are files different?
181 # are files different?
182 if n != m2[f]:
182 if n != m2[f]:
183 a = ma.get(f, nullid)
183 a = ma.get(f, nullid)
184 # are we clobbering?
184 # are we clobbering?
185 if overwrite:
185 if overwrite:
186 act("clobbering", "g", f, rflags)
186 act("clobbering", "g", f, rflags)
187 # or are we going back in time and clean?
187 # or are we going back in time and clean?
188 elif backwards and not n[20:]:
188 elif backwards and not n[20:]:
189 act("reverting", "g", f, rflags)
189 act("reverting", "g", f, rflags)
190 # are both different from the ancestor?
190 # are both different from the ancestor?
191 elif n != a and m2[f] != a:
191 elif n != a and m2[f] != a:
192 act("versions differ", "m", f, f, f, rflags, False)
192 act("versions differ", "m", f, f, f, rflags, False)
193 # is remote's version newer?
193 # is remote's version newer?
194 elif m2[f] != a:
194 elif m2[f] != a:
195 act("remote is newer", "g", f, rflags)
195 act("remote is newer", "g", f, rflags)
196 # local is newer, not overwrite, check mode bits
196 # local is newer, not overwrite, check mode bits
197 elif m1.flags(f) != rflags:
197 elif m1.flags(f) != rflags:
198 act("update permissions", "e", f, rflags)
198 act("update permissions", "e", f, rflags)
199 # contents same, check mode bits
199 # contents same, check mode bits
200 elif m1.flags(f) != rflags:
200 elif m1.flags(f) != rflags:
201 act("update permissions", "e", f, rflags)
201 act("update permissions", "e", f, rflags)
202 elif f in copied:
202 elif f in copied:
203 continue
203 continue
204 elif f in copy:
204 elif f in copy:
205 f2 = copy[f]
205 f2 = copy[f]
206 if f2 not in m2: # directory rename
206 if f2 not in m2: # directory rename
207 act("remote renamed directory to " + f2, "d",
207 act("remote renamed directory to " + f2, "d",
208 f, None, f2, m1.flags(f))
208 f, None, f2, m1.flags(f))
209 elif f2 in m1: # case 2 A,B/B/B
209 elif f2 in m1: # case 2 A,B/B/B
210 act("local copied to " + f2, "m",
210 act("local copied to " + f2, "m",
211 f, f2, f, fmerge(f, f2, f2), False)
211 f, f2, f, fmerge(f, f2, f2), False)
212 else: # case 4,21 A/B/B
212 else: # case 4,21 A/B/B
213 act("local moved to " + f2, "m",
213 act("local moved to " + f2, "m",
214 f, f2, f, fmerge(f, f2, f2), False)
214 f, f2, f, fmerge(f, f2, f2), False)
215 elif f in ma:
215 elif f in ma:
216 if n != ma[f] and not overwrite:
216 if n != ma[f] and not overwrite:
217 if repo.ui.prompt(
217 if repo.ui.prompt(
218 _(" local changed %s which remote deleted\n"
218 _(" local changed %s which remote deleted\n"
219 "use (c)hanged version or (d)elete?") % f,
219 "use (c)hanged version or (d)elete?") % f,
220 _("[cd]"), _("c")) == _("d"):
220 _("[cd]"), _("c")) == _("d"):
221 act("prompt delete", "r", f)
221 act("prompt delete", "r", f)
222 else:
222 else:
223 act("other deleted", "r", f)
223 act("other deleted", "r", f)
224 else:
224 else:
225 # file is created on branch or in working directory
225 # file is created on branch or in working directory
226 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:]):
227 act("remote deleted", "r", f)
227 act("remote deleted", "r", f)
228
228
229 for f, n in m2.iteritems():
229 for f, n in m2.iteritems():
230 if partial and not partial(f):
230 if partial and not partial(f):
231 continue
231 continue
232 if f in m1:
232 if f in m1:
233 continue
233 continue
234 if f in copied:
234 if f in copied:
235 continue
235 continue
236 if f in copy:
236 if f in copy:
237 f2 = copy[f]
237 f2 = copy[f]
238 if f2 not in m1: # directory rename
238 if f2 not in m1: # directory rename
239 act("local renamed directory to " + f2, "d",
239 act("local renamed directory to " + f2, "d",
240 None, f, f2, m2.flags(f))
240 None, f, f2, m2.flags(f))
241 elif f2 in m2: # rename case 1, A/A,B/A
241 elif f2 in m2: # rename case 1, A/A,B/A
242 act("remote copied to " + f, "m",
242 act("remote copied to " + f, "m",
243 f2, f, f, fmerge(f2, f, f2), False)
243 f2, f, f, fmerge(f2, f, f2), False)
244 else: # case 3,20 A/B/A
244 else: # case 3,20 A/B/A
245 act("remote moved to " + f, "m",
245 act("remote moved to " + f, "m",
246 f2, f, f, fmerge(f2, f, f2), True)
246 f2, f, f, fmerge(f2, f, f2), True)
247 elif f in ma:
247 elif f in ma:
248 if overwrite or backwards:
248 if overwrite or backwards:
249 act("recreating", "g", f, m2.flags(f))
249 act("recreating", "g", f, m2.flags(f))
250 elif n != ma[f]:
250 elif n != ma[f]:
251 if repo.ui.prompt(
251 if repo.ui.prompt(
252 _("remote changed %s which local deleted\n"
252 _("remote changed %s which local deleted\n"
253 "use (c)hanged version or leave (d)eleted?") % f,
253 "use (c)hanged version or leave (d)eleted?") % f,
254 _("[cd]"), _("c")) == _("c"):
254 _("[cd]"), _("c")) == _("c"):
255 act("prompt recreating", "g", f, m2.flags(f))
255 act("prompt recreating", "g", f, m2.flags(f))
256 else:
256 else:
257 act("remote created", "g", f, m2.flags(f))
257 act("remote created", "g", f, m2.flags(f))
258
258
259 return action
259 return action
260
260
261 def actioncmp(a1, a2):
262 m1 = a1[1]
263 m2 = a2[1]
264 if m1 == m2:
265 return cmp(a1, a2)
266 if m1 == 'r':
267 return -1
268 if m2 == 'r':
269 return 1
270 return cmp(a1, a2)
271
261 def applyupdates(repo, action, wctx, mctx):
272 def applyupdates(repo, action, wctx, mctx):
262 "apply the merge action list to the working directory"
273 "apply the merge action list to the working directory"
263
274
264 updated, merged, removed, unresolved = 0, 0, 0, 0
275 updated, merged, removed, unresolved = 0, 0, 0, 0
265 ms = mergestate(repo)
276 ms = mergestate(repo)
266 ms.reset(wctx.parents()[0].node())
277 ms.reset(wctx.parents()[0].node())
267 moves = []
278 moves = []
268 action.sort()
279 action.sort(actioncmp)
269
280
270 # prescan for merges
281 # prescan for merges
271 for a in action:
282 for a in action:
272 f, m = a[:2]
283 f, m = a[:2]
273 if m == 'm': # merge
284 if m == 'm': # merge
274 f2, fd, flags, move = a[2:]
285 f2, fd, flags, move = a[2:]
275 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
286 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
276 fcl = wctx[f]
287 fcl = wctx[f]
277 fco = mctx[f2]
288 fco = mctx[f2]
278 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
289 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
279 ms.add(fcl, fco, fca, fd, flags)
290 ms.add(fcl, fco, fca, fd, flags)
280 if f != fd and move:
291 if f != fd and move:
281 moves.append(f)
292 moves.append(f)
282
293
283 # remove renamed files after safely stored
294 # remove renamed files after safely stored
284 for f in moves:
295 for f in moves:
285 if util.lexists(repo.wjoin(f)):
296 if util.lexists(repo.wjoin(f)):
286 repo.ui.debug(_("removing %s\n") % f)
297 repo.ui.debug(_("removing %s\n") % f)
287 os.unlink(repo.wjoin(f))
298 os.unlink(repo.wjoin(f))
288
299
289 audit_path = util.path_auditor(repo.root)
300 audit_path = util.path_auditor(repo.root)
290
301
291 for a in action:
302 for a in action:
292 f, m = a[:2]
303 f, m = a[:2]
293 if f and f[0] == "/":
304 if f and f[0] == "/":
294 continue
305 continue
295 if m == "r": # remove
306 if m == "r": # remove
296 repo.ui.note(_("removing %s\n") % f)
307 repo.ui.note(_("removing %s\n") % f)
297 audit_path(f)
308 audit_path(f)
298 try:
309 try:
299 util.unlink(repo.wjoin(f))
310 util.unlink(repo.wjoin(f))
300 except OSError, inst:
311 except OSError, inst:
301 if inst.errno != errno.ENOENT:
312 if inst.errno != errno.ENOENT:
302 repo.ui.warn(_("update failed to remove %s: %s!\n") %
313 repo.ui.warn(_("update failed to remove %s: %s!\n") %
303 (f, inst.strerror))
314 (f, inst.strerror))
304 removed += 1
315 removed += 1
305 elif m == "m": # merge
316 elif m == "m": # merge
306 f2, fd, flags, move = a[2:]
317 f2, fd, flags, move = a[2:]
307 r = ms.resolve(fd, wctx, mctx)
318 r = ms.resolve(fd, wctx, mctx)
308 if r > 0:
319 if r > 0:
309 unresolved += 1
320 unresolved += 1
310 else:
321 else:
311 if r is None:
322 if r is None:
312 updated += 1
323 updated += 1
313 else:
324 else:
314 merged += 1
325 merged += 1
315 elif m == "g": # get
326 elif m == "g": # get
316 flags = a[2]
327 flags = a[2]
317 repo.ui.note(_("getting %s\n") % f)
328 repo.ui.note(_("getting %s\n") % f)
318 t = mctx.filectx(f).data()
329 t = mctx.filectx(f).data()
319 repo.wwrite(f, t, flags)
330 repo.wwrite(f, t, flags)
320 updated += 1
331 updated += 1
321 elif m == "d": # directory rename
332 elif m == "d": # directory rename
322 f2, fd, flags = a[2:]
333 f2, fd, flags = a[2:]
323 if f:
334 if f:
324 repo.ui.note(_("moving %s to %s\n") % (f, fd))
335 repo.ui.note(_("moving %s to %s\n") % (f, fd))
325 t = wctx.filectx(f).data()
336 t = wctx.filectx(f).data()
326 repo.wwrite(fd, t, flags)
337 repo.wwrite(fd, t, flags)
327 util.unlink(repo.wjoin(f))
338 util.unlink(repo.wjoin(f))
328 if f2:
339 if f2:
329 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
340 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
330 t = mctx.filectx(f2).data()
341 t = mctx.filectx(f2).data()
331 repo.wwrite(fd, t, flags)
342 repo.wwrite(fd, t, flags)
332 updated += 1
343 updated += 1
333 elif m == "dr": # divergent renames
344 elif m == "dr": # divergent renames
334 fl = a[2]
345 fl = a[2]
335 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
346 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
336 for nf in fl:
347 for nf in fl:
337 repo.ui.warn(" %s\n" % nf)
348 repo.ui.warn(" %s\n" % nf)
338 elif m == "e": # exec
349 elif m == "e": # exec
339 flags = a[2]
350 flags = a[2]
340 util.set_flags(repo.wjoin(f), flags)
351 util.set_flags(repo.wjoin(f), flags)
341
352
342 return updated, merged, removed, unresolved
353 return updated, merged, removed, unresolved
343
354
344 def recordupdates(repo, action, branchmerge):
355 def recordupdates(repo, action, branchmerge):
345 "record merge actions to the dirstate"
356 "record merge actions to the dirstate"
346
357
347 for a in action:
358 for a in action:
348 f, m = a[:2]
359 f, m = a[:2]
349 if m == "r": # remove
360 if m == "r": # remove
350 if branchmerge:
361 if branchmerge:
351 repo.dirstate.remove(f)
362 repo.dirstate.remove(f)
352 else:
363 else:
353 repo.dirstate.forget(f)
364 repo.dirstate.forget(f)
354 elif m == "f": # forget
365 elif m == "f": # forget
355 repo.dirstate.forget(f)
366 repo.dirstate.forget(f)
356 elif m in "ge": # get or exec change
367 elif m in "ge": # get or exec change
357 if branchmerge:
368 if branchmerge:
358 repo.dirstate.normaldirty(f)
369 repo.dirstate.normaldirty(f)
359 else:
370 else:
360 repo.dirstate.normal(f)
371 repo.dirstate.normal(f)
361 elif m == "m": # merge
372 elif m == "m": # merge
362 f2, fd, flag, move = a[2:]
373 f2, fd, flag, move = a[2:]
363 if branchmerge:
374 if branchmerge:
364 # We've done a branch merge, mark this file as merged
375 # We've done a branch merge, mark this file as merged
365 # so that we properly record the merger later
376 # so that we properly record the merger later
366 repo.dirstate.merge(fd)
377 repo.dirstate.merge(fd)
367 if f != f2: # copy/rename
378 if f != f2: # copy/rename
368 if move:
379 if move:
369 repo.dirstate.remove(f)
380 repo.dirstate.remove(f)
370 if f != fd:
381 if f != fd:
371 repo.dirstate.copy(f, fd)
382 repo.dirstate.copy(f, fd)
372 else:
383 else:
373 repo.dirstate.copy(f2, fd)
384 repo.dirstate.copy(f2, fd)
374 else:
385 else:
375 # We've update-merged a locally modified file, so
386 # We've update-merged a locally modified file, so
376 # we set the dirstate to emulate a normal checkout
387 # we set the dirstate to emulate a normal checkout
377 # of that file some time in the past. Thus our
388 # of that file some time in the past. Thus our
378 # merge will appear as a normal local file
389 # merge will appear as a normal local file
379 # modification.
390 # modification.
380 repo.dirstate.normallookup(fd)
391 repo.dirstate.normallookup(fd)
381 if move:
392 if move:
382 repo.dirstate.forget(f)
393 repo.dirstate.forget(f)
383 elif m == "d": # directory rename
394 elif m == "d": # directory rename
384 f2, fd, flag = a[2:]
395 f2, fd, flag = a[2:]
385 if not f2 and f not in repo.dirstate:
396 if not f2 and f not in repo.dirstate:
386 # untracked file moved
397 # untracked file moved
387 continue
398 continue
388 if branchmerge:
399 if branchmerge:
389 repo.dirstate.add(fd)
400 repo.dirstate.add(fd)
390 if f:
401 if f:
391 repo.dirstate.remove(f)
402 repo.dirstate.remove(f)
392 repo.dirstate.copy(f, fd)
403 repo.dirstate.copy(f, fd)
393 if f2:
404 if f2:
394 repo.dirstate.copy(f2, fd)
405 repo.dirstate.copy(f2, fd)
395 else:
406 else:
396 repo.dirstate.normal(fd)
407 repo.dirstate.normal(fd)
397 if f:
408 if f:
398 repo.dirstate.forget(f)
409 repo.dirstate.forget(f)
399
410
400 def update(repo, node, branchmerge, force, partial):
411 def update(repo, node, branchmerge, force, partial):
401 """
412 """
402 Perform a merge between the working directory and the given node
413 Perform a merge between the working directory and the given node
403
414
404 branchmerge = whether to merge between branches
415 branchmerge = whether to merge between branches
405 force = whether to force branch merging or file overwriting
416 force = whether to force branch merging or file overwriting
406 partial = a function to filter file lists (dirstate not updated)
417 partial = a function to filter file lists (dirstate not updated)
407 """
418 """
408
419
409 wlock = repo.wlock()
420 wlock = repo.wlock()
410 try:
421 try:
411 wc = repo[None]
422 wc = repo[None]
412 if node is None:
423 if node is None:
413 # tip of current branch
424 # tip of current branch
414 try:
425 try:
415 node = repo.branchtags()[wc.branch()]
426 node = repo.branchtags()[wc.branch()]
416 except KeyError:
427 except KeyError:
417 if wc.branch() == "default": # no default branch!
428 if wc.branch() == "default": # no default branch!
418 node = repo.lookup("tip") # update to tip
429 node = repo.lookup("tip") # update to tip
419 else:
430 else:
420 raise util.Abort(_("branch %s not found") % wc.branch())
431 raise util.Abort(_("branch %s not found") % wc.branch())
421 overwrite = force and not branchmerge
432 overwrite = force and not branchmerge
422 pl = wc.parents()
433 pl = wc.parents()
423 p1, p2 = pl[0], repo[node]
434 p1, p2 = pl[0], repo[node]
424 pa = p1.ancestor(p2)
435 pa = p1.ancestor(p2)
425 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
436 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
426 fastforward = False
437 fastforward = False
427
438
428 ### check phase
439 ### check phase
429 if not overwrite and len(pl) > 1:
440 if not overwrite and len(pl) > 1:
430 raise util.Abort(_("outstanding uncommitted merges"))
441 raise util.Abort(_("outstanding uncommitted merges"))
431 if branchmerge:
442 if branchmerge:
432 if pa == p2:
443 if pa == p2:
433 raise util.Abort(_("can't merge with ancestor"))
444 raise util.Abort(_("can't merge with ancestor"))
434 elif pa == p1:
445 elif pa == p1:
435 if p1.branch() != p2.branch():
446 if p1.branch() != p2.branch():
436 fastforward = True
447 fastforward = True
437 else:
448 else:
438 raise util.Abort(_("nothing to merge (use 'hg update'"
449 raise util.Abort(_("nothing to merge (use 'hg update'"
439 " or check 'hg heads')"))
450 " or check 'hg heads')"))
440 if not force and (wc.files() or wc.deleted()):
451 if not force and (wc.files() or wc.deleted()):
441 raise util.Abort(_("outstanding uncommitted changes"))
452 raise util.Abort(_("outstanding uncommitted changes"))
442 elif not overwrite:
453 elif not overwrite:
443 if pa == p1 or pa == p2: # linear
454 if pa == p1 or pa == p2: # linear
444 pass # all good
455 pass # all good
445 elif p1.branch() == p2.branch():
456 elif p1.branch() == p2.branch():
446 if wc.files() or wc.deleted():
457 if wc.files() or wc.deleted():
447 raise util.Abort(_("crosses branches (use 'hg merge' or "
458 raise util.Abort(_("crosses branches (use 'hg merge' or "
448 "'hg update -C' to discard changes)"))
459 "'hg update -C' to discard changes)"))
449 raise util.Abort(_("crosses branches (use 'hg merge' "
460 raise util.Abort(_("crosses branches (use 'hg merge' "
450 "or 'hg update -C')"))
461 "or 'hg update -C')"))
451 elif wc.files() or wc.deleted():
462 elif wc.files() or wc.deleted():
452 raise util.Abort(_("crosses named branches (use "
463 raise util.Abort(_("crosses named branches (use "
453 "'hg update -C' to discard changes)"))
464 "'hg update -C' to discard changes)"))
454 else:
465 else:
455 # Allow jumping branches if there are no changes
466 # Allow jumping branches if there are no changes
456 overwrite = True
467 overwrite = True
457
468
458 ### calculate phase
469 ### calculate phase
459 action = []
470 action = []
460 if not force:
471 if not force:
461 _checkunknown(wc, p2)
472 _checkunknown(wc, p2)
462 if not util.checkcase(repo.path):
473 if not util.checkcase(repo.path):
463 _checkcollision(p2)
474 _checkcollision(p2)
464 action += _forgetremoved(wc, p2, branchmerge)
475 action += _forgetremoved(wc, p2, branchmerge)
465 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
476 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
466
477
467 ### apply phase
478 ### apply phase
468 if not branchmerge: # just jump to the new rev
479 if not branchmerge: # just jump to the new rev
469 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
480 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
470 if not partial:
481 if not partial:
471 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
482 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
472
483
473 stats = applyupdates(repo, action, wc, p2)
484 stats = applyupdates(repo, action, wc, p2)
474
485
475 if not partial:
486 if not partial:
476 recordupdates(repo, action, branchmerge)
487 recordupdates(repo, action, branchmerge)
477 repo.dirstate.setparents(fp1, fp2)
488 repo.dirstate.setparents(fp1, fp2)
478 if not branchmerge and not fastforward:
489 if not branchmerge and not fastforward:
479 repo.dirstate.setbranch(p2.branch())
490 repo.dirstate.setbranch(p2.branch())
480 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
491 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
481
492
482 return stats
493 return stats
483 finally:
494 finally:
484 del wlock
495 del wlock
@@ -1,55 +1,55 b''
1 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2 Main should be gone
2 Main should be gone
3 a
3 a
4 created new head
4 created new head
5 changeset: 3:ded32b0db104
5 changeset: 3:ded32b0db104
6 tag: tip
6 tag: tip
7 user: test
7 user: test
8 date: Mon Jan 12 13:46:40 1970 +0000
8 date: Mon Jan 12 13:46:40 1970 +0000
9 summary: Added side2
9 summary: Added side2
10
10
11 changeset: 2:92a816cea698
11 changeset: 2:92a816cea698
12 parent: 0:537353581d3d
12 parent: 0:537353581d3d
13 user: test
13 user: test
14 date: Mon Jan 12 13:46:40 1970 +0000
14 date: Mon Jan 12 13:46:40 1970 +0000
15 summary: Added side1
15 summary: Added side1
16
16
17 changeset: 1:221226fb2bd8
17 changeset: 1:221226fb2bd8
18 user: test
18 user: test
19 date: Mon Jan 12 13:46:40 1970 +0000
19 date: Mon Jan 12 13:46:40 1970 +0000
20 summary: Added main
20 summary: Added main
21
21
22 changeset: 0:537353581d3d
22 changeset: 0:537353581d3d
23 user: test
23 user: test
24 date: Mon Jan 12 13:46:40 1970 +0000
24 date: Mon Jan 12 13:46:40 1970 +0000
25 summary: Added a
25 summary: Added a
26
26
27 Should have two heads, side2 and main
27 Should have two heads, side2 and main
28 changeset: 3:ded32b0db104
28 changeset: 3:ded32b0db104
29 tag: tip
29 tag: tip
30 user: test
30 user: test
31 date: Mon Jan 12 13:46:40 1970 +0000
31 date: Mon Jan 12 13:46:40 1970 +0000
32 summary: Added side2
32 summary: Added side2
33
33
34 changeset: 1:221226fb2bd8
34 changeset: 1:221226fb2bd8
35 user: test
35 user: test
36 date: Mon Jan 12 13:46:40 1970 +0000
36 date: Mon Jan 12 13:46:40 1970 +0000
37 summary: Added main
37 summary: Added main
38
38
39 Should show a side1 side2
39 Should show a side1 side2
40 a
40 a
41 side1
41 side1
42 side2
42 side2
43 resolving manifests
43 resolving manifests
44 overwrite True partial False
44 overwrite True partial False
45 ancestor 537353581d3d local ded32b0db104+ remote 221226fb2bd8
45 ancestor 537353581d3d local ded32b0db104+ remote 221226fb2bd8
46 side2: remote deleted -> r
46 side2: remote deleted -> r
47 side1: remote deleted -> r
47 side1: remote deleted -> r
48 main: remote created -> g
48 main: remote created -> g
49 getting main
50 removing side1
49 removing side1
51 removing side2
50 removing side2
51 getting main
52 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
52 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
53 Should only show a main
53 Should only show a main
54 a
54 a
55 main
55 main
General Comments 0
You need to be logged in to leave comments. Login now