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