##// END OF EJS Templates
merge: fix stupid indentation left over from previous refactorings
Mads Kiilerich -
r21551:bde505f4 default
parent child Browse files
Show More
@@ -1,1156 +1,1149 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import struct
8 import struct
9
9
10 from node import nullid, nullrev, hex, bin
10 from node import nullid, nullrev, hex, bin
11 from i18n import _
11 from i18n import _
12 from mercurial import obsolete
12 from mercurial import obsolete
13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
14 import errno, os, shutil
14 import errno, os, shutil
15
15
16 _pack = struct.pack
16 _pack = struct.pack
17 _unpack = struct.unpack
17 _unpack = struct.unpack
18
18
19 def _droponode(data):
19 def _droponode(data):
20 # used for compatibility for v1
20 # used for compatibility for v1
21 bits = data.split("\0")
21 bits = data.split("\0")
22 bits = bits[:-2] + bits[-1:]
22 bits = bits[:-2] + bits[-1:]
23 return "\0".join(bits)
23 return "\0".join(bits)
24
24
25 class mergestate(object):
25 class mergestate(object):
26 '''track 3-way merge state of individual files
26 '''track 3-way merge state of individual files
27
27
28 it is stored on disk when needed. Two file are used, one with an old
28 it is stored on disk when needed. Two file are used, one with an old
29 format, one with a new format. Both contains similar data, but the new
29 format, one with a new format. Both contains similar data, but the new
30 format can store new kind of field.
30 format can store new kind of field.
31
31
32 Current new format is a list of arbitrary record of the form:
32 Current new format is a list of arbitrary record of the form:
33
33
34 [type][length][content]
34 [type][length][content]
35
35
36 Type is a single character, length is a 4 bytes integer, content is an
36 Type is a single character, length is a 4 bytes integer, content is an
37 arbitrary suites of bytes of length `length`.
37 arbitrary suites of bytes of length `length`.
38
38
39 Type should be a letter. Capital letter are mandatory record, Mercurial
39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 should abort if they are unknown. lower case record can be safely ignored.
40 should abort if they are unknown. lower case record can be safely ignored.
41
41
42 Currently known record:
42 Currently known record:
43
43
44 L: the node of the "local" part of the merge (hexified version)
44 L: the node of the "local" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
46 F: a file to be merged entry
46 F: a file to be merged entry
47 '''
47 '''
48 statepathv1 = "merge/state"
48 statepathv1 = "merge/state"
49 statepathv2 = "merge/state2"
49 statepathv2 = "merge/state2"
50
50
51 def __init__(self, repo):
51 def __init__(self, repo):
52 self._repo = repo
52 self._repo = repo
53 self._dirty = False
53 self._dirty = False
54 self._read()
54 self._read()
55
55
56 def reset(self, node=None, other=None):
56 def reset(self, node=None, other=None):
57 self._state = {}
57 self._state = {}
58 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 (not repo.dirstate._ignore(f)
301 return (not repo.dirstate._ignore(f)
302 and os.path.isfile(repo.wjoin(f))
302 and os.path.isfile(repo.wjoin(f))
303 and repo.wopener.audit.check(f)
303 and repo.wopener.audit.check(f)
304 and repo.dirstate.normalize(f) not in repo.dirstate
304 and repo.dirstate.normalize(f) not in repo.dirstate
305 and mctx[f].cmp(wctx[f]))
305 and mctx[f].cmp(wctx[f]))
306
306
307 def _checkunknown(repo, wctx, mctx):
307 def _checkunknown(repo, wctx, mctx):
308 "check for collisions between unknown files and files in mctx"
308 "check for collisions between unknown files and files in mctx"
309
309
310 error = False
310 error = False
311 for f in mctx:
311 for f in mctx:
312 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
312 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
313 error = True
313 error = True
314 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
314 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
315 if error:
315 if error:
316 raise util.Abort(_("untracked files in working directory differ "
316 raise util.Abort(_("untracked files in working directory differ "
317 "from files in requested revision"))
317 "from files in requested revision"))
318
318
319 def _forgetremoved(wctx, mctx, branchmerge):
319 def _forgetremoved(wctx, mctx, branchmerge):
320 """
320 """
321 Forget removed files
321 Forget removed files
322
322
323 If we're jumping between revisions (as opposed to merging), and if
323 If we're jumping between revisions (as opposed to merging), and if
324 neither the working directory nor the target rev has the file,
324 neither the working directory nor the target rev has the file,
325 then we need to remove it from the dirstate, to prevent the
325 then we need to remove it from the dirstate, to prevent the
326 dirstate from listing the file when it is no longer in the
326 dirstate from listing the file when it is no longer in the
327 manifest.
327 manifest.
328
328
329 If we're merging, and the other revision has removed a file
329 If we're merging, and the other revision has removed a file
330 that is not present in the working directory, we need to mark it
330 that is not present in the working directory, we need to mark it
331 as removed.
331 as removed.
332 """
332 """
333
333
334 ractions = []
334 ractions = []
335 factions = xactions = []
335 factions = xactions = []
336 if branchmerge:
336 if branchmerge:
337 xactions = ractions
337 xactions = ractions
338 for f in wctx.deleted():
338 for f in wctx.deleted():
339 if f not in mctx:
339 if f not in mctx:
340 xactions.append((f, None, "forget deleted"))
340 xactions.append((f, None, "forget deleted"))
341
341
342 if not branchmerge:
342 if not branchmerge:
343 for f in wctx.removed():
343 for f in wctx.removed():
344 if f not in mctx:
344 if f not in mctx:
345 factions.append((f, None, "forget removed"))
345 factions.append((f, None, "forget removed"))
346
346
347 return ractions, factions
347 return ractions, factions
348
348
349 def _checkcollision(repo, wmf, actions):
349 def _checkcollision(repo, wmf, actions):
350 # build provisional merged manifest up
350 # build provisional merged manifest up
351 pmmf = set(wmf)
351 pmmf = set(wmf)
352
352
353 if actions:
353 if actions:
354 # k, dr, e and rd are no-op
354 # k, dr, e and rd are no-op
355 for m in 'a', 'f', 'g', 'cd', 'dc':
355 for m in 'a', 'f', 'g', 'cd', 'dc':
356 for f, args, msg in actions[m]:
356 for f, args, msg in actions[m]:
357 pmmf.add(f)
357 pmmf.add(f)
358 for f, args, msg in actions['r']:
358 for f, args, msg in actions['r']:
359 pmmf.discard(f)
359 pmmf.discard(f)
360 for f, args, msg in actions['dm']:
360 for f, args, msg in actions['dm']:
361 f2, flags = args
361 f2, flags = args
362 pmmf.discard(f2)
362 pmmf.discard(f2)
363 pmmf.add(f)
363 pmmf.add(f)
364 for f, args, msg in actions['dg']:
364 for f, args, msg in actions['dg']:
365 f2, flags = args
365 f2, flags = args
366 pmmf.add(f)
366 pmmf.add(f)
367 for f, args, msg in actions['m']:
367 for f, args, msg in actions['m']:
368 f1, f2, fa, move, anc = args
368 f1, f2, fa, move, anc = args
369 if move:
369 if move:
370 pmmf.discard(f1)
370 pmmf.discard(f1)
371 pmmf.add(f)
371 pmmf.add(f)
372
372
373 # check case-folding collision in provisional merged manifest
373 # check case-folding collision in provisional merged manifest
374 foldmap = {}
374 foldmap = {}
375 for f in sorted(pmmf):
375 for f in sorted(pmmf):
376 fold = util.normcase(f)
376 fold = util.normcase(f)
377 if fold in foldmap:
377 if fold in foldmap:
378 raise util.Abort(_("case-folding collision between %s and %s")
378 raise util.Abort(_("case-folding collision between %s and %s")
379 % (f, foldmap[fold]))
379 % (f, foldmap[fold]))
380 foldmap[fold] = f
380 foldmap[fold] = f
381
381
382 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
382 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
383 acceptremote, followcopies):
383 acceptremote, followcopies):
384 """
384 """
385 Merge p1 and p2 with ancestor pa and generate merge action list
385 Merge p1 and p2 with ancestor pa and generate merge action list
386
386
387 branchmerge and force are as passed in to update
387 branchmerge and force are as passed in to update
388 partial = function to filter file lists
388 partial = function to filter file lists
389 acceptremote = accept the incoming changes without prompting
389 acceptremote = accept the incoming changes without prompting
390 """
390 """
391
391
392 actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
392 actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
393 copy, movewithdir = {}, {}
393 copy, movewithdir = {}, {}
394
394
395 # manifests fetched in order are going to be faster, so prime the caches
395 # manifests fetched in order are going to be faster, so prime the caches
396 [x.manifest() for x in
396 [x.manifest() for x in
397 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
397 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
398
398
399 if followcopies:
399 if followcopies:
400 ret = copies.mergecopies(repo, wctx, p2, pa)
400 ret = copies.mergecopies(repo, wctx, p2, pa)
401 copy, movewithdir, diverge, renamedelete = ret
401 copy, movewithdir, diverge, renamedelete = ret
402 for of, fl in diverge.iteritems():
402 for of, fl in diverge.iteritems():
403 actions['dr'].append((of, (fl,), "divergent renames"))
403 actions['dr'].append((of, (fl,), "divergent renames"))
404 for of, fl in renamedelete.iteritems():
404 for of, fl in renamedelete.iteritems():
405 actions['rd'].append((of, (fl,), "rename and delete"))
405 actions['rd'].append((of, (fl,), "rename and delete"))
406
406
407 repo.ui.note(_("resolving manifests\n"))
407 repo.ui.note(_("resolving manifests\n"))
408 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
408 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
409 % (bool(branchmerge), bool(force), bool(partial)))
409 % (bool(branchmerge), bool(force), bool(partial)))
410 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
410 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
411
411
412 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
412 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
413 copied = set(copy.values())
413 copied = set(copy.values())
414 copied.update(movewithdir.values())
414 copied.update(movewithdir.values())
415
415
416 if '.hgsubstate' in m1:
416 if '.hgsubstate' in m1:
417 # check whether sub state is modified
417 # check whether sub state is modified
418 for s in sorted(wctx.substate):
418 for s in sorted(wctx.substate):
419 if wctx.sub(s).dirty():
419 if wctx.sub(s).dirty():
420 m1['.hgsubstate'] += "+"
420 m1['.hgsubstate'] += "+"
421 break
421 break
422
422
423 aborts = []
423 aborts = []
424 # Compare manifests
424 # Compare manifests
425 fdiff = dicthelpers.diff(m1, m2)
425 fdiff = dicthelpers.diff(m1, m2)
426 flagsdiff = m1.flagsdiff(m2)
426 flagsdiff = m1.flagsdiff(m2)
427 diff12 = dicthelpers.join(fdiff, flagsdiff)
427 diff12 = dicthelpers.join(fdiff, flagsdiff)
428
428
429 for f, (n12, fl12) in diff12.iteritems():
429 for f, (n12, fl12) in diff12.iteritems():
430 if n12:
430 if n12:
431 n1, n2 = n12
431 n1, n2 = n12
432 else: # file contents didn't change, but flags did
432 else: # file contents didn't change, but flags did
433 n1 = n2 = m1.get(f, None)
433 n1 = n2 = m1.get(f, None)
434 if n1 is None:
434 if n1 is None:
435 # Since n1 == n2, the file isn't present in m2 either. This
435 # Since n1 == n2, the file isn't present in m2 either. This
436 # means that the file was removed or deleted locally and
436 # means that the file was removed or deleted locally and
437 # removed remotely, but that residual entries remain in flags.
437 # removed remotely, but that residual entries remain in flags.
438 # This can happen in manifests generated by workingctx.
438 # This can happen in manifests generated by workingctx.
439 continue
439 continue
440 if fl12:
440 if fl12:
441 fl1, fl2 = fl12
441 fl1, fl2 = fl12
442 else: # flags didn't change, file contents did
442 else: # flags didn't change, file contents did
443 fl1 = fl2 = m1.flags(f)
443 fl1 = fl2 = m1.flags(f)
444
444
445 if partial and not partial(f):
445 if partial and not partial(f):
446 continue
446 continue
447 if n1 and n2:
447 if n1 and n2:
448 fa = f
448 fa = f
449 a = ma.get(f, nullid)
449 a = ma.get(f, nullid)
450 if a == nullid:
450 if a == nullid:
451 fa = copy.get(f, f)
451 fa = copy.get(f, f)
452 # Note: f as default is wrong - we can't really make a 3-way
452 # Note: f as default is wrong - we can't really make a 3-way
453 # merge without an ancestor file.
453 # merge without an ancestor file.
454 fla = ma.flags(fa)
454 fla = ma.flags(fa)
455 nol = 'l' not in fl1 + fl2 + fla
455 nol = 'l' not in fl1 + fl2 + fla
456 if n2 == a and fl2 == fla:
456 if n2 == a and fl2 == fla:
457 actions['k'].append((f, (), "keep")) # remote unchanged
457 actions['k'].append((f, (), "keep")) # remote unchanged
458 elif n1 == a and fl1 == fla: # local unchanged - use remote
458 elif n1 == a and fl1 == fla: # local unchanged - use remote
459 if n1 == n2: # optimization: keep local content
459 if n1 == n2: # optimization: keep local content
460 actions['e'].append((f, (fl2,), "update permissions"))
460 actions['e'].append((f, (fl2,), "update permissions"))
461 else:
461 else:
462 actions['g'].append((f, (fl2,), "remote is newer"))
462 actions['g'].append((f, (fl2,), "remote is newer"))
463 elif nol and n2 == a: # remote only changed 'x'
463 elif nol and n2 == a: # remote only changed 'x'
464 actions['e'].append((f, (fl2,), "update permissions"))
464 actions['e'].append((f, (fl2,), "update permissions"))
465 elif nol and n1 == a: # local only changed 'x'
465 elif nol and n1 == a: # local only changed 'x'
466 actions['g'].append((f, (fl1,), "remote is newer"))
466 actions['g'].append((f, (fl1,), "remote is newer"))
467 else: # both changed something
467 else: # both changed something
468 actions['m'].append((f, (f, f, fa, False, pa.node()),
468 actions['m'].append((f, (f, f, fa, False, pa.node()),
469 "versions differ"))
469 "versions differ"))
470 elif f in copied: # files we'll deal with on m2 side
470 elif f in copied: # files we'll deal with on m2 side
471 pass
471 pass
472 elif n1 and f in movewithdir: # directory rename, move local
472 elif n1 and f in movewithdir: # directory rename, move local
473 f2 = movewithdir[f]
473 f2 = movewithdir[f]
474 actions['dm'].append((f2, (f, fl1),
474 actions['dm'].append((f2, (f, fl1),
475 "remote directory rename - move from " + f))
475 "remote directory rename - move from " + f))
476 elif n1 and f in copy:
476 elif n1 and f in copy:
477 f2 = copy[f]
477 f2 = copy[f]
478 actions['m'].append((f, (f, f2, f2, False, pa.node()),
478 actions['m'].append((f, (f, f2, f2, False, pa.node()),
479 "local copied/moved from " + f2))
479 "local copied/moved from " + f2))
480 elif n1 and f in ma: # clean, a different, no remote
480 elif n1 and f in ma: # clean, a different, no remote
481 if n1 != ma[f]:
481 if n1 != ma[f]:
482 if acceptremote:
482 if acceptremote:
483 actions['r'].append((f, None, "remote delete"))
483 actions['r'].append((f, None, "remote delete"))
484 else:
484 else:
485 actions['cd'].append((f, None, "prompt changed/deleted"))
485 actions['cd'].append((f, None, "prompt changed/deleted"))
486 elif n1[20:] == "a": # added, no remote
486 elif n1[20:] == "a": # added, no remote
487 actions['f'].append((f, None, "remote deleted"))
487 actions['f'].append((f, None, "remote deleted"))
488 else:
488 else:
489 actions['r'].append((f, None, "other deleted"))
489 actions['r'].append((f, None, "other deleted"))
490 elif n2 and f in movewithdir:
490 elif n2 and f in movewithdir:
491 f2 = movewithdir[f]
491 f2 = movewithdir[f]
492 actions['dg'].append((f2, (f, fl2),
492 actions['dg'].append((f2, (f, fl2),
493 "local directory rename - get from " + f))
493 "local directory rename - get from " + f))
494 elif n2 and f in copy:
494 elif n2 and f in copy:
495 f2 = copy[f]
495 f2 = copy[f]
496 if f2 in m2:
496 if f2 in m2:
497 actions['m'].append((f, (f2, f, f2, False, pa.node()),
497 actions['m'].append((f, (f2, f, f2, False, pa.node()),
498 "remote copied from " + f2))
498 "remote copied from " + f2))
499 else:
499 else:
500 actions['m'].append((f, (f2, f, f2, True, pa.node()),
500 actions['m'].append((f, (f2, f, f2, True, pa.node()),
501 "remote moved from " + f2))
501 "remote moved from " + f2))
502 elif n2 and f not in ma:
502 elif n2 and f not in ma:
503 # local unknown, remote created: the logic is described by the
503 # local unknown, remote created: the logic is described by the
504 # following table:
504 # following table:
505 #
505 #
506 # force branchmerge different | action
506 # force branchmerge different | action
507 # n * n | get
507 # n * n | get
508 # n * y | abort
508 # n * y | abort
509 # y n * | get
509 # y n * | get
510 # y y n | get
510 # y y n | get
511 # y y y | merge
511 # y y y | merge
512 #
512 #
513 # Checking whether the files are different is expensive, so we
513 # Checking whether the files are different is expensive, so we
514 # don't do that when we can avoid it.
514 # don't do that when we can avoid it.
515 if force and not branchmerge:
515 if force and not branchmerge:
516 actions['g'].append((f, (fl2,), "remote created"))
516 actions['g'].append((f, (fl2,), "remote created"))
517 else:
517 else:
518 different = _checkunknownfile(repo, wctx, p2, f)
518 different = _checkunknownfile(repo, wctx, p2, f)
519 if force and branchmerge and different:
519 if force and branchmerge and different:
520 # FIXME: This is wrong - f is not in ma ...
520 # FIXME: This is wrong - f is not in ma ...
521 actions['m'].append((f, (f, f, f, False, pa.node()),
521 actions['m'].append((f, (f, f, f, False, pa.node()),
522 "remote differs from untracked local"))
522 "remote differs from untracked local"))
523 elif not force and different:
523 elif not force and different:
524 aborts.append((f, "ud"))
524 aborts.append((f, "ud"))
525 else:
525 else:
526 actions['g'].append((f, (fl2,), "remote created"))
526 actions['g'].append((f, (fl2,), "remote created"))
527 elif n2 and n2 != ma[f]:
527 elif n2 and n2 != ma[f]:
528 different = _checkunknownfile(repo, wctx, p2, f)
528 different = _checkunknownfile(repo, wctx, p2, f)
529 if not force and different:
529 if not force and different:
530 aborts.append((f, "ud"))
530 aborts.append((f, "ud"))
531 else:
531 else:
532 # if different: old untracked f may be overwritten and lost
532 # if different: old untracked f may be overwritten and lost
533 if acceptremote:
533 if acceptremote:
534 actions['g'].append((f, (m2.flags(f),),
534 actions['g'].append((f, (m2.flags(f),),
535 "remote recreating"))
535 "remote recreating"))
536 else:
536 else:
537 actions['dc'].append((f, (m2.flags(f),),
537 actions['dc'].append((f, (m2.flags(f),),
538 "prompt deleted/changed"))
538 "prompt deleted/changed"))
539
539
540 for f, m in sorted(aborts):
540 for f, m in sorted(aborts):
541 if m == "ud":
541 if m == "ud":
542 repo.ui.warn(_("%s: untracked file differs\n") % f)
542 repo.ui.warn(_("%s: untracked file differs\n") % f)
543 else: assert False, m
543 else: assert False, m
544 if aborts:
544 if aborts:
545 raise util.Abort(_("untracked files in working directory differ "
545 raise util.Abort(_("untracked files in working directory differ "
546 "from files in requested revision"))
546 "from files in requested revision"))
547
547
548 if not util.checkcase(repo.path):
548 if not util.checkcase(repo.path):
549 # check collision between files only in p2 for clean update
549 # check collision between files only in p2 for clean update
550 if (not branchmerge and
550 if (not branchmerge and
551 (force or not wctx.dirty(missing=True, branch=False))):
551 (force or not wctx.dirty(missing=True, branch=False))):
552 _checkcollision(repo, m2, None)
552 _checkcollision(repo, m2, None)
553 else:
553 else:
554 _checkcollision(repo, m1, actions)
554 _checkcollision(repo, m1, actions)
555
555
556 return actions
556 return actions
557
557
558 def batchremove(repo, actions):
558 def batchremove(repo, actions):
559 """apply removes to the working directory
559 """apply removes to the working directory
560
560
561 yields tuples for progress updates
561 yields tuples for progress updates
562 """
562 """
563 verbose = repo.ui.verbose
563 verbose = repo.ui.verbose
564 unlink = util.unlinkpath
564 unlink = util.unlinkpath
565 wjoin = repo.wjoin
565 wjoin = repo.wjoin
566 audit = repo.wopener.audit
566 audit = repo.wopener.audit
567 i = 0
567 i = 0
568 for f, args, msg in actions:
568 for f, args, msg in actions:
569 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
569 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
570 if True:
570 if verbose:
571 if verbose:
571 repo.ui.note(_("removing %s\n") % f)
572 repo.ui.note(_("removing %s\n") % f)
572 audit(f)
573 audit(f)
573 try:
574 try:
574 unlink(wjoin(f), ignoremissing=True)
575 unlink(wjoin(f), ignoremissing=True)
575 except OSError, inst:
576 except OSError, inst:
576 repo.ui.warn(_("update failed to remove %s: %s!\n") %
577 repo.ui.warn(_("update failed to remove %s: %s!\n") %
577 (f, inst.strerror))
578 (f, inst.strerror))
579 if i == 100:
578 if i == 100:
580 yield i, f
579 yield i, f
581 i = 0
580 i = 0
582 i += 1
581 i += 1
583 if i > 0:
582 if i > 0:
584 yield i, f
583 yield i, f
585
584
586 def batchget(repo, mctx, actions):
585 def batchget(repo, mctx, actions):
587 """apply gets to the working directory
586 """apply gets to the working directory
588
587
589 mctx is the context to get from
588 mctx is the context to get from
590
589
591 yields tuples for progress updates
590 yields tuples for progress updates
592 """
591 """
593 verbose = repo.ui.verbose
592 verbose = repo.ui.verbose
594 fctx = mctx.filectx
593 fctx = mctx.filectx
595 wwrite = repo.wwrite
594 wwrite = repo.wwrite
596 i = 0
595 i = 0
597 for f, args, msg in actions:
596 for f, args, msg in actions:
598 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
597 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
599 if True:
598 if verbose:
600 if verbose:
599 repo.ui.note(_("getting %s\n") % f)
601 repo.ui.note(_("getting %s\n") % f)
600 wwrite(f, fctx(f).data(), args[0])
602 wwrite(f, fctx(f).data(), args[0])
603 if i == 100:
601 if i == 100:
604 yield i, f
602 yield i, f
605 i = 0
603 i = 0
606 i += 1
604 i += 1
607 if i > 0:
605 if i > 0:
608 yield i, f
606 yield i, f
609
607
610 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
608 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
611 """apply the merge action list to the working directory
609 """apply the merge action list to the working directory
612
610
613 wctx is the working copy context
611 wctx is the working copy context
614 mctx is the context to be merged into the working copy
612 mctx is the context to be merged into the working copy
615
613
616 Return a tuple of counts (updated, merged, removed, unresolved) that
614 Return a tuple of counts (updated, merged, removed, unresolved) that
617 describes how many files were affected by the update.
615 describes how many files were affected by the update.
618 """
616 """
619
617
620 updated, merged, removed, unresolved = 0, 0, 0, 0
618 updated, merged, removed, unresolved = 0, 0, 0, 0
621 ms = mergestate(repo)
619 ms = mergestate(repo)
622 ms.reset(wctx.p1().node(), mctx.node())
620 ms.reset(wctx.p1().node(), mctx.node())
623 moves = []
621 moves = []
624 for m, l in actions.items():
622 for m, l in actions.items():
625 l.sort()
623 l.sort()
626
624
627 # prescan for merges
625 # prescan for merges
628 for f, args, msg in actions['m']:
626 for f, args, msg in actions['m']:
629 if True:
627 f1, f2, fa, move, anc = args
630 f1, f2, fa, move, anc = args
628 if f == '.hgsubstate': # merged internally
631 if f == '.hgsubstate': # merged internally
629 continue
632 continue
630 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
633 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
631 fcl = wctx[f1]
634 fcl = wctx[f1]
632 fco = mctx[f2]
635 fco = mctx[f2]
633 actx = repo[anc]
636 actx = repo[anc]
634 if fa in actx:
637 if fa in actx:
635 fca = actx[fa]
638 fca = actx[fa]
636 else:
639 else:
637 fca = repo.filectx(f1, fileid=nullrev)
640 fca = repo.filectx(f1, fileid=nullrev)
638 ms.add(fcl, fco, fca, f)
641 ms.add(fcl, fco, fca, f)
639 if f1 != f and move:
642 if f1 != f and move:
640 moves.append(f1)
643 moves.append(f1)
644
641
645 audit = repo.wopener.audit
642 audit = repo.wopener.audit
646 _updating = _('updating')
643 _updating = _('updating')
647 _files = _('files')
644 _files = _('files')
648 progress = repo.ui.progress
645 progress = repo.ui.progress
649
646
650 # remove renamed files after safely stored
647 # remove renamed files after safely stored
651 for f in moves:
648 for f in moves:
652 if os.path.lexists(repo.wjoin(f)):
649 if os.path.lexists(repo.wjoin(f)):
653 repo.ui.debug("removing %s\n" % f)
650 repo.ui.debug("removing %s\n" % f)
654 audit(f)
651 audit(f)
655 util.unlinkpath(repo.wjoin(f))
652 util.unlinkpath(repo.wjoin(f))
656
653
657 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
654 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
658
655
659 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
656 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
660 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
657 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
661
658
662 # remove in parallel (must come first)
659 # remove in parallel (must come first)
663 z = 0
660 z = 0
664 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
661 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
665 for i, item in prog:
662 for i, item in prog:
666 z += i
663 z += i
667 progress(_updating, z, item=item, total=numupdates, unit=_files)
664 progress(_updating, z, item=item, total=numupdates, unit=_files)
668 removed = len(actions['r'])
665 removed = len(actions['r'])
669
666
670 # get in parallel
667 # get in parallel
671 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
668 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
672 for i, item in prog:
669 for i, item in prog:
673 z += i
670 z += i
674 progress(_updating, z, item=item, total=numupdates, unit=_files)
671 progress(_updating, z, item=item, total=numupdates, unit=_files)
675 updated = len(actions['g'])
672 updated = len(actions['g'])
676
673
677 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
674 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
678 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
675 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
679
676
680 if True:
677 # forget (manifest only, just log it) (must come first)
681
678 for f, args, msg in actions['f']:
682 # forget (manifest only, just log it) (must come first)
679 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
683 for f, args, msg in actions['f']:
680 z += 1
684 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
681 progress(_updating, z, item=f, total=numupdates, unit=_files)
685 z += 1
686 progress(_updating, z, item=f, total=numupdates, unit=_files)
687
682
688 # re-add (manifest only, just log it)
683 # re-add (manifest only, just log it)
689 for f, args, msg in actions['a']:
684 for f, args, msg in actions['a']:
690 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
685 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
691 z += 1
686 z += 1
692 progress(_updating, z, item=f, total=numupdates, unit=_files)
687 progress(_updating, z, item=f, total=numupdates, unit=_files)
693
688
694 # keep (noop, just log it)
689 # keep (noop, just log it)
695 for f, args, msg in actions['k']:
690 for f, args, msg in actions['k']:
696 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
691 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
697 # no progress
692 # no progress
698
693
699 # merge
694 # merge
700 for f, args, msg in actions['m']:
695 for f, args, msg in actions['m']:
701 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
696 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
702 z += 1
697 z += 1
703 progress(_updating, z, item=f, total=numupdates, unit=_files)
698 progress(_updating, z, item=f, total=numupdates, unit=_files)
704 f1, f2, fa, move, anc = args
699 f1, f2, fa, move, anc = args
705 if f == '.hgsubstate': # subrepo states need updating
700 if f == '.hgsubstate': # subrepo states need updating
706 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
701 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
707 overwrite)
702 overwrite)
708 continue
703 continue
709 audit(f)
704 audit(f)
710 r = ms.resolve(f, wctx, labels=labels)
705 r = ms.resolve(f, wctx, labels=labels)
711 if r is not None and r > 0:
706 if r is not None and r > 0:
712 unresolved += 1
707 unresolved += 1
708 else:
709 if r is None:
710 updated += 1
713 else:
711 else:
714 if r is None:
712 merged += 1
715 updated += 1
716 else:
717 merged += 1
718
713
719 # directory rename, move local
714 # directory rename, move local
720 for f, args, msg in actions['dm']:
715 for f, args, msg in actions['dm']:
721 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
716 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
722 z += 1
717 z += 1
723 progress(_updating, z, item=f, total=numupdates, unit=_files)
718 progress(_updating, z, item=f, total=numupdates, unit=_files)
724 f0, flags = args
719 f0, flags = args
725 repo.ui.note(_("moving %s to %s\n") % (f0, f))
720 repo.ui.note(_("moving %s to %s\n") % (f0, f))
726 audit(f)
721 audit(f)
727 repo.wwrite(f, wctx.filectx(f0).data(), flags)
722 repo.wwrite(f, wctx.filectx(f0).data(), flags)
728 util.unlinkpath(repo.wjoin(f0))
723 util.unlinkpath(repo.wjoin(f0))
729 updated += 1
724 updated += 1
730
725
731 # local directory rename, get
726 # local directory rename, get
732 for f, args, msg in actions['dg']:
727 for f, args, msg in actions['dg']:
733 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
728 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
734 z += 1
729 z += 1
735 progress(_updating, z, item=f, total=numupdates, unit=_files)
730 progress(_updating, z, item=f, total=numupdates, unit=_files)
736 f0, flags = args
731 f0, flags = args
737 repo.ui.note(_("getting %s to %s\n") % (f0, f))
732 repo.ui.note(_("getting %s to %s\n") % (f0, f))
738 repo.wwrite(f, mctx.filectx(f0).data(), flags)
733 repo.wwrite(f, mctx.filectx(f0).data(), flags)
739 updated += 1
734 updated += 1
740
735
741 # divergent renames
736 # divergent renames
742 for f, args, msg in actions['dr']:
737 for f, args, msg in actions['dr']:
743 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
738 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
744 z += 1
739 z += 1
745 progress(_updating, z, item=f, total=numupdates, unit=_files)
740 progress(_updating, z, item=f, total=numupdates, unit=_files)
746 fl, = args
741 fl, = args
747 repo.ui.warn(_("note: possible conflict - %s was renamed "
742 repo.ui.warn(_("note: possible conflict - %s was renamed "
748 "multiple times to:\n") % f)
743 "multiple times to:\n") % f)
749 for nf in fl:
744 for nf in fl:
750 repo.ui.warn(" %s\n" % nf)
745 repo.ui.warn(" %s\n" % nf)
751
746
752 # rename and delete
747 # rename and delete
753 for f, args, msg in actions['rd']:
748 for f, args, msg in actions['rd']:
754 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
749 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
755 z += 1
750 z += 1
756 progress(_updating, z, item=f, total=numupdates, unit=_files)
751 progress(_updating, z, item=f, total=numupdates, unit=_files)
757 fl, = args
752 fl, = args
758 repo.ui.warn(_("note: possible conflict - %s was deleted "
753 repo.ui.warn(_("note: possible conflict - %s was deleted "
759 "and renamed to:\n") % f)
754 "and renamed to:\n") % f)
760 for nf in fl:
755 for nf in fl:
761 repo.ui.warn(" %s\n" % nf)
756 repo.ui.warn(" %s\n" % nf)
762
757
763 # exec
758 # exec
764 for f, args, msg in actions['e']:
759 for f, args, msg in actions['e']:
765 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
760 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
766 z += 1
761 z += 1
767 progress(_updating, z, item=f, total=numupdates, unit=_files)
762 progress(_updating, z, item=f, total=numupdates, unit=_files)
768 flags, = args
763 flags, = args
769 audit(f)
764 audit(f)
770 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
765 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
771 updated += 1
766 updated += 1
772
767
773 ms.commit()
768 ms.commit()
774 progress(_updating, None, total=numupdates, unit=_files)
769 progress(_updating, None, total=numupdates, unit=_files)
775
770
776 return updated, merged, removed, unresolved
771 return updated, merged, removed, unresolved
777
772
778 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
773 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
779 acceptremote, followcopies):
774 acceptremote, followcopies):
780 "Calculate the actions needed to merge mctx into wctx using ancestors"
775 "Calculate the actions needed to merge mctx into wctx using ancestors"
781
776
782 if len(ancestors) == 1: # default
777 if len(ancestors) == 1: # default
783 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
778 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
784 branchmerge, force,
779 branchmerge, force,
785 partial, acceptremote, followcopies)
780 partial, acceptremote, followcopies)
786
781
787 else: # only when merge.preferancestor=* - experimentalish code
782 else: # only when merge.preferancestor=* - experimentalish code
788 repo.ui.status(
783 repo.ui.status(
789 _("note: merging %s and %s using bids from ancestors %s\n") %
784 _("note: merging %s and %s using bids from ancestors %s\n") %
790 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
785 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
791
786
792 # Call for bids
787 # Call for bids
793 fbids = {} # mapping filename to bids (action method to list af actions)
788 fbids = {} # mapping filename to bids (action method to list af actions)
794 for ancestor in ancestors:
789 for ancestor in ancestors:
795 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
790 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
796 actions = manifestmerge(repo, wctx, mctx, ancestor,
791 actions = manifestmerge(repo, wctx, mctx, ancestor,
797 branchmerge, force,
792 branchmerge, force,
798 partial, acceptremote, followcopies)
793 partial, acceptremote, followcopies)
799 for m, l in sorted(actions.items()):
794 for m, l in sorted(actions.items()):
800 for a in l:
795 for a in l:
801 f, args, msg = a
796 f, args, msg = a
802 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
797 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
803 if f in fbids:
798 if f in fbids:
804 d = fbids[f]
799 d = fbids[f]
805 if m in d:
800 if m in d:
806 d[m].append(a)
801 d[m].append(a)
807 else:
802 else:
808 d[m] = [a]
803 d[m] = [a]
809 else:
804 else:
810 fbids[f] = {m: [a]}
805 fbids[f] = {m: [a]}
811
806
812 # Pick the best bid for each file
807 # Pick the best bid for each file
813 repo.ui.note(_('\nauction for merging merge bids\n'))
808 repo.ui.note(_('\nauction for merging merge bids\n'))
814 actions = dict((m, []) for m in actions.keys())
809 actions = dict((m, []) for m in actions.keys())
815 for f, bids in sorted(fbids.items()):
810 for f, bids in sorted(fbids.items()):
816 # bids is a mapping from action method to list af actions
811 # bids is a mapping from action method to list af actions
817 # Consensus?
812 # Consensus?
818 if len(bids) == 1: # all bids are the same kind of method
813 if len(bids) == 1: # all bids are the same kind of method
819 m, l = bids.items()[0]
814 m, l = bids.items()[0]
820 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
815 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
821 repo.ui.note(" %s: consensus for %s\n" % (f, m))
816 repo.ui.note(" %s: consensus for %s\n" % (f, m))
822 actions[m].append(l[0])
817 actions[m].append(l[0])
823 continue
818 continue
824 # If keep is an option, just do it.
819 # If keep is an option, just do it.
825 if "k" in bids:
820 if "k" in bids:
826 repo.ui.note(" %s: picking 'keep' action\n" % f)
821 repo.ui.note(" %s: picking 'keep' action\n" % f)
827 actions['k'].append(bids["k"][0])
822 actions['k'].append(bids["k"][0])
828 continue
823 continue
829 # If there are gets and they all agree [how could they not?], do it.
824 # If there are gets and they all agree [how could they not?], do it.
830 if "g" in bids:
825 if "g" in bids:
831 ga0 = bids["g"][0]
826 ga0 = bids["g"][0]
832 if util.all(a == ga0 for a in bids["g"][1:]):
827 if util.all(a == ga0 for a in bids["g"][1:]):
833 repo.ui.note(" %s: picking 'get' action\n" % f)
828 repo.ui.note(" %s: picking 'get' action\n" % f)
834 actions['g'].append(ga0)
829 actions['g'].append(ga0)
835 continue
830 continue
836 # TODO: Consider other simple actions such as mode changes
831 # TODO: Consider other simple actions such as mode changes
837 # Handle inefficient democrazy.
832 # Handle inefficient democrazy.
838 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
833 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
839 for m, l in sorted(bids.items()):
834 for m, l in sorted(bids.items()):
840 for _f, args, msg in l:
835 for _f, args, msg in l:
841 repo.ui.note(' %s -> %s\n' % (msg, m))
836 repo.ui.note(' %s -> %s\n' % (msg, m))
842 # Pick random action. TODO: Instead, prompt user when resolving
837 # Pick random action. TODO: Instead, prompt user when resolving
843 m, l = bids.items()[0]
838 m, l = bids.items()[0]
844 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
839 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
845 (f, m))
840 (f, m))
846 actions[m].append(l[0])
841 actions[m].append(l[0])
847 continue
842 continue
848 repo.ui.note(_('end of auction\n\n'))
843 repo.ui.note(_('end of auction\n\n'))
849
844
850 # Prompt and create actions. TODO: Move this towards resolve phase.
845 # Prompt and create actions. TODO: Move this towards resolve phase.
851 if True:
846 for f, args, msg in actions['cd']:
852 for f, args, msg in actions['cd']:
847 if repo.ui.promptchoice(
853 if repo.ui.promptchoice(
848 _("local changed %s which remote deleted\n"
854 _("local changed %s which remote deleted\n"
849 "use (c)hanged version or (d)elete?"
855 "use (c)hanged version or (d)elete?"
850 "$$ &Changed $$ &Delete") % f, 0):
856 "$$ &Changed $$ &Delete") % f, 0):
851 actions['r'].append((f, None, "prompt delete"))
857 actions['r'].append((f, None, "prompt delete"))
852 else:
858 else:
853 actions['a'].append((f, None, "prompt keep"))
859 actions['a'].append((f, None, "prompt keep"))
854 del actions['cd'][:]
860 del actions['cd'][:]
861
855
862 for f, args, msg in actions['dc']:
856 for f, args, msg in actions['dc']:
863 flags, = args
857 flags, = args
864 if repo.ui.promptchoice(
858 if repo.ui.promptchoice(
865 _("remote changed %s which local deleted\n"
859 _("remote changed %s which local deleted\n"
866 "use (c)hanged version or leave (d)eleted?"
860 "use (c)hanged version or leave (d)eleted?"
867 "$$ &Changed $$ &Deleted") % f, 0) == 0:
861 "$$ &Changed $$ &Deleted") % f, 0) == 0:
868 actions['g'].append((f, (flags,), "prompt recreating"))
862 actions['g'].append((f, (flags,), "prompt recreating"))
869 del actions['dc'][:]
863 del actions['dc'][:]
870
864
871 if wctx.rev() is None:
865 if wctx.rev() is None:
872 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
866 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
873 actions['r'].extend(ractions)
867 actions['r'].extend(ractions)
874 actions['f'].extend(factions)
868 actions['f'].extend(factions)
875
869
876 return actions
870 return actions
877
871
878 def recordupdates(repo, actions, branchmerge):
872 def recordupdates(repo, actions, branchmerge):
879 "record merge actions to the dirstate"
873 "record merge actions to the dirstate"
880 if True:
874 # remove (must come first)
881 # remove (must come first)
875 for f, args, msg in actions['r']:
882 for f, args, msg in actions['r']:
876 if branchmerge:
883 if branchmerge:
877 repo.dirstate.remove(f)
884 repo.dirstate.remove(f)
878 else:
885 else:
886 repo.dirstate.drop(f)
887
888 # forget (must come first)
889 for f, args, msg in actions['f']:
890 repo.dirstate.drop(f)
879 repo.dirstate.drop(f)
891
880
892 # re-add
881 # forget (must come first)
893 for f, args, msg in actions['a']:
882 for f, args, msg in actions['f']:
894 if not branchmerge:
883 repo.dirstate.drop(f)
895 repo.dirstate.add(f)
896
884
897 # exec change
885 # re-add
898 for f, args, msg in actions['e']:
886 for f, args, msg in actions['a']:
899 repo.dirstate.normallookup(f)
887 if not branchmerge:
888 repo.dirstate.add(f)
900
889
901 # keep
890 # exec change
902 for f, args, msg in actions['k']:
891 for f, args, msg in actions['e']:
903 pass
892 repo.dirstate.normallookup(f)
904
893
905 # get
894 # keep
906 for f, args, msg in actions['g']:
895 for f, args, msg in actions['k']:
907 if branchmerge:
896 pass
908 repo.dirstate.otherparent(f)
897
909 else:
898 # get
910 repo.dirstate.normal(f)
899 for f, args, msg in actions['g']:
900 if branchmerge:
901 repo.dirstate.otherparent(f)
902 else:
903 repo.dirstate.normal(f)
911
904
912 # merge
905 # merge
913 for f, args, msg in actions['m']:
906 for f, args, msg in actions['m']:
914 f1, f2, fa, move, anc = args
907 f1, f2, fa, move, anc = args
915 if branchmerge:
908 if branchmerge:
916 # We've done a branch merge, mark this file as merged
909 # We've done a branch merge, mark this file as merged
917 # so that we properly record the merger later
910 # so that we properly record the merger later
918 repo.dirstate.merge(f)
911 repo.dirstate.merge(f)
919 if f1 != f2: # copy/rename
912 if f1 != f2: # copy/rename
920 if move:
921 repo.dirstate.remove(f1)
922 if f1 != f:
923 repo.dirstate.copy(f1, f)
924 else:
925 repo.dirstate.copy(f2, f)
926 else:
927 # We've update-merged a locally modified file, so
928 # we set the dirstate to emulate a normal checkout
929 # of that file some time in the past. Thus our
930 # merge will appear as a normal local file
931 # modification.
932 if f2 == f: # file not locally copied/moved
933 repo.dirstate.normallookup(f)
934 if move:
913 if move:
935 repo.dirstate.drop(f1)
914 repo.dirstate.remove(f1)
915 if f1 != f:
916 repo.dirstate.copy(f1, f)
917 else:
918 repo.dirstate.copy(f2, f)
919 else:
920 # We've update-merged a locally modified file, so
921 # we set the dirstate to emulate a normal checkout
922 # of that file some time in the past. Thus our
923 # merge will appear as a normal local file
924 # modification.
925 if f2 == f: # file not locally copied/moved
926 repo.dirstate.normallookup(f)
927 if move:
928 repo.dirstate.drop(f1)
936
929
937 # directory rename, move local
930 # directory rename, move local
938 for f, args, msg in actions['dm']:
931 for f, args, msg in actions['dm']:
939 f0, flag = args
932 f0, flag = args
940 if f0 not in repo.dirstate:
933 if f0 not in repo.dirstate:
941 # untracked file moved
934 # untracked file moved
942 continue
935 continue
943 if branchmerge:
936 if branchmerge:
944 repo.dirstate.add(f)
937 repo.dirstate.add(f)
945 repo.dirstate.remove(f0)
938 repo.dirstate.remove(f0)
946 repo.dirstate.copy(f0, f)
939 repo.dirstate.copy(f0, f)
947 else:
940 else:
948 repo.dirstate.normal(f)
941 repo.dirstate.normal(f)
949 repo.dirstate.drop(f0)
942 repo.dirstate.drop(f0)
950
943
951 # directory rename, get
944 # directory rename, get
952 for f, args, msg in actions['dg']:
945 for f, args, msg in actions['dg']:
953 f0, flag = args
946 f0, flag = args
954 if branchmerge:
947 if branchmerge:
955 repo.dirstate.add(f)
948 repo.dirstate.add(f)
956 repo.dirstate.copy(f0, f)
949 repo.dirstate.copy(f0, f)
957 else:
950 else:
958 repo.dirstate.normal(f)
951 repo.dirstate.normal(f)
959
952
960 def update(repo, node, branchmerge, force, partial, ancestor=None,
953 def update(repo, node, branchmerge, force, partial, ancestor=None,
961 mergeancestor=False, labels=None):
954 mergeancestor=False, labels=None):
962 """
955 """
963 Perform a merge between the working directory and the given node
956 Perform a merge between the working directory and the given node
964
957
965 node = the node to update to, or None if unspecified
958 node = the node to update to, or None if unspecified
966 branchmerge = whether to merge between branches
959 branchmerge = whether to merge between branches
967 force = whether to force branch merging or file overwriting
960 force = whether to force branch merging or file overwriting
968 partial = a function to filter file lists (dirstate not updated)
961 partial = a function to filter file lists (dirstate not updated)
969 mergeancestor = whether it is merging with an ancestor. If true,
962 mergeancestor = whether it is merging with an ancestor. If true,
970 we should accept the incoming changes for any prompts that occur.
963 we should accept the incoming changes for any prompts that occur.
971 If false, merging with an ancestor (fast-forward) is only allowed
964 If false, merging with an ancestor (fast-forward) is only allowed
972 between different named branches. This flag is used by rebase extension
965 between different named branches. This flag is used by rebase extension
973 as a temporary fix and should be avoided in general.
966 as a temporary fix and should be avoided in general.
974
967
975 The table below shows all the behaviors of the update command
968 The table below shows all the behaviors of the update command
976 given the -c and -C or no options, whether the working directory
969 given the -c and -C or no options, whether the working directory
977 is dirty, whether a revision is specified, and the relationship of
970 is dirty, whether a revision is specified, and the relationship of
978 the parent rev to the target rev (linear, on the same named
971 the parent rev to the target rev (linear, on the same named
979 branch, or on another named branch).
972 branch, or on another named branch).
980
973
981 This logic is tested by test-update-branches.t.
974 This logic is tested by test-update-branches.t.
982
975
983 -c -C dirty rev | linear same cross
976 -c -C dirty rev | linear same cross
984 n n n n | ok (1) x
977 n n n n | ok (1) x
985 n n n y | ok ok ok
978 n n n y | ok ok ok
986 n n y n | merge (2) (2)
979 n n y n | merge (2) (2)
987 n n y y | merge (3) (3)
980 n n y y | merge (3) (3)
988 n y * * | --- discard ---
981 n y * * | --- discard ---
989 y n y * | --- (4) ---
982 y n y * | --- (4) ---
990 y n n * | --- ok ---
983 y n n * | --- ok ---
991 y y * * | --- (5) ---
984 y y * * | --- (5) ---
992
985
993 x = can't happen
986 x = can't happen
994 * = don't-care
987 * = don't-care
995 1 = abort: not a linear update (merge or update --check to force update)
988 1 = abort: not a linear update (merge or update --check to force update)
996 2 = abort: uncommitted changes (commit and merge, or update --clean to
989 2 = abort: uncommitted changes (commit and merge, or update --clean to
997 discard changes)
990 discard changes)
998 3 = abort: uncommitted changes (commit or update --clean to discard changes)
991 3 = abort: uncommitted changes (commit or update --clean to discard changes)
999 4 = abort: uncommitted changes (checked in commands.py)
992 4 = abort: uncommitted changes (checked in commands.py)
1000 5 = incompatible options (checked in commands.py)
993 5 = incompatible options (checked in commands.py)
1001
994
1002 Return the same tuple as applyupdates().
995 Return the same tuple as applyupdates().
1003 """
996 """
1004
997
1005 onode = node
998 onode = node
1006 wlock = repo.wlock()
999 wlock = repo.wlock()
1007 try:
1000 try:
1008 wc = repo[None]
1001 wc = repo[None]
1009 pl = wc.parents()
1002 pl = wc.parents()
1010 p1 = pl[0]
1003 p1 = pl[0]
1011 pas = [None]
1004 pas = [None]
1012 if ancestor:
1005 if ancestor:
1013 pas = [repo[ancestor]]
1006 pas = [repo[ancestor]]
1014
1007
1015 if node is None:
1008 if node is None:
1016 # Here is where we should consider bookmarks, divergent bookmarks,
1009 # Here is where we should consider bookmarks, divergent bookmarks,
1017 # foreground changesets (successors), and tip of current branch;
1010 # foreground changesets (successors), and tip of current branch;
1018 # but currently we are only checking the branch tips.
1011 # but currently we are only checking the branch tips.
1019 try:
1012 try:
1020 node = repo.branchtip(wc.branch())
1013 node = repo.branchtip(wc.branch())
1021 except error.RepoLookupError:
1014 except error.RepoLookupError:
1022 if wc.branch() == "default": # no default branch!
1015 if wc.branch() == "default": # no default branch!
1023 node = repo.lookup("tip") # update to tip
1016 node = repo.lookup("tip") # update to tip
1024 else:
1017 else:
1025 raise util.Abort(_("branch %s not found") % wc.branch())
1018 raise util.Abort(_("branch %s not found") % wc.branch())
1026
1019
1027 if p1.obsolete() and not p1.children():
1020 if p1.obsolete() and not p1.children():
1028 # allow updating to successors
1021 # allow updating to successors
1029 successors = obsolete.successorssets(repo, p1.node())
1022 successors = obsolete.successorssets(repo, p1.node())
1030
1023
1031 # behavior of certain cases is as follows,
1024 # behavior of certain cases is as follows,
1032 #
1025 #
1033 # divergent changesets: update to highest rev, similar to what
1026 # divergent changesets: update to highest rev, similar to what
1034 # is currently done when there are more than one head
1027 # is currently done when there are more than one head
1035 # (i.e. 'tip')
1028 # (i.e. 'tip')
1036 #
1029 #
1037 # replaced changesets: same as divergent except we know there
1030 # replaced changesets: same as divergent except we know there
1038 # is no conflict
1031 # is no conflict
1039 #
1032 #
1040 # pruned changeset: no update is done; though, we could
1033 # pruned changeset: no update is done; though, we could
1041 # consider updating to the first non-obsolete parent,
1034 # consider updating to the first non-obsolete parent,
1042 # similar to what is current done for 'hg prune'
1035 # similar to what is current done for 'hg prune'
1043
1036
1044 if successors:
1037 if successors:
1045 # flatten the list here handles both divergent (len > 1)
1038 # flatten the list here handles both divergent (len > 1)
1046 # and the usual case (len = 1)
1039 # and the usual case (len = 1)
1047 successors = [n for sub in successors for n in sub]
1040 successors = [n for sub in successors for n in sub]
1048
1041
1049 # get the max revision for the given successors set,
1042 # get the max revision for the given successors set,
1050 # i.e. the 'tip' of a set
1043 # i.e. the 'tip' of a set
1051 node = repo.revs("max(%ln)", successors)[0]
1044 node = repo.revs("max(%ln)", successors)[0]
1052 pas = [p1]
1045 pas = [p1]
1053
1046
1054 overwrite = force and not branchmerge
1047 overwrite = force and not branchmerge
1055
1048
1056 p2 = repo[node]
1049 p2 = repo[node]
1057 if pas[0] is None:
1050 if pas[0] is None:
1058 if repo.ui.config("merge", "preferancestor") == '*':
1051 if repo.ui.config("merge", "preferancestor") == '*':
1059 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1052 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1060 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1053 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1061 else:
1054 else:
1062 pas = [p1.ancestor(p2, warn=True)]
1055 pas = [p1.ancestor(p2, warn=True)]
1063
1056
1064 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1057 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1065
1058
1066 ### check phase
1059 ### check phase
1067 if not overwrite and len(pl) > 1:
1060 if not overwrite and len(pl) > 1:
1068 raise util.Abort(_("outstanding uncommitted merges"))
1061 raise util.Abort(_("outstanding uncommitted merges"))
1069 if branchmerge:
1062 if branchmerge:
1070 if pas == [p2]:
1063 if pas == [p2]:
1071 raise util.Abort(_("merging with a working directory ancestor"
1064 raise util.Abort(_("merging with a working directory ancestor"
1072 " has no effect"))
1065 " has no effect"))
1073 elif pas == [p1]:
1066 elif pas == [p1]:
1074 if not mergeancestor and p1.branch() == p2.branch():
1067 if not mergeancestor and p1.branch() == p2.branch():
1075 raise util.Abort(_("nothing to merge"),
1068 raise util.Abort(_("nothing to merge"),
1076 hint=_("use 'hg update' "
1069 hint=_("use 'hg update' "
1077 "or check 'hg heads'"))
1070 "or check 'hg heads'"))
1078 if not force and (wc.files() or wc.deleted()):
1071 if not force and (wc.files() or wc.deleted()):
1079 raise util.Abort(_("uncommitted changes"),
1072 raise util.Abort(_("uncommitted changes"),
1080 hint=_("use 'hg status' to list changes"))
1073 hint=_("use 'hg status' to list changes"))
1081 for s in sorted(wc.substate):
1074 for s in sorted(wc.substate):
1082 if wc.sub(s).dirty():
1075 if wc.sub(s).dirty():
1083 raise util.Abort(_("uncommitted changes in "
1076 raise util.Abort(_("uncommitted changes in "
1084 "subrepository '%s'") % s)
1077 "subrepository '%s'") % s)
1085
1078
1086 elif not overwrite:
1079 elif not overwrite:
1087 if p1 == p2: # no-op update
1080 if p1 == p2: # no-op update
1088 # call the hooks and exit early
1081 # call the hooks and exit early
1089 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1082 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1090 repo.hook('update', parent1=xp2, parent2='', error=0)
1083 repo.hook('update', parent1=xp2, parent2='', error=0)
1091 return 0, 0, 0, 0
1084 return 0, 0, 0, 0
1092
1085
1093 if pas not in ([p1], [p2]): # nonlinear
1086 if pas not in ([p1], [p2]): # nonlinear
1094 dirty = wc.dirty(missing=True)
1087 dirty = wc.dirty(missing=True)
1095 if dirty or onode is None:
1088 if dirty or onode is None:
1096 # Branching is a bit strange to ensure we do the minimal
1089 # Branching is a bit strange to ensure we do the minimal
1097 # amount of call to obsolete.background.
1090 # amount of call to obsolete.background.
1098 foreground = obsolete.foreground(repo, [p1.node()])
1091 foreground = obsolete.foreground(repo, [p1.node()])
1099 # note: the <node> variable contains a random identifier
1092 # note: the <node> variable contains a random identifier
1100 if repo[node].node() in foreground:
1093 if repo[node].node() in foreground:
1101 pas = [p1] # allow updating to successors
1094 pas = [p1] # allow updating to successors
1102 elif dirty:
1095 elif dirty:
1103 msg = _("uncommitted changes")
1096 msg = _("uncommitted changes")
1104 if onode is None:
1097 if onode is None:
1105 hint = _("commit and merge, or update --clean to"
1098 hint = _("commit and merge, or update --clean to"
1106 " discard changes")
1099 " discard changes")
1107 else:
1100 else:
1108 hint = _("commit or update --clean to discard"
1101 hint = _("commit or update --clean to discard"
1109 " changes")
1102 " changes")
1110 raise util.Abort(msg, hint=hint)
1103 raise util.Abort(msg, hint=hint)
1111 else: # node is none
1104 else: # node is none
1112 msg = _("not a linear update")
1105 msg = _("not a linear update")
1113 hint = _("merge or update --check to force update")
1106 hint = _("merge or update --check to force update")
1114 raise util.Abort(msg, hint=hint)
1107 raise util.Abort(msg, hint=hint)
1115 else:
1108 else:
1116 # Allow jumping branches if clean and specific rev given
1109 # Allow jumping branches if clean and specific rev given
1117 pas = [p1]
1110 pas = [p1]
1118
1111
1119 followcopies = False
1112 followcopies = False
1120 if overwrite:
1113 if overwrite:
1121 pas = [wc]
1114 pas = [wc]
1122 elif pas == [p2]: # backwards
1115 elif pas == [p2]: # backwards
1123 pas = [wc.p1()]
1116 pas = [wc.p1()]
1124 elif not branchmerge and not wc.dirty(missing=True):
1117 elif not branchmerge and not wc.dirty(missing=True):
1125 pass
1118 pass
1126 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1119 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1127 followcopies = True
1120 followcopies = True
1128
1121
1129 ### calculate phase
1122 ### calculate phase
1130 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1123 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1131 partial, mergeancestor, followcopies)
1124 partial, mergeancestor, followcopies)
1132
1125
1133 ### apply phase
1126 ### apply phase
1134 if not branchmerge: # just jump to the new rev
1127 if not branchmerge: # just jump to the new rev
1135 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1128 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1136 if not partial:
1129 if not partial:
1137 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1130 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1138 # note that we're in the middle of an update
1131 # note that we're in the middle of an update
1139 repo.vfs.write('updatestate', p2.hex())
1132 repo.vfs.write('updatestate', p2.hex())
1140
1133
1141 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1134 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1142
1135
1143 if not partial:
1136 if not partial:
1144 repo.setparents(fp1, fp2)
1137 repo.setparents(fp1, fp2)
1145 recordupdates(repo, actions, branchmerge)
1138 recordupdates(repo, actions, branchmerge)
1146 # update completed, clear state
1139 # update completed, clear state
1147 util.unlink(repo.join('updatestate'))
1140 util.unlink(repo.join('updatestate'))
1148
1141
1149 if not branchmerge:
1142 if not branchmerge:
1150 repo.dirstate.setbranch(p2.branch())
1143 repo.dirstate.setbranch(p2.branch())
1151 finally:
1144 finally:
1152 wlock.release()
1145 wlock.release()
1153
1146
1154 if not partial:
1147 if not partial:
1155 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1148 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1156 return stats
1149 return stats
General Comments 0
You need to be logged in to leave comments. Login now