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