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