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