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