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