##// END OF EJS Templates
merge: avoid unlinking destination of merge when case changes (issue2715)
Matt Mackall -
r13718:5b3383ea stable
parent child Browse files
Show More
@@ -1,560 +1,561 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 (move and repo.dirstate.normalize(fd) != f
342 and os.path.lexists(repo.wjoin(f))):
342 repo.ui.debug("removing %s\n" % f)
343 repo.ui.debug("removing %s\n" % f)
343 os.unlink(repo.wjoin(f))
344 os.unlink(repo.wjoin(f))
344 elif m == "g": # get
345 elif m == "g": # get
345 flags = a[2]
346 flags = a[2]
346 repo.ui.note(_("getting %s\n") % f)
347 repo.ui.note(_("getting %s\n") % f)
347 t = mctx.filectx(f).data()
348 t = mctx.filectx(f).data()
348 repo.wwrite(f, t, flags)
349 repo.wwrite(f, t, flags)
349 t = None
350 t = None
350 updated += 1
351 updated += 1
351 if f == '.hgsubstate': # subrepo states need updating
352 if f == '.hgsubstate': # subrepo states need updating
352 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
353 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
353 elif m == "d": # directory rename
354 elif m == "d": # directory rename
354 f2, fd, flags = a[2:]
355 f2, fd, flags = a[2:]
355 if f:
356 if f:
356 repo.ui.note(_("moving %s to %s\n") % (f, fd))
357 repo.ui.note(_("moving %s to %s\n") % (f, fd))
357 t = wctx.filectx(f).data()
358 t = wctx.filectx(f).data()
358 repo.wwrite(fd, t, flags)
359 repo.wwrite(fd, t, flags)
359 util.unlinkpath(repo.wjoin(f))
360 util.unlinkpath(repo.wjoin(f))
360 if f2:
361 if f2:
361 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
362 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
362 t = mctx.filectx(f2).data()
363 t = mctx.filectx(f2).data()
363 repo.wwrite(fd, t, flags)
364 repo.wwrite(fd, t, flags)
364 updated += 1
365 updated += 1
365 elif m == "dr": # divergent renames
366 elif m == "dr": # divergent renames
366 fl = a[2]
367 fl = a[2]
367 repo.ui.warn(_("note: possible conflict - %s was renamed "
368 repo.ui.warn(_("note: possible conflict - %s was renamed "
368 "multiple times to:\n") % f)
369 "multiple times to:\n") % f)
369 for nf in fl:
370 for nf in fl:
370 repo.ui.warn(" %s\n" % nf)
371 repo.ui.warn(" %s\n" % nf)
371 elif m == "e": # exec
372 elif m == "e": # exec
372 flags = a[2]
373 flags = a[2]
373 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
374 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
374 ms.commit()
375 ms.commit()
375 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
376 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
376
377
377 return updated, merged, removed, unresolved
378 return updated, merged, removed, unresolved
378
379
379 def recordupdates(repo, action, branchmerge):
380 def recordupdates(repo, action, branchmerge):
380 "record merge actions to the dirstate"
381 "record merge actions to the dirstate"
381
382
382 for a in action:
383 for a in action:
383 f, m = a[:2]
384 f, m = a[:2]
384 if m == "r": # remove
385 if m == "r": # remove
385 if branchmerge:
386 if branchmerge:
386 repo.dirstate.remove(f)
387 repo.dirstate.remove(f)
387 else:
388 else:
388 repo.dirstate.forget(f)
389 repo.dirstate.forget(f)
389 elif m == "a": # re-add
390 elif m == "a": # re-add
390 if not branchmerge:
391 if not branchmerge:
391 repo.dirstate.add(f)
392 repo.dirstate.add(f)
392 elif m == "f": # forget
393 elif m == "f": # forget
393 repo.dirstate.forget(f)
394 repo.dirstate.forget(f)
394 elif m == "e": # exec change
395 elif m == "e": # exec change
395 repo.dirstate.normallookup(f)
396 repo.dirstate.normallookup(f)
396 elif m == "g": # get
397 elif m == "g": # get
397 if branchmerge:
398 if branchmerge:
398 repo.dirstate.otherparent(f)
399 repo.dirstate.otherparent(f)
399 else:
400 else:
400 repo.dirstate.normal(f)
401 repo.dirstate.normal(f)
401 elif m == "m": # merge
402 elif m == "m": # merge
402 f2, fd, flag, move = a[2:]
403 f2, fd, flag, move = a[2:]
403 if branchmerge:
404 if branchmerge:
404 # We've done a branch merge, mark this file as merged
405 # We've done a branch merge, mark this file as merged
405 # so that we properly record the merger later
406 # so that we properly record the merger later
406 repo.dirstate.merge(fd)
407 repo.dirstate.merge(fd)
407 if f != f2: # copy/rename
408 if f != f2: # copy/rename
408 if move:
409 if move:
409 repo.dirstate.remove(f)
410 repo.dirstate.remove(f)
410 if f != fd:
411 if f != fd:
411 repo.dirstate.copy(f, fd)
412 repo.dirstate.copy(f, fd)
412 else:
413 else:
413 repo.dirstate.copy(f2, fd)
414 repo.dirstate.copy(f2, fd)
414 else:
415 else:
415 # We've update-merged a locally modified file, so
416 # We've update-merged a locally modified file, so
416 # we set the dirstate to emulate a normal checkout
417 # we set the dirstate to emulate a normal checkout
417 # of that file some time in the past. Thus our
418 # of that file some time in the past. Thus our
418 # merge will appear as a normal local file
419 # merge will appear as a normal local file
419 # modification.
420 # modification.
420 if f2 == fd: # file not locally copied/moved
421 if f2 == fd: # file not locally copied/moved
421 repo.dirstate.normallookup(fd)
422 repo.dirstate.normallookup(fd)
422 if move:
423 if move:
423 repo.dirstate.forget(f)
424 repo.dirstate.forget(f)
424 elif m == "d": # directory rename
425 elif m == "d": # directory rename
425 f2, fd, flag = a[2:]
426 f2, fd, flag = a[2:]
426 if not f2 and f not in repo.dirstate:
427 if not f2 and f not in repo.dirstate:
427 # untracked file moved
428 # untracked file moved
428 continue
429 continue
429 if branchmerge:
430 if branchmerge:
430 repo.dirstate.add(fd)
431 repo.dirstate.add(fd)
431 if f:
432 if f:
432 repo.dirstate.remove(f)
433 repo.dirstate.remove(f)
433 repo.dirstate.copy(f, fd)
434 repo.dirstate.copy(f, fd)
434 if f2:
435 if f2:
435 repo.dirstate.copy(f2, fd)
436 repo.dirstate.copy(f2, fd)
436 else:
437 else:
437 repo.dirstate.normal(fd)
438 repo.dirstate.normal(fd)
438 if f:
439 if f:
439 repo.dirstate.forget(f)
440 repo.dirstate.forget(f)
440
441
441 def update(repo, node, branchmerge, force, partial):
442 def update(repo, node, branchmerge, force, partial):
442 """
443 """
443 Perform a merge between the working directory and the given node
444 Perform a merge between the working directory and the given node
444
445
445 node = the node to update to, or None if unspecified
446 node = the node to update to, or None if unspecified
446 branchmerge = whether to merge between branches
447 branchmerge = whether to merge between branches
447 force = whether to force branch merging or file overwriting
448 force = whether to force branch merging or file overwriting
448 partial = a function to filter file lists (dirstate not updated)
449 partial = a function to filter file lists (dirstate not updated)
449
450
450 The table below shows all the behaviors of the update command
451 The table below shows all the behaviors of the update command
451 given the -c and -C or no options, whether the working directory
452 given the -c and -C or no options, whether the working directory
452 is dirty, whether a revision is specified, and the relationship of
453 is dirty, whether a revision is specified, and the relationship of
453 the parent rev to the target rev (linear, on the same named
454 the parent rev to the target rev (linear, on the same named
454 branch, or on another named branch).
455 branch, or on another named branch).
455
456
456 This logic is tested by test-update-branches.t.
457 This logic is tested by test-update-branches.t.
457
458
458 -c -C dirty rev | linear same cross
459 -c -C dirty rev | linear same cross
459 n n n n | ok (1) x
460 n n n n | ok (1) x
460 n n n y | ok ok ok
461 n n n y | ok ok ok
461 n n y * | merge (2) (2)
462 n n y * | merge (2) (2)
462 n y * * | --- discard ---
463 n y * * | --- discard ---
463 y n y * | --- (3) ---
464 y n y * | --- (3) ---
464 y n n * | --- ok ---
465 y n n * | --- ok ---
465 y y * * | --- (4) ---
466 y y * * | --- (4) ---
466
467
467 x = can't happen
468 x = can't happen
468 * = don't-care
469 * = don't-care
469 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
470 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
470 2 = abort: crosses branches (use 'hg merge' to merge or
471 2 = abort: crosses branches (use 'hg merge' to merge or
471 use 'hg update -C' to discard changes)
472 use 'hg update -C' to discard changes)
472 3 = abort: uncommitted local changes
473 3 = abort: uncommitted local changes
473 4 = incompatible options (checked in commands.py)
474 4 = incompatible options (checked in commands.py)
474
475
475 Return the same tuple as applyupdates().
476 Return the same tuple as applyupdates().
476 """
477 """
477
478
478 onode = node
479 onode = node
479 wlock = repo.wlock()
480 wlock = repo.wlock()
480 try:
481 try:
481 wc = repo[None]
482 wc = repo[None]
482 if node is None:
483 if node is None:
483 # tip of current branch
484 # tip of current branch
484 try:
485 try:
485 node = repo.branchtags()[wc.branch()]
486 node = repo.branchtags()[wc.branch()]
486 except KeyError:
487 except KeyError:
487 if wc.branch() == "default": # no default branch!
488 if wc.branch() == "default": # no default branch!
488 node = repo.lookup("tip") # update to tip
489 node = repo.lookup("tip") # update to tip
489 else:
490 else:
490 raise util.Abort(_("branch %s not found") % wc.branch())
491 raise util.Abort(_("branch %s not found") % wc.branch())
491 overwrite = force and not branchmerge
492 overwrite = force and not branchmerge
492 pl = wc.parents()
493 pl = wc.parents()
493 p1, p2 = pl[0], repo[node]
494 p1, p2 = pl[0], repo[node]
494 pa = p1.ancestor(p2)
495 pa = p1.ancestor(p2)
495 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
496 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
496 fastforward = False
497 fastforward = False
497
498
498 ### check phase
499 ### check phase
499 if not overwrite and len(pl) > 1:
500 if not overwrite and len(pl) > 1:
500 raise util.Abort(_("outstanding uncommitted merges"))
501 raise util.Abort(_("outstanding uncommitted merges"))
501 if branchmerge:
502 if branchmerge:
502 if pa == p2:
503 if pa == p2:
503 raise util.Abort(_("merging with a working directory ancestor"
504 raise util.Abort(_("merging with a working directory ancestor"
504 " has no effect"))
505 " has no effect"))
505 elif pa == p1:
506 elif pa == p1:
506 if p1.branch() != p2.branch():
507 if p1.branch() != p2.branch():
507 fastforward = True
508 fastforward = True
508 else:
509 else:
509 raise util.Abort(_("nothing to merge (use 'hg update'"
510 raise util.Abort(_("nothing to merge (use 'hg update'"
510 " or check 'hg heads')"))
511 " or check 'hg heads')"))
511 if not force and (wc.files() or wc.deleted()):
512 if not force and (wc.files() or wc.deleted()):
512 raise util.Abort(_("outstanding uncommitted changes "
513 raise util.Abort(_("outstanding uncommitted changes "
513 "(use 'hg status' to list changes)"))
514 "(use 'hg status' to list changes)"))
514 for s in wc.substate:
515 for s in wc.substate:
515 if wc.sub(s).dirty():
516 if wc.sub(s).dirty():
516 raise util.Abort(_("outstanding uncommitted changes in "
517 raise util.Abort(_("outstanding uncommitted changes in "
517 "subrepository '%s'") % s)
518 "subrepository '%s'") % s)
518
519
519 elif not overwrite:
520 elif not overwrite:
520 if pa == p1 or pa == p2: # linear
521 if pa == p1 or pa == p2: # linear
521 pass # all good
522 pass # all good
522 elif wc.files() or wc.deleted():
523 elif wc.files() or wc.deleted():
523 raise util.Abort(_("crosses branches (merge branches or use"
524 raise util.Abort(_("crosses branches (merge branches or use"
524 " --clean to discard changes)"))
525 " --clean to discard changes)"))
525 elif onode is None:
526 elif onode is None:
526 raise util.Abort(_("crosses branches (merge branches or use"
527 raise util.Abort(_("crosses branches (merge branches or use"
527 " --check to force update)"))
528 " --check to force update)"))
528 else:
529 else:
529 # Allow jumping branches if clean and specific rev given
530 # Allow jumping branches if clean and specific rev given
530 overwrite = True
531 overwrite = True
531
532
532 ### calculate phase
533 ### calculate phase
533 action = []
534 action = []
534 wc.status(unknown=True) # prime cache
535 wc.status(unknown=True) # prime cache
535 if not force:
536 if not force:
536 _checkunknown(wc, p2)
537 _checkunknown(wc, p2)
537 if not util.checkcase(repo.path):
538 if not util.checkcase(repo.path):
538 _checkcollision(p2)
539 _checkcollision(p2)
539 action += _forgetremoved(wc, p2, branchmerge)
540 action += _forgetremoved(wc, p2, branchmerge)
540 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
541 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
541
542
542 ### apply phase
543 ### apply phase
543 if not branchmerge: # just jump to the new rev
544 if not branchmerge: # just jump to the new rev
544 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
545 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
545 if not partial:
546 if not partial:
546 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
547 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
547
548
548 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
549 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
549
550
550 if not partial:
551 if not partial:
551 repo.dirstate.setparents(fp1, fp2)
552 repo.dirstate.setparents(fp1, fp2)
552 recordupdates(repo, action, branchmerge)
553 recordupdates(repo, action, branchmerge)
553 if not branchmerge and not fastforward:
554 if not branchmerge and not fastforward:
554 repo.dirstate.setbranch(p2.branch())
555 repo.dirstate.setbranch(p2.branch())
555 finally:
556 finally:
556 wlock.release()
557 wlock.release()
557
558
558 if not partial:
559 if not partial:
559 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
560 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
560 return stats
561 return stats
General Comments 0
You need to be logged in to leave comments. Login now