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