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