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