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