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