##// END OF EJS Templates
merge: let _forgetremoved() work on the file->action dict...
Martin von Zweigbergk -
r23640:b46b9865 default
parent child Browse files
Show More
@@ -1,1170 +1,1169 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 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 actions = {}
322 factions = xactions = []
322 m = 'f'
323 if branchmerge:
323 if branchmerge:
324 xactions = ractions
324 m = 'r'
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 actions[f] = m, 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 actions[f] = 'f', None, "forget removed"
333
333
334 return ractions, factions
334 return actions
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 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
378 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
379
379
380 # manifests fetched in order are going to be faster, so prime the caches
380 # manifests fetched in order are going to be faster, so prime the caches
381 [x.manifest() for x in
381 [x.manifest() for x in
382 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
382 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
383
383
384 if followcopies:
384 if followcopies:
385 ret = copies.mergecopies(repo, wctx, p2, pa)
385 ret = copies.mergecopies(repo, wctx, p2, pa)
386 copy, movewithdir, diverge, renamedelete = ret
386 copy, movewithdir, diverge, renamedelete = ret
387
387
388 repo.ui.note(_("resolving manifests\n"))
388 repo.ui.note(_("resolving manifests\n"))
389 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
389 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
390 % (bool(branchmerge), bool(force), bool(partial)))
390 % (bool(branchmerge), bool(force), bool(partial)))
391 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
391 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
392
392
393 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
393 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
394 copied = set(copy.values())
394 copied = set(copy.values())
395 copied.update(movewithdir.values())
395 copied.update(movewithdir.values())
396
396
397 if '.hgsubstate' in m1:
397 if '.hgsubstate' in m1:
398 # check whether sub state is modified
398 # check whether sub state is modified
399 for s in sorted(wctx.substate):
399 for s in sorted(wctx.substate):
400 if wctx.sub(s).dirty():
400 if wctx.sub(s).dirty():
401 m1['.hgsubstate'] += '+'
401 m1['.hgsubstate'] += '+'
402 break
402 break
403
403
404 aborts = []
404 aborts = []
405 # Compare manifests
405 # Compare manifests
406 diff = m1.diff(m2)
406 diff = m1.diff(m2)
407
407
408 actions = {}
408 actions = {}
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[f] = ('m', (f, f, fa, False, pa.node()),
416 actions[f] = ('m', (f, f, fa, False, pa.node()),
417 "both renamed from " + fa)
417 "both renamed from " + fa)
418 else:
418 else:
419 actions[f] = ('m', (f, f, None, False, pa.node()),
419 actions[f] = ('m', (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[f] = ('k' , (), "remote unchanged")
426 actions[f] = ('k' , (), "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[f] = ('e', (fl2,), "update permissions")
429 actions[f] = ('e', (fl2,), "update permissions")
430 else:
430 else:
431 actions[f] = ('g', (fl2,), "remote is newer")
431 actions[f] = ('g', (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[f] = ('e', (fl2,), "update permissions")
433 actions[f] = ('e', (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[f] = ('g', (fl1,), "remote is newer")
435 actions[f] = ('g', (fl1,), "remote is newer")
436 else: # both changed something
436 else: # both changed something
437 actions[f] = ('m', (f, f, f, False, pa.node()),
437 actions[f] = ('m', (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[f2] = ('m', (f, f2, None, True, pa.node()),
445 actions[f2] = ('m', (f, f2, None, True, pa.node()),
446 "remote directory rename, both created")
446 "remote directory rename, both created")
447 else:
447 else:
448 actions[f2] = ('dm', (f, fl1),
448 actions[f2] = ('dm', (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[f] = ('m', (f, f2, f2, False, pa.node()),
452 actions[f] = ('m', (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[f] = ('r', None, "remote delete")
457 actions[f] = ('r', None, "remote delete")
458 else:
458 else:
459 actions[f] = ('cd', None, "prompt changed/deleted")
459 actions[f] = ('cd', None, "prompt changed/deleted")
460 elif n1[20:] == 'a':
460 elif n1[20:] == 'a':
461 # This extra 'a' is added by working copy manifest to mark
461 # This extra 'a' is added by working copy manifest to mark
462 # the file as locally added. We should forget it instead of
462 # the file as locally added. We should forget it instead of
463 # deleting it.
463 # deleting it.
464 actions[f] = ('f', None, "remote deleted")
464 actions[f] = ('f', None, "remote deleted")
465 else:
465 else:
466 actions[f] = ('r', None, "other deleted")
466 actions[f] = ('r', None, "other deleted")
467 elif n2: # file exists only on remote side
467 elif n2: # file exists only on remote side
468 if f in copied:
468 if f in copied:
469 pass # we'll deal with it on m1 side
469 pass # we'll deal with it on m1 side
470 elif f in movewithdir:
470 elif f in movewithdir:
471 f2 = movewithdir[f]
471 f2 = movewithdir[f]
472 if f2 in m1:
472 if f2 in m1:
473 actions[f2] = ('m', (f2, f, None, False, pa.node()),
473 actions[f2] = ('m', (f2, f, None, False, pa.node()),
474 "local directory rename, both created")
474 "local directory rename, both created")
475 else:
475 else:
476 actions[f2] = ('dg', (f, fl2),
476 actions[f2] = ('dg', (f, fl2),
477 "local directory rename - get from " + f)
477 "local directory rename - get from " + f)
478 elif f in copy:
478 elif f in copy:
479 f2 = copy[f]
479 f2 = copy[f]
480 if f2 in m2:
480 if f2 in m2:
481 actions[f] = ('m', (f2, f, f2, False, pa.node()),
481 actions[f] = ('m', (f2, f, f2, False, pa.node()),
482 "remote copied from " + f2)
482 "remote copied from " + f2)
483 else:
483 else:
484 actions[f] = ('m', (f2, f, f2, True, pa.node()),
484 actions[f] = ('m', (f2, f, f2, True, pa.node()),
485 "remote moved from " + f2)
485 "remote moved from " + f2)
486 elif f not in ma:
486 elif f not in ma:
487 # local unknown, remote created: the logic is described by the
487 # local unknown, remote created: the logic is described by the
488 # following table:
488 # following table:
489 #
489 #
490 # force branchmerge different | action
490 # force branchmerge different | action
491 # n * n | get
491 # n * n | get
492 # n * y | abort
492 # n * y | abort
493 # y n * | get
493 # y n * | get
494 # y y n | get
494 # y y n | get
495 # y y y | merge
495 # y y y | merge
496 #
496 #
497 # Checking whether the files are different is expensive, so we
497 # Checking whether the files are different is expensive, so we
498 # don't do that when we can avoid it.
498 # don't do that when we can avoid it.
499 if force and not branchmerge:
499 if force and not branchmerge:
500 actions[f] = ('g', (fl2,), "remote created")
500 actions[f] = ('g', (fl2,), "remote created")
501 else:
501 else:
502 different = _checkunknownfile(repo, wctx, p2, f)
502 different = _checkunknownfile(repo, wctx, p2, f)
503 if force and branchmerge and different:
503 if force and branchmerge and different:
504 actions[f] = ('m', (f, f, None, False, pa.node()),
504 actions[f] = ('m', (f, f, None, False, pa.node()),
505 "remote differs from untracked local")
505 "remote differs from untracked local")
506 elif not force and different:
506 elif not force and different:
507 aborts.append((f, 'ud'))
507 aborts.append((f, 'ud'))
508 else:
508 else:
509 actions[f] = ('g', (fl2,), "remote created")
509 actions[f] = ('g', (fl2,), "remote created")
510 elif n2 != ma[f]:
510 elif n2 != ma[f]:
511 different = _checkunknownfile(repo, wctx, p2, f)
511 different = _checkunknownfile(repo, wctx, p2, f)
512 if not force and different:
512 if not force and different:
513 aborts.append((f, 'ud'))
513 aborts.append((f, 'ud'))
514 else:
514 else:
515 if acceptremote:
515 if acceptremote:
516 actions[f] = ('g', (fl2,), "remote recreating")
516 actions[f] = ('g', (fl2,), "remote recreating")
517 else:
517 else:
518 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
518 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
519
519
520 for f, m in sorted(aborts):
520 for f, m in sorted(aborts):
521 if m == 'ud':
521 if m == 'ud':
522 repo.ui.warn(_("%s: untracked file differs\n") % f)
522 repo.ui.warn(_("%s: untracked file differs\n") % f)
523 else: assert False, m
523 else: assert False, m
524 if aborts:
524 if aborts:
525 raise util.Abort(_("untracked files in working directory differ "
525 raise util.Abort(_("untracked files in working directory differ "
526 "from files in requested revision"))
526 "from files in requested revision"))
527
527
528 return actions, diverge, renamedelete
528 return actions, diverge, renamedelete
529
529
530 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
530 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
531 """Resolves false conflicts where the nodeid changed but the content
531 """Resolves false conflicts where the nodeid changed but the content
532 remained the same."""
532 remained the same."""
533
533
534 for f, (m, args, msg) in actions.items():
534 for f, (m, args, msg) in actions.items():
535 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
535 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
536 # local did change but ended up with same content
536 # local did change but ended up with same content
537 actions[f] = 'r', None, "prompt same"
537 actions[f] = 'r', None, "prompt same"
538 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
538 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
539 # remote did change but ended up with same content
539 # remote did change but ended up with same content
540 del actions[f] # don't get = keep local deleted
540 del actions[f] # don't get = keep local deleted
541
541
542 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
542 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
543 acceptremote, followcopies):
543 acceptremote, followcopies):
544 "Calculate the actions needed to merge mctx into wctx using ancestors"
544 "Calculate the actions needed to merge mctx into wctx using ancestors"
545
545
546 if len(ancestors) == 1: # default
546 if len(ancestors) == 1: # default
547 actions, diverge, renamedelete = manifestmerge(
547 actions, diverge, renamedelete = manifestmerge(
548 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
548 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
549 acceptremote, followcopies)
549 acceptremote, followcopies)
550
550
551 else: # only when merge.preferancestor=* - the default
551 else: # only when merge.preferancestor=* - the default
552 repo.ui.note(
552 repo.ui.note(
553 _("note: merging %s and %s using bids from ancestors %s\n") %
553 _("note: merging %s and %s using bids from ancestors %s\n") %
554 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
554 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
555
555
556 # Call for bids
556 # Call for bids
557 fbids = {} # mapping filename to bids (action method to list af actions)
557 fbids = {} # mapping filename to bids (action method to list af actions)
558 diverge, renamedelete = None, None
558 diverge, renamedelete = None, None
559 for ancestor in ancestors:
559 for ancestor in ancestors:
560 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
560 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
561 actions, diverge1, renamedelete1 = manifestmerge(
561 actions, diverge1, renamedelete1 = manifestmerge(
562 repo, wctx, mctx, ancestor, branchmerge, force, partial,
562 repo, wctx, mctx, ancestor, branchmerge, force, partial,
563 acceptremote, followcopies)
563 acceptremote, followcopies)
564 if diverge is None: # and renamedelete is None.
564 if diverge is None: # and renamedelete is None.
565 # Arbitrarily pick warnings from first iteration
565 # Arbitrarily pick warnings from first iteration
566 diverge = diverge1
566 diverge = diverge1
567 renamedelete = renamedelete1
567 renamedelete = renamedelete1
568 for f, a in sorted(actions.iteritems()):
568 for f, a in sorted(actions.iteritems()):
569 m, args, msg = a
569 m, args, msg = a
570 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
570 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
571 if f in fbids:
571 if f in fbids:
572 d = fbids[f]
572 d = fbids[f]
573 if m in d:
573 if m in d:
574 d[m].append(a)
574 d[m].append(a)
575 else:
575 else:
576 d[m] = [a]
576 d[m] = [a]
577 else:
577 else:
578 fbids[f] = {m: [a]}
578 fbids[f] = {m: [a]}
579
579
580 # Pick the best bid for each file
580 # Pick the best bid for each file
581 repo.ui.note(_('\nauction for merging merge bids\n'))
581 repo.ui.note(_('\nauction for merging merge bids\n'))
582 actions = {}
582 actions = {}
583 for f, bids in sorted(fbids.items()):
583 for f, bids in sorted(fbids.items()):
584 # bids is a mapping from action method to list af actions
584 # bids is a mapping from action method to list af actions
585 # Consensus?
585 # Consensus?
586 if len(bids) == 1: # all bids are the same kind of method
586 if len(bids) == 1: # all bids are the same kind of method
587 m, l = bids.items()[0]
587 m, l = bids.items()[0]
588 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
588 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
589 repo.ui.note(" %s: consensus for %s\n" % (f, m))
589 repo.ui.note(" %s: consensus for %s\n" % (f, m))
590 actions[f] = l[0]
590 actions[f] = l[0]
591 continue
591 continue
592 # If keep is an option, just do it.
592 # If keep is an option, just do it.
593 if 'k' in bids:
593 if 'k' in bids:
594 repo.ui.note(" %s: picking 'keep' action\n" % f)
594 repo.ui.note(" %s: picking 'keep' action\n" % f)
595 actions[f] = bids['k'][0]
595 actions[f] = bids['k'][0]
596 continue
596 continue
597 # If there are gets and they all agree [how could they not?], do it.
597 # If there are gets and they all agree [how could they not?], do it.
598 if 'g' in bids:
598 if 'g' in bids:
599 ga0 = bids['g'][0]
599 ga0 = bids['g'][0]
600 if util.all(a == ga0 for a in bids['g'][1:]):
600 if util.all(a == ga0 for a in bids['g'][1:]):
601 repo.ui.note(" %s: picking 'get' action\n" % f)
601 repo.ui.note(" %s: picking 'get' action\n" % f)
602 actions[f] = ga0
602 actions[f] = ga0
603 continue
603 continue
604 # TODO: Consider other simple actions such as mode changes
604 # TODO: Consider other simple actions such as mode changes
605 # Handle inefficient democrazy.
605 # Handle inefficient democrazy.
606 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
606 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
607 for m, l in sorted(bids.items()):
607 for m, l in sorted(bids.items()):
608 for _f, args, msg in l:
608 for _f, args, msg in l:
609 repo.ui.note(' %s -> %s\n' % (msg, m))
609 repo.ui.note(' %s -> %s\n' % (msg, m))
610 # Pick random action. TODO: Instead, prompt user when resolving
610 # Pick random action. TODO: Instead, prompt user when resolving
611 m, l = bids.items()[0]
611 m, l = bids.items()[0]
612 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
612 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
613 (f, m))
613 (f, m))
614 actions[f] = l[0]
614 actions[f] = l[0]
615 continue
615 continue
616 repo.ui.note(_('end of auction\n\n'))
616 repo.ui.note(_('end of auction\n\n'))
617
617
618 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
618 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
619
619
620 if wctx.rev() is None:
621 fractions = _forgetremoved(wctx, mctx, branchmerge)
622 actions.update(fractions)
623
620 # Convert to dictionary-of-lists format
624 # Convert to dictionary-of-lists format
621 actionbyfile = actions
625 actionbyfile = actions
622 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
626 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
623 for f, (m, args, msg) in actionbyfile.iteritems():
627 for f, (m, args, msg) in actionbyfile.iteritems():
624 actions[m].append((f, args, msg))
628 actions[m].append((f, args, msg))
625
629
626 if wctx.rev() is None:
627 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
628 actions['r'].extend(ractions)
629 actions['f'].extend(factions)
630
631 return actions, diverge, renamedelete
630 return actions, diverge, renamedelete
632
631
633 def batchremove(repo, actions):
632 def batchremove(repo, actions):
634 """apply removes to the working directory
633 """apply removes to the working directory
635
634
636 yields tuples for progress updates
635 yields tuples for progress updates
637 """
636 """
638 verbose = repo.ui.verbose
637 verbose = repo.ui.verbose
639 unlink = util.unlinkpath
638 unlink = util.unlinkpath
640 wjoin = repo.wjoin
639 wjoin = repo.wjoin
641 audit = repo.wopener.audit
640 audit = repo.wopener.audit
642 i = 0
641 i = 0
643 for f, args, msg in actions:
642 for f, args, msg in actions:
644 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
643 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
645 if verbose:
644 if verbose:
646 repo.ui.note(_("removing %s\n") % f)
645 repo.ui.note(_("removing %s\n") % f)
647 audit(f)
646 audit(f)
648 try:
647 try:
649 unlink(wjoin(f), ignoremissing=True)
648 unlink(wjoin(f), ignoremissing=True)
650 except OSError, inst:
649 except OSError, inst:
651 repo.ui.warn(_("update failed to remove %s: %s!\n") %
650 repo.ui.warn(_("update failed to remove %s: %s!\n") %
652 (f, inst.strerror))
651 (f, inst.strerror))
653 if i == 100:
652 if i == 100:
654 yield i, f
653 yield i, f
655 i = 0
654 i = 0
656 i += 1
655 i += 1
657 if i > 0:
656 if i > 0:
658 yield i, f
657 yield i, f
659
658
660 def batchget(repo, mctx, actions):
659 def batchget(repo, mctx, actions):
661 """apply gets to the working directory
660 """apply gets to the working directory
662
661
663 mctx is the context to get from
662 mctx is the context to get from
664
663
665 yields tuples for progress updates
664 yields tuples for progress updates
666 """
665 """
667 verbose = repo.ui.verbose
666 verbose = repo.ui.verbose
668 fctx = mctx.filectx
667 fctx = mctx.filectx
669 wwrite = repo.wwrite
668 wwrite = repo.wwrite
670 i = 0
669 i = 0
671 for f, args, msg in actions:
670 for f, args, msg in actions:
672 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
671 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
673 if verbose:
672 if verbose:
674 repo.ui.note(_("getting %s\n") % f)
673 repo.ui.note(_("getting %s\n") % f)
675 wwrite(f, fctx(f).data(), args[0])
674 wwrite(f, fctx(f).data(), args[0])
676 if i == 100:
675 if i == 100:
677 yield i, f
676 yield i, f
678 i = 0
677 i = 0
679 i += 1
678 i += 1
680 if i > 0:
679 if i > 0:
681 yield i, f
680 yield i, f
682
681
683 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
682 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
684 """apply the merge action list to the working directory
683 """apply the merge action list to the working directory
685
684
686 wctx is the working copy context
685 wctx is the working copy context
687 mctx is the context to be merged into the working copy
686 mctx is the context to be merged into the working copy
688
687
689 Return a tuple of counts (updated, merged, removed, unresolved) that
688 Return a tuple of counts (updated, merged, removed, unresolved) that
690 describes how many files were affected by the update.
689 describes how many files were affected by the update.
691 """
690 """
692
691
693 updated, merged, removed, unresolved = 0, 0, 0, 0
692 updated, merged, removed, unresolved = 0, 0, 0, 0
694 ms = mergestate(repo)
693 ms = mergestate(repo)
695 ms.reset(wctx.p1().node(), mctx.node())
694 ms.reset(wctx.p1().node(), mctx.node())
696 moves = []
695 moves = []
697 for m, l in actions.items():
696 for m, l in actions.items():
698 l.sort()
697 l.sort()
699
698
700 # prescan for merges
699 # prescan for merges
701 for f, args, msg in actions['m']:
700 for f, args, msg in actions['m']:
702 f1, f2, fa, move, anc = args
701 f1, f2, fa, move, anc = args
703 if f == '.hgsubstate': # merged internally
702 if f == '.hgsubstate': # merged internally
704 continue
703 continue
705 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
704 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
706 fcl = wctx[f1]
705 fcl = wctx[f1]
707 fco = mctx[f2]
706 fco = mctx[f2]
708 actx = repo[anc]
707 actx = repo[anc]
709 if fa in actx:
708 if fa in actx:
710 fca = actx[fa]
709 fca = actx[fa]
711 else:
710 else:
712 fca = repo.filectx(f1, fileid=nullrev)
711 fca = repo.filectx(f1, fileid=nullrev)
713 ms.add(fcl, fco, fca, f)
712 ms.add(fcl, fco, fca, f)
714 if f1 != f and move:
713 if f1 != f and move:
715 moves.append(f1)
714 moves.append(f1)
716
715
717 audit = repo.wopener.audit
716 audit = repo.wopener.audit
718 _updating = _('updating')
717 _updating = _('updating')
719 _files = _('files')
718 _files = _('files')
720 progress = repo.ui.progress
719 progress = repo.ui.progress
721
720
722 # remove renamed files after safely stored
721 # remove renamed files after safely stored
723 for f in moves:
722 for f in moves:
724 if os.path.lexists(repo.wjoin(f)):
723 if os.path.lexists(repo.wjoin(f)):
725 repo.ui.debug("removing %s\n" % f)
724 repo.ui.debug("removing %s\n" % f)
726 audit(f)
725 audit(f)
727 util.unlinkpath(repo.wjoin(f))
726 util.unlinkpath(repo.wjoin(f))
728
727
729 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
728 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
730
729
731 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
730 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
732 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
731 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
733
732
734 # remove in parallel (must come first)
733 # remove in parallel (must come first)
735 z = 0
734 z = 0
736 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
735 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
737 for i, item in prog:
736 for i, item in prog:
738 z += i
737 z += i
739 progress(_updating, z, item=item, total=numupdates, unit=_files)
738 progress(_updating, z, item=item, total=numupdates, unit=_files)
740 removed = len(actions['r'])
739 removed = len(actions['r'])
741
740
742 # get in parallel
741 # get in parallel
743 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
742 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
744 for i, item in prog:
743 for i, item in prog:
745 z += i
744 z += i
746 progress(_updating, z, item=item, total=numupdates, unit=_files)
745 progress(_updating, z, item=item, total=numupdates, unit=_files)
747 updated = len(actions['g'])
746 updated = len(actions['g'])
748
747
749 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
748 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
750 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
749 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
751
750
752 # forget (manifest only, just log it) (must come first)
751 # forget (manifest only, just log it) (must come first)
753 for f, args, msg in actions['f']:
752 for f, args, msg in actions['f']:
754 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
753 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
755 z += 1
754 z += 1
756 progress(_updating, z, item=f, total=numupdates, unit=_files)
755 progress(_updating, z, item=f, total=numupdates, unit=_files)
757
756
758 # re-add (manifest only, just log it)
757 # re-add (manifest only, just log it)
759 for f, args, msg in actions['a']:
758 for f, args, msg in actions['a']:
760 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
759 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
761 z += 1
760 z += 1
762 progress(_updating, z, item=f, total=numupdates, unit=_files)
761 progress(_updating, z, item=f, total=numupdates, unit=_files)
763
762
764 # keep (noop, just log it)
763 # keep (noop, just log it)
765 for f, args, msg in actions['k']:
764 for f, args, msg in actions['k']:
766 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
765 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
767 # no progress
766 # no progress
768
767
769 # merge
768 # merge
770 for f, args, msg in actions['m']:
769 for f, args, msg in actions['m']:
771 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
770 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
772 z += 1
771 z += 1
773 progress(_updating, z, item=f, total=numupdates, unit=_files)
772 progress(_updating, z, item=f, total=numupdates, unit=_files)
774 if f == '.hgsubstate': # subrepo states need updating
773 if f == '.hgsubstate': # subrepo states need updating
775 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
774 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
776 overwrite)
775 overwrite)
777 continue
776 continue
778 audit(f)
777 audit(f)
779 r = ms.resolve(f, wctx, labels=labels)
778 r = ms.resolve(f, wctx, labels=labels)
780 if r is not None and r > 0:
779 if r is not None and r > 0:
781 unresolved += 1
780 unresolved += 1
782 else:
781 else:
783 if r is None:
782 if r is None:
784 updated += 1
783 updated += 1
785 else:
784 else:
786 merged += 1
785 merged += 1
787
786
788 # directory rename, move local
787 # directory rename, move local
789 for f, args, msg in actions['dm']:
788 for f, args, msg in actions['dm']:
790 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
789 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
791 z += 1
790 z += 1
792 progress(_updating, z, item=f, total=numupdates, unit=_files)
791 progress(_updating, z, item=f, total=numupdates, unit=_files)
793 f0, flags = args
792 f0, flags = args
794 repo.ui.note(_("moving %s to %s\n") % (f0, f))
793 repo.ui.note(_("moving %s to %s\n") % (f0, f))
795 audit(f)
794 audit(f)
796 repo.wwrite(f, wctx.filectx(f0).data(), flags)
795 repo.wwrite(f, wctx.filectx(f0).data(), flags)
797 util.unlinkpath(repo.wjoin(f0))
796 util.unlinkpath(repo.wjoin(f0))
798 updated += 1
797 updated += 1
799
798
800 # local directory rename, get
799 # local directory rename, get
801 for f, args, msg in actions['dg']:
800 for f, args, msg in actions['dg']:
802 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
801 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
803 z += 1
802 z += 1
804 progress(_updating, z, item=f, total=numupdates, unit=_files)
803 progress(_updating, z, item=f, total=numupdates, unit=_files)
805 f0, flags = args
804 f0, flags = args
806 repo.ui.note(_("getting %s to %s\n") % (f0, f))
805 repo.ui.note(_("getting %s to %s\n") % (f0, f))
807 repo.wwrite(f, mctx.filectx(f0).data(), flags)
806 repo.wwrite(f, mctx.filectx(f0).data(), flags)
808 updated += 1
807 updated += 1
809
808
810 # exec
809 # exec
811 for f, args, msg in actions['e']:
810 for f, args, msg in actions['e']:
812 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
811 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
813 z += 1
812 z += 1
814 progress(_updating, z, item=f, total=numupdates, unit=_files)
813 progress(_updating, z, item=f, total=numupdates, unit=_files)
815 flags, = args
814 flags, = args
816 audit(f)
815 audit(f)
817 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
816 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
818 updated += 1
817 updated += 1
819
818
820 ms.commit()
819 ms.commit()
821 progress(_updating, None, total=numupdates, unit=_files)
820 progress(_updating, None, total=numupdates, unit=_files)
822
821
823 return updated, merged, removed, unresolved
822 return updated, merged, removed, unresolved
824
823
825 def recordupdates(repo, actions, branchmerge):
824 def recordupdates(repo, actions, branchmerge):
826 "record merge actions to the dirstate"
825 "record merge actions to the dirstate"
827 # remove (must come first)
826 # remove (must come first)
828 for f, args, msg in actions['r']:
827 for f, args, msg in actions['r']:
829 if branchmerge:
828 if branchmerge:
830 repo.dirstate.remove(f)
829 repo.dirstate.remove(f)
831 else:
830 else:
832 repo.dirstate.drop(f)
831 repo.dirstate.drop(f)
833
832
834 # forget (must come first)
833 # forget (must come first)
835 for f, args, msg in actions['f']:
834 for f, args, msg in actions['f']:
836 repo.dirstate.drop(f)
835 repo.dirstate.drop(f)
837
836
838 # re-add
837 # re-add
839 for f, args, msg in actions['a']:
838 for f, args, msg in actions['a']:
840 if not branchmerge:
839 if not branchmerge:
841 repo.dirstate.add(f)
840 repo.dirstate.add(f)
842
841
843 # exec change
842 # exec change
844 for f, args, msg in actions['e']:
843 for f, args, msg in actions['e']:
845 repo.dirstate.normallookup(f)
844 repo.dirstate.normallookup(f)
846
845
847 # keep
846 # keep
848 for f, args, msg in actions['k']:
847 for f, args, msg in actions['k']:
849 pass
848 pass
850
849
851 # get
850 # get
852 for f, args, msg in actions['g']:
851 for f, args, msg in actions['g']:
853 if branchmerge:
852 if branchmerge:
854 repo.dirstate.otherparent(f)
853 repo.dirstate.otherparent(f)
855 else:
854 else:
856 repo.dirstate.normal(f)
855 repo.dirstate.normal(f)
857
856
858 # merge
857 # merge
859 for f, args, msg in actions['m']:
858 for f, args, msg in actions['m']:
860 f1, f2, fa, move, anc = args
859 f1, f2, fa, move, anc = args
861 if branchmerge:
860 if branchmerge:
862 # We've done a branch merge, mark this file as merged
861 # We've done a branch merge, mark this file as merged
863 # so that we properly record the merger later
862 # so that we properly record the merger later
864 repo.dirstate.merge(f)
863 repo.dirstate.merge(f)
865 if f1 != f2: # copy/rename
864 if f1 != f2: # copy/rename
866 if move:
865 if move:
867 repo.dirstate.remove(f1)
866 repo.dirstate.remove(f1)
868 if f1 != f:
867 if f1 != f:
869 repo.dirstate.copy(f1, f)
868 repo.dirstate.copy(f1, f)
870 else:
869 else:
871 repo.dirstate.copy(f2, f)
870 repo.dirstate.copy(f2, f)
872 else:
871 else:
873 # We've update-merged a locally modified file, so
872 # We've update-merged a locally modified file, so
874 # we set the dirstate to emulate a normal checkout
873 # we set the dirstate to emulate a normal checkout
875 # of that file some time in the past. Thus our
874 # of that file some time in the past. Thus our
876 # merge will appear as a normal local file
875 # merge will appear as a normal local file
877 # modification.
876 # modification.
878 if f2 == f: # file not locally copied/moved
877 if f2 == f: # file not locally copied/moved
879 repo.dirstate.normallookup(f)
878 repo.dirstate.normallookup(f)
880 if move:
879 if move:
881 repo.dirstate.drop(f1)
880 repo.dirstate.drop(f1)
882
881
883 # directory rename, move local
882 # directory rename, move local
884 for f, args, msg in actions['dm']:
883 for f, args, msg in actions['dm']:
885 f0, flag = args
884 f0, flag = args
886 if branchmerge:
885 if branchmerge:
887 repo.dirstate.add(f)
886 repo.dirstate.add(f)
888 repo.dirstate.remove(f0)
887 repo.dirstate.remove(f0)
889 repo.dirstate.copy(f0, f)
888 repo.dirstate.copy(f0, f)
890 else:
889 else:
891 repo.dirstate.normal(f)
890 repo.dirstate.normal(f)
892 repo.dirstate.drop(f0)
891 repo.dirstate.drop(f0)
893
892
894 # directory rename, get
893 # directory rename, get
895 for f, args, msg in actions['dg']:
894 for f, args, msg in actions['dg']:
896 f0, flag = args
895 f0, flag = args
897 if branchmerge:
896 if branchmerge:
898 repo.dirstate.add(f)
897 repo.dirstate.add(f)
899 repo.dirstate.copy(f0, f)
898 repo.dirstate.copy(f0, f)
900 else:
899 else:
901 repo.dirstate.normal(f)
900 repo.dirstate.normal(f)
902
901
903 def update(repo, node, branchmerge, force, partial, ancestor=None,
902 def update(repo, node, branchmerge, force, partial, ancestor=None,
904 mergeancestor=False, labels=None):
903 mergeancestor=False, labels=None):
905 """
904 """
906 Perform a merge between the working directory and the given node
905 Perform a merge between the working directory and the given node
907
906
908 node = the node to update to, or None if unspecified
907 node = the node to update to, or None if unspecified
909 branchmerge = whether to merge between branches
908 branchmerge = whether to merge between branches
910 force = whether to force branch merging or file overwriting
909 force = whether to force branch merging or file overwriting
911 partial = a function to filter file lists (dirstate not updated)
910 partial = a function to filter file lists (dirstate not updated)
912 mergeancestor = whether it is merging with an ancestor. If true,
911 mergeancestor = whether it is merging with an ancestor. If true,
913 we should accept the incoming changes for any prompts that occur.
912 we should accept the incoming changes for any prompts that occur.
914 If false, merging with an ancestor (fast-forward) is only allowed
913 If false, merging with an ancestor (fast-forward) is only allowed
915 between different named branches. This flag is used by rebase extension
914 between different named branches. This flag is used by rebase extension
916 as a temporary fix and should be avoided in general.
915 as a temporary fix and should be avoided in general.
917
916
918 The table below shows all the behaviors of the update command
917 The table below shows all the behaviors of the update command
919 given the -c and -C or no options, whether the working directory
918 given the -c and -C or no options, whether the working directory
920 is dirty, whether a revision is specified, and the relationship of
919 is dirty, whether a revision is specified, and the relationship of
921 the parent rev to the target rev (linear, on the same named
920 the parent rev to the target rev (linear, on the same named
922 branch, or on another named branch).
921 branch, or on another named branch).
923
922
924 This logic is tested by test-update-branches.t.
923 This logic is tested by test-update-branches.t.
925
924
926 -c -C dirty rev | linear same cross
925 -c -C dirty rev | linear same cross
927 n n n n | ok (1) x
926 n n n n | ok (1) x
928 n n n y | ok ok ok
927 n n n y | ok ok ok
929 n n y n | merge (2) (2)
928 n n y n | merge (2) (2)
930 n n y y | merge (3) (3)
929 n n y y | merge (3) (3)
931 n y * * | --- discard ---
930 n y * * | --- discard ---
932 y n y * | --- (4) ---
931 y n y * | --- (4) ---
933 y n n * | --- ok ---
932 y n n * | --- ok ---
934 y y * * | --- (5) ---
933 y y * * | --- (5) ---
935
934
936 x = can't happen
935 x = can't happen
937 * = don't-care
936 * = don't-care
938 1 = abort: not a linear update (merge or update --check to force update)
937 1 = abort: not a linear update (merge or update --check to force update)
939 2 = abort: uncommitted changes (commit and merge, or update --clean to
938 2 = abort: uncommitted changes (commit and merge, or update --clean to
940 discard changes)
939 discard changes)
941 3 = abort: uncommitted changes (commit or update --clean to discard changes)
940 3 = abort: uncommitted changes (commit or update --clean to discard changes)
942 4 = abort: uncommitted changes (checked in commands.py)
941 4 = abort: uncommitted changes (checked in commands.py)
943 5 = incompatible options (checked in commands.py)
942 5 = incompatible options (checked in commands.py)
944
943
945 Return the same tuple as applyupdates().
944 Return the same tuple as applyupdates().
946 """
945 """
947
946
948 onode = node
947 onode = node
949 wlock = repo.wlock()
948 wlock = repo.wlock()
950 try:
949 try:
951 wc = repo[None]
950 wc = repo[None]
952 pl = wc.parents()
951 pl = wc.parents()
953 p1 = pl[0]
952 p1 = pl[0]
954 pas = [None]
953 pas = [None]
955 if ancestor is not None:
954 if ancestor is not None:
956 pas = [repo[ancestor]]
955 pas = [repo[ancestor]]
957
956
958 if node is None:
957 if node is None:
959 # Here is where we should consider bookmarks, divergent bookmarks,
958 # Here is where we should consider bookmarks, divergent bookmarks,
960 # foreground changesets (successors), and tip of current branch;
959 # foreground changesets (successors), and tip of current branch;
961 # but currently we are only checking the branch tips.
960 # but currently we are only checking the branch tips.
962 try:
961 try:
963 node = repo.branchtip(wc.branch())
962 node = repo.branchtip(wc.branch())
964 except errormod.RepoLookupError:
963 except errormod.RepoLookupError:
965 if wc.branch() == 'default': # no default branch!
964 if wc.branch() == 'default': # no default branch!
966 node = repo.lookup('tip') # update to tip
965 node = repo.lookup('tip') # update to tip
967 else:
966 else:
968 raise util.Abort(_("branch %s not found") % wc.branch())
967 raise util.Abort(_("branch %s not found") % wc.branch())
969
968
970 if p1.obsolete() and not p1.children():
969 if p1.obsolete() and not p1.children():
971 # allow updating to successors
970 # allow updating to successors
972 successors = obsolete.successorssets(repo, p1.node())
971 successors = obsolete.successorssets(repo, p1.node())
973
972
974 # behavior of certain cases is as follows,
973 # behavior of certain cases is as follows,
975 #
974 #
976 # divergent changesets: update to highest rev, similar to what
975 # divergent changesets: update to highest rev, similar to what
977 # is currently done when there are more than one head
976 # is currently done when there are more than one head
978 # (i.e. 'tip')
977 # (i.e. 'tip')
979 #
978 #
980 # replaced changesets: same as divergent except we know there
979 # replaced changesets: same as divergent except we know there
981 # is no conflict
980 # is no conflict
982 #
981 #
983 # pruned changeset: no update is done; though, we could
982 # pruned changeset: no update is done; though, we could
984 # consider updating to the first non-obsolete parent,
983 # consider updating to the first non-obsolete parent,
985 # similar to what is current done for 'hg prune'
984 # similar to what is current done for 'hg prune'
986
985
987 if successors:
986 if successors:
988 # flatten the list here handles both divergent (len > 1)
987 # flatten the list here handles both divergent (len > 1)
989 # and the usual case (len = 1)
988 # and the usual case (len = 1)
990 successors = [n for sub in successors for n in sub]
989 successors = [n for sub in successors for n in sub]
991
990
992 # get the max revision for the given successors set,
991 # get the max revision for the given successors set,
993 # i.e. the 'tip' of a set
992 # i.e. the 'tip' of a set
994 node = repo.revs('max(%ln)', successors).first()
993 node = repo.revs('max(%ln)', successors).first()
995 pas = [p1]
994 pas = [p1]
996
995
997 overwrite = force and not branchmerge
996 overwrite = force and not branchmerge
998
997
999 p2 = repo[node]
998 p2 = repo[node]
1000 if pas[0] is None:
999 if pas[0] is None:
1001 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1000 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1002 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1001 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1003 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1002 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1004 else:
1003 else:
1005 pas = [p1.ancestor(p2, warn=branchmerge)]
1004 pas = [p1.ancestor(p2, warn=branchmerge)]
1006
1005
1007 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1006 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1008
1007
1009 ### check phase
1008 ### check phase
1010 if not overwrite and len(pl) > 1:
1009 if not overwrite and len(pl) > 1:
1011 raise util.Abort(_("outstanding uncommitted merge"))
1010 raise util.Abort(_("outstanding uncommitted merge"))
1012 if branchmerge:
1011 if branchmerge:
1013 if pas == [p2]:
1012 if pas == [p2]:
1014 raise util.Abort(_("merging with a working directory ancestor"
1013 raise util.Abort(_("merging with a working directory ancestor"
1015 " has no effect"))
1014 " has no effect"))
1016 elif pas == [p1]:
1015 elif pas == [p1]:
1017 if not mergeancestor and p1.branch() == p2.branch():
1016 if not mergeancestor and p1.branch() == p2.branch():
1018 raise util.Abort(_("nothing to merge"),
1017 raise util.Abort(_("nothing to merge"),
1019 hint=_("use 'hg update' "
1018 hint=_("use 'hg update' "
1020 "or check 'hg heads'"))
1019 "or check 'hg heads'"))
1021 if not force and (wc.files() or wc.deleted()):
1020 if not force and (wc.files() or wc.deleted()):
1022 raise util.Abort(_("uncommitted changes"),
1021 raise util.Abort(_("uncommitted changes"),
1023 hint=_("use 'hg status' to list changes"))
1022 hint=_("use 'hg status' to list changes"))
1024 for s in sorted(wc.substate):
1023 for s in sorted(wc.substate):
1025 if wc.sub(s).dirty():
1024 if wc.sub(s).dirty():
1026 raise util.Abort(_("uncommitted changes in "
1025 raise util.Abort(_("uncommitted changes in "
1027 "subrepository '%s'") % s)
1026 "subrepository '%s'") % s)
1028
1027
1029 elif not overwrite:
1028 elif not overwrite:
1030 if p1 == p2: # no-op update
1029 if p1 == p2: # no-op update
1031 # call the hooks and exit early
1030 # call the hooks and exit early
1032 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1031 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1033 repo.hook('update', parent1=xp2, parent2='', error=0)
1032 repo.hook('update', parent1=xp2, parent2='', error=0)
1034 return 0, 0, 0, 0
1033 return 0, 0, 0, 0
1035
1034
1036 if pas not in ([p1], [p2]): # nonlinear
1035 if pas not in ([p1], [p2]): # nonlinear
1037 dirty = wc.dirty(missing=True)
1036 dirty = wc.dirty(missing=True)
1038 if dirty or onode is None:
1037 if dirty or onode is None:
1039 # Branching is a bit strange to ensure we do the minimal
1038 # Branching is a bit strange to ensure we do the minimal
1040 # amount of call to obsolete.background.
1039 # amount of call to obsolete.background.
1041 foreground = obsolete.foreground(repo, [p1.node()])
1040 foreground = obsolete.foreground(repo, [p1.node()])
1042 # note: the <node> variable contains a random identifier
1041 # note: the <node> variable contains a random identifier
1043 if repo[node].node() in foreground:
1042 if repo[node].node() in foreground:
1044 pas = [p1] # allow updating to successors
1043 pas = [p1] # allow updating to successors
1045 elif dirty:
1044 elif dirty:
1046 msg = _("uncommitted changes")
1045 msg = _("uncommitted changes")
1047 if onode is None:
1046 if onode is None:
1048 hint = _("commit and merge, or update --clean to"
1047 hint = _("commit and merge, or update --clean to"
1049 " discard changes")
1048 " discard changes")
1050 else:
1049 else:
1051 hint = _("commit or update --clean to discard"
1050 hint = _("commit or update --clean to discard"
1052 " changes")
1051 " changes")
1053 raise util.Abort(msg, hint=hint)
1052 raise util.Abort(msg, hint=hint)
1054 else: # node is none
1053 else: # node is none
1055 msg = _("not a linear update")
1054 msg = _("not a linear update")
1056 hint = _("merge or update --check to force update")
1055 hint = _("merge or update --check to force update")
1057 raise util.Abort(msg, hint=hint)
1056 raise util.Abort(msg, hint=hint)
1058 else:
1057 else:
1059 # Allow jumping branches if clean and specific rev given
1058 # Allow jumping branches if clean and specific rev given
1060 pas = [p1]
1059 pas = [p1]
1061
1060
1062 followcopies = False
1061 followcopies = False
1063 if overwrite:
1062 if overwrite:
1064 pas = [wc]
1063 pas = [wc]
1065 elif pas == [p2]: # backwards
1064 elif pas == [p2]: # backwards
1066 pas = [wc.p1()]
1065 pas = [wc.p1()]
1067 elif not branchmerge and not wc.dirty(missing=True):
1066 elif not branchmerge and not wc.dirty(missing=True):
1068 pass
1067 pass
1069 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1068 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1070 followcopies = True
1069 followcopies = True
1071
1070
1072 ### calculate phase
1071 ### calculate phase
1073 actions, diverge, renamedelete = calculateupdates(
1072 actions, diverge, renamedelete = calculateupdates(
1074 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1073 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1075 followcopies)
1074 followcopies)
1076
1075
1077 if not util.checkcase(repo.path):
1076 if not util.checkcase(repo.path):
1078 # check collision between files only in p2 for clean update
1077 # check collision between files only in p2 for clean update
1079 if (not branchmerge and
1078 if (not branchmerge and
1080 (force or not wc.dirty(missing=True, branch=False))):
1079 (force or not wc.dirty(missing=True, branch=False))):
1081 _checkcollision(repo, p2.manifest(), None)
1080 _checkcollision(repo, p2.manifest(), None)
1082 else:
1081 else:
1083 _checkcollision(repo, wc.manifest(), actions)
1082 _checkcollision(repo, wc.manifest(), actions)
1084
1083
1085 # Prompt and create actions. TODO: Move this towards resolve phase.
1084 # Prompt and create actions. TODO: Move this towards resolve phase.
1086 for f, args, msg in sorted(actions['cd']):
1085 for f, args, msg in sorted(actions['cd']):
1087 if repo.ui.promptchoice(
1086 if repo.ui.promptchoice(
1088 _("local changed %s which remote deleted\n"
1087 _("local changed %s which remote deleted\n"
1089 "use (c)hanged version or (d)elete?"
1088 "use (c)hanged version or (d)elete?"
1090 "$$ &Changed $$ &Delete") % f, 0):
1089 "$$ &Changed $$ &Delete") % f, 0):
1091 actions['r'].append((f, None, "prompt delete"))
1090 actions['r'].append((f, None, "prompt delete"))
1092 else:
1091 else:
1093 actions['a'].append((f, None, "prompt keep"))
1092 actions['a'].append((f, None, "prompt keep"))
1094 del actions['cd'][:]
1093 del actions['cd'][:]
1095
1094
1096 for f, args, msg in sorted(actions['dc']):
1095 for f, args, msg in sorted(actions['dc']):
1097 flags, = args
1096 flags, = args
1098 if repo.ui.promptchoice(
1097 if repo.ui.promptchoice(
1099 _("remote changed %s which local deleted\n"
1098 _("remote changed %s which local deleted\n"
1100 "use (c)hanged version or leave (d)eleted?"
1099 "use (c)hanged version or leave (d)eleted?"
1101 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1100 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1102 actions['g'].append((f, (flags,), "prompt recreating"))
1101 actions['g'].append((f, (flags,), "prompt recreating"))
1103 del actions['dc'][:]
1102 del actions['dc'][:]
1104
1103
1105 ### apply phase
1104 ### apply phase
1106 if not branchmerge: # just jump to the new rev
1105 if not branchmerge: # just jump to the new rev
1107 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1106 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1108 if not partial:
1107 if not partial:
1109 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1108 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1110 # note that we're in the middle of an update
1109 # note that we're in the middle of an update
1111 repo.vfs.write('updatestate', p2.hex())
1110 repo.vfs.write('updatestate', p2.hex())
1112
1111
1113 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1112 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1114
1113
1115 # divergent renames
1114 # divergent renames
1116 for f, fl in sorted(diverge.iteritems()):
1115 for f, fl in sorted(diverge.iteritems()):
1117 repo.ui.warn(_("note: possible conflict - %s was renamed "
1116 repo.ui.warn(_("note: possible conflict - %s was renamed "
1118 "multiple times to:\n") % f)
1117 "multiple times to:\n") % f)
1119 for nf in fl:
1118 for nf in fl:
1120 repo.ui.warn(" %s\n" % nf)
1119 repo.ui.warn(" %s\n" % nf)
1121
1120
1122 # rename and delete
1121 # rename and delete
1123 for f, fl in sorted(renamedelete.iteritems()):
1122 for f, fl in sorted(renamedelete.iteritems()):
1124 repo.ui.warn(_("note: possible conflict - %s was deleted "
1123 repo.ui.warn(_("note: possible conflict - %s was deleted "
1125 "and renamed to:\n") % f)
1124 "and renamed to:\n") % f)
1126 for nf in fl:
1125 for nf in fl:
1127 repo.ui.warn(" %s\n" % nf)
1126 repo.ui.warn(" %s\n" % nf)
1128
1127
1129 if not partial:
1128 if not partial:
1130 repo.dirstate.beginparentchange()
1129 repo.dirstate.beginparentchange()
1131 repo.setparents(fp1, fp2)
1130 repo.setparents(fp1, fp2)
1132 recordupdates(repo, actions, branchmerge)
1131 recordupdates(repo, actions, branchmerge)
1133 # update completed, clear state
1132 # update completed, clear state
1134 util.unlink(repo.join('updatestate'))
1133 util.unlink(repo.join('updatestate'))
1135
1134
1136 if not branchmerge:
1135 if not branchmerge:
1137 repo.dirstate.setbranch(p2.branch())
1136 repo.dirstate.setbranch(p2.branch())
1138 repo.dirstate.endparentchange()
1137 repo.dirstate.endparentchange()
1139 finally:
1138 finally:
1140 wlock.release()
1139 wlock.release()
1141
1140
1142 if not partial:
1141 if not partial:
1143 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1142 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1144 return stats
1143 return stats
1145
1144
1146 def graft(repo, ctx, pctx, labels):
1145 def graft(repo, ctx, pctx, labels):
1147 """Do a graft-like merge.
1146 """Do a graft-like merge.
1148
1147
1149 This is a merge where the merge ancestor is chosen such that one
1148 This is a merge where the merge ancestor is chosen such that one
1150 or more changesets are grafted onto the current changeset. In
1149 or more changesets are grafted onto the current changeset. In
1151 addition to the merge, this fixes up the dirstate to include only
1150 addition to the merge, this fixes up the dirstate to include only
1152 a single parent and tries to duplicate any renames/copies
1151 a single parent and tries to duplicate any renames/copies
1153 appropriately.
1152 appropriately.
1154
1153
1155 ctx - changeset to rebase
1154 ctx - changeset to rebase
1156 pctx - merge base, usually ctx.p1()
1155 pctx - merge base, usually ctx.p1()
1157 labels - merge labels eg ['local', 'graft']
1156 labels - merge labels eg ['local', 'graft']
1158
1157
1159 """
1158 """
1160
1159
1161 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1160 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1162 labels=labels)
1161 labels=labels)
1163 # drop the second merge parent
1162 # drop the second merge parent
1164 repo.dirstate.beginparentchange()
1163 repo.dirstate.beginparentchange()
1165 repo.setparents(repo['.'].node(), nullid)
1164 repo.setparents(repo['.'].node(), nullid)
1166 repo.dirstate.write()
1165 repo.dirstate.write()
1167 # fix up dirstate for copies and renames
1166 # fix up dirstate for copies and renames
1168 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1167 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1169 repo.dirstate.endparentchange()
1168 repo.dirstate.endparentchange()
1170 return stats
1169 return stats
General Comments 0
You need to be logged in to leave comments. Login now