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