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