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