##// END OF EJS Templates
python-2.6: use sha wrapper from util for new merge code
Dirkjan Ochtman -
r6517:fcfb6a0a default
parent child Browse files
Show More
@@ -1,460 +1,460
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import nullid, nullrev, hex
8 from node import nullid, nullrev, hex
9 from i18n import _
9 from i18n import _
10 import errno, util, os, filemerge, copies, sha, shutil
10 import errno, util, os, filemerge, copies, shutil
11
11
12 class mergestate(object):
12 class mergestate(object):
13 '''track 3-way merge state of individual files'''
13 '''track 3-way merge state of individual files'''
14 def __init__(self, repo):
14 def __init__(self, repo):
15 self._repo = repo
15 self._repo = repo
16 self._state = {}
16 self._state = {}
17 self._data = {}
17 self._data = {}
18 def reset(self, node):
18 def reset(self, node):
19 self._local = node
19 self._local = node
20 shutil.rmtree(self._repo.join("merge"), True)
20 shutil.rmtree(self._repo.join("merge"), True)
21 def add(self, fcl, fco, fca, fd, flags):
21 def add(self, fcl, fco, fca, fd, flags):
22 hash = sha.sha(fcl.path()).hexdigest()
22 hash = util.sha1(fcl.path()).hexdigest()
23 self._repo.opener("merge/" + hash, "w").write(fcl.data())
23 self._repo.opener("merge/" + hash, "w").write(fcl.data())
24 self._state[fd] = 'u'
24 self._state[fd] = 'u'
25 self._data[fd] = (hash, fcl.path(), fca.path(), hex(fca.filenode()),
25 self._data[fd] = (hash, fcl.path(), fca.path(), hex(fca.filenode()),
26 fco.path(), flags)
26 fco.path(), flags)
27 def __contains__(self, dfile):
27 def __contains__(self, dfile):
28 return dfile in self._state
28 return dfile in self._state
29 def __getitem__(self, dfile):
29 def __getitem__(self, dfile):
30 return self._state[dfile]
30 return self._state[dfile]
31 def mark(self, dfile, state):
31 def mark(self, dfile, state):
32 self._state[dfile] = state
32 self._state[dfile] = state
33 def resolve(self, dfile, wctx, octx):
33 def resolve(self, dfile, wctx, octx):
34 if self[dfile] == 'r':
34 if self[dfile] == 'r':
35 return 0
35 return 0
36 hash, lfile, afile, anode, ofile, flags = self._data[dfile]
36 hash, lfile, afile, anode, ofile, flags = self._data[dfile]
37 f = self._repo.opener("merge/" + hash)
37 f = self._repo.opener("merge/" + hash)
38 self._repo.wwrite(dfile, f.read(), flags)
38 self._repo.wwrite(dfile, f.read(), flags)
39 fcd = wctx[dfile]
39 fcd = wctx[dfile]
40 fco = octx[ofile]
40 fco = octx[ofile]
41 fca = self._repo.filectx(afile, fileid=anode)
41 fca = self._repo.filectx(afile, fileid=anode)
42 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
42 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
43 if not r:
43 if not r:
44 util.set_flags(self._repo.wjoin(dfile), flags)
44 util.set_flags(self._repo.wjoin(dfile), flags)
45 self.mark(dfile, 'r')
45 self.mark(dfile, 'r')
46 return r
46 return r
47
47
48 def _checkunknown(wctx, mctx):
48 def _checkunknown(wctx, mctx):
49 "check for collisions between unknown files and files in mctx"
49 "check for collisions between unknown files and files in mctx"
50 for f in wctx.unknown():
50 for f in wctx.unknown():
51 if f in mctx and mctx[f].cmp(wctx[f].data()):
51 if f in mctx and mctx[f].cmp(wctx[f].data()):
52 raise util.Abort(_("untracked file in working directory differs"
52 raise util.Abort(_("untracked file in working directory differs"
53 " from file in requested revision: '%s'") % f)
53 " from file in requested revision: '%s'") % f)
54
54
55 def _checkcollision(mctx):
55 def _checkcollision(mctx):
56 "check for case folding collisions in the destination context"
56 "check for case folding collisions in the destination context"
57 folded = {}
57 folded = {}
58 for fn in mctx:
58 for fn in mctx:
59 fold = fn.lower()
59 fold = fn.lower()
60 if fold in folded:
60 if fold in folded:
61 raise util.Abort(_("case-folding collision between %s and %s")
61 raise util.Abort(_("case-folding collision between %s and %s")
62 % (fn, folded[fold]))
62 % (fn, folded[fold]))
63 folded[fold] = fn
63 folded[fold] = fn
64
64
65 def _forgetremoved(wctx, mctx, branchmerge):
65 def _forgetremoved(wctx, mctx, branchmerge):
66 """
66 """
67 Forget removed files
67 Forget removed files
68
68
69 If we're jumping between revisions (as opposed to merging), and if
69 If we're jumping between revisions (as opposed to merging), and if
70 neither the working directory nor the target rev has the file,
70 neither the working directory nor the target rev has the file,
71 then we need to remove it from the dirstate, to prevent the
71 then we need to remove it from the dirstate, to prevent the
72 dirstate from listing the file when it is no longer in the
72 dirstate from listing the file when it is no longer in the
73 manifest.
73 manifest.
74
74
75 If we're merging, and the other revision has removed a file
75 If we're merging, and the other revision has removed a file
76 that is not present in the working directory, we need to mark it
76 that is not present in the working directory, we need to mark it
77 as removed.
77 as removed.
78 """
78 """
79
79
80 action = []
80 action = []
81 state = branchmerge and 'r' or 'f'
81 state = branchmerge and 'r' or 'f'
82 for f in wctx.deleted():
82 for f in wctx.deleted():
83 if f not in mctx:
83 if f not in mctx:
84 action.append((f, state))
84 action.append((f, state))
85
85
86 if not branchmerge:
86 if not branchmerge:
87 for f in wctx.removed():
87 for f in wctx.removed():
88 if f not in mctx:
88 if f not in mctx:
89 action.append((f, "f"))
89 action.append((f, "f"))
90
90
91 return action
91 return action
92
92
93 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
93 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
94 """
94 """
95 Merge p1 and p2 with ancestor ma and generate merge action list
95 Merge p1 and p2 with ancestor ma and generate merge action list
96
96
97 overwrite = whether we clobber working files
97 overwrite = whether we clobber working files
98 partial = function to filter file lists
98 partial = function to filter file lists
99 """
99 """
100
100
101 repo.ui.note(_("resolving manifests\n"))
101 repo.ui.note(_("resolving manifests\n"))
102 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
102 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
103 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
103 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
104
104
105 m1 = p1.manifest()
105 m1 = p1.manifest()
106 m2 = p2.manifest()
106 m2 = p2.manifest()
107 ma = pa.manifest()
107 ma = pa.manifest()
108 backwards = (pa == p2)
108 backwards = (pa == p2)
109 action = []
109 action = []
110 copy, copied, diverge = {}, {}, {}
110 copy, copied, diverge = {}, {}, {}
111
111
112 def fmerge(f, f2=None, fa=None):
112 def fmerge(f, f2=None, fa=None):
113 """merge flags"""
113 """merge flags"""
114 if not f2:
114 if not f2:
115 f2 = f
115 f2 = f
116 fa = f
116 fa = f
117 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
117 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
118 if m == n: # flags agree
118 if m == n: # flags agree
119 return m # unchanged
119 return m # unchanged
120 if m and n: # flags are set but don't agree
120 if m and n: # flags are set but don't agree
121 if not a: # both differ from parent
121 if not a: # both differ from parent
122 r = repo.ui.prompt(
122 r = repo.ui.prompt(
123 _(" conflicting flags for %s\n"
123 _(" conflicting flags for %s\n"
124 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
124 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
125 return r != "n" and r or ''
125 return r != "n" and r or ''
126 if m == a:
126 if m == a:
127 return n # changed from m to n
127 return n # changed from m to n
128 return m # changed from n to m
128 return m # changed from n to m
129 if m and m != a: # changed from a to m
129 if m and m != a: # changed from a to m
130 return m
130 return m
131 if n and n != a: # changed from a to n
131 if n and n != a: # changed from a to n
132 return n
132 return n
133 return '' # flag was cleared
133 return '' # flag was cleared
134
134
135 def act(msg, m, f, *args):
135 def act(msg, m, f, *args):
136 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
136 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
137 action.append((f, m) + args)
137 action.append((f, m) + args)
138
138
139 if pa and not (backwards or overwrite):
139 if pa and not (backwards or overwrite):
140 if repo.ui.configbool("merge", "followcopies", True):
140 if repo.ui.configbool("merge", "followcopies", True):
141 dirs = repo.ui.configbool("merge", "followdirs", True)
141 dirs = repo.ui.configbool("merge", "followdirs", True)
142 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
142 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
143 copied = dict.fromkeys(copy.values())
143 copied = dict.fromkeys(copy.values())
144 for of, fl in diverge.items():
144 for of, fl in diverge.items():
145 act("divergent renames", "dr", of, fl)
145 act("divergent renames", "dr", of, fl)
146
146
147 # Compare manifests
147 # Compare manifests
148 for f, n in m1.iteritems():
148 for f, n in m1.iteritems():
149 if partial and not partial(f):
149 if partial and not partial(f):
150 continue
150 continue
151 if f in m2:
151 if f in m2:
152 if overwrite or backwards:
152 if overwrite or backwards:
153 rflags = m2.flags(f)
153 rflags = m2.flags(f)
154 else:
154 else:
155 rflags = fmerge(f)
155 rflags = fmerge(f)
156 # are files different?
156 # are files different?
157 if n != m2[f]:
157 if n != m2[f]:
158 a = ma.get(f, nullid)
158 a = ma.get(f, nullid)
159 # are we clobbering?
159 # are we clobbering?
160 if overwrite:
160 if overwrite:
161 act("clobbering", "g", f, rflags)
161 act("clobbering", "g", f, rflags)
162 # or are we going back in time and clean?
162 # or are we going back in time and clean?
163 elif backwards and not n[20:]:
163 elif backwards and not n[20:]:
164 act("reverting", "g", f, rflags)
164 act("reverting", "g", f, rflags)
165 # are both different from the ancestor?
165 # are both different from the ancestor?
166 elif n != a and m2[f] != a:
166 elif n != a and m2[f] != a:
167 act("versions differ", "m", f, f, f, rflags, False)
167 act("versions differ", "m", f, f, f, rflags, False)
168 # is remote's version newer?
168 # is remote's version newer?
169 elif m2[f] != a:
169 elif m2[f] != a:
170 act("remote is newer", "g", f, rflags)
170 act("remote is newer", "g", f, rflags)
171 # local is newer, not overwrite, check mode bits
171 # local is newer, not overwrite, check mode bits
172 elif m1.flags(f) != rflags:
172 elif m1.flags(f) != rflags:
173 act("update permissions", "e", f, rflags)
173 act("update permissions", "e", f, rflags)
174 # contents same, check mode bits
174 # contents same, check mode bits
175 elif m1.flags(f) != rflags:
175 elif m1.flags(f) != rflags:
176 act("update permissions", "e", f, rflags)
176 act("update permissions", "e", f, rflags)
177 elif f in copied:
177 elif f in copied:
178 continue
178 continue
179 elif f in copy:
179 elif f in copy:
180 f2 = copy[f]
180 f2 = copy[f]
181 if f2 not in m2: # directory rename
181 if f2 not in m2: # directory rename
182 act("remote renamed directory to " + f2, "d",
182 act("remote renamed directory to " + f2, "d",
183 f, None, f2, m1.flags(f))
183 f, None, f2, m1.flags(f))
184 elif f2 in m1: # case 2 A,B/B/B
184 elif f2 in m1: # case 2 A,B/B/B
185 act("local copied to " + f2, "m",
185 act("local copied to " + f2, "m",
186 f, f2, f, fmerge(f, f2, f2), False)
186 f, f2, f, fmerge(f, f2, f2), False)
187 else: # case 4,21 A/B/B
187 else: # case 4,21 A/B/B
188 act("local moved to " + f2, "m",
188 act("local moved to " + f2, "m",
189 f, f2, f, fmerge(f, f2, f2), False)
189 f, f2, f, fmerge(f, f2, f2), False)
190 elif f in ma:
190 elif f in ma:
191 if n != ma[f] and not overwrite:
191 if n != ma[f] and not overwrite:
192 if repo.ui.prompt(
192 if repo.ui.prompt(
193 _(" local changed %s which remote deleted\n"
193 _(" local changed %s which remote deleted\n"
194 "use (c)hanged version or (d)elete?") % f,
194 "use (c)hanged version or (d)elete?") % f,
195 _("[cd]"), _("c")) == _("d"):
195 _("[cd]"), _("c")) == _("d"):
196 act("prompt delete", "r", f)
196 act("prompt delete", "r", f)
197 else:
197 else:
198 act("other deleted", "r", f)
198 act("other deleted", "r", f)
199 else:
199 else:
200 # file is created on branch or in working directory
200 # file is created on branch or in working directory
201 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
201 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
202 act("remote deleted", "r", f)
202 act("remote deleted", "r", f)
203
203
204 for f, n in m2.iteritems():
204 for f, n in m2.iteritems():
205 if partial and not partial(f):
205 if partial and not partial(f):
206 continue
206 continue
207 if f in m1:
207 if f in m1:
208 continue
208 continue
209 if f in copied:
209 if f in copied:
210 continue
210 continue
211 if f in copy:
211 if f in copy:
212 f2 = copy[f]
212 f2 = copy[f]
213 if f2 not in m1: # directory rename
213 if f2 not in m1: # directory rename
214 act("local renamed directory to " + f2, "d",
214 act("local renamed directory to " + f2, "d",
215 None, f, f2, m2.flags(f))
215 None, f, f2, m2.flags(f))
216 elif f2 in m2: # rename case 1, A/A,B/A
216 elif f2 in m2: # rename case 1, A/A,B/A
217 act("remote copied to " + f, "m",
217 act("remote copied to " + f, "m",
218 f2, f, f, fmerge(f2, f, f2), False)
218 f2, f, f, fmerge(f2, f, f2), False)
219 else: # case 3,20 A/B/A
219 else: # case 3,20 A/B/A
220 act("remote moved to " + f, "m",
220 act("remote moved to " + f, "m",
221 f2, f, f, fmerge(f2, f, f2), True)
221 f2, f, f, fmerge(f2, f, f2), True)
222 elif f in ma:
222 elif f in ma:
223 if overwrite or backwards:
223 if overwrite or backwards:
224 act("recreating", "g", f, m2.flags(f))
224 act("recreating", "g", f, m2.flags(f))
225 elif n != ma[f]:
225 elif n != ma[f]:
226 if repo.ui.prompt(
226 if repo.ui.prompt(
227 _("remote changed %s which local deleted\n"
227 _("remote changed %s which local deleted\n"
228 "use (c)hanged version or leave (d)eleted?") % f,
228 "use (c)hanged version or leave (d)eleted?") % f,
229 _("[cd]"), _("c")) == _("c"):
229 _("[cd]"), _("c")) == _("c"):
230 act("prompt recreating", "g", f, m2.flags(f))
230 act("prompt recreating", "g", f, m2.flags(f))
231 else:
231 else:
232 act("remote created", "g", f, m2.flags(f))
232 act("remote created", "g", f, m2.flags(f))
233
233
234 return action
234 return action
235
235
236 def applyupdates(repo, action, wctx, mctx):
236 def applyupdates(repo, action, wctx, mctx):
237 "apply the merge action list to the working directory"
237 "apply the merge action list to the working directory"
238
238
239 updated, merged, removed, unresolved = 0, 0, 0, 0
239 updated, merged, removed, unresolved = 0, 0, 0, 0
240 action.sort()
240 action.sort()
241
241
242 ms = mergestate(repo)
242 ms = mergestate(repo)
243 ms.reset(wctx.parents()[0].node())
243 ms.reset(wctx.parents()[0].node())
244 moves = []
244 moves = []
245
245
246 # prescan for merges
246 # prescan for merges
247 for a in action:
247 for a in action:
248 f, m = a[:2]
248 f, m = a[:2]
249 if m == 'm': # merge
249 if m == 'm': # merge
250 f2, fd, flags, move = a[2:]
250 f2, fd, flags, move = a[2:]
251 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
251 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
252 fcl = wctx[f]
252 fcl = wctx[f]
253 fco = mctx[f2]
253 fco = mctx[f2]
254 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
254 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
255 ms.add(fcl, fco, fca, fd, flags)
255 ms.add(fcl, fco, fca, fd, flags)
256 if f != fd and move:
256 if f != fd and move:
257 moves.append(f)
257 moves.append(f)
258
258
259 # remove renamed files after safely stored
259 # remove renamed files after safely stored
260 for f in moves:
260 for f in moves:
261 if util.lexists(repo.wjoin(f)):
261 if util.lexists(repo.wjoin(f)):
262 repo.ui.debug(_("removing %s\n") % f)
262 repo.ui.debug(_("removing %s\n") % f)
263 os.unlink(repo.wjoin(f))
263 os.unlink(repo.wjoin(f))
264
264
265 audit_path = util.path_auditor(repo.root)
265 audit_path = util.path_auditor(repo.root)
266
266
267 for a in action:
267 for a in action:
268 f, m = a[:2]
268 f, m = a[:2]
269 if f and f[0] == "/":
269 if f and f[0] == "/":
270 continue
270 continue
271 if m == "r": # remove
271 if m == "r": # remove
272 repo.ui.note(_("removing %s\n") % f)
272 repo.ui.note(_("removing %s\n") % f)
273 audit_path(f)
273 audit_path(f)
274 try:
274 try:
275 util.unlink(repo.wjoin(f))
275 util.unlink(repo.wjoin(f))
276 except OSError, inst:
276 except OSError, inst:
277 if inst.errno != errno.ENOENT:
277 if inst.errno != errno.ENOENT:
278 repo.ui.warn(_("update failed to remove %s: %s!\n") %
278 repo.ui.warn(_("update failed to remove %s: %s!\n") %
279 (f, inst.strerror))
279 (f, inst.strerror))
280 removed += 1
280 removed += 1
281 elif m == "m": # merge
281 elif m == "m": # merge
282 f2, fd, flags, move = a[2:]
282 f2, fd, flags, move = a[2:]
283 r = ms.resolve(fd, wctx, mctx)
283 r = ms.resolve(fd, wctx, mctx)
284 if r > 0:
284 if r > 0:
285 unresolved += 1
285 unresolved += 1
286 else:
286 else:
287 if r is None:
287 if r is None:
288 updated += 1
288 updated += 1
289 else:
289 else:
290 merged += 1
290 merged += 1
291 elif m == "g": # get
291 elif m == "g": # get
292 flags = a[2]
292 flags = a[2]
293 repo.ui.note(_("getting %s\n") % f)
293 repo.ui.note(_("getting %s\n") % f)
294 t = mctx.filectx(f).data()
294 t = mctx.filectx(f).data()
295 repo.wwrite(f, t, flags)
295 repo.wwrite(f, t, flags)
296 updated += 1
296 updated += 1
297 elif m == "d": # directory rename
297 elif m == "d": # directory rename
298 f2, fd, flags = a[2:]
298 f2, fd, flags = a[2:]
299 if f:
299 if f:
300 repo.ui.note(_("moving %s to %s\n") % (f, fd))
300 repo.ui.note(_("moving %s to %s\n") % (f, fd))
301 t = wctx.filectx(f).data()
301 t = wctx.filectx(f).data()
302 repo.wwrite(fd, t, flags)
302 repo.wwrite(fd, t, flags)
303 util.unlink(repo.wjoin(f))
303 util.unlink(repo.wjoin(f))
304 if f2:
304 if f2:
305 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
305 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
306 t = mctx.filectx(f2).data()
306 t = mctx.filectx(f2).data()
307 repo.wwrite(fd, t, flags)
307 repo.wwrite(fd, t, flags)
308 updated += 1
308 updated += 1
309 elif m == "dr": # divergent renames
309 elif m == "dr": # divergent renames
310 fl = a[2]
310 fl = a[2]
311 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
311 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
312 for nf in fl:
312 for nf in fl:
313 repo.ui.warn(" %s\n" % nf)
313 repo.ui.warn(" %s\n" % nf)
314 elif m == "e": # exec
314 elif m == "e": # exec
315 flags = a[2]
315 flags = a[2]
316 util.set_flags(repo.wjoin(f), flags)
316 util.set_flags(repo.wjoin(f), flags)
317
317
318 return updated, merged, removed, unresolved
318 return updated, merged, removed, unresolved
319
319
320 def recordupdates(repo, action, branchmerge):
320 def recordupdates(repo, action, branchmerge):
321 "record merge actions to the dirstate"
321 "record merge actions to the dirstate"
322
322
323 for a in action:
323 for a in action:
324 f, m = a[:2]
324 f, m = a[:2]
325 if m == "r": # remove
325 if m == "r": # remove
326 if branchmerge:
326 if branchmerge:
327 repo.dirstate.remove(f)
327 repo.dirstate.remove(f)
328 else:
328 else:
329 repo.dirstate.forget(f)
329 repo.dirstate.forget(f)
330 elif m == "f": # forget
330 elif m == "f": # forget
331 repo.dirstate.forget(f)
331 repo.dirstate.forget(f)
332 elif m in "ge": # get or exec change
332 elif m in "ge": # get or exec change
333 if branchmerge:
333 if branchmerge:
334 repo.dirstate.normaldirty(f)
334 repo.dirstate.normaldirty(f)
335 else:
335 else:
336 repo.dirstate.normal(f)
336 repo.dirstate.normal(f)
337 elif m == "m": # merge
337 elif m == "m": # merge
338 f2, fd, flag, move = a[2:]
338 f2, fd, flag, move = a[2:]
339 if branchmerge:
339 if branchmerge:
340 # We've done a branch merge, mark this file as merged
340 # We've done a branch merge, mark this file as merged
341 # so that we properly record the merger later
341 # so that we properly record the merger later
342 repo.dirstate.merge(fd)
342 repo.dirstate.merge(fd)
343 if f != f2: # copy/rename
343 if f != f2: # copy/rename
344 if move:
344 if move:
345 repo.dirstate.remove(f)
345 repo.dirstate.remove(f)
346 if f != fd:
346 if f != fd:
347 repo.dirstate.copy(f, fd)
347 repo.dirstate.copy(f, fd)
348 else:
348 else:
349 repo.dirstate.copy(f2, fd)
349 repo.dirstate.copy(f2, fd)
350 else:
350 else:
351 # We've update-merged a locally modified file, so
351 # We've update-merged a locally modified file, so
352 # we set the dirstate to emulate a normal checkout
352 # we set the dirstate to emulate a normal checkout
353 # of that file some time in the past. Thus our
353 # of that file some time in the past. Thus our
354 # merge will appear as a normal local file
354 # merge will appear as a normal local file
355 # modification.
355 # modification.
356 repo.dirstate.normallookup(fd)
356 repo.dirstate.normallookup(fd)
357 if move:
357 if move:
358 repo.dirstate.forget(f)
358 repo.dirstate.forget(f)
359 elif m == "d": # directory rename
359 elif m == "d": # directory rename
360 f2, fd, flag = a[2:]
360 f2, fd, flag = a[2:]
361 if not f2 and f not in repo.dirstate:
361 if not f2 and f not in repo.dirstate:
362 # untracked file moved
362 # untracked file moved
363 continue
363 continue
364 if branchmerge:
364 if branchmerge:
365 repo.dirstate.add(fd)
365 repo.dirstate.add(fd)
366 if f:
366 if f:
367 repo.dirstate.remove(f)
367 repo.dirstate.remove(f)
368 repo.dirstate.copy(f, fd)
368 repo.dirstate.copy(f, fd)
369 if f2:
369 if f2:
370 repo.dirstate.copy(f2, fd)
370 repo.dirstate.copy(f2, fd)
371 else:
371 else:
372 repo.dirstate.normal(fd)
372 repo.dirstate.normal(fd)
373 if f:
373 if f:
374 repo.dirstate.forget(f)
374 repo.dirstate.forget(f)
375
375
376 def update(repo, node, branchmerge, force, partial):
376 def update(repo, node, branchmerge, force, partial):
377 """
377 """
378 Perform a merge between the working directory and the given node
378 Perform a merge between the working directory and the given node
379
379
380 branchmerge = whether to merge between branches
380 branchmerge = whether to merge between branches
381 force = whether to force branch merging or file overwriting
381 force = whether to force branch merging or file overwriting
382 partial = a function to filter file lists (dirstate not updated)
382 partial = a function to filter file lists (dirstate not updated)
383 """
383 """
384
384
385 wlock = repo.wlock()
385 wlock = repo.wlock()
386 try:
386 try:
387 wc = repo.workingctx()
387 wc = repo.workingctx()
388 if node is None:
388 if node is None:
389 # tip of current branch
389 # tip of current branch
390 try:
390 try:
391 node = repo.branchtags()[wc.branch()]
391 node = repo.branchtags()[wc.branch()]
392 except KeyError:
392 except KeyError:
393 if wc.branch() == "default": # no default branch!
393 if wc.branch() == "default": # no default branch!
394 node = repo.lookup("tip") # update to tip
394 node = repo.lookup("tip") # update to tip
395 else:
395 else:
396 raise util.Abort(_("branch %s not found") % wc.branch())
396 raise util.Abort(_("branch %s not found") % wc.branch())
397 overwrite = force and not branchmerge
397 overwrite = force and not branchmerge
398 pl = wc.parents()
398 pl = wc.parents()
399 p1, p2 = pl[0], repo.changectx(node)
399 p1, p2 = pl[0], repo.changectx(node)
400 pa = p1.ancestor(p2)
400 pa = p1.ancestor(p2)
401 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
401 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
402 fastforward = False
402 fastforward = False
403
403
404 ### check phase
404 ### check phase
405 if not overwrite and len(pl) > 1:
405 if not overwrite and len(pl) > 1:
406 raise util.Abort(_("outstanding uncommitted merges"))
406 raise util.Abort(_("outstanding uncommitted merges"))
407 if branchmerge:
407 if branchmerge:
408 if pa == p2:
408 if pa == p2:
409 raise util.Abort(_("can't merge with ancestor"))
409 raise util.Abort(_("can't merge with ancestor"))
410 elif pa == p1:
410 elif pa == p1:
411 if p1.branch() != p2.branch():
411 if p1.branch() != p2.branch():
412 fastforward = True
412 fastforward = True
413 else:
413 else:
414 raise util.Abort(_("nothing to merge (use 'hg update'"
414 raise util.Abort(_("nothing to merge (use 'hg update'"
415 " or check 'hg heads')"))
415 " or check 'hg heads')"))
416 if not force and (wc.files() or wc.deleted()):
416 if not force and (wc.files() or wc.deleted()):
417 raise util.Abort(_("outstanding uncommitted changes"))
417 raise util.Abort(_("outstanding uncommitted changes"))
418 elif not overwrite:
418 elif not overwrite:
419 if pa == p1 or pa == p2: # linear
419 if pa == p1 or pa == p2: # linear
420 pass # all good
420 pass # all good
421 elif p1.branch() == p2.branch():
421 elif p1.branch() == p2.branch():
422 if wc.files() or wc.deleted():
422 if wc.files() or wc.deleted():
423 raise util.Abort(_("crosses branches (use 'hg merge' or "
423 raise util.Abort(_("crosses branches (use 'hg merge' or "
424 "'hg update -C' to discard changes)"))
424 "'hg update -C' to discard changes)"))
425 raise util.Abort(_("crosses branches (use 'hg merge' "
425 raise util.Abort(_("crosses branches (use 'hg merge' "
426 "or 'hg update -C')"))
426 "or 'hg update -C')"))
427 elif wc.files() or wc.deleted():
427 elif wc.files() or wc.deleted():
428 raise util.Abort(_("crosses named branches (use "
428 raise util.Abort(_("crosses named branches (use "
429 "'hg update -C' to discard changes)"))
429 "'hg update -C' to discard changes)"))
430 else:
430 else:
431 # Allow jumping branches if there are no changes
431 # Allow jumping branches if there are no changes
432 overwrite = True
432 overwrite = True
433
433
434 ### calculate phase
434 ### calculate phase
435 action = []
435 action = []
436 if not force:
436 if not force:
437 _checkunknown(wc, p2)
437 _checkunknown(wc, p2)
438 if not util.checkfolding(repo.path):
438 if not util.checkfolding(repo.path):
439 _checkcollision(p2)
439 _checkcollision(p2)
440 action += _forgetremoved(wc, p2, branchmerge)
440 action += _forgetremoved(wc, p2, branchmerge)
441 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
441 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
442
442
443 ### apply phase
443 ### apply phase
444 if not branchmerge: # just jump to the new rev
444 if not branchmerge: # just jump to the new rev
445 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
445 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
446 if not partial:
446 if not partial:
447 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
447 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
448
448
449 stats = applyupdates(repo, action, wc, p2)
449 stats = applyupdates(repo, action, wc, p2)
450
450
451 if not partial:
451 if not partial:
452 recordupdates(repo, action, branchmerge)
452 recordupdates(repo, action, branchmerge)
453 repo.dirstate.setparents(fp1, fp2)
453 repo.dirstate.setparents(fp1, fp2)
454 if not branchmerge and not fastforward:
454 if not branchmerge and not fastforward:
455 repo.dirstate.setbranch(p2.branch())
455 repo.dirstate.setbranch(p2.branch())
456 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
456 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
457
457
458 return stats
458 return stats
459 finally:
459 finally:
460 del wlock
460 del wlock
General Comments 0
You need to be logged in to leave comments. Login now