##// END OF EJS Templates
merge: add a files method to the mergestate class...
Bryan O'Sullivan -
r19285:feaf5749 default
parent child Browse files
Show More
@@ -1,761 +1,763 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):
65 return self._state.keys()
64 def mark(self, dfile, state):
66 def mark(self, dfile, state):
65 self._state[dfile][0] = state
67 self._state[dfile][0] = state
66 self._dirty = True
68 self._dirty = True
67 def resolve(self, dfile, wctx, octx):
69 def resolve(self, dfile, wctx, octx):
68 if self[dfile] == 'r':
70 if self[dfile] == 'r':
69 return 0
71 return 0
70 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
72 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
71 fcd = wctx[dfile]
73 fcd = wctx[dfile]
72 fco = octx[ofile]
74 fco = octx[ofile]
73 fca = self._repo.filectx(afile, fileid=anode)
75 fca = self._repo.filectx(afile, fileid=anode)
74 # "premerge" x flags
76 # "premerge" x flags
75 flo = fco.flags()
77 flo = fco.flags()
76 fla = fca.flags()
78 fla = fca.flags()
77 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:
78 if fca.node() == nullid:
80 if fca.node() == nullid:
79 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
81 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
80 afile)
82 afile)
81 elif flags == fla:
83 elif flags == fla:
82 flags = flo
84 flags = flo
83 # restore local
85 # restore local
84 f = self._repo.opener("merge/" + hash)
86 f = self._repo.opener("merge/" + hash)
85 self._repo.wwrite(dfile, f.read(), flags)
87 self._repo.wwrite(dfile, f.read(), flags)
86 f.close()
88 f.close()
87 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
89 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
88 if r is None:
90 if r is None:
89 # no real conflict
91 # no real conflict
90 del self._state[dfile]
92 del self._state[dfile]
91 elif not r:
93 elif not r:
92 self.mark(dfile, 'r')
94 self.mark(dfile, 'r')
93 return r
95 return r
94
96
95 def _checkunknownfile(repo, wctx, mctx, f):
97 def _checkunknownfile(repo, wctx, mctx, f):
96 return (not repo.dirstate._ignore(f)
98 return (not repo.dirstate._ignore(f)
97 and os.path.isfile(repo.wjoin(f))
99 and os.path.isfile(repo.wjoin(f))
98 and repo.wopener.audit.check(f)
100 and repo.wopener.audit.check(f)
99 and repo.dirstate.normalize(f) not in repo.dirstate
101 and repo.dirstate.normalize(f) not in repo.dirstate
100 and mctx[f].cmp(wctx[f]))
102 and mctx[f].cmp(wctx[f]))
101
103
102 def _checkunknown(repo, wctx, mctx):
104 def _checkunknown(repo, wctx, mctx):
103 "check for collisions between unknown files and files in mctx"
105 "check for collisions between unknown files and files in mctx"
104
106
105 error = False
107 error = False
106 for f in mctx:
108 for f in mctx:
107 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
109 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
108 error = True
110 error = True
109 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
111 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
110 if error:
112 if error:
111 raise util.Abort(_("untracked files in working directory differ "
113 raise util.Abort(_("untracked files in working directory differ "
112 "from files in requested revision"))
114 "from files in requested revision"))
113
115
114 def _forgetremoved(wctx, mctx, branchmerge):
116 def _forgetremoved(wctx, mctx, branchmerge):
115 """
117 """
116 Forget removed files
118 Forget removed files
117
119
118 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
119 neither the working directory nor the target rev has the file,
121 neither the working directory nor the target rev has the file,
120 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
121 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
122 manifest.
124 manifest.
123
125
124 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
125 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
126 as removed.
128 as removed.
127 """
129 """
128
130
129 actions = []
131 actions = []
130 state = branchmerge and 'r' or 'f'
132 state = branchmerge and 'r' or 'f'
131 for f in wctx.deleted():
133 for f in wctx.deleted():
132 if f not in mctx:
134 if f not in mctx:
133 actions.append((f, state, None, "forget deleted"))
135 actions.append((f, state, None, "forget deleted"))
134
136
135 if not branchmerge:
137 if not branchmerge:
136 for f in wctx.removed():
138 for f in wctx.removed():
137 if f not in mctx:
139 if f not in mctx:
138 actions.append((f, "f", None, "forget removed"))
140 actions.append((f, "f", None, "forget removed"))
139
141
140 return actions
142 return actions
141
143
142 def _checkcollision(repo, wmf, actions, prompts):
144 def _checkcollision(repo, wmf, actions, prompts):
143 # build provisional merged manifest up
145 # build provisional merged manifest up
144 pmmf = set(wmf)
146 pmmf = set(wmf)
145
147
146 def addop(f, args):
148 def addop(f, args):
147 pmmf.add(f)
149 pmmf.add(f)
148 def removeop(f, args):
150 def removeop(f, args):
149 pmmf.discard(f)
151 pmmf.discard(f)
150 def nop(f, args):
152 def nop(f, args):
151 pass
153 pass
152
154
153 def renameop(f, args):
155 def renameop(f, args):
154 f2, fd, flags = args
156 f2, fd, flags = args
155 if f:
157 if f:
156 pmmf.discard(f)
158 pmmf.discard(f)
157 pmmf.add(fd)
159 pmmf.add(fd)
158 def mergeop(f, args):
160 def mergeop(f, args):
159 f2, fd, move = args
161 f2, fd, move = args
160 if move:
162 if move:
161 pmmf.discard(f)
163 pmmf.discard(f)
162 pmmf.add(fd)
164 pmmf.add(fd)
163
165
164 opmap = {
166 opmap = {
165 "a": addop,
167 "a": addop,
166 "d": renameop,
168 "d": renameop,
167 "dr": nop,
169 "dr": nop,
168 "e": nop,
170 "e": nop,
169 "f": addop, # untracked file should be kept in working directory
171 "f": addop, # untracked file should be kept in working directory
170 "g": addop,
172 "g": addop,
171 "m": mergeop,
173 "m": mergeop,
172 "r": removeop,
174 "r": removeop,
173 "rd": nop,
175 "rd": nop,
174 }
176 }
175 for f, m, args, msg in actions:
177 for f, m, args, msg in actions:
176 op = opmap.get(m)
178 op = opmap.get(m)
177 assert op, m
179 assert op, m
178 op(f, args)
180 op(f, args)
179
181
180 opmap = {
182 opmap = {
181 "cd": addop,
183 "cd": addop,
182 "dc": addop,
184 "dc": addop,
183 }
185 }
184 for f, m in prompts:
186 for f, m in prompts:
185 op = opmap.get(m)
187 op = opmap.get(m)
186 assert op, m
188 assert op, m
187 op(f, None)
189 op(f, None)
188
190
189 # check case-folding collision in provisional merged manifest
191 # check case-folding collision in provisional merged manifest
190 foldmap = {}
192 foldmap = {}
191 for f in sorted(pmmf):
193 for f in sorted(pmmf):
192 fold = util.normcase(f)
194 fold = util.normcase(f)
193 if fold in foldmap:
195 if fold in foldmap:
194 raise util.Abort(_("case-folding collision between %s and %s")
196 raise util.Abort(_("case-folding collision between %s and %s")
195 % (f, foldmap[fold]))
197 % (f, foldmap[fold]))
196 foldmap[fold] = f
198 foldmap[fold] = f
197
199
198 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
200 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
199 acceptremote=False):
201 acceptremote=False):
200 """
202 """
201 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
202
204
203 branchmerge and force are as passed in to update
205 branchmerge and force are as passed in to update
204 partial = function to filter file lists
206 partial = function to filter file lists
205 acceptremote = accept the incoming changes without prompting
207 acceptremote = accept the incoming changes without prompting
206 """
208 """
207
209
208 overwrite = force and not branchmerge
210 overwrite = force and not branchmerge
209 actions, copy, movewithdir = [], {}, {}
211 actions, copy, movewithdir = [], {}, {}
210
212
211 followcopies = False
213 followcopies = False
212 if overwrite:
214 if overwrite:
213 pa = wctx
215 pa = wctx
214 elif pa == p2: # backwards
216 elif pa == p2: # backwards
215 pa = wctx.p1()
217 pa = wctx.p1()
216 elif not branchmerge and not wctx.dirty(missing=True):
218 elif not branchmerge and not wctx.dirty(missing=True):
217 pass
219 pass
218 elif pa and repo.ui.configbool("merge", "followcopies", True):
220 elif pa and repo.ui.configbool("merge", "followcopies", True):
219 followcopies = True
221 followcopies = True
220
222
221 # 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
222 [x.manifest() for x in
224 [x.manifest() for x in
223 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
225 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
224
226
225 if followcopies:
227 if followcopies:
226 ret = copies.mergecopies(repo, wctx, p2, pa)
228 ret = copies.mergecopies(repo, wctx, p2, pa)
227 copy, movewithdir, diverge, renamedelete = ret
229 copy, movewithdir, diverge, renamedelete = ret
228 for of, fl in diverge.iteritems():
230 for of, fl in diverge.iteritems():
229 actions.append((of, "dr", (fl,), "divergent renames"))
231 actions.append((of, "dr", (fl,), "divergent renames"))
230 for of, fl in renamedelete.iteritems():
232 for of, fl in renamedelete.iteritems():
231 actions.append((of, "rd", (fl,), "rename and delete"))
233 actions.append((of, "rd", (fl,), "rename and delete"))
232
234
233 repo.ui.note(_("resolving manifests\n"))
235 repo.ui.note(_("resolving manifests\n"))
234 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
236 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
235 % (bool(branchmerge), bool(force), bool(partial)))
237 % (bool(branchmerge), bool(force), bool(partial)))
236 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))
237
239
238 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
240 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
239 copied = set(copy.values())
241 copied = set(copy.values())
240 copied.update(movewithdir.values())
242 copied.update(movewithdir.values())
241
243
242 if '.hgsubstate' in m1:
244 if '.hgsubstate' in m1:
243 # check whether sub state is modified
245 # check whether sub state is modified
244 for s in sorted(wctx.substate):
246 for s in sorted(wctx.substate):
245 if wctx.sub(s).dirty():
247 if wctx.sub(s).dirty():
246 m1['.hgsubstate'] += "+"
248 m1['.hgsubstate'] += "+"
247 break
249 break
248
250
249 aborts, prompts = [], []
251 aborts, prompts = [], []
250 # Compare manifests
252 # Compare manifests
251 fdiff = dicthelpers.diff(m1, m2)
253 fdiff = dicthelpers.diff(m1, m2)
252 flagsdiff = m1.flagsdiff(m2)
254 flagsdiff = m1.flagsdiff(m2)
253 diff12 = dicthelpers.join(fdiff, flagsdiff)
255 diff12 = dicthelpers.join(fdiff, flagsdiff)
254
256
255 for f, (n12, fl12) in diff12.iteritems():
257 for f, (n12, fl12) in diff12.iteritems():
256 if n12:
258 if n12:
257 n1, n2 = n12
259 n1, n2 = n12
258 else: # file contents didn't change, but flags did
260 else: # file contents didn't change, but flags did
259 n1 = n2 = m1.get(f, None)
261 n1 = n2 = m1.get(f, None)
260 if n1 is None:
262 if n1 is None:
261 # 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
262 # means that the file was removed or deleted locally and
264 # means that the file was removed or deleted locally and
263 # removed remotely, but that residual entries remain in flags.
265 # removed remotely, but that residual entries remain in flags.
264 # This can happen in manifests generated by workingctx.
266 # This can happen in manifests generated by workingctx.
265 continue
267 continue
266 if fl12:
268 if fl12:
267 fl1, fl2 = fl12
269 fl1, fl2 = fl12
268 else: # flags didn't change, file contents did
270 else: # flags didn't change, file contents did
269 fl1 = fl2 = m1.flags(f)
271 fl1 = fl2 = m1.flags(f)
270
272
271 if partial and not partial(f):
273 if partial and not partial(f):
272 continue
274 continue
273 if n1 and n2:
275 if n1 and n2:
274 fla = ma.flags(f)
276 fla = ma.flags(f)
275 nol = 'l' not in fl1 + fl2 + fla
277 nol = 'l' not in fl1 + fl2 + fla
276 a = ma.get(f, nullid)
278 a = ma.get(f, nullid)
277 if n2 == a and fl2 == fla:
279 if n2 == a and fl2 == fla:
278 pass # remote unchanged - keep local
280 pass # remote unchanged - keep local
279 elif n1 == a and fl1 == fla: # local unchanged - use remote
281 elif n1 == a and fl1 == fla: # local unchanged - use remote
280 if n1 == n2: # optimization: keep local content
282 if n1 == n2: # optimization: keep local content
281 actions.append((f, "e", (fl2,), "update permissions"))
283 actions.append((f, "e", (fl2,), "update permissions"))
282 else:
284 else:
283 actions.append((f, "g", (fl2,), "remote is newer"))
285 actions.append((f, "g", (fl2,), "remote is newer"))
284 elif nol and n2 == a: # remote only changed 'x'
286 elif nol and n2 == a: # remote only changed 'x'
285 actions.append((f, "e", (fl2,), "update permissions"))
287 actions.append((f, "e", (fl2,), "update permissions"))
286 elif nol and n1 == a: # local only changed 'x'
288 elif nol and n1 == a: # local only changed 'x'
287 actions.append((f, "g", (fl1,), "remote is newer"))
289 actions.append((f, "g", (fl1,), "remote is newer"))
288 else: # both changed something
290 else: # both changed something
289 actions.append((f, "m", (f, f, False), "versions differ"))
291 actions.append((f, "m", (f, f, False), "versions differ"))
290 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
291 pass
293 pass
292 elif n1 and f in movewithdir: # directory rename
294 elif n1 and f in movewithdir: # directory rename
293 f2 = movewithdir[f]
295 f2 = movewithdir[f]
294 actions.append((f, "d", (None, f2, fl1),
296 actions.append((f, "d", (None, f2, fl1),
295 "remote renamed directory to " + f2))
297 "remote renamed directory to " + f2))
296 elif n1 and f in copy:
298 elif n1 and f in copy:
297 f2 = copy[f]
299 f2 = copy[f]
298 actions.append((f, "m", (f2, f, False),
300 actions.append((f, "m", (f2, f, False),
299 "local copied/moved to " + f2))
301 "local copied/moved to " + f2))
300 elif n1 and f in ma: # clean, a different, no remote
302 elif n1 and f in ma: # clean, a different, no remote
301 if n1 != ma[f]:
303 if n1 != ma[f]:
302 prompts.append((f, "cd")) # prompt changed/deleted
304 prompts.append((f, "cd")) # prompt changed/deleted
303 elif n1[20:] == "a": # added, no remote
305 elif n1[20:] == "a": # added, no remote
304 actions.append((f, "f", None, "remote deleted"))
306 actions.append((f, "f", None, "remote deleted"))
305 else:
307 else:
306 actions.append((f, "r", None, "other deleted"))
308 actions.append((f, "r", None, "other deleted"))
307 elif n2 and f in movewithdir:
309 elif n2 and f in movewithdir:
308 f2 = movewithdir[f]
310 f2 = movewithdir[f]
309 actions.append((None, "d", (f, f2, fl2),
311 actions.append((None, "d", (f, f2, fl2),
310 "local renamed directory to " + f2))
312 "local renamed directory to " + f2))
311 elif n2 and f in copy:
313 elif n2 and f in copy:
312 f2 = copy[f]
314 f2 = copy[f]
313 if f2 in m2:
315 if f2 in m2:
314 actions.append((f2, "m", (f, f, False),
316 actions.append((f2, "m", (f, f, False),
315 "remote copied to " + f))
317 "remote copied to " + f))
316 else:
318 else:
317 actions.append((f2, "m", (f, f, True),
319 actions.append((f2, "m", (f, f, True),
318 "remote moved to " + f))
320 "remote moved to " + f))
319 elif n2 and f not in ma:
321 elif n2 and f not in ma:
320 # local unknown, remote created: the logic is described by the
322 # local unknown, remote created: the logic is described by the
321 # following table:
323 # following table:
322 #
324 #
323 # force branchmerge different | action
325 # force branchmerge different | action
324 # n * n | get
326 # n * n | get
325 # n * y | abort
327 # n * y | abort
326 # y n * | get
328 # y n * | get
327 # y y n | get
329 # y y n | get
328 # y y y | merge
330 # y y y | merge
329 #
331 #
330 # Checking whether the files are different is expensive, so we
332 # Checking whether the files are different is expensive, so we
331 # don't do that when we can avoid it.
333 # don't do that when we can avoid it.
332 if force and not branchmerge:
334 if force and not branchmerge:
333 actions.append((f, "g", (fl2,), "remote created"))
335 actions.append((f, "g", (fl2,), "remote created"))
334 else:
336 else:
335 different = _checkunknownfile(repo, wctx, p2, f)
337 different = _checkunknownfile(repo, wctx, p2, f)
336 if force and branchmerge and different:
338 if force and branchmerge and different:
337 actions.append((f, "m", (f, f, False),
339 actions.append((f, "m", (f, f, False),
338 "remote differs from untracked local"))
340 "remote differs from untracked local"))
339 elif not force and different:
341 elif not force and different:
340 aborts.append((f, "ud"))
342 aborts.append((f, "ud"))
341 else:
343 else:
342 actions.append((f, "g", (fl2,), "remote created"))
344 actions.append((f, "g", (fl2,), "remote created"))
343 elif n2 and n2 != ma[f]:
345 elif n2 and n2 != ma[f]:
344 prompts.append((f, "dc")) # prompt deleted/changed
346 prompts.append((f, "dc")) # prompt deleted/changed
345
347
346 for f, m in sorted(aborts):
348 for f, m in sorted(aborts):
347 if m == "ud":
349 if m == "ud":
348 repo.ui.warn(_("%s: untracked file differs\n") % f)
350 repo.ui.warn(_("%s: untracked file differs\n") % f)
349 else: assert False, m
351 else: assert False, m
350 if aborts:
352 if aborts:
351 raise util.Abort(_("untracked files in working directory differ "
353 raise util.Abort(_("untracked files in working directory differ "
352 "from files in requested revision"))
354 "from files in requested revision"))
353
355
354 if not util.checkcase(repo.path):
356 if not util.checkcase(repo.path):
355 # check collision between files only in p2 for clean update
357 # check collision between files only in p2 for clean update
356 if (not branchmerge and
358 if (not branchmerge and
357 (force or not wctx.dirty(missing=True, branch=False))):
359 (force or not wctx.dirty(missing=True, branch=False))):
358 _checkcollision(repo, m2, [], [])
360 _checkcollision(repo, m2, [], [])
359 else:
361 else:
360 _checkcollision(repo, m1, actions, prompts)
362 _checkcollision(repo, m1, actions, prompts)
361
363
362 for f, m in sorted(prompts):
364 for f, m in sorted(prompts):
363 if m == "cd":
365 if m == "cd":
364 if acceptremote:
366 if acceptremote:
365 actions.append((f, "r", None, "remote delete"))
367 actions.append((f, "r", None, "remote delete"))
366 elif repo.ui.promptchoice(
368 elif repo.ui.promptchoice(
367 _("local changed %s which remote deleted\n"
369 _("local changed %s which remote deleted\n"
368 "use (c)hanged version or (d)elete?"
370 "use (c)hanged version or (d)elete?"
369 "$$ &Changed $$ &Delete") % f, 0):
371 "$$ &Changed $$ &Delete") % f, 0):
370 actions.append((f, "r", None, "prompt delete"))
372 actions.append((f, "r", None, "prompt delete"))
371 else:
373 else:
372 actions.append((f, "a", None, "prompt keep"))
374 actions.append((f, "a", None, "prompt keep"))
373 elif m == "dc":
375 elif m == "dc":
374 if acceptremote:
376 if acceptremote:
375 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
377 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
376 elif repo.ui.promptchoice(
378 elif repo.ui.promptchoice(
377 _("remote changed %s which local deleted\n"
379 _("remote changed %s which local deleted\n"
378 "use (c)hanged version or leave (d)eleted?"
380 "use (c)hanged version or leave (d)eleted?"
379 "$$ &Changed $$ &Deleted") % f, 0) == 0:
381 "$$ &Changed $$ &Deleted") % f, 0) == 0:
380 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
382 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
381 else: assert False, m
383 else: assert False, m
382 return actions
384 return actions
383
385
384 def actionkey(a):
386 def actionkey(a):
385 return a[1] == "r" and -1 or 0, a
387 return a[1] == "r" and -1 or 0, a
386
388
387 def getremove(repo, mctx, overwrite, args):
389 def getremove(repo, mctx, overwrite, args):
388 """apply usually-non-interactive updates to the working directory
390 """apply usually-non-interactive updates to the working directory
389
391
390 mctx is the context to be merged into the working copy
392 mctx is the context to be merged into the working copy
391
393
392 yields tuples for progress updates
394 yields tuples for progress updates
393 """
395 """
394 verbose = repo.ui.verbose
396 verbose = repo.ui.verbose
395 unlink = util.unlinkpath
397 unlink = util.unlinkpath
396 wjoin = repo.wjoin
398 wjoin = repo.wjoin
397 fctx = mctx.filectx
399 fctx = mctx.filectx
398 wwrite = repo.wwrite
400 wwrite = repo.wwrite
399 audit = repo.wopener.audit
401 audit = repo.wopener.audit
400 i = 0
402 i = 0
401 for arg in args:
403 for arg in args:
402 f = arg[0]
404 f = arg[0]
403 if arg[1] == 'r':
405 if arg[1] == 'r':
404 if verbose:
406 if verbose:
405 repo.ui.note(_("removing %s\n") % f)
407 repo.ui.note(_("removing %s\n") % f)
406 audit(f)
408 audit(f)
407 try:
409 try:
408 unlink(wjoin(f), ignoremissing=True)
410 unlink(wjoin(f), ignoremissing=True)
409 except OSError, inst:
411 except OSError, inst:
410 repo.ui.warn(_("update failed to remove %s: %s!\n") %
412 repo.ui.warn(_("update failed to remove %s: %s!\n") %
411 (f, inst.strerror))
413 (f, inst.strerror))
412 else:
414 else:
413 if verbose:
415 if verbose:
414 repo.ui.note(_("getting %s\n") % f)
416 repo.ui.note(_("getting %s\n") % f)
415 wwrite(f, fctx(f).data(), arg[2][0])
417 wwrite(f, fctx(f).data(), arg[2][0])
416 if i == 100:
418 if i == 100:
417 yield i, f
419 yield i, f
418 i = 0
420 i = 0
419 i += 1
421 i += 1
420 if i > 0:
422 if i > 0:
421 yield i, f
423 yield i, f
422
424
423 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
425 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
424 """apply the merge action list to the working directory
426 """apply the merge action list to the working directory
425
427
426 wctx is the working copy context
428 wctx is the working copy context
427 mctx is the context to be merged into the working copy
429 mctx is the context to be merged into the working copy
428 actx is the context of the common ancestor
430 actx is the context of the common ancestor
429
431
430 Return a tuple of counts (updated, merged, removed, unresolved) that
432 Return a tuple of counts (updated, merged, removed, unresolved) that
431 describes how many files were affected by the update.
433 describes how many files were affected by the update.
432 """
434 """
433
435
434 updated, merged, removed, unresolved = 0, 0, 0, 0
436 updated, merged, removed, unresolved = 0, 0, 0, 0
435 ms = mergestate(repo)
437 ms = mergestate(repo)
436 ms.reset(wctx.p1().node())
438 ms.reset(wctx.p1().node())
437 moves = []
439 moves = []
438 actions.sort(key=actionkey)
440 actions.sort(key=actionkey)
439
441
440 # prescan for merges
442 # prescan for merges
441 for a in actions:
443 for a in actions:
442 f, m, args, msg = a
444 f, m, args, msg = a
443 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
445 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
444 if m == "m": # merge
446 if m == "m": # merge
445 f2, fd, move = args
447 f2, fd, move = args
446 if fd == '.hgsubstate': # merged internally
448 if fd == '.hgsubstate': # merged internally
447 continue
449 continue
448 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))
449 fcl = wctx[f]
451 fcl = wctx[f]
450 fco = mctx[f2]
452 fco = mctx[f2]
451 if mctx == actx: # backwards, use working dir parent as ancestor
453 if mctx == actx: # backwards, use working dir parent as ancestor
452 if fcl.parents():
454 if fcl.parents():
453 fca = fcl.p1()
455 fca = fcl.p1()
454 else:
456 else:
455 fca = repo.filectx(f, fileid=nullrev)
457 fca = repo.filectx(f, fileid=nullrev)
456 else:
458 else:
457 fca = fcl.ancestor(fco, actx)
459 fca = fcl.ancestor(fco, actx)
458 if not fca:
460 if not fca:
459 fca = repo.filectx(f, fileid=nullrev)
461 fca = repo.filectx(f, fileid=nullrev)
460 ms.add(fcl, fco, fca, fd)
462 ms.add(fcl, fco, fca, fd)
461 if f != fd and move:
463 if f != fd and move:
462 moves.append(f)
464 moves.append(f)
463
465
464 audit = repo.wopener.audit
466 audit = repo.wopener.audit
465
467
466 # remove renamed files after safely stored
468 # remove renamed files after safely stored
467 for f in moves:
469 for f in moves:
468 if os.path.lexists(repo.wjoin(f)):
470 if os.path.lexists(repo.wjoin(f)):
469 repo.ui.debug("removing %s\n" % f)
471 repo.ui.debug("removing %s\n" % f)
470 audit(f)
472 audit(f)
471 util.unlinkpath(repo.wjoin(f))
473 util.unlinkpath(repo.wjoin(f))
472
474
473 numupdates = len(actions)
475 numupdates = len(actions)
474 workeractions = [a for a in actions if a[1] in 'gr']
476 workeractions = [a for a in actions if a[1] in 'gr']
475 updateactions = [a for a in workeractions if a[1] == 'g']
477 updateactions = [a for a in workeractions if a[1] == 'g']
476 updated = len(updateactions)
478 updated = len(updateactions)
477 removeactions = [a for a in workeractions if a[1] == 'r']
479 removeactions = [a for a in workeractions if a[1] == 'r']
478 removed = len(removeactions)
480 removed = len(removeactions)
479 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']
480
482
481 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
483 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
482 if hgsub and hgsub[0] == 'r':
484 if hgsub and hgsub[0] == 'r':
483 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
485 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
484
486
485 z = 0
487 z = 0
486 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
488 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
487 removeactions)
489 removeactions)
488 for i, item in prog:
490 for i, item in prog:
489 z += i
491 z += i
490 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
492 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
491 unit=_('files'))
493 unit=_('files'))
492 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
494 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
493 updateactions)
495 updateactions)
494 for i, item in prog:
496 for i, item in prog:
495 z += i
497 z += i
496 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
498 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
497 unit=_('files'))
499 unit=_('files'))
498
500
499 if hgsub and hgsub[0] == 'g':
501 if hgsub and hgsub[0] == 'g':
500 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
502 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
501
503
502 _updating = _('updating')
504 _updating = _('updating')
503 _files = _('files')
505 _files = _('files')
504 progress = repo.ui.progress
506 progress = repo.ui.progress
505
507
506 for i, a in enumerate(actions):
508 for i, a in enumerate(actions):
507 f, m, args, msg = a
509 f, m, args, msg = a
508 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
510 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
509 if m == "m": # merge
511 if m == "m": # merge
510 f2, fd, move = args
512 f2, fd, move = args
511 if fd == '.hgsubstate': # subrepo states need updating
513 if fd == '.hgsubstate': # subrepo states need updating
512 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
514 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
513 overwrite)
515 overwrite)
514 continue
516 continue
515 audit(fd)
517 audit(fd)
516 r = ms.resolve(fd, wctx, mctx)
518 r = ms.resolve(fd, wctx, mctx)
517 if r is not None and r > 0:
519 if r is not None and r > 0:
518 unresolved += 1
520 unresolved += 1
519 else:
521 else:
520 if r is None:
522 if r is None:
521 updated += 1
523 updated += 1
522 else:
524 else:
523 merged += 1
525 merged += 1
524 elif m == "d": # directory rename
526 elif m == "d": # directory rename
525 f2, fd, flags = args
527 f2, fd, flags = args
526 if f:
528 if f:
527 repo.ui.note(_("moving %s to %s\n") % (f, fd))
529 repo.ui.note(_("moving %s to %s\n") % (f, fd))
528 audit(f)
530 audit(f)
529 repo.wwrite(fd, wctx.filectx(f).data(), flags)
531 repo.wwrite(fd, wctx.filectx(f).data(), flags)
530 util.unlinkpath(repo.wjoin(f))
532 util.unlinkpath(repo.wjoin(f))
531 if f2:
533 if f2:
532 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
534 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
533 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
535 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
534 updated += 1
536 updated += 1
535 elif m == "dr": # divergent renames
537 elif m == "dr": # divergent renames
536 fl, = args
538 fl, = args
537 repo.ui.warn(_("note: possible conflict - %s was renamed "
539 repo.ui.warn(_("note: possible conflict - %s was renamed "
538 "multiple times to:\n") % f)
540 "multiple times to:\n") % f)
539 for nf in fl:
541 for nf in fl:
540 repo.ui.warn(" %s\n" % nf)
542 repo.ui.warn(" %s\n" % nf)
541 elif m == "rd": # rename and delete
543 elif m == "rd": # rename and delete
542 fl, = args
544 fl, = args
543 repo.ui.warn(_("note: possible conflict - %s was deleted "
545 repo.ui.warn(_("note: possible conflict - %s was deleted "
544 "and renamed to:\n") % f)
546 "and renamed to:\n") % f)
545 for nf in fl:
547 for nf in fl:
546 repo.ui.warn(" %s\n" % nf)
548 repo.ui.warn(" %s\n" % nf)
547 elif m == "e": # exec
549 elif m == "e": # exec
548 flags, = args
550 flags, = args
549 audit(f)
551 audit(f)
550 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
552 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
551 updated += 1
553 updated += 1
552 ms.commit()
554 ms.commit()
553 progress(_updating, None, total=numupdates, unit=_files)
555 progress(_updating, None, total=numupdates, unit=_files)
554
556
555 return updated, merged, removed, unresolved
557 return updated, merged, removed, unresolved
556
558
557 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
559 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
558 acceptremote=False):
560 acceptremote=False):
559 "Calculate the actions needed to merge mctx into tctx"
561 "Calculate the actions needed to merge mctx into tctx"
560 actions = []
562 actions = []
561 actions += manifestmerge(repo, tctx, mctx,
563 actions += manifestmerge(repo, tctx, mctx,
562 ancestor,
564 ancestor,
563 branchmerge, force,
565 branchmerge, force,
564 partial, acceptremote)
566 partial, acceptremote)
565 if tctx.rev() is None:
567 if tctx.rev() is None:
566 actions += _forgetremoved(tctx, mctx, branchmerge)
568 actions += _forgetremoved(tctx, mctx, branchmerge)
567 return actions
569 return actions
568
570
569 def recordupdates(repo, actions, branchmerge):
571 def recordupdates(repo, actions, branchmerge):
570 "record merge actions to the dirstate"
572 "record merge actions to the dirstate"
571
573
572 for a in actions:
574 for a in actions:
573 f, m, args, msg = a
575 f, m, args, msg = a
574 if m == "r": # remove
576 if m == "r": # remove
575 if branchmerge:
577 if branchmerge:
576 repo.dirstate.remove(f)
578 repo.dirstate.remove(f)
577 else:
579 else:
578 repo.dirstate.drop(f)
580 repo.dirstate.drop(f)
579 elif m == "a": # re-add
581 elif m == "a": # re-add
580 if not branchmerge:
582 if not branchmerge:
581 repo.dirstate.add(f)
583 repo.dirstate.add(f)
582 elif m == "f": # forget
584 elif m == "f": # forget
583 repo.dirstate.drop(f)
585 repo.dirstate.drop(f)
584 elif m == "e": # exec change
586 elif m == "e": # exec change
585 repo.dirstate.normallookup(f)
587 repo.dirstate.normallookup(f)
586 elif m == "g": # get
588 elif m == "g": # get
587 if branchmerge:
589 if branchmerge:
588 repo.dirstate.otherparent(f)
590 repo.dirstate.otherparent(f)
589 else:
591 else:
590 repo.dirstate.normal(f)
592 repo.dirstate.normal(f)
591 elif m == "m": # merge
593 elif m == "m": # merge
592 f2, fd, move = args
594 f2, fd, move = args
593 if branchmerge:
595 if branchmerge:
594 # We've done a branch merge, mark this file as merged
596 # We've done a branch merge, mark this file as merged
595 # so that we properly record the merger later
597 # so that we properly record the merger later
596 repo.dirstate.merge(fd)
598 repo.dirstate.merge(fd)
597 if f != f2: # copy/rename
599 if f != f2: # copy/rename
598 if move:
600 if move:
599 repo.dirstate.remove(f)
601 repo.dirstate.remove(f)
600 if f != fd:
602 if f != fd:
601 repo.dirstate.copy(f, fd)
603 repo.dirstate.copy(f, fd)
602 else:
604 else:
603 repo.dirstate.copy(f2, fd)
605 repo.dirstate.copy(f2, fd)
604 else:
606 else:
605 # We've update-merged a locally modified file, so
607 # We've update-merged a locally modified file, so
606 # we set the dirstate to emulate a normal checkout
608 # we set the dirstate to emulate a normal checkout
607 # of that file some time in the past. Thus our
609 # of that file some time in the past. Thus our
608 # merge will appear as a normal local file
610 # merge will appear as a normal local file
609 # modification.
611 # modification.
610 if f2 == fd: # file not locally copied/moved
612 if f2 == fd: # file not locally copied/moved
611 repo.dirstate.normallookup(fd)
613 repo.dirstate.normallookup(fd)
612 if move:
614 if move:
613 repo.dirstate.drop(f)
615 repo.dirstate.drop(f)
614 elif m == "d": # directory rename
616 elif m == "d": # directory rename
615 f2, fd, flag = args
617 f2, fd, flag = args
616 if not f2 and f not in repo.dirstate:
618 if not f2 and f not in repo.dirstate:
617 # untracked file moved
619 # untracked file moved
618 continue
620 continue
619 if branchmerge:
621 if branchmerge:
620 repo.dirstate.add(fd)
622 repo.dirstate.add(fd)
621 if f:
623 if f:
622 repo.dirstate.remove(f)
624 repo.dirstate.remove(f)
623 repo.dirstate.copy(f, fd)
625 repo.dirstate.copy(f, fd)
624 if f2:
626 if f2:
625 repo.dirstate.copy(f2, fd)
627 repo.dirstate.copy(f2, fd)
626 else:
628 else:
627 repo.dirstate.normal(fd)
629 repo.dirstate.normal(fd)
628 if f:
630 if f:
629 repo.dirstate.drop(f)
631 repo.dirstate.drop(f)
630
632
631 def update(repo, node, branchmerge, force, partial, ancestor=None,
633 def update(repo, node, branchmerge, force, partial, ancestor=None,
632 mergeancestor=False):
634 mergeancestor=False):
633 """
635 """
634 Perform a merge between the working directory and the given node
636 Perform a merge between the working directory and the given node
635
637
636 node = the node to update to, or None if unspecified
638 node = the node to update to, or None if unspecified
637 branchmerge = whether to merge between branches
639 branchmerge = whether to merge between branches
638 force = whether to force branch merging or file overwriting
640 force = whether to force branch merging or file overwriting
639 partial = a function to filter file lists (dirstate not updated)
641 partial = a function to filter file lists (dirstate not updated)
640 mergeancestor = whether it is merging with an ancestor. If true,
642 mergeancestor = whether it is merging with an ancestor. If true,
641 we should accept the incoming changes for any prompts that occur.
643 we should accept the incoming changes for any prompts that occur.
642 If false, merging with an ancestor (fast-forward) is only allowed
644 If false, merging with an ancestor (fast-forward) is only allowed
643 between different named branches. This flag is used by rebase extension
645 between different named branches. This flag is used by rebase extension
644 as a temporary fix and should be avoided in general.
646 as a temporary fix and should be avoided in general.
645
647
646 The table below shows all the behaviors of the update command
648 The table below shows all the behaviors of the update command
647 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
648 is dirty, whether a revision is specified, and the relationship of
650 is dirty, whether a revision is specified, and the relationship of
649 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
650 branch, or on another named branch).
652 branch, or on another named branch).
651
653
652 This logic is tested by test-update-branches.t.
654 This logic is tested by test-update-branches.t.
653
655
654 -c -C dirty rev | linear same cross
656 -c -C dirty rev | linear same cross
655 n n n n | ok (1) x
657 n n n n | ok (1) x
656 n n n y | ok ok ok
658 n n n y | ok ok ok
657 n n y * | merge (2) (2)
659 n n y * | merge (2) (2)
658 n y * * | --- discard ---
660 n y * * | --- discard ---
659 y n y * | --- (3) ---
661 y n y * | --- (3) ---
660 y n n * | --- ok ---
662 y n n * | --- ok ---
661 y y * * | --- (4) ---
663 y y * * | --- (4) ---
662
664
663 x = can't happen
665 x = can't happen
664 * = don't-care
666 * = don't-care
665 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
667 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
666 2 = abort: crosses branches (use 'hg merge' to merge or
668 2 = abort: crosses branches (use 'hg merge' to merge or
667 use 'hg update -C' to discard changes)
669 use 'hg update -C' to discard changes)
668 3 = abort: uncommitted local changes
670 3 = abort: uncommitted local changes
669 4 = incompatible options (checked in commands.py)
671 4 = incompatible options (checked in commands.py)
670
672
671 Return the same tuple as applyupdates().
673 Return the same tuple as applyupdates().
672 """
674 """
673
675
674 onode = node
676 onode = node
675 wlock = repo.wlock()
677 wlock = repo.wlock()
676 try:
678 try:
677 wc = repo[None]
679 wc = repo[None]
678 if node is None:
680 if node is None:
679 # tip of current branch
681 # tip of current branch
680 try:
682 try:
681 node = repo.branchtip(wc.branch())
683 node = repo.branchtip(wc.branch())
682 except error.RepoLookupError:
684 except error.RepoLookupError:
683 if wc.branch() == "default": # no default branch!
685 if wc.branch() == "default": # no default branch!
684 node = repo.lookup("tip") # update to tip
686 node = repo.lookup("tip") # update to tip
685 else:
687 else:
686 raise util.Abort(_("branch %s not found") % wc.branch())
688 raise util.Abort(_("branch %s not found") % wc.branch())
687 overwrite = force and not branchmerge
689 overwrite = force and not branchmerge
688 pl = wc.parents()
690 pl = wc.parents()
689 p1, p2 = pl[0], repo[node]
691 p1, p2 = pl[0], repo[node]
690 if ancestor:
692 if ancestor:
691 pa = repo[ancestor]
693 pa = repo[ancestor]
692 else:
694 else:
693 pa = p1.ancestor(p2)
695 pa = p1.ancestor(p2)
694
696
695 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
697 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
696
698
697 ### check phase
699 ### check phase
698 if not overwrite and len(pl) > 1:
700 if not overwrite and len(pl) > 1:
699 raise util.Abort(_("outstanding uncommitted merges"))
701 raise util.Abort(_("outstanding uncommitted merges"))
700 if branchmerge:
702 if branchmerge:
701 if pa == p2:
703 if pa == p2:
702 raise util.Abort(_("merging with a working directory ancestor"
704 raise util.Abort(_("merging with a working directory ancestor"
703 " has no effect"))
705 " has no effect"))
704 elif pa == p1:
706 elif pa == p1:
705 if not mergeancestor and p1.branch() == p2.branch():
707 if not mergeancestor and p1.branch() == p2.branch():
706 raise util.Abort(_("nothing to merge"),
708 raise util.Abort(_("nothing to merge"),
707 hint=_("use 'hg update' "
709 hint=_("use 'hg update' "
708 "or check 'hg heads'"))
710 "or check 'hg heads'"))
709 if not force and (wc.files() or wc.deleted()):
711 if not force and (wc.files() or wc.deleted()):
710 raise util.Abort(_("outstanding uncommitted changes"),
712 raise util.Abort(_("outstanding uncommitted changes"),
711 hint=_("use 'hg status' to list changes"))
713 hint=_("use 'hg status' to list changes"))
712 for s in sorted(wc.substate):
714 for s in sorted(wc.substate):
713 if wc.sub(s).dirty():
715 if wc.sub(s).dirty():
714 raise util.Abort(_("outstanding uncommitted changes in "
716 raise util.Abort(_("outstanding uncommitted changes in "
715 "subrepository '%s'") % s)
717 "subrepository '%s'") % s)
716
718
717 elif not overwrite:
719 elif not overwrite:
718 if pa not in (p1, p2): # nolinear
720 if pa not in (p1, p2): # nolinear
719 dirty = wc.dirty(missing=True)
721 dirty = wc.dirty(missing=True)
720 if dirty or onode is None:
722 if dirty or onode is None:
721 # Branching is a bit strange to ensure we do the minimal
723 # Branching is a bit strange to ensure we do the minimal
722 # amount of call to obsolete.background.
724 # amount of call to obsolete.background.
723 foreground = obsolete.foreground(repo, [p1.node()])
725 foreground = obsolete.foreground(repo, [p1.node()])
724 # note: the <node> variable contains a random identifier
726 # note: the <node> variable contains a random identifier
725 if repo[node].node() in foreground:
727 if repo[node].node() in foreground:
726 pa = p1 # allow updating to successors
728 pa = p1 # allow updating to successors
727 elif dirty:
729 elif dirty:
728 msg = _("crosses branches (merge branches or use"
730 msg = _("crosses branches (merge branches or use"
729 " --clean to discard changes)")
731 " --clean to discard changes)")
730 raise util.Abort(msg)
732 raise util.Abort(msg)
731 else: # node is none
733 else: # node is none
732 msg = _("crosses branches (merge branches or update"
734 msg = _("crosses branches (merge branches or update"
733 " --check to force update)")
735 " --check to force update)")
734 raise util.Abort(msg)
736 raise util.Abort(msg)
735 else:
737 else:
736 # Allow jumping branches if clean and specific rev given
738 # Allow jumping branches if clean and specific rev given
737 pa = p1
739 pa = p1
738
740
739 ### calculate phase
741 ### calculate phase
740 actions = calculateupdates(repo, wc, p2, pa,
742 actions = calculateupdates(repo, wc, p2, pa,
741 branchmerge, force, partial, mergeancestor)
743 branchmerge, force, partial, mergeancestor)
742
744
743 ### apply phase
745 ### apply phase
744 if not branchmerge: # just jump to the new rev
746 if not branchmerge: # just jump to the new rev
745 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
747 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
746 if not partial:
748 if not partial:
747 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
749 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
748
750
749 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
751 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
750
752
751 if not partial:
753 if not partial:
752 repo.setparents(fp1, fp2)
754 repo.setparents(fp1, fp2)
753 recordupdates(repo, actions, branchmerge)
755 recordupdates(repo, actions, branchmerge)
754 if not branchmerge:
756 if not branchmerge:
755 repo.dirstate.setbranch(p2.branch())
757 repo.dirstate.setbranch(p2.branch())
756 finally:
758 finally:
757 wlock.release()
759 wlock.release()
758
760
759 if not partial:
761 if not partial:
760 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
762 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
761 return stats
763 return stats
General Comments 0
You need to be logged in to leave comments. Login now