##// END OF EJS Templates
icasefs: make case-folding collision detection as rename aware (issue3370)...
FUJIWARA Katsunori -
r16478:cbf2ea2f stable
parent child Browse files
Show More
@@ -1,595 +1,607 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import scmutil, util, filemerge, copies, subrepo
10 import scmutil, util, filemerge, copies, subrepo
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._dirty = False
17 self._dirty = False
18 self._read()
18 self._read()
19 def reset(self, node=None):
19 def reset(self, node=None):
20 self._state = {}
20 self._state = {}
21 if node:
21 if node:
22 self._local = node
22 self._local = node
23 shutil.rmtree(self._repo.join("merge"), True)
23 shutil.rmtree(self._repo.join("merge"), True)
24 self._dirty = False
24 self._dirty = False
25 def _read(self):
25 def _read(self):
26 self._state = {}
26 self._state = {}
27 try:
27 try:
28 f = self._repo.opener("merge/state")
28 f = self._repo.opener("merge/state")
29 for i, l in enumerate(f):
29 for i, l in enumerate(f):
30 if i == 0:
30 if i == 0:
31 self._local = bin(l[:-1])
31 self._local = bin(l[:-1])
32 else:
32 else:
33 bits = l[:-1].split("\0")
33 bits = l[:-1].split("\0")
34 self._state[bits[0]] = bits[1:]
34 self._state[bits[0]] = bits[1:]
35 f.close()
35 f.close()
36 except IOError, err:
36 except IOError, err:
37 if err.errno != errno.ENOENT:
37 if err.errno != errno.ENOENT:
38 raise
38 raise
39 self._dirty = False
39 self._dirty = False
40 def commit(self):
40 def commit(self):
41 if self._dirty:
41 if self._dirty:
42 f = self._repo.opener("merge/state", "w")
42 f = self._repo.opener("merge/state", "w")
43 f.write(hex(self._local) + "\n")
43 f.write(hex(self._local) + "\n")
44 for d, v in self._state.iteritems():
44 for d, v in self._state.iteritems():
45 f.write("\0".join([d] + v) + "\n")
45 f.write("\0".join([d] + v) + "\n")
46 f.close()
46 f.close()
47 self._dirty = False
47 self._dirty = False
48 def add(self, fcl, fco, fca, fd, flags):
48 def add(self, fcl, fco, fca, fd, flags):
49 hash = util.sha1(fcl.path()).hexdigest()
49 hash = util.sha1(fcl.path()).hexdigest()
50 self._repo.opener.write("merge/" + hash, fcl.data())
50 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 hex(fca.filenode()), fco.path(), flags]
52 hex(fca.filenode()), fco.path(), flags]
53 self._dirty = True
53 self._dirty = True
54 def __contains__(self, dfile):
54 def __contains__(self, dfile):
55 return dfile in self._state
55 return dfile in self._state
56 def __getitem__(self, dfile):
56 def __getitem__(self, dfile):
57 return self._state[dfile][0]
57 return self._state[dfile][0]
58 def __iter__(self):
58 def __iter__(self):
59 l = self._state.keys()
59 l = self._state.keys()
60 l.sort()
60 l.sort()
61 for f in l:
61 for f in l:
62 yield f
62 yield f
63 def mark(self, dfile, state):
63 def mark(self, dfile, state):
64 self._state[dfile][0] = state
64 self._state[dfile][0] = state
65 self._dirty = True
65 self._dirty = True
66 def resolve(self, dfile, wctx, octx):
66 def resolve(self, dfile, wctx, octx):
67 if self[dfile] == 'r':
67 if self[dfile] == 'r':
68 return 0
68 return 0
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 f = self._repo.opener("merge/" + hash)
70 f = self._repo.opener("merge/" + hash)
71 self._repo.wwrite(dfile, f.read(), flags)
71 self._repo.wwrite(dfile, f.read(), flags)
72 f.close()
72 f.close()
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 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 if r is None:
77 if r is None:
78 # no real conflict
78 # no real conflict
79 del self._state[dfile]
79 del self._state[dfile]
80 elif not r:
80 elif not r:
81 self.mark(dfile, 'r')
81 self.mark(dfile, 'r')
82 return r
82 return r
83
83
84 def _checkunknownfile(repo, wctx, mctx, f):
84 def _checkunknownfile(repo, wctx, mctx, f):
85 return (not repo.dirstate._ignore(f)
85 return (not repo.dirstate._ignore(f)
86 and os.path.exists(repo.wjoin(f))
86 and os.path.exists(repo.wjoin(f))
87 and repo.dirstate.normalize(f) not in repo.dirstate
87 and repo.dirstate.normalize(f) not in repo.dirstate
88 and mctx[f].cmp(wctx[f]))
88 and mctx[f].cmp(wctx[f]))
89
89
90 def _checkunknown(repo, wctx, mctx):
90 def _checkunknown(repo, wctx, mctx):
91 "check for collisions between unknown files and files in mctx"
91 "check for collisions between unknown files and files in mctx"
92
92
93 error = False
93 error = False
94 for f in mctx:
94 for f in mctx:
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
96 error = True
96 error = True
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
98 if error:
98 if error:
99 raise util.Abort(_("untracked files in working directory differ "
99 raise util.Abort(_("untracked files in working directory differ "
100 "from files in requested revision"))
100 "from files in requested revision"))
101
101
102 def _checkcollision(mctx, wctx):
102 def _checkcollision(mctx, wctx):
103 "check for case folding collisions in the destination context"
103 "check for case folding collisions in the destination context"
104 folded = {}
104 folded = {}
105 for fn in mctx:
105 for fn in mctx:
106 fold = util.normcase(fn)
106 fold = util.normcase(fn)
107 if fold in folded:
107 if fold in folded:
108 raise util.Abort(_("case-folding collision between %s and %s")
108 raise util.Abort(_("case-folding collision between %s and %s")
109 % (fn, folded[fold]))
109 % (fn, folded[fold]))
110 folded[fold] = fn
110 folded[fold] = fn
111
111
112 if wctx:
112 if wctx:
113 # class to delay looking up copy mapping
114 class pathcopies(object):
115 @util.propertycache
116 def map(self):
117 # {dst@mctx: src@wctx} copy mapping
118 return copies.pathcopies(wctx, mctx)
119 pc = pathcopies()
120
113 for fn in wctx:
121 for fn in wctx:
114 fold = util.normcase(fn)
122 fold = util.normcase(fn)
115 mfn = folded.get(fold, None)
123 mfn = folded.get(fold, None)
116 if mfn and (mfn != fn):
124 if mfn and mfn != fn and pc.map.get(mfn) != fn:
117 raise util.Abort(_("case-folding collision between %s and %s")
125 raise util.Abort(_("case-folding collision between %s and %s")
118 % (mfn, fn))
126 % (mfn, fn))
119
127
120 def _forgetremoved(wctx, mctx, branchmerge):
128 def _forgetremoved(wctx, mctx, branchmerge):
121 """
129 """
122 Forget removed files
130 Forget removed files
123
131
124 If we're jumping between revisions (as opposed to merging), and if
132 If we're jumping between revisions (as opposed to merging), and if
125 neither the working directory nor the target rev has the file,
133 neither the working directory nor the target rev has the file,
126 then we need to remove it from the dirstate, to prevent the
134 then we need to remove it from the dirstate, to prevent the
127 dirstate from listing the file when it is no longer in the
135 dirstate from listing the file when it is no longer in the
128 manifest.
136 manifest.
129
137
130 If we're merging, and the other revision has removed a file
138 If we're merging, and the other revision has removed a file
131 that is not present in the working directory, we need to mark it
139 that is not present in the working directory, we need to mark it
132 as removed.
140 as removed.
133 """
141 """
134
142
135 action = []
143 action = []
136 state = branchmerge and 'r' or 'f'
144 state = branchmerge and 'r' or 'f'
137 for f in wctx.deleted():
145 for f in wctx.deleted():
138 if f not in mctx:
146 if f not in mctx:
139 action.append((f, state))
147 action.append((f, state))
140
148
141 if not branchmerge:
149 if not branchmerge:
142 for f in wctx.removed():
150 for f in wctx.removed():
143 if f not in mctx:
151 if f not in mctx:
144 action.append((f, "f"))
152 action.append((f, "f"))
145
153
146 return action
154 return action
147
155
148 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
156 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
149 """
157 """
150 Merge p1 and p2 with ancestor pa and generate merge action list
158 Merge p1 and p2 with ancestor pa and generate merge action list
151
159
152 overwrite = whether we clobber working files
160 overwrite = whether we clobber working files
153 partial = function to filter file lists
161 partial = function to filter file lists
154 """
162 """
155
163
156 def fmerge(f, f2, fa):
164 def fmerge(f, f2, fa):
157 """merge flags"""
165 """merge flags"""
158 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
166 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
159 if m == n: # flags agree
167 if m == n: # flags agree
160 return m # unchanged
168 return m # unchanged
161 if m and n and not a: # flags set, don't agree, differ from parent
169 if m and n and not a: # flags set, don't agree, differ from parent
162 r = repo.ui.promptchoice(
170 r = repo.ui.promptchoice(
163 _(" conflicting flags for %s\n"
171 _(" conflicting flags for %s\n"
164 "(n)one, e(x)ec or sym(l)ink?") % f,
172 "(n)one, e(x)ec or sym(l)ink?") % f,
165 (_("&None"), _("E&xec"), _("Sym&link")), 0)
173 (_("&None"), _("E&xec"), _("Sym&link")), 0)
166 if r == 1:
174 if r == 1:
167 return "x" # Exec
175 return "x" # Exec
168 if r == 2:
176 if r == 2:
169 return "l" # Symlink
177 return "l" # Symlink
170 return ""
178 return ""
171 if m and m != a: # changed from a to m
179 if m and m != a: # changed from a to m
172 return m
180 return m
173 if n and n != a: # changed from a to n
181 if n and n != a: # changed from a to n
174 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
182 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
175 # can't automatically merge symlink flag when there
183 # can't automatically merge symlink flag when there
176 # are file-level conflicts here, let filemerge take
184 # are file-level conflicts here, let filemerge take
177 # care of it
185 # care of it
178 return m
186 return m
179 return n
187 return n
180 return '' # flag was cleared
188 return '' # flag was cleared
181
189
182 def act(msg, m, f, *args):
190 def act(msg, m, f, *args):
183 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
191 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
184 action.append((f, m) + args)
192 action.append((f, m) + args)
185
193
186 action, copy = [], {}
194 action, copy = [], {}
187
195
188 if overwrite:
196 if overwrite:
189 pa = p1
197 pa = p1
190 elif pa == p2: # backwards
198 elif pa == p2: # backwards
191 pa = p1.p1()
199 pa = p1.p1()
192 elif pa and repo.ui.configbool("merge", "followcopies", True):
200 elif pa and repo.ui.configbool("merge", "followcopies", True):
193 copy, diverge = copies.mergecopies(repo, p1, p2, pa)
201 copy, diverge = copies.mergecopies(repo, p1, p2, pa)
194 for of, fl in diverge.iteritems():
202 for of, fl in diverge.iteritems():
195 act("divergent renames", "dr", of, fl)
203 act("divergent renames", "dr", of, fl)
196
204
197 repo.ui.note(_("resolving manifests\n"))
205 repo.ui.note(_("resolving manifests\n"))
198 repo.ui.debug(" overwrite: %s, partial: %s\n"
206 repo.ui.debug(" overwrite: %s, partial: %s\n"
199 % (bool(overwrite), bool(partial)))
207 % (bool(overwrite), bool(partial)))
200 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
208 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
201
209
202 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
210 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
203 copied = set(copy.values())
211 copied = set(copy.values())
204
212
205 if '.hgsubstate' in m1:
213 if '.hgsubstate' in m1:
206 # check whether sub state is modified
214 # check whether sub state is modified
207 for s in p1.substate:
215 for s in p1.substate:
208 if p1.sub(s).dirty():
216 if p1.sub(s).dirty():
209 m1['.hgsubstate'] += "+"
217 m1['.hgsubstate'] += "+"
210 break
218 break
211
219
212 # Compare manifests
220 # Compare manifests
213 for f, n in m1.iteritems():
221 for f, n in m1.iteritems():
214 if partial and not partial(f):
222 if partial and not partial(f):
215 continue
223 continue
216 if f in m2:
224 if f in m2:
217 rflags = fmerge(f, f, f)
225 rflags = fmerge(f, f, f)
218 a = ma.get(f, nullid)
226 a = ma.get(f, nullid)
219 if n == m2[f] or m2[f] == a: # same or local newer
227 if n == m2[f] or m2[f] == a: # same or local newer
220 # is file locally modified or flags need changing?
228 # is file locally modified or flags need changing?
221 # dirstate flags may need to be made current
229 # dirstate flags may need to be made current
222 if m1.flags(f) != rflags or n[20:]:
230 if m1.flags(f) != rflags or n[20:]:
223 act("update permissions", "e", f, rflags)
231 act("update permissions", "e", f, rflags)
224 elif n == a: # remote newer
232 elif n == a: # remote newer
225 act("remote is newer", "g", f, rflags)
233 act("remote is newer", "g", f, rflags)
226 else: # both changed
234 else: # both changed
227 act("versions differ", "m", f, f, f, rflags, False)
235 act("versions differ", "m", f, f, f, rflags, False)
228 elif f in copied: # files we'll deal with on m2 side
236 elif f in copied: # files we'll deal with on m2 side
229 pass
237 pass
230 elif f in copy:
238 elif f in copy:
231 f2 = copy[f]
239 f2 = copy[f]
232 if f2 not in m2: # directory rename
240 if f2 not in m2: # directory rename
233 act("remote renamed directory to " + f2, "d",
241 act("remote renamed directory to " + f2, "d",
234 f, None, f2, m1.flags(f))
242 f, None, f2, m1.flags(f))
235 else: # case 2 A,B/B/B or case 4,21 A/B/B
243 else: # case 2 A,B/B/B or case 4,21 A/B/B
236 act("local copied/moved to " + f2, "m",
244 act("local copied/moved to " + f2, "m",
237 f, f2, f, fmerge(f, f2, f2), False)
245 f, f2, f, fmerge(f, f2, f2), False)
238 elif f in ma: # clean, a different, no remote
246 elif f in ma: # clean, a different, no remote
239 if n != ma[f]:
247 if n != ma[f]:
240 if repo.ui.promptchoice(
248 if repo.ui.promptchoice(
241 _(" local changed %s which remote deleted\n"
249 _(" local changed %s which remote deleted\n"
242 "use (c)hanged version or (d)elete?") % f,
250 "use (c)hanged version or (d)elete?") % f,
243 (_("&Changed"), _("&Delete")), 0):
251 (_("&Changed"), _("&Delete")), 0):
244 act("prompt delete", "r", f)
252 act("prompt delete", "r", f)
245 else:
253 else:
246 act("prompt keep", "a", f)
254 act("prompt keep", "a", f)
247 elif n[20:] == "a": # added, no remote
255 elif n[20:] == "a": # added, no remote
248 act("remote deleted", "f", f)
256 act("remote deleted", "f", f)
249 else:
257 else:
250 act("other deleted", "r", f)
258 act("other deleted", "r", f)
251
259
252 for f, n in m2.iteritems():
260 for f, n in m2.iteritems():
253 if partial and not partial(f):
261 if partial and not partial(f):
254 continue
262 continue
255 if f in m1 or f in copied: # files already visited
263 if f in m1 or f in copied: # files already visited
256 continue
264 continue
257 if f in copy:
265 if f in copy:
258 f2 = copy[f]
266 f2 = copy[f]
259 if f2 not in m1: # directory rename
267 if f2 not in m1: # directory rename
260 act("local renamed directory to " + f2, "d",
268 act("local renamed directory to " + f2, "d",
261 None, f, f2, m2.flags(f))
269 None, f, f2, m2.flags(f))
262 elif f2 in m2: # rename case 1, A/A,B/A
270 elif f2 in m2: # rename case 1, A/A,B/A
263 act("remote copied to " + f, "m",
271 act("remote copied to " + f, "m",
264 f2, f, f, fmerge(f2, f, f2), False)
272 f2, f, f, fmerge(f2, f, f2), False)
265 else: # case 3,20 A/B/A
273 else: # case 3,20 A/B/A
266 act("remote moved to " + f, "m",
274 act("remote moved to " + f, "m",
267 f2, f, f, fmerge(f2, f, f2), True)
275 f2, f, f, fmerge(f2, f, f2), True)
268 elif f not in ma:
276 elif f not in ma:
269 if (not overwrite
277 if (not overwrite
270 and _checkunknownfile(repo, p1, p2, f)):
278 and _checkunknownfile(repo, p1, p2, f)):
271 rflags = fmerge(f, f, f)
279 rflags = fmerge(f, f, f)
272 act("remote differs from untracked local",
280 act("remote differs from untracked local",
273 "m", f, f, f, rflags, False)
281 "m", f, f, f, rflags, False)
274 else:
282 else:
275 act("remote created", "g", f, m2.flags(f))
283 act("remote created", "g", f, m2.flags(f))
276 elif n != ma[f]:
284 elif n != ma[f]:
277 if repo.ui.promptchoice(
285 if repo.ui.promptchoice(
278 _("remote changed %s which local deleted\n"
286 _("remote changed %s which local deleted\n"
279 "use (c)hanged version or leave (d)eleted?") % f,
287 "use (c)hanged version or leave (d)eleted?") % f,
280 (_("&Changed"), _("&Deleted")), 0) == 0:
288 (_("&Changed"), _("&Deleted")), 0) == 0:
281 act("prompt recreating", "g", f, m2.flags(f))
289 act("prompt recreating", "g", f, m2.flags(f))
282
290
283 return action
291 return action
284
292
285 def actionkey(a):
293 def actionkey(a):
286 return a[1] == 'r' and -1 or 0, a
294 return a[1] == 'r' and -1 or 0, a
287
295
288 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
296 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
289 """apply the merge action list to the working directory
297 """apply the merge action list to the working directory
290
298
291 wctx is the working copy context
299 wctx is the working copy context
292 mctx is the context to be merged into the working copy
300 mctx is the context to be merged into the working copy
293 actx is the context of the common ancestor
301 actx is the context of the common ancestor
294
302
295 Return a tuple of counts (updated, merged, removed, unresolved) that
303 Return a tuple of counts (updated, merged, removed, unresolved) that
296 describes how many files were affected by the update.
304 describes how many files were affected by the update.
297 """
305 """
298
306
299 updated, merged, removed, unresolved = 0, 0, 0, 0
307 updated, merged, removed, unresolved = 0, 0, 0, 0
300 ms = mergestate(repo)
308 ms = mergestate(repo)
301 ms.reset(wctx.p1().node())
309 ms.reset(wctx.p1().node())
302 moves = []
310 moves = []
303 action.sort(key=actionkey)
311 action.sort(key=actionkey)
304
312
305 # prescan for merges
313 # prescan for merges
306 for a in action:
314 for a in action:
307 f, m = a[:2]
315 f, m = a[:2]
308 if m == 'm': # merge
316 if m == 'm': # merge
309 f2, fd, flags, move = a[2:]
317 f2, fd, flags, move = a[2:]
310 if f == '.hgsubstate': # merged internally
318 if f == '.hgsubstate': # merged internally
311 continue
319 continue
312 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
320 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
313 fcl = wctx[f]
321 fcl = wctx[f]
314 fco = mctx[f2]
322 fco = mctx[f2]
315 if mctx == actx: # backwards, use working dir parent as ancestor
323 if mctx == actx: # backwards, use working dir parent as ancestor
316 if fcl.parents():
324 if fcl.parents():
317 fca = fcl.p1()
325 fca = fcl.p1()
318 else:
326 else:
319 fca = repo.filectx(f, fileid=nullrev)
327 fca = repo.filectx(f, fileid=nullrev)
320 else:
328 else:
321 fca = fcl.ancestor(fco, actx)
329 fca = fcl.ancestor(fco, actx)
322 if not fca:
330 if not fca:
323 fca = repo.filectx(f, fileid=nullrev)
331 fca = repo.filectx(f, fileid=nullrev)
324 ms.add(fcl, fco, fca, fd, flags)
332 ms.add(fcl, fco, fca, fd, flags)
325 if f != fd and move:
333 if f != fd and move:
326 moves.append(f)
334 moves.append(f)
327
335
328 audit = scmutil.pathauditor(repo.root)
336 audit = scmutil.pathauditor(repo.root)
329
337
330 # remove renamed files after safely stored
338 # remove renamed files after safely stored
331 for f in moves:
339 for f in moves:
332 if os.path.lexists(repo.wjoin(f)):
340 if os.path.lexists(repo.wjoin(f)):
333 repo.ui.debug("removing %s\n" % f)
341 repo.ui.debug("removing %s\n" % f)
334 audit(f)
342 audit(f)
335 os.unlink(repo.wjoin(f))
343 os.unlink(repo.wjoin(f))
336
344
337 numupdates = len(action)
345 numupdates = len(action)
338 for i, a in enumerate(action):
346 for i, a in enumerate(action):
339 f, m = a[:2]
347 f, m = a[:2]
340 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
348 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
341 unit=_('files'))
349 unit=_('files'))
342 if f and f[0] == "/":
350 if f and f[0] == "/":
343 continue
351 continue
344 if m == "r": # remove
352 if m == "r": # remove
345 repo.ui.note(_("removing %s\n") % f)
353 repo.ui.note(_("removing %s\n") % f)
346 audit(f)
354 audit(f)
347 if f == '.hgsubstate': # subrepo states need updating
355 if f == '.hgsubstate': # subrepo states need updating
348 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
356 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
349 try:
357 try:
350 util.unlinkpath(repo.wjoin(f))
358 util.unlinkpath(repo.wjoin(f))
351 except OSError, inst:
359 except OSError, inst:
352 if inst.errno != errno.ENOENT:
360 if inst.errno != errno.ENOENT:
353 repo.ui.warn(_("update failed to remove %s: %s!\n") %
361 repo.ui.warn(_("update failed to remove %s: %s!\n") %
354 (f, inst.strerror))
362 (f, inst.strerror))
355 removed += 1
363 removed += 1
356 elif m == "m": # merge
364 elif m == "m": # merge
357 if f == '.hgsubstate': # subrepo states need updating
365 if f == '.hgsubstate': # subrepo states need updating
358 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
366 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
359 continue
367 continue
360 f2, fd, flags, move = a[2:]
368 f2, fd, flags, move = a[2:]
361 repo.wopener.audit(fd)
369 repo.wopener.audit(fd)
362 r = ms.resolve(fd, wctx, mctx)
370 r = ms.resolve(fd, wctx, mctx)
363 if r is not None and r > 0:
371 if r is not None and r > 0:
364 unresolved += 1
372 unresolved += 1
365 else:
373 else:
366 if r is None:
374 if r is None:
367 updated += 1
375 updated += 1
368 else:
376 else:
369 merged += 1
377 merged += 1
370 if (move and repo.dirstate.normalize(fd) != f
378 if (move and repo.dirstate.normalize(fd) != f
371 and os.path.lexists(repo.wjoin(f))):
379 and os.path.lexists(repo.wjoin(f))):
372 repo.ui.debug("removing %s\n" % f)
380 repo.ui.debug("removing %s\n" % f)
373 audit(f)
381 audit(f)
374 os.unlink(repo.wjoin(f))
382 os.unlink(repo.wjoin(f))
375 elif m == "g": # get
383 elif m == "g": # get
376 flags = a[2]
384 flags = a[2]
377 repo.ui.note(_("getting %s\n") % f)
385 repo.ui.note(_("getting %s\n") % f)
378 t = mctx.filectx(f).data()
386 t = mctx.filectx(f).data()
379 repo.wwrite(f, t, flags)
387 repo.wwrite(f, t, flags)
380 t = None
388 t = None
381 updated += 1
389 updated += 1
382 if f == '.hgsubstate': # subrepo states need updating
390 if f == '.hgsubstate': # subrepo states need updating
383 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
391 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
384 elif m == "d": # directory rename
392 elif m == "d": # directory rename
385 f2, fd, flags = a[2:]
393 f2, fd, flags = a[2:]
386 if f:
394 if f:
387 repo.ui.note(_("moving %s to %s\n") % (f, fd))
395 repo.ui.note(_("moving %s to %s\n") % (f, fd))
388 audit(f)
396 audit(f)
389 t = wctx.filectx(f).data()
397 t = wctx.filectx(f).data()
390 repo.wwrite(fd, t, flags)
398 repo.wwrite(fd, t, flags)
391 util.unlinkpath(repo.wjoin(f))
399 util.unlinkpath(repo.wjoin(f))
392 if f2:
400 if f2:
393 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
401 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
394 t = mctx.filectx(f2).data()
402 t = mctx.filectx(f2).data()
395 repo.wwrite(fd, t, flags)
403 repo.wwrite(fd, t, flags)
396 updated += 1
404 updated += 1
397 elif m == "dr": # divergent renames
405 elif m == "dr": # divergent renames
398 fl = a[2]
406 fl = a[2]
399 repo.ui.warn(_("note: possible conflict - %s was renamed "
407 repo.ui.warn(_("note: possible conflict - %s was renamed "
400 "multiple times to:\n") % f)
408 "multiple times to:\n") % f)
401 for nf in fl:
409 for nf in fl:
402 repo.ui.warn(" %s\n" % nf)
410 repo.ui.warn(" %s\n" % nf)
403 elif m == "e": # exec
411 elif m == "e": # exec
404 flags = a[2]
412 flags = a[2]
405 repo.wopener.audit(f)
413 repo.wopener.audit(f)
406 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
414 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
407 ms.commit()
415 ms.commit()
408 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
416 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
409
417
410 return updated, merged, removed, unresolved
418 return updated, merged, removed, unresolved
411
419
412 def recordupdates(repo, action, branchmerge):
420 def recordupdates(repo, action, branchmerge):
413 "record merge actions to the dirstate"
421 "record merge actions to the dirstate"
414
422
415 for a in action:
423 for a in action:
416 f, m = a[:2]
424 f, m = a[:2]
417 if m == "r": # remove
425 if m == "r": # remove
418 if branchmerge:
426 if branchmerge:
419 repo.dirstate.remove(f)
427 repo.dirstate.remove(f)
420 else:
428 else:
421 repo.dirstate.drop(f)
429 repo.dirstate.drop(f)
422 elif m == "a": # re-add
430 elif m == "a": # re-add
423 if not branchmerge:
431 if not branchmerge:
424 repo.dirstate.add(f)
432 repo.dirstate.add(f)
425 elif m == "f": # forget
433 elif m == "f": # forget
426 repo.dirstate.drop(f)
434 repo.dirstate.drop(f)
427 elif m == "e": # exec change
435 elif m == "e": # exec change
428 repo.dirstate.normallookup(f)
436 repo.dirstate.normallookup(f)
429 elif m == "g": # get
437 elif m == "g": # get
430 if branchmerge:
438 if branchmerge:
431 repo.dirstate.otherparent(f)
439 repo.dirstate.otherparent(f)
432 else:
440 else:
433 repo.dirstate.normal(f)
441 repo.dirstate.normal(f)
434 elif m == "m": # merge
442 elif m == "m": # merge
435 f2, fd, flag, move = a[2:]
443 f2, fd, flag, move = a[2:]
436 if branchmerge:
444 if branchmerge:
437 # We've done a branch merge, mark this file as merged
445 # We've done a branch merge, mark this file as merged
438 # so that we properly record the merger later
446 # so that we properly record the merger later
439 repo.dirstate.merge(fd)
447 repo.dirstate.merge(fd)
440 if f != f2: # copy/rename
448 if f != f2: # copy/rename
441 if move:
449 if move:
442 repo.dirstate.remove(f)
450 repo.dirstate.remove(f)
443 if f != fd:
451 if f != fd:
444 repo.dirstate.copy(f, fd)
452 repo.dirstate.copy(f, fd)
445 else:
453 else:
446 repo.dirstate.copy(f2, fd)
454 repo.dirstate.copy(f2, fd)
447 else:
455 else:
448 # We've update-merged a locally modified file, so
456 # We've update-merged a locally modified file, so
449 # we set the dirstate to emulate a normal checkout
457 # we set the dirstate to emulate a normal checkout
450 # of that file some time in the past. Thus our
458 # of that file some time in the past. Thus our
451 # merge will appear as a normal local file
459 # merge will appear as a normal local file
452 # modification.
460 # modification.
453 if f2 == fd: # file not locally copied/moved
461 if f2 == fd: # file not locally copied/moved
454 repo.dirstate.normallookup(fd)
462 repo.dirstate.normallookup(fd)
455 if move:
463 if move:
456 repo.dirstate.drop(f)
464 repo.dirstate.drop(f)
457 elif m == "d": # directory rename
465 elif m == "d": # directory rename
458 f2, fd, flag = a[2:]
466 f2, fd, flag = a[2:]
459 if not f2 and f not in repo.dirstate:
467 if not f2 and f not in repo.dirstate:
460 # untracked file moved
468 # untracked file moved
461 continue
469 continue
462 if branchmerge:
470 if branchmerge:
463 repo.dirstate.add(fd)
471 repo.dirstate.add(fd)
464 if f:
472 if f:
465 repo.dirstate.remove(f)
473 repo.dirstate.remove(f)
466 repo.dirstate.copy(f, fd)
474 repo.dirstate.copy(f, fd)
467 if f2:
475 if f2:
468 repo.dirstate.copy(f2, fd)
476 repo.dirstate.copy(f2, fd)
469 else:
477 else:
470 repo.dirstate.normal(fd)
478 repo.dirstate.normal(fd)
471 if f:
479 if f:
472 repo.dirstate.drop(f)
480 repo.dirstate.drop(f)
473
481
474 def update(repo, node, branchmerge, force, partial, ancestor=None):
482 def update(repo, node, branchmerge, force, partial, ancestor=None):
475 """
483 """
476 Perform a merge between the working directory and the given node
484 Perform a merge between the working directory and the given node
477
485
478 node = the node to update to, or None if unspecified
486 node = the node to update to, or None if unspecified
479 branchmerge = whether to merge between branches
487 branchmerge = whether to merge between branches
480 force = whether to force branch merging or file overwriting
488 force = whether to force branch merging or file overwriting
481 partial = a function to filter file lists (dirstate not updated)
489 partial = a function to filter file lists (dirstate not updated)
482
490
483 The table below shows all the behaviors of the update command
491 The table below shows all the behaviors of the update command
484 given the -c and -C or no options, whether the working directory
492 given the -c and -C or no options, whether the working directory
485 is dirty, whether a revision is specified, and the relationship of
493 is dirty, whether a revision is specified, and the relationship of
486 the parent rev to the target rev (linear, on the same named
494 the parent rev to the target rev (linear, on the same named
487 branch, or on another named branch).
495 branch, or on another named branch).
488
496
489 This logic is tested by test-update-branches.t.
497 This logic is tested by test-update-branches.t.
490
498
491 -c -C dirty rev | linear same cross
499 -c -C dirty rev | linear same cross
492 n n n n | ok (1) x
500 n n n n | ok (1) x
493 n n n y | ok ok ok
501 n n n y | ok ok ok
494 n n y * | merge (2) (2)
502 n n y * | merge (2) (2)
495 n y * * | --- discard ---
503 n y * * | --- discard ---
496 y n y * | --- (3) ---
504 y n y * | --- (3) ---
497 y n n * | --- ok ---
505 y n n * | --- ok ---
498 y y * * | --- (4) ---
506 y y * * | --- (4) ---
499
507
500 x = can't happen
508 x = can't happen
501 * = don't-care
509 * = don't-care
502 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
510 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
503 2 = abort: crosses branches (use 'hg merge' to merge or
511 2 = abort: crosses branches (use 'hg merge' to merge or
504 use 'hg update -C' to discard changes)
512 use 'hg update -C' to discard changes)
505 3 = abort: uncommitted local changes
513 3 = abort: uncommitted local changes
506 4 = incompatible options (checked in commands.py)
514 4 = incompatible options (checked in commands.py)
507
515
508 Return the same tuple as applyupdates().
516 Return the same tuple as applyupdates().
509 """
517 """
510
518
511 onode = node
519 onode = node
512 wlock = repo.wlock()
520 wlock = repo.wlock()
513 try:
521 try:
514 wc = repo[None]
522 wc = repo[None]
515 if node is None:
523 if node is None:
516 # tip of current branch
524 # tip of current branch
517 try:
525 try:
518 node = repo.branchtags()[wc.branch()]
526 node = repo.branchtags()[wc.branch()]
519 except KeyError:
527 except KeyError:
520 if wc.branch() == "default": # no default branch!
528 if wc.branch() == "default": # no default branch!
521 node = repo.lookup("tip") # update to tip
529 node = repo.lookup("tip") # update to tip
522 else:
530 else:
523 raise util.Abort(_("branch %s not found") % wc.branch())
531 raise util.Abort(_("branch %s not found") % wc.branch())
524 overwrite = force and not branchmerge
532 overwrite = force and not branchmerge
525 pl = wc.parents()
533 pl = wc.parents()
526 p1, p2 = pl[0], repo[node]
534 p1, p2 = pl[0], repo[node]
527 if ancestor:
535 if ancestor:
528 pa = repo[ancestor]
536 pa = repo[ancestor]
529 else:
537 else:
530 pa = p1.ancestor(p2)
538 pa = p1.ancestor(p2)
531
539
532 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
540 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
533
541
534 ### check phase
542 ### check phase
535 if not overwrite and len(pl) > 1:
543 if not overwrite and len(pl) > 1:
536 raise util.Abort(_("outstanding uncommitted merges"))
544 raise util.Abort(_("outstanding uncommitted merges"))
537 if branchmerge:
545 if branchmerge:
538 if pa == p2:
546 if pa == p2:
539 raise util.Abort(_("merging with a working directory ancestor"
547 raise util.Abort(_("merging with a working directory ancestor"
540 " has no effect"))
548 " has no effect"))
541 elif pa == p1:
549 elif pa == p1:
542 if p1.branch() == p2.branch():
550 if p1.branch() == p2.branch():
543 raise util.Abort(_("nothing to merge"),
551 raise util.Abort(_("nothing to merge"),
544 hint=_("use 'hg update' "
552 hint=_("use 'hg update' "
545 "or check 'hg heads'"))
553 "or check 'hg heads'"))
546 if not force and (wc.files() or wc.deleted()):
554 if not force and (wc.files() or wc.deleted()):
547 raise util.Abort(_("outstanding uncommitted changes"),
555 raise util.Abort(_("outstanding uncommitted changes"),
548 hint=_("use 'hg status' to list changes"))
556 hint=_("use 'hg status' to list changes"))
549 for s in wc.substate:
557 for s in wc.substate:
550 if wc.sub(s).dirty():
558 if wc.sub(s).dirty():
551 raise util.Abort(_("outstanding uncommitted changes in "
559 raise util.Abort(_("outstanding uncommitted changes in "
552 "subrepository '%s'") % s)
560 "subrepository '%s'") % s)
553
561
554 elif not overwrite:
562 elif not overwrite:
555 if pa == p1 or pa == p2: # linear
563 if pa == p1 or pa == p2: # linear
556 pass # all good
564 pass # all good
557 elif wc.dirty(missing=True):
565 elif wc.dirty(missing=True):
558 raise util.Abort(_("crosses branches (merge branches or use"
566 raise util.Abort(_("crosses branches (merge branches or use"
559 " --clean to discard changes)"))
567 " --clean to discard changes)"))
560 elif onode is None:
568 elif onode is None:
561 raise util.Abort(_("crosses branches (merge branches or update"
569 raise util.Abort(_("crosses branches (merge branches or update"
562 " --check to force update)"))
570 " --check to force update)"))
563 else:
571 else:
564 # Allow jumping branches if clean and specific rev given
572 # Allow jumping branches if clean and specific rev given
565 pa = p1
573 pa = p1
566
574
567 ### calculate phase
575 ### calculate phase
568 action = []
576 action = []
569 folding = not util.checkcase(repo.path)
577 folding = not util.checkcase(repo.path)
570 if folding:
578 if folding:
571 _checkcollision(p2, branchmerge and p1)
579 # collision check is not needed for clean update
580 if not branchmerge and force:
581 _checkcollision(p2, None)
582 else:
583 _checkcollision(p2, wc)
572 if not force:
584 if not force:
573 _checkunknown(repo, wc, p2)
585 _checkunknown(repo, wc, p2)
574 action += _forgetremoved(wc, p2, branchmerge)
586 action += _forgetremoved(wc, p2, branchmerge)
575 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
587 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
576
588
577 ### apply phase
589 ### apply phase
578 if not branchmerge: # just jump to the new rev
590 if not branchmerge: # just jump to the new rev
579 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
591 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
580 if not partial:
592 if not partial:
581 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
593 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
582
594
583 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
595 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
584
596
585 if not partial:
597 if not partial:
586 repo.dirstate.setparents(fp1, fp2)
598 repo.dirstate.setparents(fp1, fp2)
587 recordupdates(repo, action, branchmerge)
599 recordupdates(repo, action, branchmerge)
588 if not branchmerge:
600 if not branchmerge:
589 repo.dirstate.setbranch(p2.branch())
601 repo.dirstate.setbranch(p2.branch())
590 finally:
602 finally:
591 wlock.release()
603 wlock.release()
592
604
593 if not partial:
605 if not partial:
594 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
606 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
595 return stats
607 return stats
@@ -1,109 +1,209 b''
1 run only on case-insensitive filesystems
1 run only on case-insensitive filesystems
2
2
3 $ "$TESTDIR/hghave" icasefs || exit 80
3 $ "$TESTDIR/hghave" icasefs || exit 80
4
4
5 ################################
5 ################################
6 test for branch merging
6 test for branch merging
7 ################################
7 ################################
8
8
9 $ hg init repo1
9 test for rename awareness of case-folding collision check:
10 $ cd repo1
11
12 create base revision
13
10
14 $ echo base > base.txt
11 (1) colliding file is one renamed from collided file:
15 $ hg add base.txt
12 this is also case for issue3370.
16 $ hg commit -m 'base'
17
13
18 add same file in different case on both heads
14 $ hg init merge_renameaware_1
15 $ cd merge_renameaware_1
19
16
20 $ echo a > a.txt
17 $ echo a > a
21 $ hg add a.txt
18 $ hg add a
22 $ hg commit -m 'add a.txt'
19 $ hg commit -m '#0'
23
20 $ hg rename a tmp
21 $ hg rename tmp A
22 $ hg commit -m '#1'
24 $ hg update 0
23 $ hg update 0
25 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
24 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
26
25 $ echo 'modified at #2' > a
27 $ echo A > A.TXT
26 $ hg commit -m '#2'
28 $ hg add A.TXT
29 $ hg commit -m 'add A.TXT'
30 created new head
27 created new head
31
28
32 merge another, and fail with case-folding collision
29 $ hg merge
30 merging a and A to A
31 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
32 (branch merge, don't forget to commit)
33 $ hg status -A
34 M A
35 a
36 R a
37 $ cat A
38 modified at #2
39
40 $ hg update --clean 1
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 $ hg merge
43 merging A and a to A
44 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
45 (branch merge, don't forget to commit)
46 $ hg status -A
47 M A
48 a
49 $ cat A
50 modified at #2
51
52 $ cd ..
53
54 (2) colliding file is not related to collided file
55
56 $ hg init merge_renameaware_2
57 $ cd merge_renameaware_2
58
59 $ echo a > a
60 $ hg add a
61 $ hg commit -m '#0'
62 $ hg remove a
63 $ hg commit -m '#1'
64 $ echo A > A
65 $ hg add A
66 $ hg commit -m '#2'
67 $ hg update --clean 0
68 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
69 $ echo 'modified at #3' > a
70 $ hg commit -m '#3'
71 created new head
33
72
34 $ hg merge
73 $ hg merge
35 abort: case-folding collision between a.txt and A.TXT
74 abort: case-folding collision between A and a
36 [255]
75 [255]
76 $ hg parents --template '{rev}\n'
77 3
78 $ hg status -A
79 C a
80 $ cat a
81 modified at #3
37
82
38 check clean-ness of working directory
83 $ hg update --clean 2
39
84 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 $ hg status
85 $ hg merge
86 abort: case-folding collision between a and A
87 [255]
41 $ hg parents --template '{rev}\n'
88 $ hg parents --template '{rev}\n'
42 2
89 2
90 $ hg status -A
91 C A
92 $ cat A
93 A
94
43 $ cd ..
95 $ cd ..
44
96
97
45 ################################
98 ################################
46 test for linear updates
99 test for linear updates
47 ################################
100 ################################
48
101
49 $ hg init repo2
102 test for rename awareness of case-folding collision check:
50 $ cd repo2
51
103
52 create base revision (rev:0)
104 (1) colliding file is one renamed from collided file
105
106 $ hg init linearupdate_renameaware_1
107 $ cd linearupdate_renameaware_1
53
108
54 $ hg import --bypass --exact - <<EOF
109 $ echo a > a
55 > # HG changeset patch
110 $ hg add a
56 > # User null
111 $ hg commit -m '#0'
57 > # Date 1 0
112 $ hg rename a tmp
58 > # Node ID e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
113 $ hg rename tmp A
59 > # Parent 0000000000000000000000000000000000000000
114 $ hg commit -m '#1'
60 > add a
61 >
62 > diff --git a/a b/a
63 > new file mode 100644
64 > --- /dev/null
65 > +++ b/a
66 > @@ -0,0 +1,3 @@
67 > +this is line 1
68 > +this is line 2
69 > +this is line 3
70 > EOF
71 applying patch from stdin
72
73 create rename revision (rev:1)
74
75 $ hg import --bypass --exact - <<EOF
76 > # HG changeset patch
77 > # User null
78 > # Date 1 0
79 > # Node ID 9dca9f19bb91851bc693544b598b0740629edfad
80 > # Parent e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
81 > rename a to A
82 >
83 > diff --git a/a b/A
84 > rename from a
85 > rename to A
86 > EOF
87 applying patch from stdin
88
89 update to base revision, and modify 'a'
90
115
91 $ hg update 0
116 $ hg update 0
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
93 $ echo 'this is added line' >> a
94
118
95 update to current tip linearly
119 $ echo 'this is added line' >> a
96
97 $ hg update 1
120 $ hg update 1
98 merging a and A to A
121 merging a and A to A
99 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
122 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
100
101 check status and contents of file
102
103 $ hg status -A
123 $ hg status -A
104 M A
124 M A
105 $ cat A
125 $ cat A
106 this is line 1
126 a
107 this is line 2
108 this is line 3
109 this is added line
127 this is added line
128
129 $ cd ..
130
131 (2) colliding file is not related to collided file
132
133 $ hg init linearupdate_renameaware_2
134 $ cd linearupdate_renameaware_2
135
136 $ echo a > a
137 $ hg add a
138 $ hg commit -m '#0'
139 $ hg remove a
140 $ hg commit -m '#1'
141 $ echo A > A
142 $ hg add A
143 $ hg commit -m '#2'
144
145 $ hg update 0
146 abort: case-folding collision between a and A
147 [255]
148 $ hg parents --template '{rev}\n'
149 2
150 $ hg status -A
151 C A
152 $ cat A
153 A
154
155 $ hg update --check 0
156 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
157 $ hg parents --template '{rev}\n'
158 0
159 $ hg status -A
160 C a
161 $ cat a
162 a
163
164 $ hg update --clean 2
165 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
166 $ hg parents --template '{rev}\n'
167 2
168 $ hg status -A
169 C A
170 $ cat A
171 A
172
173 $ cd ..
174
175 (3) colliding file is not related to collided file: added in working dir
176
177 $ hg init linearupdate_renameaware_3
178 $ cd linearupdate_renameaware_3
179
180 $ echo a > a
181 $ hg add a
182 $ hg commit -m '#0'
183 $ hg rename a b
184 $ hg commit -m '#1'
185 $ hg update 0
186 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
187
188 $ echo B > B
189 $ hg add B
190 $ hg status
191 A B
192 $ hg update
193 abort: case-folding collision between b and B
194 [255]
195
196 $ hg update --check
197 abort: uncommitted local changes
198 [255]
199
200 $ hg update --clean
201 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
202 $ hg parents --template '{rev}\n'
203 1
204 $ hg status -A
205 C b
206 $ cat b
207 a
208
209 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now