##// END OF EJS Templates
resolve: do not crash on empty mergestate...
Martin Geisler -
r11451:51021f4c stable
parent child Browse files
Show More
@@ -1,522 +1,520 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 util, filemerge, copies, subrepo
10 import 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._read()
17 self._read()
18 def reset(self, node=None):
18 def reset(self, node=None):
19 self._state = {}
19 self._state = {}
20 if node:
20 if node:
21 self._local = node
21 self._local = node
22 shutil.rmtree(self._repo.join("merge"), True)
22 shutil.rmtree(self._repo.join("merge"), True)
23 def _read(self):
23 def _read(self):
24 self._state = {}
24 self._state = {}
25 try:
25 try:
26 localnode = None
27 f = self._repo.opener("merge/state")
26 f = self._repo.opener("merge/state")
28 for i, l in enumerate(f):
27 for i, l in enumerate(f):
29 if i == 0:
28 if i == 0:
30 localnode = l[:-1]
29 self._local = bin(l[:-1])
31 else:
30 else:
32 bits = l[:-1].split("\0")
31 bits = l[:-1].split("\0")
33 self._state[bits[0]] = bits[1:]
32 self._state[bits[0]] = bits[1:]
34 self._local = bin(localnode)
35 except IOError, err:
33 except IOError, err:
36 if err.errno != errno.ENOENT:
34 if err.errno != errno.ENOENT:
37 raise
35 raise
38 def _write(self):
36 def _write(self):
39 f = self._repo.opener("merge/state", "w")
37 f = self._repo.opener("merge/state", "w")
40 f.write(hex(self._local) + "\n")
38 f.write(hex(self._local) + "\n")
41 for d, v in self._state.iteritems():
39 for d, v in self._state.iteritems():
42 f.write("\0".join([d] + v) + "\n")
40 f.write("\0".join([d] + v) + "\n")
43 def add(self, fcl, fco, fca, fd, flags):
41 def add(self, fcl, fco, fca, fd, flags):
44 hash = util.sha1(fcl.path()).hexdigest()
42 hash = util.sha1(fcl.path()).hexdigest()
45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
47 hex(fca.filenode()), fco.path(), flags]
45 hex(fca.filenode()), fco.path(), flags]
48 self._write()
46 self._write()
49 def __contains__(self, dfile):
47 def __contains__(self, dfile):
50 return dfile in self._state
48 return dfile in self._state
51 def __getitem__(self, dfile):
49 def __getitem__(self, dfile):
52 return self._state[dfile][0]
50 return self._state[dfile][0]
53 def __iter__(self):
51 def __iter__(self):
54 l = self._state.keys()
52 l = self._state.keys()
55 l.sort()
53 l.sort()
56 for f in l:
54 for f in l:
57 yield f
55 yield f
58 def mark(self, dfile, state):
56 def mark(self, dfile, state):
59 self._state[dfile][0] = state
57 self._state[dfile][0] = state
60 self._write()
58 self._write()
61 def resolve(self, dfile, wctx, octx):
59 def resolve(self, dfile, wctx, octx):
62 if self[dfile] == 'r':
60 if self[dfile] == 'r':
63 return 0
61 return 0
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
65 f = self._repo.opener("merge/" + hash)
63 f = self._repo.opener("merge/" + hash)
66 self._repo.wwrite(dfile, f.read(), flags)
64 self._repo.wwrite(dfile, f.read(), flags)
67 fcd = wctx[dfile]
65 fcd = wctx[dfile]
68 fco = octx[ofile]
66 fco = octx[ofile]
69 fca = self._repo.filectx(afile, fileid=anode)
67 fca = self._repo.filectx(afile, fileid=anode)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
71 if not r:
69 if not r:
72 self.mark(dfile, 'r')
70 self.mark(dfile, 'r')
73 return r
71 return r
74
72
75 def _checkunknown(wctx, mctx):
73 def _checkunknown(wctx, mctx):
76 "check for collisions between unknown files and files in mctx"
74 "check for collisions between unknown files and files in mctx"
77 for f in wctx.unknown():
75 for f in wctx.unknown():
78 if f in mctx and mctx[f].cmp(wctx[f].data()):
76 if f in mctx and mctx[f].cmp(wctx[f].data()):
79 raise util.Abort(_("untracked file in working directory differs"
77 raise util.Abort(_("untracked file in working directory differs"
80 " from file in requested revision: '%s'") % f)
78 " from file in requested revision: '%s'") % f)
81
79
82 def _checkcollision(mctx):
80 def _checkcollision(mctx):
83 "check for case folding collisions in the destination context"
81 "check for case folding collisions in the destination context"
84 folded = {}
82 folded = {}
85 for fn in mctx:
83 for fn in mctx:
86 fold = fn.lower()
84 fold = fn.lower()
87 if fold in folded:
85 if fold in folded:
88 raise util.Abort(_("case-folding collision between %s and %s")
86 raise util.Abort(_("case-folding collision between %s and %s")
89 % (fn, folded[fold]))
87 % (fn, folded[fold]))
90 folded[fold] = fn
88 folded[fold] = fn
91
89
92 def _forgetremoved(wctx, mctx, branchmerge):
90 def _forgetremoved(wctx, mctx, branchmerge):
93 """
91 """
94 Forget removed files
92 Forget removed files
95
93
96 If we're jumping between revisions (as opposed to merging), and if
94 If we're jumping between revisions (as opposed to merging), and if
97 neither the working directory nor the target rev has the file,
95 neither the working directory nor the target rev has the file,
98 then we need to remove it from the dirstate, to prevent the
96 then we need to remove it from the dirstate, to prevent the
99 dirstate from listing the file when it is no longer in the
97 dirstate from listing the file when it is no longer in the
100 manifest.
98 manifest.
101
99
102 If we're merging, and the other revision has removed a file
100 If we're merging, and the other revision has removed a file
103 that is not present in the working directory, we need to mark it
101 that is not present in the working directory, we need to mark it
104 as removed.
102 as removed.
105 """
103 """
106
104
107 action = []
105 action = []
108 state = branchmerge and 'r' or 'f'
106 state = branchmerge and 'r' or 'f'
109 for f in wctx.deleted():
107 for f in wctx.deleted():
110 if f not in mctx:
108 if f not in mctx:
111 action.append((f, state))
109 action.append((f, state))
112
110
113 if not branchmerge:
111 if not branchmerge:
114 for f in wctx.removed():
112 for f in wctx.removed():
115 if f not in mctx:
113 if f not in mctx:
116 action.append((f, "f"))
114 action.append((f, "f"))
117
115
118 return action
116 return action
119
117
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
118 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
121 """
119 """
122 Merge p1 and p2 with ancestor ma and generate merge action list
120 Merge p1 and p2 with ancestor ma and generate merge action list
123
121
124 overwrite = whether we clobber working files
122 overwrite = whether we clobber working files
125 partial = function to filter file lists
123 partial = function to filter file lists
126 """
124 """
127
125
128 def fmerge(f, f2, fa):
126 def fmerge(f, f2, fa):
129 """merge flags"""
127 """merge flags"""
130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
128 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
131 if m == n: # flags agree
129 if m == n: # flags agree
132 return m # unchanged
130 return m # unchanged
133 if m and n and not a: # flags set, don't agree, differ from parent
131 if m and n and not a: # flags set, don't agree, differ from parent
134 r = repo.ui.promptchoice(
132 r = repo.ui.promptchoice(
135 _(" conflicting flags for %s\n"
133 _(" conflicting flags for %s\n"
136 "(n)one, e(x)ec or sym(l)ink?") % f,
134 "(n)one, e(x)ec or sym(l)ink?") % f,
137 (_("&None"), _("E&xec"), _("Sym&link")), 0)
135 (_("&None"), _("E&xec"), _("Sym&link")), 0)
138 if r == 1:
136 if r == 1:
139 return "x" # Exec
137 return "x" # Exec
140 if r == 2:
138 if r == 2:
141 return "l" # Symlink
139 return "l" # Symlink
142 return ""
140 return ""
143 if m and m != a: # changed from a to m
141 if m and m != a: # changed from a to m
144 return m
142 return m
145 if n and n != a: # changed from a to n
143 if n and n != a: # changed from a to n
146 return n
144 return n
147 return '' # flag was cleared
145 return '' # flag was cleared
148
146
149 def act(msg, m, f, *args):
147 def act(msg, m, f, *args):
150 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
148 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
151 action.append((f, m) + args)
149 action.append((f, m) + args)
152
150
153 action, copy = [], {}
151 action, copy = [], {}
154
152
155 if overwrite:
153 if overwrite:
156 pa = p1
154 pa = p1
157 elif pa == p2: # backwards
155 elif pa == p2: # backwards
158 pa = p1.p1()
156 pa = p1.p1()
159 elif pa and repo.ui.configbool("merge", "followcopies", True):
157 elif pa and repo.ui.configbool("merge", "followcopies", True):
160 dirs = repo.ui.configbool("merge", "followdirs", True)
158 dirs = repo.ui.configbool("merge", "followdirs", True)
161 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
159 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
162 for of, fl in diverge.iteritems():
160 for of, fl in diverge.iteritems():
163 act("divergent renames", "dr", of, fl)
161 act("divergent renames", "dr", of, fl)
164
162
165 repo.ui.note(_("resolving manifests\n"))
163 repo.ui.note(_("resolving manifests\n"))
166 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
164 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
167 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
165 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
168
166
169 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
167 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
170 copied = set(copy.values())
168 copied = set(copy.values())
171
169
172 if not overwrite and '.hgsubstate' in m1:
170 if not overwrite and '.hgsubstate' in m1:
173 # check whether sub state is modified
171 # check whether sub state is modified
174 for s in p1.substate:
172 for s in p1.substate:
175 if p1.sub(s).dirty():
173 if p1.sub(s).dirty():
176 m1['.hgsubstate'] += "+"
174 m1['.hgsubstate'] += "+"
177 break
175 break
178
176
179 # Compare manifests
177 # Compare manifests
180 for f, n in m1.iteritems():
178 for f, n in m1.iteritems():
181 if partial and not partial(f):
179 if partial and not partial(f):
182 continue
180 continue
183 if f in m2:
181 if f in m2:
184 rflags = fmerge(f, f, f)
182 rflags = fmerge(f, f, f)
185 a = ma.get(f, nullid)
183 a = ma.get(f, nullid)
186 if n == m2[f] or m2[f] == a: # same or local newer
184 if n == m2[f] or m2[f] == a: # same or local newer
187 if m1.flags(f) != rflags:
185 if m1.flags(f) != rflags:
188 act("update permissions", "e", f, rflags)
186 act("update permissions", "e", f, rflags)
189 elif n == a: # remote newer
187 elif n == a: # remote newer
190 act("remote is newer", "g", f, rflags)
188 act("remote is newer", "g", f, rflags)
191 else: # both changed
189 else: # both changed
192 act("versions differ", "m", f, f, f, rflags, False)
190 act("versions differ", "m", f, f, f, rflags, False)
193 elif f in copied: # files we'll deal with on m2 side
191 elif f in copied: # files we'll deal with on m2 side
194 pass
192 pass
195 elif f in copy:
193 elif f in copy:
196 f2 = copy[f]
194 f2 = copy[f]
197 if f2 not in m2: # directory rename
195 if f2 not in m2: # directory rename
198 act("remote renamed directory to " + f2, "d",
196 act("remote renamed directory to " + f2, "d",
199 f, None, f2, m1.flags(f))
197 f, None, f2, m1.flags(f))
200 else: # case 2 A,B/B/B or case 4,21 A/B/B
198 else: # case 2 A,B/B/B or case 4,21 A/B/B
201 act("local copied/moved to " + f2, "m",
199 act("local copied/moved to " + f2, "m",
202 f, f2, f, fmerge(f, f2, f2), False)
200 f, f2, f, fmerge(f, f2, f2), False)
203 elif f in ma: # clean, a different, no remote
201 elif f in ma: # clean, a different, no remote
204 if n != ma[f]:
202 if n != ma[f]:
205 if repo.ui.promptchoice(
203 if repo.ui.promptchoice(
206 _(" local changed %s which remote deleted\n"
204 _(" local changed %s which remote deleted\n"
207 "use (c)hanged version or (d)elete?") % f,
205 "use (c)hanged version or (d)elete?") % f,
208 (_("&Changed"), _("&Delete")), 0):
206 (_("&Changed"), _("&Delete")), 0):
209 act("prompt delete", "r", f)
207 act("prompt delete", "r", f)
210 else:
208 else:
211 act("prompt keep", "a", f)
209 act("prompt keep", "a", f)
212 elif n[20:] == "a": # added, no remote
210 elif n[20:] == "a": # added, no remote
213 act("remote deleted", "f", f)
211 act("remote deleted", "f", f)
214 elif n[20:] != "u":
212 elif n[20:] != "u":
215 act("other deleted", "r", f)
213 act("other deleted", "r", f)
216
214
217 for f, n in m2.iteritems():
215 for f, n in m2.iteritems():
218 if partial and not partial(f):
216 if partial and not partial(f):
219 continue
217 continue
220 if f in m1 or f in copied: # files already visited
218 if f in m1 or f in copied: # files already visited
221 continue
219 continue
222 if f in copy:
220 if f in copy:
223 f2 = copy[f]
221 f2 = copy[f]
224 if f2 not in m1: # directory rename
222 if f2 not in m1: # directory rename
225 act("local renamed directory to " + f2, "d",
223 act("local renamed directory to " + f2, "d",
226 None, f, f2, m2.flags(f))
224 None, f, f2, m2.flags(f))
227 elif f2 in m2: # rename case 1, A/A,B/A
225 elif f2 in m2: # rename case 1, A/A,B/A
228 act("remote copied to " + f, "m",
226 act("remote copied to " + f, "m",
229 f2, f, f, fmerge(f2, f, f2), False)
227 f2, f, f, fmerge(f2, f, f2), False)
230 else: # case 3,20 A/B/A
228 else: # case 3,20 A/B/A
231 act("remote moved to " + f, "m",
229 act("remote moved to " + f, "m",
232 f2, f, f, fmerge(f2, f, f2), True)
230 f2, f, f, fmerge(f2, f, f2), True)
233 elif f not in ma:
231 elif f not in ma:
234 act("remote created", "g", f, m2.flags(f))
232 act("remote created", "g", f, m2.flags(f))
235 elif n != ma[f]:
233 elif n != ma[f]:
236 if repo.ui.promptchoice(
234 if repo.ui.promptchoice(
237 _("remote changed %s which local deleted\n"
235 _("remote changed %s which local deleted\n"
238 "use (c)hanged version or leave (d)eleted?") % f,
236 "use (c)hanged version or leave (d)eleted?") % f,
239 (_("&Changed"), _("&Deleted")), 0) == 0:
237 (_("&Changed"), _("&Deleted")), 0) == 0:
240 act("prompt recreating", "g", f, m2.flags(f))
238 act("prompt recreating", "g", f, m2.flags(f))
241
239
242 return action
240 return action
243
241
244 def actionkey(a):
242 def actionkey(a):
245 return a[1] == 'r' and -1 or 0, a
243 return a[1] == 'r' and -1 or 0, a
246
244
247 def applyupdates(repo, action, wctx, mctx):
245 def applyupdates(repo, action, wctx, mctx):
248 "apply the merge action list to the working directory"
246 "apply the merge action list to the working directory"
249
247
250 updated, merged, removed, unresolved = 0, 0, 0, 0
248 updated, merged, removed, unresolved = 0, 0, 0, 0
251 ms = mergestate(repo)
249 ms = mergestate(repo)
252 ms.reset(wctx.parents()[0].node())
250 ms.reset(wctx.parents()[0].node())
253 moves = []
251 moves = []
254 action.sort(key=actionkey)
252 action.sort(key=actionkey)
255 substate = wctx.substate # prime
253 substate = wctx.substate # prime
256
254
257 # prescan for merges
255 # prescan for merges
258 u = repo.ui
256 u = repo.ui
259 for a in action:
257 for a in action:
260 f, m = a[:2]
258 f, m = a[:2]
261 if m == 'm': # merge
259 if m == 'm': # merge
262 f2, fd, flags, move = a[2:]
260 f2, fd, flags, move = a[2:]
263 if f == '.hgsubstate': # merged internally
261 if f == '.hgsubstate': # merged internally
264 continue
262 continue
265 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
263 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
266 fcl = wctx[f]
264 fcl = wctx[f]
267 fco = mctx[f2]
265 fco = mctx[f2]
268 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
266 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
269 ms.add(fcl, fco, fca, fd, flags)
267 ms.add(fcl, fco, fca, fd, flags)
270 if f != fd and move:
268 if f != fd and move:
271 moves.append(f)
269 moves.append(f)
272
270
273 # remove renamed files after safely stored
271 # remove renamed files after safely stored
274 for f in moves:
272 for f in moves:
275 if util.lexists(repo.wjoin(f)):
273 if util.lexists(repo.wjoin(f)):
276 repo.ui.debug("removing %s\n" % f)
274 repo.ui.debug("removing %s\n" % f)
277 os.unlink(repo.wjoin(f))
275 os.unlink(repo.wjoin(f))
278
276
279 audit_path = util.path_auditor(repo.root)
277 audit_path = util.path_auditor(repo.root)
280
278
281 numupdates = len(action)
279 numupdates = len(action)
282 for i, a in enumerate(action):
280 for i, a in enumerate(action):
283 f, m = a[:2]
281 f, m = a[:2]
284 u.progress('update', i + 1, item=f, total=numupdates, unit='files')
282 u.progress('update', i + 1, item=f, total=numupdates, unit='files')
285 if f and f[0] == "/":
283 if f and f[0] == "/":
286 continue
284 continue
287 if m == "r": # remove
285 if m == "r": # remove
288 repo.ui.note(_("removing %s\n") % f)
286 repo.ui.note(_("removing %s\n") % f)
289 audit_path(f)
287 audit_path(f)
290 if f == '.hgsubstate': # subrepo states need updating
288 if f == '.hgsubstate': # subrepo states need updating
291 subrepo.submerge(repo, wctx, mctx, wctx)
289 subrepo.submerge(repo, wctx, mctx, wctx)
292 try:
290 try:
293 util.unlink(repo.wjoin(f))
291 util.unlink(repo.wjoin(f))
294 except OSError, inst:
292 except OSError, inst:
295 if inst.errno != errno.ENOENT:
293 if inst.errno != errno.ENOENT:
296 repo.ui.warn(_("update failed to remove %s: %s!\n") %
294 repo.ui.warn(_("update failed to remove %s: %s!\n") %
297 (f, inst.strerror))
295 (f, inst.strerror))
298 removed += 1
296 removed += 1
299 elif m == "m": # merge
297 elif m == "m": # merge
300 if f == '.hgsubstate': # subrepo states need updating
298 if f == '.hgsubstate': # subrepo states need updating
301 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
299 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
302 continue
300 continue
303 f2, fd, flags, move = a[2:]
301 f2, fd, flags, move = a[2:]
304 r = ms.resolve(fd, wctx, mctx)
302 r = ms.resolve(fd, wctx, mctx)
305 if r is not None and r > 0:
303 if r is not None and r > 0:
306 unresolved += 1
304 unresolved += 1
307 else:
305 else:
308 if r is None:
306 if r is None:
309 updated += 1
307 updated += 1
310 else:
308 else:
311 merged += 1
309 merged += 1
312 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
310 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
313 if f != fd and move and util.lexists(repo.wjoin(f)):
311 if f != fd and move and util.lexists(repo.wjoin(f)):
314 repo.ui.debug("removing %s\n" % f)
312 repo.ui.debug("removing %s\n" % f)
315 os.unlink(repo.wjoin(f))
313 os.unlink(repo.wjoin(f))
316 elif m == "g": # get
314 elif m == "g": # get
317 flags = a[2]
315 flags = a[2]
318 repo.ui.note(_("getting %s\n") % f)
316 repo.ui.note(_("getting %s\n") % f)
319 t = mctx.filectx(f).data()
317 t = mctx.filectx(f).data()
320 repo.wwrite(f, t, flags)
318 repo.wwrite(f, t, flags)
321 updated += 1
319 updated += 1
322 if f == '.hgsubstate': # subrepo states need updating
320 if f == '.hgsubstate': # subrepo states need updating
323 subrepo.submerge(repo, wctx, mctx, wctx)
321 subrepo.submerge(repo, wctx, mctx, wctx)
324 elif m == "d": # directory rename
322 elif m == "d": # directory rename
325 f2, fd, flags = a[2:]
323 f2, fd, flags = a[2:]
326 if f:
324 if f:
327 repo.ui.note(_("moving %s to %s\n") % (f, fd))
325 repo.ui.note(_("moving %s to %s\n") % (f, fd))
328 t = wctx.filectx(f).data()
326 t = wctx.filectx(f).data()
329 repo.wwrite(fd, t, flags)
327 repo.wwrite(fd, t, flags)
330 util.unlink(repo.wjoin(f))
328 util.unlink(repo.wjoin(f))
331 if f2:
329 if f2:
332 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
330 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
333 t = mctx.filectx(f2).data()
331 t = mctx.filectx(f2).data()
334 repo.wwrite(fd, t, flags)
332 repo.wwrite(fd, t, flags)
335 updated += 1
333 updated += 1
336 elif m == "dr": # divergent renames
334 elif m == "dr": # divergent renames
337 fl = a[2]
335 fl = a[2]
338 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
336 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
339 for nf in fl:
337 for nf in fl:
340 repo.ui.warn(" %s\n" % nf)
338 repo.ui.warn(" %s\n" % nf)
341 elif m == "e": # exec
339 elif m == "e": # exec
342 flags = a[2]
340 flags = a[2]
343 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
341 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
344 u.progress('update', None, total=numupdates, unit='files')
342 u.progress('update', None, total=numupdates, unit='files')
345
343
346 return updated, merged, removed, unresolved
344 return updated, merged, removed, unresolved
347
345
348 def recordupdates(repo, action, branchmerge):
346 def recordupdates(repo, action, branchmerge):
349 "record merge actions to the dirstate"
347 "record merge actions to the dirstate"
350
348
351 for a in action:
349 for a in action:
352 f, m = a[:2]
350 f, m = a[:2]
353 if m == "r": # remove
351 if m == "r": # remove
354 if branchmerge:
352 if branchmerge:
355 repo.dirstate.remove(f)
353 repo.dirstate.remove(f)
356 else:
354 else:
357 repo.dirstate.forget(f)
355 repo.dirstate.forget(f)
358 elif m == "a": # re-add
356 elif m == "a": # re-add
359 if not branchmerge:
357 if not branchmerge:
360 repo.dirstate.add(f)
358 repo.dirstate.add(f)
361 elif m == "f": # forget
359 elif m == "f": # forget
362 repo.dirstate.forget(f)
360 repo.dirstate.forget(f)
363 elif m == "e": # exec change
361 elif m == "e": # exec change
364 repo.dirstate.normallookup(f)
362 repo.dirstate.normallookup(f)
365 elif m == "g": # get
363 elif m == "g": # get
366 if branchmerge:
364 if branchmerge:
367 repo.dirstate.otherparent(f)
365 repo.dirstate.otherparent(f)
368 else:
366 else:
369 repo.dirstate.normal(f)
367 repo.dirstate.normal(f)
370 elif m == "m": # merge
368 elif m == "m": # merge
371 f2, fd, flag, move = a[2:]
369 f2, fd, flag, move = a[2:]
372 if branchmerge:
370 if branchmerge:
373 # We've done a branch merge, mark this file as merged
371 # We've done a branch merge, mark this file as merged
374 # so that we properly record the merger later
372 # so that we properly record the merger later
375 repo.dirstate.merge(fd)
373 repo.dirstate.merge(fd)
376 if f != f2: # copy/rename
374 if f != f2: # copy/rename
377 if move:
375 if move:
378 repo.dirstate.remove(f)
376 repo.dirstate.remove(f)
379 if f != fd:
377 if f != fd:
380 repo.dirstate.copy(f, fd)
378 repo.dirstate.copy(f, fd)
381 else:
379 else:
382 repo.dirstate.copy(f2, fd)
380 repo.dirstate.copy(f2, fd)
383 else:
381 else:
384 # We've update-merged a locally modified file, so
382 # We've update-merged a locally modified file, so
385 # we set the dirstate to emulate a normal checkout
383 # we set the dirstate to emulate a normal checkout
386 # of that file some time in the past. Thus our
384 # of that file some time in the past. Thus our
387 # merge will appear as a normal local file
385 # merge will appear as a normal local file
388 # modification.
386 # modification.
389 if f2 == fd: # file not locally copied/moved
387 if f2 == fd: # file not locally copied/moved
390 repo.dirstate.normallookup(fd)
388 repo.dirstate.normallookup(fd)
391 if move:
389 if move:
392 repo.dirstate.forget(f)
390 repo.dirstate.forget(f)
393 elif m == "d": # directory rename
391 elif m == "d": # directory rename
394 f2, fd, flag = a[2:]
392 f2, fd, flag = a[2:]
395 if not f2 and f not in repo.dirstate:
393 if not f2 and f not in repo.dirstate:
396 # untracked file moved
394 # untracked file moved
397 continue
395 continue
398 if branchmerge:
396 if branchmerge:
399 repo.dirstate.add(fd)
397 repo.dirstate.add(fd)
400 if f:
398 if f:
401 repo.dirstate.remove(f)
399 repo.dirstate.remove(f)
402 repo.dirstate.copy(f, fd)
400 repo.dirstate.copy(f, fd)
403 if f2:
401 if f2:
404 repo.dirstate.copy(f2, fd)
402 repo.dirstate.copy(f2, fd)
405 else:
403 else:
406 repo.dirstate.normal(fd)
404 repo.dirstate.normal(fd)
407 if f:
405 if f:
408 repo.dirstate.forget(f)
406 repo.dirstate.forget(f)
409
407
410 def update(repo, node, branchmerge, force, partial):
408 def update(repo, node, branchmerge, force, partial):
411 """
409 """
412 Perform a merge between the working directory and the given node
410 Perform a merge between the working directory and the given node
413
411
414 node = the node to update to, or None if unspecified
412 node = the node to update to, or None if unspecified
415 branchmerge = whether to merge between branches
413 branchmerge = whether to merge between branches
416 force = whether to force branch merging or file overwriting
414 force = whether to force branch merging or file overwriting
417 partial = a function to filter file lists (dirstate not updated)
415 partial = a function to filter file lists (dirstate not updated)
418
416
419 The table below shows all the behaviors of the update command
417 The table below shows all the behaviors of the update command
420 given the -c and -C or no options, whether the working directory
418 given the -c and -C or no options, whether the working directory
421 is dirty, whether a revision is specified, and the relationship of
419 is dirty, whether a revision is specified, and the relationship of
422 the parent rev to the target rev (linear, on the same named
420 the parent rev to the target rev (linear, on the same named
423 branch, or on another named branch).
421 branch, or on another named branch).
424
422
425 This logic is tested by test-update-branches.
423 This logic is tested by test-update-branches.
426
424
427 -c -C dirty rev | linear same cross
425 -c -C dirty rev | linear same cross
428 n n n n | ok (1) x
426 n n n n | ok (1) x
429 n n n y | ok ok ok
427 n n n y | ok ok ok
430 n n y * | merge (2) (2)
428 n n y * | merge (2) (2)
431 n y * * | --- discard ---
429 n y * * | --- discard ---
432 y n y * | --- (3) ---
430 y n y * | --- (3) ---
433 y n n * | --- ok ---
431 y n n * | --- ok ---
434 y y * * | --- (4) ---
432 y y * * | --- (4) ---
435
433
436 x = can't happen
434 x = can't happen
437 * = don't-care
435 * = don't-care
438 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
436 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
439 2 = abort: crosses branches (use 'hg merge' to merge or
437 2 = abort: crosses branches (use 'hg merge' to merge or
440 use 'hg update -C' to discard changes)
438 use 'hg update -C' to discard changes)
441 3 = abort: uncommitted local changes
439 3 = abort: uncommitted local changes
442 4 = incompatible options (checked in commands.py)
440 4 = incompatible options (checked in commands.py)
443 """
441 """
444
442
445 onode = node
443 onode = node
446 wlock = repo.wlock()
444 wlock = repo.wlock()
447 try:
445 try:
448 wc = repo[None]
446 wc = repo[None]
449 if node is None:
447 if node is None:
450 # tip of current branch
448 # tip of current branch
451 try:
449 try:
452 node = repo.branchtags()[wc.branch()]
450 node = repo.branchtags()[wc.branch()]
453 except KeyError:
451 except KeyError:
454 if wc.branch() == "default": # no default branch!
452 if wc.branch() == "default": # no default branch!
455 node = repo.lookup("tip") # update to tip
453 node = repo.lookup("tip") # update to tip
456 else:
454 else:
457 raise util.Abort(_("branch %s not found") % wc.branch())
455 raise util.Abort(_("branch %s not found") % wc.branch())
458 overwrite = force and not branchmerge
456 overwrite = force and not branchmerge
459 pl = wc.parents()
457 pl = wc.parents()
460 p1, p2 = pl[0], repo[node]
458 p1, p2 = pl[0], repo[node]
461 pa = p1.ancestor(p2)
459 pa = p1.ancestor(p2)
462 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
460 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
463 fastforward = False
461 fastforward = False
464
462
465 ### check phase
463 ### check phase
466 if not overwrite and len(pl) > 1:
464 if not overwrite and len(pl) > 1:
467 raise util.Abort(_("outstanding uncommitted merges"))
465 raise util.Abort(_("outstanding uncommitted merges"))
468 if branchmerge:
466 if branchmerge:
469 if pa == p2:
467 if pa == p2:
470 raise util.Abort(_("merging with a working directory ancestor"
468 raise util.Abort(_("merging with a working directory ancestor"
471 " has no effect"))
469 " has no effect"))
472 elif pa == p1:
470 elif pa == p1:
473 if p1.branch() != p2.branch():
471 if p1.branch() != p2.branch():
474 fastforward = True
472 fastforward = True
475 else:
473 else:
476 raise util.Abort(_("nothing to merge (use 'hg update'"
474 raise util.Abort(_("nothing to merge (use 'hg update'"
477 " or check 'hg heads')"))
475 " or check 'hg heads')"))
478 if not force and (wc.files() or wc.deleted()):
476 if not force and (wc.files() or wc.deleted()):
479 raise util.Abort(_("outstanding uncommitted changes "
477 raise util.Abort(_("outstanding uncommitted changes "
480 "(use 'hg status' to list changes)"))
478 "(use 'hg status' to list changes)"))
481 elif not overwrite:
479 elif not overwrite:
482 if pa == p1 or pa == p2: # linear
480 if pa == p1 or pa == p2: # linear
483 pass # all good
481 pass # all good
484 elif wc.files() or wc.deleted():
482 elif wc.files() or wc.deleted():
485 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
483 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
486 "or use 'hg update -C' to discard changes)"))
484 "or use 'hg update -C' to discard changes)"))
487 elif onode is None:
485 elif onode is None:
488 raise util.Abort(_("crosses branches (use 'hg merge' or use "
486 raise util.Abort(_("crosses branches (use 'hg merge' or use "
489 "'hg update -c')"))
487 "'hg update -c')"))
490 else:
488 else:
491 # Allow jumping branches if clean and specific rev given
489 # Allow jumping branches if clean and specific rev given
492 overwrite = True
490 overwrite = True
493
491
494 ### calculate phase
492 ### calculate phase
495 action = []
493 action = []
496 wc.status(unknown=True) # prime cache
494 wc.status(unknown=True) # prime cache
497 if not force:
495 if not force:
498 _checkunknown(wc, p2)
496 _checkunknown(wc, p2)
499 if not util.checkcase(repo.path):
497 if not util.checkcase(repo.path):
500 _checkcollision(p2)
498 _checkcollision(p2)
501 action += _forgetremoved(wc, p2, branchmerge)
499 action += _forgetremoved(wc, p2, branchmerge)
502 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
500 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
503
501
504 ### apply phase
502 ### apply phase
505 if not branchmerge: # just jump to the new rev
503 if not branchmerge: # just jump to the new rev
506 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
504 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
507 if not partial:
505 if not partial:
508 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
506 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
509
507
510 stats = applyupdates(repo, action, wc, p2)
508 stats = applyupdates(repo, action, wc, p2)
511
509
512 if not partial:
510 if not partial:
513 repo.dirstate.setparents(fp1, fp2)
511 repo.dirstate.setparents(fp1, fp2)
514 recordupdates(repo, action, branchmerge)
512 recordupdates(repo, action, branchmerge)
515 if not branchmerge and not fastforward:
513 if not branchmerge and not fastforward:
516 repo.dirstate.setbranch(p2.branch())
514 repo.dirstate.setbranch(p2.branch())
517 finally:
515 finally:
518 wlock.release()
516 wlock.release()
519
517
520 if not partial:
518 if not partial:
521 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
519 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
522 return stats
520 return stats
@@ -1,27 +1,33 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # test that a commit clears the merge state.
3 # test that a commit clears the merge state.
4
4
5 hg init repo
5 hg init repo
6 cd repo
6 cd repo
7
7
8 echo foo > file
8 echo foo > file
9 hg commit -Am 'add file'
9 hg commit -Am 'add file'
10
10
11 echo bar >> file
11 echo bar >> file
12 hg commit -Am 'append bar'
12 hg commit -Am 'append bar'
13
13
14 echo % create a second head
14 echo % create a second head
15 hg up -C 0
15 hg up -C 0
16 echo baz >> file
16 echo baz >> file
17 hg commit -Am 'append baz'
17 hg commit -Am 'append baz'
18
18
19 echo % failing merge
19 echo % failing merge
20 HGMERGE=internal:fail hg merge
20 HGMERGE=internal:fail hg merge
21
21
22 echo resolved > file
22 echo resolved > file
23 hg resolve -m file
23 hg resolve -m file
24 hg commit -m 'resolved'
24 hg commit -m 'resolved'
25
25
26 echo % resolve -l, should be empty
26 echo % resolve -l, should be empty
27 hg resolve -l
27 hg resolve -l
28
29 # test crashed merge with empty mergestate
30 mkdir .hg/merge
31 touch .hg/merge/state
32 echo % resolve -l, should be empty
33 hg resolve -l
@@ -1,8 +1,9 b''
1 adding file
1 adding file
2 % create a second head
2 % create a second head
3 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 created new head
4 created new head
5 % failing merge
5 % failing merge
6 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
6 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
7 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
7 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
8 % resolve -l, should be empty
8 % resolve -l, should be empty
9 % resolve -l, should be empty
General Comments 0
You need to be logged in to leave comments. Login now