##// END OF EJS Templates
merge: refactor action calculation into function...
David Schleimer -
r18035:5881d5b7 default
parent child Browse files
Show More
@@ -1,644 +1,652
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):
452 "Calculate the actions needed to merge mctx into tctx"
453 action = []
454 folding = not util.checkcase(repo.path)
455 if folding:
456 # collision check is not needed for clean update
457 if (not branchmerge and
458 (force or not tctx.dirty(missing=True, branch=False))):
459 _checkcollision(mctx, None)
460 else:
461 _checkcollision(mctx, tctx)
462 if not force:
463 _checkunknown(repo, tctx, mctx)
464 action += _forgetremoved(tctx, mctx, branchmerge)
465 action += manifestmerge(repo, tctx, mctx,
466 ancestor,
467 force and not branchmerge,
468 partial)
469 return action
470
451 def recordupdates(repo, action, branchmerge):
471 def recordupdates(repo, action, branchmerge):
452 "record merge actions to the dirstate"
472 "record merge actions to the dirstate"
453
473
454 for a in action:
474 for a in action:
455 f, m = a[:2]
475 f, m = a[:2]
456 if m == "r": # remove
476 if m == "r": # remove
457 if branchmerge:
477 if branchmerge:
458 repo.dirstate.remove(f)
478 repo.dirstate.remove(f)
459 else:
479 else:
460 repo.dirstate.drop(f)
480 repo.dirstate.drop(f)
461 elif m == "a": # re-add
481 elif m == "a": # re-add
462 if not branchmerge:
482 if not branchmerge:
463 repo.dirstate.add(f)
483 repo.dirstate.add(f)
464 elif m == "f": # forget
484 elif m == "f": # forget
465 repo.dirstate.drop(f)
485 repo.dirstate.drop(f)
466 elif m == "e": # exec change
486 elif m == "e": # exec change
467 repo.dirstate.normallookup(f)
487 repo.dirstate.normallookup(f)
468 elif m == "g": # get
488 elif m == "g": # get
469 if branchmerge:
489 if branchmerge:
470 repo.dirstate.otherparent(f)
490 repo.dirstate.otherparent(f)
471 else:
491 else:
472 repo.dirstate.normal(f)
492 repo.dirstate.normal(f)
473 elif m == "m": # merge
493 elif m == "m": # merge
474 f2, fd, flag, move = a[2:]
494 f2, fd, flag, move = a[2:]
475 if branchmerge:
495 if branchmerge:
476 # We've done a branch merge, mark this file as merged
496 # We've done a branch merge, mark this file as merged
477 # so that we properly record the merger later
497 # so that we properly record the merger later
478 repo.dirstate.merge(fd)
498 repo.dirstate.merge(fd)
479 if f != f2: # copy/rename
499 if f != f2: # copy/rename
480 if move:
500 if move:
481 repo.dirstate.remove(f)
501 repo.dirstate.remove(f)
482 if f != fd:
502 if f != fd:
483 repo.dirstate.copy(f, fd)
503 repo.dirstate.copy(f, fd)
484 else:
504 else:
485 repo.dirstate.copy(f2, fd)
505 repo.dirstate.copy(f2, fd)
486 else:
506 else:
487 # We've update-merged a locally modified file, so
507 # We've update-merged a locally modified file, so
488 # we set the dirstate to emulate a normal checkout
508 # we set the dirstate to emulate a normal checkout
489 # of that file some time in the past. Thus our
509 # of that file some time in the past. Thus our
490 # merge will appear as a normal local file
510 # merge will appear as a normal local file
491 # modification.
511 # modification.
492 if f2 == fd: # file not locally copied/moved
512 if f2 == fd: # file not locally copied/moved
493 repo.dirstate.normallookup(fd)
513 repo.dirstate.normallookup(fd)
494 if move:
514 if move:
495 repo.dirstate.drop(f)
515 repo.dirstate.drop(f)
496 elif m == "d": # directory rename
516 elif m == "d": # directory rename
497 f2, fd, flag = a[2:]
517 f2, fd, flag = a[2:]
498 if not f2 and f not in repo.dirstate:
518 if not f2 and f not in repo.dirstate:
499 # untracked file moved
519 # untracked file moved
500 continue
520 continue
501 if branchmerge:
521 if branchmerge:
502 repo.dirstate.add(fd)
522 repo.dirstate.add(fd)
503 if f:
523 if f:
504 repo.dirstate.remove(f)
524 repo.dirstate.remove(f)
505 repo.dirstate.copy(f, fd)
525 repo.dirstate.copy(f, fd)
506 if f2:
526 if f2:
507 repo.dirstate.copy(f2, fd)
527 repo.dirstate.copy(f2, fd)
508 else:
528 else:
509 repo.dirstate.normal(fd)
529 repo.dirstate.normal(fd)
510 if f:
530 if f:
511 repo.dirstate.drop(f)
531 repo.dirstate.drop(f)
512
532
513 def update(repo, node, branchmerge, force, partial, ancestor=None,
533 def update(repo, node, branchmerge, force, partial, ancestor=None,
514 mergeancestor=False):
534 mergeancestor=False):
515 """
535 """
516 Perform a merge between the working directory and the given node
536 Perform a merge between the working directory and the given node
517
537
518 node = the node to update to, or None if unspecified
538 node = the node to update to, or None if unspecified
519 branchmerge = whether to merge between branches
539 branchmerge = whether to merge between branches
520 force = whether to force branch merging or file overwriting
540 force = whether to force branch merging or file overwriting
521 partial = a function to filter file lists (dirstate not updated)
541 partial = a function to filter file lists (dirstate not updated)
522 mergeancestor = if false, merging with an ancestor (fast-forward)
542 mergeancestor = if false, merging with an ancestor (fast-forward)
523 is only allowed between different named branches. This flag
543 is only allowed between different named branches. This flag
524 is used by rebase extension as a temporary fix and should be
544 is used by rebase extension as a temporary fix and should be
525 avoided in general.
545 avoided in general.
526
546
527 The table below shows all the behaviors of the update command
547 The table below shows all the behaviors of the update command
528 given the -c and -C or no options, whether the working directory
548 given the -c and -C or no options, whether the working directory
529 is dirty, whether a revision is specified, and the relationship of
549 is dirty, whether a revision is specified, and the relationship of
530 the parent rev to the target rev (linear, on the same named
550 the parent rev to the target rev (linear, on the same named
531 branch, or on another named branch).
551 branch, or on another named branch).
532
552
533 This logic is tested by test-update-branches.t.
553 This logic is tested by test-update-branches.t.
534
554
535 -c -C dirty rev | linear same cross
555 -c -C dirty rev | linear same cross
536 n n n n | ok (1) x
556 n n n n | ok (1) x
537 n n n y | ok ok ok
557 n n n y | ok ok ok
538 n n y * | merge (2) (2)
558 n n y * | merge (2) (2)
539 n y * * | --- discard ---
559 n y * * | --- discard ---
540 y n y * | --- (3) ---
560 y n y * | --- (3) ---
541 y n n * | --- ok ---
561 y n n * | --- ok ---
542 y y * * | --- (4) ---
562 y y * * | --- (4) ---
543
563
544 x = can't happen
564 x = can't happen
545 * = don't-care
565 * = don't-care
546 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
566 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
547 2 = abort: crosses branches (use 'hg merge' to merge or
567 2 = abort: crosses branches (use 'hg merge' to merge or
548 use 'hg update -C' to discard changes)
568 use 'hg update -C' to discard changes)
549 3 = abort: uncommitted local changes
569 3 = abort: uncommitted local changes
550 4 = incompatible options (checked in commands.py)
570 4 = incompatible options (checked in commands.py)
551
571
552 Return the same tuple as applyupdates().
572 Return the same tuple as applyupdates().
553 """
573 """
554
574
555 onode = node
575 onode = node
556 wlock = repo.wlock()
576 wlock = repo.wlock()
557 try:
577 try:
558 wc = repo[None]
578 wc = repo[None]
559 if node is None:
579 if node is None:
560 # tip of current branch
580 # tip of current branch
561 try:
581 try:
562 node = repo.branchtip(wc.branch())
582 node = repo.branchtip(wc.branch())
563 except error.RepoLookupError:
583 except error.RepoLookupError:
564 if wc.branch() == "default": # no default branch!
584 if wc.branch() == "default": # no default branch!
565 node = repo.lookup("tip") # update to tip
585 node = repo.lookup("tip") # update to tip
566 else:
586 else:
567 raise util.Abort(_("branch %s not found") % wc.branch())
587 raise util.Abort(_("branch %s not found") % wc.branch())
568 overwrite = force and not branchmerge
588 overwrite = force and not branchmerge
569 pl = wc.parents()
589 pl = wc.parents()
570 p1, p2 = pl[0], repo[node]
590 p1, p2 = pl[0], repo[node]
571 if ancestor:
591 if ancestor:
572 pa = repo[ancestor]
592 pa = repo[ancestor]
573 else:
593 else:
574 pa = p1.ancestor(p2)
594 pa = p1.ancestor(p2)
575
595
576 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
596 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
577
597
578 ### check phase
598 ### check phase
579 if not overwrite and len(pl) > 1:
599 if not overwrite and len(pl) > 1:
580 raise util.Abort(_("outstanding uncommitted merges"))
600 raise util.Abort(_("outstanding uncommitted merges"))
581 if branchmerge:
601 if branchmerge:
582 if pa == p2:
602 if pa == p2:
583 raise util.Abort(_("merging with a working directory ancestor"
603 raise util.Abort(_("merging with a working directory ancestor"
584 " has no effect"))
604 " has no effect"))
585 elif pa == p1:
605 elif pa == p1:
586 if not mergeancestor and p1.branch() == p2.branch():
606 if not mergeancestor and p1.branch() == p2.branch():
587 raise util.Abort(_("nothing to merge"),
607 raise util.Abort(_("nothing to merge"),
588 hint=_("use 'hg update' "
608 hint=_("use 'hg update' "
589 "or check 'hg heads'"))
609 "or check 'hg heads'"))
590 if not force and (wc.files() or wc.deleted()):
610 if not force and (wc.files() or wc.deleted()):
591 raise util.Abort(_("outstanding uncommitted changes"),
611 raise util.Abort(_("outstanding uncommitted changes"),
592 hint=_("use 'hg status' to list changes"))
612 hint=_("use 'hg status' to list changes"))
593 for s in wc.substate:
613 for s in wc.substate:
594 if wc.sub(s).dirty():
614 if wc.sub(s).dirty():
595 raise util.Abort(_("outstanding uncommitted changes in "
615 raise util.Abort(_("outstanding uncommitted changes in "
596 "subrepository '%s'") % s)
616 "subrepository '%s'") % s)
597
617
598 elif not overwrite:
618 elif not overwrite:
599 if pa == p1 or pa == p2: # linear
619 if pa == p1 or pa == p2: # linear
600 pass # all good
620 pass # all good
601 elif wc.dirty(missing=True):
621 elif wc.dirty(missing=True):
602 raise util.Abort(_("crosses branches (merge branches or use"
622 raise util.Abort(_("crosses branches (merge branches or use"
603 " --clean to discard changes)"))
623 " --clean to discard changes)"))
604 elif onode is None:
624 elif onode is None:
605 raise util.Abort(_("crosses branches (merge branches or update"
625 raise util.Abort(_("crosses branches (merge branches or update"
606 " --check to force update)"))
626 " --check to force update)"))
607 else:
627 else:
608 # Allow jumping branches if clean and specific rev given
628 # Allow jumping branches if clean and specific rev given
609 pa = p1
629 pa = p1
610
630
611 ### calculate phase
631 ### calculate phase
612 action = []
632 action = calculateupdates(repo, wc, p2, pa, branchmerge, force, partial)
613 folding = not util.checkcase(repo.path)
614 if folding:
615 # collision check is not needed for clean update
616 if (not branchmerge and
617 (force or not wc.dirty(missing=True, branch=False))):
618 _checkcollision(p2, None)
619 else:
620 _checkcollision(p2, (wc, pa))
621 if not force:
622 _checkunknown(repo, wc, p2)
623 action += _forgetremoved(wc, p2, branchmerge)
624 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
625
633
626 ### apply phase
634 ### apply phase
627 if not branchmerge: # just jump to the new rev
635 if not branchmerge: # just jump to the new rev
628 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
636 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
629 if not partial:
637 if not partial:
630 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
638 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
631
639
632 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
640 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
633
641
634 if not partial:
642 if not partial:
635 repo.setparents(fp1, fp2)
643 repo.setparents(fp1, fp2)
636 recordupdates(repo, action, branchmerge)
644 recordupdates(repo, action, branchmerge)
637 if not branchmerge:
645 if not branchmerge:
638 repo.dirstate.setbranch(p2.branch())
646 repo.dirstate.setbranch(p2.branch())
639 finally:
647 finally:
640 wlock.release()
648 wlock.release()
641
649
642 if not partial:
650 if not partial:
643 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
651 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
644 return stats
652 return stats
General Comments 0
You need to be logged in to leave comments. Login now