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