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