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