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