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