##// END OF EJS Templates
graft: allow creating sibling grafts...
Durham Goode -
r24643:a8e6897d default
parent child Browse files
Show More
@@ -1,1197 +1,1205
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 as errormod, util, filemerge, copies, subrepo, worker
13 import error as errormod, util, filemerge, copies, subrepo, worker
14 import errno, os, shutil
14 import errno, os, shutil
15
15
16 _pack = struct.pack
16 _pack = struct.pack
17 _unpack = struct.unpack
17 _unpack = struct.unpack
18
18
19 def _droponode(data):
19 def _droponode(data):
20 # used for compatibility for v1
20 # used for compatibility for v1
21 bits = data.split('\0')
21 bits = data.split('\0')
22 bits = bits[:-2] + bits[-1:]
22 bits = bits[:-2] + bits[-1:]
23 return '\0'.join(bits)
23 return '\0'.join(bits)
24
24
25 class mergestate(object):
25 class mergestate(object):
26 '''track 3-way merge state of individual files
26 '''track 3-way merge state of individual files
27
27
28 it is stored on disk when needed. Two file are used, one with an old
28 it is stored on disk when needed. Two file are used, one with an old
29 format, one with a new format. Both contains similar data, but the new
29 format, one with a new format. Both contains similar data, but the new
30 format can store new kind of field.
30 format can store new kind of field.
31
31
32 Current new format is a list of arbitrary record of the form:
32 Current new format is a list of arbitrary record of the form:
33
33
34 [type][length][content]
34 [type][length][content]
35
35
36 Type is a single character, length is a 4 bytes integer, content is an
36 Type is a single character, length is a 4 bytes integer, content is an
37 arbitrary suites of bytes of length `length`.
37 arbitrary suites of bytes of length `length`.
38
38
39 Type should be a letter. Capital letter are mandatory record, Mercurial
39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 should abort if they are unknown. lower case record can be safely ignored.
40 should abort if they are unknown. lower case record can be safely ignored.
41
41
42 Currently known record:
42 Currently known record:
43
43
44 L: the node of the "local" part of the merge (hexified version)
44 L: the node of the "local" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
46 F: a file to be merged entry
46 F: a file to be merged entry
47 '''
47 '''
48 statepathv1 = 'merge/state'
48 statepathv1 = 'merge/state'
49 statepathv2 = 'merge/state2'
49 statepathv2 = 'merge/state2'
50
50
51 def __init__(self, repo):
51 def __init__(self, repo):
52 self._repo = repo
52 self._repo = repo
53 self._dirty = False
53 self._dirty = False
54 self._read()
54 self._read()
55
55
56 def reset(self, node=None, other=None):
56 def reset(self, node=None, other=None):
57 self._state = {}
57 self._state = {}
58 self._local = None
58 self._local = None
59 self._other = None
59 self._other = None
60 if node:
60 if node:
61 self._local = node
61 self._local = node
62 self._other = other
62 self._other = other
63 shutil.rmtree(self._repo.join('merge'), True)
63 shutil.rmtree(self._repo.join('merge'), True)
64 self._dirty = False
64 self._dirty = False
65
65
66 def _read(self):
66 def _read(self):
67 """Analyse each record content to restore a serialized state from disk
67 """Analyse each record content to restore a serialized state from disk
68
68
69 This function process "record" entry produced by the de-serialization
69 This function process "record" entry produced by the de-serialization
70 of on disk file.
70 of on disk file.
71 """
71 """
72 self._state = {}
72 self._state = {}
73 self._local = None
73 self._local = None
74 self._other = None
74 self._other = None
75 records = self._readrecords()
75 records = self._readrecords()
76 for rtype, record in records:
76 for rtype, record in records:
77 if rtype == 'L':
77 if rtype == 'L':
78 self._local = bin(record)
78 self._local = bin(record)
79 elif rtype == 'O':
79 elif rtype == 'O':
80 self._other = bin(record)
80 self._other = bin(record)
81 elif rtype == 'F':
81 elif rtype == 'F':
82 bits = record.split('\0')
82 bits = record.split('\0')
83 self._state[bits[0]] = bits[1:]
83 self._state[bits[0]] = bits[1:]
84 elif not rtype.islower():
84 elif not rtype.islower():
85 raise util.Abort(_('unsupported merge state record: %s')
85 raise util.Abort(_('unsupported merge state record: %s')
86 % rtype)
86 % rtype)
87 self._dirty = False
87 self._dirty = False
88
88
89 def _readrecords(self):
89 def _readrecords(self):
90 """Read merge state from disk and return a list of record (TYPE, data)
90 """Read merge state from disk and return a list of record (TYPE, data)
91
91
92 We read data from both v1 and v2 files and decide which one to use.
92 We read data from both v1 and v2 files and decide which one to use.
93
93
94 V1 has been used by version prior to 2.9.1 and contains less data than
94 V1 has been used by version prior to 2.9.1 and contains less data than
95 v2. We read both versions and check if no data in v2 contradicts
95 v2. We read both versions and check if no data in v2 contradicts
96 v1. If there is not contradiction we can safely assume that both v1
96 v1. If there is not contradiction we can safely assume that both v1
97 and v2 were written at the same time and use the extract data in v2. If
97 and v2 were written at the same time and use the extract data in v2. If
98 there is contradiction we ignore v2 content as we assume an old version
98 there is contradiction we ignore v2 content as we assume an old version
99 of Mercurial has overwritten the mergestate file and left an old v2
99 of Mercurial has overwritten the mergestate file and left an old v2
100 file around.
100 file around.
101
101
102 returns list of record [(TYPE, data), ...]"""
102 returns list of record [(TYPE, data), ...]"""
103 v1records = self._readrecordsv1()
103 v1records = self._readrecordsv1()
104 v2records = self._readrecordsv2()
104 v2records = self._readrecordsv2()
105 oldv2 = set() # old format version of v2 record
105 oldv2 = set() # old format version of v2 record
106 for rec in v2records:
106 for rec in v2records:
107 if rec[0] == 'L':
107 if rec[0] == 'L':
108 oldv2.add(rec)
108 oldv2.add(rec)
109 elif rec[0] == 'F':
109 elif rec[0] == 'F':
110 # drop the onode data (not contained in v1)
110 # drop the onode data (not contained in v1)
111 oldv2.add(('F', _droponode(rec[1])))
111 oldv2.add(('F', _droponode(rec[1])))
112 for rec in v1records:
112 for rec in v1records:
113 if rec not in oldv2:
113 if rec not in oldv2:
114 # v1 file is newer than v2 file, use it
114 # v1 file is newer than v2 file, use it
115 # we have to infer the "other" changeset of the merge
115 # we have to infer the "other" changeset of the merge
116 # we cannot do better than that with v1 of the format
116 # we cannot do better than that with v1 of the format
117 mctx = self._repo[None].parents()[-1]
117 mctx = self._repo[None].parents()[-1]
118 v1records.append(('O', mctx.hex()))
118 v1records.append(('O', mctx.hex()))
119 # add place holder "other" file node information
119 # add place holder "other" file node information
120 # nobody is using it yet so we do no need to fetch the data
120 # nobody is using it yet so we do no need to fetch the data
121 # if mctx was wrong `mctx[bits[-2]]` may fails.
121 # if mctx was wrong `mctx[bits[-2]]` may fails.
122 for idx, r in enumerate(v1records):
122 for idx, r in enumerate(v1records):
123 if r[0] == 'F':
123 if r[0] == 'F':
124 bits = r[1].split('\0')
124 bits = r[1].split('\0')
125 bits.insert(-2, '')
125 bits.insert(-2, '')
126 v1records[idx] = (r[0], '\0'.join(bits))
126 v1records[idx] = (r[0], '\0'.join(bits))
127 return v1records
127 return v1records
128 else:
128 else:
129 return v2records
129 return v2records
130
130
131 def _readrecordsv1(self):
131 def _readrecordsv1(self):
132 """read on disk merge state for version 1 file
132 """read on disk merge state for version 1 file
133
133
134 returns list of record [(TYPE, data), ...]
134 returns list of record [(TYPE, data), ...]
135
135
136 Note: the "F" data from this file are one entry short
136 Note: the "F" data from this file are one entry short
137 (no "other file node" entry)
137 (no "other file node" entry)
138 """
138 """
139 records = []
139 records = []
140 try:
140 try:
141 f = self._repo.vfs(self.statepathv1)
141 f = self._repo.vfs(self.statepathv1)
142 for i, l in enumerate(f):
142 for i, l in enumerate(f):
143 if i == 0:
143 if i == 0:
144 records.append(('L', l[:-1]))
144 records.append(('L', l[:-1]))
145 else:
145 else:
146 records.append(('F', l[:-1]))
146 records.append(('F', l[:-1]))
147 f.close()
147 f.close()
148 except IOError, err:
148 except IOError, err:
149 if err.errno != errno.ENOENT:
149 if err.errno != errno.ENOENT:
150 raise
150 raise
151 return records
151 return records
152
152
153 def _readrecordsv2(self):
153 def _readrecordsv2(self):
154 """read on disk merge state for version 2 file
154 """read on disk merge state for version 2 file
155
155
156 returns list of record [(TYPE, data), ...]
156 returns list of record [(TYPE, data), ...]
157 """
157 """
158 records = []
158 records = []
159 try:
159 try:
160 f = self._repo.vfs(self.statepathv2)
160 f = self._repo.vfs(self.statepathv2)
161 data = f.read()
161 data = f.read()
162 off = 0
162 off = 0
163 end = len(data)
163 end = len(data)
164 while off < end:
164 while off < end:
165 rtype = data[off]
165 rtype = data[off]
166 off += 1
166 off += 1
167 length = _unpack('>I', data[off:(off + 4)])[0]
167 length = _unpack('>I', data[off:(off + 4)])[0]
168 off += 4
168 off += 4
169 record = data[off:(off + length)]
169 record = data[off:(off + length)]
170 off += length
170 off += length
171 records.append((rtype, record))
171 records.append((rtype, record))
172 f.close()
172 f.close()
173 except IOError, err:
173 except IOError, err:
174 if err.errno != errno.ENOENT:
174 if err.errno != errno.ENOENT:
175 raise
175 raise
176 return records
176 return records
177
177
178 def active(self):
178 def active(self):
179 """Whether mergestate is active.
179 """Whether mergestate is active.
180
180
181 Returns True if there appears to be mergestate. This is a rough proxy
181 Returns True if there appears to be mergestate. This is a rough proxy
182 for "is a merge in progress."
182 for "is a merge in progress."
183 """
183 """
184 # Check local variables before looking at filesystem for performance
184 # Check local variables before looking at filesystem for performance
185 # reasons.
185 # reasons.
186 return bool(self._local) or bool(self._state) or \
186 return bool(self._local) or bool(self._state) or \
187 self._repo.vfs.exists(self.statepathv1) or \
187 self._repo.vfs.exists(self.statepathv1) or \
188 self._repo.vfs.exists(self.statepathv2)
188 self._repo.vfs.exists(self.statepathv2)
189
189
190 def commit(self):
190 def commit(self):
191 """Write current state on disk (if necessary)"""
191 """Write current state on disk (if necessary)"""
192 if self._dirty:
192 if self._dirty:
193 records = []
193 records = []
194 records.append(('L', hex(self._local)))
194 records.append(('L', hex(self._local)))
195 records.append(('O', hex(self._other)))
195 records.append(('O', hex(self._other)))
196 for d, v in self._state.iteritems():
196 for d, v in self._state.iteritems():
197 records.append(('F', '\0'.join([d] + v)))
197 records.append(('F', '\0'.join([d] + v)))
198 self._writerecords(records)
198 self._writerecords(records)
199 self._dirty = False
199 self._dirty = False
200
200
201 def _writerecords(self, records):
201 def _writerecords(self, records):
202 """Write current state on disk (both v1 and v2)"""
202 """Write current state on disk (both v1 and v2)"""
203 self._writerecordsv1(records)
203 self._writerecordsv1(records)
204 self._writerecordsv2(records)
204 self._writerecordsv2(records)
205
205
206 def _writerecordsv1(self, records):
206 def _writerecordsv1(self, records):
207 """Write current state on disk in a version 1 file"""
207 """Write current state on disk in a version 1 file"""
208 f = self._repo.vfs(self.statepathv1, 'w')
208 f = self._repo.vfs(self.statepathv1, 'w')
209 irecords = iter(records)
209 irecords = iter(records)
210 lrecords = irecords.next()
210 lrecords = irecords.next()
211 assert lrecords[0] == 'L'
211 assert lrecords[0] == 'L'
212 f.write(hex(self._local) + '\n')
212 f.write(hex(self._local) + '\n')
213 for rtype, data in irecords:
213 for rtype, data in irecords:
214 if rtype == 'F':
214 if rtype == 'F':
215 f.write('%s\n' % _droponode(data))
215 f.write('%s\n' % _droponode(data))
216 f.close()
216 f.close()
217
217
218 def _writerecordsv2(self, records):
218 def _writerecordsv2(self, records):
219 """Write current state on disk in a version 2 file"""
219 """Write current state on disk in a version 2 file"""
220 f = self._repo.vfs(self.statepathv2, 'w')
220 f = self._repo.vfs(self.statepathv2, 'w')
221 for key, data in records:
221 for key, data in records:
222 assert len(key) == 1
222 assert len(key) == 1
223 format = '>sI%is' % len(data)
223 format = '>sI%is' % len(data)
224 f.write(_pack(format, key, len(data), data))
224 f.write(_pack(format, key, len(data), data))
225 f.close()
225 f.close()
226
226
227 def add(self, fcl, fco, fca, fd):
227 def add(self, fcl, fco, fca, fd):
228 """add a new (potentially?) conflicting file the merge state
228 """add a new (potentially?) conflicting file the merge state
229 fcl: file context for local,
229 fcl: file context for local,
230 fco: file context for remote,
230 fco: file context for remote,
231 fca: file context for ancestors,
231 fca: file context for ancestors,
232 fd: file path of the resulting merge.
232 fd: file path of the resulting merge.
233
233
234 note: also write the local version to the `.hg/merge` directory.
234 note: also write the local version to the `.hg/merge` directory.
235 """
235 """
236 hash = util.sha1(fcl.path()).hexdigest()
236 hash = util.sha1(fcl.path()).hexdigest()
237 self._repo.vfs.write('merge/' + hash, fcl.data())
237 self._repo.vfs.write('merge/' + hash, fcl.data())
238 self._state[fd] = ['u', hash, fcl.path(),
238 self._state[fd] = ['u', hash, fcl.path(),
239 fca.path(), hex(fca.filenode()),
239 fca.path(), hex(fca.filenode()),
240 fco.path(), hex(fco.filenode()),
240 fco.path(), hex(fco.filenode()),
241 fcl.flags()]
241 fcl.flags()]
242 self._dirty = True
242 self._dirty = True
243
243
244 def __contains__(self, dfile):
244 def __contains__(self, dfile):
245 return dfile in self._state
245 return dfile in self._state
246
246
247 def __getitem__(self, dfile):
247 def __getitem__(self, dfile):
248 return self._state[dfile][0]
248 return self._state[dfile][0]
249
249
250 def __iter__(self):
250 def __iter__(self):
251 return iter(sorted(self._state))
251 return iter(sorted(self._state))
252
252
253 def files(self):
253 def files(self):
254 return self._state.keys()
254 return self._state.keys()
255
255
256 def mark(self, dfile, state):
256 def mark(self, dfile, state):
257 self._state[dfile][0] = state
257 self._state[dfile][0] = state
258 self._dirty = True
258 self._dirty = True
259
259
260 def unresolved(self):
260 def unresolved(self):
261 """Obtain the paths of unresolved files."""
261 """Obtain the paths of unresolved files."""
262
262
263 for f, entry in self._state.items():
263 for f, entry in self._state.items():
264 if entry[0] == 'u':
264 if entry[0] == 'u':
265 yield f
265 yield f
266
266
267 def resolve(self, dfile, wctx, labels=None):
267 def resolve(self, dfile, wctx, labels=None):
268 """rerun merge process for file path `dfile`"""
268 """rerun merge process for file path `dfile`"""
269 if self[dfile] == 'r':
269 if self[dfile] == 'r':
270 return 0
270 return 0
271 stateentry = self._state[dfile]
271 stateentry = self._state[dfile]
272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
273 octx = self._repo[self._other]
273 octx = self._repo[self._other]
274 fcd = wctx[dfile]
274 fcd = wctx[dfile]
275 fco = octx[ofile]
275 fco = octx[ofile]
276 fca = self._repo.filectx(afile, fileid=anode)
276 fca = self._repo.filectx(afile, fileid=anode)
277 # "premerge" x flags
277 # "premerge" x flags
278 flo = fco.flags()
278 flo = fco.flags()
279 fla = fca.flags()
279 fla = fca.flags()
280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
281 if fca.node() == nullid:
281 if fca.node() == nullid:
282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
283 afile)
283 afile)
284 elif flags == fla:
284 elif flags == fla:
285 flags = flo
285 flags = flo
286 # restore local
286 # restore local
287 f = self._repo.vfs('merge/' + hash)
287 f = self._repo.vfs('merge/' + hash)
288 self._repo.wwrite(dfile, f.read(), flags)
288 self._repo.wwrite(dfile, f.read(), flags)
289 f.close()
289 f.close()
290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
291 labels=labels)
291 labels=labels)
292 if r is None:
292 if r is None:
293 # no real conflict
293 # no real conflict
294 del self._state[dfile]
294 del self._state[dfile]
295 self._dirty = True
295 self._dirty = True
296 elif not r:
296 elif not r:
297 self.mark(dfile, 'r')
297 self.mark(dfile, 'r')
298 return r
298 return r
299
299
300 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
300 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
301 if f2 is None:
301 if f2 is None:
302 f2 = f
302 f2 = f
303 return (os.path.isfile(repo.wjoin(f))
303 return (os.path.isfile(repo.wjoin(f))
304 and repo.wvfs.audit.check(f)
304 and repo.wvfs.audit.check(f)
305 and repo.dirstate.normalize(f) not in repo.dirstate
305 and repo.dirstate.normalize(f) not in repo.dirstate
306 and mctx[f2].cmp(wctx[f]))
306 and mctx[f2].cmp(wctx[f]))
307
307
308 def _checkunknownfiles(repo, wctx, mctx, force, actions):
308 def _checkunknownfiles(repo, wctx, mctx, force, actions):
309 """
309 """
310 Considers any actions that care about the presence of conflicting unknown
310 Considers any actions that care about the presence of conflicting unknown
311 files. For some actions, the result is to abort; for others, it is to
311 files. For some actions, the result is to abort; for others, it is to
312 choose a different action.
312 choose a different action.
313 """
313 """
314 aborts = []
314 aborts = []
315 if not force:
315 if not force:
316 for f, (m, args, msg) in actions.iteritems():
316 for f, (m, args, msg) in actions.iteritems():
317 if m in ('c', 'dc'):
317 if m in ('c', 'dc'):
318 if _checkunknownfile(repo, wctx, mctx, f):
318 if _checkunknownfile(repo, wctx, mctx, f):
319 aborts.append(f)
319 aborts.append(f)
320 elif m == 'dg':
320 elif m == 'dg':
321 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
321 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
322 aborts.append(f)
322 aborts.append(f)
323
323
324 for f in sorted(aborts):
324 for f in sorted(aborts):
325 repo.ui.warn(_("%s: untracked file differs\n") % f)
325 repo.ui.warn(_("%s: untracked file differs\n") % f)
326 if aborts:
326 if aborts:
327 raise util.Abort(_("untracked files in working directory differ "
327 raise util.Abort(_("untracked files in working directory differ "
328 "from files in requested revision"))
328 "from files in requested revision"))
329
329
330 for f, (m, args, msg) in actions.iteritems():
330 for f, (m, args, msg) in actions.iteritems():
331 if m == 'c':
331 if m == 'c':
332 actions[f] = ('g', args, msg)
332 actions[f] = ('g', args, msg)
333 elif m == 'cm':
333 elif m == 'cm':
334 fl2, anc = args
334 fl2, anc = args
335 different = _checkunknownfile(repo, wctx, mctx, f)
335 different = _checkunknownfile(repo, wctx, mctx, f)
336 if different:
336 if different:
337 actions[f] = ('m', (f, f, None, False, anc),
337 actions[f] = ('m', (f, f, None, False, anc),
338 "remote differs from untracked local")
338 "remote differs from untracked local")
339 else:
339 else:
340 actions[f] = ('g', (fl2,), "remote created")
340 actions[f] = ('g', (fl2,), "remote created")
341
341
342 def _forgetremoved(wctx, mctx, branchmerge):
342 def _forgetremoved(wctx, mctx, branchmerge):
343 """
343 """
344 Forget removed files
344 Forget removed files
345
345
346 If we're jumping between revisions (as opposed to merging), and if
346 If we're jumping between revisions (as opposed to merging), and if
347 neither the working directory nor the target rev has the file,
347 neither the working directory nor the target rev has the file,
348 then we need to remove it from the dirstate, to prevent the
348 then we need to remove it from the dirstate, to prevent the
349 dirstate from listing the file when it is no longer in the
349 dirstate from listing the file when it is no longer in the
350 manifest.
350 manifest.
351
351
352 If we're merging, and the other revision has removed a file
352 If we're merging, and the other revision has removed a file
353 that is not present in the working directory, we need to mark it
353 that is not present in the working directory, we need to mark it
354 as removed.
354 as removed.
355 """
355 """
356
356
357 actions = {}
357 actions = {}
358 m = 'f'
358 m = 'f'
359 if branchmerge:
359 if branchmerge:
360 m = 'r'
360 m = 'r'
361 for f in wctx.deleted():
361 for f in wctx.deleted():
362 if f not in mctx:
362 if f not in mctx:
363 actions[f] = m, None, "forget deleted"
363 actions[f] = m, None, "forget deleted"
364
364
365 if not branchmerge:
365 if not branchmerge:
366 for f in wctx.removed():
366 for f in wctx.removed():
367 if f not in mctx:
367 if f not in mctx:
368 actions[f] = 'f', None, "forget removed"
368 actions[f] = 'f', None, "forget removed"
369
369
370 return actions
370 return actions
371
371
372 def _checkcollision(repo, wmf, actions):
372 def _checkcollision(repo, wmf, actions):
373 # build provisional merged manifest up
373 # build provisional merged manifest up
374 pmmf = set(wmf)
374 pmmf = set(wmf)
375
375
376 if actions:
376 if actions:
377 # k, dr, e and rd are no-op
377 # k, dr, e and rd are no-op
378 for m in 'a', 'f', 'g', 'cd', 'dc':
378 for m in 'a', 'f', 'g', 'cd', 'dc':
379 for f, args, msg in actions[m]:
379 for f, args, msg in actions[m]:
380 pmmf.add(f)
380 pmmf.add(f)
381 for f, args, msg in actions['r']:
381 for f, args, msg in actions['r']:
382 pmmf.discard(f)
382 pmmf.discard(f)
383 for f, args, msg in actions['dm']:
383 for f, args, msg in actions['dm']:
384 f2, flags = args
384 f2, flags = args
385 pmmf.discard(f2)
385 pmmf.discard(f2)
386 pmmf.add(f)
386 pmmf.add(f)
387 for f, args, msg in actions['dg']:
387 for f, args, msg in actions['dg']:
388 pmmf.add(f)
388 pmmf.add(f)
389 for f, args, msg in actions['m']:
389 for f, args, msg in actions['m']:
390 f1, f2, fa, move, anc = args
390 f1, f2, fa, move, anc = args
391 if move:
391 if move:
392 pmmf.discard(f1)
392 pmmf.discard(f1)
393 pmmf.add(f)
393 pmmf.add(f)
394
394
395 # check case-folding collision in provisional merged manifest
395 # check case-folding collision in provisional merged manifest
396 foldmap = {}
396 foldmap = {}
397 for f in sorted(pmmf):
397 for f in sorted(pmmf):
398 fold = util.normcase(f)
398 fold = util.normcase(f)
399 if fold in foldmap:
399 if fold in foldmap:
400 raise util.Abort(_("case-folding collision between %s and %s")
400 raise util.Abort(_("case-folding collision between %s and %s")
401 % (f, foldmap[fold]))
401 % (f, foldmap[fold]))
402 foldmap[fold] = f
402 foldmap[fold] = f
403
403
404 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
404 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
405 acceptremote, followcopies):
405 acceptremote, followcopies):
406 """
406 """
407 Merge p1 and p2 with ancestor pa and generate merge action list
407 Merge p1 and p2 with ancestor pa and generate merge action list
408
408
409 branchmerge and force are as passed in to update
409 branchmerge and force are as passed in to update
410 partial = function to filter file lists
410 partial = function to filter file lists
411 acceptremote = accept the incoming changes without prompting
411 acceptremote = accept the incoming changes without prompting
412 """
412 """
413
413
414 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
414 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
415
415
416 # manifests fetched in order are going to be faster, so prime the caches
416 # manifests fetched in order are going to be faster, so prime the caches
417 [x.manifest() for x in
417 [x.manifest() for x in
418 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
418 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
419
419
420 if followcopies:
420 if followcopies:
421 ret = copies.mergecopies(repo, wctx, p2, pa)
421 ret = copies.mergecopies(repo, wctx, p2, pa)
422 copy, movewithdir, diverge, renamedelete = ret
422 copy, movewithdir, diverge, renamedelete = ret
423
423
424 repo.ui.note(_("resolving manifests\n"))
424 repo.ui.note(_("resolving manifests\n"))
425 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
425 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
426 % (bool(branchmerge), bool(force), bool(partial)))
426 % (bool(branchmerge), bool(force), bool(partial)))
427 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
427 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
428
428
429 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
429 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
430 copied = set(copy.values())
430 copied = set(copy.values())
431 copied.update(movewithdir.values())
431 copied.update(movewithdir.values())
432
432
433 if '.hgsubstate' in m1:
433 if '.hgsubstate' in m1:
434 # check whether sub state is modified
434 # check whether sub state is modified
435 for s in sorted(wctx.substate):
435 for s in sorted(wctx.substate):
436 if wctx.sub(s).dirty():
436 if wctx.sub(s).dirty():
437 m1['.hgsubstate'] += '+'
437 m1['.hgsubstate'] += '+'
438 break
438 break
439
439
440 # Compare manifests
440 # Compare manifests
441 diff = m1.diff(m2)
441 diff = m1.diff(m2)
442
442
443 actions = {}
443 actions = {}
444 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
444 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
445 if partial and not partial(f):
445 if partial and not partial(f):
446 continue
446 continue
447 if n1 and n2: # file exists on both local and remote side
447 if n1 and n2: # file exists on both local and remote side
448 if f not in ma:
448 if f not in ma:
449 fa = copy.get(f, None)
449 fa = copy.get(f, None)
450 if fa is not None:
450 if fa is not None:
451 actions[f] = ('m', (f, f, fa, False, pa.node()),
451 actions[f] = ('m', (f, f, fa, False, pa.node()),
452 "both renamed from " + fa)
452 "both renamed from " + fa)
453 else:
453 else:
454 actions[f] = ('m', (f, f, None, False, pa.node()),
454 actions[f] = ('m', (f, f, None, False, pa.node()),
455 "both created")
455 "both created")
456 else:
456 else:
457 a = ma[f]
457 a = ma[f]
458 fla = ma.flags(f)
458 fla = ma.flags(f)
459 nol = 'l' not in fl1 + fl2 + fla
459 nol = 'l' not in fl1 + fl2 + fla
460 if n2 == a and fl2 == fla:
460 if n2 == a and fl2 == fla:
461 actions[f] = ('k' , (), "remote unchanged")
461 actions[f] = ('k' , (), "remote unchanged")
462 elif n1 == a and fl1 == fla: # local unchanged - use remote
462 elif n1 == a and fl1 == fla: # local unchanged - use remote
463 if n1 == n2: # optimization: keep local content
463 if n1 == n2: # optimization: keep local content
464 actions[f] = ('e', (fl2,), "update permissions")
464 actions[f] = ('e', (fl2,), "update permissions")
465 else:
465 else:
466 actions[f] = ('g', (fl2,), "remote is newer")
466 actions[f] = ('g', (fl2,), "remote is newer")
467 elif nol and n2 == a: # remote only changed 'x'
467 elif nol and n2 == a: # remote only changed 'x'
468 actions[f] = ('e', (fl2,), "update permissions")
468 actions[f] = ('e', (fl2,), "update permissions")
469 elif nol and n1 == a: # local only changed 'x'
469 elif nol and n1 == a: # local only changed 'x'
470 actions[f] = ('g', (fl1,), "remote is newer")
470 actions[f] = ('g', (fl1,), "remote is newer")
471 else: # both changed something
471 else: # both changed something
472 actions[f] = ('m', (f, f, f, False, pa.node()),
472 actions[f] = ('m', (f, f, f, False, pa.node()),
473 "versions differ")
473 "versions differ")
474 elif n1: # file exists only on local side
474 elif n1: # file exists only on local side
475 if f in copied:
475 if f in copied:
476 pass # we'll deal with it on m2 side
476 pass # we'll deal with it on m2 side
477 elif f in movewithdir: # directory rename, move local
477 elif f in movewithdir: # directory rename, move local
478 f2 = movewithdir[f]
478 f2 = movewithdir[f]
479 if f2 in m2:
479 if f2 in m2:
480 actions[f2] = ('m', (f, f2, None, True, pa.node()),
480 actions[f2] = ('m', (f, f2, None, True, pa.node()),
481 "remote directory rename, both created")
481 "remote directory rename, both created")
482 else:
482 else:
483 actions[f2] = ('dm', (f, fl1),
483 actions[f2] = ('dm', (f, fl1),
484 "remote directory rename - move from " + f)
484 "remote directory rename - move from " + f)
485 elif f in copy:
485 elif f in copy:
486 f2 = copy[f]
486 f2 = copy[f]
487 actions[f] = ('m', (f, f2, f2, False, pa.node()),
487 actions[f] = ('m', (f, f2, f2, False, pa.node()),
488 "local copied/moved from " + f2)
488 "local copied/moved from " + f2)
489 elif f in ma: # clean, a different, no remote
489 elif f in ma: # clean, a different, no remote
490 if n1 != ma[f]:
490 if n1 != ma[f]:
491 if acceptremote:
491 if acceptremote:
492 actions[f] = ('r', None, "remote delete")
492 actions[f] = ('r', None, "remote delete")
493 else:
493 else:
494 actions[f] = ('cd', None, "prompt changed/deleted")
494 actions[f] = ('cd', None, "prompt changed/deleted")
495 elif n1[20:] == 'a':
495 elif n1[20:] == 'a':
496 # This extra 'a' is added by working copy manifest to mark
496 # This extra 'a' is added by working copy manifest to mark
497 # the file as locally added. We should forget it instead of
497 # the file as locally added. We should forget it instead of
498 # deleting it.
498 # deleting it.
499 actions[f] = ('f', None, "remote deleted")
499 actions[f] = ('f', None, "remote deleted")
500 else:
500 else:
501 actions[f] = ('r', None, "other deleted")
501 actions[f] = ('r', None, "other deleted")
502 elif n2: # file exists only on remote side
502 elif n2: # file exists only on remote side
503 if f in copied:
503 if f in copied:
504 pass # we'll deal with it on m1 side
504 pass # we'll deal with it on m1 side
505 elif f in movewithdir:
505 elif f in movewithdir:
506 f2 = movewithdir[f]
506 f2 = movewithdir[f]
507 if f2 in m1:
507 if f2 in m1:
508 actions[f2] = ('m', (f2, f, None, False, pa.node()),
508 actions[f2] = ('m', (f2, f, None, False, pa.node()),
509 "local directory rename, both created")
509 "local directory rename, both created")
510 else:
510 else:
511 actions[f2] = ('dg', (f, fl2),
511 actions[f2] = ('dg', (f, fl2),
512 "local directory rename - get from " + f)
512 "local directory rename - get from " + f)
513 elif f in copy:
513 elif f in copy:
514 f2 = copy[f]
514 f2 = copy[f]
515 if f2 in m2:
515 if f2 in m2:
516 actions[f] = ('m', (f2, f, f2, False, pa.node()),
516 actions[f] = ('m', (f2, f, f2, False, pa.node()),
517 "remote copied from " + f2)
517 "remote copied from " + f2)
518 else:
518 else:
519 actions[f] = ('m', (f2, f, f2, True, pa.node()),
519 actions[f] = ('m', (f2, f, f2, True, pa.node()),
520 "remote moved from " + f2)
520 "remote moved from " + f2)
521 elif f not in ma:
521 elif f not in ma:
522 # local unknown, remote created: the logic is described by the
522 # local unknown, remote created: the logic is described by the
523 # following table:
523 # following table:
524 #
524 #
525 # force branchmerge different | action
525 # force branchmerge different | action
526 # n * * | create
526 # n * * | create
527 # y n * | create
527 # y n * | create
528 # y y n | create
528 # y y n | create
529 # y y y | merge
529 # y y y | merge
530 #
530 #
531 # Checking whether the files are different is expensive, so we
531 # Checking whether the files are different is expensive, so we
532 # don't do that when we can avoid it.
532 # don't do that when we can avoid it.
533 if not force:
533 if not force:
534 actions[f] = ('c', (fl2,), "remote created")
534 actions[f] = ('c', (fl2,), "remote created")
535 elif not branchmerge:
535 elif not branchmerge:
536 actions[f] = ('c', (fl2,), "remote created")
536 actions[f] = ('c', (fl2,), "remote created")
537 else:
537 else:
538 actions[f] = ('cm', (fl2, pa.node()),
538 actions[f] = ('cm', (fl2, pa.node()),
539 "remote created, get or merge")
539 "remote created, get or merge")
540 elif n2 != ma[f]:
540 elif n2 != ma[f]:
541 if acceptremote:
541 if acceptremote:
542 actions[f] = ('c', (fl2,), "remote recreating")
542 actions[f] = ('c', (fl2,), "remote recreating")
543 else:
543 else:
544 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
544 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
545
545
546 return actions, diverge, renamedelete
546 return actions, diverge, renamedelete
547
547
548 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
548 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
549 """Resolves false conflicts where the nodeid changed but the content
549 """Resolves false conflicts where the nodeid changed but the content
550 remained the same."""
550 remained the same."""
551
551
552 for f, (m, args, msg) in actions.items():
552 for f, (m, args, msg) in actions.items():
553 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
553 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
554 # local did change but ended up with same content
554 # local did change but ended up with same content
555 actions[f] = 'r', None, "prompt same"
555 actions[f] = 'r', None, "prompt same"
556 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
556 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
557 # remote did change but ended up with same content
557 # remote did change but ended up with same content
558 del actions[f] # don't get = keep local deleted
558 del actions[f] # don't get = keep local deleted
559
559
560 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
560 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
561 acceptremote, followcopies):
561 acceptremote, followcopies):
562 "Calculate the actions needed to merge mctx into wctx using ancestors"
562 "Calculate the actions needed to merge mctx into wctx using ancestors"
563
563
564 if len(ancestors) == 1: # default
564 if len(ancestors) == 1: # default
565 actions, diverge, renamedelete = manifestmerge(
565 actions, diverge, renamedelete = manifestmerge(
566 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
566 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
567 acceptremote, followcopies)
567 acceptremote, followcopies)
568 _checkunknownfiles(repo, wctx, mctx, force, actions)
568 _checkunknownfiles(repo, wctx, mctx, force, actions)
569
569
570 else: # only when merge.preferancestor=* - the default
570 else: # only when merge.preferancestor=* - the default
571 repo.ui.note(
571 repo.ui.note(
572 _("note: merging %s and %s using bids from ancestors %s\n") %
572 _("note: merging %s and %s using bids from ancestors %s\n") %
573 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
573 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
574
574
575 # Call for bids
575 # Call for bids
576 fbids = {} # mapping filename to bids (action method to list af actions)
576 fbids = {} # mapping filename to bids (action method to list af actions)
577 diverge, renamedelete = None, None
577 diverge, renamedelete = None, None
578 for ancestor in ancestors:
578 for ancestor in ancestors:
579 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
579 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
580 actions, diverge1, renamedelete1 = manifestmerge(
580 actions, diverge1, renamedelete1 = manifestmerge(
581 repo, wctx, mctx, ancestor, branchmerge, force, partial,
581 repo, wctx, mctx, ancestor, branchmerge, force, partial,
582 acceptremote, followcopies)
582 acceptremote, followcopies)
583 _checkunknownfiles(repo, wctx, mctx, force, actions)
583 _checkunknownfiles(repo, wctx, mctx, force, actions)
584 if diverge is None: # and renamedelete is None.
584 if diverge is None: # and renamedelete is None.
585 # Arbitrarily pick warnings from first iteration
585 # Arbitrarily pick warnings from first iteration
586 diverge = diverge1
586 diverge = diverge1
587 renamedelete = renamedelete1
587 renamedelete = renamedelete1
588 for f, a in sorted(actions.iteritems()):
588 for f, a in sorted(actions.iteritems()):
589 m, args, msg = a
589 m, args, msg = a
590 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
590 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
591 if f in fbids:
591 if f in fbids:
592 d = fbids[f]
592 d = fbids[f]
593 if m in d:
593 if m in d:
594 d[m].append(a)
594 d[m].append(a)
595 else:
595 else:
596 d[m] = [a]
596 d[m] = [a]
597 else:
597 else:
598 fbids[f] = {m: [a]}
598 fbids[f] = {m: [a]}
599
599
600 # Pick the best bid for each file
600 # Pick the best bid for each file
601 repo.ui.note(_('\nauction for merging merge bids\n'))
601 repo.ui.note(_('\nauction for merging merge bids\n'))
602 actions = {}
602 actions = {}
603 for f, bids in sorted(fbids.items()):
603 for f, bids in sorted(fbids.items()):
604 # bids is a mapping from action method to list af actions
604 # bids is a mapping from action method to list af actions
605 # Consensus?
605 # Consensus?
606 if len(bids) == 1: # all bids are the same kind of method
606 if len(bids) == 1: # all bids are the same kind of method
607 m, l = bids.items()[0]
607 m, l = bids.items()[0]
608 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
608 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
609 repo.ui.note(" %s: consensus for %s\n" % (f, m))
609 repo.ui.note(" %s: consensus for %s\n" % (f, m))
610 actions[f] = l[0]
610 actions[f] = l[0]
611 continue
611 continue
612 # If keep is an option, just do it.
612 # If keep is an option, just do it.
613 if 'k' in bids:
613 if 'k' in bids:
614 repo.ui.note(" %s: picking 'keep' action\n" % f)
614 repo.ui.note(" %s: picking 'keep' action\n" % f)
615 actions[f] = bids['k'][0]
615 actions[f] = bids['k'][0]
616 continue
616 continue
617 # If there are gets and they all agree [how could they not?], do it.
617 # If there are gets and they all agree [how could they not?], do it.
618 if 'g' in bids:
618 if 'g' in bids:
619 ga0 = bids['g'][0]
619 ga0 = bids['g'][0]
620 if util.all(a == ga0 for a in bids['g'][1:]):
620 if util.all(a == ga0 for a in bids['g'][1:]):
621 repo.ui.note(" %s: picking 'get' action\n" % f)
621 repo.ui.note(" %s: picking 'get' action\n" % f)
622 actions[f] = ga0
622 actions[f] = ga0
623 continue
623 continue
624 # TODO: Consider other simple actions such as mode changes
624 # TODO: Consider other simple actions such as mode changes
625 # Handle inefficient democrazy.
625 # Handle inefficient democrazy.
626 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
626 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
627 for m, l in sorted(bids.items()):
627 for m, l in sorted(bids.items()):
628 for _f, args, msg in l:
628 for _f, args, msg in l:
629 repo.ui.note(' %s -> %s\n' % (msg, m))
629 repo.ui.note(' %s -> %s\n' % (msg, m))
630 # Pick random action. TODO: Instead, prompt user when resolving
630 # Pick random action. TODO: Instead, prompt user when resolving
631 m, l = bids.items()[0]
631 m, l = bids.items()[0]
632 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
632 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
633 (f, m))
633 (f, m))
634 actions[f] = l[0]
634 actions[f] = l[0]
635 continue
635 continue
636 repo.ui.note(_('end of auction\n\n'))
636 repo.ui.note(_('end of auction\n\n'))
637
637
638 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
638 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
639
639
640 if wctx.rev() is None:
640 if wctx.rev() is None:
641 fractions = _forgetremoved(wctx, mctx, branchmerge)
641 fractions = _forgetremoved(wctx, mctx, branchmerge)
642 actions.update(fractions)
642 actions.update(fractions)
643
643
644 return actions, diverge, renamedelete
644 return actions, diverge, renamedelete
645
645
646 def batchremove(repo, actions):
646 def batchremove(repo, actions):
647 """apply removes to the working directory
647 """apply removes to the working directory
648
648
649 yields tuples for progress updates
649 yields tuples for progress updates
650 """
650 """
651 verbose = repo.ui.verbose
651 verbose = repo.ui.verbose
652 unlink = util.unlinkpath
652 unlink = util.unlinkpath
653 wjoin = repo.wjoin
653 wjoin = repo.wjoin
654 audit = repo.wvfs.audit
654 audit = repo.wvfs.audit
655 i = 0
655 i = 0
656 for f, args, msg in actions:
656 for f, args, msg in actions:
657 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
657 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
658 if verbose:
658 if verbose:
659 repo.ui.note(_("removing %s\n") % f)
659 repo.ui.note(_("removing %s\n") % f)
660 audit(f)
660 audit(f)
661 try:
661 try:
662 unlink(wjoin(f), ignoremissing=True)
662 unlink(wjoin(f), ignoremissing=True)
663 except OSError, inst:
663 except OSError, inst:
664 repo.ui.warn(_("update failed to remove %s: %s!\n") %
664 repo.ui.warn(_("update failed to remove %s: %s!\n") %
665 (f, inst.strerror))
665 (f, inst.strerror))
666 if i == 100:
666 if i == 100:
667 yield i, f
667 yield i, f
668 i = 0
668 i = 0
669 i += 1
669 i += 1
670 if i > 0:
670 if i > 0:
671 yield i, f
671 yield i, f
672
672
673 def batchget(repo, mctx, actions):
673 def batchget(repo, mctx, actions):
674 """apply gets to the working directory
674 """apply gets to the working directory
675
675
676 mctx is the context to get from
676 mctx is the context to get from
677
677
678 yields tuples for progress updates
678 yields tuples for progress updates
679 """
679 """
680 verbose = repo.ui.verbose
680 verbose = repo.ui.verbose
681 fctx = mctx.filectx
681 fctx = mctx.filectx
682 wwrite = repo.wwrite
682 wwrite = repo.wwrite
683 i = 0
683 i = 0
684 for f, args, msg in actions:
684 for f, args, msg in actions:
685 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
685 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
686 if verbose:
686 if verbose:
687 repo.ui.note(_("getting %s\n") % f)
687 repo.ui.note(_("getting %s\n") % f)
688 wwrite(f, fctx(f).data(), args[0])
688 wwrite(f, fctx(f).data(), args[0])
689 if i == 100:
689 if i == 100:
690 yield i, f
690 yield i, f
691 i = 0
691 i = 0
692 i += 1
692 i += 1
693 if i > 0:
693 if i > 0:
694 yield i, f
694 yield i, f
695
695
696 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
696 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
697 """apply the merge action list to the working directory
697 """apply the merge action list to the working directory
698
698
699 wctx is the working copy context
699 wctx is the working copy context
700 mctx is the context to be merged into the working copy
700 mctx is the context to be merged into the working copy
701
701
702 Return a tuple of counts (updated, merged, removed, unresolved) that
702 Return a tuple of counts (updated, merged, removed, unresolved) that
703 describes how many files were affected by the update.
703 describes how many files were affected by the update.
704 """
704 """
705
705
706 updated, merged, removed, unresolved = 0, 0, 0, 0
706 updated, merged, removed, unresolved = 0, 0, 0, 0
707 ms = mergestate(repo)
707 ms = mergestate(repo)
708 ms.reset(wctx.p1().node(), mctx.node())
708 ms.reset(wctx.p1().node(), mctx.node())
709 moves = []
709 moves = []
710 for m, l in actions.items():
710 for m, l in actions.items():
711 l.sort()
711 l.sort()
712
712
713 # prescan for merges
713 # prescan for merges
714 for f, args, msg in actions['m']:
714 for f, args, msg in actions['m']:
715 f1, f2, fa, move, anc = args
715 f1, f2, fa, move, anc = args
716 if f == '.hgsubstate': # merged internally
716 if f == '.hgsubstate': # merged internally
717 continue
717 continue
718 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
718 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
719 fcl = wctx[f1]
719 fcl = wctx[f1]
720 fco = mctx[f2]
720 fco = mctx[f2]
721 actx = repo[anc]
721 actx = repo[anc]
722 if fa in actx:
722 if fa in actx:
723 fca = actx[fa]
723 fca = actx[fa]
724 else:
724 else:
725 fca = repo.filectx(f1, fileid=nullrev)
725 fca = repo.filectx(f1, fileid=nullrev)
726 ms.add(fcl, fco, fca, f)
726 ms.add(fcl, fco, fca, f)
727 if f1 != f and move:
727 if f1 != f and move:
728 moves.append(f1)
728 moves.append(f1)
729
729
730 audit = repo.wvfs.audit
730 audit = repo.wvfs.audit
731 _updating = _('updating')
731 _updating = _('updating')
732 _files = _('files')
732 _files = _('files')
733 progress = repo.ui.progress
733 progress = repo.ui.progress
734
734
735 # remove renamed files after safely stored
735 # remove renamed files after safely stored
736 for f in moves:
736 for f in moves:
737 if os.path.lexists(repo.wjoin(f)):
737 if os.path.lexists(repo.wjoin(f)):
738 repo.ui.debug("removing %s\n" % f)
738 repo.ui.debug("removing %s\n" % f)
739 audit(f)
739 audit(f)
740 util.unlinkpath(repo.wjoin(f))
740 util.unlinkpath(repo.wjoin(f))
741
741
742 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
742 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
743
743
744 def dirtysubstate():
744 def dirtysubstate():
745 # mark '.hgsubstate' as possibly dirty forcibly, because
745 # mark '.hgsubstate' as possibly dirty forcibly, because
746 # modified '.hgsubstate' is misunderstood as clean,
746 # modified '.hgsubstate' is misunderstood as clean,
747 # when both st_size/st_mtime of '.hgsubstate' aren't changed,
747 # when both st_size/st_mtime of '.hgsubstate' aren't changed,
748 # even if "submerge" fails and '.hgsubstate' is inconsistent
748 # even if "submerge" fails and '.hgsubstate' is inconsistent
749 repo.dirstate.normallookup('.hgsubstate')
749 repo.dirstate.normallookup('.hgsubstate')
750
750
751 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
751 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
752 dirtysubstate()
752 dirtysubstate()
753 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
753 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
754
754
755 # remove in parallel (must come first)
755 # remove in parallel (must come first)
756 z = 0
756 z = 0
757 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
757 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
758 for i, item in prog:
758 for i, item in prog:
759 z += i
759 z += i
760 progress(_updating, z, item=item, total=numupdates, unit=_files)
760 progress(_updating, z, item=item, total=numupdates, unit=_files)
761 removed = len(actions['r'])
761 removed = len(actions['r'])
762
762
763 # get in parallel
763 # get in parallel
764 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
764 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
765 for i, item in prog:
765 for i, item in prog:
766 z += i
766 z += i
767 progress(_updating, z, item=item, total=numupdates, unit=_files)
767 progress(_updating, z, item=item, total=numupdates, unit=_files)
768 updated = len(actions['g'])
768 updated = len(actions['g'])
769
769
770 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
770 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
771 dirtysubstate()
771 dirtysubstate()
772 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
772 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
773
773
774 # forget (manifest only, just log it) (must come first)
774 # forget (manifest only, just log it) (must come first)
775 for f, args, msg in actions['f']:
775 for f, args, msg in actions['f']:
776 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
776 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
777 z += 1
777 z += 1
778 progress(_updating, z, item=f, total=numupdates, unit=_files)
778 progress(_updating, z, item=f, total=numupdates, unit=_files)
779
779
780 # re-add (manifest only, just log it)
780 # re-add (manifest only, just log it)
781 for f, args, msg in actions['a']:
781 for f, args, msg in actions['a']:
782 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
782 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
783 z += 1
783 z += 1
784 progress(_updating, z, item=f, total=numupdates, unit=_files)
784 progress(_updating, z, item=f, total=numupdates, unit=_files)
785
785
786 # keep (noop, just log it)
786 # keep (noop, just log it)
787 for f, args, msg in actions['k']:
787 for f, args, msg in actions['k']:
788 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
788 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
789 # no progress
789 # no progress
790
790
791 # merge
791 # merge
792 for f, args, msg in actions['m']:
792 for f, args, msg in actions['m']:
793 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
793 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
794 z += 1
794 z += 1
795 progress(_updating, z, item=f, total=numupdates, unit=_files)
795 progress(_updating, z, item=f, total=numupdates, unit=_files)
796 if f == '.hgsubstate': # subrepo states need updating
796 if f == '.hgsubstate': # subrepo states need updating
797 dirtysubstate()
797 dirtysubstate()
798 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
798 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
799 overwrite)
799 overwrite)
800 continue
800 continue
801 audit(f)
801 audit(f)
802 r = ms.resolve(f, wctx, labels=labels)
802 r = ms.resolve(f, wctx, labels=labels)
803 if r is not None and r > 0:
803 if r is not None and r > 0:
804 unresolved += 1
804 unresolved += 1
805 else:
805 else:
806 if r is None:
806 if r is None:
807 updated += 1
807 updated += 1
808 else:
808 else:
809 merged += 1
809 merged += 1
810
810
811 # directory rename, move local
811 # directory rename, move local
812 for f, args, msg in actions['dm']:
812 for f, args, msg in actions['dm']:
813 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
813 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
814 z += 1
814 z += 1
815 progress(_updating, z, item=f, total=numupdates, unit=_files)
815 progress(_updating, z, item=f, total=numupdates, unit=_files)
816 f0, flags = args
816 f0, flags = args
817 repo.ui.note(_("moving %s to %s\n") % (f0, f))
817 repo.ui.note(_("moving %s to %s\n") % (f0, f))
818 audit(f)
818 audit(f)
819 repo.wwrite(f, wctx.filectx(f0).data(), flags)
819 repo.wwrite(f, wctx.filectx(f0).data(), flags)
820 util.unlinkpath(repo.wjoin(f0))
820 util.unlinkpath(repo.wjoin(f0))
821 updated += 1
821 updated += 1
822
822
823 # local directory rename, get
823 # local directory rename, get
824 for f, args, msg in actions['dg']:
824 for f, args, msg in actions['dg']:
825 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
825 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
826 z += 1
826 z += 1
827 progress(_updating, z, item=f, total=numupdates, unit=_files)
827 progress(_updating, z, item=f, total=numupdates, unit=_files)
828 f0, flags = args
828 f0, flags = args
829 repo.ui.note(_("getting %s to %s\n") % (f0, f))
829 repo.ui.note(_("getting %s to %s\n") % (f0, f))
830 repo.wwrite(f, mctx.filectx(f0).data(), flags)
830 repo.wwrite(f, mctx.filectx(f0).data(), flags)
831 updated += 1
831 updated += 1
832
832
833 # exec
833 # exec
834 for f, args, msg in actions['e']:
834 for f, args, msg in actions['e']:
835 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
835 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
836 z += 1
836 z += 1
837 progress(_updating, z, item=f, total=numupdates, unit=_files)
837 progress(_updating, z, item=f, total=numupdates, unit=_files)
838 flags, = args
838 flags, = args
839 audit(f)
839 audit(f)
840 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
840 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
841 updated += 1
841 updated += 1
842
842
843 ms.commit()
843 ms.commit()
844 progress(_updating, None, total=numupdates, unit=_files)
844 progress(_updating, None, total=numupdates, unit=_files)
845
845
846 return updated, merged, removed, unresolved
846 return updated, merged, removed, unresolved
847
847
848 def recordupdates(repo, actions, branchmerge):
848 def recordupdates(repo, actions, branchmerge):
849 "record merge actions to the dirstate"
849 "record merge actions to the dirstate"
850 # remove (must come first)
850 # remove (must come first)
851 for f, args, msg in actions['r']:
851 for f, args, msg in actions['r']:
852 if branchmerge:
852 if branchmerge:
853 repo.dirstate.remove(f)
853 repo.dirstate.remove(f)
854 else:
854 else:
855 repo.dirstate.drop(f)
855 repo.dirstate.drop(f)
856
856
857 # forget (must come first)
857 # forget (must come first)
858 for f, args, msg in actions['f']:
858 for f, args, msg in actions['f']:
859 repo.dirstate.drop(f)
859 repo.dirstate.drop(f)
860
860
861 # re-add
861 # re-add
862 for f, args, msg in actions['a']:
862 for f, args, msg in actions['a']:
863 if not branchmerge:
863 if not branchmerge:
864 repo.dirstate.add(f)
864 repo.dirstate.add(f)
865
865
866 # exec change
866 # exec change
867 for f, args, msg in actions['e']:
867 for f, args, msg in actions['e']:
868 repo.dirstate.normallookup(f)
868 repo.dirstate.normallookup(f)
869
869
870 # keep
870 # keep
871 for f, args, msg in actions['k']:
871 for f, args, msg in actions['k']:
872 pass
872 pass
873
873
874 # get
874 # get
875 for f, args, msg in actions['g']:
875 for f, args, msg in actions['g']:
876 if branchmerge:
876 if branchmerge:
877 repo.dirstate.otherparent(f)
877 repo.dirstate.otherparent(f)
878 else:
878 else:
879 repo.dirstate.normal(f)
879 repo.dirstate.normal(f)
880
880
881 # merge
881 # merge
882 for f, args, msg in actions['m']:
882 for f, args, msg in actions['m']:
883 f1, f2, fa, move, anc = args
883 f1, f2, fa, move, anc = args
884 if branchmerge:
884 if branchmerge:
885 # We've done a branch merge, mark this file as merged
885 # We've done a branch merge, mark this file as merged
886 # so that we properly record the merger later
886 # so that we properly record the merger later
887 repo.dirstate.merge(f)
887 repo.dirstate.merge(f)
888 if f1 != f2: # copy/rename
888 if f1 != f2: # copy/rename
889 if move:
889 if move:
890 repo.dirstate.remove(f1)
890 repo.dirstate.remove(f1)
891 if f1 != f:
891 if f1 != f:
892 repo.dirstate.copy(f1, f)
892 repo.dirstate.copy(f1, f)
893 else:
893 else:
894 repo.dirstate.copy(f2, f)
894 repo.dirstate.copy(f2, f)
895 else:
895 else:
896 # We've update-merged a locally modified file, so
896 # We've update-merged a locally modified file, so
897 # we set the dirstate to emulate a normal checkout
897 # we set the dirstate to emulate a normal checkout
898 # of that file some time in the past. Thus our
898 # of that file some time in the past. Thus our
899 # merge will appear as a normal local file
899 # merge will appear as a normal local file
900 # modification.
900 # modification.
901 if f2 == f: # file not locally copied/moved
901 if f2 == f: # file not locally copied/moved
902 repo.dirstate.normallookup(f)
902 repo.dirstate.normallookup(f)
903 if move:
903 if move:
904 repo.dirstate.drop(f1)
904 repo.dirstate.drop(f1)
905
905
906 # directory rename, move local
906 # directory rename, move local
907 for f, args, msg in actions['dm']:
907 for f, args, msg in actions['dm']:
908 f0, flag = args
908 f0, flag = args
909 if branchmerge:
909 if branchmerge:
910 repo.dirstate.add(f)
910 repo.dirstate.add(f)
911 repo.dirstate.remove(f0)
911 repo.dirstate.remove(f0)
912 repo.dirstate.copy(f0, f)
912 repo.dirstate.copy(f0, f)
913 else:
913 else:
914 repo.dirstate.normal(f)
914 repo.dirstate.normal(f)
915 repo.dirstate.drop(f0)
915 repo.dirstate.drop(f0)
916
916
917 # directory rename, get
917 # directory rename, get
918 for f, args, msg in actions['dg']:
918 for f, args, msg in actions['dg']:
919 f0, flag = args
919 f0, flag = args
920 if branchmerge:
920 if branchmerge:
921 repo.dirstate.add(f)
921 repo.dirstate.add(f)
922 repo.dirstate.copy(f0, f)
922 repo.dirstate.copy(f0, f)
923 else:
923 else:
924 repo.dirstate.normal(f)
924 repo.dirstate.normal(f)
925
925
926 def update(repo, node, branchmerge, force, partial, ancestor=None,
926 def update(repo, node, branchmerge, force, partial, ancestor=None,
927 mergeancestor=False, labels=None):
927 mergeancestor=False, labels=None):
928 """
928 """
929 Perform a merge between the working directory and the given node
929 Perform a merge between the working directory and the given node
930
930
931 node = the node to update to, or None if unspecified
931 node = the node to update to, or None if unspecified
932 branchmerge = whether to merge between branches
932 branchmerge = whether to merge between branches
933 force = whether to force branch merging or file overwriting
933 force = whether to force branch merging or file overwriting
934 partial = a function to filter file lists (dirstate not updated)
934 partial = a function to filter file lists (dirstate not updated)
935 mergeancestor = whether it is merging with an ancestor. If true,
935 mergeancestor = whether it is merging with an ancestor. If true,
936 we should accept the incoming changes for any prompts that occur.
936 we should accept the incoming changes for any prompts that occur.
937 If false, merging with an ancestor (fast-forward) is only allowed
937 If false, merging with an ancestor (fast-forward) is only allowed
938 between different named branches. This flag is used by rebase extension
938 between different named branches. This flag is used by rebase extension
939 as a temporary fix and should be avoided in general.
939 as a temporary fix and should be avoided in general.
940
940
941 The table below shows all the behaviors of the update command
941 The table below shows all the behaviors of the update command
942 given the -c and -C or no options, whether the working directory
942 given the -c and -C or no options, whether the working directory
943 is dirty, whether a revision is specified, and the relationship of
943 is dirty, whether a revision is specified, and the relationship of
944 the parent rev to the target rev (linear, on the same named
944 the parent rev to the target rev (linear, on the same named
945 branch, or on another named branch).
945 branch, or on another named branch).
946
946
947 This logic is tested by test-update-branches.t.
947 This logic is tested by test-update-branches.t.
948
948
949 -c -C dirty rev | linear same cross
949 -c -C dirty rev | linear same cross
950 n n n n | ok (1) x
950 n n n n | ok (1) x
951 n n n y | ok ok ok
951 n n n y | ok ok ok
952 n n y n | merge (2) (2)
952 n n y n | merge (2) (2)
953 n n y y | merge (3) (3)
953 n n y y | merge (3) (3)
954 n y * * | --- discard ---
954 n y * * | --- discard ---
955 y n y * | --- (4) ---
955 y n y * | --- (4) ---
956 y n n * | --- ok ---
956 y n n * | --- ok ---
957 y y * * | --- (5) ---
957 y y * * | --- (5) ---
958
958
959 x = can't happen
959 x = can't happen
960 * = don't-care
960 * = don't-care
961 1 = abort: not a linear update (merge or update --check to force update)
961 1 = abort: not a linear update (merge or update --check to force update)
962 2 = abort: uncommitted changes (commit and merge, or update --clean to
962 2 = abort: uncommitted changes (commit and merge, or update --clean to
963 discard changes)
963 discard changes)
964 3 = abort: uncommitted changes (commit or update --clean to discard changes)
964 3 = abort: uncommitted changes (commit or update --clean to discard changes)
965 4 = abort: uncommitted changes (checked in commands.py)
965 4 = abort: uncommitted changes (checked in commands.py)
966 5 = incompatible options (checked in commands.py)
966 5 = incompatible options (checked in commands.py)
967
967
968 Return the same tuple as applyupdates().
968 Return the same tuple as applyupdates().
969 """
969 """
970
970
971 onode = node
971 onode = node
972 wlock = repo.wlock()
972 wlock = repo.wlock()
973 try:
973 try:
974 wc = repo[None]
974 wc = repo[None]
975 pl = wc.parents()
975 pl = wc.parents()
976 p1 = pl[0]
976 p1 = pl[0]
977 pas = [None]
977 pas = [None]
978 if ancestor is not None:
978 if ancestor is not None:
979 pas = [repo[ancestor]]
979 pas = [repo[ancestor]]
980
980
981 if node is None:
981 if node is None:
982 # Here is where we should consider bookmarks, divergent bookmarks,
982 # Here is where we should consider bookmarks, divergent bookmarks,
983 # foreground changesets (successors), and tip of current branch;
983 # foreground changesets (successors), and tip of current branch;
984 # but currently we are only checking the branch tips.
984 # but currently we are only checking the branch tips.
985 try:
985 try:
986 node = repo.branchtip(wc.branch())
986 node = repo.branchtip(wc.branch())
987 except errormod.RepoLookupError:
987 except errormod.RepoLookupError:
988 if wc.branch() == 'default': # no default branch!
988 if wc.branch() == 'default': # no default branch!
989 node = repo.lookup('tip') # update to tip
989 node = repo.lookup('tip') # update to tip
990 else:
990 else:
991 raise util.Abort(_("branch %s not found") % wc.branch())
991 raise util.Abort(_("branch %s not found") % wc.branch())
992
992
993 if p1.obsolete() and not p1.children():
993 if p1.obsolete() and not p1.children():
994 # allow updating to successors
994 # allow updating to successors
995 successors = obsolete.successorssets(repo, p1.node())
995 successors = obsolete.successorssets(repo, p1.node())
996
996
997 # behavior of certain cases is as follows,
997 # behavior of certain cases is as follows,
998 #
998 #
999 # divergent changesets: update to highest rev, similar to what
999 # divergent changesets: update to highest rev, similar to what
1000 # is currently done when there are more than one head
1000 # is currently done when there are more than one head
1001 # (i.e. 'tip')
1001 # (i.e. 'tip')
1002 #
1002 #
1003 # replaced changesets: same as divergent except we know there
1003 # replaced changesets: same as divergent except we know there
1004 # is no conflict
1004 # is no conflict
1005 #
1005 #
1006 # pruned changeset: no update is done; though, we could
1006 # pruned changeset: no update is done; though, we could
1007 # consider updating to the first non-obsolete parent,
1007 # consider updating to the first non-obsolete parent,
1008 # similar to what is current done for 'hg prune'
1008 # similar to what is current done for 'hg prune'
1009
1009
1010 if successors:
1010 if successors:
1011 # flatten the list here handles both divergent (len > 1)
1011 # flatten the list here handles both divergent (len > 1)
1012 # and the usual case (len = 1)
1012 # and the usual case (len = 1)
1013 successors = [n for sub in successors for n in sub]
1013 successors = [n for sub in successors for n in sub]
1014
1014
1015 # get the max revision for the given successors set,
1015 # get the max revision for the given successors set,
1016 # i.e. the 'tip' of a set
1016 # i.e. the 'tip' of a set
1017 node = repo.revs('max(%ln)', successors).first()
1017 node = repo.revs('max(%ln)', successors).first()
1018 pas = [p1]
1018 pas = [p1]
1019
1019
1020 overwrite = force and not branchmerge
1020 overwrite = force and not branchmerge
1021
1021
1022 p2 = repo[node]
1022 p2 = repo[node]
1023 if pas[0] is None:
1023 if pas[0] is None:
1024 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1024 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1025 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1025 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1026 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1026 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1027 else:
1027 else:
1028 pas = [p1.ancestor(p2, warn=branchmerge)]
1028 pas = [p1.ancestor(p2, warn=branchmerge)]
1029
1029
1030 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1030 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1031
1031
1032 ### check phase
1032 ### check phase
1033 if not overwrite and len(pl) > 1:
1033 if not overwrite and len(pl) > 1:
1034 raise util.Abort(_("outstanding uncommitted merge"))
1034 raise util.Abort(_("outstanding uncommitted merge"))
1035 if branchmerge:
1035 if branchmerge:
1036 if pas == [p2]:
1036 if pas == [p2]:
1037 raise util.Abort(_("merging with a working directory ancestor"
1037 raise util.Abort(_("merging with a working directory ancestor"
1038 " has no effect"))
1038 " has no effect"))
1039 elif pas == [p1]:
1039 elif pas == [p1]:
1040 if not mergeancestor and p1.branch() == p2.branch():
1040 if not mergeancestor and p1.branch() == p2.branch():
1041 raise util.Abort(_("nothing to merge"),
1041 raise util.Abort(_("nothing to merge"),
1042 hint=_("use 'hg update' "
1042 hint=_("use 'hg update' "
1043 "or check 'hg heads'"))
1043 "or check 'hg heads'"))
1044 if not force and (wc.files() or wc.deleted()):
1044 if not force and (wc.files() or wc.deleted()):
1045 raise util.Abort(_("uncommitted changes"),
1045 raise util.Abort(_("uncommitted changes"),
1046 hint=_("use 'hg status' to list changes"))
1046 hint=_("use 'hg status' to list changes"))
1047 for s in sorted(wc.substate):
1047 for s in sorted(wc.substate):
1048 wc.sub(s).bailifchanged()
1048 wc.sub(s).bailifchanged()
1049
1049
1050 elif not overwrite:
1050 elif not overwrite:
1051 if p1 == p2: # no-op update
1051 if p1 == p2: # no-op update
1052 # call the hooks and exit early
1052 # call the hooks and exit early
1053 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1053 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1054 repo.hook('update', parent1=xp2, parent2='', error=0)
1054 repo.hook('update', parent1=xp2, parent2='', error=0)
1055 return 0, 0, 0, 0
1055 return 0, 0, 0, 0
1056
1056
1057 if pas not in ([p1], [p2]): # nonlinear
1057 if pas not in ([p1], [p2]): # nonlinear
1058 dirty = wc.dirty(missing=True)
1058 dirty = wc.dirty(missing=True)
1059 if dirty or onode is None:
1059 if dirty or onode is None:
1060 # Branching is a bit strange to ensure we do the minimal
1060 # Branching is a bit strange to ensure we do the minimal
1061 # amount of call to obsolete.background.
1061 # amount of call to obsolete.background.
1062 foreground = obsolete.foreground(repo, [p1.node()])
1062 foreground = obsolete.foreground(repo, [p1.node()])
1063 # note: the <node> variable contains a random identifier
1063 # note: the <node> variable contains a random identifier
1064 if repo[node].node() in foreground:
1064 if repo[node].node() in foreground:
1065 pas = [p1] # allow updating to successors
1065 pas = [p1] # allow updating to successors
1066 elif dirty:
1066 elif dirty:
1067 msg = _("uncommitted changes")
1067 msg = _("uncommitted changes")
1068 if onode is None:
1068 if onode is None:
1069 hint = _("commit and merge, or update --clean to"
1069 hint = _("commit and merge, or update --clean to"
1070 " discard changes")
1070 " discard changes")
1071 else:
1071 else:
1072 hint = _("commit or update --clean to discard"
1072 hint = _("commit or update --clean to discard"
1073 " changes")
1073 " changes")
1074 raise util.Abort(msg, hint=hint)
1074 raise util.Abort(msg, hint=hint)
1075 else: # node is none
1075 else: # node is none
1076 msg = _("not a linear update")
1076 msg = _("not a linear update")
1077 hint = _("merge or update --check to force update")
1077 hint = _("merge or update --check to force update")
1078 raise util.Abort(msg, hint=hint)
1078 raise util.Abort(msg, hint=hint)
1079 else:
1079 else:
1080 # Allow jumping branches if clean and specific rev given
1080 # Allow jumping branches if clean and specific rev given
1081 pas = [p1]
1081 pas = [p1]
1082
1082
1083 followcopies = False
1083 followcopies = False
1084 if overwrite:
1084 if overwrite:
1085 pas = [wc]
1085 pas = [wc]
1086 elif pas == [p2]: # backwards
1086 elif pas == [p2]: # backwards
1087 pas = [wc.p1()]
1087 pas = [wc.p1()]
1088 elif not branchmerge and not wc.dirty(missing=True):
1088 elif not branchmerge and not wc.dirty(missing=True):
1089 pass
1089 pass
1090 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1090 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1091 followcopies = True
1091 followcopies = True
1092
1092
1093 ### calculate phase
1093 ### calculate phase
1094 actionbyfile, diverge, renamedelete = calculateupdates(
1094 actionbyfile, diverge, renamedelete = calculateupdates(
1095 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1095 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1096 followcopies)
1096 followcopies)
1097 # Convert to dictionary-of-lists format
1097 # Convert to dictionary-of-lists format
1098 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
1098 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
1099 for f, (m, args, msg) in actionbyfile.iteritems():
1099 for f, (m, args, msg) in actionbyfile.iteritems():
1100 if m not in actions:
1100 if m not in actions:
1101 actions[m] = []
1101 actions[m] = []
1102 actions[m].append((f, args, msg))
1102 actions[m].append((f, args, msg))
1103
1103
1104 if not util.checkcase(repo.path):
1104 if not util.checkcase(repo.path):
1105 # check collision between files only in p2 for clean update
1105 # check collision between files only in p2 for clean update
1106 if (not branchmerge and
1106 if (not branchmerge and
1107 (force or not wc.dirty(missing=True, branch=False))):
1107 (force or not wc.dirty(missing=True, branch=False))):
1108 _checkcollision(repo, p2.manifest(), None)
1108 _checkcollision(repo, p2.manifest(), None)
1109 else:
1109 else:
1110 _checkcollision(repo, wc.manifest(), actions)
1110 _checkcollision(repo, wc.manifest(), actions)
1111
1111
1112 # Prompt and create actions. TODO: Move this towards resolve phase.
1112 # Prompt and create actions. TODO: Move this towards resolve phase.
1113 for f, args, msg in sorted(actions['cd']):
1113 for f, args, msg in sorted(actions['cd']):
1114 if repo.ui.promptchoice(
1114 if repo.ui.promptchoice(
1115 _("local changed %s which remote deleted\n"
1115 _("local changed %s which remote deleted\n"
1116 "use (c)hanged version or (d)elete?"
1116 "use (c)hanged version or (d)elete?"
1117 "$$ &Changed $$ &Delete") % f, 0):
1117 "$$ &Changed $$ &Delete") % f, 0):
1118 actions['r'].append((f, None, "prompt delete"))
1118 actions['r'].append((f, None, "prompt delete"))
1119 else:
1119 else:
1120 actions['a'].append((f, None, "prompt keep"))
1120 actions['a'].append((f, None, "prompt keep"))
1121 del actions['cd'][:]
1121 del actions['cd'][:]
1122
1122
1123 for f, args, msg in sorted(actions['dc']):
1123 for f, args, msg in sorted(actions['dc']):
1124 flags, = args
1124 flags, = args
1125 if repo.ui.promptchoice(
1125 if repo.ui.promptchoice(
1126 _("remote changed %s which local deleted\n"
1126 _("remote changed %s which local deleted\n"
1127 "use (c)hanged version or leave (d)eleted?"
1127 "use (c)hanged version or leave (d)eleted?"
1128 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1128 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1129 actions['g'].append((f, (flags,), "prompt recreating"))
1129 actions['g'].append((f, (flags,), "prompt recreating"))
1130 del actions['dc'][:]
1130 del actions['dc'][:]
1131
1131
1132 ### apply phase
1132 ### apply phase
1133 if not branchmerge: # just jump to the new rev
1133 if not branchmerge: # just jump to the new rev
1134 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1134 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1135 if not partial:
1135 if not partial:
1136 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1136 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1137 # note that we're in the middle of an update
1137 # note that we're in the middle of an update
1138 repo.vfs.write('updatestate', p2.hex())
1138 repo.vfs.write('updatestate', p2.hex())
1139
1139
1140 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1140 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1141
1141
1142 # divergent renames
1142 # divergent renames
1143 for f, fl in sorted(diverge.iteritems()):
1143 for f, fl in sorted(diverge.iteritems()):
1144 repo.ui.warn(_("note: possible conflict - %s was renamed "
1144 repo.ui.warn(_("note: possible conflict - %s was renamed "
1145 "multiple times to:\n") % f)
1145 "multiple times to:\n") % f)
1146 for nf in fl:
1146 for nf in fl:
1147 repo.ui.warn(" %s\n" % nf)
1147 repo.ui.warn(" %s\n" % nf)
1148
1148
1149 # rename and delete
1149 # rename and delete
1150 for f, fl in sorted(renamedelete.iteritems()):
1150 for f, fl in sorted(renamedelete.iteritems()):
1151 repo.ui.warn(_("note: possible conflict - %s was deleted "
1151 repo.ui.warn(_("note: possible conflict - %s was deleted "
1152 "and renamed to:\n") % f)
1152 "and renamed to:\n") % f)
1153 for nf in fl:
1153 for nf in fl:
1154 repo.ui.warn(" %s\n" % nf)
1154 repo.ui.warn(" %s\n" % nf)
1155
1155
1156 if not partial:
1156 if not partial:
1157 repo.dirstate.beginparentchange()
1157 repo.dirstate.beginparentchange()
1158 repo.setparents(fp1, fp2)
1158 repo.setparents(fp1, fp2)
1159 recordupdates(repo, actions, branchmerge)
1159 recordupdates(repo, actions, branchmerge)
1160 # update completed, clear state
1160 # update completed, clear state
1161 util.unlink(repo.join('updatestate'))
1161 util.unlink(repo.join('updatestate'))
1162
1162
1163 if not branchmerge:
1163 if not branchmerge:
1164 repo.dirstate.setbranch(p2.branch())
1164 repo.dirstate.setbranch(p2.branch())
1165 repo.dirstate.endparentchange()
1165 repo.dirstate.endparentchange()
1166 finally:
1166 finally:
1167 wlock.release()
1167 wlock.release()
1168
1168
1169 if not partial:
1169 if not partial:
1170 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1170 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1171 return stats
1171 return stats
1172
1172
1173 def graft(repo, ctx, pctx, labels):
1173 def graft(repo, ctx, pctx, labels):
1174 """Do a graft-like merge.
1174 """Do a graft-like merge.
1175
1175
1176 This is a merge where the merge ancestor is chosen such that one
1176 This is a merge where the merge ancestor is chosen such that one
1177 or more changesets are grafted onto the current changeset. In
1177 or more changesets are grafted onto the current changeset. In
1178 addition to the merge, this fixes up the dirstate to include only
1178 addition to the merge, this fixes up the dirstate to include only
1179 a single parent and tries to duplicate any renames/copies
1179 a single parent and tries to duplicate any renames/copies
1180 appropriately.
1180 appropriately.
1181
1181
1182 ctx - changeset to rebase
1182 ctx - changeset to rebase
1183 pctx - merge base, usually ctx.p1()
1183 pctx - merge base, usually ctx.p1()
1184 labels - merge labels eg ['local', 'graft']
1184 labels - merge labels eg ['local', 'graft']
1185
1185
1186 """
1186 """
1187 # If we're grafting a descendant onto an ancestor, be sure to pass
1188 # mergeancestor=True to update. This does two things: 1) allows the merge if
1189 # the destination is the same as the parent of the ctx (so we can use graft
1190 # to copy commits), and 2) informs update that the incoming changes are
1191 # newer than the destination so it doesn't prompt about "remote changed foo
1192 # which local deleted".
1193 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1187
1194
1188 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1195 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1189 labels=labels)
1196 mergeancestor=mergeancestor, labels=labels)
1197
1190 # drop the second merge parent
1198 # drop the second merge parent
1191 repo.dirstate.beginparentchange()
1199 repo.dirstate.beginparentchange()
1192 repo.setparents(repo['.'].node(), nullid)
1200 repo.setparents(repo['.'].node(), nullid)
1193 repo.dirstate.write()
1201 repo.dirstate.write()
1194 # fix up dirstate for copies and renames
1202 # fix up dirstate for copies and renames
1195 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1203 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1196 repo.dirstate.endparentchange()
1204 repo.dirstate.endparentchange()
1197 return stats
1205 return stats
@@ -1,732 +1,758
1 Create a repo with some stuff in it:
1 Create a repo with some stuff in it:
2
2
3 $ hg init a
3 $ hg init a
4 $ cd a
4 $ cd a
5 $ echo a > a
5 $ echo a > a
6 $ echo a > d
6 $ echo a > d
7 $ echo a > e
7 $ echo a > e
8 $ hg ci -qAm0
8 $ hg ci -qAm0
9 $ echo b > a
9 $ echo b > a
10 $ hg ci -m1 -u bar
10 $ hg ci -m1 -u bar
11 $ hg mv a b
11 $ hg mv a b
12 $ hg ci -m2
12 $ hg ci -m2
13 $ hg cp b c
13 $ hg cp b c
14 $ hg ci -m3 -u baz
14 $ hg ci -m3 -u baz
15 $ echo b > d
15 $ echo b > d
16 $ echo f > e
16 $ echo f > e
17 $ hg ci -m4
17 $ hg ci -m4
18 $ hg up -q 3
18 $ hg up -q 3
19 $ echo b > e
19 $ echo b > e
20 $ hg branch -q stable
20 $ hg branch -q stable
21 $ hg ci -m5
21 $ hg ci -m5
22 $ hg merge -q default --tool internal:local
22 $ hg merge -q default --tool internal:local
23 $ hg branch -q default
23 $ hg branch -q default
24 $ hg ci -m6
24 $ hg ci -m6
25 $ hg phase --public 3
25 $ hg phase --public 3
26 $ hg phase --force --secret 6
26 $ hg phase --force --secret 6
27
27
28 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
28 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
29 @ test@6.secret: 6
29 @ test@6.secret: 6
30 |\
30 |\
31 | o test@5.draft: 5
31 | o test@5.draft: 5
32 | |
32 | |
33 o | test@4.draft: 4
33 o | test@4.draft: 4
34 |/
34 |/
35 o baz@3.public: 3
35 o baz@3.public: 3
36 |
36 |
37 o test@2.public: 2
37 o test@2.public: 2
38 |
38 |
39 o bar@1.public: 1
39 o bar@1.public: 1
40 |
40 |
41 o test@0.public: 0
41 o test@0.public: 0
42
42
43
43
44 Need to specify a rev:
44 Need to specify a rev:
45
45
46 $ hg graft
46 $ hg graft
47 abort: no revisions specified
47 abort: no revisions specified
48 [255]
48 [255]
49
49
50 Can't graft ancestor:
50 Can't graft ancestor:
51
51
52 $ hg graft 1 2
52 $ hg graft 1 2
53 skipping ancestor revision 1:5d205f8b35b6
53 skipping ancestor revision 1:5d205f8b35b6
54 skipping ancestor revision 2:5c095ad7e90f
54 skipping ancestor revision 2:5c095ad7e90f
55 [255]
55 [255]
56
56
57 Specify revisions with -r:
57 Specify revisions with -r:
58
58
59 $ hg graft -r 1 -r 2
59 $ hg graft -r 1 -r 2
60 skipping ancestor revision 1:5d205f8b35b6
60 skipping ancestor revision 1:5d205f8b35b6
61 skipping ancestor revision 2:5c095ad7e90f
61 skipping ancestor revision 2:5c095ad7e90f
62 [255]
62 [255]
63
63
64 $ hg graft -r 1 2
64 $ hg graft -r 1 2
65 skipping ancestor revision 2:5c095ad7e90f
65 skipping ancestor revision 2:5c095ad7e90f
66 skipping ancestor revision 1:5d205f8b35b6
66 skipping ancestor revision 1:5d205f8b35b6
67 [255]
67 [255]
68
68
69 Can't graft with dirty wd:
69 Can't graft with dirty wd:
70
70
71 $ hg up -q 0
71 $ hg up -q 0
72 $ echo foo > a
72 $ echo foo > a
73 $ hg graft 1
73 $ hg graft 1
74 abort: uncommitted changes
74 abort: uncommitted changes
75 [255]
75 [255]
76 $ hg revert a
76 $ hg revert a
77
77
78 Graft a rename:
78 Graft a rename:
79 (this also tests that editor is invoked if '--edit' is specified)
79 (this also tests that editor is invoked if '--edit' is specified)
80
80
81 $ hg status --rev "2^1" --rev 2
81 $ hg status --rev "2^1" --rev 2
82 A b
82 A b
83 R a
83 R a
84 $ HGEDITOR=cat hg graft 2 -u foo --edit
84 $ HGEDITOR=cat hg graft 2 -u foo --edit
85 grafting 2:5c095ad7e90f "2"
85 grafting 2:5c095ad7e90f "2"
86 merging a and b to b
86 merging a and b to b
87 2
87 2
88
88
89
89
90 HG: Enter commit message. Lines beginning with 'HG:' are removed.
90 HG: Enter commit message. Lines beginning with 'HG:' are removed.
91 HG: Leave message empty to abort commit.
91 HG: Leave message empty to abort commit.
92 HG: --
92 HG: --
93 HG: user: foo
93 HG: user: foo
94 HG: branch 'default'
94 HG: branch 'default'
95 HG: added b
95 HG: added b
96 HG: removed a
96 HG: removed a
97 $ hg export tip --git
97 $ hg export tip --git
98 # HG changeset patch
98 # HG changeset patch
99 # User foo
99 # User foo
100 # Date 0 0
100 # Date 0 0
101 # Thu Jan 01 00:00:00 1970 +0000
101 # Thu Jan 01 00:00:00 1970 +0000
102 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
102 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
103 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
103 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
104 2
104 2
105
105
106 diff --git a/a b/b
106 diff --git a/a b/b
107 rename from a
107 rename from a
108 rename to b
108 rename to b
109
109
110 Look for extra:source
110 Look for extra:source
111
111
112 $ hg log --debug -r tip
112 $ hg log --debug -r tip
113 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
113 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
114 tag: tip
114 tag: tip
115 phase: draft
115 phase: draft
116 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
116 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
117 parent: -1:0000000000000000000000000000000000000000
117 parent: -1:0000000000000000000000000000000000000000
118 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
118 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
119 user: foo
119 user: foo
120 date: Thu Jan 01 00:00:00 1970 +0000
120 date: Thu Jan 01 00:00:00 1970 +0000
121 files+: b
121 files+: b
122 files-: a
122 files-: a
123 extra: branch=default
123 extra: branch=default
124 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
124 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
125 description:
125 description:
126 2
126 2
127
127
128
128
129
129
130 Graft out of order, skipping a merge and a duplicate
130 Graft out of order, skipping a merge and a duplicate
131 (this also tests that editor is not invoked if '--edit' is not specified)
131 (this also tests that editor is not invoked if '--edit' is not specified)
132
132
133 $ hg graft 1 5 4 3 'merge()' 2 -n
133 $ hg graft 1 5 4 3 'merge()' 2 -n
134 skipping ungraftable merge revision 6
134 skipping ungraftable merge revision 6
135 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
135 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
136 grafting 1:5d205f8b35b6 "1"
136 grafting 1:5d205f8b35b6 "1"
137 grafting 5:97f8bfe72746 "5"
137 grafting 5:97f8bfe72746 "5"
138 grafting 4:9c233e8e184d "4"
138 grafting 4:9c233e8e184d "4"
139 grafting 3:4c60f11aa304 "3"
139 grafting 3:4c60f11aa304 "3"
140
140
141 $ HGEDITOR=cat hg graft 1 5 4 3 'merge()' 2 --debug
141 $ HGEDITOR=cat hg graft 1 5 4 3 'merge()' 2 --debug
142 skipping ungraftable merge revision 6
142 skipping ungraftable merge revision 6
143 scanning for duplicate grafts
143 scanning for duplicate grafts
144 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
144 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
145 grafting 1:5d205f8b35b6 "1"
145 grafting 1:5d205f8b35b6 "1"
146 searching for copies back to rev 1
146 searching for copies back to rev 1
147 unmatched files in local:
147 unmatched files in local:
148 b
148 b
149 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
149 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
150 src: 'a' -> dst: 'b' *
150 src: 'a' -> dst: 'b' *
151 checking for directory renames
151 checking for directory renames
152 resolving manifests
152 resolving manifests
153 branchmerge: True, force: True, partial: False
153 branchmerge: True, force: True, partial: False
154 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
154 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
155 preserving b for resolve of b
155 preserving b for resolve of b
156 b: local copied/moved from a -> m
156 b: local copied/moved from a -> m
157 updating: b 1/1 files (100.00%)
157 updating: b 1/1 files (100.00%)
158 picked tool 'internal:merge' for b (binary False symlink False)
158 picked tool 'internal:merge' for b (binary False symlink False)
159 merging b and a to b
159 merging b and a to b
160 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
160 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
161 premerge successful
161 premerge successful
162 committing files:
162 committing files:
163 b
163 b
164 committing manifest
164 committing manifest
165 committing changelog
165 committing changelog
166 grafting 5:97f8bfe72746 "5"
166 grafting 5:97f8bfe72746 "5"
167 searching for copies back to rev 1
167 searching for copies back to rev 1
168 resolving manifests
168 resolving manifests
169 branchmerge: True, force: True, partial: False
169 branchmerge: True, force: True, partial: False
170 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
170 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
171 e: remote is newer -> g
171 e: remote is newer -> g
172 getting e
172 getting e
173 updating: e 1/1 files (100.00%)
173 updating: e 1/1 files (100.00%)
174 b: remote unchanged -> k
174 b: remote unchanged -> k
175 committing files:
175 committing files:
176 e
176 e
177 committing manifest
177 committing manifest
178 committing changelog
178 committing changelog
179 grafting 4:9c233e8e184d "4"
179 grafting 4:9c233e8e184d "4"
180 searching for copies back to rev 1
180 searching for copies back to rev 1
181 resolving manifests
181 resolving manifests
182 branchmerge: True, force: True, partial: False
182 branchmerge: True, force: True, partial: False
183 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
183 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
184 preserving e for resolve of e
184 preserving e for resolve of e
185 d: remote is newer -> g
185 d: remote is newer -> g
186 getting d
186 getting d
187 updating: d 1/2 files (50.00%)
187 updating: d 1/2 files (50.00%)
188 b: remote unchanged -> k
188 b: remote unchanged -> k
189 e: versions differ -> m
189 e: versions differ -> m
190 updating: e 2/2 files (100.00%)
190 updating: e 2/2 files (100.00%)
191 picked tool 'internal:merge' for e (binary False symlink False)
191 picked tool 'internal:merge' for e (binary False symlink False)
192 merging e
192 merging e
193 my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622
193 my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622
194 warning: conflicts during merge.
194 warning: conflicts during merge.
195 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
195 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
196 abort: unresolved conflicts, can't continue
196 abort: unresolved conflicts, can't continue
197 (use hg resolve and hg graft --continue)
197 (use hg resolve and hg graft --continue)
198 [255]
198 [255]
199
199
200 Commit while interrupted should fail:
200 Commit while interrupted should fail:
201
201
202 $ hg ci -m 'commit interrupted graft'
202 $ hg ci -m 'commit interrupted graft'
203 abort: graft in progress
203 abort: graft in progress
204 (use 'hg graft --continue' or 'hg update' to abort)
204 (use 'hg graft --continue' or 'hg update' to abort)
205 [255]
205 [255]
206
206
207 Abort the graft and try committing:
207 Abort the graft and try committing:
208
208
209 $ hg up -C .
209 $ hg up -C .
210 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
210 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
211 $ echo c >> e
211 $ echo c >> e
212 $ hg ci -mtest
212 $ hg ci -mtest
213
213
214 $ hg strip . --config extensions.strip=
214 $ hg strip . --config extensions.strip=
215 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
215 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
216 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
216 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
217
217
218 Graft again:
218 Graft again:
219
219
220 $ hg graft 1 5 4 3 'merge()' 2
220 $ hg graft 1 5 4 3 'merge()' 2
221 skipping ungraftable merge revision 6
221 skipping ungraftable merge revision 6
222 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
222 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
223 skipping revision 1:5d205f8b35b6 (already grafted to 8:6b9e5368ca4e)
223 skipping revision 1:5d205f8b35b6 (already grafted to 8:6b9e5368ca4e)
224 skipping revision 5:97f8bfe72746 (already grafted to 9:1905859650ec)
224 skipping revision 5:97f8bfe72746 (already grafted to 9:1905859650ec)
225 grafting 4:9c233e8e184d "4"
225 grafting 4:9c233e8e184d "4"
226 merging e
226 merging e
227 warning: conflicts during merge.
227 warning: conflicts during merge.
228 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
228 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
229 abort: unresolved conflicts, can't continue
229 abort: unresolved conflicts, can't continue
230 (use hg resolve and hg graft --continue)
230 (use hg resolve and hg graft --continue)
231 [255]
231 [255]
232
232
233 Continue without resolve should fail:
233 Continue without resolve should fail:
234
234
235 $ hg graft -c
235 $ hg graft -c
236 grafting 4:9c233e8e184d "4"
236 grafting 4:9c233e8e184d "4"
237 abort: unresolved merge conflicts (see "hg help resolve")
237 abort: unresolved merge conflicts (see "hg help resolve")
238 [255]
238 [255]
239
239
240 Fix up:
240 Fix up:
241
241
242 $ echo b > e
242 $ echo b > e
243 $ hg resolve -m e
243 $ hg resolve -m e
244 (no more unresolved files)
244 (no more unresolved files)
245
245
246 Continue with a revision should fail:
246 Continue with a revision should fail:
247
247
248 $ hg graft -c 6
248 $ hg graft -c 6
249 abort: can't specify --continue and revisions
249 abort: can't specify --continue and revisions
250 [255]
250 [255]
251
251
252 $ hg graft -c -r 6
252 $ hg graft -c -r 6
253 abort: can't specify --continue and revisions
253 abort: can't specify --continue and revisions
254 [255]
254 [255]
255
255
256 Continue for real, clobber usernames
256 Continue for real, clobber usernames
257
257
258 $ hg graft -c -U
258 $ hg graft -c -U
259 grafting 4:9c233e8e184d "4"
259 grafting 4:9c233e8e184d "4"
260 grafting 3:4c60f11aa304 "3"
260 grafting 3:4c60f11aa304 "3"
261
261
262 Compare with original:
262 Compare with original:
263
263
264 $ hg diff -r 6
264 $ hg diff -r 6
265 $ hg status --rev 0:. -C
265 $ hg status --rev 0:. -C
266 M d
266 M d
267 M e
267 M e
268 A b
268 A b
269 a
269 a
270 A c
270 A c
271 a
271 a
272 R a
272 R a
273
273
274 View graph:
274 View graph:
275
275
276 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
276 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
277 @ test@11.draft: 3
277 @ test@11.draft: 3
278 |
278 |
279 o test@10.draft: 4
279 o test@10.draft: 4
280 |
280 |
281 o test@9.draft: 5
281 o test@9.draft: 5
282 |
282 |
283 o bar@8.draft: 1
283 o bar@8.draft: 1
284 |
284 |
285 o foo@7.draft: 2
285 o foo@7.draft: 2
286 |
286 |
287 | o test@6.secret: 6
287 | o test@6.secret: 6
288 | |\
288 | |\
289 | | o test@5.draft: 5
289 | | o test@5.draft: 5
290 | | |
290 | | |
291 | o | test@4.draft: 4
291 | o | test@4.draft: 4
292 | |/
292 | |/
293 | o baz@3.public: 3
293 | o baz@3.public: 3
294 | |
294 | |
295 | o test@2.public: 2
295 | o test@2.public: 2
296 | |
296 | |
297 | o bar@1.public: 1
297 | o bar@1.public: 1
298 |/
298 |/
299 o test@0.public: 0
299 o test@0.public: 0
300
300
301 Graft again onto another branch should preserve the original source
301 Graft again onto another branch should preserve the original source
302 $ hg up -q 0
302 $ hg up -q 0
303 $ echo 'g'>g
303 $ echo 'g'>g
304 $ hg add g
304 $ hg add g
305 $ hg ci -m 7
305 $ hg ci -m 7
306 created new head
306 created new head
307 $ hg graft 7
307 $ hg graft 7
308 grafting 7:ef0ef43d49e7 "2"
308 grafting 7:ef0ef43d49e7 "2"
309
309
310 $ hg log -r 7 --template '{rev}:{node}\n'
310 $ hg log -r 7 --template '{rev}:{node}\n'
311 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
311 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
312 $ hg log -r 2 --template '{rev}:{node}\n'
312 $ hg log -r 2 --template '{rev}:{node}\n'
313 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
313 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
314
314
315 $ hg log --debug -r tip
315 $ hg log --debug -r tip
316 changeset: 13:9db0f28fd3747e92c57d015f53b5593aeec53c2d
316 changeset: 13:9db0f28fd3747e92c57d015f53b5593aeec53c2d
317 tag: tip
317 tag: tip
318 phase: draft
318 phase: draft
319 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
319 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
320 parent: -1:0000000000000000000000000000000000000000
320 parent: -1:0000000000000000000000000000000000000000
321 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
321 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
322 user: foo
322 user: foo
323 date: Thu Jan 01 00:00:00 1970 +0000
323 date: Thu Jan 01 00:00:00 1970 +0000
324 files+: b
324 files+: b
325 files-: a
325 files-: a
326 extra: branch=default
326 extra: branch=default
327 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
327 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
328 description:
328 description:
329 2
329 2
330
330
331
331
332 Disallow grafting an already grafted cset onto its original branch
332 Disallow grafting an already grafted cset onto its original branch
333 $ hg up -q 6
333 $ hg up -q 6
334 $ hg graft 7
334 $ hg graft 7
335 skipping already grafted revision 7:ef0ef43d49e7 (was grafted from 2:5c095ad7e90f)
335 skipping already grafted revision 7:ef0ef43d49e7 (was grafted from 2:5c095ad7e90f)
336 [255]
336 [255]
337
337
338 Disallow grafting already grafted csets with the same origin onto each other
338 Disallow grafting already grafted csets with the same origin onto each other
339 $ hg up -q 13
339 $ hg up -q 13
340 $ hg graft 2
340 $ hg graft 2
341 skipping revision 2:5c095ad7e90f (already grafted to 13:9db0f28fd374)
341 skipping revision 2:5c095ad7e90f (already grafted to 13:9db0f28fd374)
342 [255]
342 [255]
343 $ hg graft 7
343 $ hg graft 7
344 skipping already grafted revision 7:ef0ef43d49e7 (13:9db0f28fd374 also has origin 2:5c095ad7e90f)
344 skipping already grafted revision 7:ef0ef43d49e7 (13:9db0f28fd374 also has origin 2:5c095ad7e90f)
345 [255]
345 [255]
346
346
347 $ hg up -q 7
347 $ hg up -q 7
348 $ hg graft 2
348 $ hg graft 2
349 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
349 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
350 [255]
350 [255]
351 $ hg graft tip
351 $ hg graft tip
352 skipping already grafted revision 13:9db0f28fd374 (7:ef0ef43d49e7 also has origin 2:5c095ad7e90f)
352 skipping already grafted revision 13:9db0f28fd374 (7:ef0ef43d49e7 also has origin 2:5c095ad7e90f)
353 [255]
353 [255]
354
354
355 Graft with --log
355 Graft with --log
356
356
357 $ hg up -Cq 1
357 $ hg up -Cq 1
358 $ hg graft 3 --log -u foo
358 $ hg graft 3 --log -u foo
359 grafting 3:4c60f11aa304 "3"
359 grafting 3:4c60f11aa304 "3"
360 warning: can't find ancestor for 'c' copied from 'b'!
360 warning: can't find ancestor for 'c' copied from 'b'!
361 $ hg log --template '{rev} {parents} {desc}\n' -r tip
361 $ hg log --template '{rev} {parents} {desc}\n' -r tip
362 14 1:5d205f8b35b6 3
362 14 1:5d205f8b35b6 3
363 (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
363 (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
364
364
365 Resolve conflicted graft
365 Resolve conflicted graft
366 $ hg up -q 0
366 $ hg up -q 0
367 $ echo b > a
367 $ echo b > a
368 $ hg ci -m 8
368 $ hg ci -m 8
369 created new head
369 created new head
370 $ echo c > a
370 $ echo c > a
371 $ hg ci -m 9
371 $ hg ci -m 9
372 $ hg graft 1 --tool internal:fail
372 $ hg graft 1 --tool internal:fail
373 grafting 1:5d205f8b35b6 "1"
373 grafting 1:5d205f8b35b6 "1"
374 abort: unresolved conflicts, can't continue
374 abort: unresolved conflicts, can't continue
375 (use hg resolve and hg graft --continue)
375 (use hg resolve and hg graft --continue)
376 [255]
376 [255]
377 $ hg resolve --all
377 $ hg resolve --all
378 merging a
378 merging a
379 warning: conflicts during merge.
379 warning: conflicts during merge.
380 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
380 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
381 [1]
381 [1]
382 $ cat a
382 $ cat a
383 <<<<<<< local: aaa4406d4f0a - test: 9
383 <<<<<<< local: aaa4406d4f0a - test: 9
384 c
384 c
385 =======
385 =======
386 b
386 b
387 >>>>>>> other: 5d205f8b35b6 - bar: 1
387 >>>>>>> other: 5d205f8b35b6 - bar: 1
388 $ echo b > a
388 $ echo b > a
389 $ hg resolve -m a
389 $ hg resolve -m a
390 (no more unresolved files)
390 (no more unresolved files)
391 $ hg graft -c
391 $ hg graft -c
392 grafting 1:5d205f8b35b6 "1"
392 grafting 1:5d205f8b35b6 "1"
393 $ hg export tip --git
393 $ hg export tip --git
394 # HG changeset patch
394 # HG changeset patch
395 # User bar
395 # User bar
396 # Date 0 0
396 # Date 0 0
397 # Thu Jan 01 00:00:00 1970 +0000
397 # Thu Jan 01 00:00:00 1970 +0000
398 # Node ID f67661df0c4804d301f064f332b57e7d5ddaf2be
398 # Node ID f67661df0c4804d301f064f332b57e7d5ddaf2be
399 # Parent aaa4406d4f0ae9befd6e58c82ec63706460cbca6
399 # Parent aaa4406d4f0ae9befd6e58c82ec63706460cbca6
400 1
400 1
401
401
402 diff --git a/a b/a
402 diff --git a/a b/a
403 --- a/a
403 --- a/a
404 +++ b/a
404 +++ b/a
405 @@ -1,1 +1,1 @@
405 @@ -1,1 +1,1 @@
406 -c
406 -c
407 +b
407 +b
408
408
409 Resolve conflicted graft with rename
409 Resolve conflicted graft with rename
410 $ echo c > a
410 $ echo c > a
411 $ hg ci -m 10
411 $ hg ci -m 10
412 $ hg graft 2 --tool internal:fail
412 $ hg graft 2 --tool internal:fail
413 grafting 2:5c095ad7e90f "2"
413 grafting 2:5c095ad7e90f "2"
414 abort: unresolved conflicts, can't continue
414 abort: unresolved conflicts, can't continue
415 (use hg resolve and hg graft --continue)
415 (use hg resolve and hg graft --continue)
416 [255]
416 [255]
417 $ hg resolve --all
417 $ hg resolve --all
418 merging a and b to b
418 merging a and b to b
419 (no more unresolved files)
419 (no more unresolved files)
420 $ hg graft -c
420 $ hg graft -c
421 grafting 2:5c095ad7e90f "2"
421 grafting 2:5c095ad7e90f "2"
422 $ hg export tip --git
422 $ hg export tip --git
423 # HG changeset patch
423 # HG changeset patch
424 # User test
424 # User test
425 # Date 0 0
425 # Date 0 0
426 # Thu Jan 01 00:00:00 1970 +0000
426 # Thu Jan 01 00:00:00 1970 +0000
427 # Node ID 9627f653b421c61fc1ea4c4e366745070fa3d2bc
427 # Node ID 9627f653b421c61fc1ea4c4e366745070fa3d2bc
428 # Parent ee295f490a40b97f3d18dd4c4f1c8936c233b612
428 # Parent ee295f490a40b97f3d18dd4c4f1c8936c233b612
429 2
429 2
430
430
431 diff --git a/a b/b
431 diff --git a/a b/b
432 rename from a
432 rename from a
433 rename to b
433 rename to b
434
434
435 Test simple origin(), with and without args
435 Test simple origin(), with and without args
436 $ hg log -r 'origin()'
436 $ hg log -r 'origin()'
437 changeset: 1:5d205f8b35b6
437 changeset: 1:5d205f8b35b6
438 user: bar
438 user: bar
439 date: Thu Jan 01 00:00:00 1970 +0000
439 date: Thu Jan 01 00:00:00 1970 +0000
440 summary: 1
440 summary: 1
441
441
442 changeset: 2:5c095ad7e90f
442 changeset: 2:5c095ad7e90f
443 user: test
443 user: test
444 date: Thu Jan 01 00:00:00 1970 +0000
444 date: Thu Jan 01 00:00:00 1970 +0000
445 summary: 2
445 summary: 2
446
446
447 changeset: 3:4c60f11aa304
447 changeset: 3:4c60f11aa304
448 user: baz
448 user: baz
449 date: Thu Jan 01 00:00:00 1970 +0000
449 date: Thu Jan 01 00:00:00 1970 +0000
450 summary: 3
450 summary: 3
451
451
452 changeset: 4:9c233e8e184d
452 changeset: 4:9c233e8e184d
453 user: test
453 user: test
454 date: Thu Jan 01 00:00:00 1970 +0000
454 date: Thu Jan 01 00:00:00 1970 +0000
455 summary: 4
455 summary: 4
456
456
457 changeset: 5:97f8bfe72746
457 changeset: 5:97f8bfe72746
458 branch: stable
458 branch: stable
459 parent: 3:4c60f11aa304
459 parent: 3:4c60f11aa304
460 user: test
460 user: test
461 date: Thu Jan 01 00:00:00 1970 +0000
461 date: Thu Jan 01 00:00:00 1970 +0000
462 summary: 5
462 summary: 5
463
463
464 $ hg log -r 'origin(7)'
464 $ hg log -r 'origin(7)'
465 changeset: 2:5c095ad7e90f
465 changeset: 2:5c095ad7e90f
466 user: test
466 user: test
467 date: Thu Jan 01 00:00:00 1970 +0000
467 date: Thu Jan 01 00:00:00 1970 +0000
468 summary: 2
468 summary: 2
469
469
470 Now transplant a graft to test following through copies
470 Now transplant a graft to test following through copies
471 $ hg up -q 0
471 $ hg up -q 0
472 $ hg branch -q dev
472 $ hg branch -q dev
473 $ hg ci -qm "dev branch"
473 $ hg ci -qm "dev branch"
474 $ hg --config extensions.transplant= transplant -q 7
474 $ hg --config extensions.transplant= transplant -q 7
475 $ hg log -r 'origin(.)'
475 $ hg log -r 'origin(.)'
476 changeset: 2:5c095ad7e90f
476 changeset: 2:5c095ad7e90f
477 user: test
477 user: test
478 date: Thu Jan 01 00:00:00 1970 +0000
478 date: Thu Jan 01 00:00:00 1970 +0000
479 summary: 2
479 summary: 2
480
480
481 Test that the graft and transplant markers in extra are converted, allowing
481 Test that the graft and transplant markers in extra are converted, allowing
482 origin() to still work. Note that these recheck the immediately preceeding two
482 origin() to still work. Note that these recheck the immediately preceeding two
483 tests.
483 tests.
484 $ hg --quiet --config extensions.convert= --config convert.hg.saverev=True convert . ../converted
484 $ hg --quiet --config extensions.convert= --config convert.hg.saverev=True convert . ../converted
485
485
486 The graft case
486 The graft case
487 $ hg -R ../converted log -r 7 --template "{rev}: {node}\n{join(extras, '\n')}\n"
487 $ hg -R ../converted log -r 7 --template "{rev}: {node}\n{join(extras, '\n')}\n"
488 7: 7ae846e9111fc8f57745634250c7b9ac0a60689b
488 7: 7ae846e9111fc8f57745634250c7b9ac0a60689b
489 branch=default
489 branch=default
490 convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
490 convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
491 source=e0213322b2c1a5d5d236c74e79666441bee67a7d
491 source=e0213322b2c1a5d5d236c74e79666441bee67a7d
492 $ hg -R ../converted log -r 'origin(7)'
492 $ hg -R ../converted log -r 'origin(7)'
493 changeset: 2:e0213322b2c1
493 changeset: 2:e0213322b2c1
494 user: test
494 user: test
495 date: Thu Jan 01 00:00:00 1970 +0000
495 date: Thu Jan 01 00:00:00 1970 +0000
496 summary: 2
496 summary: 2
497
497
498 Test that template correctly expands more than one 'extra' (issue4362)
498 Test that template correctly expands more than one 'extra' (issue4362)
499 $ hg -R ../converted log -r 7 --template "{extras % ' Extra: {extra}\n'}"
499 $ hg -R ../converted log -r 7 --template "{extras % ' Extra: {extra}\n'}"
500 Extra: branch=default
500 Extra: branch=default
501 Extra: convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
501 Extra: convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
502 Extra: source=e0213322b2c1a5d5d236c74e79666441bee67a7d
502 Extra: source=e0213322b2c1a5d5d236c74e79666441bee67a7d
503
503
504 The transplant case
504 The transplant case
505 $ hg -R ../converted log -r tip --template "{rev}: {node}\n{join(extras, '\n')}\n"
505 $ hg -R ../converted log -r tip --template "{rev}: {node}\n{join(extras, '\n')}\n"
506 21: fbb6c5cc81002f2b4b49c9d731404688bcae5ade
506 21: fbb6c5cc81002f2b4b49c9d731404688bcae5ade
507 branch=dev
507 branch=dev
508 convert_revision=7e61b508e709a11d28194a5359bc3532d910af21
508 convert_revision=7e61b508e709a11d28194a5359bc3532d910af21
509 transplant_source=z\xe8F\xe9\x11\x1f\xc8\xf5wEcBP\xc7\xb9\xac (esc)
509 transplant_source=z\xe8F\xe9\x11\x1f\xc8\xf5wEcBP\xc7\xb9\xac (esc)
510 `h\x9b (esc)
510 `h\x9b (esc)
511 $ hg -R ../converted log -r 'origin(tip)'
511 $ hg -R ../converted log -r 'origin(tip)'
512 changeset: 2:e0213322b2c1
512 changeset: 2:e0213322b2c1
513 user: test
513 user: test
514 date: Thu Jan 01 00:00:00 1970 +0000
514 date: Thu Jan 01 00:00:00 1970 +0000
515 summary: 2
515 summary: 2
516
516
517
517
518 Test simple destination
518 Test simple destination
519 $ hg log -r 'destination()'
519 $ hg log -r 'destination()'
520 changeset: 7:ef0ef43d49e7
520 changeset: 7:ef0ef43d49e7
521 parent: 0:68795b066622
521 parent: 0:68795b066622
522 user: foo
522 user: foo
523 date: Thu Jan 01 00:00:00 1970 +0000
523 date: Thu Jan 01 00:00:00 1970 +0000
524 summary: 2
524 summary: 2
525
525
526 changeset: 8:6b9e5368ca4e
526 changeset: 8:6b9e5368ca4e
527 user: bar
527 user: bar
528 date: Thu Jan 01 00:00:00 1970 +0000
528 date: Thu Jan 01 00:00:00 1970 +0000
529 summary: 1
529 summary: 1
530
530
531 changeset: 9:1905859650ec
531 changeset: 9:1905859650ec
532 user: test
532 user: test
533 date: Thu Jan 01 00:00:00 1970 +0000
533 date: Thu Jan 01 00:00:00 1970 +0000
534 summary: 5
534 summary: 5
535
535
536 changeset: 10:52dc0b4c6907
536 changeset: 10:52dc0b4c6907
537 user: test
537 user: test
538 date: Thu Jan 01 00:00:00 1970 +0000
538 date: Thu Jan 01 00:00:00 1970 +0000
539 summary: 4
539 summary: 4
540
540
541 changeset: 11:882b35362a6b
541 changeset: 11:882b35362a6b
542 user: test
542 user: test
543 date: Thu Jan 01 00:00:00 1970 +0000
543 date: Thu Jan 01 00:00:00 1970 +0000
544 summary: 3
544 summary: 3
545
545
546 changeset: 13:9db0f28fd374
546 changeset: 13:9db0f28fd374
547 user: foo
547 user: foo
548 date: Thu Jan 01 00:00:00 1970 +0000
548 date: Thu Jan 01 00:00:00 1970 +0000
549 summary: 2
549 summary: 2
550
550
551 changeset: 14:f64defefacee
551 changeset: 14:f64defefacee
552 parent: 1:5d205f8b35b6
552 parent: 1:5d205f8b35b6
553 user: foo
553 user: foo
554 date: Thu Jan 01 00:00:00 1970 +0000
554 date: Thu Jan 01 00:00:00 1970 +0000
555 summary: 3
555 summary: 3
556
556
557 changeset: 17:f67661df0c48
557 changeset: 17:f67661df0c48
558 user: bar
558 user: bar
559 date: Thu Jan 01 00:00:00 1970 +0000
559 date: Thu Jan 01 00:00:00 1970 +0000
560 summary: 1
560 summary: 1
561
561
562 changeset: 19:9627f653b421
562 changeset: 19:9627f653b421
563 user: test
563 user: test
564 date: Thu Jan 01 00:00:00 1970 +0000
564 date: Thu Jan 01 00:00:00 1970 +0000
565 summary: 2
565 summary: 2
566
566
567 changeset: 21:7e61b508e709
567 changeset: 21:7e61b508e709
568 branch: dev
568 branch: dev
569 tag: tip
569 tag: tip
570 user: foo
570 user: foo
571 date: Thu Jan 01 00:00:00 1970 +0000
571 date: Thu Jan 01 00:00:00 1970 +0000
572 summary: 2
572 summary: 2
573
573
574 $ hg log -r 'destination(2)'
574 $ hg log -r 'destination(2)'
575 changeset: 7:ef0ef43d49e7
575 changeset: 7:ef0ef43d49e7
576 parent: 0:68795b066622
576 parent: 0:68795b066622
577 user: foo
577 user: foo
578 date: Thu Jan 01 00:00:00 1970 +0000
578 date: Thu Jan 01 00:00:00 1970 +0000
579 summary: 2
579 summary: 2
580
580
581 changeset: 13:9db0f28fd374
581 changeset: 13:9db0f28fd374
582 user: foo
582 user: foo
583 date: Thu Jan 01 00:00:00 1970 +0000
583 date: Thu Jan 01 00:00:00 1970 +0000
584 summary: 2
584 summary: 2
585
585
586 changeset: 19:9627f653b421
586 changeset: 19:9627f653b421
587 user: test
587 user: test
588 date: Thu Jan 01 00:00:00 1970 +0000
588 date: Thu Jan 01 00:00:00 1970 +0000
589 summary: 2
589 summary: 2
590
590
591 changeset: 21:7e61b508e709
591 changeset: 21:7e61b508e709
592 branch: dev
592 branch: dev
593 tag: tip
593 tag: tip
594 user: foo
594 user: foo
595 date: Thu Jan 01 00:00:00 1970 +0000
595 date: Thu Jan 01 00:00:00 1970 +0000
596 summary: 2
596 summary: 2
597
597
598 Transplants of grafts can find a destination...
598 Transplants of grafts can find a destination...
599 $ hg log -r 'destination(7)'
599 $ hg log -r 'destination(7)'
600 changeset: 21:7e61b508e709
600 changeset: 21:7e61b508e709
601 branch: dev
601 branch: dev
602 tag: tip
602 tag: tip
603 user: foo
603 user: foo
604 date: Thu Jan 01 00:00:00 1970 +0000
604 date: Thu Jan 01 00:00:00 1970 +0000
605 summary: 2
605 summary: 2
606
606
607 ... grafts of grafts unfortunately can't
607 ... grafts of grafts unfortunately can't
608 $ hg graft -q 13
608 $ hg graft -q 13
609 warning: can't find ancestor for 'b' copied from 'a'!
609 warning: can't find ancestor for 'b' copied from 'a'!
610 $ hg log -r 'destination(13)'
610 $ hg log -r 'destination(13)'
611 All copies of a cset
611 All copies of a cset
612 $ hg log -r 'origin(13) or destination(origin(13))'
612 $ hg log -r 'origin(13) or destination(origin(13))'
613 changeset: 2:5c095ad7e90f
613 changeset: 2:5c095ad7e90f
614 user: test
614 user: test
615 date: Thu Jan 01 00:00:00 1970 +0000
615 date: Thu Jan 01 00:00:00 1970 +0000
616 summary: 2
616 summary: 2
617
617
618 changeset: 7:ef0ef43d49e7
618 changeset: 7:ef0ef43d49e7
619 parent: 0:68795b066622
619 parent: 0:68795b066622
620 user: foo
620 user: foo
621 date: Thu Jan 01 00:00:00 1970 +0000
621 date: Thu Jan 01 00:00:00 1970 +0000
622 summary: 2
622 summary: 2
623
623
624 changeset: 13:9db0f28fd374
624 changeset: 13:9db0f28fd374
625 user: foo
625 user: foo
626 date: Thu Jan 01 00:00:00 1970 +0000
626 date: Thu Jan 01 00:00:00 1970 +0000
627 summary: 2
627 summary: 2
628
628
629 changeset: 19:9627f653b421
629 changeset: 19:9627f653b421
630 user: test
630 user: test
631 date: Thu Jan 01 00:00:00 1970 +0000
631 date: Thu Jan 01 00:00:00 1970 +0000
632 summary: 2
632 summary: 2
633
633
634 changeset: 21:7e61b508e709
634 changeset: 21:7e61b508e709
635 branch: dev
635 branch: dev
636 user: foo
636 user: foo
637 date: Thu Jan 01 00:00:00 1970 +0000
637 date: Thu Jan 01 00:00:00 1970 +0000
638 summary: 2
638 summary: 2
639
639
640 changeset: 22:e95864da75a0
640 changeset: 22:e95864da75a0
641 branch: dev
641 branch: dev
642 tag: tip
642 tag: tip
643 user: foo
643 user: foo
644 date: Thu Jan 01 00:00:00 1970 +0000
644 date: Thu Jan 01 00:00:00 1970 +0000
645 summary: 2
645 summary: 2
646
646
647
647
648 graft works on complex revset
648 graft works on complex revset
649
649
650 $ hg graft 'origin(13) or destination(origin(13))'
650 $ hg graft 'origin(13) or destination(origin(13))'
651 skipping ancestor revision 21:7e61b508e709
651 skipping ancestor revision 21:7e61b508e709
652 skipping ancestor revision 22:e95864da75a0
652 skipping ancestor revision 22:e95864da75a0
653 skipping revision 2:5c095ad7e90f (already grafted to 22:e95864da75a0)
653 skipping revision 2:5c095ad7e90f (already grafted to 22:e95864da75a0)
654 grafting 7:ef0ef43d49e7 "2"
654 grafting 7:ef0ef43d49e7 "2"
655 warning: can't find ancestor for 'b' copied from 'a'!
655 warning: can't find ancestor for 'b' copied from 'a'!
656 grafting 13:9db0f28fd374 "2"
656 grafting 13:9db0f28fd374 "2"
657 warning: can't find ancestor for 'b' copied from 'a'!
657 warning: can't find ancestor for 'b' copied from 'a'!
658 grafting 19:9627f653b421 "2"
658 grafting 19:9627f653b421 "2"
659 merging b
659 merging b
660 warning: can't find ancestor for 'b' copied from 'a'!
660 warning: can't find ancestor for 'b' copied from 'a'!
661
661
662 graft with --force (still doesn't graft merges)
662 graft with --force (still doesn't graft merges)
663
663
664 $ hg graft 19 0 6
664 $ hg graft 19 0 6
665 skipping ungraftable merge revision 6
665 skipping ungraftable merge revision 6
666 skipping ancestor revision 0:68795b066622
666 skipping ancestor revision 0:68795b066622
667 skipping already grafted revision 19:9627f653b421 (22:e95864da75a0 also has origin 2:5c095ad7e90f)
667 skipping already grafted revision 19:9627f653b421 (22:e95864da75a0 also has origin 2:5c095ad7e90f)
668 [255]
668 [255]
669 $ hg graft 19 0 6 --force
669 $ hg graft 19 0 6 --force
670 skipping ungraftable merge revision 6
670 skipping ungraftable merge revision 6
671 grafting 19:9627f653b421 "2"
671 grafting 19:9627f653b421 "2"
672 merging b
672 merging b
673 warning: can't find ancestor for 'b' copied from 'a'!
673 warning: can't find ancestor for 'b' copied from 'a'!
674 grafting 0:68795b066622 "0"
674 grafting 0:68795b066622 "0"
675
675
676 graft --force after backout
676 graft --force after backout
677
677
678 $ echo abc > a
678 $ echo abc > a
679 $ hg ci -m 28
679 $ hg ci -m 28
680 $ hg backout 28
680 $ hg backout 28
681 reverting a
681 reverting a
682 changeset 29:8389853bba65 backs out changeset 28:cd42a33e1848
682 changeset 29:8389853bba65 backs out changeset 28:cd42a33e1848
683 $ hg graft 28
683 $ hg graft 28
684 skipping ancestor revision 28:cd42a33e1848
684 skipping ancestor revision 28:cd42a33e1848
685 [255]
685 [255]
686 $ hg graft 28 --force
686 $ hg graft 28 --force
687 grafting 28:cd42a33e1848 "28"
687 grafting 28:cd42a33e1848 "28"
688 merging a
688 merging a
689 $ cat a
689 $ cat a
690 abc
690 abc
691
691
692 graft --continue after --force
692 graft --continue after --force
693
693
694 $ echo def > a
694 $ echo def > a
695 $ hg ci -m 31
695 $ hg ci -m 31
696 $ hg graft 28 --force --tool internal:fail
696 $ hg graft 28 --force --tool internal:fail
697 grafting 28:cd42a33e1848 "28"
697 grafting 28:cd42a33e1848 "28"
698 abort: unresolved conflicts, can't continue
698 abort: unresolved conflicts, can't continue
699 (use hg resolve and hg graft --continue)
699 (use hg resolve and hg graft --continue)
700 [255]
700 [255]
701 $ hg resolve --all
701 $ hg resolve --all
702 merging a
702 merging a
703 warning: conflicts during merge.
703 warning: conflicts during merge.
704 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
704 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
705 [1]
705 [1]
706 $ echo abc > a
706 $ echo abc > a
707 $ hg resolve -m a
707 $ hg resolve -m a
708 (no more unresolved files)
708 (no more unresolved files)
709 $ hg graft -c
709 $ hg graft -c
710 grafting 28:cd42a33e1848 "28"
710 grafting 28:cd42a33e1848 "28"
711 $ cat a
711 $ cat a
712 abc
712 abc
713
713
714 Continue testing same origin policy, using revision numbers from test above
714 Continue testing same origin policy, using revision numbers from test above
715 but do some destructive editing of the repo:
715 but do some destructive editing of the repo:
716
716
717 $ hg up -qC 7
717 $ hg up -qC 7
718 $ hg tag -l -r 13 tmp
718 $ hg tag -l -r 13 tmp
719 $ hg --config extensions.strip= strip 2
719 $ hg --config extensions.strip= strip 2
720 saved backup bundle to $TESTTMP/a/.hg/strip-backup/5c095ad7e90f-d323a1e4-backup.hg (glob)
720 saved backup bundle to $TESTTMP/a/.hg/strip-backup/5c095ad7e90f-d323a1e4-backup.hg (glob)
721 $ hg graft tmp
721 $ hg graft tmp
722 skipping already grafted revision 8:9db0f28fd374 (2:ef0ef43d49e7 also has unknown origin 5c095ad7e90f)
722 skipping already grafted revision 8:9db0f28fd374 (2:ef0ef43d49e7 also has unknown origin 5c095ad7e90f)
723 [255]
723 [255]
724
724
725 Empty graft
725 Empty graft
726
726
727 $ hg up -qr 26
727 $ hg up -qr 26
728 $ hg tag -f something
728 $ hg tag -f something
729 $ hg graft -qr 27
729 $ hg graft -qr 27
730 $ hg graft -f 27
730 $ hg graft -f 27
731 grafting 27:3d35c4c79e5a "28"
731 grafting 27:3d35c4c79e5a "28"
732 note: graft of 27:3d35c4c79e5a created no changes to commit
732 note: graft of 27:3d35c4c79e5a created no changes to commit
733
734 $ cd ..
735
736 Graft to duplicate a commit
737
738 $ hg init graftsibling
739 $ cd graftsibling
740 $ touch a
741 $ hg commit -qAm a
742 $ touch b
743 $ hg commit -qAm b
744 $ hg log -G -T '{rev}\n'
745 @ 1
746 |
747 o 0
748
749 $ hg up -q 0
750 $ hg graft -r 1
751 grafting 1:0e067c57feba "b" (tip)
752 $ hg log -G -T '{rev}\n'
753 @ 2
754 |
755 | o 1
756 |/
757 o 0
758
@@ -1,126 +1,124
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > histedit=
5 > histedit=
6 > EOF
6 > EOF
7
7
8 $ initrepo ()
8 $ initrepo ()
9 > {
9 > {
10 > hg init r
10 > hg init r
11 > cd r
11 > cd r
12 > for x in a b c d e f ; do
12 > for x in a b c d e f ; do
13 > echo $x > $x
13 > echo $x > $x
14 > hg add $x
14 > hg add $x
15 > hg ci -m $x
15 > hg ci -m $x
16 > done
16 > done
17 > echo a >> e
17 > echo a >> e
18 > hg ci -m 'does not commute with e'
18 > hg ci -m 'does not commute with e'
19 > cd ..
19 > cd ..
20 > }
20 > }
21
21
22 $ initrepo
22 $ initrepo
23 $ cd r
23 $ cd r
24
24
25 log before edit
25 log before edit
26 $ hg log --graph
26 $ hg log --graph
27 @ changeset: 6:bfa474341cc9
27 @ changeset: 6:bfa474341cc9
28 | tag: tip
28 | tag: tip
29 | user: test
29 | user: test
30 | date: Thu Jan 01 00:00:00 1970 +0000
30 | date: Thu Jan 01 00:00:00 1970 +0000
31 | summary: does not commute with e
31 | summary: does not commute with e
32 |
32 |
33 o changeset: 5:652413bf663e
33 o changeset: 5:652413bf663e
34 | user: test
34 | user: test
35 | date: Thu Jan 01 00:00:00 1970 +0000
35 | date: Thu Jan 01 00:00:00 1970 +0000
36 | summary: f
36 | summary: f
37 |
37 |
38 o changeset: 4:e860deea161a
38 o changeset: 4:e860deea161a
39 | user: test
39 | user: test
40 | date: Thu Jan 01 00:00:00 1970 +0000
40 | date: Thu Jan 01 00:00:00 1970 +0000
41 | summary: e
41 | summary: e
42 |
42 |
43 o changeset: 3:055a42cdd887
43 o changeset: 3:055a42cdd887
44 | user: test
44 | user: test
45 | date: Thu Jan 01 00:00:00 1970 +0000
45 | date: Thu Jan 01 00:00:00 1970 +0000
46 | summary: d
46 | summary: d
47 |
47 |
48 o changeset: 2:177f92b77385
48 o changeset: 2:177f92b77385
49 | user: test
49 | user: test
50 | date: Thu Jan 01 00:00:00 1970 +0000
50 | date: Thu Jan 01 00:00:00 1970 +0000
51 | summary: c
51 | summary: c
52 |
52 |
53 o changeset: 1:d2ae7f538514
53 o changeset: 1:d2ae7f538514
54 | user: test
54 | user: test
55 | date: Thu Jan 01 00:00:00 1970 +0000
55 | date: Thu Jan 01 00:00:00 1970 +0000
56 | summary: b
56 | summary: b
57 |
57 |
58 o changeset: 0:cb9a9f314b8b
58 o changeset: 0:cb9a9f314b8b
59 user: test
59 user: test
60 date: Thu Jan 01 00:00:00 1970 +0000
60 date: Thu Jan 01 00:00:00 1970 +0000
61 summary: a
61 summary: a
62
62
63
63
64 edit the history
64 edit the history
65 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
65 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
66 > pick 177f92b77385 c
66 > pick 177f92b77385 c
67 > pick 055a42cdd887 d
67 > pick 055a42cdd887 d
68 > pick bfa474341cc9 does not commute with e
68 > pick bfa474341cc9 does not commute with e
69 > pick e860deea161a e
69 > pick e860deea161a e
70 > pick 652413bf663e f
70 > pick 652413bf663e f
71 > EOF
71 > EOF
72 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
72 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
73 remote changed e which local deleted
74 use (c)hanged version or leave (d)eleted? c
75 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
73 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 merging e
74 merging e
77 warning: conflicts during merge.
75 warning: conflicts during merge.
78 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
76 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
79 Fix up the change and run hg histedit --continue
77 Fix up the change and run hg histedit --continue
80
78
81
79
82 abort the edit
80 abort the edit
83 $ hg histedit --abort 2>&1 | fixbundle
81 $ hg histedit --abort 2>&1 | fixbundle
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85
83
86 log after abort
84 log after abort
87 $ hg resolve -l
85 $ hg resolve -l
88 $ hg log --graph
86 $ hg log --graph
89 @ changeset: 6:bfa474341cc9
87 @ changeset: 6:bfa474341cc9
90 | tag: tip
88 | tag: tip
91 | user: test
89 | user: test
92 | date: Thu Jan 01 00:00:00 1970 +0000
90 | date: Thu Jan 01 00:00:00 1970 +0000
93 | summary: does not commute with e
91 | summary: does not commute with e
94 |
92 |
95 o changeset: 5:652413bf663e
93 o changeset: 5:652413bf663e
96 | user: test
94 | user: test
97 | date: Thu Jan 01 00:00:00 1970 +0000
95 | date: Thu Jan 01 00:00:00 1970 +0000
98 | summary: f
96 | summary: f
99 |
97 |
100 o changeset: 4:e860deea161a
98 o changeset: 4:e860deea161a
101 | user: test
99 | user: test
102 | date: Thu Jan 01 00:00:00 1970 +0000
100 | date: Thu Jan 01 00:00:00 1970 +0000
103 | summary: e
101 | summary: e
104 |
102 |
105 o changeset: 3:055a42cdd887
103 o changeset: 3:055a42cdd887
106 | user: test
104 | user: test
107 | date: Thu Jan 01 00:00:00 1970 +0000
105 | date: Thu Jan 01 00:00:00 1970 +0000
108 | summary: d
106 | summary: d
109 |
107 |
110 o changeset: 2:177f92b77385
108 o changeset: 2:177f92b77385
111 | user: test
109 | user: test
112 | date: Thu Jan 01 00:00:00 1970 +0000
110 | date: Thu Jan 01 00:00:00 1970 +0000
113 | summary: c
111 | summary: c
114 |
112 |
115 o changeset: 1:d2ae7f538514
113 o changeset: 1:d2ae7f538514
116 | user: test
114 | user: test
117 | date: Thu Jan 01 00:00:00 1970 +0000
115 | date: Thu Jan 01 00:00:00 1970 +0000
118 | summary: b
116 | summary: b
119 |
117 |
120 o changeset: 0:cb9a9f314b8b
118 o changeset: 0:cb9a9f314b8b
121 user: test
119 user: test
122 date: Thu Jan 01 00:00:00 1970 +0000
120 date: Thu Jan 01 00:00:00 1970 +0000
123 summary: a
121 summary: a
124
122
125
123
126 $ cd ..
124 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now