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