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