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