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