##// END OF EJS Templates
merge: consider successor changesets for a bare update...
Sean Farley -
r20280:95b9c614 default
parent child Browse files
Show More
@@ -1,787 +1,815
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 or any later version.
6 # GNU General Public License version 2 or any later version.
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 from mercurial import obsolete
10 from mercurial import obsolete
11 import error, util, filemerge, copies, subrepo, worker, dicthelpers
11 import error, util, filemerge, copies, subrepo, worker, dicthelpers
12 import errno, os, shutil
12 import errno, os, shutil
13
13
14 class mergestate(object):
14 class mergestate(object):
15 '''track 3-way merge state of individual files'''
15 '''track 3-way merge state of individual files'''
16 def __init__(self, repo):
16 def __init__(self, repo):
17 self._repo = repo
17 self._repo = repo
18 self._dirty = False
18 self._dirty = False
19 self._read()
19 self._read()
20 def reset(self, node=None):
20 def reset(self, node=None):
21 self._state = {}
21 self._state = {}
22 if node:
22 if node:
23 self._local = node
23 self._local = node
24 shutil.rmtree(self._repo.join("merge"), True)
24 shutil.rmtree(self._repo.join("merge"), True)
25 self._dirty = False
25 self._dirty = False
26 def _read(self):
26 def _read(self):
27 self._state = {}
27 self._state = {}
28 try:
28 try:
29 f = self._repo.opener("merge/state")
29 f = self._repo.opener("merge/state")
30 for i, l in enumerate(f):
30 for i, l in enumerate(f):
31 if i == 0:
31 if i == 0:
32 self._local = bin(l[:-1])
32 self._local = bin(l[:-1])
33 else:
33 else:
34 bits = l[:-1].split("\0")
34 bits = l[:-1].split("\0")
35 self._state[bits[0]] = bits[1:]
35 self._state[bits[0]] = bits[1:]
36 f.close()
36 f.close()
37 except IOError, err:
37 except IOError, err:
38 if err.errno != errno.ENOENT:
38 if err.errno != errno.ENOENT:
39 raise
39 raise
40 self._dirty = False
40 self._dirty = False
41 def commit(self):
41 def commit(self):
42 if self._dirty:
42 if self._dirty:
43 f = self._repo.opener("merge/state", "w")
43 f = self._repo.opener("merge/state", "w")
44 f.write(hex(self._local) + "\n")
44 f.write(hex(self._local) + "\n")
45 for d, v in self._state.iteritems():
45 for d, v in self._state.iteritems():
46 f.write("\0".join([d] + v) + "\n")
46 f.write("\0".join([d] + v) + "\n")
47 f.close()
47 f.close()
48 self._dirty = False
48 self._dirty = False
49 def add(self, fcl, fco, fca, fd):
49 def add(self, fcl, fco, fca, fd):
50 hash = util.sha1(fcl.path()).hexdigest()
50 hash = util.sha1(fcl.path()).hexdigest()
51 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._repo.opener.write("merge/" + hash, fcl.data())
52 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
53 hex(fca.filenode()), fco.path(), fcl.flags()]
53 hex(fca.filenode()), fco.path(), fcl.flags()]
54 self._dirty = True
54 self._dirty = True
55 def __contains__(self, dfile):
55 def __contains__(self, dfile):
56 return dfile in self._state
56 return dfile in self._state
57 def __getitem__(self, dfile):
57 def __getitem__(self, dfile):
58 return self._state[dfile][0]
58 return self._state[dfile][0]
59 def __iter__(self):
59 def __iter__(self):
60 l = self._state.keys()
60 l = self._state.keys()
61 l.sort()
61 l.sort()
62 for f in l:
62 for f in l:
63 yield f
63 yield f
64 def files(self):
64 def files(self):
65 return self._state.keys()
65 return self._state.keys()
66 def mark(self, dfile, state):
66 def mark(self, dfile, state):
67 self._state[dfile][0] = state
67 self._state[dfile][0] = state
68 self._dirty = True
68 self._dirty = True
69 def resolve(self, dfile, wctx, octx):
69 def resolve(self, dfile, wctx, octx):
70 if self[dfile] == 'r':
70 if self[dfile] == 'r':
71 return 0
71 return 0
72 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
72 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
73 fcd = wctx[dfile]
73 fcd = wctx[dfile]
74 fco = octx[ofile]
74 fco = octx[ofile]
75 fca = self._repo.filectx(afile, fileid=anode)
75 fca = self._repo.filectx(afile, fileid=anode)
76 # "premerge" x flags
76 # "premerge" x flags
77 flo = fco.flags()
77 flo = fco.flags()
78 fla = fca.flags()
78 fla = fca.flags()
79 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
79 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
80 if fca.node() == nullid:
80 if fca.node() == nullid:
81 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
81 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
82 afile)
82 afile)
83 elif flags == fla:
83 elif flags == fla:
84 flags = flo
84 flags = flo
85 # restore local
85 # restore local
86 f = self._repo.opener("merge/" + hash)
86 f = self._repo.opener("merge/" + hash)
87 self._repo.wwrite(dfile, f.read(), flags)
87 self._repo.wwrite(dfile, f.read(), flags)
88 f.close()
88 f.close()
89 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
89 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
90 if r is None:
90 if r is None:
91 # no real conflict
91 # no real conflict
92 del self._state[dfile]
92 del self._state[dfile]
93 elif not r:
93 elif not r:
94 self.mark(dfile, 'r')
94 self.mark(dfile, 'r')
95 return r
95 return r
96
96
97 def _checkunknownfile(repo, wctx, mctx, f):
97 def _checkunknownfile(repo, wctx, mctx, f):
98 return (not repo.dirstate._ignore(f)
98 return (not repo.dirstate._ignore(f)
99 and os.path.isfile(repo.wjoin(f))
99 and os.path.isfile(repo.wjoin(f))
100 and repo.wopener.audit.check(f)
100 and repo.wopener.audit.check(f)
101 and repo.dirstate.normalize(f) not in repo.dirstate
101 and repo.dirstate.normalize(f) not in repo.dirstate
102 and mctx[f].cmp(wctx[f]))
102 and mctx[f].cmp(wctx[f]))
103
103
104 def _checkunknown(repo, wctx, mctx):
104 def _checkunknown(repo, wctx, mctx):
105 "check for collisions between unknown files and files in mctx"
105 "check for collisions between unknown files and files in mctx"
106
106
107 error = False
107 error = False
108 for f in mctx:
108 for f in mctx:
109 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
109 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
110 error = True
110 error = True
111 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
111 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
112 if error:
112 if error:
113 raise util.Abort(_("untracked files in working directory differ "
113 raise util.Abort(_("untracked files in working directory differ "
114 "from files in requested revision"))
114 "from files in requested revision"))
115
115
116 def _forgetremoved(wctx, mctx, branchmerge):
116 def _forgetremoved(wctx, mctx, branchmerge):
117 """
117 """
118 Forget removed files
118 Forget removed files
119
119
120 If we're jumping between revisions (as opposed to merging), and if
120 If we're jumping between revisions (as opposed to merging), and if
121 neither the working directory nor the target rev has the file,
121 neither the working directory nor the target rev has the file,
122 then we need to remove it from the dirstate, to prevent the
122 then we need to remove it from the dirstate, to prevent the
123 dirstate from listing the file when it is no longer in the
123 dirstate from listing the file when it is no longer in the
124 manifest.
124 manifest.
125
125
126 If we're merging, and the other revision has removed a file
126 If we're merging, and the other revision has removed a file
127 that is not present in the working directory, we need to mark it
127 that is not present in the working directory, we need to mark it
128 as removed.
128 as removed.
129 """
129 """
130
130
131 actions = []
131 actions = []
132 state = branchmerge and 'r' or 'f'
132 state = branchmerge and 'r' or 'f'
133 for f in wctx.deleted():
133 for f in wctx.deleted():
134 if f not in mctx:
134 if f not in mctx:
135 actions.append((f, state, None, "forget deleted"))
135 actions.append((f, state, None, "forget deleted"))
136
136
137 if not branchmerge:
137 if not branchmerge:
138 for f in wctx.removed():
138 for f in wctx.removed():
139 if f not in mctx:
139 if f not in mctx:
140 actions.append((f, "f", None, "forget removed"))
140 actions.append((f, "f", None, "forget removed"))
141
141
142 return actions
142 return actions
143
143
144 def _checkcollision(repo, wmf, actions, prompts):
144 def _checkcollision(repo, wmf, actions, prompts):
145 # build provisional merged manifest up
145 # build provisional merged manifest up
146 pmmf = set(wmf)
146 pmmf = set(wmf)
147
147
148 def addop(f, args):
148 def addop(f, args):
149 pmmf.add(f)
149 pmmf.add(f)
150 def removeop(f, args):
150 def removeop(f, args):
151 pmmf.discard(f)
151 pmmf.discard(f)
152 def nop(f, args):
152 def nop(f, args):
153 pass
153 pass
154
154
155 def renameop(f, args):
155 def renameop(f, args):
156 f2, fd, flags = args
156 f2, fd, flags = args
157 if f:
157 if f:
158 pmmf.discard(f)
158 pmmf.discard(f)
159 pmmf.add(fd)
159 pmmf.add(fd)
160 def mergeop(f, args):
160 def mergeop(f, args):
161 f2, fd, move = args
161 f2, fd, move = args
162 if move:
162 if move:
163 pmmf.discard(f)
163 pmmf.discard(f)
164 pmmf.add(fd)
164 pmmf.add(fd)
165
165
166 opmap = {
166 opmap = {
167 "a": addop,
167 "a": addop,
168 "d": renameop,
168 "d": renameop,
169 "dr": nop,
169 "dr": nop,
170 "e": nop,
170 "e": nop,
171 "f": addop, # untracked file should be kept in working directory
171 "f": addop, # untracked file should be kept in working directory
172 "g": addop,
172 "g": addop,
173 "m": mergeop,
173 "m": mergeop,
174 "r": removeop,
174 "r": removeop,
175 "rd": nop,
175 "rd": nop,
176 }
176 }
177 for f, m, args, msg in actions:
177 for f, m, args, msg in actions:
178 op = opmap.get(m)
178 op = opmap.get(m)
179 assert op, m
179 assert op, m
180 op(f, args)
180 op(f, args)
181
181
182 opmap = {
182 opmap = {
183 "cd": addop,
183 "cd": addop,
184 "dc": addop,
184 "dc": addop,
185 }
185 }
186 for f, m in prompts:
186 for f, m in prompts:
187 op = opmap.get(m)
187 op = opmap.get(m)
188 assert op, m
188 assert op, m
189 op(f, None)
189 op(f, None)
190
190
191 # check case-folding collision in provisional merged manifest
191 # check case-folding collision in provisional merged manifest
192 foldmap = {}
192 foldmap = {}
193 for f in sorted(pmmf):
193 for f in sorted(pmmf):
194 fold = util.normcase(f)
194 fold = util.normcase(f)
195 if fold in foldmap:
195 if fold in foldmap:
196 raise util.Abort(_("case-folding collision between %s and %s")
196 raise util.Abort(_("case-folding collision between %s and %s")
197 % (f, foldmap[fold]))
197 % (f, foldmap[fold]))
198 foldmap[fold] = f
198 foldmap[fold] = f
199
199
200 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
200 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
201 acceptremote=False):
201 acceptremote=False):
202 """
202 """
203 Merge p1 and p2 with ancestor pa and generate merge action list
203 Merge p1 and p2 with ancestor pa and generate merge action list
204
204
205 branchmerge and force are as passed in to update
205 branchmerge and force are as passed in to update
206 partial = function to filter file lists
206 partial = function to filter file lists
207 acceptremote = accept the incoming changes without prompting
207 acceptremote = accept the incoming changes without prompting
208 """
208 """
209
209
210 overwrite = force and not branchmerge
210 overwrite = force and not branchmerge
211 actions, copy, movewithdir = [], {}, {}
211 actions, copy, movewithdir = [], {}, {}
212
212
213 followcopies = False
213 followcopies = False
214 if overwrite:
214 if overwrite:
215 pa = wctx
215 pa = wctx
216 elif pa == p2: # backwards
216 elif pa == p2: # backwards
217 pa = wctx.p1()
217 pa = wctx.p1()
218 elif not branchmerge and not wctx.dirty(missing=True):
218 elif not branchmerge and not wctx.dirty(missing=True):
219 pass
219 pass
220 elif pa and repo.ui.configbool("merge", "followcopies", True):
220 elif pa and repo.ui.configbool("merge", "followcopies", True):
221 followcopies = True
221 followcopies = True
222
222
223 # manifests fetched in order are going to be faster, so prime the caches
223 # manifests fetched in order are going to be faster, so prime the caches
224 [x.manifest() for x in
224 [x.manifest() for x in
225 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
225 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
226
226
227 if followcopies:
227 if followcopies:
228 ret = copies.mergecopies(repo, wctx, p2, pa)
228 ret = copies.mergecopies(repo, wctx, p2, pa)
229 copy, movewithdir, diverge, renamedelete = ret
229 copy, movewithdir, diverge, renamedelete = ret
230 for of, fl in diverge.iteritems():
230 for of, fl in diverge.iteritems():
231 actions.append((of, "dr", (fl,), "divergent renames"))
231 actions.append((of, "dr", (fl,), "divergent renames"))
232 for of, fl in renamedelete.iteritems():
232 for of, fl in renamedelete.iteritems():
233 actions.append((of, "rd", (fl,), "rename and delete"))
233 actions.append((of, "rd", (fl,), "rename and delete"))
234
234
235 repo.ui.note(_("resolving manifests\n"))
235 repo.ui.note(_("resolving manifests\n"))
236 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
236 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
237 % (bool(branchmerge), bool(force), bool(partial)))
237 % (bool(branchmerge), bool(force), bool(partial)))
238 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
238 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
239
239
240 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
240 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
241 copied = set(copy.values())
241 copied = set(copy.values())
242 copied.update(movewithdir.values())
242 copied.update(movewithdir.values())
243
243
244 if '.hgsubstate' in m1:
244 if '.hgsubstate' in m1:
245 # check whether sub state is modified
245 # check whether sub state is modified
246 for s in sorted(wctx.substate):
246 for s in sorted(wctx.substate):
247 if wctx.sub(s).dirty():
247 if wctx.sub(s).dirty():
248 m1['.hgsubstate'] += "+"
248 m1['.hgsubstate'] += "+"
249 break
249 break
250
250
251 aborts, prompts = [], []
251 aborts, prompts = [], []
252 # Compare manifests
252 # Compare manifests
253 fdiff = dicthelpers.diff(m1, m2)
253 fdiff = dicthelpers.diff(m1, m2)
254 flagsdiff = m1.flagsdiff(m2)
254 flagsdiff = m1.flagsdiff(m2)
255 diff12 = dicthelpers.join(fdiff, flagsdiff)
255 diff12 = dicthelpers.join(fdiff, flagsdiff)
256
256
257 for f, (n12, fl12) in diff12.iteritems():
257 for f, (n12, fl12) in diff12.iteritems():
258 if n12:
258 if n12:
259 n1, n2 = n12
259 n1, n2 = n12
260 else: # file contents didn't change, but flags did
260 else: # file contents didn't change, but flags did
261 n1 = n2 = m1.get(f, None)
261 n1 = n2 = m1.get(f, None)
262 if n1 is None:
262 if n1 is None:
263 # Since n1 == n2, the file isn't present in m2 either. This
263 # Since n1 == n2, the file isn't present in m2 either. This
264 # means that the file was removed or deleted locally and
264 # means that the file was removed or deleted locally and
265 # removed remotely, but that residual entries remain in flags.
265 # removed remotely, but that residual entries remain in flags.
266 # This can happen in manifests generated by workingctx.
266 # This can happen in manifests generated by workingctx.
267 continue
267 continue
268 if fl12:
268 if fl12:
269 fl1, fl2 = fl12
269 fl1, fl2 = fl12
270 else: # flags didn't change, file contents did
270 else: # flags didn't change, file contents did
271 fl1 = fl2 = m1.flags(f)
271 fl1 = fl2 = m1.flags(f)
272
272
273 if partial and not partial(f):
273 if partial and not partial(f):
274 continue
274 continue
275 if n1 and n2:
275 if n1 and n2:
276 fla = ma.flags(f)
276 fla = ma.flags(f)
277 nol = 'l' not in fl1 + fl2 + fla
277 nol = 'l' not in fl1 + fl2 + fla
278 a = ma.get(f, nullid)
278 a = ma.get(f, nullid)
279 if n2 == a and fl2 == fla:
279 if n2 == a and fl2 == fla:
280 pass # remote unchanged - keep local
280 pass # remote unchanged - keep local
281 elif n1 == a and fl1 == fla: # local unchanged - use remote
281 elif n1 == a and fl1 == fla: # local unchanged - use remote
282 if n1 == n2: # optimization: keep local content
282 if n1 == n2: # optimization: keep local content
283 actions.append((f, "e", (fl2,), "update permissions"))
283 actions.append((f, "e", (fl2,), "update permissions"))
284 else:
284 else:
285 actions.append((f, "g", (fl2,), "remote is newer"))
285 actions.append((f, "g", (fl2,), "remote is newer"))
286 elif nol and n2 == a: # remote only changed 'x'
286 elif nol and n2 == a: # remote only changed 'x'
287 actions.append((f, "e", (fl2,), "update permissions"))
287 actions.append((f, "e", (fl2,), "update permissions"))
288 elif nol and n1 == a: # local only changed 'x'
288 elif nol and n1 == a: # local only changed 'x'
289 actions.append((f, "g", (fl1,), "remote is newer"))
289 actions.append((f, "g", (fl1,), "remote is newer"))
290 else: # both changed something
290 else: # both changed something
291 actions.append((f, "m", (f, f, False), "versions differ"))
291 actions.append((f, "m", (f, f, False), "versions differ"))
292 elif f in copied: # files we'll deal with on m2 side
292 elif f in copied: # files we'll deal with on m2 side
293 pass
293 pass
294 elif n1 and f in movewithdir: # directory rename
294 elif n1 and f in movewithdir: # directory rename
295 f2 = movewithdir[f]
295 f2 = movewithdir[f]
296 actions.append((f, "d", (None, f2, fl1),
296 actions.append((f, "d", (None, f2, fl1),
297 "remote renamed directory to " + f2))
297 "remote renamed directory to " + f2))
298 elif n1 and f in copy:
298 elif n1 and f in copy:
299 f2 = copy[f]
299 f2 = copy[f]
300 actions.append((f, "m", (f2, f, False),
300 actions.append((f, "m", (f2, f, False),
301 "local copied/moved to " + f2))
301 "local copied/moved to " + f2))
302 elif n1 and f in ma: # clean, a different, no remote
302 elif n1 and f in ma: # clean, a different, no remote
303 if n1 != ma[f]:
303 if n1 != ma[f]:
304 prompts.append((f, "cd")) # prompt changed/deleted
304 prompts.append((f, "cd")) # prompt changed/deleted
305 elif n1[20:] == "a": # added, no remote
305 elif n1[20:] == "a": # added, no remote
306 actions.append((f, "f", None, "remote deleted"))
306 actions.append((f, "f", None, "remote deleted"))
307 else:
307 else:
308 actions.append((f, "r", None, "other deleted"))
308 actions.append((f, "r", None, "other deleted"))
309 elif n2 and f in movewithdir:
309 elif n2 and f in movewithdir:
310 f2 = movewithdir[f]
310 f2 = movewithdir[f]
311 actions.append((None, "d", (f, f2, fl2),
311 actions.append((None, "d", (f, f2, fl2),
312 "local renamed directory to " + f2))
312 "local renamed directory to " + f2))
313 elif n2 and f in copy:
313 elif n2 and f in copy:
314 f2 = copy[f]
314 f2 = copy[f]
315 if f2 in m2:
315 if f2 in m2:
316 actions.append((f2, "m", (f, f, False),
316 actions.append((f2, "m", (f, f, False),
317 "remote copied to " + f))
317 "remote copied to " + f))
318 else:
318 else:
319 actions.append((f2, "m", (f, f, True),
319 actions.append((f2, "m", (f, f, True),
320 "remote moved to " + f))
320 "remote moved to " + f))
321 elif n2 and f not in ma:
321 elif n2 and f not in ma:
322 # local unknown, remote created: the logic is described by the
322 # local unknown, remote created: the logic is described by the
323 # following table:
323 # following table:
324 #
324 #
325 # force branchmerge different | action
325 # force branchmerge different | action
326 # n * n | get
326 # n * n | get
327 # n * y | abort
327 # n * y | abort
328 # y n * | get
328 # y n * | get
329 # y y n | get
329 # y y n | get
330 # y y y | merge
330 # y y y | merge
331 #
331 #
332 # Checking whether the files are different is expensive, so we
332 # Checking whether the files are different is expensive, so we
333 # don't do that when we can avoid it.
333 # don't do that when we can avoid it.
334 if force and not branchmerge:
334 if force and not branchmerge:
335 actions.append((f, "g", (fl2,), "remote created"))
335 actions.append((f, "g", (fl2,), "remote created"))
336 else:
336 else:
337 different = _checkunknownfile(repo, wctx, p2, f)
337 different = _checkunknownfile(repo, wctx, p2, f)
338 if force and branchmerge and different:
338 if force and branchmerge and different:
339 actions.append((f, "m", (f, f, False),
339 actions.append((f, "m", (f, f, False),
340 "remote differs from untracked local"))
340 "remote differs from untracked local"))
341 elif not force and different:
341 elif not force and different:
342 aborts.append((f, "ud"))
342 aborts.append((f, "ud"))
343 else:
343 else:
344 actions.append((f, "g", (fl2,), "remote created"))
344 actions.append((f, "g", (fl2,), "remote created"))
345 elif n2 and n2 != ma[f]:
345 elif n2 and n2 != ma[f]:
346 prompts.append((f, "dc")) # prompt deleted/changed
346 prompts.append((f, "dc")) # prompt deleted/changed
347
347
348 for f, m in sorted(aborts):
348 for f, m in sorted(aborts):
349 if m == "ud":
349 if m == "ud":
350 repo.ui.warn(_("%s: untracked file differs\n") % f)
350 repo.ui.warn(_("%s: untracked file differs\n") % f)
351 else: assert False, m
351 else: assert False, m
352 if aborts:
352 if aborts:
353 raise util.Abort(_("untracked files in working directory differ "
353 raise util.Abort(_("untracked files in working directory differ "
354 "from files in requested revision"))
354 "from files in requested revision"))
355
355
356 if not util.checkcase(repo.path):
356 if not util.checkcase(repo.path):
357 # check collision between files only in p2 for clean update
357 # check collision between files only in p2 for clean update
358 if (not branchmerge and
358 if (not branchmerge and
359 (force or not wctx.dirty(missing=True, branch=False))):
359 (force or not wctx.dirty(missing=True, branch=False))):
360 _checkcollision(repo, m2, [], [])
360 _checkcollision(repo, m2, [], [])
361 else:
361 else:
362 _checkcollision(repo, m1, actions, prompts)
362 _checkcollision(repo, m1, actions, prompts)
363
363
364 for f, m in sorted(prompts):
364 for f, m in sorted(prompts):
365 if m == "cd":
365 if m == "cd":
366 if acceptremote:
366 if acceptremote:
367 actions.append((f, "r", None, "remote delete"))
367 actions.append((f, "r", None, "remote delete"))
368 elif repo.ui.promptchoice(
368 elif repo.ui.promptchoice(
369 _("local changed %s which remote deleted\n"
369 _("local changed %s which remote deleted\n"
370 "use (c)hanged version or (d)elete?"
370 "use (c)hanged version or (d)elete?"
371 "$$ &Changed $$ &Delete") % f, 0):
371 "$$ &Changed $$ &Delete") % f, 0):
372 actions.append((f, "r", None, "prompt delete"))
372 actions.append((f, "r", None, "prompt delete"))
373 else:
373 else:
374 actions.append((f, "a", None, "prompt keep"))
374 actions.append((f, "a", None, "prompt keep"))
375 elif m == "dc":
375 elif m == "dc":
376 if acceptremote:
376 if acceptremote:
377 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
377 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
378 elif repo.ui.promptchoice(
378 elif repo.ui.promptchoice(
379 _("remote changed %s which local deleted\n"
379 _("remote changed %s which local deleted\n"
380 "use (c)hanged version or leave (d)eleted?"
380 "use (c)hanged version or leave (d)eleted?"
381 "$$ &Changed $$ &Deleted") % f, 0) == 0:
381 "$$ &Changed $$ &Deleted") % f, 0) == 0:
382 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
382 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
383 else: assert False, m
383 else: assert False, m
384 return actions
384 return actions
385
385
386 def actionkey(a):
386 def actionkey(a):
387 return a[1] in "rf" and -1 or 0, a
387 return a[1] in "rf" and -1 or 0, a
388
388
389 def getremove(repo, mctx, overwrite, args):
389 def getremove(repo, mctx, overwrite, args):
390 """apply usually-non-interactive updates to the working directory
390 """apply usually-non-interactive updates to the working directory
391
391
392 mctx is the context to be merged into the working copy
392 mctx is the context to be merged into the working copy
393
393
394 yields tuples for progress updates
394 yields tuples for progress updates
395 """
395 """
396 verbose = repo.ui.verbose
396 verbose = repo.ui.verbose
397 unlink = util.unlinkpath
397 unlink = util.unlinkpath
398 wjoin = repo.wjoin
398 wjoin = repo.wjoin
399 fctx = mctx.filectx
399 fctx = mctx.filectx
400 wwrite = repo.wwrite
400 wwrite = repo.wwrite
401 audit = repo.wopener.audit
401 audit = repo.wopener.audit
402 i = 0
402 i = 0
403 for arg in args:
403 for arg in args:
404 f = arg[0]
404 f = arg[0]
405 if arg[1] == 'r':
405 if arg[1] == 'r':
406 if verbose:
406 if verbose:
407 repo.ui.note(_("removing %s\n") % f)
407 repo.ui.note(_("removing %s\n") % f)
408 audit(f)
408 audit(f)
409 try:
409 try:
410 unlink(wjoin(f), ignoremissing=True)
410 unlink(wjoin(f), ignoremissing=True)
411 except OSError, inst:
411 except OSError, inst:
412 repo.ui.warn(_("update failed to remove %s: %s!\n") %
412 repo.ui.warn(_("update failed to remove %s: %s!\n") %
413 (f, inst.strerror))
413 (f, inst.strerror))
414 else:
414 else:
415 if verbose:
415 if verbose:
416 repo.ui.note(_("getting %s\n") % f)
416 repo.ui.note(_("getting %s\n") % f)
417 wwrite(f, fctx(f).data(), arg[2][0])
417 wwrite(f, fctx(f).data(), arg[2][0])
418 if i == 100:
418 if i == 100:
419 yield i, f
419 yield i, f
420 i = 0
420 i = 0
421 i += 1
421 i += 1
422 if i > 0:
422 if i > 0:
423 yield i, f
423 yield i, f
424
424
425 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
425 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
426 """apply the merge action list to the working directory
426 """apply the merge action list to the working directory
427
427
428 wctx is the working copy context
428 wctx is the working copy context
429 mctx is the context to be merged into the working copy
429 mctx is the context to be merged into the working copy
430 actx is the context of the common ancestor
430 actx is the context of the common ancestor
431
431
432 Return a tuple of counts (updated, merged, removed, unresolved) that
432 Return a tuple of counts (updated, merged, removed, unresolved) that
433 describes how many files were affected by the update.
433 describes how many files were affected by the update.
434 """
434 """
435
435
436 updated, merged, removed, unresolved = 0, 0, 0, 0
436 updated, merged, removed, unresolved = 0, 0, 0, 0
437 ms = mergestate(repo)
437 ms = mergestate(repo)
438 ms.reset(wctx.p1().node())
438 ms.reset(wctx.p1().node())
439 moves = []
439 moves = []
440 actions.sort(key=actionkey)
440 actions.sort(key=actionkey)
441
441
442 # prescan for merges
442 # prescan for merges
443 for a in actions:
443 for a in actions:
444 f, m, args, msg = a
444 f, m, args, msg = a
445 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
445 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
446 if m == "m": # merge
446 if m == "m": # merge
447 f2, fd, move = args
447 f2, fd, move = args
448 if fd == '.hgsubstate': # merged internally
448 if fd == '.hgsubstate': # merged internally
449 continue
449 continue
450 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
450 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
451 fcl = wctx[f]
451 fcl = wctx[f]
452 fco = mctx[f2]
452 fco = mctx[f2]
453 if mctx == actx: # backwards, use working dir parent as ancestor
453 if mctx == actx: # backwards, use working dir parent as ancestor
454 if fcl.parents():
454 if fcl.parents():
455 fca = fcl.p1()
455 fca = fcl.p1()
456 else:
456 else:
457 fca = repo.filectx(f, fileid=nullrev)
457 fca = repo.filectx(f, fileid=nullrev)
458 else:
458 else:
459 fca = fcl.ancestor(fco, actx)
459 fca = fcl.ancestor(fco, actx)
460 if not fca:
460 if not fca:
461 fca = repo.filectx(f, fileid=nullrev)
461 fca = repo.filectx(f, fileid=nullrev)
462 ms.add(fcl, fco, fca, fd)
462 ms.add(fcl, fco, fca, fd)
463 if f != fd and move:
463 if f != fd and move:
464 moves.append(f)
464 moves.append(f)
465
465
466 audit = repo.wopener.audit
466 audit = repo.wopener.audit
467
467
468 # remove renamed files after safely stored
468 # remove renamed files after safely stored
469 for f in moves:
469 for f in moves:
470 if os.path.lexists(repo.wjoin(f)):
470 if os.path.lexists(repo.wjoin(f)):
471 repo.ui.debug("removing %s\n" % f)
471 repo.ui.debug("removing %s\n" % f)
472 audit(f)
472 audit(f)
473 util.unlinkpath(repo.wjoin(f))
473 util.unlinkpath(repo.wjoin(f))
474
474
475 numupdates = len(actions)
475 numupdates = len(actions)
476 workeractions = [a for a in actions if a[1] in 'gr']
476 workeractions = [a for a in actions if a[1] in 'gr']
477 updateactions = [a for a in workeractions if a[1] == 'g']
477 updateactions = [a for a in workeractions if a[1] == 'g']
478 updated = len(updateactions)
478 updated = len(updateactions)
479 removeactions = [a for a in workeractions if a[1] == 'r']
479 removeactions = [a for a in workeractions if a[1] == 'r']
480 removed = len(removeactions)
480 removed = len(removeactions)
481 actions = [a for a in actions if a[1] not in 'gr']
481 actions = [a for a in actions if a[1] not in 'gr']
482
482
483 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
483 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
484 if hgsub and hgsub[0] == 'r':
484 if hgsub and hgsub[0] == 'r':
485 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
485 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
486
486
487 z = 0
487 z = 0
488 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
488 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
489 removeactions)
489 removeactions)
490 for i, item in prog:
490 for i, item in prog:
491 z += i
491 z += i
492 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
492 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
493 unit=_('files'))
493 unit=_('files'))
494 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
494 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
495 updateactions)
495 updateactions)
496 for i, item in prog:
496 for i, item in prog:
497 z += i
497 z += i
498 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
498 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
499 unit=_('files'))
499 unit=_('files'))
500
500
501 if hgsub and hgsub[0] == 'g':
501 if hgsub and hgsub[0] == 'g':
502 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
502 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
503
503
504 _updating = _('updating')
504 _updating = _('updating')
505 _files = _('files')
505 _files = _('files')
506 progress = repo.ui.progress
506 progress = repo.ui.progress
507
507
508 for i, a in enumerate(actions):
508 for i, a in enumerate(actions):
509 f, m, args, msg = a
509 f, m, args, msg = a
510 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
510 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
511 if m == "m": # merge
511 if m == "m": # merge
512 f2, fd, move = args
512 f2, fd, move = args
513 if fd == '.hgsubstate': # subrepo states need updating
513 if fd == '.hgsubstate': # subrepo states need updating
514 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
514 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
515 overwrite)
515 overwrite)
516 continue
516 continue
517 audit(fd)
517 audit(fd)
518 r = ms.resolve(fd, wctx, mctx)
518 r = ms.resolve(fd, wctx, mctx)
519 if r is not None and r > 0:
519 if r is not None and r > 0:
520 unresolved += 1
520 unresolved += 1
521 else:
521 else:
522 if r is None:
522 if r is None:
523 updated += 1
523 updated += 1
524 else:
524 else:
525 merged += 1
525 merged += 1
526 elif m == "d": # directory rename
526 elif m == "d": # directory rename
527 f2, fd, flags = args
527 f2, fd, flags = args
528 if f:
528 if f:
529 repo.ui.note(_("moving %s to %s\n") % (f, fd))
529 repo.ui.note(_("moving %s to %s\n") % (f, fd))
530 audit(f)
530 audit(f)
531 repo.wwrite(fd, wctx.filectx(f).data(), flags)
531 repo.wwrite(fd, wctx.filectx(f).data(), flags)
532 util.unlinkpath(repo.wjoin(f))
532 util.unlinkpath(repo.wjoin(f))
533 if f2:
533 if f2:
534 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
534 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
535 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
535 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
536 updated += 1
536 updated += 1
537 elif m == "dr": # divergent renames
537 elif m == "dr": # divergent renames
538 fl, = args
538 fl, = args
539 repo.ui.warn(_("note: possible conflict - %s was renamed "
539 repo.ui.warn(_("note: possible conflict - %s was renamed "
540 "multiple times to:\n") % f)
540 "multiple times to:\n") % f)
541 for nf in fl:
541 for nf in fl:
542 repo.ui.warn(" %s\n" % nf)
542 repo.ui.warn(" %s\n" % nf)
543 elif m == "rd": # rename and delete
543 elif m == "rd": # rename and delete
544 fl, = args
544 fl, = args
545 repo.ui.warn(_("note: possible conflict - %s was deleted "
545 repo.ui.warn(_("note: possible conflict - %s was deleted "
546 "and renamed to:\n") % f)
546 "and renamed to:\n") % f)
547 for nf in fl:
547 for nf in fl:
548 repo.ui.warn(" %s\n" % nf)
548 repo.ui.warn(" %s\n" % nf)
549 elif m == "e": # exec
549 elif m == "e": # exec
550 flags, = args
550 flags, = args
551 audit(f)
551 audit(f)
552 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
552 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
553 updated += 1
553 updated += 1
554 ms.commit()
554 ms.commit()
555 progress(_updating, None, total=numupdates, unit=_files)
555 progress(_updating, None, total=numupdates, unit=_files)
556
556
557 return updated, merged, removed, unresolved
557 return updated, merged, removed, unresolved
558
558
559 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
559 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
560 acceptremote=False):
560 acceptremote=False):
561 "Calculate the actions needed to merge mctx into tctx"
561 "Calculate the actions needed to merge mctx into tctx"
562 actions = []
562 actions = []
563 actions += manifestmerge(repo, tctx, mctx,
563 actions += manifestmerge(repo, tctx, mctx,
564 ancestor,
564 ancestor,
565 branchmerge, force,
565 branchmerge, force,
566 partial, acceptremote)
566 partial, acceptremote)
567 if tctx.rev() is None:
567 if tctx.rev() is None:
568 actions += _forgetremoved(tctx, mctx, branchmerge)
568 actions += _forgetremoved(tctx, mctx, branchmerge)
569 return actions
569 return actions
570
570
571 def recordupdates(repo, actions, branchmerge):
571 def recordupdates(repo, actions, branchmerge):
572 "record merge actions to the dirstate"
572 "record merge actions to the dirstate"
573
573
574 for a in actions:
574 for a in actions:
575 f, m, args, msg = a
575 f, m, args, msg = a
576 if m == "r": # remove
576 if m == "r": # remove
577 if branchmerge:
577 if branchmerge:
578 repo.dirstate.remove(f)
578 repo.dirstate.remove(f)
579 else:
579 else:
580 repo.dirstate.drop(f)
580 repo.dirstate.drop(f)
581 elif m == "a": # re-add
581 elif m == "a": # re-add
582 if not branchmerge:
582 if not branchmerge:
583 repo.dirstate.add(f)
583 repo.dirstate.add(f)
584 elif m == "f": # forget
584 elif m == "f": # forget
585 repo.dirstate.drop(f)
585 repo.dirstate.drop(f)
586 elif m == "e": # exec change
586 elif m == "e": # exec change
587 repo.dirstate.normallookup(f)
587 repo.dirstate.normallookup(f)
588 elif m == "g": # get
588 elif m == "g": # get
589 if branchmerge:
589 if branchmerge:
590 repo.dirstate.otherparent(f)
590 repo.dirstate.otherparent(f)
591 else:
591 else:
592 repo.dirstate.normal(f)
592 repo.dirstate.normal(f)
593 elif m == "m": # merge
593 elif m == "m": # merge
594 f2, fd, move = args
594 f2, fd, move = args
595 if branchmerge:
595 if branchmerge:
596 # We've done a branch merge, mark this file as merged
596 # We've done a branch merge, mark this file as merged
597 # so that we properly record the merger later
597 # so that we properly record the merger later
598 repo.dirstate.merge(fd)
598 repo.dirstate.merge(fd)
599 if f != f2: # copy/rename
599 if f != f2: # copy/rename
600 if move:
600 if move:
601 repo.dirstate.remove(f)
601 repo.dirstate.remove(f)
602 if f != fd:
602 if f != fd:
603 repo.dirstate.copy(f, fd)
603 repo.dirstate.copy(f, fd)
604 else:
604 else:
605 repo.dirstate.copy(f2, fd)
605 repo.dirstate.copy(f2, fd)
606 else:
606 else:
607 # We've update-merged a locally modified file, so
607 # We've update-merged a locally modified file, so
608 # we set the dirstate to emulate a normal checkout
608 # we set the dirstate to emulate a normal checkout
609 # of that file some time in the past. Thus our
609 # of that file some time in the past. Thus our
610 # merge will appear as a normal local file
610 # merge will appear as a normal local file
611 # modification.
611 # modification.
612 if f2 == fd: # file not locally copied/moved
612 if f2 == fd: # file not locally copied/moved
613 repo.dirstate.normallookup(fd)
613 repo.dirstate.normallookup(fd)
614 if move:
614 if move:
615 repo.dirstate.drop(f)
615 repo.dirstate.drop(f)
616 elif m == "d": # directory rename
616 elif m == "d": # directory rename
617 f2, fd, flag = args
617 f2, fd, flag = args
618 if not f2 and f not in repo.dirstate:
618 if not f2 and f not in repo.dirstate:
619 # untracked file moved
619 # untracked file moved
620 continue
620 continue
621 if branchmerge:
621 if branchmerge:
622 repo.dirstate.add(fd)
622 repo.dirstate.add(fd)
623 if f:
623 if f:
624 repo.dirstate.remove(f)
624 repo.dirstate.remove(f)
625 repo.dirstate.copy(f, fd)
625 repo.dirstate.copy(f, fd)
626 if f2:
626 if f2:
627 repo.dirstate.copy(f2, fd)
627 repo.dirstate.copy(f2, fd)
628 else:
628 else:
629 repo.dirstate.normal(fd)
629 repo.dirstate.normal(fd)
630 if f:
630 if f:
631 repo.dirstate.drop(f)
631 repo.dirstate.drop(f)
632
632
633 def update(repo, node, branchmerge, force, partial, ancestor=None,
633 def update(repo, node, branchmerge, force, partial, ancestor=None,
634 mergeancestor=False):
634 mergeancestor=False):
635 """
635 """
636 Perform a merge between the working directory and the given node
636 Perform a merge between the working directory and the given node
637
637
638 node = the node to update to, or None if unspecified
638 node = the node to update to, or None if unspecified
639 branchmerge = whether to merge between branches
639 branchmerge = whether to merge between branches
640 force = whether to force branch merging or file overwriting
640 force = whether to force branch merging or file overwriting
641 partial = a function to filter file lists (dirstate not updated)
641 partial = a function to filter file lists (dirstate not updated)
642 mergeancestor = whether it is merging with an ancestor. If true,
642 mergeancestor = whether it is merging with an ancestor. If true,
643 we should accept the incoming changes for any prompts that occur.
643 we should accept the incoming changes for any prompts that occur.
644 If false, merging with an ancestor (fast-forward) is only allowed
644 If false, merging with an ancestor (fast-forward) is only allowed
645 between different named branches. This flag is used by rebase extension
645 between different named branches. This flag is used by rebase extension
646 as a temporary fix and should be avoided in general.
646 as a temporary fix and should be avoided in general.
647
647
648 The table below shows all the behaviors of the update command
648 The table below shows all the behaviors of the update command
649 given the -c and -C or no options, whether the working directory
649 given the -c and -C or no options, whether the working directory
650 is dirty, whether a revision is specified, and the relationship of
650 is dirty, whether a revision is specified, and the relationship of
651 the parent rev to the target rev (linear, on the same named
651 the parent rev to the target rev (linear, on the same named
652 branch, or on another named branch).
652 branch, or on another named branch).
653
653
654 This logic is tested by test-update-branches.t.
654 This logic is tested by test-update-branches.t.
655
655
656 -c -C dirty rev | linear same cross
656 -c -C dirty rev | linear same cross
657 n n n n | ok (1) x
657 n n n n | ok (1) x
658 n n n y | ok ok ok
658 n n n y | ok ok ok
659 n n y n | merge (2) (2)
659 n n y n | merge (2) (2)
660 n n y y | merge (3) (3)
660 n n y y | merge (3) (3)
661 n y * * | --- discard ---
661 n y * * | --- discard ---
662 y n y * | --- (4) ---
662 y n y * | --- (4) ---
663 y n n * | --- ok ---
663 y n n * | --- ok ---
664 y y * * | --- (5) ---
664 y y * * | --- (5) ---
665
665
666 x = can't happen
666 x = can't happen
667 * = don't-care
667 * = don't-care
668 1 = abort: not a linear update (merge or update --check to force update)
668 1 = abort: not a linear update (merge or update --check to force update)
669 2 = abort: uncommitted changes (commit and merge, or update --clean to
669 2 = abort: uncommitted changes (commit and merge, or update --clean to
670 discard changes)
670 discard changes)
671 3 = abort: uncommitted changes (commit or update --clean to discard changes)
671 3 = abort: uncommitted changes (commit or update --clean to discard changes)
672 4 = abort: uncommitted changes (checked in commands.py)
672 4 = abort: uncommitted changes (checked in commands.py)
673 5 = incompatible options (checked in commands.py)
673 5 = incompatible options (checked in commands.py)
674
674
675 Return the same tuple as applyupdates().
675 Return the same tuple as applyupdates().
676 """
676 """
677
677
678 onode = node
678 onode = node
679 wlock = repo.wlock()
679 wlock = repo.wlock()
680 try:
680 try:
681 wc = repo[None]
681 wc = repo[None]
682 pl = wc.parents()
682 pl = wc.parents()
683 p1 = pl[0]
683 p1 = pl[0]
684 pa = None
684 pa = None
685 if ancestor:
685 if ancestor:
686 pa = repo[ancestor]
686 pa = repo[ancestor]
687
687
688 if node is None:
688 if node is None:
689 # Here is where we should consider bookmarks, divergent bookmarks,
689 # Here is where we should consider bookmarks, divergent bookmarks,
690 # foreground changesets (successors), and tip of current branch;
690 # foreground changesets (successors), and tip of current branch;
691 # but currently we are only checking the branch tips.
691 # but currently we are only checking the branch tips.
692 try:
692 try:
693 node = repo.branchtip(wc.branch())
693 node = repo.branchtip(wc.branch())
694 except error.RepoLookupError:
694 except error.RepoLookupError:
695 if wc.branch() == "default": # no default branch!
695 if wc.branch() == "default": # no default branch!
696 node = repo.lookup("tip") # update to tip
696 node = repo.lookup("tip") # update to tip
697 else:
697 else:
698 raise util.Abort(_("branch %s not found") % wc.branch())
698 raise util.Abort(_("branch %s not found") % wc.branch())
699
700 if p1.obsolete() and not p1.children():
701 # allow updating to successors
702 successors = obsolete.successorssets(repo, p1.node())
703
704 # behavior of certain cases is as follows,
705 #
706 # divergent changesets: update to highest rev, similar to what
707 # is currently done when there are more than one head
708 # (i.e. 'tip')
709 #
710 # replaced changesets: same as divergent except we know there
711 # is no conflict
712 #
713 # pruned changeset: no update is done; though, we could
714 # consider updating to the first non-obsolete parent,
715 # similar to what is current done for 'hg prune'
716
717 if successors:
718 # flatten the list here handles both divergent (len > 1)
719 # and the usual case (len = 1)
720 successors = [n for sub in successors for n in sub]
721
722 # get the max revision for the given successors set,
723 # i.e. the 'tip' of a set
724 node = repo.revs("max(%ln)", successors)[0]
725 pa = p1
726
699 overwrite = force and not branchmerge
727 overwrite = force and not branchmerge
700
728
701 p2 = repo[node]
729 p2 = repo[node]
702 if pa is None:
730 if pa is None:
703 pa = p1.ancestor(p2)
731 pa = p1.ancestor(p2)
704
732
705 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
733 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
706
734
707 ### check phase
735 ### check phase
708 if not overwrite and len(pl) > 1:
736 if not overwrite and len(pl) > 1:
709 raise util.Abort(_("outstanding uncommitted merges"))
737 raise util.Abort(_("outstanding uncommitted merges"))
710 if branchmerge:
738 if branchmerge:
711 if pa == p2:
739 if pa == p2:
712 raise util.Abort(_("merging with a working directory ancestor"
740 raise util.Abort(_("merging with a working directory ancestor"
713 " has no effect"))
741 " has no effect"))
714 elif pa == p1:
742 elif pa == p1:
715 if not mergeancestor and p1.branch() == p2.branch():
743 if not mergeancestor and p1.branch() == p2.branch():
716 raise util.Abort(_("nothing to merge"),
744 raise util.Abort(_("nothing to merge"),
717 hint=_("use 'hg update' "
745 hint=_("use 'hg update' "
718 "or check 'hg heads'"))
746 "or check 'hg heads'"))
719 if not force and (wc.files() or wc.deleted()):
747 if not force and (wc.files() or wc.deleted()):
720 raise util.Abort(_("uncommitted changes"),
748 raise util.Abort(_("uncommitted changes"),
721 hint=_("use 'hg status' to list changes"))
749 hint=_("use 'hg status' to list changes"))
722 for s in sorted(wc.substate):
750 for s in sorted(wc.substate):
723 if wc.sub(s).dirty():
751 if wc.sub(s).dirty():
724 raise util.Abort(_("uncommitted changes in "
752 raise util.Abort(_("uncommitted changes in "
725 "subrepository '%s'") % s)
753 "subrepository '%s'") % s)
726
754
727 elif not overwrite:
755 elif not overwrite:
728 if p1 == p2: # no-op update
756 if p1 == p2: # no-op update
729 # call the hooks and exit early
757 # call the hooks and exit early
730 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
758 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
731 repo.hook('update', parent1=xp2, parent2='', error=0)
759 repo.hook('update', parent1=xp2, parent2='', error=0)
732 return 0, 0, 0, 0
760 return 0, 0, 0, 0
733
761
734 if pa not in (p1, p2): # nonlinear
762 if pa not in (p1, p2): # nonlinear
735 dirty = wc.dirty(missing=True)
763 dirty = wc.dirty(missing=True)
736 if dirty or onode is None:
764 if dirty or onode is None:
737 # Branching is a bit strange to ensure we do the minimal
765 # Branching is a bit strange to ensure we do the minimal
738 # amount of call to obsolete.background.
766 # amount of call to obsolete.background.
739 foreground = obsolete.foreground(repo, [p1.node()])
767 foreground = obsolete.foreground(repo, [p1.node()])
740 # note: the <node> variable contains a random identifier
768 # note: the <node> variable contains a random identifier
741 if repo[node].node() in foreground:
769 if repo[node].node() in foreground:
742 pa = p1 # allow updating to successors
770 pa = p1 # allow updating to successors
743 elif dirty:
771 elif dirty:
744 msg = _("uncommitted changes")
772 msg = _("uncommitted changes")
745 if onode is None:
773 if onode is None:
746 hint = _("commit and merge, or update --clean to"
774 hint = _("commit and merge, or update --clean to"
747 " discard changes")
775 " discard changes")
748 else:
776 else:
749 hint = _("commit or update --clean to discard"
777 hint = _("commit or update --clean to discard"
750 " changes")
778 " changes")
751 raise util.Abort(msg, hint=hint)
779 raise util.Abort(msg, hint=hint)
752 else: # node is none
780 else: # node is none
753 msg = _("not a linear update")
781 msg = _("not a linear update")
754 hint = _("merge or update --check to force update")
782 hint = _("merge or update --check to force update")
755 raise util.Abort(msg, hint=hint)
783 raise util.Abort(msg, hint=hint)
756 else:
784 else:
757 # Allow jumping branches if clean and specific rev given
785 # Allow jumping branches if clean and specific rev given
758 pa = p1
786 pa = p1
759
787
760 ### calculate phase
788 ### calculate phase
761 actions = calculateupdates(repo, wc, p2, pa,
789 actions = calculateupdates(repo, wc, p2, pa,
762 branchmerge, force, partial, mergeancestor)
790 branchmerge, force, partial, mergeancestor)
763
791
764 ### apply phase
792 ### apply phase
765 if not branchmerge: # just jump to the new rev
793 if not branchmerge: # just jump to the new rev
766 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
794 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
767 if not partial:
795 if not partial:
768 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
796 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
769 # note that we're in the middle of an update
797 # note that we're in the middle of an update
770 repo.vfs.write('updatestate', p2.hex())
798 repo.vfs.write('updatestate', p2.hex())
771
799
772 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
800 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
773
801
774 if not partial:
802 if not partial:
775 repo.setparents(fp1, fp2)
803 repo.setparents(fp1, fp2)
776 recordupdates(repo, actions, branchmerge)
804 recordupdates(repo, actions, branchmerge)
777 # update completed, clear state
805 # update completed, clear state
778 util.unlink(repo.join('updatestate'))
806 util.unlink(repo.join('updatestate'))
779
807
780 if not branchmerge:
808 if not branchmerge:
781 repo.dirstate.setbranch(p2.branch())
809 repo.dirstate.setbranch(p2.branch())
782 finally:
810 finally:
783 wlock.release()
811 wlock.release()
784
812
785 if not partial:
813 if not partial:
786 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
814 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
787 return stats
815 return stats
@@ -1,244 +1,263
1 # Construct the following history tree:
1 # Construct the following history tree:
2 #
2 #
3 # @ 5:e1bb631146ca b1
3 # @ 5:e1bb631146ca b1
4 # |
4 # |
5 # o 4:a4fdb3b883c4 0:b608b9236435 b1
5 # o 4:a4fdb3b883c4 0:b608b9236435 b1
6 # |
6 # |
7 # | o 3:4b57d2520816 1:44592833ba9f
7 # | o 3:4b57d2520816 1:44592833ba9f
8 # | |
8 # | |
9 # | | o 2:063f31070f65
9 # | | o 2:063f31070f65
10 # | |/
10 # | |/
11 # | o 1:44592833ba9f
11 # | o 1:44592833ba9f
12 # |/
12 # |/
13 # o 0:b608b9236435
13 # o 0:b608b9236435
14
14
15 $ mkdir b1
15 $ mkdir b1
16 $ cd b1
16 $ cd b1
17 $ hg init
17 $ hg init
18 $ echo foo > foo
18 $ echo foo > foo
19 $ echo zero > a
19 $ echo zero > a
20 $ hg init sub
20 $ hg init sub
21 $ echo suba > sub/suba
21 $ echo suba > sub/suba
22 $ hg --cwd sub ci -Am addsuba
22 $ hg --cwd sub ci -Am addsuba
23 adding suba
23 adding suba
24 $ echo 'sub = sub' > .hgsub
24 $ echo 'sub = sub' > .hgsub
25 $ hg ci -qAm0
25 $ hg ci -qAm0
26 $ echo one > a ; hg ci -m1
26 $ echo one > a ; hg ci -m1
27 $ echo two > a ; hg ci -m2
27 $ echo two > a ; hg ci -m2
28 $ hg up -q 1
28 $ hg up -q 1
29 $ echo three > a ; hg ci -qm3
29 $ echo three > a ; hg ci -qm3
30 $ hg up -q 0
30 $ hg up -q 0
31 $ hg branch -q b1
31 $ hg branch -q b1
32 $ echo four > a ; hg ci -qm4
32 $ echo four > a ; hg ci -qm4
33 $ echo five > a ; hg ci -qm5
33 $ echo five > a ; hg ci -qm5
34
34
35 Initial repo state:
35 Initial repo state:
36
36
37 $ hg log -G --template '{rev}:{node|short} {parents} {branches}\n'
37 $ hg log -G --template '{rev}:{node|short} {parents} {branches}\n'
38 @ 5:ff252e8273df b1
38 @ 5:ff252e8273df b1
39 |
39 |
40 o 4:d047485b3896 0:60829823a42a b1
40 o 4:d047485b3896 0:60829823a42a b1
41 |
41 |
42 | o 3:6efa171f091b 1:0786582aa4b1
42 | o 3:6efa171f091b 1:0786582aa4b1
43 | |
43 | |
44 | | o 2:bd10386d478c
44 | | o 2:bd10386d478c
45 | |/
45 | |/
46 | o 1:0786582aa4b1
46 | o 1:0786582aa4b1
47 |/
47 |/
48 o 0:60829823a42a
48 o 0:60829823a42a
49
49
50
50
51 Make sure update doesn't assume b1 is a repository if invoked from outside:
51 Make sure update doesn't assume b1 is a repository if invoked from outside:
52
52
53 $ cd ..
53 $ cd ..
54 $ hg update b1
54 $ hg update b1
55 abort: no repository found in '$TESTTMP' (.hg not found)!
55 abort: no repository found in '$TESTTMP' (.hg not found)!
56 [255]
56 [255]
57 $ cd b1
57 $ cd b1
58
58
59 Test helper functions:
59 Test helper functions:
60
60
61 $ revtest () {
61 $ revtest () {
62 > msg=$1
62 > msg=$1
63 > dirtyflag=$2 # 'clean', 'dirty' or 'dirtysub'
63 > dirtyflag=$2 # 'clean', 'dirty' or 'dirtysub'
64 > startrev=$3
64 > startrev=$3
65 > targetrev=$4
65 > targetrev=$4
66 > opt=$5
66 > opt=$5
67 > hg up -qC $startrev
67 > hg up -qC $startrev
68 > test $dirtyflag = dirty && echo dirty > foo
68 > test $dirtyflag = dirty && echo dirty > foo
69 > test $dirtyflag = dirtysub && echo dirty > sub/suba
69 > test $dirtyflag = dirtysub && echo dirty > sub/suba
70 > hg up $opt $targetrev
70 > hg up $opt $targetrev
71 > hg parent --template 'parent={rev}\n'
71 > hg parent --template 'parent={rev}\n'
72 > hg stat -S
72 > hg stat -S
73 > }
73 > }
74
74
75 $ norevtest () {
75 $ norevtest () {
76 > msg=$1
76 > msg=$1
77 > dirtyflag=$2 # 'clean', 'dirty' or 'dirtysub'
77 > dirtyflag=$2 # 'clean', 'dirty' or 'dirtysub'
78 > startrev=$3
78 > startrev=$3
79 > opt=$4
79 > opt=$4
80 > hg up -qC $startrev
80 > hg up -qC $startrev
81 > test $dirtyflag = dirty && echo dirty > foo
81 > test $dirtyflag = dirty && echo dirty > foo
82 > test $dirtyflag = dirtysub && echo dirty > sub/suba
82 > test $dirtyflag = dirtysub && echo dirty > sub/suba
83 > hg up $opt
83 > hg up $opt
84 > hg parent --template 'parent={rev}\n'
84 > hg parent --template 'parent={rev}\n'
85 > hg stat -S
85 > hg stat -S
86 > }
86 > }
87
87
88 Test cases are documented in a table in the update function of merge.py.
88 Test cases are documented in a table in the update function of merge.py.
89 Cases are run as shown in that table, row by row.
89 Cases are run as shown in that table, row by row.
90
90
91 $ norevtest 'none clean linear' clean 4
91 $ norevtest 'none clean linear' clean 4
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 parent=5
93 parent=5
94
94
95 $ norevtest 'none clean same' clean 2
95 $ norevtest 'none clean same' clean 2
96 abort: not a linear update
96 abort: not a linear update
97 (merge or update --check to force update)
97 (merge or update --check to force update)
98 parent=2
98 parent=2
99
99
100
100
101 $ revtest 'none clean linear' clean 1 2
101 $ revtest 'none clean linear' clean 1 2
102 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 parent=2
103 parent=2
104
104
105 $ revtest 'none clean same' clean 2 3
105 $ revtest 'none clean same' clean 2 3
106 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 parent=3
107 parent=3
108
108
109 $ revtest 'none clean cross' clean 3 4
109 $ revtest 'none clean cross' clean 3 4
110 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 parent=4
111 parent=4
112
112
113
113
114 $ revtest 'none dirty linear' dirty 1 2
114 $ revtest 'none dirty linear' dirty 1 2
115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 parent=2
116 parent=2
117 M foo
117 M foo
118
118
119 $ revtest 'none dirtysub linear' dirtysub 1 2
119 $ revtest 'none dirtysub linear' dirtysub 1 2
120 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 parent=2
121 parent=2
122 M sub/suba
122 M sub/suba
123
123
124 $ revtest 'none dirty same' dirty 2 3
124 $ revtest 'none dirty same' dirty 2 3
125 abort: uncommitted changes
125 abort: uncommitted changes
126 (commit or update --clean to discard changes)
126 (commit or update --clean to discard changes)
127 parent=2
127 parent=2
128 M foo
128 M foo
129
129
130 $ revtest 'none dirtysub same' dirtysub 2 3
130 $ revtest 'none dirtysub same' dirtysub 2 3
131 abort: uncommitted changes
131 abort: uncommitted changes
132 (commit or update --clean to discard changes)
132 (commit or update --clean to discard changes)
133 parent=2
133 parent=2
134 M sub/suba
134 M sub/suba
135
135
136 $ revtest 'none dirty cross' dirty 3 4
136 $ revtest 'none dirty cross' dirty 3 4
137 abort: uncommitted changes
137 abort: uncommitted changes
138 (commit or update --clean to discard changes)
138 (commit or update --clean to discard changes)
139 parent=3
139 parent=3
140 M foo
140 M foo
141
141
142 $ norevtest 'none dirty cross' dirty 2
142 $ norevtest 'none dirty cross' dirty 2
143 abort: uncommitted changes
143 abort: uncommitted changes
144 (commit and merge, or update --clean to discard changes)
144 (commit and merge, or update --clean to discard changes)
145 parent=2
145 parent=2
146 M foo
146 M foo
147
147
148 $ revtest 'none dirtysub cross' dirtysub 3 4
148 $ revtest 'none dirtysub cross' dirtysub 3 4
149 abort: uncommitted changes
149 abort: uncommitted changes
150 (commit or update --clean to discard changes)
150 (commit or update --clean to discard changes)
151 parent=3
151 parent=3
152 M sub/suba
152 M sub/suba
153
153
154 $ revtest '-C dirty linear' dirty 1 2 -C
154 $ revtest '-C dirty linear' dirty 1 2 -C
155 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 parent=2
156 parent=2
157
157
158 $ revtest '-c dirty linear' dirty 1 2 -c
158 $ revtest '-c dirty linear' dirty 1 2 -c
159 abort: uncommitted changes
159 abort: uncommitted changes
160 parent=1
160 parent=1
161 M foo
161 M foo
162
162
163 $ revtest '-c dirtysub linear' dirtysub 1 2 -c
163 $ revtest '-c dirtysub linear' dirtysub 1 2 -c
164 abort: uncommitted changes
164 abort: uncommitted changes
165 parent=1
165 parent=1
166 M sub/suba
166 M sub/suba
167
167
168 $ norevtest '-c clean same' clean 2 -c
168 $ norevtest '-c clean same' clean 2 -c
169 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 parent=3
170 parent=3
171
171
172 $ revtest '-cC dirty linear' dirty 1 2 -cC
172 $ revtest '-cC dirty linear' dirty 1 2 -cC
173 abort: cannot specify both -c/--check and -C/--clean
173 abort: cannot specify both -c/--check and -C/--clean
174 parent=1
174 parent=1
175 M foo
175 M foo
176
176
177 Test obsolescence behavior
177 Test obsolescence behavior
178 ---------------------------------------------------------------------
178 ---------------------------------------------------------------------
179
179
180 successors should be taken in account when checking head destination
180 successors should be taken in account when checking head destination
181
181
182 $ cat << EOF >> $HGRCPATH
182 $ cat << EOF >> $HGRCPATH
183 > [extensions]
183 > [extensions]
184 > obs=$TESTTMP/obs.py
184 > obs=$TESTTMP/obs.py
185 > [ui]
185 > [ui]
186 > logtemplate={rev}:{node|short} {desc|firstline}
186 > logtemplate={rev}:{node|short} {desc|firstline}
187 > EOF
187 > EOF
188 $ cat > $TESTTMP/obs.py << EOF
188 $ cat > $TESTTMP/obs.py << EOF
189 > import mercurial.obsolete
189 > import mercurial.obsolete
190 > mercurial.obsolete._enabled = True
190 > mercurial.obsolete._enabled = True
191 > EOF
191 > EOF
192
192
193 Test no-argument update to a successor of an obsoleted changeset
193 Test no-argument update to a successor of an obsoleted changeset
194
194
195 $ hg log -G
195 $ hg log -G
196 o 5:ff252e8273df 5
196 o 5:ff252e8273df 5
197 |
197 |
198 o 4:d047485b3896 4
198 o 4:d047485b3896 4
199 |
199 |
200 | o 3:6efa171f091b 3
200 | o 3:6efa171f091b 3
201 | |
201 | |
202 | | o 2:bd10386d478c 2
202 | | o 2:bd10386d478c 2
203 | |/
203 | |/
204 | @ 1:0786582aa4b1 1
204 | @ 1:0786582aa4b1 1
205 |/
205 |/
206 o 0:60829823a42a 0
206 o 0:60829823a42a 0
207
207
208 $ hg book bm -r 3
208 $ hg book bm -r 3
209 $ hg status
209 $ hg status
210 M foo
210 M foo
211
211
212 We add simple obsolescence marker between 3 and 4 (indirect successors)
212 We add simple obsolescence marker between 3 and 4 (indirect successors)
213
213
214 $ hg id --debug -i -r 3
214 $ hg id --debug -i -r 3
215 6efa171f091b00a3c35edc15d48c52a498929953
215 6efa171f091b00a3c35edc15d48c52a498929953
216 $ hg id --debug -i -r 4
216 $ hg id --debug -i -r 4
217 d047485b3896813b2a624e86201983520f003206
217 d047485b3896813b2a624e86201983520f003206
218 $ hg debugobsolete 6efa171f091b00a3c35edc15d48c52a498929953 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
218 $ hg debugobsolete 6efa171f091b00a3c35edc15d48c52a498929953 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
219 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa d047485b3896813b2a624e86201983520f003206
219 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa d047485b3896813b2a624e86201983520f003206
220
220
221 Test that 5 is detected as a valid destination from 3 and also accepts moving
221 Test that 5 is detected as a valid destination from 3 and also accepts moving
222 the bookmark (issue4015)
222 the bookmark (issue4015)
223
223
224 $ hg up --quiet --hidden 3
224 $ hg up --quiet --hidden 3
225 $ hg up 5
225 $ hg up 5
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 $ hg book bm
227 $ hg book bm
228 moving bookmark 'bm' forward from 6efa171f091b
228 moving bookmark 'bm' forward from 6efa171f091b
229 $ hg bookmarks
229 $ hg bookmarks
230 * bm 5:ff252e8273df
230 * bm 5:ff252e8273df
231
231
232 Test that 4 is detected as the no-argument destination from 3
233 $ hg up --quiet 0 # we should be able to update to 3 directly
234 $ hg up --quiet --hidden 3 # but not implemented yet.
235 $ hg up
236 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 $ hg id
238 d047485b3896+ (b1)
239
232 Test that 5 is detected as a valid destination from 1
240 Test that 5 is detected as a valid destination from 1
233 $ hg up --quiet 0 # we should be able to update to 3 directly
241 $ hg up --quiet 0 # we should be able to update to 3 directly
234 $ hg up --quiet --hidden 3 # but not implemented yet.
242 $ hg up --quiet --hidden 3 # but not implemented yet.
235 $ hg up 5
243 $ hg up 5
236 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
237
245
238 Test that 5 is not detected as a valid destination from 2
246 Test that 5 is not detected as a valid destination from 2
239 $ hg up --quiet 0
247 $ hg up --quiet 0
240 $ hg up --quiet 2
248 $ hg up --quiet 2
241 $ hg up 5
249 $ hg up 5
242 abort: uncommitted changes
250 abort: uncommitted changes
243 (commit or update --clean to discard changes)
251 (commit or update --clean to discard changes)
244 [255]
252 [255]
253
254 Test that we don't crash when updating from a pruned changeset (i.e. has no
255 successors). Behavior should probably be that we update to the first
256 non-obsolete parent but that will be decided later.
257 $ hg id --debug -r 2
258 bd10386d478cd5a9faf2e604114c8e6da62d3889
259 $ hg up --quiet 0
260 $ hg up --quiet 2
261 $ hg debugobsolete bd10386d478cd5a9faf2e604114c8e6da62d3889
262 $ hg up
263 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
General Comments 0
You need to be logged in to leave comments. Login now