##// END OF EJS Templates
merge: update comment for future devs
Sean Farley -
r20278:20ef533f default
parent child Browse files
Show More
@@ -1,781 +1,783 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 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 if node is None:
682 if node is None:
683 # tip of current branch
683 # Here is where we should consider bookmarks, divergent bookmarks,
684 # foreground changesets (successors), and tip of current branch;
685 # but currently we are only checking the branch tips.
684 try:
686 try:
685 node = repo.branchtip(wc.branch())
687 node = repo.branchtip(wc.branch())
686 except error.RepoLookupError:
688 except error.RepoLookupError:
687 if wc.branch() == "default": # no default branch!
689 if wc.branch() == "default": # no default branch!
688 node = repo.lookup("tip") # update to tip
690 node = repo.lookup("tip") # update to tip
689 else:
691 else:
690 raise util.Abort(_("branch %s not found") % wc.branch())
692 raise util.Abort(_("branch %s not found") % wc.branch())
691 overwrite = force and not branchmerge
693 overwrite = force and not branchmerge
692 pl = wc.parents()
694 pl = wc.parents()
693 p1, p2 = pl[0], repo[node]
695 p1, p2 = pl[0], repo[node]
694 if ancestor:
696 if ancestor:
695 pa = repo[ancestor]
697 pa = repo[ancestor]
696 else:
698 else:
697 pa = p1.ancestor(p2)
699 pa = p1.ancestor(p2)
698
700
699 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
701 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
700
702
701 ### check phase
703 ### check phase
702 if not overwrite and len(pl) > 1:
704 if not overwrite and len(pl) > 1:
703 raise util.Abort(_("outstanding uncommitted merges"))
705 raise util.Abort(_("outstanding uncommitted merges"))
704 if branchmerge:
706 if branchmerge:
705 if pa == p2:
707 if pa == p2:
706 raise util.Abort(_("merging with a working directory ancestor"
708 raise util.Abort(_("merging with a working directory ancestor"
707 " has no effect"))
709 " has no effect"))
708 elif pa == p1:
710 elif pa == p1:
709 if not mergeancestor and p1.branch() == p2.branch():
711 if not mergeancestor and p1.branch() == p2.branch():
710 raise util.Abort(_("nothing to merge"),
712 raise util.Abort(_("nothing to merge"),
711 hint=_("use 'hg update' "
713 hint=_("use 'hg update' "
712 "or check 'hg heads'"))
714 "or check 'hg heads'"))
713 if not force and (wc.files() or wc.deleted()):
715 if not force and (wc.files() or wc.deleted()):
714 raise util.Abort(_("uncommitted changes"),
716 raise util.Abort(_("uncommitted changes"),
715 hint=_("use 'hg status' to list changes"))
717 hint=_("use 'hg status' to list changes"))
716 for s in sorted(wc.substate):
718 for s in sorted(wc.substate):
717 if wc.sub(s).dirty():
719 if wc.sub(s).dirty():
718 raise util.Abort(_("uncommitted changes in "
720 raise util.Abort(_("uncommitted changes in "
719 "subrepository '%s'") % s)
721 "subrepository '%s'") % s)
720
722
721 elif not overwrite:
723 elif not overwrite:
722 if p1 == p2: # no-op update
724 if p1 == p2: # no-op update
723 # call the hooks and exit early
725 # call the hooks and exit early
724 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
726 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
725 repo.hook('update', parent1=xp2, parent2='', error=0)
727 repo.hook('update', parent1=xp2, parent2='', error=0)
726 return 0, 0, 0, 0
728 return 0, 0, 0, 0
727
729
728 if pa not in (p1, p2): # nonlinear
730 if pa not in (p1, p2): # nonlinear
729 dirty = wc.dirty(missing=True)
731 dirty = wc.dirty(missing=True)
730 if dirty or onode is None:
732 if dirty or onode is None:
731 # Branching is a bit strange to ensure we do the minimal
733 # Branching is a bit strange to ensure we do the minimal
732 # amount of call to obsolete.background.
734 # amount of call to obsolete.background.
733 foreground = obsolete.foreground(repo, [p1.node()])
735 foreground = obsolete.foreground(repo, [p1.node()])
734 # note: the <node> variable contains a random identifier
736 # note: the <node> variable contains a random identifier
735 if repo[node].node() in foreground:
737 if repo[node].node() in foreground:
736 pa = p1 # allow updating to successors
738 pa = p1 # allow updating to successors
737 elif dirty:
739 elif dirty:
738 msg = _("uncommitted changes")
740 msg = _("uncommitted changes")
739 if onode is None:
741 if onode is None:
740 hint = _("commit and merge, or update --clean to"
742 hint = _("commit and merge, or update --clean to"
741 " discard changes")
743 " discard changes")
742 else:
744 else:
743 hint = _("commit or update --clean to discard"
745 hint = _("commit or update --clean to discard"
744 " changes")
746 " changes")
745 raise util.Abort(msg, hint=hint)
747 raise util.Abort(msg, hint=hint)
746 else: # node is none
748 else: # node is none
747 msg = _("not a linear update")
749 msg = _("not a linear update")
748 hint = _("merge or update --check to force update")
750 hint = _("merge or update --check to force update")
749 raise util.Abort(msg, hint=hint)
751 raise util.Abort(msg, hint=hint)
750 else:
752 else:
751 # Allow jumping branches if clean and specific rev given
753 # Allow jumping branches if clean and specific rev given
752 pa = p1
754 pa = p1
753
755
754 ### calculate phase
756 ### calculate phase
755 actions = calculateupdates(repo, wc, p2, pa,
757 actions = calculateupdates(repo, wc, p2, pa,
756 branchmerge, force, partial, mergeancestor)
758 branchmerge, force, partial, mergeancestor)
757
759
758 ### apply phase
760 ### apply phase
759 if not branchmerge: # just jump to the new rev
761 if not branchmerge: # just jump to the new rev
760 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
762 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
761 if not partial:
763 if not partial:
762 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
764 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
763 # note that we're in the middle of an update
765 # note that we're in the middle of an update
764 repo.vfs.write('updatestate', p2.hex())
766 repo.vfs.write('updatestate', p2.hex())
765
767
766 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
768 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
767
769
768 if not partial:
770 if not partial:
769 repo.setparents(fp1, fp2)
771 repo.setparents(fp1, fp2)
770 recordupdates(repo, actions, branchmerge)
772 recordupdates(repo, actions, branchmerge)
771 # update completed, clear state
773 # update completed, clear state
772 util.unlink(repo.join('updatestate'))
774 util.unlink(repo.join('updatestate'))
773
775
774 if not branchmerge:
776 if not branchmerge:
775 repo.dirstate.setbranch(p2.branch())
777 repo.dirstate.setbranch(p2.branch())
776 finally:
778 finally:
777 wlock.release()
779 wlock.release()
778
780
779 if not partial:
781 if not partial:
780 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
782 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
781 return stats
783 return stats
General Comments 0
You need to be logged in to leave comments. Login now