##// END OF EJS Templates
merge: fix test failures with new merge code on OS X...
Matt Mackall -
r21167:1ce131b3 stable
parent child Browse files
Show More
@@ -1,1084 +1,1085 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 import struct
8 import struct
9
9
10 from node import nullid, nullrev, hex, bin
10 from node import nullid, nullrev, hex, bin
11 from i18n import _
11 from i18n import _
12 from mercurial import obsolete
12 from mercurial import obsolete
13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
14 import errno, os, shutil
14 import errno, os, shutil
15
15
16 _pack = struct.pack
16 _pack = struct.pack
17 _unpack = struct.unpack
17 _unpack = struct.unpack
18
18
19 def _droponode(data):
19 def _droponode(data):
20 # used for compatibility for v1
20 # used for compatibility for v1
21 bits = data.split("\0")
21 bits = data.split("\0")
22 bits = bits[:-2] + bits[-1:]
22 bits = bits[:-2] + bits[-1:]
23 return "\0".join(bits)
23 return "\0".join(bits)
24
24
25 class mergestate(object):
25 class mergestate(object):
26 '''track 3-way merge state of individual files
26 '''track 3-way merge state of individual files
27
27
28 it is stored on disk when needed. Two file are used, one with an old
28 it is stored on disk when needed. Two file are used, one with an old
29 format, one with a new format. Both contains similar data, but the new
29 format, one with a new format. Both contains similar data, but the new
30 format can store new kind of field.
30 format can store new kind of field.
31
31
32 Current new format is a list of arbitrary record of the form:
32 Current new format is a list of arbitrary record of the form:
33
33
34 [type][length][content]
34 [type][length][content]
35
35
36 Type is a single character, length is a 4 bytes integer, content is an
36 Type is a single character, length is a 4 bytes integer, content is an
37 arbitrary suites of bytes of length `length`.
37 arbitrary suites of bytes of length `length`.
38
38
39 Type should be a letter. Capital letter are mandatory record, Mercurial
39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 should abort if they are unknown. lower case record can be safely ignored.
40 should abort if they are unknown. lower case record can be safely ignored.
41
41
42 Currently known record:
42 Currently known record:
43
43
44 L: the node of the "local" part of the merge (hexified version)
44 L: the node of the "local" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
46 F: a file to be merged entry
46 F: a file to be merged entry
47 '''
47 '''
48 statepathv1 = "merge/state"
48 statepathv1 = "merge/state"
49 statepathv2 = "merge/state2"
49 statepathv2 = "merge/state2"
50
50
51 def __init__(self, repo):
51 def __init__(self, repo):
52 self._repo = repo
52 self._repo = repo
53 self._dirty = False
53 self._dirty = False
54 self._read()
54 self._read()
55
55
56 def reset(self, node=None, other=None):
56 def reset(self, node=None, other=None):
57 self._state = {}
57 self._state = {}
58 if node:
58 if node:
59 self._local = node
59 self._local = node
60 self._other = other
60 self._other = other
61 shutil.rmtree(self._repo.join("merge"), True)
61 shutil.rmtree(self._repo.join("merge"), True)
62 self._dirty = False
62 self._dirty = False
63
63
64 def _read(self):
64 def _read(self):
65 """Analyse each record content to restore a serialized state from disk
65 """Analyse each record content to restore a serialized state from disk
66
66
67 This function process "record" entry produced by the de-serialization
67 This function process "record" entry produced by the de-serialization
68 of on disk file.
68 of on disk file.
69 """
69 """
70 self._state = {}
70 self._state = {}
71 records = self._readrecords()
71 records = self._readrecords()
72 for rtype, record in records:
72 for rtype, record in records:
73 if rtype == 'L':
73 if rtype == 'L':
74 self._local = bin(record)
74 self._local = bin(record)
75 elif rtype == 'O':
75 elif rtype == 'O':
76 self._other = bin(record)
76 self._other = bin(record)
77 elif rtype == "F":
77 elif rtype == "F":
78 bits = record.split("\0")
78 bits = record.split("\0")
79 self._state[bits[0]] = bits[1:]
79 self._state[bits[0]] = bits[1:]
80 elif not rtype.islower():
80 elif not rtype.islower():
81 raise util.Abort(_('unsupported merge state record: %s')
81 raise util.Abort(_('unsupported merge state record: %s')
82 % rtype)
82 % rtype)
83 self._dirty = False
83 self._dirty = False
84
84
85 def _readrecords(self):
85 def _readrecords(self):
86 """Read merge state from disk and return a list of record (TYPE, data)
86 """Read merge state from disk and return a list of record (TYPE, data)
87
87
88 We read data from both v1 and v2 files and decide which one to use.
88 We read data from both v1 and v2 files and decide which one to use.
89
89
90 V1 has been used by version prior to 2.9.1 and contains less data than
90 V1 has been used by version prior to 2.9.1 and contains less data than
91 v2. We read both versions and check if no data in v2 contradicts
91 v2. We read both versions and check if no data in v2 contradicts
92 v1. If there is not contradiction we can safely assume that both v1
92 v1. If there is not contradiction we can safely assume that both v1
93 and v2 were written at the same time and use the extract data in v2. If
93 and v2 were written at the same time and use the extract data in v2. If
94 there is contradiction we ignore v2 content as we assume an old version
94 there is contradiction we ignore v2 content as we assume an old version
95 of Mercurial has overwritten the mergestate file and left an old v2
95 of Mercurial has overwritten the mergestate file and left an old v2
96 file around.
96 file around.
97
97
98 returns list of record [(TYPE, data), ...]"""
98 returns list of record [(TYPE, data), ...]"""
99 v1records = self._readrecordsv1()
99 v1records = self._readrecordsv1()
100 v2records = self._readrecordsv2()
100 v2records = self._readrecordsv2()
101 oldv2 = set() # old format version of v2 record
101 oldv2 = set() # old format version of v2 record
102 for rec in v2records:
102 for rec in v2records:
103 if rec[0] == 'L':
103 if rec[0] == 'L':
104 oldv2.add(rec)
104 oldv2.add(rec)
105 elif rec[0] == 'F':
105 elif rec[0] == 'F':
106 # drop the onode data (not contained in v1)
106 # drop the onode data (not contained in v1)
107 oldv2.add(('F', _droponode(rec[1])))
107 oldv2.add(('F', _droponode(rec[1])))
108 for rec in v1records:
108 for rec in v1records:
109 if rec not in oldv2:
109 if rec not in oldv2:
110 # v1 file is newer than v2 file, use it
110 # v1 file is newer than v2 file, use it
111 # we have to infer the "other" changeset of the merge
111 # we have to infer the "other" changeset of the merge
112 # we cannot do better than that with v1 of the format
112 # we cannot do better than that with v1 of the format
113 mctx = self._repo[None].parents()[-1]
113 mctx = self._repo[None].parents()[-1]
114 v1records.append(('O', mctx.hex()))
114 v1records.append(('O', mctx.hex()))
115 # add place holder "other" file node information
115 # add place holder "other" file node information
116 # nobody is using it yet so we do no need to fetch the data
116 # nobody is using it yet so we do no need to fetch the data
117 # if mctx was wrong `mctx[bits[-2]]` may fails.
117 # if mctx was wrong `mctx[bits[-2]]` may fails.
118 for idx, r in enumerate(v1records):
118 for idx, r in enumerate(v1records):
119 if r[0] == 'F':
119 if r[0] == 'F':
120 bits = r[1].split("\0")
120 bits = r[1].split("\0")
121 bits.insert(-2, '')
121 bits.insert(-2, '')
122 v1records[idx] = (r[0], "\0".join(bits))
122 v1records[idx] = (r[0], "\0".join(bits))
123 return v1records
123 return v1records
124 else:
124 else:
125 return v2records
125 return v2records
126
126
127 def _readrecordsv1(self):
127 def _readrecordsv1(self):
128 """read on disk merge state for version 1 file
128 """read on disk merge state for version 1 file
129
129
130 returns list of record [(TYPE, data), ...]
130 returns list of record [(TYPE, data), ...]
131
131
132 Note: the "F" data from this file are one entry short
132 Note: the "F" data from this file are one entry short
133 (no "other file node" entry)
133 (no "other file node" entry)
134 """
134 """
135 records = []
135 records = []
136 try:
136 try:
137 f = self._repo.opener(self.statepathv1)
137 f = self._repo.opener(self.statepathv1)
138 for i, l in enumerate(f):
138 for i, l in enumerate(f):
139 if i == 0:
139 if i == 0:
140 records.append(('L', l[:-1]))
140 records.append(('L', l[:-1]))
141 else:
141 else:
142 records.append(('F', l[:-1]))
142 records.append(('F', l[:-1]))
143 f.close()
143 f.close()
144 except IOError, err:
144 except IOError, err:
145 if err.errno != errno.ENOENT:
145 if err.errno != errno.ENOENT:
146 raise
146 raise
147 return records
147 return records
148
148
149 def _readrecordsv2(self):
149 def _readrecordsv2(self):
150 """read on disk merge state for version 2 file
150 """read on disk merge state for version 2 file
151
151
152 returns list of record [(TYPE, data), ...]
152 returns list of record [(TYPE, data), ...]
153 """
153 """
154 records = []
154 records = []
155 try:
155 try:
156 f = self._repo.opener(self.statepathv2)
156 f = self._repo.opener(self.statepathv2)
157 data = f.read()
157 data = f.read()
158 off = 0
158 off = 0
159 end = len(data)
159 end = len(data)
160 while off < end:
160 while off < end:
161 rtype = data[off]
161 rtype = data[off]
162 off += 1
162 off += 1
163 length = _unpack('>I', data[off:(off + 4)])[0]
163 length = _unpack('>I', data[off:(off + 4)])[0]
164 off += 4
164 off += 4
165 record = data[off:(off + length)]
165 record = data[off:(off + length)]
166 off += length
166 off += length
167 records.append((rtype, record))
167 records.append((rtype, record))
168 f.close()
168 f.close()
169 except IOError, err:
169 except IOError, err:
170 if err.errno != errno.ENOENT:
170 if err.errno != errno.ENOENT:
171 raise
171 raise
172 return records
172 return records
173
173
174 def commit(self):
174 def commit(self):
175 """Write current state on disk (if necessary)"""
175 """Write current state on disk (if necessary)"""
176 if self._dirty:
176 if self._dirty:
177 records = []
177 records = []
178 records.append(("L", hex(self._local)))
178 records.append(("L", hex(self._local)))
179 records.append(("O", hex(self._other)))
179 records.append(("O", hex(self._other)))
180 for d, v in self._state.iteritems():
180 for d, v in self._state.iteritems():
181 records.append(("F", "\0".join([d] + v)))
181 records.append(("F", "\0".join([d] + v)))
182 self._writerecords(records)
182 self._writerecords(records)
183 self._dirty = False
183 self._dirty = False
184
184
185 def _writerecords(self, records):
185 def _writerecords(self, records):
186 """Write current state on disk (both v1 and v2)"""
186 """Write current state on disk (both v1 and v2)"""
187 self._writerecordsv1(records)
187 self._writerecordsv1(records)
188 self._writerecordsv2(records)
188 self._writerecordsv2(records)
189
189
190 def _writerecordsv1(self, records):
190 def _writerecordsv1(self, records):
191 """Write current state on disk in a version 1 file"""
191 """Write current state on disk in a version 1 file"""
192 f = self._repo.opener(self.statepathv1, "w")
192 f = self._repo.opener(self.statepathv1, "w")
193 irecords = iter(records)
193 irecords = iter(records)
194 lrecords = irecords.next()
194 lrecords = irecords.next()
195 assert lrecords[0] == 'L'
195 assert lrecords[0] == 'L'
196 f.write(hex(self._local) + "\n")
196 f.write(hex(self._local) + "\n")
197 for rtype, data in irecords:
197 for rtype, data in irecords:
198 if rtype == "F":
198 if rtype == "F":
199 f.write("%s\n" % _droponode(data))
199 f.write("%s\n" % _droponode(data))
200 f.close()
200 f.close()
201
201
202 def _writerecordsv2(self, records):
202 def _writerecordsv2(self, records):
203 """Write current state on disk in a version 2 file"""
203 """Write current state on disk in a version 2 file"""
204 f = self._repo.opener(self.statepathv2, "w")
204 f = self._repo.opener(self.statepathv2, "w")
205 for key, data in records:
205 for key, data in records:
206 assert len(key) == 1
206 assert len(key) == 1
207 format = ">sI%is" % len(data)
207 format = ">sI%is" % len(data)
208 f.write(_pack(format, key, len(data), data))
208 f.write(_pack(format, key, len(data), data))
209 f.close()
209 f.close()
210
210
211 def add(self, fcl, fco, fca, fd):
211 def add(self, fcl, fco, fca, fd):
212 """add a new (potentially?) conflicting file the merge state
212 """add a new (potentially?) conflicting file the merge state
213 fcl: file context for local,
213 fcl: file context for local,
214 fco: file context for remote,
214 fco: file context for remote,
215 fca: file context for ancestors,
215 fca: file context for ancestors,
216 fd: file path of the resulting merge.
216 fd: file path of the resulting merge.
217
217
218 note: also write the local version to the `.hg/merge` directory.
218 note: also write the local version to the `.hg/merge` directory.
219 """
219 """
220 hash = util.sha1(fcl.path()).hexdigest()
220 hash = util.sha1(fcl.path()).hexdigest()
221 self._repo.opener.write("merge/" + hash, fcl.data())
221 self._repo.opener.write("merge/" + hash, fcl.data())
222 self._state[fd] = ['u', hash, fcl.path(),
222 self._state[fd] = ['u', hash, fcl.path(),
223 fca.path(), hex(fca.filenode()),
223 fca.path(), hex(fca.filenode()),
224 fco.path(), hex(fco.filenode()),
224 fco.path(), hex(fco.filenode()),
225 fcl.flags()]
225 fcl.flags()]
226 self._dirty = True
226 self._dirty = True
227
227
228 def __contains__(self, dfile):
228 def __contains__(self, dfile):
229 return dfile in self._state
229 return dfile in self._state
230
230
231 def __getitem__(self, dfile):
231 def __getitem__(self, dfile):
232 return self._state[dfile][0]
232 return self._state[dfile][0]
233
233
234 def __iter__(self):
234 def __iter__(self):
235 l = self._state.keys()
235 l = self._state.keys()
236 l.sort()
236 l.sort()
237 for f in l:
237 for f in l:
238 yield f
238 yield f
239
239
240 def files(self):
240 def files(self):
241 return self._state.keys()
241 return self._state.keys()
242
242
243 def mark(self, dfile, state):
243 def mark(self, dfile, state):
244 self._state[dfile][0] = state
244 self._state[dfile][0] = state
245 self._dirty = True
245 self._dirty = True
246
246
247 def resolve(self, dfile, wctx):
247 def resolve(self, dfile, wctx):
248 """rerun merge process for file path `dfile`"""
248 """rerun merge process for file path `dfile`"""
249 if self[dfile] == 'r':
249 if self[dfile] == 'r':
250 return 0
250 return 0
251 stateentry = self._state[dfile]
251 stateentry = self._state[dfile]
252 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
252 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
253 octx = self._repo[self._other]
253 octx = self._repo[self._other]
254 fcd = wctx[dfile]
254 fcd = wctx[dfile]
255 fco = octx[ofile]
255 fco = octx[ofile]
256 fca = self._repo.filectx(afile, fileid=anode)
256 fca = self._repo.filectx(afile, fileid=anode)
257 # "premerge" x flags
257 # "premerge" x flags
258 flo = fco.flags()
258 flo = fco.flags()
259 fla = fca.flags()
259 fla = fca.flags()
260 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
260 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
261 if fca.node() == nullid:
261 if fca.node() == nullid:
262 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
262 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
263 afile)
263 afile)
264 elif flags == fla:
264 elif flags == fla:
265 flags = flo
265 flags = flo
266 # restore local
266 # restore local
267 f = self._repo.opener("merge/" + hash)
267 f = self._repo.opener("merge/" + hash)
268 self._repo.wwrite(dfile, f.read(), flags)
268 self._repo.wwrite(dfile, f.read(), flags)
269 f.close()
269 f.close()
270 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
270 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
271 if r is None:
271 if r is None:
272 # no real conflict
272 # no real conflict
273 del self._state[dfile]
273 del self._state[dfile]
274 self._dirty = True
274 self._dirty = True
275 elif not r:
275 elif not r:
276 self.mark(dfile, 'r')
276 self.mark(dfile, 'r')
277 return r
277 return r
278
278
279 def _checkunknownfile(repo, wctx, mctx, f):
279 def _checkunknownfile(repo, wctx, mctx, f):
280 return (not repo.dirstate._ignore(f)
280 return (not repo.dirstate._ignore(f)
281 and os.path.isfile(repo.wjoin(f))
281 and os.path.isfile(repo.wjoin(f))
282 and repo.wopener.audit.check(f)
282 and repo.wopener.audit.check(f)
283 and repo.dirstate.normalize(f) not in repo.dirstate
283 and repo.dirstate.normalize(f) not in repo.dirstate
284 and mctx[f].cmp(wctx[f]))
284 and mctx[f].cmp(wctx[f]))
285
285
286 def _checkunknown(repo, wctx, mctx):
286 def _checkunknown(repo, wctx, mctx):
287 "check for collisions between unknown files and files in mctx"
287 "check for collisions between unknown files and files in mctx"
288
288
289 error = False
289 error = False
290 for f in mctx:
290 for f in mctx:
291 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
291 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
292 error = True
292 error = True
293 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
293 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
294 if error:
294 if error:
295 raise util.Abort(_("untracked files in working directory differ "
295 raise util.Abort(_("untracked files in working directory differ "
296 "from files in requested revision"))
296 "from files in requested revision"))
297
297
298 def _forgetremoved(wctx, mctx, branchmerge):
298 def _forgetremoved(wctx, mctx, branchmerge):
299 """
299 """
300 Forget removed files
300 Forget removed files
301
301
302 If we're jumping between revisions (as opposed to merging), and if
302 If we're jumping between revisions (as opposed to merging), and if
303 neither the working directory nor the target rev has the file,
303 neither the working directory nor the target rev has the file,
304 then we need to remove it from the dirstate, to prevent the
304 then we need to remove it from the dirstate, to prevent the
305 dirstate from listing the file when it is no longer in the
305 dirstate from listing the file when it is no longer in the
306 manifest.
306 manifest.
307
307
308 If we're merging, and the other revision has removed a file
308 If we're merging, and the other revision has removed a file
309 that is not present in the working directory, we need to mark it
309 that is not present in the working directory, we need to mark it
310 as removed.
310 as removed.
311 """
311 """
312
312
313 actions = []
313 actions = []
314 state = branchmerge and 'r' or 'f'
314 state = branchmerge and 'r' or 'f'
315 for f in wctx.deleted():
315 for f in wctx.deleted():
316 if f not in mctx:
316 if f not in mctx:
317 actions.append((f, state, None, "forget deleted"))
317 actions.append((f, state, None, "forget deleted"))
318
318
319 if not branchmerge:
319 if not branchmerge:
320 for f in wctx.removed():
320 for f in wctx.removed():
321 if f not in mctx:
321 if f not in mctx:
322 actions.append((f, "f", None, "forget removed"))
322 actions.append((f, "f", None, "forget removed"))
323
323
324 return actions
324 return actions
325
325
326 def _checkcollision(repo, wmf, actions):
326 def _checkcollision(repo, wmf, actions):
327 # build provisional merged manifest up
327 # build provisional merged manifest up
328 pmmf = set(wmf)
328 pmmf = set(wmf)
329
329
330 def addop(f, args):
330 def addop(f, args):
331 pmmf.add(f)
331 pmmf.add(f)
332 def removeop(f, args):
332 def removeop(f, args):
333 pmmf.discard(f)
333 pmmf.discard(f)
334 def nop(f, args):
334 def nop(f, args):
335 pass
335 pass
336
336
337 def renamemoveop(f, args):
337 def renamemoveop(f, args):
338 f2, flags = args
338 f2, flags = args
339 pmmf.discard(f2)
339 pmmf.discard(f2)
340 pmmf.add(f)
340 pmmf.add(f)
341 def renamegetop(f, args):
341 def renamegetop(f, args):
342 f2, flags = args
342 f2, flags = args
343 pmmf.add(f)
343 pmmf.add(f)
344 def mergeop(f, args):
344 def mergeop(f, args):
345 f1, f2, fa, move, anc = args
345 f1, f2, fa, move, anc = args
346 if move:
346 if move:
347 pmmf.discard(f1)
347 pmmf.discard(f1)
348 pmmf.add(f)
348 pmmf.add(f)
349
349
350 opmap = {
350 opmap = {
351 "a": addop,
351 "a": addop,
352 "dm": renamemoveop,
352 "dm": renamemoveop,
353 "dg": renamegetop,
353 "dg": renamegetop,
354 "dr": nop,
354 "dr": nop,
355 "e": nop,
355 "e": nop,
356 "k": nop,
356 "f": addop, # untracked file should be kept in working directory
357 "f": addop, # untracked file should be kept in working directory
357 "g": addop,
358 "g": addop,
358 "m": mergeop,
359 "m": mergeop,
359 "r": removeop,
360 "r": removeop,
360 "rd": nop,
361 "rd": nop,
361 "cd": addop,
362 "cd": addop,
362 "dc": addop,
363 "dc": addop,
363 }
364 }
364 for f, m, args, msg in actions:
365 for f, m, args, msg in actions:
365 op = opmap.get(m)
366 op = opmap.get(m)
366 assert op, m
367 assert op, m
367 op(f, args)
368 op(f, args)
368
369
369 # check case-folding collision in provisional merged manifest
370 # check case-folding collision in provisional merged manifest
370 foldmap = {}
371 foldmap = {}
371 for f in sorted(pmmf):
372 for f in sorted(pmmf):
372 fold = util.normcase(f)
373 fold = util.normcase(f)
373 if fold in foldmap:
374 if fold in foldmap:
374 raise util.Abort(_("case-folding collision between %s and %s")
375 raise util.Abort(_("case-folding collision between %s and %s")
375 % (f, foldmap[fold]))
376 % (f, foldmap[fold]))
376 foldmap[fold] = f
377 foldmap[fold] = f
377
378
378 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
379 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
379 acceptremote, followcopies):
380 acceptremote, followcopies):
380 """
381 """
381 Merge p1 and p2 with ancestor pa and generate merge action list
382 Merge p1 and p2 with ancestor pa and generate merge action list
382
383
383 branchmerge and force are as passed in to update
384 branchmerge and force are as passed in to update
384 partial = function to filter file lists
385 partial = function to filter file lists
385 acceptremote = accept the incoming changes without prompting
386 acceptremote = accept the incoming changes without prompting
386 """
387 """
387
388
388 actions, copy, movewithdir = [], {}, {}
389 actions, copy, movewithdir = [], {}, {}
389
390
390 # manifests fetched in order are going to be faster, so prime the caches
391 # manifests fetched in order are going to be faster, so prime the caches
391 [x.manifest() for x in
392 [x.manifest() for x in
392 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
393 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
393
394
394 if followcopies:
395 if followcopies:
395 ret = copies.mergecopies(repo, wctx, p2, pa)
396 ret = copies.mergecopies(repo, wctx, p2, pa)
396 copy, movewithdir, diverge, renamedelete = ret
397 copy, movewithdir, diverge, renamedelete = ret
397 for of, fl in diverge.iteritems():
398 for of, fl in diverge.iteritems():
398 actions.append((of, "dr", (fl,), "divergent renames"))
399 actions.append((of, "dr", (fl,), "divergent renames"))
399 for of, fl in renamedelete.iteritems():
400 for of, fl in renamedelete.iteritems():
400 actions.append((of, "rd", (fl,), "rename and delete"))
401 actions.append((of, "rd", (fl,), "rename and delete"))
401
402
402 repo.ui.note(_("resolving manifests\n"))
403 repo.ui.note(_("resolving manifests\n"))
403 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
404 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
404 % (bool(branchmerge), bool(force), bool(partial)))
405 % (bool(branchmerge), bool(force), bool(partial)))
405 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
406 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
406
407
407 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
408 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
408 copied = set(copy.values())
409 copied = set(copy.values())
409 copied.update(movewithdir.values())
410 copied.update(movewithdir.values())
410
411
411 if '.hgsubstate' in m1:
412 if '.hgsubstate' in m1:
412 # check whether sub state is modified
413 # check whether sub state is modified
413 for s in sorted(wctx.substate):
414 for s in sorted(wctx.substate):
414 if wctx.sub(s).dirty():
415 if wctx.sub(s).dirty():
415 m1['.hgsubstate'] += "+"
416 m1['.hgsubstate'] += "+"
416 break
417 break
417
418
418 aborts = []
419 aborts = []
419 # Compare manifests
420 # Compare manifests
420 fdiff = dicthelpers.diff(m1, m2)
421 fdiff = dicthelpers.diff(m1, m2)
421 flagsdiff = m1.flagsdiff(m2)
422 flagsdiff = m1.flagsdiff(m2)
422 diff12 = dicthelpers.join(fdiff, flagsdiff)
423 diff12 = dicthelpers.join(fdiff, flagsdiff)
423
424
424 for f, (n12, fl12) in diff12.iteritems():
425 for f, (n12, fl12) in diff12.iteritems():
425 if n12:
426 if n12:
426 n1, n2 = n12
427 n1, n2 = n12
427 else: # file contents didn't change, but flags did
428 else: # file contents didn't change, but flags did
428 n1 = n2 = m1.get(f, None)
429 n1 = n2 = m1.get(f, None)
429 if n1 is None:
430 if n1 is None:
430 # Since n1 == n2, the file isn't present in m2 either. This
431 # Since n1 == n2, the file isn't present in m2 either. This
431 # means that the file was removed or deleted locally and
432 # means that the file was removed or deleted locally and
432 # removed remotely, but that residual entries remain in flags.
433 # removed remotely, but that residual entries remain in flags.
433 # This can happen in manifests generated by workingctx.
434 # This can happen in manifests generated by workingctx.
434 continue
435 continue
435 if fl12:
436 if fl12:
436 fl1, fl2 = fl12
437 fl1, fl2 = fl12
437 else: # flags didn't change, file contents did
438 else: # flags didn't change, file contents did
438 fl1 = fl2 = m1.flags(f)
439 fl1 = fl2 = m1.flags(f)
439
440
440 if partial and not partial(f):
441 if partial and not partial(f):
441 continue
442 continue
442 if n1 and n2:
443 if n1 and n2:
443 fa = f
444 fa = f
444 a = ma.get(f, nullid)
445 a = ma.get(f, nullid)
445 if a == nullid:
446 if a == nullid:
446 fa = copy.get(f, f)
447 fa = copy.get(f, f)
447 # Note: f as default is wrong - we can't really make a 3-way
448 # Note: f as default is wrong - we can't really make a 3-way
448 # merge without an ancestor file.
449 # merge without an ancestor file.
449 fla = ma.flags(fa)
450 fla = ma.flags(fa)
450 nol = 'l' not in fl1 + fl2 + fla
451 nol = 'l' not in fl1 + fl2 + fla
451 if n2 == a and fl2 == fla:
452 if n2 == a and fl2 == fla:
452 actions.append((f, "k", (), "keep")) # remote unchanged
453 actions.append((f, "k", (), "keep")) # remote unchanged
453 elif n1 == a and fl1 == fla: # local unchanged - use remote
454 elif n1 == a and fl1 == fla: # local unchanged - use remote
454 if n1 == n2: # optimization: keep local content
455 if n1 == n2: # optimization: keep local content
455 actions.append((f, "e", (fl2,), "update permissions"))
456 actions.append((f, "e", (fl2,), "update permissions"))
456 else:
457 else:
457 actions.append((f, "g", (fl2,), "remote is newer"))
458 actions.append((f, "g", (fl2,), "remote is newer"))
458 elif nol and n2 == a: # remote only changed 'x'
459 elif nol and n2 == a: # remote only changed 'x'
459 actions.append((f, "e", (fl2,), "update permissions"))
460 actions.append((f, "e", (fl2,), "update permissions"))
460 elif nol and n1 == a: # local only changed 'x'
461 elif nol and n1 == a: # local only changed 'x'
461 actions.append((f, "g", (fl1,), "remote is newer"))
462 actions.append((f, "g", (fl1,), "remote is newer"))
462 else: # both changed something
463 else: # both changed something
463 actions.append((f, "m", (f, f, fa, False, pa.node()),
464 actions.append((f, "m", (f, f, fa, False, pa.node()),
464 "versions differ"))
465 "versions differ"))
465 elif f in copied: # files we'll deal with on m2 side
466 elif f in copied: # files we'll deal with on m2 side
466 pass
467 pass
467 elif n1 and f in movewithdir: # directory rename, move local
468 elif n1 and f in movewithdir: # directory rename, move local
468 f2 = movewithdir[f]
469 f2 = movewithdir[f]
469 actions.append((f2, "dm", (f, fl1),
470 actions.append((f2, "dm", (f, fl1),
470 "remote directory rename - move from " + f))
471 "remote directory rename - move from " + f))
471 elif n1 and f in copy:
472 elif n1 and f in copy:
472 f2 = copy[f]
473 f2 = copy[f]
473 actions.append((f, "m", (f, f2, f2, False, pa.node()),
474 actions.append((f, "m", (f, f2, f2, False, pa.node()),
474 "local copied/moved from " + f2))
475 "local copied/moved from " + f2))
475 elif n1 and f in ma: # clean, a different, no remote
476 elif n1 and f in ma: # clean, a different, no remote
476 if n1 != ma[f]:
477 if n1 != ma[f]:
477 if acceptremote:
478 if acceptremote:
478 actions.append((f, "r", None, "remote delete"))
479 actions.append((f, "r", None, "remote delete"))
479 else:
480 else:
480 actions.append((f, "cd", None, "prompt changed/deleted"))
481 actions.append((f, "cd", None, "prompt changed/deleted"))
481 elif n1[20:] == "a": # added, no remote
482 elif n1[20:] == "a": # added, no remote
482 actions.append((f, "f", None, "remote deleted"))
483 actions.append((f, "f", None, "remote deleted"))
483 else:
484 else:
484 actions.append((f, "r", None, "other deleted"))
485 actions.append((f, "r", None, "other deleted"))
485 elif n2 and f in movewithdir:
486 elif n2 and f in movewithdir:
486 f2 = movewithdir[f]
487 f2 = movewithdir[f]
487 actions.append((f2, "dg", (f, fl2),
488 actions.append((f2, "dg", (f, fl2),
488 "local directory rename - get from " + f))
489 "local directory rename - get from " + f))
489 elif n2 and f in copy:
490 elif n2 and f in copy:
490 f2 = copy[f]
491 f2 = copy[f]
491 if f2 in m2:
492 if f2 in m2:
492 actions.append((f, "m", (f2, f, f2, False, pa.node()),
493 actions.append((f, "m", (f2, f, f2, False, pa.node()),
493 "remote copied from " + f2))
494 "remote copied from " + f2))
494 else:
495 else:
495 actions.append((f, "m", (f2, f, f2, True, pa.node()),
496 actions.append((f, "m", (f2, f, f2, True, pa.node()),
496 "remote moved from " + f2))
497 "remote moved from " + f2))
497 elif n2 and f not in ma:
498 elif n2 and f not in ma:
498 # local unknown, remote created: the logic is described by the
499 # local unknown, remote created: the logic is described by the
499 # following table:
500 # following table:
500 #
501 #
501 # force branchmerge different | action
502 # force branchmerge different | action
502 # n * n | get
503 # n * n | get
503 # n * y | abort
504 # n * y | abort
504 # y n * | get
505 # y n * | get
505 # y y n | get
506 # y y n | get
506 # y y y | merge
507 # y y y | merge
507 #
508 #
508 # Checking whether the files are different is expensive, so we
509 # Checking whether the files are different is expensive, so we
509 # don't do that when we can avoid it.
510 # don't do that when we can avoid it.
510 if force and not branchmerge:
511 if force and not branchmerge:
511 actions.append((f, "g", (fl2,), "remote created"))
512 actions.append((f, "g", (fl2,), "remote created"))
512 else:
513 else:
513 different = _checkunknownfile(repo, wctx, p2, f)
514 different = _checkunknownfile(repo, wctx, p2, f)
514 if force and branchmerge and different:
515 if force and branchmerge and different:
515 # FIXME: This is wrong - f is not in ma ...
516 # FIXME: This is wrong - f is not in ma ...
516 actions.append((f, "m", (f, f, f, False, pa.node()),
517 actions.append((f, "m", (f, f, f, False, pa.node()),
517 "remote differs from untracked local"))
518 "remote differs from untracked local"))
518 elif not force and different:
519 elif not force and different:
519 aborts.append((f, "ud"))
520 aborts.append((f, "ud"))
520 else:
521 else:
521 actions.append((f, "g", (fl2,), "remote created"))
522 actions.append((f, "g", (fl2,), "remote created"))
522 elif n2 and n2 != ma[f]:
523 elif n2 and n2 != ma[f]:
523 different = _checkunknownfile(repo, wctx, p2, f)
524 different = _checkunknownfile(repo, wctx, p2, f)
524 if not force and different:
525 if not force and different:
525 aborts.append((f, "ud"))
526 aborts.append((f, "ud"))
526 else:
527 else:
527 # if different: old untracked f may be overwritten and lost
528 # if different: old untracked f may be overwritten and lost
528 if acceptremote:
529 if acceptremote:
529 actions.append((f, "g", (m2.flags(f),),
530 actions.append((f, "g", (m2.flags(f),),
530 "remote recreating"))
531 "remote recreating"))
531 else:
532 else:
532 actions.append((f, "dc", (m2.flags(f),),
533 actions.append((f, "dc", (m2.flags(f),),
533 "prompt deleted/changed"))
534 "prompt deleted/changed"))
534
535
535 for f, m in sorted(aborts):
536 for f, m in sorted(aborts):
536 if m == "ud":
537 if m == "ud":
537 repo.ui.warn(_("%s: untracked file differs\n") % f)
538 repo.ui.warn(_("%s: untracked file differs\n") % f)
538 else: assert False, m
539 else: assert False, m
539 if aborts:
540 if aborts:
540 raise util.Abort(_("untracked files in working directory differ "
541 raise util.Abort(_("untracked files in working directory differ "
541 "from files in requested revision"))
542 "from files in requested revision"))
542
543
543 if not util.checkcase(repo.path):
544 if not util.checkcase(repo.path):
544 # check collision between files only in p2 for clean update
545 # check collision between files only in p2 for clean update
545 if (not branchmerge and
546 if (not branchmerge and
546 (force or not wctx.dirty(missing=True, branch=False))):
547 (force or not wctx.dirty(missing=True, branch=False))):
547 _checkcollision(repo, m2, [])
548 _checkcollision(repo, m2, [])
548 else:
549 else:
549 _checkcollision(repo, m1, actions)
550 _checkcollision(repo, m1, actions)
550
551
551 return actions
552 return actions
552
553
553 def actionkey(a):
554 def actionkey(a):
554 return a[1] in "rf" and -1 or 0, a
555 return a[1] in "rf" and -1 or 0, a
555
556
556 def getremove(repo, mctx, overwrite, args):
557 def getremove(repo, mctx, overwrite, args):
557 """apply usually-non-interactive updates to the working directory
558 """apply usually-non-interactive updates to the working directory
558
559
559 mctx is the context to be merged into the working copy
560 mctx is the context to be merged into the working copy
560
561
561 yields tuples for progress updates
562 yields tuples for progress updates
562 """
563 """
563 verbose = repo.ui.verbose
564 verbose = repo.ui.verbose
564 unlink = util.unlinkpath
565 unlink = util.unlinkpath
565 wjoin = repo.wjoin
566 wjoin = repo.wjoin
566 fctx = mctx.filectx
567 fctx = mctx.filectx
567 wwrite = repo.wwrite
568 wwrite = repo.wwrite
568 audit = repo.wopener.audit
569 audit = repo.wopener.audit
569 i = 0
570 i = 0
570 for arg in args:
571 for arg in args:
571 f = arg[0]
572 f = arg[0]
572 if arg[1] == 'r':
573 if arg[1] == 'r':
573 if verbose:
574 if verbose:
574 repo.ui.note(_("removing %s\n") % f)
575 repo.ui.note(_("removing %s\n") % f)
575 audit(f)
576 audit(f)
576 try:
577 try:
577 unlink(wjoin(f), ignoremissing=True)
578 unlink(wjoin(f), ignoremissing=True)
578 except OSError, inst:
579 except OSError, inst:
579 repo.ui.warn(_("update failed to remove %s: %s!\n") %
580 repo.ui.warn(_("update failed to remove %s: %s!\n") %
580 (f, inst.strerror))
581 (f, inst.strerror))
581 else:
582 else:
582 if verbose:
583 if verbose:
583 repo.ui.note(_("getting %s\n") % f)
584 repo.ui.note(_("getting %s\n") % f)
584 wwrite(f, fctx(f).data(), arg[2][0])
585 wwrite(f, fctx(f).data(), arg[2][0])
585 if i == 100:
586 if i == 100:
586 yield i, f
587 yield i, f
587 i = 0
588 i = 0
588 i += 1
589 i += 1
589 if i > 0:
590 if i > 0:
590 yield i, f
591 yield i, f
591
592
592 def applyupdates(repo, actions, wctx, mctx, overwrite):
593 def applyupdates(repo, actions, wctx, mctx, overwrite):
593 """apply the merge action list to the working directory
594 """apply the merge action list to the working directory
594
595
595 wctx is the working copy context
596 wctx is the working copy context
596 mctx is the context to be merged into the working copy
597 mctx is the context to be merged into the working copy
597
598
598 Return a tuple of counts (updated, merged, removed, unresolved) that
599 Return a tuple of counts (updated, merged, removed, unresolved) that
599 describes how many files were affected by the update.
600 describes how many files were affected by the update.
600 """
601 """
601
602
602 updated, merged, removed, unresolved = 0, 0, 0, 0
603 updated, merged, removed, unresolved = 0, 0, 0, 0
603 ms = mergestate(repo)
604 ms = mergestate(repo)
604 ms.reset(wctx.p1().node(), mctx.node())
605 ms.reset(wctx.p1().node(), mctx.node())
605 moves = []
606 moves = []
606 actions.sort(key=actionkey)
607 actions.sort(key=actionkey)
607
608
608 # prescan for merges
609 # prescan for merges
609 for a in actions:
610 for a in actions:
610 f, m, args, msg = a
611 f, m, args, msg = a
611 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
612 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
612 if m == "m": # merge
613 if m == "m": # merge
613 f1, f2, fa, move, anc = args
614 f1, f2, fa, move, anc = args
614 if f == '.hgsubstate': # merged internally
615 if f == '.hgsubstate': # merged internally
615 continue
616 continue
616 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
617 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
617 fcl = wctx[f1]
618 fcl = wctx[f1]
618 fco = mctx[f2]
619 fco = mctx[f2]
619 actx = repo[anc]
620 actx = repo[anc]
620 if fa in actx:
621 if fa in actx:
621 fca = actx[fa]
622 fca = actx[fa]
622 else:
623 else:
623 fca = repo.filectx(f1, fileid=nullrev)
624 fca = repo.filectx(f1, fileid=nullrev)
624 ms.add(fcl, fco, fca, f)
625 ms.add(fcl, fco, fca, f)
625 if f1 != f and move:
626 if f1 != f and move:
626 moves.append(f1)
627 moves.append(f1)
627
628
628 audit = repo.wopener.audit
629 audit = repo.wopener.audit
629
630
630 # remove renamed files after safely stored
631 # remove renamed files after safely stored
631 for f in moves:
632 for f in moves:
632 if os.path.lexists(repo.wjoin(f)):
633 if os.path.lexists(repo.wjoin(f)):
633 repo.ui.debug("removing %s\n" % f)
634 repo.ui.debug("removing %s\n" % f)
634 audit(f)
635 audit(f)
635 util.unlinkpath(repo.wjoin(f))
636 util.unlinkpath(repo.wjoin(f))
636
637
637 numupdates = len([a for a in actions if a[1] != 'k'])
638 numupdates = len([a for a in actions if a[1] != 'k'])
638 workeractions = [a for a in actions if a[1] in 'gr']
639 workeractions = [a for a in actions if a[1] in 'gr']
639 updateactions = [a for a in workeractions if a[1] == 'g']
640 updateactions = [a for a in workeractions if a[1] == 'g']
640 updated = len(updateactions)
641 updated = len(updateactions)
641 removeactions = [a for a in workeractions if a[1] == 'r']
642 removeactions = [a for a in workeractions if a[1] == 'r']
642 removed = len(removeactions)
643 removed = len(removeactions)
643 actions = [a for a in actions if a[1] not in 'grk']
644 actions = [a for a in actions if a[1] not in 'grk']
644
645
645 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
646 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
646 if hgsub and hgsub[0] == 'r':
647 if hgsub and hgsub[0] == 'r':
647 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
648 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
648
649
649 z = 0
650 z = 0
650 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
651 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
651 removeactions)
652 removeactions)
652 for i, item in prog:
653 for i, item in prog:
653 z += i
654 z += i
654 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
655 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
655 unit=_('files'))
656 unit=_('files'))
656 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
657 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
657 updateactions)
658 updateactions)
658 for i, item in prog:
659 for i, item in prog:
659 z += i
660 z += i
660 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
661 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
661 unit=_('files'))
662 unit=_('files'))
662
663
663 if hgsub and hgsub[0] == 'g':
664 if hgsub and hgsub[0] == 'g':
664 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
665 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
665
666
666 _updating = _('updating')
667 _updating = _('updating')
667 _files = _('files')
668 _files = _('files')
668 progress = repo.ui.progress
669 progress = repo.ui.progress
669
670
670 for i, a in enumerate(actions):
671 for i, a in enumerate(actions):
671 f, m, args, msg = a
672 f, m, args, msg = a
672 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
673 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
673 if m == "m": # merge
674 if m == "m": # merge
674 f1, f2, fa, move, anc = args
675 f1, f2, fa, move, anc = args
675 if f == '.hgsubstate': # subrepo states need updating
676 if f == '.hgsubstate': # subrepo states need updating
676 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
677 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
677 overwrite)
678 overwrite)
678 continue
679 continue
679 audit(f)
680 audit(f)
680 r = ms.resolve(f, wctx)
681 r = ms.resolve(f, wctx)
681 if r is not None and r > 0:
682 if r is not None and r > 0:
682 unresolved += 1
683 unresolved += 1
683 else:
684 else:
684 if r is None:
685 if r is None:
685 updated += 1
686 updated += 1
686 else:
687 else:
687 merged += 1
688 merged += 1
688 elif m == "dm": # directory rename, move local
689 elif m == "dm": # directory rename, move local
689 f0, flags = args
690 f0, flags = args
690 repo.ui.note(_("moving %s to %s\n") % (f0, f))
691 repo.ui.note(_("moving %s to %s\n") % (f0, f))
691 audit(f)
692 audit(f)
692 repo.wwrite(f, wctx.filectx(f0).data(), flags)
693 repo.wwrite(f, wctx.filectx(f0).data(), flags)
693 util.unlinkpath(repo.wjoin(f0))
694 util.unlinkpath(repo.wjoin(f0))
694 updated += 1
695 updated += 1
695 elif m == "dg": # local directory rename, get
696 elif m == "dg": # local directory rename, get
696 f0, flags = args
697 f0, flags = args
697 repo.ui.note(_("getting %s to %s\n") % (f0, f))
698 repo.ui.note(_("getting %s to %s\n") % (f0, f))
698 repo.wwrite(f, mctx.filectx(f0).data(), flags)
699 repo.wwrite(f, mctx.filectx(f0).data(), flags)
699 updated += 1
700 updated += 1
700 elif m == "dr": # divergent renames
701 elif m == "dr": # divergent renames
701 fl, = args
702 fl, = args
702 repo.ui.warn(_("note: possible conflict - %s was renamed "
703 repo.ui.warn(_("note: possible conflict - %s was renamed "
703 "multiple times to:\n") % f)
704 "multiple times to:\n") % f)
704 for nf in fl:
705 for nf in fl:
705 repo.ui.warn(" %s\n" % nf)
706 repo.ui.warn(" %s\n" % nf)
706 elif m == "rd": # rename and delete
707 elif m == "rd": # rename and delete
707 fl, = args
708 fl, = args
708 repo.ui.warn(_("note: possible conflict - %s was deleted "
709 repo.ui.warn(_("note: possible conflict - %s was deleted "
709 "and renamed to:\n") % f)
710 "and renamed to:\n") % f)
710 for nf in fl:
711 for nf in fl:
711 repo.ui.warn(" %s\n" % nf)
712 repo.ui.warn(" %s\n" % nf)
712 elif m == "e": # exec
713 elif m == "e": # exec
713 flags, = args
714 flags, = args
714 audit(f)
715 audit(f)
715 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
716 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
716 updated += 1
717 updated += 1
717 ms.commit()
718 ms.commit()
718 progress(_updating, None, total=numupdates, unit=_files)
719 progress(_updating, None, total=numupdates, unit=_files)
719
720
720 return updated, merged, removed, unresolved
721 return updated, merged, removed, unresolved
721
722
722 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
723 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
723 acceptremote, followcopies):
724 acceptremote, followcopies):
724 "Calculate the actions needed to merge mctx into wctx using ancestors"
725 "Calculate the actions needed to merge mctx into wctx using ancestors"
725
726
726 if len(ancestors) == 1: # default
727 if len(ancestors) == 1: # default
727 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
728 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
728 branchmerge, force,
729 branchmerge, force,
729 partial, acceptremote, followcopies)
730 partial, acceptremote, followcopies)
730
731
731 else: # only when merge.preferancestor=* - experimentalish code
732 else: # only when merge.preferancestor=* - experimentalish code
732 # Call for bids
733 # Call for bids
733 fbids = {} # mapping filename to list af action bids
734 fbids = {} # mapping filename to list af action bids
734 for ancestor in ancestors:
735 for ancestor in ancestors:
735 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
736 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
736 actions = manifestmerge(repo, wctx, mctx, ancestor,
737 actions = manifestmerge(repo, wctx, mctx, ancestor,
737 branchmerge, force,
738 branchmerge, force,
738 partial, acceptremote, followcopies)
739 partial, acceptremote, followcopies)
739 for a in sorted(actions):
740 for a in sorted(actions):
740 repo.ui.debug(' %s: %s\n' % (a[0], a[1]))
741 repo.ui.debug(' %s: %s\n' % (a[0], a[1]))
741 f = a[0]
742 f = a[0]
742 if f in fbids:
743 if f in fbids:
743 fbids[f].append(a)
744 fbids[f].append(a)
744 else:
745 else:
745 fbids[f] = [a]
746 fbids[f] = [a]
746
747
747 # Pick the best bid for each file
748 # Pick the best bid for each file
748 repo.ui.note(_('\nauction for merging merge bids\n'))
749 repo.ui.note(_('\nauction for merging merge bids\n'))
749 actions = []
750 actions = []
750 for f, bidsl in sorted(fbids.items()):
751 for f, bidsl in sorted(fbids.items()):
751 # Consensus?
752 # Consensus?
752 a0 = bidsl[0]
753 a0 = bidsl[0]
753 if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1
754 if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1
754 repo.ui.note(" %s: consensus for %s\n" % (f, a0[1]))
755 repo.ui.note(" %s: consensus for %s\n" % (f, a0[1]))
755 actions.append(a0)
756 actions.append(a0)
756 continue
757 continue
757 # Group bids by kind of action
758 # Group bids by kind of action
758 bids = {}
759 bids = {}
759 for a in bidsl:
760 for a in bidsl:
760 m = a[1]
761 m = a[1]
761 if m in bids:
762 if m in bids:
762 bids[m].append(a)
763 bids[m].append(a)
763 else:
764 else:
764 bids[m] = [a]
765 bids[m] = [a]
765 # If keep is an option, just do it.
766 # If keep is an option, just do it.
766 if "k" in bids:
767 if "k" in bids:
767 repo.ui.note(" %s: picking 'keep' action\n" % f)
768 repo.ui.note(" %s: picking 'keep' action\n" % f)
768 actions.append(bids["k"][0])
769 actions.append(bids["k"][0])
769 continue
770 continue
770 # If all gets agree [how could they not?], just do it.
771 # If all gets agree [how could they not?], just do it.
771 if "g" in bids:
772 if "g" in bids:
772 ga0 = bids["g"][0]
773 ga0 = bids["g"][0]
773 if util.all(a == ga0 for a in bids["g"][1:]):
774 if util.all(a == ga0 for a in bids["g"][1:]):
774 repo.ui.note(" %s: picking 'get' action\n" % f)
775 repo.ui.note(" %s: picking 'get' action\n" % f)
775 actions.append(ga0)
776 actions.append(ga0)
776 continue
777 continue
777 # TODO: Consider other simple actions such as mode changes
778 # TODO: Consider other simple actions such as mode changes
778 # Handle inefficient democrazy.
779 # Handle inefficient democrazy.
779 repo.ui.note(_(' %s: multiple merge bids:\n') % (f, m))
780 repo.ui.note(_(' %s: multiple merge bids:\n') % (f, m))
780 for a in bidsl:
781 for a in bidsl:
781 repo.ui.note(' %s: %s\n' % (f, a[1]))
782 repo.ui.note(' %s: %s\n' % (f, a[1]))
782 # Pick random action. TODO: Instead, prompt user when resolving
783 # Pick random action. TODO: Instead, prompt user when resolving
783 a0 = bidsl[0]
784 a0 = bidsl[0]
784 repo.ui.warn(_(' %s: ambiguous merge - picked %s action)\n') %
785 repo.ui.warn(_(' %s: ambiguous merge - picked %s action)\n') %
785 (f, a0[1]))
786 (f, a0[1]))
786 actions.append(a0)
787 actions.append(a0)
787 continue
788 continue
788 repo.ui.note(_('end of auction\n\n'))
789 repo.ui.note(_('end of auction\n\n'))
789
790
790 # Filter out prompts.
791 # Filter out prompts.
791 newactions, prompts = [], []
792 newactions, prompts = [], []
792 for a in actions:
793 for a in actions:
793 if a[1] in ("cd", "dc"):
794 if a[1] in ("cd", "dc"):
794 prompts.append(a)
795 prompts.append(a)
795 else:
796 else:
796 newactions.append(a)
797 newactions.append(a)
797 # Prompt and create actions. TODO: Move this towards resolve phase.
798 # Prompt and create actions. TODO: Move this towards resolve phase.
798 for f, m, args, msg in sorted(prompts):
799 for f, m, args, msg in sorted(prompts):
799 if m == "cd":
800 if m == "cd":
800 if repo.ui.promptchoice(
801 if repo.ui.promptchoice(
801 _("local changed %s which remote deleted\n"
802 _("local changed %s which remote deleted\n"
802 "use (c)hanged version or (d)elete?"
803 "use (c)hanged version or (d)elete?"
803 "$$ &Changed $$ &Delete") % f, 0):
804 "$$ &Changed $$ &Delete") % f, 0):
804 newactions.append((f, "r", None, "prompt delete"))
805 newactions.append((f, "r", None, "prompt delete"))
805 else:
806 else:
806 newactions.append((f, "a", None, "prompt keep"))
807 newactions.append((f, "a", None, "prompt keep"))
807 elif m == "dc":
808 elif m == "dc":
808 flags, = args
809 flags, = args
809 if repo.ui.promptchoice(
810 if repo.ui.promptchoice(
810 _("remote changed %s which local deleted\n"
811 _("remote changed %s which local deleted\n"
811 "use (c)hanged version or leave (d)eleted?"
812 "use (c)hanged version or leave (d)eleted?"
812 "$$ &Changed $$ &Deleted") % f, 0) == 0:
813 "$$ &Changed $$ &Deleted") % f, 0) == 0:
813 newactions.append((f, "g", (flags,), "prompt recreating"))
814 newactions.append((f, "g", (flags,), "prompt recreating"))
814 else: assert False, m
815 else: assert False, m
815
816
816 if wctx.rev() is None:
817 if wctx.rev() is None:
817 newactions += _forgetremoved(wctx, mctx, branchmerge)
818 newactions += _forgetremoved(wctx, mctx, branchmerge)
818
819
819 return newactions
820 return newactions
820
821
821 def recordupdates(repo, actions, branchmerge):
822 def recordupdates(repo, actions, branchmerge):
822 "record merge actions to the dirstate"
823 "record merge actions to the dirstate"
823
824
824 for a in actions:
825 for a in actions:
825 f, m, args, msg = a
826 f, m, args, msg = a
826 if m == "r": # remove
827 if m == "r": # remove
827 if branchmerge:
828 if branchmerge:
828 repo.dirstate.remove(f)
829 repo.dirstate.remove(f)
829 else:
830 else:
830 repo.dirstate.drop(f)
831 repo.dirstate.drop(f)
831 elif m == "a": # re-add
832 elif m == "a": # re-add
832 if not branchmerge:
833 if not branchmerge:
833 repo.dirstate.add(f)
834 repo.dirstate.add(f)
834 elif m == "f": # forget
835 elif m == "f": # forget
835 repo.dirstate.drop(f)
836 repo.dirstate.drop(f)
836 elif m == "e": # exec change
837 elif m == "e": # exec change
837 repo.dirstate.normallookup(f)
838 repo.dirstate.normallookup(f)
838 elif m == "k": # keep
839 elif m == "k": # keep
839 pass
840 pass
840 elif m == "g": # get
841 elif m == "g": # get
841 if branchmerge:
842 if branchmerge:
842 repo.dirstate.otherparent(f)
843 repo.dirstate.otherparent(f)
843 else:
844 else:
844 repo.dirstate.normal(f)
845 repo.dirstate.normal(f)
845 elif m == "m": # merge
846 elif m == "m": # merge
846 f1, f2, fa, move, anc = args
847 f1, f2, fa, move, anc = args
847 if branchmerge:
848 if branchmerge:
848 # We've done a branch merge, mark this file as merged
849 # We've done a branch merge, mark this file as merged
849 # so that we properly record the merger later
850 # so that we properly record the merger later
850 repo.dirstate.merge(f)
851 repo.dirstate.merge(f)
851 if f1 != f2: # copy/rename
852 if f1 != f2: # copy/rename
852 if move:
853 if move:
853 repo.dirstate.remove(f1)
854 repo.dirstate.remove(f1)
854 if f1 != f:
855 if f1 != f:
855 repo.dirstate.copy(f1, f)
856 repo.dirstate.copy(f1, f)
856 else:
857 else:
857 repo.dirstate.copy(f2, f)
858 repo.dirstate.copy(f2, f)
858 else:
859 else:
859 # We've update-merged a locally modified file, so
860 # We've update-merged a locally modified file, so
860 # we set the dirstate to emulate a normal checkout
861 # we set the dirstate to emulate a normal checkout
861 # of that file some time in the past. Thus our
862 # of that file some time in the past. Thus our
862 # merge will appear as a normal local file
863 # merge will appear as a normal local file
863 # modification.
864 # modification.
864 if f2 == f: # file not locally copied/moved
865 if f2 == f: # file not locally copied/moved
865 repo.dirstate.normallookup(f)
866 repo.dirstate.normallookup(f)
866 if move:
867 if move:
867 repo.dirstate.drop(f1)
868 repo.dirstate.drop(f1)
868 elif m == "dm": # directory rename, move local
869 elif m == "dm": # directory rename, move local
869 f0, flag = args
870 f0, flag = args
870 if f0 not in repo.dirstate:
871 if f0 not in repo.dirstate:
871 # untracked file moved
872 # untracked file moved
872 continue
873 continue
873 if branchmerge:
874 if branchmerge:
874 repo.dirstate.add(f)
875 repo.dirstate.add(f)
875 repo.dirstate.remove(f0)
876 repo.dirstate.remove(f0)
876 repo.dirstate.copy(f0, f)
877 repo.dirstate.copy(f0, f)
877 else:
878 else:
878 repo.dirstate.normal(f)
879 repo.dirstate.normal(f)
879 repo.dirstate.drop(f0)
880 repo.dirstate.drop(f0)
880 elif m == "dg": # directory rename, get
881 elif m == "dg": # directory rename, get
881 f0, flag = args
882 f0, flag = args
882 if branchmerge:
883 if branchmerge:
883 repo.dirstate.add(f)
884 repo.dirstate.add(f)
884 repo.dirstate.copy(f0, f)
885 repo.dirstate.copy(f0, f)
885 else:
886 else:
886 repo.dirstate.normal(f)
887 repo.dirstate.normal(f)
887
888
888 def update(repo, node, branchmerge, force, partial, ancestor=None,
889 def update(repo, node, branchmerge, force, partial, ancestor=None,
889 mergeancestor=False):
890 mergeancestor=False):
890 """
891 """
891 Perform a merge between the working directory and the given node
892 Perform a merge between the working directory and the given node
892
893
893 node = the node to update to, or None if unspecified
894 node = the node to update to, or None if unspecified
894 branchmerge = whether to merge between branches
895 branchmerge = whether to merge between branches
895 force = whether to force branch merging or file overwriting
896 force = whether to force branch merging or file overwriting
896 partial = a function to filter file lists (dirstate not updated)
897 partial = a function to filter file lists (dirstate not updated)
897 mergeancestor = whether it is merging with an ancestor. If true,
898 mergeancestor = whether it is merging with an ancestor. If true,
898 we should accept the incoming changes for any prompts that occur.
899 we should accept the incoming changes for any prompts that occur.
899 If false, merging with an ancestor (fast-forward) is only allowed
900 If false, merging with an ancestor (fast-forward) is only allowed
900 between different named branches. This flag is used by rebase extension
901 between different named branches. This flag is used by rebase extension
901 as a temporary fix and should be avoided in general.
902 as a temporary fix and should be avoided in general.
902
903
903 The table below shows all the behaviors of the update command
904 The table below shows all the behaviors of the update command
904 given the -c and -C or no options, whether the working directory
905 given the -c and -C or no options, whether the working directory
905 is dirty, whether a revision is specified, and the relationship of
906 is dirty, whether a revision is specified, and the relationship of
906 the parent rev to the target rev (linear, on the same named
907 the parent rev to the target rev (linear, on the same named
907 branch, or on another named branch).
908 branch, or on another named branch).
908
909
909 This logic is tested by test-update-branches.t.
910 This logic is tested by test-update-branches.t.
910
911
911 -c -C dirty rev | linear same cross
912 -c -C dirty rev | linear same cross
912 n n n n | ok (1) x
913 n n n n | ok (1) x
913 n n n y | ok ok ok
914 n n n y | ok ok ok
914 n n y n | merge (2) (2)
915 n n y n | merge (2) (2)
915 n n y y | merge (3) (3)
916 n n y y | merge (3) (3)
916 n y * * | --- discard ---
917 n y * * | --- discard ---
917 y n y * | --- (4) ---
918 y n y * | --- (4) ---
918 y n n * | --- ok ---
919 y n n * | --- ok ---
919 y y * * | --- (5) ---
920 y y * * | --- (5) ---
920
921
921 x = can't happen
922 x = can't happen
922 * = don't-care
923 * = don't-care
923 1 = abort: not a linear update (merge or update --check to force update)
924 1 = abort: not a linear update (merge or update --check to force update)
924 2 = abort: uncommitted changes (commit and merge, or update --clean to
925 2 = abort: uncommitted changes (commit and merge, or update --clean to
925 discard changes)
926 discard changes)
926 3 = abort: uncommitted changes (commit or update --clean to discard changes)
927 3 = abort: uncommitted changes (commit or update --clean to discard changes)
927 4 = abort: uncommitted changes (checked in commands.py)
928 4 = abort: uncommitted changes (checked in commands.py)
928 5 = incompatible options (checked in commands.py)
929 5 = incompatible options (checked in commands.py)
929
930
930 Return the same tuple as applyupdates().
931 Return the same tuple as applyupdates().
931 """
932 """
932
933
933 onode = node
934 onode = node
934 wlock = repo.wlock()
935 wlock = repo.wlock()
935 try:
936 try:
936 wc = repo[None]
937 wc = repo[None]
937 pl = wc.parents()
938 pl = wc.parents()
938 p1 = pl[0]
939 p1 = pl[0]
939 pas = [None]
940 pas = [None]
940 if ancestor:
941 if ancestor:
941 pas = [repo[ancestor]]
942 pas = [repo[ancestor]]
942
943
943 if node is None:
944 if node is None:
944 # Here is where we should consider bookmarks, divergent bookmarks,
945 # Here is where we should consider bookmarks, divergent bookmarks,
945 # foreground changesets (successors), and tip of current branch;
946 # foreground changesets (successors), and tip of current branch;
946 # but currently we are only checking the branch tips.
947 # but currently we are only checking the branch tips.
947 try:
948 try:
948 node = repo.branchtip(wc.branch())
949 node = repo.branchtip(wc.branch())
949 except error.RepoLookupError:
950 except error.RepoLookupError:
950 if wc.branch() == "default": # no default branch!
951 if wc.branch() == "default": # no default branch!
951 node = repo.lookup("tip") # update to tip
952 node = repo.lookup("tip") # update to tip
952 else:
953 else:
953 raise util.Abort(_("branch %s not found") % wc.branch())
954 raise util.Abort(_("branch %s not found") % wc.branch())
954
955
955 if p1.obsolete() and not p1.children():
956 if p1.obsolete() and not p1.children():
956 # allow updating to successors
957 # allow updating to successors
957 successors = obsolete.successorssets(repo, p1.node())
958 successors = obsolete.successorssets(repo, p1.node())
958
959
959 # behavior of certain cases is as follows,
960 # behavior of certain cases is as follows,
960 #
961 #
961 # divergent changesets: update to highest rev, similar to what
962 # divergent changesets: update to highest rev, similar to what
962 # is currently done when there are more than one head
963 # is currently done when there are more than one head
963 # (i.e. 'tip')
964 # (i.e. 'tip')
964 #
965 #
965 # replaced changesets: same as divergent except we know there
966 # replaced changesets: same as divergent except we know there
966 # is no conflict
967 # is no conflict
967 #
968 #
968 # pruned changeset: no update is done; though, we could
969 # pruned changeset: no update is done; though, we could
969 # consider updating to the first non-obsolete parent,
970 # consider updating to the first non-obsolete parent,
970 # similar to what is current done for 'hg prune'
971 # similar to what is current done for 'hg prune'
971
972
972 if successors:
973 if successors:
973 # flatten the list here handles both divergent (len > 1)
974 # flatten the list here handles both divergent (len > 1)
974 # and the usual case (len = 1)
975 # and the usual case (len = 1)
975 successors = [n for sub in successors for n in sub]
976 successors = [n for sub in successors for n in sub]
976
977
977 # get the max revision for the given successors set,
978 # get the max revision for the given successors set,
978 # i.e. the 'tip' of a set
979 # i.e. the 'tip' of a set
979 node = repo.revs("max(%ln)", successors)[0]
980 node = repo.revs("max(%ln)", successors)[0]
980 pas = [p1]
981 pas = [p1]
981
982
982 overwrite = force and not branchmerge
983 overwrite = force and not branchmerge
983
984
984 p2 = repo[node]
985 p2 = repo[node]
985 if pas[0] is None:
986 if pas[0] is None:
986 if repo.ui.config("merge", "preferancestor") == '*':
987 if repo.ui.config("merge", "preferancestor") == '*':
987 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
988 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
988 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
989 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
989 else:
990 else:
990 pas = [p1.ancestor(p2)]
991 pas = [p1.ancestor(p2)]
991
992
992 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
993 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
993
994
994 ### check phase
995 ### check phase
995 if not overwrite and len(pl) > 1:
996 if not overwrite and len(pl) > 1:
996 raise util.Abort(_("outstanding uncommitted merges"))
997 raise util.Abort(_("outstanding uncommitted merges"))
997 if branchmerge:
998 if branchmerge:
998 if pas == [p2]:
999 if pas == [p2]:
999 raise util.Abort(_("merging with a working directory ancestor"
1000 raise util.Abort(_("merging with a working directory ancestor"
1000 " has no effect"))
1001 " has no effect"))
1001 elif pas == [p1]:
1002 elif pas == [p1]:
1002 if not mergeancestor and p1.branch() == p2.branch():
1003 if not mergeancestor and p1.branch() == p2.branch():
1003 raise util.Abort(_("nothing to merge"),
1004 raise util.Abort(_("nothing to merge"),
1004 hint=_("use 'hg update' "
1005 hint=_("use 'hg update' "
1005 "or check 'hg heads'"))
1006 "or check 'hg heads'"))
1006 if not force and (wc.files() or wc.deleted()):
1007 if not force and (wc.files() or wc.deleted()):
1007 raise util.Abort(_("uncommitted changes"),
1008 raise util.Abort(_("uncommitted changes"),
1008 hint=_("use 'hg status' to list changes"))
1009 hint=_("use 'hg status' to list changes"))
1009 for s in sorted(wc.substate):
1010 for s in sorted(wc.substate):
1010 if wc.sub(s).dirty():
1011 if wc.sub(s).dirty():
1011 raise util.Abort(_("uncommitted changes in "
1012 raise util.Abort(_("uncommitted changes in "
1012 "subrepository '%s'") % s)
1013 "subrepository '%s'") % s)
1013
1014
1014 elif not overwrite:
1015 elif not overwrite:
1015 if p1 == p2: # no-op update
1016 if p1 == p2: # no-op update
1016 # call the hooks and exit early
1017 # call the hooks and exit early
1017 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1018 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1018 repo.hook('update', parent1=xp2, parent2='', error=0)
1019 repo.hook('update', parent1=xp2, parent2='', error=0)
1019 return 0, 0, 0, 0
1020 return 0, 0, 0, 0
1020
1021
1021 if pas not in ([p1], [p2]): # nonlinear
1022 if pas not in ([p1], [p2]): # nonlinear
1022 dirty = wc.dirty(missing=True)
1023 dirty = wc.dirty(missing=True)
1023 if dirty or onode is None:
1024 if dirty or onode is None:
1024 # Branching is a bit strange to ensure we do the minimal
1025 # Branching is a bit strange to ensure we do the minimal
1025 # amount of call to obsolete.background.
1026 # amount of call to obsolete.background.
1026 foreground = obsolete.foreground(repo, [p1.node()])
1027 foreground = obsolete.foreground(repo, [p1.node()])
1027 # note: the <node> variable contains a random identifier
1028 # note: the <node> variable contains a random identifier
1028 if repo[node].node() in foreground:
1029 if repo[node].node() in foreground:
1029 pas = [p1] # allow updating to successors
1030 pas = [p1] # allow updating to successors
1030 elif dirty:
1031 elif dirty:
1031 msg = _("uncommitted changes")
1032 msg = _("uncommitted changes")
1032 if onode is None:
1033 if onode is None:
1033 hint = _("commit and merge, or update --clean to"
1034 hint = _("commit and merge, or update --clean to"
1034 " discard changes")
1035 " discard changes")
1035 else:
1036 else:
1036 hint = _("commit or update --clean to discard"
1037 hint = _("commit or update --clean to discard"
1037 " changes")
1038 " changes")
1038 raise util.Abort(msg, hint=hint)
1039 raise util.Abort(msg, hint=hint)
1039 else: # node is none
1040 else: # node is none
1040 msg = _("not a linear update")
1041 msg = _("not a linear update")
1041 hint = _("merge or update --check to force update")
1042 hint = _("merge or update --check to force update")
1042 raise util.Abort(msg, hint=hint)
1043 raise util.Abort(msg, hint=hint)
1043 else:
1044 else:
1044 # Allow jumping branches if clean and specific rev given
1045 # Allow jumping branches if clean and specific rev given
1045 pas = [p1]
1046 pas = [p1]
1046
1047
1047 followcopies = False
1048 followcopies = False
1048 if overwrite:
1049 if overwrite:
1049 pas = [wc]
1050 pas = [wc]
1050 elif pas == [p2]: # backwards
1051 elif pas == [p2]: # backwards
1051 pas = [wc.p1()]
1052 pas = [wc.p1()]
1052 elif not branchmerge and not wc.dirty(missing=True):
1053 elif not branchmerge and not wc.dirty(missing=True):
1053 pass
1054 pass
1054 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1055 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1055 followcopies = True
1056 followcopies = True
1056
1057
1057 ### calculate phase
1058 ### calculate phase
1058 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1059 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1059 partial, mergeancestor, followcopies)
1060 partial, mergeancestor, followcopies)
1060
1061
1061 ### apply phase
1062 ### apply phase
1062 if not branchmerge: # just jump to the new rev
1063 if not branchmerge: # just jump to the new rev
1063 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1064 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1064 if not partial:
1065 if not partial:
1065 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1066 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1066 # note that we're in the middle of an update
1067 # note that we're in the middle of an update
1067 repo.vfs.write('updatestate', p2.hex())
1068 repo.vfs.write('updatestate', p2.hex())
1068
1069
1069 stats = applyupdates(repo, actions, wc, p2, overwrite)
1070 stats = applyupdates(repo, actions, wc, p2, overwrite)
1070
1071
1071 if not partial:
1072 if not partial:
1072 repo.setparents(fp1, fp2)
1073 repo.setparents(fp1, fp2)
1073 recordupdates(repo, actions, branchmerge)
1074 recordupdates(repo, actions, branchmerge)
1074 # update completed, clear state
1075 # update completed, clear state
1075 util.unlink(repo.join('updatestate'))
1076 util.unlink(repo.join('updatestate'))
1076
1077
1077 if not branchmerge:
1078 if not branchmerge:
1078 repo.dirstate.setbranch(p2.branch())
1079 repo.dirstate.setbranch(p2.branch())
1079 finally:
1080 finally:
1080 wlock.release()
1081 wlock.release()
1081
1082
1082 if not partial:
1083 if not partial:
1083 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1084 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1084 return stats
1085 return stats
General Comments 0
You need to be logged in to leave comments. Login now