##// END OF EJS Templates
merge: use ancestor filename from planning phase instead of filectx ancestor...
Mads Kiilerich -
r20897:0b50788c default
parent child Browse files
Show More
@@ -1,1016 +1,1017
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, fd, move = args
343 f2, fa, fd, move = 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, f, False), "versions differ"))
471 actions.append((f, "m", (f, fa, f, False), "versions differ"))
472 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
473 pass
473 pass
474 elif n1 and f in movewithdir: # directory rename
474 elif n1 and f in movewithdir: # directory rename
475 f2 = movewithdir[f]
475 f2 = movewithdir[f]
476 actions.append((f, "d", (None, f2, fl1),
476 actions.append((f, "d", (None, f2, fl1),
477 "remote renamed directory to " + f2))
477 "remote renamed directory to " + f2))
478 elif n1 and f in copy:
478 elif n1 and f in copy:
479 f2 = copy[f]
479 f2 = copy[f]
480 actions.append((f, "m", (f2, f, False),
480 actions.append((f, "m", (f2, f2, f, False),
481 "local copied/moved to " + f2))
481 "local copied/moved to " + f2))
482 elif n1 and f in ma: # clean, a different, no remote
482 elif n1 and f in ma: # clean, a different, no remote
483 if n1 != ma[f]:
483 if n1 != ma[f]:
484 if acceptremote:
484 if acceptremote:
485 actions.append((f, "r", None, "remote delete"))
485 actions.append((f, "r", None, "remote delete"))
486 else:
486 else:
487 actions.append((f, "cd", None, "prompt changed/deleted"))
487 actions.append((f, "cd", None, "prompt changed/deleted"))
488 elif n1[20:] == "a": # added, no remote
488 elif n1[20:] == "a": # added, no remote
489 actions.append((f, "f", None, "remote deleted"))
489 actions.append((f, "f", None, "remote deleted"))
490 else:
490 else:
491 actions.append((f, "r", None, "other deleted"))
491 actions.append((f, "r", None, "other deleted"))
492 elif n2 and f in movewithdir:
492 elif n2 and f in movewithdir:
493 f2 = movewithdir[f]
493 f2 = movewithdir[f]
494 actions.append((None, "d", (f, f2, fl2),
494 actions.append((None, "d", (f, f2, fl2),
495 "local renamed directory to " + f2))
495 "local renamed directory to " + f2))
496 elif n2 and f in copy:
496 elif n2 and f in copy:
497 f2 = copy[f]
497 f2 = copy[f]
498 if f2 in m2:
498 if f2 in m2:
499 actions.append((f2, "m", (f, f, False),
499 actions.append((f2, "m", (f, f2, f, False),
500 "remote copied to " + f))
500 "remote copied to " + f))
501 else:
501 else:
502 actions.append((f2, "m", (f, f, True),
502 actions.append((f2, "m", (f, f2, f, True),
503 "remote moved to " + f))
503 "remote moved to " + f))
504 elif n2 and f not in ma:
504 elif n2 and f not in ma:
505 # local unknown, remote created: the logic is described by the
505 # local unknown, remote created: the logic is described by the
506 # following table:
506 # following table:
507 #
507 #
508 # force branchmerge different | action
508 # force branchmerge different | action
509 # n * n | get
509 # n * n | get
510 # n * y | abort
510 # n * y | abort
511 # y n * | get
511 # y n * | get
512 # y y n | get
512 # y y n | get
513 # y y y | merge
513 # y y y | merge
514 #
514 #
515 # Checking whether the files are different is expensive, so we
515 # Checking whether the files are different is expensive, so we
516 # don't do that when we can avoid it.
516 # don't do that when we can avoid it.
517 if force and not branchmerge:
517 if force and not branchmerge:
518 actions.append((f, "g", (fl2,), "remote created"))
518 actions.append((f, "g", (fl2,), "remote created"))
519 else:
519 else:
520 different = _checkunknownfile(repo, wctx, p2, f)
520 different = _checkunknownfile(repo, wctx, p2, f)
521 if force and branchmerge and different:
521 if force and branchmerge and different:
522 actions.append((f, "m", (f, f, False),
522 # FIXME: This is wrong - f is not in ma ...
523 actions.append((f, "m", (f, f, f, False),
523 "remote differs from untracked local"))
524 "remote differs from untracked local"))
524 elif not force and different:
525 elif not force and different:
525 aborts.append((f, "ud"))
526 aborts.append((f, "ud"))
526 else:
527 else:
527 actions.append((f, "g", (fl2,), "remote created"))
528 actions.append((f, "g", (fl2,), "remote created"))
528 elif n2 and n2 != ma[f]:
529 elif n2 and n2 != ma[f]:
529 different = _checkunknownfile(repo, wctx, p2, f)
530 different = _checkunknownfile(repo, wctx, p2, f)
530 if not force and different:
531 if not force and different:
531 aborts.append((f, "ud"))
532 aborts.append((f, "ud"))
532 else:
533 else:
533 # if different: old untracked f may be overwritten and lost
534 # if different: old untracked f may be overwritten and lost
534 if acceptremote:
535 if acceptremote:
535 actions.append((f, "g", (m2.flags(f),),
536 actions.append((f, "g", (m2.flags(f),),
536 "remote recreating"))
537 "remote recreating"))
537 else:
538 else:
538 actions.append((f, "dc", (m2.flags(f),),
539 actions.append((f, "dc", (m2.flags(f),),
539 "prompt deleted/changed"))
540 "prompt deleted/changed"))
540
541
541 for f, m in sorted(aborts):
542 for f, m in sorted(aborts):
542 if m == "ud":
543 if m == "ud":
543 repo.ui.warn(_("%s: untracked file differs\n") % f)
544 repo.ui.warn(_("%s: untracked file differs\n") % f)
544 else: assert False, m
545 else: assert False, m
545 if aborts:
546 if aborts:
546 raise util.Abort(_("untracked files in working directory differ "
547 raise util.Abort(_("untracked files in working directory differ "
547 "from files in requested revision"))
548 "from files in requested revision"))
548
549
549 if not util.checkcase(repo.path):
550 if not util.checkcase(repo.path):
550 # check collision between files only in p2 for clean update
551 # check collision between files only in p2 for clean update
551 if (not branchmerge and
552 if (not branchmerge and
552 (force or not wctx.dirty(missing=True, branch=False))):
553 (force or not wctx.dirty(missing=True, branch=False))):
553 _checkcollision(repo, m2, [])
554 _checkcollision(repo, m2, [])
554 else:
555 else:
555 _checkcollision(repo, m1, actions)
556 _checkcollision(repo, m1, actions)
556
557
557 return actions
558 return actions
558
559
559 def actionkey(a):
560 def actionkey(a):
560 return a[1] in "rf" and -1 or 0, a
561 return a[1] in "rf" and -1 or 0, a
561
562
562 def getremove(repo, mctx, overwrite, args):
563 def getremove(repo, mctx, overwrite, args):
563 """apply usually-non-interactive updates to the working directory
564 """apply usually-non-interactive updates to the working directory
564
565
565 mctx is the context to be merged into the working copy
566 mctx is the context to be merged into the working copy
566
567
567 yields tuples for progress updates
568 yields tuples for progress updates
568 """
569 """
569 verbose = repo.ui.verbose
570 verbose = repo.ui.verbose
570 unlink = util.unlinkpath
571 unlink = util.unlinkpath
571 wjoin = repo.wjoin
572 wjoin = repo.wjoin
572 fctx = mctx.filectx
573 fctx = mctx.filectx
573 wwrite = repo.wwrite
574 wwrite = repo.wwrite
574 audit = repo.wopener.audit
575 audit = repo.wopener.audit
575 i = 0
576 i = 0
576 for arg in args:
577 for arg in args:
577 f = arg[0]
578 f = arg[0]
578 if arg[1] == 'r':
579 if arg[1] == 'r':
579 if verbose:
580 if verbose:
580 repo.ui.note(_("removing %s\n") % f)
581 repo.ui.note(_("removing %s\n") % f)
581 audit(f)
582 audit(f)
582 try:
583 try:
583 unlink(wjoin(f), ignoremissing=True)
584 unlink(wjoin(f), ignoremissing=True)
584 except OSError, inst:
585 except OSError, inst:
585 repo.ui.warn(_("update failed to remove %s: %s!\n") %
586 repo.ui.warn(_("update failed to remove %s: %s!\n") %
586 (f, inst.strerror))
587 (f, inst.strerror))
587 else:
588 else:
588 if verbose:
589 if verbose:
589 repo.ui.note(_("getting %s\n") % f)
590 repo.ui.note(_("getting %s\n") % f)
590 wwrite(f, fctx(f).data(), arg[2][0])
591 wwrite(f, fctx(f).data(), arg[2][0])
591 if i == 100:
592 if i == 100:
592 yield i, f
593 yield i, f
593 i = 0
594 i = 0
594 i += 1
595 i += 1
595 if i > 0:
596 if i > 0:
596 yield i, f
597 yield i, f
597
598
598 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
599 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
599 """apply the merge action list to the working directory
600 """apply the merge action list to the working directory
600
601
601 wctx is the working copy context
602 wctx is the working copy context
602 mctx is the context to be merged into the working copy
603 mctx is the context to be merged into the working copy
603 actx is the context of the common ancestor
604 actx is the context of the common ancestor
604
605
605 Return a tuple of counts (updated, merged, removed, unresolved) that
606 Return a tuple of counts (updated, merged, removed, unresolved) that
606 describes how many files were affected by the update.
607 describes how many files were affected by the update.
607 """
608 """
608
609
609 updated, merged, removed, unresolved = 0, 0, 0, 0
610 updated, merged, removed, unresolved = 0, 0, 0, 0
610 ms = mergestate(repo)
611 ms = mergestate(repo)
611 ms.reset(wctx.p1().node(), mctx.node())
612 ms.reset(wctx.p1().node(), mctx.node())
612 moves = []
613 moves = []
613 actions.sort(key=actionkey)
614 actions.sort(key=actionkey)
614
615
615 # prescan for merges
616 # prescan for merges
616 for a in actions:
617 for a in actions:
617 f, m, args, msg = a
618 f, m, args, msg = a
618 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
619 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
619 if m == "m": # merge
620 if m == "m": # merge
620 f2, fd, move = args
621 f2, fa, fd, move = args
621 if fd == '.hgsubstate': # merged internally
622 if fd == '.hgsubstate': # merged internally
622 continue
623 continue
623 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))
624 fcl = wctx[f]
625 fcl = wctx[f]
625 fco = mctx[f2]
626 fco = mctx[f2]
626 if mctx == actx: # backwards, use working dir parent as ancestor
627 if mctx == actx: # backwards, use working dir parent as ancestor
627 if fcl.parents():
628 if fcl.parents():
628 fca = fcl.p1()
629 fca = fcl.p1()
629 else:
630 else:
630 fca = repo.filectx(f, fileid=nullrev)
631 fca = repo.filectx(f, fileid=nullrev)
632 elif fa in actx:
633 fca = actx[fa]
631 else:
634 else:
632 fca = fcl.ancestor(fco, actx)
633 if not fca:
634 fca = repo.filectx(f, fileid=nullrev)
635 fca = repo.filectx(f, fileid=nullrev)
635 ms.add(fcl, fco, fca, fd)
636 ms.add(fcl, fco, fca, fd)
636 if f != fd and move:
637 if f != fd and move:
637 moves.append(f)
638 moves.append(f)
638
639
639 audit = repo.wopener.audit
640 audit = repo.wopener.audit
640
641
641 # remove renamed files after safely stored
642 # remove renamed files after safely stored
642 for f in moves:
643 for f in moves:
643 if os.path.lexists(repo.wjoin(f)):
644 if os.path.lexists(repo.wjoin(f)):
644 repo.ui.debug("removing %s\n" % f)
645 repo.ui.debug("removing %s\n" % f)
645 audit(f)
646 audit(f)
646 util.unlinkpath(repo.wjoin(f))
647 util.unlinkpath(repo.wjoin(f))
647
648
648 numupdates = len(actions)
649 numupdates = len(actions)
649 workeractions = [a for a in actions if a[1] in 'gr']
650 workeractions = [a for a in actions if a[1] in 'gr']
650 updateactions = [a for a in workeractions if a[1] == 'g']
651 updateactions = [a for a in workeractions if a[1] == 'g']
651 updated = len(updateactions)
652 updated = len(updateactions)
652 removeactions = [a for a in workeractions if a[1] == 'r']
653 removeactions = [a for a in workeractions if a[1] == 'r']
653 removed = len(removeactions)
654 removed = len(removeactions)
654 actions = [a for a in actions if a[1] not in 'gr']
655 actions = [a for a in actions if a[1] not in 'gr']
655
656
656 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
657 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
657 if hgsub and hgsub[0] == 'r':
658 if hgsub and hgsub[0] == 'r':
658 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
659 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
659
660
660 z = 0
661 z = 0
661 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
662 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
662 removeactions)
663 removeactions)
663 for i, item in prog:
664 for i, item in prog:
664 z += i
665 z += i
665 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
666 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
666 unit=_('files'))
667 unit=_('files'))
667 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
668 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
668 updateactions)
669 updateactions)
669 for i, item in prog:
670 for i, item in prog:
670 z += i
671 z += i
671 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
672 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
672 unit=_('files'))
673 unit=_('files'))
673
674
674 if hgsub and hgsub[0] == 'g':
675 if hgsub and hgsub[0] == 'g':
675 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
676 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
676
677
677 _updating = _('updating')
678 _updating = _('updating')
678 _files = _('files')
679 _files = _('files')
679 progress = repo.ui.progress
680 progress = repo.ui.progress
680
681
681 for i, a in enumerate(actions):
682 for i, a in enumerate(actions):
682 f, m, args, msg = a
683 f, m, args, msg = a
683 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
684 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
684 if m == "m": # merge
685 if m == "m": # merge
685 f2, fd, move = args
686 f2, fa, fd, move = args
686 if fd == '.hgsubstate': # subrepo states need updating
687 if fd == '.hgsubstate': # subrepo states need updating
687 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
688 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
688 overwrite)
689 overwrite)
689 continue
690 continue
690 audit(fd)
691 audit(fd)
691 r = ms.resolve(fd, wctx)
692 r = ms.resolve(fd, wctx)
692 if r is not None and r > 0:
693 if r is not None and r > 0:
693 unresolved += 1
694 unresolved += 1
694 else:
695 else:
695 if r is None:
696 if r is None:
696 updated += 1
697 updated += 1
697 else:
698 else:
698 merged += 1
699 merged += 1
699 elif m == "d": # directory rename
700 elif m == "d": # directory rename
700 f2, fd, flags = args
701 f2, fd, flags = args
701 if f:
702 if f:
702 repo.ui.note(_("moving %s to %s\n") % (f, fd))
703 repo.ui.note(_("moving %s to %s\n") % (f, fd))
703 audit(fd)
704 audit(fd)
704 repo.wwrite(fd, wctx.filectx(f).data(), flags)
705 repo.wwrite(fd, wctx.filectx(f).data(), flags)
705 util.unlinkpath(repo.wjoin(f))
706 util.unlinkpath(repo.wjoin(f))
706 if f2:
707 if f2:
707 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
708 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
708 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
709 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
709 updated += 1
710 updated += 1
710 elif m == "dr": # divergent renames
711 elif m == "dr": # divergent renames
711 fl, = args
712 fl, = args
712 repo.ui.warn(_("note: possible conflict - %s was renamed "
713 repo.ui.warn(_("note: possible conflict - %s was renamed "
713 "multiple times to:\n") % f)
714 "multiple times to:\n") % f)
714 for nf in fl:
715 for nf in fl:
715 repo.ui.warn(" %s\n" % nf)
716 repo.ui.warn(" %s\n" % nf)
716 elif m == "rd": # rename and delete
717 elif m == "rd": # rename and delete
717 fl, = args
718 fl, = args
718 repo.ui.warn(_("note: possible conflict - %s was deleted "
719 repo.ui.warn(_("note: possible conflict - %s was deleted "
719 "and renamed to:\n") % f)
720 "and renamed to:\n") % f)
720 for nf in fl:
721 for nf in fl:
721 repo.ui.warn(" %s\n" % nf)
722 repo.ui.warn(" %s\n" % nf)
722 elif m == "e": # exec
723 elif m == "e": # exec
723 flags, = args
724 flags, = args
724 audit(f)
725 audit(f)
725 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
726 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
726 updated += 1
727 updated += 1
727 ms.commit()
728 ms.commit()
728 progress(_updating, None, total=numupdates, unit=_files)
729 progress(_updating, None, total=numupdates, unit=_files)
729
730
730 return updated, merged, removed, unresolved
731 return updated, merged, removed, unresolved
731
732
732 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
733 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
733 acceptremote=False):
734 acceptremote=False):
734 "Calculate the actions needed to merge mctx into tctx"
735 "Calculate the actions needed to merge mctx into tctx"
735 actions = []
736 actions = []
736 actions += manifestmerge(repo, tctx, mctx,
737 actions += manifestmerge(repo, tctx, mctx,
737 ancestor,
738 ancestor,
738 branchmerge, force,
739 branchmerge, force,
739 partial, acceptremote)
740 partial, acceptremote)
740
741
741 # Filter out prompts.
742 # Filter out prompts.
742 newactions, prompts = [], []
743 newactions, prompts = [], []
743 for a in actions:
744 for a in actions:
744 if a[1] in ("cd", "dc"):
745 if a[1] in ("cd", "dc"):
745 prompts.append(a)
746 prompts.append(a)
746 else:
747 else:
747 newactions.append(a)
748 newactions.append(a)
748 # Prompt and create actions. TODO: Move this towards resolve phase.
749 # Prompt and create actions. TODO: Move this towards resolve phase.
749 for f, m, args, msg in sorted(prompts):
750 for f, m, args, msg in sorted(prompts):
750 if m == "cd":
751 if m == "cd":
751 if repo.ui.promptchoice(
752 if repo.ui.promptchoice(
752 _("local changed %s which remote deleted\n"
753 _("local changed %s which remote deleted\n"
753 "use (c)hanged version or (d)elete?"
754 "use (c)hanged version or (d)elete?"
754 "$$ &Changed $$ &Delete") % f, 0):
755 "$$ &Changed $$ &Delete") % f, 0):
755 newactions.append((f, "r", None, "prompt delete"))
756 newactions.append((f, "r", None, "prompt delete"))
756 else:
757 else:
757 newactions.append((f, "a", None, "prompt keep"))
758 newactions.append((f, "a", None, "prompt keep"))
758 elif m == "dc":
759 elif m == "dc":
759 flags, = args
760 flags, = args
760 if repo.ui.promptchoice(
761 if repo.ui.promptchoice(
761 _("remote changed %s which local deleted\n"
762 _("remote changed %s which local deleted\n"
762 "use (c)hanged version or leave (d)eleted?"
763 "use (c)hanged version or leave (d)eleted?"
763 "$$ &Changed $$ &Deleted") % f, 0) == 0:
764 "$$ &Changed $$ &Deleted") % f, 0) == 0:
764 newactions.append((f, "g", (flags,), "prompt recreating"))
765 newactions.append((f, "g", (flags,), "prompt recreating"))
765 else: assert False, m
766 else: assert False, m
766
767
767 if tctx.rev() is None:
768 if tctx.rev() is None:
768 newactions += _forgetremoved(tctx, mctx, branchmerge)
769 newactions += _forgetremoved(tctx, mctx, branchmerge)
769
770
770 return newactions
771 return newactions
771
772
772 def recordupdates(repo, actions, branchmerge):
773 def recordupdates(repo, actions, branchmerge):
773 "record merge actions to the dirstate"
774 "record merge actions to the dirstate"
774
775
775 for a in actions:
776 for a in actions:
776 f, m, args, msg = a
777 f, m, args, msg = a
777 if m == "r": # remove
778 if m == "r": # remove
778 if branchmerge:
779 if branchmerge:
779 repo.dirstate.remove(f)
780 repo.dirstate.remove(f)
780 else:
781 else:
781 repo.dirstate.drop(f)
782 repo.dirstate.drop(f)
782 elif m == "a": # re-add
783 elif m == "a": # re-add
783 if not branchmerge:
784 if not branchmerge:
784 repo.dirstate.add(f)
785 repo.dirstate.add(f)
785 elif m == "f": # forget
786 elif m == "f": # forget
786 repo.dirstate.drop(f)
787 repo.dirstate.drop(f)
787 elif m == "e": # exec change
788 elif m == "e": # exec change
788 repo.dirstate.normallookup(f)
789 repo.dirstate.normallookup(f)
789 elif m == "g": # get
790 elif m == "g": # get
790 if branchmerge:
791 if branchmerge:
791 repo.dirstate.otherparent(f)
792 repo.dirstate.otherparent(f)
792 else:
793 else:
793 repo.dirstate.normal(f)
794 repo.dirstate.normal(f)
794 elif m == "m": # merge
795 elif m == "m": # merge
795 f2, fd, move = args
796 f2, fa, fd, move = args
796 if branchmerge:
797 if branchmerge:
797 # We've done a branch merge, mark this file as merged
798 # We've done a branch merge, mark this file as merged
798 # so that we properly record the merger later
799 # so that we properly record the merger later
799 repo.dirstate.merge(fd)
800 repo.dirstate.merge(fd)
800 if f != f2: # copy/rename
801 if f != f2: # copy/rename
801 if move:
802 if move:
802 repo.dirstate.remove(f)
803 repo.dirstate.remove(f)
803 if f != fd:
804 if f != fd:
804 repo.dirstate.copy(f, fd)
805 repo.dirstate.copy(f, fd)
805 else:
806 else:
806 repo.dirstate.copy(f2, fd)
807 repo.dirstate.copy(f2, fd)
807 else:
808 else:
808 # We've update-merged a locally modified file, so
809 # We've update-merged a locally modified file, so
809 # we set the dirstate to emulate a normal checkout
810 # we set the dirstate to emulate a normal checkout
810 # of that file some time in the past. Thus our
811 # of that file some time in the past. Thus our
811 # merge will appear as a normal local file
812 # merge will appear as a normal local file
812 # modification.
813 # modification.
813 if f2 == fd: # file not locally copied/moved
814 if f2 == fd: # file not locally copied/moved
814 repo.dirstate.normallookup(fd)
815 repo.dirstate.normallookup(fd)
815 if move:
816 if move:
816 repo.dirstate.drop(f)
817 repo.dirstate.drop(f)
817 elif m == "d": # directory rename
818 elif m == "d": # directory rename
818 f2, fd, flag = args
819 f2, fd, flag = args
819 if not f2 and f not in repo.dirstate:
820 if not f2 and f not in repo.dirstate:
820 # untracked file moved
821 # untracked file moved
821 continue
822 continue
822 if branchmerge:
823 if branchmerge:
823 repo.dirstate.add(fd)
824 repo.dirstate.add(fd)
824 if f:
825 if f:
825 repo.dirstate.remove(f)
826 repo.dirstate.remove(f)
826 repo.dirstate.copy(f, fd)
827 repo.dirstate.copy(f, fd)
827 if f2:
828 if f2:
828 repo.dirstate.copy(f2, fd)
829 repo.dirstate.copy(f2, fd)
829 else:
830 else:
830 repo.dirstate.normal(fd)
831 repo.dirstate.normal(fd)
831 if f:
832 if f:
832 repo.dirstate.drop(f)
833 repo.dirstate.drop(f)
833
834
834 def update(repo, node, branchmerge, force, partial, ancestor=None,
835 def update(repo, node, branchmerge, force, partial, ancestor=None,
835 mergeancestor=False):
836 mergeancestor=False):
836 """
837 """
837 Perform a merge between the working directory and the given node
838 Perform a merge between the working directory and the given node
838
839
839 node = the node to update to, or None if unspecified
840 node = the node to update to, or None if unspecified
840 branchmerge = whether to merge between branches
841 branchmerge = whether to merge between branches
841 force = whether to force branch merging or file overwriting
842 force = whether to force branch merging or file overwriting
842 partial = a function to filter file lists (dirstate not updated)
843 partial = a function to filter file lists (dirstate not updated)
843 mergeancestor = whether it is merging with an ancestor. If true,
844 mergeancestor = whether it is merging with an ancestor. If true,
844 we should accept the incoming changes for any prompts that occur.
845 we should accept the incoming changes for any prompts that occur.
845 If false, merging with an ancestor (fast-forward) is only allowed
846 If false, merging with an ancestor (fast-forward) is only allowed
846 between different named branches. This flag is used by rebase extension
847 between different named branches. This flag is used by rebase extension
847 as a temporary fix and should be avoided in general.
848 as a temporary fix and should be avoided in general.
848
849
849 The table below shows all the behaviors of the update command
850 The table below shows all the behaviors of the update command
850 given the -c and -C or no options, whether the working directory
851 given the -c and -C or no options, whether the working directory
851 is dirty, whether a revision is specified, and the relationship of
852 is dirty, whether a revision is specified, and the relationship of
852 the parent rev to the target rev (linear, on the same named
853 the parent rev to the target rev (linear, on the same named
853 branch, or on another named branch).
854 branch, or on another named branch).
854
855
855 This logic is tested by test-update-branches.t.
856 This logic is tested by test-update-branches.t.
856
857
857 -c -C dirty rev | linear same cross
858 -c -C dirty rev | linear same cross
858 n n n n | ok (1) x
859 n n n n | ok (1) x
859 n n n y | ok ok ok
860 n n n y | ok ok ok
860 n n y n | merge (2) (2)
861 n n y n | merge (2) (2)
861 n n y y | merge (3) (3)
862 n n y y | merge (3) (3)
862 n y * * | --- discard ---
863 n y * * | --- discard ---
863 y n y * | --- (4) ---
864 y n y * | --- (4) ---
864 y n n * | --- ok ---
865 y n n * | --- ok ---
865 y y * * | --- (5) ---
866 y y * * | --- (5) ---
866
867
867 x = can't happen
868 x = can't happen
868 * = don't-care
869 * = don't-care
869 1 = abort: not a linear update (merge or update --check to force update)
870 1 = abort: not a linear update (merge or update --check to force update)
870 2 = abort: uncommitted changes (commit and merge, or update --clean to
871 2 = abort: uncommitted changes (commit and merge, or update --clean to
871 discard changes)
872 discard changes)
872 3 = abort: uncommitted changes (commit or update --clean to discard changes)
873 3 = abort: uncommitted changes (commit or update --clean to discard changes)
873 4 = abort: uncommitted changes (checked in commands.py)
874 4 = abort: uncommitted changes (checked in commands.py)
874 5 = incompatible options (checked in commands.py)
875 5 = incompatible options (checked in commands.py)
875
876
876 Return the same tuple as applyupdates().
877 Return the same tuple as applyupdates().
877 """
878 """
878
879
879 onode = node
880 onode = node
880 wlock = repo.wlock()
881 wlock = repo.wlock()
881 try:
882 try:
882 wc = repo[None]
883 wc = repo[None]
883 pl = wc.parents()
884 pl = wc.parents()
884 p1 = pl[0]
885 p1 = pl[0]
885 pa = None
886 pa = None
886 if ancestor:
887 if ancestor:
887 pa = repo[ancestor]
888 pa = repo[ancestor]
888
889
889 if node is None:
890 if node is None:
890 # Here is where we should consider bookmarks, divergent bookmarks,
891 # Here is where we should consider bookmarks, divergent bookmarks,
891 # foreground changesets (successors), and tip of current branch;
892 # foreground changesets (successors), and tip of current branch;
892 # but currently we are only checking the branch tips.
893 # but currently we are only checking the branch tips.
893 try:
894 try:
894 node = repo.branchtip(wc.branch())
895 node = repo.branchtip(wc.branch())
895 except error.RepoLookupError:
896 except error.RepoLookupError:
896 if wc.branch() == "default": # no default branch!
897 if wc.branch() == "default": # no default branch!
897 node = repo.lookup("tip") # update to tip
898 node = repo.lookup("tip") # update to tip
898 else:
899 else:
899 raise util.Abort(_("branch %s not found") % wc.branch())
900 raise util.Abort(_("branch %s not found") % wc.branch())
900
901
901 if p1.obsolete() and not p1.children():
902 if p1.obsolete() and not p1.children():
902 # allow updating to successors
903 # allow updating to successors
903 successors = obsolete.successorssets(repo, p1.node())
904 successors = obsolete.successorssets(repo, p1.node())
904
905
905 # behavior of certain cases is as follows,
906 # behavior of certain cases is as follows,
906 #
907 #
907 # divergent changesets: update to highest rev, similar to what
908 # divergent changesets: update to highest rev, similar to what
908 # is currently done when there are more than one head
909 # is currently done when there are more than one head
909 # (i.e. 'tip')
910 # (i.e. 'tip')
910 #
911 #
911 # replaced changesets: same as divergent except we know there
912 # replaced changesets: same as divergent except we know there
912 # is no conflict
913 # is no conflict
913 #
914 #
914 # pruned changeset: no update is done; though, we could
915 # pruned changeset: no update is done; though, we could
915 # consider updating to the first non-obsolete parent,
916 # consider updating to the first non-obsolete parent,
916 # similar to what is current done for 'hg prune'
917 # similar to what is current done for 'hg prune'
917
918
918 if successors:
919 if successors:
919 # flatten the list here handles both divergent (len > 1)
920 # flatten the list here handles both divergent (len > 1)
920 # and the usual case (len = 1)
921 # and the usual case (len = 1)
921 successors = [n for sub in successors for n in sub]
922 successors = [n for sub in successors for n in sub]
922
923
923 # get the max revision for the given successors set,
924 # get the max revision for the given successors set,
924 # i.e. the 'tip' of a set
925 # i.e. the 'tip' of a set
925 node = repo.revs("max(%ln)", successors)[0]
926 node = repo.revs("max(%ln)", successors)[0]
926 pa = p1
927 pa = p1
927
928
928 overwrite = force and not branchmerge
929 overwrite = force and not branchmerge
929
930
930 p2 = repo[node]
931 p2 = repo[node]
931 if pa is None:
932 if pa is None:
932 pa = p1.ancestor(p2)
933 pa = p1.ancestor(p2)
933
934
934 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
935 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
935
936
936 ### check phase
937 ### check phase
937 if not overwrite and len(pl) > 1:
938 if not overwrite and len(pl) > 1:
938 raise util.Abort(_("outstanding uncommitted merges"))
939 raise util.Abort(_("outstanding uncommitted merges"))
939 if branchmerge:
940 if branchmerge:
940 if pa == p2:
941 if pa == p2:
941 raise util.Abort(_("merging with a working directory ancestor"
942 raise util.Abort(_("merging with a working directory ancestor"
942 " has no effect"))
943 " has no effect"))
943 elif pa == p1:
944 elif pa == p1:
944 if not mergeancestor and p1.branch() == p2.branch():
945 if not mergeancestor and p1.branch() == p2.branch():
945 raise util.Abort(_("nothing to merge"),
946 raise util.Abort(_("nothing to merge"),
946 hint=_("use 'hg update' "
947 hint=_("use 'hg update' "
947 "or check 'hg heads'"))
948 "or check 'hg heads'"))
948 if not force and (wc.files() or wc.deleted()):
949 if not force and (wc.files() or wc.deleted()):
949 raise util.Abort(_("uncommitted changes"),
950 raise util.Abort(_("uncommitted changes"),
950 hint=_("use 'hg status' to list changes"))
951 hint=_("use 'hg status' to list changes"))
951 for s in sorted(wc.substate):
952 for s in sorted(wc.substate):
952 if wc.sub(s).dirty():
953 if wc.sub(s).dirty():
953 raise util.Abort(_("uncommitted changes in "
954 raise util.Abort(_("uncommitted changes in "
954 "subrepository '%s'") % s)
955 "subrepository '%s'") % s)
955
956
956 elif not overwrite:
957 elif not overwrite:
957 if p1 == p2: # no-op update
958 if p1 == p2: # no-op update
958 # call the hooks and exit early
959 # call the hooks and exit early
959 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
960 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
960 repo.hook('update', parent1=xp2, parent2='', error=0)
961 repo.hook('update', parent1=xp2, parent2='', error=0)
961 return 0, 0, 0, 0
962 return 0, 0, 0, 0
962
963
963 if pa not in (p1, p2): # nonlinear
964 if pa not in (p1, p2): # nonlinear
964 dirty = wc.dirty(missing=True)
965 dirty = wc.dirty(missing=True)
965 if dirty or onode is None:
966 if dirty or onode is None:
966 # Branching is a bit strange to ensure we do the minimal
967 # Branching is a bit strange to ensure we do the minimal
967 # amount of call to obsolete.background.
968 # amount of call to obsolete.background.
968 foreground = obsolete.foreground(repo, [p1.node()])
969 foreground = obsolete.foreground(repo, [p1.node()])
969 # note: the <node> variable contains a random identifier
970 # note: the <node> variable contains a random identifier
970 if repo[node].node() in foreground:
971 if repo[node].node() in foreground:
971 pa = p1 # allow updating to successors
972 pa = p1 # allow updating to successors
972 elif dirty:
973 elif dirty:
973 msg = _("uncommitted changes")
974 msg = _("uncommitted changes")
974 if onode is None:
975 if onode is None:
975 hint = _("commit and merge, or update --clean to"
976 hint = _("commit and merge, or update --clean to"
976 " discard changes")
977 " discard changes")
977 else:
978 else:
978 hint = _("commit or update --clean to discard"
979 hint = _("commit or update --clean to discard"
979 " changes")
980 " changes")
980 raise util.Abort(msg, hint=hint)
981 raise util.Abort(msg, hint=hint)
981 else: # node is none
982 else: # node is none
982 msg = _("not a linear update")
983 msg = _("not a linear update")
983 hint = _("merge or update --check to force update")
984 hint = _("merge or update --check to force update")
984 raise util.Abort(msg, hint=hint)
985 raise util.Abort(msg, hint=hint)
985 else:
986 else:
986 # Allow jumping branches if clean and specific rev given
987 # Allow jumping branches if clean and specific rev given
987 pa = p1
988 pa = p1
988
989
989 ### calculate phase
990 ### calculate phase
990 actions = calculateupdates(repo, wc, p2, pa,
991 actions = calculateupdates(repo, wc, p2, pa,
991 branchmerge, force, partial, mergeancestor)
992 branchmerge, force, partial, mergeancestor)
992
993
993 ### apply phase
994 ### apply phase
994 if not branchmerge: # just jump to the new rev
995 if not branchmerge: # just jump to the new rev
995 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
996 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
996 if not partial:
997 if not partial:
997 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
998 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
998 # note that we're in the middle of an update
999 # note that we're in the middle of an update
999 repo.vfs.write('updatestate', p2.hex())
1000 repo.vfs.write('updatestate', p2.hex())
1000
1001
1001 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
1002 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
1002
1003
1003 if not partial:
1004 if not partial:
1004 repo.setparents(fp1, fp2)
1005 repo.setparents(fp1, fp2)
1005 recordupdates(repo, actions, branchmerge)
1006 recordupdates(repo, actions, branchmerge)
1006 # update completed, clear state
1007 # update completed, clear state
1007 util.unlink(repo.join('updatestate'))
1008 util.unlink(repo.join('updatestate'))
1008
1009
1009 if not branchmerge:
1010 if not branchmerge:
1010 repo.dirstate.setbranch(p2.branch())
1011 repo.dirstate.setbranch(p2.branch())
1011 finally:
1012 finally:
1012 wlock.release()
1013 wlock.release()
1013
1014
1014 if not partial:
1015 if not partial:
1015 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1016 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1016 return stats
1017 return stats
@@ -1,376 +1,378
1 $ "$TESTDIR/hghave" symlink execbit || exit 80
1 $ "$TESTDIR/hghave" symlink execbit || exit 80
2
2
3 $ tellmeabout() {
3 $ tellmeabout() {
4 > if [ -h $1 ]; then
4 > if [ -h $1 ]; then
5 > echo $1 is a symlink:
5 > echo $1 is a symlink:
6 > $TESTDIR/readlink.py $1
6 > $TESTDIR/readlink.py $1
7 > elif [ -x $1 ]; then
7 > elif [ -x $1 ]; then
8 > echo $1 is an executable file with content:
8 > echo $1 is an executable file with content:
9 > cat $1
9 > cat $1
10 > else
10 > else
11 > echo $1 is a plain file with content:
11 > echo $1 is a plain file with content:
12 > cat $1
12 > cat $1
13 > fi
13 > fi
14 > }
14 > }
15
15
16 $ hg init test1
16 $ hg init test1
17 $ cd test1
17 $ cd test1
18
18
19 $ echo a > a
19 $ echo a > a
20 $ hg ci -Aqmadd
20 $ hg ci -Aqmadd
21 $ chmod +x a
21 $ chmod +x a
22 $ hg ci -mexecutable
22 $ hg ci -mexecutable
23
23
24 $ hg up -q 0
24 $ hg up -q 0
25 $ rm a
25 $ rm a
26 $ ln -s symlink a
26 $ ln -s symlink a
27 $ hg ci -msymlink
27 $ hg ci -msymlink
28 created new head
28 created new head
29
29
30 Symlink is local parent, executable is other:
30 Symlink is local parent, executable is other:
31
31
32 $ hg merge --debug
32 $ hg merge --debug
33 searching for copies back to rev 1
33 searching for copies back to rev 1
34 resolving manifests
34 resolving manifests
35 branchmerge: True, force: False, partial: False
35 branchmerge: True, force: False, partial: False
36 ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c
36 ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c
37 a: versions differ -> m
37 a: versions differ -> m
38 preserving a for resolve of a
38 preserving a for resolve of a
39 updating: a 1/1 files (100.00%)
39 updating: a 1/1 files (100.00%)
40 picked tool 'internal:merge' for a (binary False symlink True)
40 picked tool 'internal:merge' for a (binary False symlink True)
41 merging a
41 merging a
42 my a@521a1e40188f+ other a@3574f3e69b1c ancestor a@c334dc3be0da
42 my a@521a1e40188f+ other a@3574f3e69b1c ancestor a@c334dc3be0da
43 warning: internal:merge cannot merge symlinks for a
43 warning: internal:merge cannot merge symlinks for a
44 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
44 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
45 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
45 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
46 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
46 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
47 [1]
47 [1]
48
48
49 $ tellmeabout a
49 $ tellmeabout a
50 a is a symlink:
50 a is a symlink:
51 a -> symlink
51 a -> symlink
52 $ hg resolve a --tool internal:other
52 $ hg resolve a --tool internal:other
53 $ tellmeabout a
53 $ tellmeabout a
54 a is an executable file with content:
54 a is an executable file with content:
55 a
55 a
56 $ hg st
56 $ hg st
57 M a
57 M a
58 ? a.orig
58 ? a.orig
59
59
60 Symlink is other parent, executable is local:
60 Symlink is other parent, executable is local:
61
61
62 $ hg update -C 1
62 $ hg update -C 1
63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64
64
65 $ hg merge --debug
65 $ hg merge --debug
66 searching for copies back to rev 1
66 searching for copies back to rev 1
67 resolving manifests
67 resolving manifests
68 branchmerge: True, force: False, partial: False
68 branchmerge: True, force: False, partial: False
69 ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
69 ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
70 a: versions differ -> m
70 a: versions differ -> m
71 preserving a for resolve of a
71 preserving a for resolve of a
72 updating: a 1/1 files (100.00%)
72 updating: a 1/1 files (100.00%)
73 picked tool 'internal:merge' for a (binary False symlink True)
73 picked tool 'internal:merge' for a (binary False symlink True)
74 merging a
74 merging a
75 my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
75 my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
76 warning: internal:merge cannot merge symlinks for a
76 warning: internal:merge cannot merge symlinks for a
77 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
77 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
78 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
78 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
79 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
79 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
80 [1]
80 [1]
81
81
82 $ tellmeabout a
82 $ tellmeabout a
83 a is an executable file with content:
83 a is an executable file with content:
84 a
84 a
85
85
86 Update to link without local change should get us a symlink (issue3316):
86 Update to link without local change should get us a symlink (issue3316):
87
87
88 $ hg up -C 0
88 $ hg up -C 0
89 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 $ hg up
90 $ hg up
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 $ hg st
92 $ hg st
93 ? a.orig
93 ? a.orig
94
94
95 Update to link with local change should cause a merge prompt (issue3200):
95 Update to link with local change should cause a merge prompt (issue3200):
96
96
97 $ hg up -Cq 0
97 $ hg up -Cq 0
98 $ echo data > a
98 $ echo data > a
99 $ HGMERGE= hg up -y --debug
99 $ HGMERGE= hg up -y --debug
100 searching for copies back to rev 2
100 searching for copies back to rev 2
101 resolving manifests
101 resolving manifests
102 branchmerge: False, force: False, partial: False
102 branchmerge: False, force: False, partial: False
103 ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f
103 ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f
104 a: versions differ -> m
104 a: versions differ -> m
105 preserving a for resolve of a
105 preserving a for resolve of a
106 updating: a 1/1 files (100.00%)
106 updating: a 1/1 files (100.00%)
107 (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
107 (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
108 picked tool 'internal:prompt' for a (binary False symlink True)
108 picked tool 'internal:prompt' for a (binary False symlink True)
109 no tool found to merge a
109 no tool found to merge a
110 keep (l)ocal or take (o)ther? l
110 keep (l)ocal or take (o)ther? l
111 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
111 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
112 $ hg diff --git
112 $ hg diff --git
113 diff --git a/a b/a
113 diff --git a/a b/a
114 old mode 120000
114 old mode 120000
115 new mode 100644
115 new mode 100644
116 --- a/a
116 --- a/a
117 +++ b/a
117 +++ b/a
118 @@ -1,1 +1,1 @@
118 @@ -1,1 +1,1 @@
119 -symlink
119 -symlink
120 \ No newline at end of file
120 \ No newline at end of file
121 +data
121 +data
122
122
123
123
124 Test only 'l' change - happens rarely, except when recovering from situations
124 Test only 'l' change - happens rarely, except when recovering from situations
125 where that was what happened.
125 where that was what happened.
126
126
127 $ hg init test2
127 $ hg init test2
128 $ cd test2
128 $ cd test2
129 $ printf base > f
129 $ printf base > f
130 $ hg ci -Aqm0
130 $ hg ci -Aqm0
131 $ echo file > f
131 $ echo file > f
132 $ echo content >> f
132 $ echo content >> f
133 $ hg ci -qm1
133 $ hg ci -qm1
134 $ hg up -qr0
134 $ hg up -qr0
135 $ rm f
135 $ rm f
136 $ ln -s base f
136 $ ln -s base f
137 $ hg ci -qm2
137 $ hg ci -qm2
138 $ hg merge
138 $ hg merge
139 merging f
139 merging f
140 warning: internal:merge cannot merge symlinks for f
140 warning: internal:merge cannot merge symlinks for f
141 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
141 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
142 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
142 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
143 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
143 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
144 [1]
144 [1]
145 $ tellmeabout f
145 $ tellmeabout f
146 f is a symlink:
146 f is a symlink:
147 f -> base
147 f -> base
148
148
149 $ hg up -Cqr1
149 $ hg up -Cqr1
150 $ hg merge
150 $ hg merge
151 merging f
151 merging f
152 warning: internal:merge cannot merge symlinks for f
152 warning: internal:merge cannot merge symlinks for f
153 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
153 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
154 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
154 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
155 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
155 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
156 [1]
156 [1]
157 $ tellmeabout f
157 $ tellmeabout f
158 f is a plain file with content:
158 f is a plain file with content:
159 file
159 file
160 content
160 content
161
161
162 $ cd ..
162 $ cd ..
163
163
164 Test removed 'x' flag merged with change to symlink
164 Test removed 'x' flag merged with change to symlink
165
165
166 $ hg init test3
166 $ hg init test3
167 $ cd test3
167 $ cd test3
168 $ echo f > f
168 $ echo f > f
169 $ chmod +x f
169 $ chmod +x f
170 $ hg ci -Aqm0
170 $ hg ci -Aqm0
171 $ chmod -x f
171 $ chmod -x f
172 $ hg ci -qm1
172 $ hg ci -qm1
173 $ hg up -qr0
173 $ hg up -qr0
174 $ rm f
174 $ rm f
175 $ ln -s dangling f
175 $ ln -s dangling f
176 $ hg ci -qm2
176 $ hg ci -qm2
177 $ hg merge
177 $ hg merge
178 merging f
178 merging f
179 warning: internal:merge cannot merge symlinks for f
179 warning: internal:merge cannot merge symlinks for f
180 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
180 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
181 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
181 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
182 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
182 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
183 [1]
183 [1]
184 $ tellmeabout f
184 $ tellmeabout f
185 f is a symlink:
185 f is a symlink:
186 f -> dangling
186 f -> dangling
187
187
188 $ hg up -Cqr1
188 $ hg up -Cqr1
189 $ hg merge
189 $ hg merge
190 merging f
190 merging f
191 warning: internal:merge cannot merge symlinks for f
191 warning: internal:merge cannot merge symlinks for f
192 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
192 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
193 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
193 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
194 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
194 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
195 [1]
195 [1]
196 $ tellmeabout f
196 $ tellmeabout f
197 f is a plain file with content:
197 f is a plain file with content:
198 f
198 f
199
199
200 Test removed 'x' flag merged with content change - both ways
200 Test removed 'x' flag merged with content change - both ways
201
201
202 $ hg up -Cqr0
202 $ hg up -Cqr0
203 $ echo change > f
203 $ echo change > f
204 $ hg ci -qm3
204 $ hg ci -qm3
205 $ hg merge -r1
205 $ hg merge -r1
206 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 (branch merge, don't forget to commit)
207 (branch merge, don't forget to commit)
208 $ tellmeabout f
208 $ tellmeabout f
209 f is a plain file with content:
209 f is a plain file with content:
210 change
210 change
211
211
212 $ hg up -qCr1
212 $ hg up -qCr1
213 $ hg merge -r3
213 $ hg merge -r3
214 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
215 (branch merge, don't forget to commit)
215 (branch merge, don't forget to commit)
216 $ tellmeabout f
216 $ tellmeabout f
217 f is a plain file with content:
217 f is a plain file with content:
218 change
218 change
219
219
220 $ cd ..
220 $ cd ..
221
221
222 Test merge with no common ancestor:
222 Test merge with no common ancestor:
223 a: just different
223 a: just different
224 b: x vs -, different (cannot calculate x, cannot ask merge tool)
224 b: x vs -, different (cannot calculate x, cannot ask merge tool)
225 c: x vs -, same (cannot calculate x, merge tool is no good)
225 c: x vs -, same (cannot calculate x, merge tool is no good)
226 d: x vs l, different
226 d: x vs l, different
227 e: x vs l, same
227 e: x vs l, same
228 f: - vs l, different
228 f: - vs l, different
229 g: - vs l, same
229 g: - vs l, same
230 h: l vs l, different
230 h: l vs l, different
231 (where same means the filelog entry is shared and there thus is an ancestor!)
231 (where same means the filelog entry is shared and there thus is an ancestor!)
232
232
233 $ hg init test4
233 $ hg init test4
234 $ cd test4
234 $ cd test4
235 $ echo 0 > 0
235 $ echo 0 > 0
236 $ hg ci -Aqm0
236 $ hg ci -Aqm0
237
237
238 $ echo 1 > a
238 $ echo 1 > a
239 $ echo 1 > b
239 $ echo 1 > b
240 $ chmod +x b
240 $ chmod +x b
241 $ echo x > c
241 $ echo x > c
242 $ chmod +x c
242 $ chmod +x c
243 $ echo 1 > d
243 $ echo 1 > d
244 $ chmod +x d
244 $ chmod +x d
245 $ printf x > e
245 $ printf x > e
246 $ chmod +x e
246 $ chmod +x e
247 $ echo 1 > f
247 $ echo 1 > f
248 $ printf x > g
248 $ printf x > g
249 $ ln -s 1 h
249 $ ln -s 1 h
250 $ hg ci -qAm1
250 $ hg ci -qAm1
251
251
252 $ hg up -qr0
252 $ hg up -qr0
253 $ echo 2 > a
253 $ echo 2 > a
254 $ echo 2 > b
254 $ echo 2 > b
255 $ echo x > c
255 $ echo x > c
256 $ ln -s 2 d
256 $ ln -s 2 d
257 $ ln -s x e
257 $ ln -s x e
258 $ ln -s 2 f
258 $ ln -s 2 f
259 $ ln -s x g
259 $ ln -s x g
260 $ ln -s 2 h
260 $ ln -s 2 h
261 $ hg ci -Aqm2
261 $ hg ci -Aqm2
262
262
263 $ hg merge
263 $ hg merge
264 merging a
264 merging a
265 warning: conflicts during merge.
265 warning: conflicts during merge.
266 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
266 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
267 warning: cannot merge flags for b
267 warning: cannot merge flags for b
268 merging b
268 merging b
269 warning: conflicts during merge.
269 warning: conflicts during merge.
270 merging b incomplete! (edit conflicts, then use 'hg resolve --mark')
270 merging b incomplete! (edit conflicts, then use 'hg resolve --mark')
271 warning: cannot merge flags for c
271 merging d
272 merging d
272 warning: internal:merge cannot merge symlinks for d
273 warning: internal:merge cannot merge symlinks for d
273 merging d incomplete! (edit conflicts, then use 'hg resolve --mark')
274 merging d incomplete! (edit conflicts, then use 'hg resolve --mark')
274 merging f
275 merging f
275 warning: internal:merge cannot merge symlinks for f
276 warning: internal:merge cannot merge symlinks for f
276 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
277 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
277 merging h
278 merging h
278 warning: internal:merge cannot merge symlinks for h
279 warning: internal:merge cannot merge symlinks for h
279 merging h incomplete! (edit conflicts, then use 'hg resolve --mark')
280 merging h incomplete! (edit conflicts, then use 'hg resolve --mark')
280 3 files updated, 0 files merged, 0 files removed, 5 files unresolved
281 3 files updated, 0 files merged, 0 files removed, 5 files unresolved
281 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
282 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
282 [1]
283 [1]
283 $ hg resolve -l
284 $ hg resolve -l
284 U a
285 U a
285 U b
286 U b
286 U d
287 U d
287 U f
288 U f
288 U h
289 U h
289 $ tellmeabout a
290 $ tellmeabout a
290 a is a plain file with content:
291 a is a plain file with content:
291 <<<<<<< local
292 <<<<<<< local
292 2
293 2
293 =======
294 =======
294 1
295 1
295 >>>>>>> other
296 >>>>>>> other
296 $ tellmeabout b
297 $ tellmeabout b
297 b is a plain file with content:
298 b is a plain file with content:
298 <<<<<<< local
299 <<<<<<< local
299 2
300 2
300 =======
301 =======
301 1
302 1
302 >>>>>>> other
303 >>>>>>> other
303 $ tellmeabout c
304 $ tellmeabout c
304 c is a plain file with content:
305 c is a plain file with content:
305 x
306 x
306 $ tellmeabout d
307 $ tellmeabout d
307 d is a symlink:
308 d is a symlink:
308 d -> 2
309 d -> 2
309 $ tellmeabout e
310 $ tellmeabout e
310 e is a symlink:
311 e is a symlink:
311 e -> x
312 e -> x
312 $ tellmeabout f
313 $ tellmeabout f
313 f is a symlink:
314 f is a symlink:
314 f -> 2
315 f -> 2
315 $ tellmeabout g
316 $ tellmeabout g
316 g is a symlink:
317 g is a symlink:
317 g -> x
318 g -> x
318 $ tellmeabout h
319 $ tellmeabout h
319 h is a symlink:
320 h is a symlink:
320 h -> 2
321 h -> 2
321
322
322 $ hg up -Cqr1
323 $ hg up -Cqr1
323 $ hg merge
324 $ hg merge
324 merging a
325 merging a
325 warning: conflicts during merge.
326 warning: conflicts during merge.
326 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
327 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
327 warning: cannot merge flags for b
328 warning: cannot merge flags for b
328 merging b
329 merging b
329 warning: conflicts during merge.
330 warning: conflicts during merge.
330 merging b incomplete! (edit conflicts, then use 'hg resolve --mark')
331 merging b incomplete! (edit conflicts, then use 'hg resolve --mark')
332 warning: cannot merge flags for c
331 merging d
333 merging d
332 warning: internal:merge cannot merge symlinks for d
334 warning: internal:merge cannot merge symlinks for d
333 merging d incomplete! (edit conflicts, then use 'hg resolve --mark')
335 merging d incomplete! (edit conflicts, then use 'hg resolve --mark')
334 merging f
336 merging f
335 warning: internal:merge cannot merge symlinks for f
337 warning: internal:merge cannot merge symlinks for f
336 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
338 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
337 merging h
339 merging h
338 warning: internal:merge cannot merge symlinks for h
340 warning: internal:merge cannot merge symlinks for h
339 merging h incomplete! (edit conflicts, then use 'hg resolve --mark')
341 merging h incomplete! (edit conflicts, then use 'hg resolve --mark')
340 3 files updated, 0 files merged, 0 files removed, 5 files unresolved
342 3 files updated, 0 files merged, 0 files removed, 5 files unresolved
341 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
343 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
342 [1]
344 [1]
343 $ tellmeabout a
345 $ tellmeabout a
344 a is a plain file with content:
346 a is a plain file with content:
345 <<<<<<< local
347 <<<<<<< local
346 1
348 1
347 =======
349 =======
348 2
350 2
349 >>>>>>> other
351 >>>>>>> other
350 $ tellmeabout b
352 $ tellmeabout b
351 b is an executable file with content:
353 b is an executable file with content:
352 <<<<<<< local
354 <<<<<<< local
353 1
355 1
354 =======
356 =======
355 2
357 2
356 >>>>>>> other
358 >>>>>>> other
357 $ tellmeabout c
359 $ tellmeabout c
358 c is a plain file with content:
360 c is an executable file with content:
359 x
361 x
360 $ tellmeabout d
362 $ tellmeabout d
361 d is an executable file with content:
363 d is an executable file with content:
362 1
364 1
363 $ tellmeabout e
365 $ tellmeabout e
364 e is an executable file with content:
366 e is an executable file with content:
365 x (no-eol)
367 x (no-eol)
366 $ tellmeabout f
368 $ tellmeabout f
367 f is a plain file with content:
369 f is a plain file with content:
368 1
370 1
369 $ tellmeabout g
371 $ tellmeabout g
370 g is a plain file with content:
372 g is a plain file with content:
371 x (no-eol)
373 x (no-eol)
372 $ tellmeabout h
374 $ tellmeabout h
373 h is a symlink:
375 h is a symlink:
374 h -> 1
376 h -> 1
375
377
376 $ cd ..
378 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now