##// END OF EJS Templates
merge: defer symlink flag merging to filemerge (issue3200)...
Matt Mackall -
r16001:fcf66193 stable
parent child Browse files
Show More
@@ -1,589 +1,592
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':
177 # can't automatically merge symlink flag change here, let
178 # filemerge take care of it
179 return m
176 return n
180 return n
177 return '' # flag was cleared
181 return '' # flag was cleared
178
182
179 def act(msg, m, f, *args):
183 def act(msg, m, f, *args):
180 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
184 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
181 action.append((f, m) + args)
185 action.append((f, m) + args)
182
186
183 action, copy = [], {}
187 action, copy = [], {}
184
188
185 if overwrite:
189 if overwrite:
186 pa = p1
190 pa = p1
187 elif pa == p2: # backwards
191 elif pa == p2: # backwards
188 pa = p1.p1()
192 pa = p1.p1()
189 elif pa and repo.ui.configbool("merge", "followcopies", True):
193 elif pa and repo.ui.configbool("merge", "followcopies", True):
190 dirs = repo.ui.configbool("merge", "followdirs", True)
194 dirs = repo.ui.configbool("merge", "followdirs", True)
191 copy, diverge = copies.mergecopies(repo, p1, p2, pa, dirs)
195 copy, diverge = copies.mergecopies(repo, p1, p2, pa, dirs)
192 for of, fl in diverge.iteritems():
196 for of, fl in diverge.iteritems():
193 act("divergent renames", "dr", of, fl)
197 act("divergent renames", "dr", of, fl)
194
198
195 repo.ui.note(_("resolving manifests\n"))
199 repo.ui.note(_("resolving manifests\n"))
196 repo.ui.debug(" overwrite: %s, partial: %s\n"
200 repo.ui.debug(" overwrite: %s, partial: %s\n"
197 % (bool(overwrite), bool(partial)))
201 % (bool(overwrite), bool(partial)))
198 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
202 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
199
203
200 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
204 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
201 copied = set(copy.values())
205 copied = set(copy.values())
202
206
203 if '.hgsubstate' in m1:
207 if '.hgsubstate' in m1:
204 # check whether sub state is modified
208 # check whether sub state is modified
205 for s in p1.substate:
209 for s in p1.substate:
206 if p1.sub(s).dirty():
210 if p1.sub(s).dirty():
207 m1['.hgsubstate'] += "+"
211 m1['.hgsubstate'] += "+"
208 break
212 break
209
213
210 # Compare manifests
214 # Compare manifests
211 for f, n in m1.iteritems():
215 for f, n in m1.iteritems():
212 if partial and not partial(f):
216 if partial and not partial(f):
213 continue
217 continue
214 if f in m2:
218 if f in m2:
215 rflags = fmerge(f, f, f)
219 rflags = fmerge(f, f, f)
216 a = ma.get(f, nullid)
220 a = ma.get(f, nullid)
217 if n == m2[f] or m2[f] == a: # same or local newer
221 if n == m2[f] or m2[f] == a: # same or local newer
218 # is file locally modified or flags need changing?
222 # is file locally modified or flags need changing?
219 # dirstate flags may need to be made current
223 # dirstate flags may need to be made current
220 if m1.flags(f) != rflags or n[20:]:
224 if m1.flags(f) != rflags or n[20:]:
221 act("update permissions", "e", f, rflags)
225 act("update permissions", "e", f, rflags)
222 elif n == a: # remote newer
226 elif n == a: # remote newer
223 act("remote is newer", "g", f, rflags)
227 act("remote is newer", "g", f, rflags)
224 else: # both changed
228 else: # both changed
225 act("versions differ", "m", f, f, f, rflags, False)
229 act("versions differ", "m", f, f, f, rflags, False)
226 elif f in copied: # files we'll deal with on m2 side
230 elif f in copied: # files we'll deal with on m2 side
227 pass
231 pass
228 elif f in copy:
232 elif f in copy:
229 f2 = copy[f]
233 f2 = copy[f]
230 if f2 not in m2: # directory rename
234 if f2 not in m2: # directory rename
231 act("remote renamed directory to " + f2, "d",
235 act("remote renamed directory to " + f2, "d",
232 f, None, f2, m1.flags(f))
236 f, None, f2, m1.flags(f))
233 else: # case 2 A,B/B/B or case 4,21 A/B/B
237 else: # case 2 A,B/B/B or case 4,21 A/B/B
234 act("local copied/moved to " + f2, "m",
238 act("local copied/moved to " + f2, "m",
235 f, f2, f, fmerge(f, f2, f2), False)
239 f, f2, f, fmerge(f, f2, f2), False)
236 elif f in ma: # clean, a different, no remote
240 elif f in ma: # clean, a different, no remote
237 if n != ma[f]:
241 if n != ma[f]:
238 if repo.ui.promptchoice(
242 if repo.ui.promptchoice(
239 _(" local changed %s which remote deleted\n"
243 _(" local changed %s which remote deleted\n"
240 "use (c)hanged version or (d)elete?") % f,
244 "use (c)hanged version or (d)elete?") % f,
241 (_("&Changed"), _("&Delete")), 0):
245 (_("&Changed"), _("&Delete")), 0):
242 act("prompt delete", "r", f)
246 act("prompt delete", "r", f)
243 else:
247 else:
244 act("prompt keep", "a", f)
248 act("prompt keep", "a", f)
245 elif n[20:] == "a": # added, no remote
249 elif n[20:] == "a": # added, no remote
246 act("remote deleted", "f", f)
250 act("remote deleted", "f", f)
247 elif n[20:] != "u":
251 elif n[20:] != "u":
248 act("other deleted", "r", f)
252 act("other deleted", "r", f)
249
253
250 for f, n in m2.iteritems():
254 for f, n in m2.iteritems():
251 if partial and not partial(f):
255 if partial and not partial(f):
252 continue
256 continue
253 if f in m1 or f in copied: # files already visited
257 if f in m1 or f in copied: # files already visited
254 continue
258 continue
255 if f in copy:
259 if f in copy:
256 f2 = copy[f]
260 f2 = copy[f]
257 if f2 not in m1: # directory rename
261 if f2 not in m1: # directory rename
258 act("local renamed directory to " + f2, "d",
262 act("local renamed directory to " + f2, "d",
259 None, f, f2, m2.flags(f))
263 None, f, f2, m2.flags(f))
260 elif f2 in m2: # rename case 1, A/A,B/A
264 elif f2 in m2: # rename case 1, A/A,B/A
261 act("remote copied to " + f, "m",
265 act("remote copied to " + f, "m",
262 f2, f, f, fmerge(f2, f, f2), False)
266 f2, f, f, fmerge(f2, f, f2), False)
263 else: # case 3,20 A/B/A
267 else: # case 3,20 A/B/A
264 act("remote moved to " + f, "m",
268 act("remote moved to " + f, "m",
265 f2, f, f, fmerge(f2, f, f2), True)
269 f2, f, f, fmerge(f2, f, f2), True)
266 elif f not in ma:
270 elif f not in ma:
267 act("remote created", "g", f, m2.flags(f))
271 act("remote created", "g", f, m2.flags(f))
268 elif n != ma[f]:
272 elif n != ma[f]:
269 if repo.ui.promptchoice(
273 if repo.ui.promptchoice(
270 _("remote changed %s which local deleted\n"
274 _("remote changed %s which local deleted\n"
271 "use (c)hanged version or leave (d)eleted?") % f,
275 "use (c)hanged version or leave (d)eleted?") % f,
272 (_("&Changed"), _("&Deleted")), 0) == 0:
276 (_("&Changed"), _("&Deleted")), 0) == 0:
273 act("prompt recreating", "g", f, m2.flags(f))
277 act("prompt recreating", "g", f, m2.flags(f))
274
278
275 return action
279 return action
276
280
277 def actionkey(a):
281 def actionkey(a):
278 return a[1] == 'r' and -1 or 0, a
282 return a[1] == 'r' and -1 or 0, a
279
283
280 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
284 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
281 """apply the merge action list to the working directory
285 """apply the merge action list to the working directory
282
286
283 wctx is the working copy context
287 wctx is the working copy context
284 mctx is the context to be merged into the working copy
288 mctx is the context to be merged into the working copy
285 actx is the context of the common ancestor
289 actx is the context of the common ancestor
286
290
287 Return a tuple of counts (updated, merged, removed, unresolved) that
291 Return a tuple of counts (updated, merged, removed, unresolved) that
288 describes how many files were affected by the update.
292 describes how many files were affected by the update.
289 """
293 """
290
294
291 updated, merged, removed, unresolved = 0, 0, 0, 0
295 updated, merged, removed, unresolved = 0, 0, 0, 0
292 ms = mergestate(repo)
296 ms = mergestate(repo)
293 ms.reset(wctx.p1().node())
297 ms.reset(wctx.p1().node())
294 moves = []
298 moves = []
295 action.sort(key=actionkey)
299 action.sort(key=actionkey)
296
300
297 # prescan for merges
301 # prescan for merges
298 for a in action:
302 for a in action:
299 f, m = a[:2]
303 f, m = a[:2]
300 if m == 'm': # merge
304 if m == 'm': # merge
301 f2, fd, flags, move = a[2:]
305 f2, fd, flags, move = a[2:]
302 if f == '.hgsubstate': # merged internally
306 if f == '.hgsubstate': # merged internally
303 continue
307 continue
304 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
308 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
305 fcl = wctx[f]
309 fcl = wctx[f]
306 fco = mctx[f2]
310 fco = mctx[f2]
307 if mctx == actx: # backwards, use working dir parent as ancestor
311 if mctx == actx: # backwards, use working dir parent as ancestor
308 if fcl.parents():
312 if fcl.parents():
309 fca = fcl.p1()
313 fca = fcl.p1()
310 else:
314 else:
311 fca = repo.filectx(f, fileid=nullrev)
315 fca = repo.filectx(f, fileid=nullrev)
312 else:
316 else:
313 fca = fcl.ancestor(fco, actx)
317 fca = fcl.ancestor(fco, actx)
314 if not fca:
318 if not fca:
315 fca = repo.filectx(f, fileid=nullrev)
319 fca = repo.filectx(f, fileid=nullrev)
316 ms.add(fcl, fco, fca, fd, flags)
320 ms.add(fcl, fco, fca, fd, flags)
317 if f != fd and move:
321 if f != fd and move:
318 moves.append(f)
322 moves.append(f)
319
323
320 audit = scmutil.pathauditor(repo.root)
324 audit = scmutil.pathauditor(repo.root)
321
325
322 # remove renamed files after safely stored
326 # remove renamed files after safely stored
323 for f in moves:
327 for f in moves:
324 if os.path.lexists(repo.wjoin(f)):
328 if os.path.lexists(repo.wjoin(f)):
325 repo.ui.debug("removing %s\n" % f)
329 repo.ui.debug("removing %s\n" % f)
326 audit(f)
330 audit(f)
327 os.unlink(repo.wjoin(f))
331 os.unlink(repo.wjoin(f))
328
332
329 numupdates = len(action)
333 numupdates = len(action)
330 for i, a in enumerate(action):
334 for i, a in enumerate(action):
331 f, m = a[:2]
335 f, m = a[:2]
332 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
336 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
333 unit=_('files'))
337 unit=_('files'))
334 if f and f[0] == "/":
338 if f and f[0] == "/":
335 continue
339 continue
336 if m == "r": # remove
340 if m == "r": # remove
337 repo.ui.note(_("removing %s\n") % f)
341 repo.ui.note(_("removing %s\n") % f)
338 audit(f)
342 audit(f)
339 if f == '.hgsubstate': # subrepo states need updating
343 if f == '.hgsubstate': # subrepo states need updating
340 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
344 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
341 try:
345 try:
342 util.unlinkpath(repo.wjoin(f))
346 util.unlinkpath(repo.wjoin(f))
343 except OSError, inst:
347 except OSError, inst:
344 if inst.errno != errno.ENOENT:
348 if inst.errno != errno.ENOENT:
345 repo.ui.warn(_("update failed to remove %s: %s!\n") %
349 repo.ui.warn(_("update failed to remove %s: %s!\n") %
346 (f, inst.strerror))
350 (f, inst.strerror))
347 removed += 1
351 removed += 1
348 elif m == "m": # merge
352 elif m == "m": # merge
349 if f == '.hgsubstate': # subrepo states need updating
353 if f == '.hgsubstate': # subrepo states need updating
350 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
354 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
351 continue
355 continue
352 f2, fd, flags, move = a[2:]
356 f2, fd, flags, move = a[2:]
353 repo.wopener.audit(fd)
357 repo.wopener.audit(fd)
354 r = ms.resolve(fd, wctx, mctx)
358 r = ms.resolve(fd, wctx, mctx)
355 if r is not None and r > 0:
359 if r is not None and r > 0:
356 unresolved += 1
360 unresolved += 1
357 else:
361 else:
358 if r is None:
362 if r is None:
359 updated += 1
363 updated += 1
360 else:
364 else:
361 merged += 1
365 merged += 1
362 util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
363 if (move and repo.dirstate.normalize(fd) != f
366 if (move and repo.dirstate.normalize(fd) != f
364 and os.path.lexists(repo.wjoin(f))):
367 and os.path.lexists(repo.wjoin(f))):
365 repo.ui.debug("removing %s\n" % f)
368 repo.ui.debug("removing %s\n" % f)
366 audit(f)
369 audit(f)
367 os.unlink(repo.wjoin(f))
370 os.unlink(repo.wjoin(f))
368 elif m == "g": # get
371 elif m == "g": # get
369 flags = a[2]
372 flags = a[2]
370 repo.ui.note(_("getting %s\n") % f)
373 repo.ui.note(_("getting %s\n") % f)
371 t = mctx.filectx(f).data()
374 t = mctx.filectx(f).data()
372 repo.wwrite(f, t, flags)
375 repo.wwrite(f, t, flags)
373 t = None
376 t = None
374 updated += 1
377 updated += 1
375 if f == '.hgsubstate': # subrepo states need updating
378 if f == '.hgsubstate': # subrepo states need updating
376 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
379 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
377 elif m == "d": # directory rename
380 elif m == "d": # directory rename
378 f2, fd, flags = a[2:]
381 f2, fd, flags = a[2:]
379 if f:
382 if f:
380 repo.ui.note(_("moving %s to %s\n") % (f, fd))
383 repo.ui.note(_("moving %s to %s\n") % (f, fd))
381 audit(f)
384 audit(f)
382 t = wctx.filectx(f).data()
385 t = wctx.filectx(f).data()
383 repo.wwrite(fd, t, flags)
386 repo.wwrite(fd, t, flags)
384 util.unlinkpath(repo.wjoin(f))
387 util.unlinkpath(repo.wjoin(f))
385 if f2:
388 if f2:
386 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
389 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
387 t = mctx.filectx(f2).data()
390 t = mctx.filectx(f2).data()
388 repo.wwrite(fd, t, flags)
391 repo.wwrite(fd, t, flags)
389 updated += 1
392 updated += 1
390 elif m == "dr": # divergent renames
393 elif m == "dr": # divergent renames
391 fl = a[2]
394 fl = a[2]
392 repo.ui.warn(_("note: possible conflict - %s was renamed "
395 repo.ui.warn(_("note: possible conflict - %s was renamed "
393 "multiple times to:\n") % f)
396 "multiple times to:\n") % f)
394 for nf in fl:
397 for nf in fl:
395 repo.ui.warn(" %s\n" % nf)
398 repo.ui.warn(" %s\n" % nf)
396 elif m == "e": # exec
399 elif m == "e": # exec
397 flags = a[2]
400 flags = a[2]
398 repo.wopener.audit(f)
401 repo.wopener.audit(f)
399 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
402 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
400 ms.commit()
403 ms.commit()
401 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
404 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
402
405
403 return updated, merged, removed, unresolved
406 return updated, merged, removed, unresolved
404
407
405 def recordupdates(repo, action, branchmerge):
408 def recordupdates(repo, action, branchmerge):
406 "record merge actions to the dirstate"
409 "record merge actions to the dirstate"
407
410
408 for a in action:
411 for a in action:
409 f, m = a[:2]
412 f, m = a[:2]
410 if m == "r": # remove
413 if m == "r": # remove
411 if branchmerge:
414 if branchmerge:
412 repo.dirstate.remove(f)
415 repo.dirstate.remove(f)
413 else:
416 else:
414 repo.dirstate.drop(f)
417 repo.dirstate.drop(f)
415 elif m == "a": # re-add
418 elif m == "a": # re-add
416 if not branchmerge:
419 if not branchmerge:
417 repo.dirstate.add(f)
420 repo.dirstate.add(f)
418 elif m == "f": # forget
421 elif m == "f": # forget
419 repo.dirstate.drop(f)
422 repo.dirstate.drop(f)
420 elif m == "e": # exec change
423 elif m == "e": # exec change
421 repo.dirstate.normallookup(f)
424 repo.dirstate.normallookup(f)
422 elif m == "g": # get
425 elif m == "g": # get
423 if branchmerge:
426 if branchmerge:
424 repo.dirstate.otherparent(f)
427 repo.dirstate.otherparent(f)
425 else:
428 else:
426 repo.dirstate.normal(f)
429 repo.dirstate.normal(f)
427 elif m == "m": # merge
430 elif m == "m": # merge
428 f2, fd, flag, move = a[2:]
431 f2, fd, flag, move = a[2:]
429 if branchmerge:
432 if branchmerge:
430 # We've done a branch merge, mark this file as merged
433 # We've done a branch merge, mark this file as merged
431 # so that we properly record the merger later
434 # so that we properly record the merger later
432 repo.dirstate.merge(fd)
435 repo.dirstate.merge(fd)
433 if f != f2: # copy/rename
436 if f != f2: # copy/rename
434 if move:
437 if move:
435 repo.dirstate.remove(f)
438 repo.dirstate.remove(f)
436 if f != fd:
439 if f != fd:
437 repo.dirstate.copy(f, fd)
440 repo.dirstate.copy(f, fd)
438 else:
441 else:
439 repo.dirstate.copy(f2, fd)
442 repo.dirstate.copy(f2, fd)
440 else:
443 else:
441 # We've update-merged a locally modified file, so
444 # We've update-merged a locally modified file, so
442 # we set the dirstate to emulate a normal checkout
445 # we set the dirstate to emulate a normal checkout
443 # of that file some time in the past. Thus our
446 # of that file some time in the past. Thus our
444 # merge will appear as a normal local file
447 # merge will appear as a normal local file
445 # modification.
448 # modification.
446 if f2 == fd: # file not locally copied/moved
449 if f2 == fd: # file not locally copied/moved
447 repo.dirstate.normallookup(fd)
450 repo.dirstate.normallookup(fd)
448 if move:
451 if move:
449 repo.dirstate.drop(f)
452 repo.dirstate.drop(f)
450 elif m == "d": # directory rename
453 elif m == "d": # directory rename
451 f2, fd, flag = a[2:]
454 f2, fd, flag = a[2:]
452 if not f2 and f not in repo.dirstate:
455 if not f2 and f not in repo.dirstate:
453 # untracked file moved
456 # untracked file moved
454 continue
457 continue
455 if branchmerge:
458 if branchmerge:
456 repo.dirstate.add(fd)
459 repo.dirstate.add(fd)
457 if f:
460 if f:
458 repo.dirstate.remove(f)
461 repo.dirstate.remove(f)
459 repo.dirstate.copy(f, fd)
462 repo.dirstate.copy(f, fd)
460 if f2:
463 if f2:
461 repo.dirstate.copy(f2, fd)
464 repo.dirstate.copy(f2, fd)
462 else:
465 else:
463 repo.dirstate.normal(fd)
466 repo.dirstate.normal(fd)
464 if f:
467 if f:
465 repo.dirstate.drop(f)
468 repo.dirstate.drop(f)
466
469
467 def update(repo, node, branchmerge, force, partial, ancestor=None):
470 def update(repo, node, branchmerge, force, partial, ancestor=None):
468 """
471 """
469 Perform a merge between the working directory and the given node
472 Perform a merge between the working directory and the given node
470
473
471 node = the node to update to, or None if unspecified
474 node = the node to update to, or None if unspecified
472 branchmerge = whether to merge between branches
475 branchmerge = whether to merge between branches
473 force = whether to force branch merging or file overwriting
476 force = whether to force branch merging or file overwriting
474 partial = a function to filter file lists (dirstate not updated)
477 partial = a function to filter file lists (dirstate not updated)
475
478
476 The table below shows all the behaviors of the update command
479 The table below shows all the behaviors of the update command
477 given the -c and -C or no options, whether the working directory
480 given the -c and -C or no options, whether the working directory
478 is dirty, whether a revision is specified, and the relationship of
481 is dirty, whether a revision is specified, and the relationship of
479 the parent rev to the target rev (linear, on the same named
482 the parent rev to the target rev (linear, on the same named
480 branch, or on another named branch).
483 branch, or on another named branch).
481
484
482 This logic is tested by test-update-branches.t.
485 This logic is tested by test-update-branches.t.
483
486
484 -c -C dirty rev | linear same cross
487 -c -C dirty rev | linear same cross
485 n n n n | ok (1) x
488 n n n n | ok (1) x
486 n n n y | ok ok ok
489 n n n y | ok ok ok
487 n n y * | merge (2) (2)
490 n n y * | merge (2) (2)
488 n y * * | --- discard ---
491 n y * * | --- discard ---
489 y n y * | --- (3) ---
492 y n y * | --- (3) ---
490 y n n * | --- ok ---
493 y n n * | --- ok ---
491 y y * * | --- (4) ---
494 y y * * | --- (4) ---
492
495
493 x = can't happen
496 x = can't happen
494 * = don't-care
497 * = don't-care
495 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
498 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
496 2 = abort: crosses branches (use 'hg merge' to merge or
499 2 = abort: crosses branches (use 'hg merge' to merge or
497 use 'hg update -C' to discard changes)
500 use 'hg update -C' to discard changes)
498 3 = abort: uncommitted local changes
501 3 = abort: uncommitted local changes
499 4 = incompatible options (checked in commands.py)
502 4 = incompatible options (checked in commands.py)
500
503
501 Return the same tuple as applyupdates().
504 Return the same tuple as applyupdates().
502 """
505 """
503
506
504 onode = node
507 onode = node
505 wlock = repo.wlock()
508 wlock = repo.wlock()
506 try:
509 try:
507 wc = repo[None]
510 wc = repo[None]
508 if node is None:
511 if node is None:
509 # tip of current branch
512 # tip of current branch
510 try:
513 try:
511 node = repo.branchtags()[wc.branch()]
514 node = repo.branchtags()[wc.branch()]
512 except KeyError:
515 except KeyError:
513 if wc.branch() == "default": # no default branch!
516 if wc.branch() == "default": # no default branch!
514 node = repo.lookup("tip") # update to tip
517 node = repo.lookup("tip") # update to tip
515 else:
518 else:
516 raise util.Abort(_("branch %s not found") % wc.branch())
519 raise util.Abort(_("branch %s not found") % wc.branch())
517 overwrite = force and not branchmerge
520 overwrite = force and not branchmerge
518 pl = wc.parents()
521 pl = wc.parents()
519 p1, p2 = pl[0], repo[node]
522 p1, p2 = pl[0], repo[node]
520 if ancestor:
523 if ancestor:
521 pa = repo[ancestor]
524 pa = repo[ancestor]
522 else:
525 else:
523 pa = p1.ancestor(p2)
526 pa = p1.ancestor(p2)
524
527
525 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
528 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
526
529
527 ### check phase
530 ### check phase
528 if not overwrite and len(pl) > 1:
531 if not overwrite and len(pl) > 1:
529 raise util.Abort(_("outstanding uncommitted merges"))
532 raise util.Abort(_("outstanding uncommitted merges"))
530 if branchmerge:
533 if branchmerge:
531 if pa == p2:
534 if pa == p2:
532 raise util.Abort(_("merging with a working directory ancestor"
535 raise util.Abort(_("merging with a working directory ancestor"
533 " has no effect"))
536 " has no effect"))
534 elif pa == p1:
537 elif pa == p1:
535 if p1.branch() == p2.branch():
538 if p1.branch() == p2.branch():
536 raise util.Abort(_("nothing to merge"),
539 raise util.Abort(_("nothing to merge"),
537 hint=_("use 'hg update' "
540 hint=_("use 'hg update' "
538 "or check 'hg heads'"))
541 "or check 'hg heads'"))
539 if not force and (wc.files() or wc.deleted()):
542 if not force and (wc.files() or wc.deleted()):
540 raise util.Abort(_("outstanding uncommitted changes"),
543 raise util.Abort(_("outstanding uncommitted changes"),
541 hint=_("use 'hg status' to list changes"))
544 hint=_("use 'hg status' to list changes"))
542 for s in wc.substate:
545 for s in wc.substate:
543 if wc.sub(s).dirty():
546 if wc.sub(s).dirty():
544 raise util.Abort(_("outstanding uncommitted changes in "
547 raise util.Abort(_("outstanding uncommitted changes in "
545 "subrepository '%s'") % s)
548 "subrepository '%s'") % s)
546
549
547 elif not overwrite:
550 elif not overwrite:
548 if pa == p1 or pa == p2: # linear
551 if pa == p1 or pa == p2: # linear
549 pass # all good
552 pass # all good
550 elif wc.dirty(missing=True):
553 elif wc.dirty(missing=True):
551 raise util.Abort(_("crosses branches (merge branches or use"
554 raise util.Abort(_("crosses branches (merge branches or use"
552 " --clean to discard changes)"))
555 " --clean to discard changes)"))
553 elif onode is None:
556 elif onode is None:
554 raise util.Abort(_("crosses branches (merge branches or update"
557 raise util.Abort(_("crosses branches (merge branches or update"
555 " --check to force update)"))
558 " --check to force update)"))
556 else:
559 else:
557 # Allow jumping branches if clean and specific rev given
560 # Allow jumping branches if clean and specific rev given
558 overwrite = True
561 overwrite = True
559
562
560 ### calculate phase
563 ### calculate phase
561 action = []
564 action = []
562 wc.status(unknown=True) # prime cache
565 wc.status(unknown=True) # prime cache
563 folding = not util.checkcase(repo.path)
566 folding = not util.checkcase(repo.path)
564 if not force:
567 if not force:
565 _checkunknown(wc, p2, folding)
568 _checkunknown(wc, p2, folding)
566 if folding:
569 if folding:
567 _checkcollision(p2, branchmerge and p1)
570 _checkcollision(p2, branchmerge and p1)
568 action += _forgetremoved(wc, p2, branchmerge)
571 action += _forgetremoved(wc, p2, branchmerge)
569 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
572 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
570
573
571 ### apply phase
574 ### apply phase
572 if not branchmerge: # just jump to the new rev
575 if not branchmerge: # just jump to the new rev
573 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
576 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
574 if not partial:
577 if not partial:
575 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
578 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
576
579
577 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
580 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
578
581
579 if not partial:
582 if not partial:
580 repo.dirstate.setparents(fp1, fp2)
583 repo.dirstate.setparents(fp1, fp2)
581 recordupdates(repo, action, branchmerge)
584 recordupdates(repo, action, branchmerge)
582 if not branchmerge:
585 if not branchmerge:
583 repo.dirstate.setbranch(p2.branch())
586 repo.dirstate.setbranch(p2.branch())
584 finally:
587 finally:
585 wlock.release()
588 wlock.release()
586
589
587 if not partial:
590 if not partial:
588 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
591 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
589 return stats
592 return stats
General Comments 0
You need to be logged in to leave comments. Login now