##// END OF EJS Templates
merge: .hgsubstate is special as merge destination, not as merge source
Mads Kiilerich -
r18332:6ba58ab7 default
parent child Browse files
Show More
@@ -1,651 +1,651 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 error, util, filemerge, copies, subrepo
10 import error, 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.write("merge/" + hash, fcl.data())
50 self._repo.opener.write("merge/" + hash, 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 _checkunknownfile(repo, wctx, mctx, f):
84 def _checkunknownfile(repo, wctx, mctx, f):
85 return (not repo.dirstate._ignore(f)
85 return (not repo.dirstate._ignore(f)
86 and os.path.isfile(repo.wjoin(f))
86 and os.path.isfile(repo.wjoin(f))
87 and repo.dirstate.normalize(f) not in repo.dirstate
87 and repo.dirstate.normalize(f) not in repo.dirstate
88 and mctx[f].cmp(wctx[f]))
88 and mctx[f].cmp(wctx[f]))
89
89
90 def _checkunknown(repo, wctx, mctx):
90 def _checkunknown(repo, wctx, mctx):
91 "check for collisions between unknown files and files in mctx"
91 "check for collisions between unknown files and files in mctx"
92
92
93 error = False
93 error = False
94 for f in mctx:
94 for f in mctx:
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
96 error = True
96 error = True
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
98 if error:
98 if error:
99 raise util.Abort(_("untracked files in working directory differ "
99 raise util.Abort(_("untracked files in working directory differ "
100 "from files in requested revision"))
100 "from files in requested revision"))
101
101
102 def _remains(f, m, ma, workingctx=False):
102 def _remains(f, m, ma, workingctx=False):
103 """check whether specified file remains after merge.
103 """check whether specified file remains after merge.
104
104
105 It is assumed that specified file is not contained in the manifest
105 It is assumed that specified file is not contained in the manifest
106 of the other context.
106 of the other context.
107 """
107 """
108 if f in ma:
108 if f in ma:
109 n = m[f]
109 n = m[f]
110 if n != ma[f]:
110 if n != ma[f]:
111 return True # because it is changed locally
111 return True # because it is changed locally
112 # even though it doesn't remain, if "remote deleted" is
112 # even though it doesn't remain, if "remote deleted" is
113 # chosen in manifestmerge()
113 # chosen in manifestmerge()
114 elif workingctx and n[20:] == "a":
114 elif workingctx and n[20:] == "a":
115 return True # because it is added locally (linear merge specific)
115 return True # because it is added locally (linear merge specific)
116 else:
116 else:
117 return False # because it is removed remotely
117 return False # because it is removed remotely
118 else:
118 else:
119 return True # because it is added locally
119 return True # because it is added locally
120
120
121 def _checkcollision(mctx, extractxs):
121 def _checkcollision(mctx, extractxs):
122 "check for case folding collisions in the destination context"
122 "check for case folding collisions in the destination context"
123 folded = {}
123 folded = {}
124 for fn in mctx:
124 for fn in mctx:
125 fold = util.normcase(fn)
125 fold = util.normcase(fn)
126 if fold in folded:
126 if fold in folded:
127 raise util.Abort(_("case-folding collision between %s and %s")
127 raise util.Abort(_("case-folding collision between %s and %s")
128 % (fn, folded[fold]))
128 % (fn, folded[fold]))
129 folded[fold] = fn
129 folded[fold] = fn
130
130
131 if extractxs:
131 if extractxs:
132 wctx, actx = extractxs
132 wctx, actx = extractxs
133 # class to delay looking up copy mapping
133 # class to delay looking up copy mapping
134 class pathcopies(object):
134 class pathcopies(object):
135 @util.propertycache
135 @util.propertycache
136 def map(self):
136 def map(self):
137 # {dst@mctx: src@wctx} copy mapping
137 # {dst@mctx: src@wctx} copy mapping
138 return copies.pathcopies(wctx, mctx)
138 return copies.pathcopies(wctx, mctx)
139 pc = pathcopies()
139 pc = pathcopies()
140
140
141 for fn in wctx:
141 for fn in wctx:
142 fold = util.normcase(fn)
142 fold = util.normcase(fn)
143 mfn = folded.get(fold, None)
143 mfn = folded.get(fold, None)
144 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
144 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
145 _remains(fn, wctx.manifest(), actx.manifest(), True) and
145 _remains(fn, wctx.manifest(), actx.manifest(), True) and
146 _remains(mfn, mctx.manifest(), actx.manifest())):
146 _remains(mfn, mctx.manifest(), actx.manifest())):
147 raise util.Abort(_("case-folding collision between %s and %s")
147 raise util.Abort(_("case-folding collision between %s and %s")
148 % (mfn, fn))
148 % (mfn, fn))
149
149
150 def _forgetremoved(wctx, mctx, branchmerge):
150 def _forgetremoved(wctx, mctx, branchmerge):
151 """
151 """
152 Forget removed files
152 Forget removed files
153
153
154 If we're jumping between revisions (as opposed to merging), and if
154 If we're jumping between revisions (as opposed to merging), and if
155 neither the working directory nor the target rev has the file,
155 neither the working directory nor the target rev has the file,
156 then we need to remove it from the dirstate, to prevent the
156 then we need to remove it from the dirstate, to prevent the
157 dirstate from listing the file when it is no longer in the
157 dirstate from listing the file when it is no longer in the
158 manifest.
158 manifest.
159
159
160 If we're merging, and the other revision has removed a file
160 If we're merging, and the other revision has removed a file
161 that is not present in the working directory, we need to mark it
161 that is not present in the working directory, we need to mark it
162 as removed.
162 as removed.
163 """
163 """
164
164
165 actions = []
165 actions = []
166 state = branchmerge and 'r' or 'f'
166 state = branchmerge and 'r' or 'f'
167 for f in wctx.deleted():
167 for f in wctx.deleted():
168 if f not in mctx:
168 if f not in mctx:
169 actions.append((f, state))
169 actions.append((f, state))
170
170
171 if not branchmerge:
171 if not branchmerge:
172 for f in wctx.removed():
172 for f in wctx.removed():
173 if f not in mctx:
173 if f not in mctx:
174 actions.append((f, "f"))
174 actions.append((f, "f"))
175
175
176 return actions
176 return actions
177
177
178 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
178 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
179 """
179 """
180 Merge p1 and p2 with ancestor pa and generate merge action list
180 Merge p1 and p2 with ancestor pa and generate merge action list
181
181
182 overwrite = whether we clobber working files
182 overwrite = whether we clobber working files
183 partial = function to filter file lists
183 partial = function to filter file lists
184 """
184 """
185
185
186 def fmerge(f, f2, fa):
186 def fmerge(f, f2, fa):
187 """merge flags"""
187 """merge flags"""
188 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
188 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
189 if m == n: # flags agree
189 if m == n: # flags agree
190 return m # unchanged
190 return m # unchanged
191 if m and n and not a: # flags set, don't agree, differ from parent
191 if m and n and not a: # flags set, don't agree, differ from parent
192 r = repo.ui.promptchoice(
192 r = repo.ui.promptchoice(
193 _(" conflicting flags for %s\n"
193 _(" conflicting flags for %s\n"
194 "(n)one, e(x)ec or sym(l)ink?") % f,
194 "(n)one, e(x)ec or sym(l)ink?") % f,
195 (_("&None"), _("E&xec"), _("Sym&link")), 0)
195 (_("&None"), _("E&xec"), _("Sym&link")), 0)
196 if r == 1:
196 if r == 1:
197 return "x" # Exec
197 return "x" # Exec
198 if r == 2:
198 if r == 2:
199 return "l" # Symlink
199 return "l" # Symlink
200 return ""
200 return ""
201 if m and m != a: # changed from a to m
201 if m and m != a: # changed from a to m
202 return m
202 return m
203 if n and n != a: # changed from a to n
203 if n and n != a: # changed from a to n
204 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
204 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
205 # can't automatically merge symlink flag when there
205 # can't automatically merge symlink flag when there
206 # are file-level conflicts here, let filemerge take
206 # are file-level conflicts here, let filemerge take
207 # care of it
207 # care of it
208 return m
208 return m
209 return n
209 return n
210 return '' # flag was cleared
210 return '' # flag was cleared
211
211
212 def act(msg, m, f, *args):
212 def act(msg, m, f, *args):
213 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
213 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
214 actions.append((f, m) + args)
214 actions.append((f, m) + args)
215
215
216 actions, copy, movewithdir = [], {}, {}
216 actions, copy, movewithdir = [], {}, {}
217
217
218 if overwrite:
218 if overwrite:
219 pa = p1
219 pa = p1
220 elif pa == p2: # backwards
220 elif pa == p2: # backwards
221 pa = p1.p1()
221 pa = p1.p1()
222 elif pa and repo.ui.configbool("merge", "followcopies", True):
222 elif pa and repo.ui.configbool("merge", "followcopies", True):
223 ret = copies.mergecopies(repo, p1, p2, pa)
223 ret = copies.mergecopies(repo, p1, p2, pa)
224 copy, movewithdir, diverge, renamedelete = ret
224 copy, movewithdir, diverge, renamedelete = ret
225 for of, fl in diverge.iteritems():
225 for of, fl in diverge.iteritems():
226 act("divergent renames", "dr", of, fl)
226 act("divergent renames", "dr", of, fl)
227 for of, fl in renamedelete.iteritems():
227 for of, fl in renamedelete.iteritems():
228 act("rename and delete", "rd", of, fl)
228 act("rename and delete", "rd", of, fl)
229
229
230 repo.ui.note(_("resolving manifests\n"))
230 repo.ui.note(_("resolving manifests\n"))
231 repo.ui.debug(" overwrite: %s, partial: %s\n"
231 repo.ui.debug(" overwrite: %s, partial: %s\n"
232 % (bool(overwrite), bool(partial)))
232 % (bool(overwrite), bool(partial)))
233 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
233 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
234
234
235 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
235 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
236 copied = set(copy.values())
236 copied = set(copy.values())
237 copied.update(movewithdir.values())
237 copied.update(movewithdir.values())
238
238
239 if '.hgsubstate' in m1:
239 if '.hgsubstate' in m1:
240 # check whether sub state is modified
240 # check whether sub state is modified
241 for s in p1.substate:
241 for s in p1.substate:
242 if p1.sub(s).dirty():
242 if p1.sub(s).dirty():
243 m1['.hgsubstate'] += "+"
243 m1['.hgsubstate'] += "+"
244 break
244 break
245
245
246 # Compare manifests
246 # Compare manifests
247 for f, n in m1.iteritems():
247 for f, n in m1.iteritems():
248 if partial and not partial(f):
248 if partial and not partial(f):
249 continue
249 continue
250 if f in m2:
250 if f in m2:
251 rflags = fmerge(f, f, f)
251 rflags = fmerge(f, f, f)
252 a = ma.get(f, nullid)
252 a = ma.get(f, nullid)
253 if n == m2[f] or m2[f] == a: # same or local newer
253 if n == m2[f] or m2[f] == a: # same or local newer
254 # is file locally modified or flags need changing?
254 # is file locally modified or flags need changing?
255 # dirstate flags may need to be made current
255 # dirstate flags may need to be made current
256 if m1.flags(f) != rflags or n[20:]:
256 if m1.flags(f) != rflags or n[20:]:
257 act("update permissions", "e", f, rflags)
257 act("update permissions", "e", f, rflags)
258 elif n == a: # remote newer
258 elif n == a: # remote newer
259 act("remote is newer", "g", f, rflags)
259 act("remote is newer", "g", f, rflags)
260 else: # both changed
260 else: # both changed
261 act("versions differ", "m", f, f, f, rflags, False)
261 act("versions differ", "m", f, f, f, rflags, False)
262 elif f in copied: # files we'll deal with on m2 side
262 elif f in copied: # files we'll deal with on m2 side
263 pass
263 pass
264 elif f in movewithdir: # directory rename
264 elif f in movewithdir: # directory rename
265 f2 = movewithdir[f]
265 f2 = movewithdir[f]
266 act("remote renamed directory to " + f2, "d", f, None, f2,
266 act("remote renamed directory to " + f2, "d", f, None, f2,
267 m1.flags(f))
267 m1.flags(f))
268 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
268 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
269 f2 = copy[f]
269 f2 = copy[f]
270 act("local copied/moved to " + f2, "m", f, f2, f,
270 act("local copied/moved to " + f2, "m", f, f2, f,
271 fmerge(f, f2, f2), False)
271 fmerge(f, f2, f2), False)
272 elif f in ma: # clean, a different, no remote
272 elif f in ma: # clean, a different, no remote
273 if n != ma[f]:
273 if n != ma[f]:
274 if repo.ui.promptchoice(
274 if repo.ui.promptchoice(
275 _(" local changed %s which remote deleted\n"
275 _(" local changed %s which remote deleted\n"
276 "use (c)hanged version or (d)elete?") % f,
276 "use (c)hanged version or (d)elete?") % f,
277 (_("&Changed"), _("&Delete")), 0):
277 (_("&Changed"), _("&Delete")), 0):
278 act("prompt delete", "r", f)
278 act("prompt delete", "r", f)
279 else:
279 else:
280 act("prompt keep", "a", f)
280 act("prompt keep", "a", f)
281 elif n[20:] == "a": # added, no remote
281 elif n[20:] == "a": # added, no remote
282 act("remote deleted", "f", f)
282 act("remote deleted", "f", f)
283 else:
283 else:
284 act("other deleted", "r", f)
284 act("other deleted", "r", f)
285
285
286 for f, n in m2.iteritems():
286 for f, n in m2.iteritems():
287 if partial and not partial(f):
287 if partial and not partial(f):
288 continue
288 continue
289 if f in m1 or f in copied: # files already visited
289 if f in m1 or f in copied: # files already visited
290 continue
290 continue
291 if f in movewithdir:
291 if f in movewithdir:
292 f2 = movewithdir[f]
292 f2 = movewithdir[f]
293 act("local renamed directory to " + f2, "d", None, f, f2,
293 act("local renamed directory to " + f2, "d", None, f, f2,
294 m2.flags(f))
294 m2.flags(f))
295 elif f in copy:
295 elif f in copy:
296 f2 = copy[f]
296 f2 = copy[f]
297 if f2 in m2: # rename case 1, A/A,B/A
297 if f2 in m2: # rename case 1, A/A,B/A
298 act("remote copied to " + f, "m",
298 act("remote copied to " + f, "m",
299 f2, f, f, fmerge(f2, f, f2), False)
299 f2, f, f, fmerge(f2, f, f2), False)
300 else: # case 3,20 A/B/A
300 else: # case 3,20 A/B/A
301 act("remote moved to " + f, "m",
301 act("remote moved to " + f, "m",
302 f2, f, f, fmerge(f2, f, f2), True)
302 f2, f, f, fmerge(f2, f, f2), True)
303 elif f not in ma:
303 elif f not in ma:
304 if (not overwrite
304 if (not overwrite
305 and _checkunknownfile(repo, p1, p2, f)):
305 and _checkunknownfile(repo, p1, p2, f)):
306 rflags = fmerge(f, f, f)
306 rflags = fmerge(f, f, f)
307 act("remote differs from untracked local",
307 act("remote differs from untracked local",
308 "m", f, f, f, rflags, False)
308 "m", f, f, f, rflags, False)
309 else:
309 else:
310 act("remote created", "g", f, m2.flags(f))
310 act("remote created", "g", f, m2.flags(f))
311 elif n != ma[f]:
311 elif n != ma[f]:
312 if repo.ui.promptchoice(
312 if repo.ui.promptchoice(
313 _("remote changed %s which local deleted\n"
313 _("remote changed %s which local deleted\n"
314 "use (c)hanged version or leave (d)eleted?") % f,
314 "use (c)hanged version or leave (d)eleted?") % f,
315 (_("&Changed"), _("&Deleted")), 0) == 0:
315 (_("&Changed"), _("&Deleted")), 0) == 0:
316 act("prompt recreating", "g", f, m2.flags(f))
316 act("prompt recreating", "g", f, m2.flags(f))
317
317
318 return actions
318 return actions
319
319
320 def actionkey(a):
320 def actionkey(a):
321 return a[1] == "r" and -1 or 0, a
321 return a[1] == "r" and -1 or 0, a
322
322
323 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
323 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
324 """apply the merge action list to the working directory
324 """apply the merge action list to the working directory
325
325
326 wctx is the working copy context
326 wctx is the working copy context
327 mctx is the context to be merged into the working copy
327 mctx is the context to be merged into the working copy
328 actx is the context of the common ancestor
328 actx is the context of the common ancestor
329
329
330 Return a tuple of counts (updated, merged, removed, unresolved) that
330 Return a tuple of counts (updated, merged, removed, unresolved) that
331 describes how many files were affected by the update.
331 describes how many files were affected by the update.
332 """
332 """
333
333
334 updated, merged, removed, unresolved = 0, 0, 0, 0
334 updated, merged, removed, unresolved = 0, 0, 0, 0
335 ms = mergestate(repo)
335 ms = mergestate(repo)
336 ms.reset(wctx.p1().node())
336 ms.reset(wctx.p1().node())
337 moves = []
337 moves = []
338 actions.sort(key=actionkey)
338 actions.sort(key=actionkey)
339
339
340 # prescan for merges
340 # prescan for merges
341 for a in actions:
341 for a in actions:
342 f, m = a[:2]
342 f, m = a[:2]
343 if m == "m": # merge
343 if m == "m": # merge
344 f2, fd, flags, move = a[2:]
344 f2, fd, flags, move = a[2:]
345 if f == '.hgsubstate': # merged internally
345 if fd == '.hgsubstate': # merged internally
346 continue
346 continue
347 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
347 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
348 fcl = wctx[f]
348 fcl = wctx[f]
349 fco = mctx[f2]
349 fco = mctx[f2]
350 if mctx == actx: # backwards, use working dir parent as ancestor
350 if mctx == actx: # backwards, use working dir parent as ancestor
351 if fcl.parents():
351 if fcl.parents():
352 fca = fcl.p1()
352 fca = fcl.p1()
353 else:
353 else:
354 fca = repo.filectx(f, fileid=nullrev)
354 fca = repo.filectx(f, fileid=nullrev)
355 else:
355 else:
356 fca = fcl.ancestor(fco, actx)
356 fca = fcl.ancestor(fco, actx)
357 if not fca:
357 if not fca:
358 fca = repo.filectx(f, fileid=nullrev)
358 fca = repo.filectx(f, fileid=nullrev)
359 ms.add(fcl, fco, fca, fd, flags)
359 ms.add(fcl, fco, fca, fd, flags)
360 if f != fd and move:
360 if f != fd and move:
361 moves.append(f)
361 moves.append(f)
362
362
363 audit = repo.wopener.audit
363 audit = repo.wopener.audit
364
364
365 # remove renamed files after safely stored
365 # remove renamed files after safely stored
366 for f in moves:
366 for f in moves:
367 if os.path.lexists(repo.wjoin(f)):
367 if os.path.lexists(repo.wjoin(f)):
368 repo.ui.debug("removing %s\n" % f)
368 repo.ui.debug("removing %s\n" % f)
369 audit(f)
369 audit(f)
370 os.unlink(repo.wjoin(f))
370 os.unlink(repo.wjoin(f))
371
371
372 numupdates = len(actions)
372 numupdates = len(actions)
373 for i, a in enumerate(actions):
373 for i, a in enumerate(actions):
374 f, m = a[:2]
374 f, m = a[:2]
375 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
375 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
376 unit=_('files'))
376 unit=_('files'))
377 if f and f[0] == "/":
377 if f and f[0] == "/":
378 continue
378 continue
379 if m == "r": # remove
379 if m == "r": # remove
380 repo.ui.note(_("removing %s\n") % f)
380 repo.ui.note(_("removing %s\n") % f)
381 audit(f)
381 audit(f)
382 if f == '.hgsubstate': # subrepo states need updating
382 if f == '.hgsubstate': # subrepo states need updating
383 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
383 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
384 try:
384 try:
385 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
385 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
386 except OSError, inst:
386 except OSError, inst:
387 repo.ui.warn(_("update failed to remove %s: %s!\n") %
387 repo.ui.warn(_("update failed to remove %s: %s!\n") %
388 (f, inst.strerror))
388 (f, inst.strerror))
389 removed += 1
389 removed += 1
390 elif m == "m": # merge
390 elif m == "m": # merge
391 if f == '.hgsubstate': # subrepo states need updating
391 if fd == '.hgsubstate': # subrepo states need updating
392 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
392 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
393 overwrite)
393 overwrite)
394 continue
394 continue
395 f2, fd, flags, move = a[2:]
395 f2, fd, flags, move = a[2:]
396 audit(fd)
396 audit(fd)
397 r = ms.resolve(fd, wctx, mctx)
397 r = ms.resolve(fd, wctx, mctx)
398 if r is not None and r > 0:
398 if r is not None and r > 0:
399 unresolved += 1
399 unresolved += 1
400 else:
400 else:
401 if r is None:
401 if r is None:
402 updated += 1
402 updated += 1
403 else:
403 else:
404 merged += 1
404 merged += 1
405 elif m == "g": # get
405 elif m == "g": # get
406 flags = a[2]
406 flags = a[2]
407 repo.ui.note(_("getting %s\n") % f)
407 repo.ui.note(_("getting %s\n") % f)
408 t = mctx.filectx(f).data()
408 t = mctx.filectx(f).data()
409 repo.wwrite(f, t, flags)
409 repo.wwrite(f, t, flags)
410 t = None
410 t = None
411 updated += 1
411 updated += 1
412 if f == '.hgsubstate': # subrepo states need updating
412 if f == '.hgsubstate': # subrepo states need updating
413 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
413 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
414 elif m == "d": # directory rename
414 elif m == "d": # directory rename
415 f2, fd, flags = a[2:]
415 f2, fd, flags = a[2:]
416 if f:
416 if f:
417 repo.ui.note(_("moving %s to %s\n") % (f, fd))
417 repo.ui.note(_("moving %s to %s\n") % (f, fd))
418 audit(f)
418 audit(f)
419 t = wctx.filectx(f).data()
419 t = wctx.filectx(f).data()
420 repo.wwrite(fd, t, flags)
420 repo.wwrite(fd, t, flags)
421 util.unlinkpath(repo.wjoin(f))
421 util.unlinkpath(repo.wjoin(f))
422 if f2:
422 if f2:
423 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
423 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
424 t = mctx.filectx(f2).data()
424 t = mctx.filectx(f2).data()
425 repo.wwrite(fd, t, flags)
425 repo.wwrite(fd, t, flags)
426 updated += 1
426 updated += 1
427 elif m == "dr": # divergent renames
427 elif m == "dr": # divergent renames
428 fl = a[2]
428 fl = a[2]
429 repo.ui.warn(_("note: possible conflict - %s was renamed "
429 repo.ui.warn(_("note: possible conflict - %s was renamed "
430 "multiple times to:\n") % f)
430 "multiple times to:\n") % f)
431 for nf in fl:
431 for nf in fl:
432 repo.ui.warn(" %s\n" % nf)
432 repo.ui.warn(" %s\n" % nf)
433 elif m == "rd": # rename and delete
433 elif m == "rd": # rename and delete
434 fl = a[2]
434 fl = a[2]
435 repo.ui.warn(_("note: possible conflict - %s was deleted "
435 repo.ui.warn(_("note: possible conflict - %s was deleted "
436 "and renamed to:\n") % f)
436 "and renamed to:\n") % f)
437 for nf in fl:
437 for nf in fl:
438 repo.ui.warn(" %s\n" % nf)
438 repo.ui.warn(" %s\n" % nf)
439 elif m == "e": # exec
439 elif m == "e": # exec
440 flags = a[2]
440 flags = a[2]
441 audit(f)
441 audit(f)
442 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
442 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
443 ms.commit()
443 ms.commit()
444 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
444 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
445
445
446 return updated, merged, removed, unresolved
446 return updated, merged, removed, unresolved
447
447
448 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
448 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
449 "Calculate the actions needed to merge mctx into tctx"
449 "Calculate the actions needed to merge mctx into tctx"
450 actions = []
450 actions = []
451 folding = not util.checkcase(repo.path)
451 folding = not util.checkcase(repo.path)
452 if folding:
452 if folding:
453 # collision check is not needed for clean update
453 # collision check is not needed for clean update
454 if (not branchmerge and
454 if (not branchmerge and
455 (force or not tctx.dirty(missing=True, branch=False))):
455 (force or not tctx.dirty(missing=True, branch=False))):
456 _checkcollision(mctx, None)
456 _checkcollision(mctx, None)
457 else:
457 else:
458 _checkcollision(mctx, (tctx, ancestor))
458 _checkcollision(mctx, (tctx, ancestor))
459 if not force:
459 if not force:
460 _checkunknown(repo, tctx, mctx)
460 _checkunknown(repo, tctx, mctx)
461 if tctx.rev() is None:
461 if tctx.rev() is None:
462 actions += _forgetremoved(tctx, mctx, branchmerge)
462 actions += _forgetremoved(tctx, mctx, branchmerge)
463 actions += manifestmerge(repo, tctx, mctx,
463 actions += manifestmerge(repo, tctx, mctx,
464 ancestor,
464 ancestor,
465 force and not branchmerge,
465 force and not branchmerge,
466 partial)
466 partial)
467 return actions
467 return actions
468
468
469 def recordupdates(repo, actions, branchmerge):
469 def recordupdates(repo, actions, branchmerge):
470 "record merge actions to the dirstate"
470 "record merge actions to the dirstate"
471
471
472 for a in actions:
472 for a in actions:
473 f, m = a[:2]
473 f, m = a[:2]
474 if m == "r": # remove
474 if m == "r": # remove
475 if branchmerge:
475 if branchmerge:
476 repo.dirstate.remove(f)
476 repo.dirstate.remove(f)
477 else:
477 else:
478 repo.dirstate.drop(f)
478 repo.dirstate.drop(f)
479 elif m == "a": # re-add
479 elif m == "a": # re-add
480 if not branchmerge:
480 if not branchmerge:
481 repo.dirstate.add(f)
481 repo.dirstate.add(f)
482 elif m == "f": # forget
482 elif m == "f": # forget
483 repo.dirstate.drop(f)
483 repo.dirstate.drop(f)
484 elif m == "e": # exec change
484 elif m == "e": # exec change
485 repo.dirstate.normallookup(f)
485 repo.dirstate.normallookup(f)
486 elif m == "g": # get
486 elif m == "g": # get
487 if branchmerge:
487 if branchmerge:
488 repo.dirstate.otherparent(f)
488 repo.dirstate.otherparent(f)
489 else:
489 else:
490 repo.dirstate.normal(f)
490 repo.dirstate.normal(f)
491 elif m == "m": # merge
491 elif m == "m": # merge
492 f2, fd, flag, move = a[2:]
492 f2, fd, flag, move = a[2:]
493 if branchmerge:
493 if branchmerge:
494 # We've done a branch merge, mark this file as merged
494 # We've done a branch merge, mark this file as merged
495 # so that we properly record the merger later
495 # so that we properly record the merger later
496 repo.dirstate.merge(fd)
496 repo.dirstate.merge(fd)
497 if f != f2: # copy/rename
497 if f != f2: # copy/rename
498 if move:
498 if move:
499 repo.dirstate.remove(f)
499 repo.dirstate.remove(f)
500 if f != fd:
500 if f != fd:
501 repo.dirstate.copy(f, fd)
501 repo.dirstate.copy(f, fd)
502 else:
502 else:
503 repo.dirstate.copy(f2, fd)
503 repo.dirstate.copy(f2, fd)
504 else:
504 else:
505 # We've update-merged a locally modified file, so
505 # We've update-merged a locally modified file, so
506 # we set the dirstate to emulate a normal checkout
506 # we set the dirstate to emulate a normal checkout
507 # of that file some time in the past. Thus our
507 # of that file some time in the past. Thus our
508 # merge will appear as a normal local file
508 # merge will appear as a normal local file
509 # modification.
509 # modification.
510 if f2 == fd: # file not locally copied/moved
510 if f2 == fd: # file not locally copied/moved
511 repo.dirstate.normallookup(fd)
511 repo.dirstate.normallookup(fd)
512 if move:
512 if move:
513 repo.dirstate.drop(f)
513 repo.dirstate.drop(f)
514 elif m == "d": # directory rename
514 elif m == "d": # directory rename
515 f2, fd, flag = a[2:]
515 f2, fd, flag = a[2:]
516 if not f2 and f not in repo.dirstate:
516 if not f2 and f not in repo.dirstate:
517 # untracked file moved
517 # untracked file moved
518 continue
518 continue
519 if branchmerge:
519 if branchmerge:
520 repo.dirstate.add(fd)
520 repo.dirstate.add(fd)
521 if f:
521 if f:
522 repo.dirstate.remove(f)
522 repo.dirstate.remove(f)
523 repo.dirstate.copy(f, fd)
523 repo.dirstate.copy(f, fd)
524 if f2:
524 if f2:
525 repo.dirstate.copy(f2, fd)
525 repo.dirstate.copy(f2, fd)
526 else:
526 else:
527 repo.dirstate.normal(fd)
527 repo.dirstate.normal(fd)
528 if f:
528 if f:
529 repo.dirstate.drop(f)
529 repo.dirstate.drop(f)
530
530
531 def update(repo, node, branchmerge, force, partial, ancestor=None,
531 def update(repo, node, branchmerge, force, partial, ancestor=None,
532 mergeancestor=False):
532 mergeancestor=False):
533 """
533 """
534 Perform a merge between the working directory and the given node
534 Perform a merge between the working directory and the given node
535
535
536 node = the node to update to, or None if unspecified
536 node = the node to update to, or None if unspecified
537 branchmerge = whether to merge between branches
537 branchmerge = whether to merge between branches
538 force = whether to force branch merging or file overwriting
538 force = whether to force branch merging or file overwriting
539 partial = a function to filter file lists (dirstate not updated)
539 partial = a function to filter file lists (dirstate not updated)
540 mergeancestor = if false, merging with an ancestor (fast-forward)
540 mergeancestor = if false, merging with an ancestor (fast-forward)
541 is only allowed between different named branches. This flag
541 is only allowed between different named branches. This flag
542 is used by rebase extension as a temporary fix and should be
542 is used by rebase extension as a temporary fix and should be
543 avoided in general.
543 avoided in general.
544
544
545 The table below shows all the behaviors of the update command
545 The table below shows all the behaviors of the update command
546 given the -c and -C or no options, whether the working directory
546 given the -c and -C or no options, whether the working directory
547 is dirty, whether a revision is specified, and the relationship of
547 is dirty, whether a revision is specified, and the relationship of
548 the parent rev to the target rev (linear, on the same named
548 the parent rev to the target rev (linear, on the same named
549 branch, or on another named branch).
549 branch, or on another named branch).
550
550
551 This logic is tested by test-update-branches.t.
551 This logic is tested by test-update-branches.t.
552
552
553 -c -C dirty rev | linear same cross
553 -c -C dirty rev | linear same cross
554 n n n n | ok (1) x
554 n n n n | ok (1) x
555 n n n y | ok ok ok
555 n n n y | ok ok ok
556 n n y * | merge (2) (2)
556 n n y * | merge (2) (2)
557 n y * * | --- discard ---
557 n y * * | --- discard ---
558 y n y * | --- (3) ---
558 y n y * | --- (3) ---
559 y n n * | --- ok ---
559 y n n * | --- ok ---
560 y y * * | --- (4) ---
560 y y * * | --- (4) ---
561
561
562 x = can't happen
562 x = can't happen
563 * = don't-care
563 * = don't-care
564 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
564 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
565 2 = abort: crosses branches (use 'hg merge' to merge or
565 2 = abort: crosses branches (use 'hg merge' to merge or
566 use 'hg update -C' to discard changes)
566 use 'hg update -C' to discard changes)
567 3 = abort: uncommitted local changes
567 3 = abort: uncommitted local changes
568 4 = incompatible options (checked in commands.py)
568 4 = incompatible options (checked in commands.py)
569
569
570 Return the same tuple as applyupdates().
570 Return the same tuple as applyupdates().
571 """
571 """
572
572
573 onode = node
573 onode = node
574 wlock = repo.wlock()
574 wlock = repo.wlock()
575 try:
575 try:
576 wc = repo[None]
576 wc = repo[None]
577 if node is None:
577 if node is None:
578 # tip of current branch
578 # tip of current branch
579 try:
579 try:
580 node = repo.branchtip(wc.branch())
580 node = repo.branchtip(wc.branch())
581 except error.RepoLookupError:
581 except error.RepoLookupError:
582 if wc.branch() == "default": # no default branch!
582 if wc.branch() == "default": # no default branch!
583 node = repo.lookup("tip") # update to tip
583 node = repo.lookup("tip") # update to tip
584 else:
584 else:
585 raise util.Abort(_("branch %s not found") % wc.branch())
585 raise util.Abort(_("branch %s not found") % wc.branch())
586 overwrite = force and not branchmerge
586 overwrite = force and not branchmerge
587 pl = wc.parents()
587 pl = wc.parents()
588 p1, p2 = pl[0], repo[node]
588 p1, p2 = pl[0], repo[node]
589 if ancestor:
589 if ancestor:
590 pa = repo[ancestor]
590 pa = repo[ancestor]
591 else:
591 else:
592 pa = p1.ancestor(p2)
592 pa = p1.ancestor(p2)
593
593
594 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
594 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
595
595
596 ### check phase
596 ### check phase
597 if not overwrite and len(pl) > 1:
597 if not overwrite and len(pl) > 1:
598 raise util.Abort(_("outstanding uncommitted merges"))
598 raise util.Abort(_("outstanding uncommitted merges"))
599 if branchmerge:
599 if branchmerge:
600 if pa == p2:
600 if pa == p2:
601 raise util.Abort(_("merging with a working directory ancestor"
601 raise util.Abort(_("merging with a working directory ancestor"
602 " has no effect"))
602 " has no effect"))
603 elif pa == p1:
603 elif pa == p1:
604 if not mergeancestor and p1.branch() == p2.branch():
604 if not mergeancestor and p1.branch() == p2.branch():
605 raise util.Abort(_("nothing to merge"),
605 raise util.Abort(_("nothing to merge"),
606 hint=_("use 'hg update' "
606 hint=_("use 'hg update' "
607 "or check 'hg heads'"))
607 "or check 'hg heads'"))
608 if not force and (wc.files() or wc.deleted()):
608 if not force and (wc.files() or wc.deleted()):
609 raise util.Abort(_("outstanding uncommitted changes"),
609 raise util.Abort(_("outstanding uncommitted changes"),
610 hint=_("use 'hg status' to list changes"))
610 hint=_("use 'hg status' to list changes"))
611 for s in wc.substate:
611 for s in wc.substate:
612 if wc.sub(s).dirty():
612 if wc.sub(s).dirty():
613 raise util.Abort(_("outstanding uncommitted changes in "
613 raise util.Abort(_("outstanding uncommitted changes in "
614 "subrepository '%s'") % s)
614 "subrepository '%s'") % s)
615
615
616 elif not overwrite:
616 elif not overwrite:
617 if pa == p1 or pa == p2: # linear
617 if pa == p1 or pa == p2: # linear
618 pass # all good
618 pass # all good
619 elif wc.dirty(missing=True):
619 elif wc.dirty(missing=True):
620 raise util.Abort(_("crosses branches (merge branches or use"
620 raise util.Abort(_("crosses branches (merge branches or use"
621 " --clean to discard changes)"))
621 " --clean to discard changes)"))
622 elif onode is None:
622 elif onode is None:
623 raise util.Abort(_("crosses branches (merge branches or update"
623 raise util.Abort(_("crosses branches (merge branches or update"
624 " --check to force update)"))
624 " --check to force update)"))
625 else:
625 else:
626 # Allow jumping branches if clean and specific rev given
626 # Allow jumping branches if clean and specific rev given
627 pa = p1
627 pa = p1
628
628
629 ### calculate phase
629 ### calculate phase
630 actions = calculateupdates(repo, wc, p2, pa,
630 actions = calculateupdates(repo, wc, p2, pa,
631 branchmerge, force, partial)
631 branchmerge, force, partial)
632
632
633 ### apply phase
633 ### apply phase
634 if not branchmerge: # just jump to the new rev
634 if not branchmerge: # just jump to the new rev
635 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
635 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
636 if not partial:
636 if not partial:
637 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
637 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
638
638
639 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
639 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
640
640
641 if not partial:
641 if not partial:
642 repo.setparents(fp1, fp2)
642 repo.setparents(fp1, fp2)
643 recordupdates(repo, actions, branchmerge)
643 recordupdates(repo, actions, branchmerge)
644 if not branchmerge:
644 if not branchmerge:
645 repo.dirstate.setbranch(p2.branch())
645 repo.dirstate.setbranch(p2.branch())
646 finally:
646 finally:
647 wlock.release()
647 wlock.release()
648
648
649 if not partial:
649 if not partial:
650 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
650 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
651 return stats
651 return stats
General Comments 0
You need to be logged in to leave comments. Login now