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