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