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