##// END OF EJS Templates
update: warn if cwd was deleted...
Stanislau Hlebik -
r30172:90a6c18a default
parent child Browse files
Show More
@@ -1,1676 +1,1694 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import os
12 import os
13 import shutil
13 import shutil
14 import struct
14 import struct
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 nullhex,
20 nullhex,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 )
23 )
24 from . import (
24 from . import (
25 copies,
25 copies,
26 destutil,
26 destutil,
27 error,
27 error,
28 filemerge,
28 filemerge,
29 obsolete,
29 obsolete,
30 scmutil,
30 scmutil,
31 subrepo,
31 subrepo,
32 util,
32 util,
33 worker,
33 worker,
34 )
34 )
35
35
36 _pack = struct.pack
36 _pack = struct.pack
37 _unpack = struct.unpack
37 _unpack = struct.unpack
38
38
39 def _droponode(data):
39 def _droponode(data):
40 # used for compatibility for v1
40 # used for compatibility for v1
41 bits = data.split('\0')
41 bits = data.split('\0')
42 bits = bits[:-2] + bits[-1:]
42 bits = bits[:-2] + bits[-1:]
43 return '\0'.join(bits)
43 return '\0'.join(bits)
44
44
45 class mergestate(object):
45 class mergestate(object):
46 '''track 3-way merge state of individual files
46 '''track 3-way merge state of individual files
47
47
48 The merge state is stored on disk when needed. Two files are used: one with
48 The merge state is stored on disk when needed. Two files are used: one with
49 an old format (version 1), and one with a new format (version 2). Version 2
49 an old format (version 1), and one with a new format (version 2). Version 2
50 stores a superset of the data in version 1, including new kinds of records
50 stores a superset of the data in version 1, including new kinds of records
51 in the future. For more about the new format, see the documentation for
51 in the future. For more about the new format, see the documentation for
52 `_readrecordsv2`.
52 `_readrecordsv2`.
53
53
54 Each record can contain arbitrary content, and has an associated type. This
54 Each record can contain arbitrary content, and has an associated type. This
55 `type` should be a letter. If `type` is uppercase, the record is mandatory:
55 `type` should be a letter. If `type` is uppercase, the record is mandatory:
56 versions of Mercurial that don't support it should abort. If `type` is
56 versions of Mercurial that don't support it should abort. If `type` is
57 lowercase, the record can be safely ignored.
57 lowercase, the record can be safely ignored.
58
58
59 Currently known records:
59 Currently known records:
60
60
61 L: the node of the "local" part of the merge (hexified version)
61 L: the node of the "local" part of the merge (hexified version)
62 O: the node of the "other" part of the merge (hexified version)
62 O: the node of the "other" part of the merge (hexified version)
63 F: a file to be merged entry
63 F: a file to be merged entry
64 C: a change/delete or delete/change conflict
64 C: a change/delete or delete/change conflict
65 D: a file that the external merge driver will merge internally
65 D: a file that the external merge driver will merge internally
66 (experimental)
66 (experimental)
67 m: the external merge driver defined for this merge plus its run state
67 m: the external merge driver defined for this merge plus its run state
68 (experimental)
68 (experimental)
69 f: a (filename, dictonary) tuple of optional values for a given file
69 f: a (filename, dictonary) tuple of optional values for a given file
70 X: unsupported mandatory record type (used in tests)
70 X: unsupported mandatory record type (used in tests)
71 x: unsupported advisory record type (used in tests)
71 x: unsupported advisory record type (used in tests)
72 l: the labels for the parts of the merge.
72 l: the labels for the parts of the merge.
73
73
74 Merge driver run states (experimental):
74 Merge driver run states (experimental):
75 u: driver-resolved files unmarked -- needs to be run next time we're about
75 u: driver-resolved files unmarked -- needs to be run next time we're about
76 to resolve or commit
76 to resolve or commit
77 m: driver-resolved files marked -- only needs to be run before commit
77 m: driver-resolved files marked -- only needs to be run before commit
78 s: success/skipped -- does not need to be run any more
78 s: success/skipped -- does not need to be run any more
79
79
80 '''
80 '''
81 statepathv1 = 'merge/state'
81 statepathv1 = 'merge/state'
82 statepathv2 = 'merge/state2'
82 statepathv2 = 'merge/state2'
83
83
84 @staticmethod
84 @staticmethod
85 def clean(repo, node=None, other=None, labels=None):
85 def clean(repo, node=None, other=None, labels=None):
86 """Initialize a brand new merge state, removing any existing state on
86 """Initialize a brand new merge state, removing any existing state on
87 disk."""
87 disk."""
88 ms = mergestate(repo)
88 ms = mergestate(repo)
89 ms.reset(node, other, labels)
89 ms.reset(node, other, labels)
90 return ms
90 return ms
91
91
92 @staticmethod
92 @staticmethod
93 def read(repo):
93 def read(repo):
94 """Initialize the merge state, reading it from disk."""
94 """Initialize the merge state, reading it from disk."""
95 ms = mergestate(repo)
95 ms = mergestate(repo)
96 ms._read()
96 ms._read()
97 return ms
97 return ms
98
98
99 def __init__(self, repo):
99 def __init__(self, repo):
100 """Initialize the merge state.
100 """Initialize the merge state.
101
101
102 Do not use this directly! Instead call read() or clean()."""
102 Do not use this directly! Instead call read() or clean()."""
103 self._repo = repo
103 self._repo = repo
104 self._dirty = False
104 self._dirty = False
105 self._labels = None
105 self._labels = None
106
106
107 def reset(self, node=None, other=None, labels=None):
107 def reset(self, node=None, other=None, labels=None):
108 self._state = {}
108 self._state = {}
109 self._stateextras = {}
109 self._stateextras = {}
110 self._local = None
110 self._local = None
111 self._other = None
111 self._other = None
112 self._labels = labels
112 self._labels = labels
113 for var in ('localctx', 'otherctx'):
113 for var in ('localctx', 'otherctx'):
114 if var in vars(self):
114 if var in vars(self):
115 delattr(self, var)
115 delattr(self, var)
116 if node:
116 if node:
117 self._local = node
117 self._local = node
118 self._other = other
118 self._other = other
119 self._readmergedriver = None
119 self._readmergedriver = None
120 if self.mergedriver:
120 if self.mergedriver:
121 self._mdstate = 's'
121 self._mdstate = 's'
122 else:
122 else:
123 self._mdstate = 'u'
123 self._mdstate = 'u'
124 shutil.rmtree(self._repo.join('merge'), True)
124 shutil.rmtree(self._repo.join('merge'), True)
125 self._results = {}
125 self._results = {}
126 self._dirty = False
126 self._dirty = False
127
127
128 def _read(self):
128 def _read(self):
129 """Analyse each record content to restore a serialized state from disk
129 """Analyse each record content to restore a serialized state from disk
130
130
131 This function process "record" entry produced by the de-serialization
131 This function process "record" entry produced by the de-serialization
132 of on disk file.
132 of on disk file.
133 """
133 """
134 self._state = {}
134 self._state = {}
135 self._stateextras = {}
135 self._stateextras = {}
136 self._local = None
136 self._local = None
137 self._other = None
137 self._other = None
138 for var in ('localctx', 'otherctx'):
138 for var in ('localctx', 'otherctx'):
139 if var in vars(self):
139 if var in vars(self):
140 delattr(self, var)
140 delattr(self, var)
141 self._readmergedriver = None
141 self._readmergedriver = None
142 self._mdstate = 's'
142 self._mdstate = 's'
143 unsupported = set()
143 unsupported = set()
144 records = self._readrecords()
144 records = self._readrecords()
145 for rtype, record in records:
145 for rtype, record in records:
146 if rtype == 'L':
146 if rtype == 'L':
147 self._local = bin(record)
147 self._local = bin(record)
148 elif rtype == 'O':
148 elif rtype == 'O':
149 self._other = bin(record)
149 self._other = bin(record)
150 elif rtype == 'm':
150 elif rtype == 'm':
151 bits = record.split('\0', 1)
151 bits = record.split('\0', 1)
152 mdstate = bits[1]
152 mdstate = bits[1]
153 if len(mdstate) != 1 or mdstate not in 'ums':
153 if len(mdstate) != 1 or mdstate not in 'ums':
154 # the merge driver should be idempotent, so just rerun it
154 # the merge driver should be idempotent, so just rerun it
155 mdstate = 'u'
155 mdstate = 'u'
156
156
157 self._readmergedriver = bits[0]
157 self._readmergedriver = bits[0]
158 self._mdstate = mdstate
158 self._mdstate = mdstate
159 elif rtype in 'FDC':
159 elif rtype in 'FDC':
160 bits = record.split('\0')
160 bits = record.split('\0')
161 self._state[bits[0]] = bits[1:]
161 self._state[bits[0]] = bits[1:]
162 elif rtype == 'f':
162 elif rtype == 'f':
163 filename, rawextras = record.split('\0', 1)
163 filename, rawextras = record.split('\0', 1)
164 extraparts = rawextras.split('\0')
164 extraparts = rawextras.split('\0')
165 extras = {}
165 extras = {}
166 i = 0
166 i = 0
167 while i < len(extraparts):
167 while i < len(extraparts):
168 extras[extraparts[i]] = extraparts[i + 1]
168 extras[extraparts[i]] = extraparts[i + 1]
169 i += 2
169 i += 2
170
170
171 self._stateextras[filename] = extras
171 self._stateextras[filename] = extras
172 elif rtype == 'l':
172 elif rtype == 'l':
173 labels = record.split('\0', 2)
173 labels = record.split('\0', 2)
174 self._labels = [l for l in labels if len(l) > 0]
174 self._labels = [l for l in labels if len(l) > 0]
175 elif not rtype.islower():
175 elif not rtype.islower():
176 unsupported.add(rtype)
176 unsupported.add(rtype)
177 self._results = {}
177 self._results = {}
178 self._dirty = False
178 self._dirty = False
179
179
180 if unsupported:
180 if unsupported:
181 raise error.UnsupportedMergeRecords(unsupported)
181 raise error.UnsupportedMergeRecords(unsupported)
182
182
183 def _readrecords(self):
183 def _readrecords(self):
184 """Read merge state from disk and return a list of record (TYPE, data)
184 """Read merge state from disk and return a list of record (TYPE, data)
185
185
186 We read data from both v1 and v2 files and decide which one to use.
186 We read data from both v1 and v2 files and decide which one to use.
187
187
188 V1 has been used by version prior to 2.9.1 and contains less data than
188 V1 has been used by version prior to 2.9.1 and contains less data than
189 v2. We read both versions and check if no data in v2 contradicts
189 v2. We read both versions and check if no data in v2 contradicts
190 v1. If there is not contradiction we can safely assume that both v1
190 v1. If there is not contradiction we can safely assume that both v1
191 and v2 were written at the same time and use the extract data in v2. If
191 and v2 were written at the same time and use the extract data in v2. If
192 there is contradiction we ignore v2 content as we assume an old version
192 there is contradiction we ignore v2 content as we assume an old version
193 of Mercurial has overwritten the mergestate file and left an old v2
193 of Mercurial has overwritten the mergestate file and left an old v2
194 file around.
194 file around.
195
195
196 returns list of record [(TYPE, data), ...]"""
196 returns list of record [(TYPE, data), ...]"""
197 v1records = self._readrecordsv1()
197 v1records = self._readrecordsv1()
198 v2records = self._readrecordsv2()
198 v2records = self._readrecordsv2()
199 if self._v1v2match(v1records, v2records):
199 if self._v1v2match(v1records, v2records):
200 return v2records
200 return v2records
201 else:
201 else:
202 # v1 file is newer than v2 file, use it
202 # v1 file is newer than v2 file, use it
203 # we have to infer the "other" changeset of the merge
203 # we have to infer the "other" changeset of the merge
204 # we cannot do better than that with v1 of the format
204 # we cannot do better than that with v1 of the format
205 mctx = self._repo[None].parents()[-1]
205 mctx = self._repo[None].parents()[-1]
206 v1records.append(('O', mctx.hex()))
206 v1records.append(('O', mctx.hex()))
207 # add place holder "other" file node information
207 # add place holder "other" file node information
208 # nobody is using it yet so we do no need to fetch the data
208 # nobody is using it yet so we do no need to fetch the data
209 # if mctx was wrong `mctx[bits[-2]]` may fails.
209 # if mctx was wrong `mctx[bits[-2]]` may fails.
210 for idx, r in enumerate(v1records):
210 for idx, r in enumerate(v1records):
211 if r[0] == 'F':
211 if r[0] == 'F':
212 bits = r[1].split('\0')
212 bits = r[1].split('\0')
213 bits.insert(-2, '')
213 bits.insert(-2, '')
214 v1records[idx] = (r[0], '\0'.join(bits))
214 v1records[idx] = (r[0], '\0'.join(bits))
215 return v1records
215 return v1records
216
216
217 def _v1v2match(self, v1records, v2records):
217 def _v1v2match(self, v1records, v2records):
218 oldv2 = set() # old format version of v2 record
218 oldv2 = set() # old format version of v2 record
219 for rec in v2records:
219 for rec in v2records:
220 if rec[0] == 'L':
220 if rec[0] == 'L':
221 oldv2.add(rec)
221 oldv2.add(rec)
222 elif rec[0] == 'F':
222 elif rec[0] == 'F':
223 # drop the onode data (not contained in v1)
223 # drop the onode data (not contained in v1)
224 oldv2.add(('F', _droponode(rec[1])))
224 oldv2.add(('F', _droponode(rec[1])))
225 for rec in v1records:
225 for rec in v1records:
226 if rec not in oldv2:
226 if rec not in oldv2:
227 return False
227 return False
228 else:
228 else:
229 return True
229 return True
230
230
231 def _readrecordsv1(self):
231 def _readrecordsv1(self):
232 """read on disk merge state for version 1 file
232 """read on disk merge state for version 1 file
233
233
234 returns list of record [(TYPE, data), ...]
234 returns list of record [(TYPE, data), ...]
235
235
236 Note: the "F" data from this file are one entry short
236 Note: the "F" data from this file are one entry short
237 (no "other file node" entry)
237 (no "other file node" entry)
238 """
238 """
239 records = []
239 records = []
240 try:
240 try:
241 f = self._repo.vfs(self.statepathv1)
241 f = self._repo.vfs(self.statepathv1)
242 for i, l in enumerate(f):
242 for i, l in enumerate(f):
243 if i == 0:
243 if i == 0:
244 records.append(('L', l[:-1]))
244 records.append(('L', l[:-1]))
245 else:
245 else:
246 records.append(('F', l[:-1]))
246 records.append(('F', l[:-1]))
247 f.close()
247 f.close()
248 except IOError as err:
248 except IOError as err:
249 if err.errno != errno.ENOENT:
249 if err.errno != errno.ENOENT:
250 raise
250 raise
251 return records
251 return records
252
252
253 def _readrecordsv2(self):
253 def _readrecordsv2(self):
254 """read on disk merge state for version 2 file
254 """read on disk merge state for version 2 file
255
255
256 This format is a list of arbitrary records of the form:
256 This format is a list of arbitrary records of the form:
257
257
258 [type][length][content]
258 [type][length][content]
259
259
260 `type` is a single character, `length` is a 4 byte integer, and
260 `type` is a single character, `length` is a 4 byte integer, and
261 `content` is an arbitrary byte sequence of length `length`.
261 `content` is an arbitrary byte sequence of length `length`.
262
262
263 Mercurial versions prior to 3.7 have a bug where if there are
263 Mercurial versions prior to 3.7 have a bug where if there are
264 unsupported mandatory merge records, attempting to clear out the merge
264 unsupported mandatory merge records, attempting to clear out the merge
265 state with hg update --clean or similar aborts. The 't' record type
265 state with hg update --clean or similar aborts. The 't' record type
266 works around that by writing out what those versions treat as an
266 works around that by writing out what those versions treat as an
267 advisory record, but later versions interpret as special: the first
267 advisory record, but later versions interpret as special: the first
268 character is the 'real' record type and everything onwards is the data.
268 character is the 'real' record type and everything onwards is the data.
269
269
270 Returns list of records [(TYPE, data), ...]."""
270 Returns list of records [(TYPE, data), ...]."""
271 records = []
271 records = []
272 try:
272 try:
273 f = self._repo.vfs(self.statepathv2)
273 f = self._repo.vfs(self.statepathv2)
274 data = f.read()
274 data = f.read()
275 off = 0
275 off = 0
276 end = len(data)
276 end = len(data)
277 while off < end:
277 while off < end:
278 rtype = data[off]
278 rtype = data[off]
279 off += 1
279 off += 1
280 length = _unpack('>I', data[off:(off + 4)])[0]
280 length = _unpack('>I', data[off:(off + 4)])[0]
281 off += 4
281 off += 4
282 record = data[off:(off + length)]
282 record = data[off:(off + length)]
283 off += length
283 off += length
284 if rtype == 't':
284 if rtype == 't':
285 rtype, record = record[0], record[1:]
285 rtype, record = record[0], record[1:]
286 records.append((rtype, record))
286 records.append((rtype, record))
287 f.close()
287 f.close()
288 except IOError as err:
288 except IOError as err:
289 if err.errno != errno.ENOENT:
289 if err.errno != errno.ENOENT:
290 raise
290 raise
291 return records
291 return records
292
292
293 @util.propertycache
293 @util.propertycache
294 def mergedriver(self):
294 def mergedriver(self):
295 # protect against the following:
295 # protect against the following:
296 # - A configures a malicious merge driver in their hgrc, then
296 # - A configures a malicious merge driver in their hgrc, then
297 # pauses the merge
297 # pauses the merge
298 # - A edits their hgrc to remove references to the merge driver
298 # - A edits their hgrc to remove references to the merge driver
299 # - A gives a copy of their entire repo, including .hg, to B
299 # - A gives a copy of their entire repo, including .hg, to B
300 # - B inspects .hgrc and finds it to be clean
300 # - B inspects .hgrc and finds it to be clean
301 # - B then continues the merge and the malicious merge driver
301 # - B then continues the merge and the malicious merge driver
302 # gets invoked
302 # gets invoked
303 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
303 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
304 if (self._readmergedriver is not None
304 if (self._readmergedriver is not None
305 and self._readmergedriver != configmergedriver):
305 and self._readmergedriver != configmergedriver):
306 raise error.ConfigError(
306 raise error.ConfigError(
307 _("merge driver changed since merge started"),
307 _("merge driver changed since merge started"),
308 hint=_("revert merge driver change or abort merge"))
308 hint=_("revert merge driver change or abort merge"))
309
309
310 return configmergedriver
310 return configmergedriver
311
311
312 @util.propertycache
312 @util.propertycache
313 def localctx(self):
313 def localctx(self):
314 if self._local is None:
314 if self._local is None:
315 raise RuntimeError("localctx accessed but self._local isn't set")
315 raise RuntimeError("localctx accessed but self._local isn't set")
316 return self._repo[self._local]
316 return self._repo[self._local]
317
317
318 @util.propertycache
318 @util.propertycache
319 def otherctx(self):
319 def otherctx(self):
320 if self._other is None:
320 if self._other is None:
321 raise RuntimeError("otherctx accessed but self._other isn't set")
321 raise RuntimeError("otherctx accessed but self._other isn't set")
322 return self._repo[self._other]
322 return self._repo[self._other]
323
323
324 def active(self):
324 def active(self):
325 """Whether mergestate is active.
325 """Whether mergestate is active.
326
326
327 Returns True if there appears to be mergestate. This is a rough proxy
327 Returns True if there appears to be mergestate. This is a rough proxy
328 for "is a merge in progress."
328 for "is a merge in progress."
329 """
329 """
330 # Check local variables before looking at filesystem for performance
330 # Check local variables before looking at filesystem for performance
331 # reasons.
331 # reasons.
332 return bool(self._local) or bool(self._state) or \
332 return bool(self._local) or bool(self._state) or \
333 self._repo.vfs.exists(self.statepathv1) or \
333 self._repo.vfs.exists(self.statepathv1) or \
334 self._repo.vfs.exists(self.statepathv2)
334 self._repo.vfs.exists(self.statepathv2)
335
335
336 def commit(self):
336 def commit(self):
337 """Write current state on disk (if necessary)"""
337 """Write current state on disk (if necessary)"""
338 if self._dirty:
338 if self._dirty:
339 records = self._makerecords()
339 records = self._makerecords()
340 self._writerecords(records)
340 self._writerecords(records)
341 self._dirty = False
341 self._dirty = False
342
342
343 def _makerecords(self):
343 def _makerecords(self):
344 records = []
344 records = []
345 records.append(('L', hex(self._local)))
345 records.append(('L', hex(self._local)))
346 records.append(('O', hex(self._other)))
346 records.append(('O', hex(self._other)))
347 if self.mergedriver:
347 if self.mergedriver:
348 records.append(('m', '\0'.join([
348 records.append(('m', '\0'.join([
349 self.mergedriver, self._mdstate])))
349 self.mergedriver, self._mdstate])))
350 for d, v in self._state.iteritems():
350 for d, v in self._state.iteritems():
351 if v[0] == 'd':
351 if v[0] == 'd':
352 records.append(('D', '\0'.join([d] + v)))
352 records.append(('D', '\0'.join([d] + v)))
353 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
353 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
354 # older versions of Mercurial
354 # older versions of Mercurial
355 elif v[1] == nullhex or v[6] == nullhex:
355 elif v[1] == nullhex or v[6] == nullhex:
356 records.append(('C', '\0'.join([d] + v)))
356 records.append(('C', '\0'.join([d] + v)))
357 else:
357 else:
358 records.append(('F', '\0'.join([d] + v)))
358 records.append(('F', '\0'.join([d] + v)))
359 for filename, extras in sorted(self._stateextras.iteritems()):
359 for filename, extras in sorted(self._stateextras.iteritems()):
360 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
360 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
361 extras.iteritems())
361 extras.iteritems())
362 records.append(('f', '%s\0%s' % (filename, rawextras)))
362 records.append(('f', '%s\0%s' % (filename, rawextras)))
363 if self._labels is not None:
363 if self._labels is not None:
364 labels = '\0'.join(self._labels)
364 labels = '\0'.join(self._labels)
365 records.append(('l', labels))
365 records.append(('l', labels))
366 return records
366 return records
367
367
368 def _writerecords(self, records):
368 def _writerecords(self, records):
369 """Write current state on disk (both v1 and v2)"""
369 """Write current state on disk (both v1 and v2)"""
370 self._writerecordsv1(records)
370 self._writerecordsv1(records)
371 self._writerecordsv2(records)
371 self._writerecordsv2(records)
372
372
373 def _writerecordsv1(self, records):
373 def _writerecordsv1(self, records):
374 """Write current state on disk in a version 1 file"""
374 """Write current state on disk in a version 1 file"""
375 f = self._repo.vfs(self.statepathv1, 'w')
375 f = self._repo.vfs(self.statepathv1, 'w')
376 irecords = iter(records)
376 irecords = iter(records)
377 lrecords = next(irecords)
377 lrecords = next(irecords)
378 assert lrecords[0] == 'L'
378 assert lrecords[0] == 'L'
379 f.write(hex(self._local) + '\n')
379 f.write(hex(self._local) + '\n')
380 for rtype, data in irecords:
380 for rtype, data in irecords:
381 if rtype == 'F':
381 if rtype == 'F':
382 f.write('%s\n' % _droponode(data))
382 f.write('%s\n' % _droponode(data))
383 f.close()
383 f.close()
384
384
385 def _writerecordsv2(self, records):
385 def _writerecordsv2(self, records):
386 """Write current state on disk in a version 2 file
386 """Write current state on disk in a version 2 file
387
387
388 See the docstring for _readrecordsv2 for why we use 't'."""
388 See the docstring for _readrecordsv2 for why we use 't'."""
389 # these are the records that all version 2 clients can read
389 # these are the records that all version 2 clients can read
390 whitelist = 'LOF'
390 whitelist = 'LOF'
391 f = self._repo.vfs(self.statepathv2, 'w')
391 f = self._repo.vfs(self.statepathv2, 'w')
392 for key, data in records:
392 for key, data in records:
393 assert len(key) == 1
393 assert len(key) == 1
394 if key not in whitelist:
394 if key not in whitelist:
395 key, data = 't', '%s%s' % (key, data)
395 key, data = 't', '%s%s' % (key, data)
396 format = '>sI%is' % len(data)
396 format = '>sI%is' % len(data)
397 f.write(_pack(format, key, len(data), data))
397 f.write(_pack(format, key, len(data), data))
398 f.close()
398 f.close()
399
399
400 def add(self, fcl, fco, fca, fd):
400 def add(self, fcl, fco, fca, fd):
401 """add a new (potentially?) conflicting file the merge state
401 """add a new (potentially?) conflicting file the merge state
402 fcl: file context for local,
402 fcl: file context for local,
403 fco: file context for remote,
403 fco: file context for remote,
404 fca: file context for ancestors,
404 fca: file context for ancestors,
405 fd: file path of the resulting merge.
405 fd: file path of the resulting merge.
406
406
407 note: also write the local version to the `.hg/merge` directory.
407 note: also write the local version to the `.hg/merge` directory.
408 """
408 """
409 if fcl.isabsent():
409 if fcl.isabsent():
410 hash = nullhex
410 hash = nullhex
411 else:
411 else:
412 hash = hashlib.sha1(fcl.path()).hexdigest()
412 hash = hashlib.sha1(fcl.path()).hexdigest()
413 self._repo.vfs.write('merge/' + hash, fcl.data())
413 self._repo.vfs.write('merge/' + hash, fcl.data())
414 self._state[fd] = ['u', hash, fcl.path(),
414 self._state[fd] = ['u', hash, fcl.path(),
415 fca.path(), hex(fca.filenode()),
415 fca.path(), hex(fca.filenode()),
416 fco.path(), hex(fco.filenode()),
416 fco.path(), hex(fco.filenode()),
417 fcl.flags()]
417 fcl.flags()]
418 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
418 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
419 self._dirty = True
419 self._dirty = True
420
420
421 def __contains__(self, dfile):
421 def __contains__(self, dfile):
422 return dfile in self._state
422 return dfile in self._state
423
423
424 def __getitem__(self, dfile):
424 def __getitem__(self, dfile):
425 return self._state[dfile][0]
425 return self._state[dfile][0]
426
426
427 def __iter__(self):
427 def __iter__(self):
428 return iter(sorted(self._state))
428 return iter(sorted(self._state))
429
429
430 def files(self):
430 def files(self):
431 return self._state.keys()
431 return self._state.keys()
432
432
433 def mark(self, dfile, state):
433 def mark(self, dfile, state):
434 self._state[dfile][0] = state
434 self._state[dfile][0] = state
435 self._dirty = True
435 self._dirty = True
436
436
437 def mdstate(self):
437 def mdstate(self):
438 return self._mdstate
438 return self._mdstate
439
439
440 def unresolved(self):
440 def unresolved(self):
441 """Obtain the paths of unresolved files."""
441 """Obtain the paths of unresolved files."""
442
442
443 for f, entry in self._state.items():
443 for f, entry in self._state.items():
444 if entry[0] == 'u':
444 if entry[0] == 'u':
445 yield f
445 yield f
446
446
447 def driverresolved(self):
447 def driverresolved(self):
448 """Obtain the paths of driver-resolved files."""
448 """Obtain the paths of driver-resolved files."""
449
449
450 for f, entry in self._state.items():
450 for f, entry in self._state.items():
451 if entry[0] == 'd':
451 if entry[0] == 'd':
452 yield f
452 yield f
453
453
454 def extras(self, filename):
454 def extras(self, filename):
455 return self._stateextras.setdefault(filename, {})
455 return self._stateextras.setdefault(filename, {})
456
456
457 def _resolve(self, preresolve, dfile, wctx):
457 def _resolve(self, preresolve, dfile, wctx):
458 """rerun merge process for file path `dfile`"""
458 """rerun merge process for file path `dfile`"""
459 if self[dfile] in 'rd':
459 if self[dfile] in 'rd':
460 return True, 0
460 return True, 0
461 stateentry = self._state[dfile]
461 stateentry = self._state[dfile]
462 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
462 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
463 octx = self._repo[self._other]
463 octx = self._repo[self._other]
464 extras = self.extras(dfile)
464 extras = self.extras(dfile)
465 anccommitnode = extras.get('ancestorlinknode')
465 anccommitnode = extras.get('ancestorlinknode')
466 if anccommitnode:
466 if anccommitnode:
467 actx = self._repo[anccommitnode]
467 actx = self._repo[anccommitnode]
468 else:
468 else:
469 actx = None
469 actx = None
470 fcd = self._filectxorabsent(hash, wctx, dfile)
470 fcd = self._filectxorabsent(hash, wctx, dfile)
471 fco = self._filectxorabsent(onode, octx, ofile)
471 fco = self._filectxorabsent(onode, octx, ofile)
472 # TODO: move this to filectxorabsent
472 # TODO: move this to filectxorabsent
473 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
473 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
474 # "premerge" x flags
474 # "premerge" x flags
475 flo = fco.flags()
475 flo = fco.flags()
476 fla = fca.flags()
476 fla = fca.flags()
477 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
477 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
478 if fca.node() == nullid and flags != flo:
478 if fca.node() == nullid and flags != flo:
479 if preresolve:
479 if preresolve:
480 self._repo.ui.warn(
480 self._repo.ui.warn(
481 _('warning: cannot merge flags for %s '
481 _('warning: cannot merge flags for %s '
482 'without common ancestor - keeping local flags\n')
482 'without common ancestor - keeping local flags\n')
483 % afile)
483 % afile)
484 elif flags == fla:
484 elif flags == fla:
485 flags = flo
485 flags = flo
486 if preresolve:
486 if preresolve:
487 # restore local
487 # restore local
488 if hash != nullhex:
488 if hash != nullhex:
489 f = self._repo.vfs('merge/' + hash)
489 f = self._repo.vfs('merge/' + hash)
490 self._repo.wwrite(dfile, f.read(), flags)
490 self._repo.wwrite(dfile, f.read(), flags)
491 f.close()
491 f.close()
492 else:
492 else:
493 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
493 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
494 complete, r, deleted = filemerge.premerge(self._repo, self._local,
494 complete, r, deleted = filemerge.premerge(self._repo, self._local,
495 lfile, fcd, fco, fca,
495 lfile, fcd, fco, fca,
496 labels=self._labels)
496 labels=self._labels)
497 else:
497 else:
498 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
498 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
499 lfile, fcd, fco, fca,
499 lfile, fcd, fco, fca,
500 labels=self._labels)
500 labels=self._labels)
501 if r is None:
501 if r is None:
502 # no real conflict
502 # no real conflict
503 del self._state[dfile]
503 del self._state[dfile]
504 self._stateextras.pop(dfile, None)
504 self._stateextras.pop(dfile, None)
505 self._dirty = True
505 self._dirty = True
506 elif not r:
506 elif not r:
507 self.mark(dfile, 'r')
507 self.mark(dfile, 'r')
508
508
509 if complete:
509 if complete:
510 action = None
510 action = None
511 if deleted:
511 if deleted:
512 if fcd.isabsent():
512 if fcd.isabsent():
513 # dc: local picked. Need to drop if present, which may
513 # dc: local picked. Need to drop if present, which may
514 # happen on re-resolves.
514 # happen on re-resolves.
515 action = 'f'
515 action = 'f'
516 else:
516 else:
517 # cd: remote picked (or otherwise deleted)
517 # cd: remote picked (or otherwise deleted)
518 action = 'r'
518 action = 'r'
519 else:
519 else:
520 if fcd.isabsent(): # dc: remote picked
520 if fcd.isabsent(): # dc: remote picked
521 action = 'g'
521 action = 'g'
522 elif fco.isabsent(): # cd: local picked
522 elif fco.isabsent(): # cd: local picked
523 if dfile in self.localctx:
523 if dfile in self.localctx:
524 action = 'am'
524 action = 'am'
525 else:
525 else:
526 action = 'a'
526 action = 'a'
527 # else: regular merges (no action necessary)
527 # else: regular merges (no action necessary)
528 self._results[dfile] = r, action
528 self._results[dfile] = r, action
529
529
530 return complete, r
530 return complete, r
531
531
532 def _filectxorabsent(self, hexnode, ctx, f):
532 def _filectxorabsent(self, hexnode, ctx, f):
533 if hexnode == nullhex:
533 if hexnode == nullhex:
534 return filemerge.absentfilectx(ctx, f)
534 return filemerge.absentfilectx(ctx, f)
535 else:
535 else:
536 return ctx[f]
536 return ctx[f]
537
537
538 def preresolve(self, dfile, wctx):
538 def preresolve(self, dfile, wctx):
539 """run premerge process for dfile
539 """run premerge process for dfile
540
540
541 Returns whether the merge is complete, and the exit code."""
541 Returns whether the merge is complete, and the exit code."""
542 return self._resolve(True, dfile, wctx)
542 return self._resolve(True, dfile, wctx)
543
543
544 def resolve(self, dfile, wctx):
544 def resolve(self, dfile, wctx):
545 """run merge process (assuming premerge was run) for dfile
545 """run merge process (assuming premerge was run) for dfile
546
546
547 Returns the exit code of the merge."""
547 Returns the exit code of the merge."""
548 return self._resolve(False, dfile, wctx)[1]
548 return self._resolve(False, dfile, wctx)[1]
549
549
550 def counts(self):
550 def counts(self):
551 """return counts for updated, merged and removed files in this
551 """return counts for updated, merged and removed files in this
552 session"""
552 session"""
553 updated, merged, removed = 0, 0, 0
553 updated, merged, removed = 0, 0, 0
554 for r, action in self._results.itervalues():
554 for r, action in self._results.itervalues():
555 if r is None:
555 if r is None:
556 updated += 1
556 updated += 1
557 elif r == 0:
557 elif r == 0:
558 if action == 'r':
558 if action == 'r':
559 removed += 1
559 removed += 1
560 else:
560 else:
561 merged += 1
561 merged += 1
562 return updated, merged, removed
562 return updated, merged, removed
563
563
564 def unresolvedcount(self):
564 def unresolvedcount(self):
565 """get unresolved count for this merge (persistent)"""
565 """get unresolved count for this merge (persistent)"""
566 return len([True for f, entry in self._state.iteritems()
566 return len([True for f, entry in self._state.iteritems()
567 if entry[0] == 'u'])
567 if entry[0] == 'u'])
568
568
569 def actions(self):
569 def actions(self):
570 """return lists of actions to perform on the dirstate"""
570 """return lists of actions to perform on the dirstate"""
571 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
571 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
572 for f, (r, action) in self._results.iteritems():
572 for f, (r, action) in self._results.iteritems():
573 if action is not None:
573 if action is not None:
574 actions[action].append((f, None, "merge result"))
574 actions[action].append((f, None, "merge result"))
575 return actions
575 return actions
576
576
577 def recordactions(self):
577 def recordactions(self):
578 """record remove/add/get actions in the dirstate"""
578 """record remove/add/get actions in the dirstate"""
579 branchmerge = self._repo.dirstate.p2() != nullid
579 branchmerge = self._repo.dirstate.p2() != nullid
580 recordupdates(self._repo, self.actions(), branchmerge)
580 recordupdates(self._repo, self.actions(), branchmerge)
581
581
582 def queueremove(self, f):
582 def queueremove(self, f):
583 """queues a file to be removed from the dirstate
583 """queues a file to be removed from the dirstate
584
584
585 Meant for use by custom merge drivers."""
585 Meant for use by custom merge drivers."""
586 self._results[f] = 0, 'r'
586 self._results[f] = 0, 'r'
587
587
588 def queueadd(self, f):
588 def queueadd(self, f):
589 """queues a file to be added to the dirstate
589 """queues a file to be added to the dirstate
590
590
591 Meant for use by custom merge drivers."""
591 Meant for use by custom merge drivers."""
592 self._results[f] = 0, 'a'
592 self._results[f] = 0, 'a'
593
593
594 def queueget(self, f):
594 def queueget(self, f):
595 """queues a file to be marked modified in the dirstate
595 """queues a file to be marked modified in the dirstate
596
596
597 Meant for use by custom merge drivers."""
597 Meant for use by custom merge drivers."""
598 self._results[f] = 0, 'g'
598 self._results[f] = 0, 'g'
599
599
600 def _getcheckunknownconfig(repo, section, name):
600 def _getcheckunknownconfig(repo, section, name):
601 config = repo.ui.config(section, name, default='abort')
601 config = repo.ui.config(section, name, default='abort')
602 valid = ['abort', 'ignore', 'warn']
602 valid = ['abort', 'ignore', 'warn']
603 if config not in valid:
603 if config not in valid:
604 validstr = ', '.join(["'" + v + "'" for v in valid])
604 validstr = ', '.join(["'" + v + "'" for v in valid])
605 raise error.ConfigError(_("%s.%s not valid "
605 raise error.ConfigError(_("%s.%s not valid "
606 "('%s' is none of %s)")
606 "('%s' is none of %s)")
607 % (section, name, config, validstr))
607 % (section, name, config, validstr))
608 return config
608 return config
609
609
610 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
610 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
611 if f2 is None:
611 if f2 is None:
612 f2 = f
612 f2 = f
613 return (repo.wvfs.audit.check(f)
613 return (repo.wvfs.audit.check(f)
614 and repo.wvfs.isfileorlink(f)
614 and repo.wvfs.isfileorlink(f)
615 and repo.dirstate.normalize(f) not in repo.dirstate
615 and repo.dirstate.normalize(f) not in repo.dirstate
616 and mctx[f2].cmp(wctx[f]))
616 and mctx[f2].cmp(wctx[f]))
617
617
618 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
618 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
619 """
619 """
620 Considers any actions that care about the presence of conflicting unknown
620 Considers any actions that care about the presence of conflicting unknown
621 files. For some actions, the result is to abort; for others, it is to
621 files. For some actions, the result is to abort; for others, it is to
622 choose a different action.
622 choose a different action.
623 """
623 """
624 conflicts = set()
624 conflicts = set()
625 warnconflicts = set()
625 warnconflicts = set()
626 abortconflicts = set()
626 abortconflicts = set()
627 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
627 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
628 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
628 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
629 if not force:
629 if not force:
630 def collectconflicts(conflicts, config):
630 def collectconflicts(conflicts, config):
631 if config == 'abort':
631 if config == 'abort':
632 abortconflicts.update(conflicts)
632 abortconflicts.update(conflicts)
633 elif config == 'warn':
633 elif config == 'warn':
634 warnconflicts.update(conflicts)
634 warnconflicts.update(conflicts)
635
635
636 for f, (m, args, msg) in actions.iteritems():
636 for f, (m, args, msg) in actions.iteritems():
637 if m in ('c', 'dc'):
637 if m in ('c', 'dc'):
638 if _checkunknownfile(repo, wctx, mctx, f):
638 if _checkunknownfile(repo, wctx, mctx, f):
639 conflicts.add(f)
639 conflicts.add(f)
640 elif m == 'dg':
640 elif m == 'dg':
641 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
641 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
642 conflicts.add(f)
642 conflicts.add(f)
643
643
644 ignoredconflicts = set([c for c in conflicts
644 ignoredconflicts = set([c for c in conflicts
645 if repo.dirstate._ignore(c)])
645 if repo.dirstate._ignore(c)])
646 unknownconflicts = conflicts - ignoredconflicts
646 unknownconflicts = conflicts - ignoredconflicts
647 collectconflicts(ignoredconflicts, ignoredconfig)
647 collectconflicts(ignoredconflicts, ignoredconfig)
648 collectconflicts(unknownconflicts, unknownconfig)
648 collectconflicts(unknownconflicts, unknownconfig)
649 else:
649 else:
650 for f, (m, args, msg) in actions.iteritems():
650 for f, (m, args, msg) in actions.iteritems():
651 if m == 'cm':
651 if m == 'cm':
652 fl2, anc = args
652 fl2, anc = args
653 different = _checkunknownfile(repo, wctx, mctx, f)
653 different = _checkunknownfile(repo, wctx, mctx, f)
654 if repo.dirstate._ignore(f):
654 if repo.dirstate._ignore(f):
655 config = ignoredconfig
655 config = ignoredconfig
656 else:
656 else:
657 config = unknownconfig
657 config = unknownconfig
658
658
659 # The behavior when force is True is described by this table:
659 # The behavior when force is True is described by this table:
660 # config different mergeforce | action backup
660 # config different mergeforce | action backup
661 # * n * | get n
661 # * n * | get n
662 # * y y | merge -
662 # * y y | merge -
663 # abort y n | merge - (1)
663 # abort y n | merge - (1)
664 # warn y n | warn + get y
664 # warn y n | warn + get y
665 # ignore y n | get y
665 # ignore y n | get y
666 #
666 #
667 # (1) this is probably the wrong behavior here -- we should
667 # (1) this is probably the wrong behavior here -- we should
668 # probably abort, but some actions like rebases currently
668 # probably abort, but some actions like rebases currently
669 # don't like an abort happening in the middle of
669 # don't like an abort happening in the middle of
670 # merge.update.
670 # merge.update.
671 if not different:
671 if not different:
672 actions[f] = ('g', (fl2, False), "remote created")
672 actions[f] = ('g', (fl2, False), "remote created")
673 elif mergeforce or config == 'abort':
673 elif mergeforce or config == 'abort':
674 actions[f] = ('m', (f, f, None, False, anc),
674 actions[f] = ('m', (f, f, None, False, anc),
675 "remote differs from untracked local")
675 "remote differs from untracked local")
676 elif config == 'abort':
676 elif config == 'abort':
677 abortconflicts.add(f)
677 abortconflicts.add(f)
678 else:
678 else:
679 if config == 'warn':
679 if config == 'warn':
680 warnconflicts.add(f)
680 warnconflicts.add(f)
681 actions[f] = ('g', (fl2, True), "remote created")
681 actions[f] = ('g', (fl2, True), "remote created")
682
682
683 for f in sorted(abortconflicts):
683 for f in sorted(abortconflicts):
684 repo.ui.warn(_("%s: untracked file differs\n") % f)
684 repo.ui.warn(_("%s: untracked file differs\n") % f)
685 if abortconflicts:
685 if abortconflicts:
686 raise error.Abort(_("untracked files in working directory "
686 raise error.Abort(_("untracked files in working directory "
687 "differ from files in requested revision"))
687 "differ from files in requested revision"))
688
688
689 for f in sorted(warnconflicts):
689 for f in sorted(warnconflicts):
690 repo.ui.warn(_("%s: replacing untracked file\n") % f)
690 repo.ui.warn(_("%s: replacing untracked file\n") % f)
691
691
692 for f, (m, args, msg) in actions.iteritems():
692 for f, (m, args, msg) in actions.iteritems():
693 backup = f in conflicts
693 backup = f in conflicts
694 if m == 'c':
694 if m == 'c':
695 flags, = args
695 flags, = args
696 actions[f] = ('g', (flags, backup), msg)
696 actions[f] = ('g', (flags, backup), msg)
697
697
698 def _forgetremoved(wctx, mctx, branchmerge):
698 def _forgetremoved(wctx, mctx, branchmerge):
699 """
699 """
700 Forget removed files
700 Forget removed files
701
701
702 If we're jumping between revisions (as opposed to merging), and if
702 If we're jumping between revisions (as opposed to merging), and if
703 neither the working directory nor the target rev has the file,
703 neither the working directory nor the target rev has the file,
704 then we need to remove it from the dirstate, to prevent the
704 then we need to remove it from the dirstate, to prevent the
705 dirstate from listing the file when it is no longer in the
705 dirstate from listing the file when it is no longer in the
706 manifest.
706 manifest.
707
707
708 If we're merging, and the other revision has removed a file
708 If we're merging, and the other revision has removed a file
709 that is not present in the working directory, we need to mark it
709 that is not present in the working directory, we need to mark it
710 as removed.
710 as removed.
711 """
711 """
712
712
713 actions = {}
713 actions = {}
714 m = 'f'
714 m = 'f'
715 if branchmerge:
715 if branchmerge:
716 m = 'r'
716 m = 'r'
717 for f in wctx.deleted():
717 for f in wctx.deleted():
718 if f not in mctx:
718 if f not in mctx:
719 actions[f] = m, None, "forget deleted"
719 actions[f] = m, None, "forget deleted"
720
720
721 if not branchmerge:
721 if not branchmerge:
722 for f in wctx.removed():
722 for f in wctx.removed():
723 if f not in mctx:
723 if f not in mctx:
724 actions[f] = 'f', None, "forget removed"
724 actions[f] = 'f', None, "forget removed"
725
725
726 return actions
726 return actions
727
727
728 def _checkcollision(repo, wmf, actions):
728 def _checkcollision(repo, wmf, actions):
729 # build provisional merged manifest up
729 # build provisional merged manifest up
730 pmmf = set(wmf)
730 pmmf = set(wmf)
731
731
732 if actions:
732 if actions:
733 # k, dr, e and rd are no-op
733 # k, dr, e and rd are no-op
734 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
734 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
735 for f, args, msg in actions[m]:
735 for f, args, msg in actions[m]:
736 pmmf.add(f)
736 pmmf.add(f)
737 for f, args, msg in actions['r']:
737 for f, args, msg in actions['r']:
738 pmmf.discard(f)
738 pmmf.discard(f)
739 for f, args, msg in actions['dm']:
739 for f, args, msg in actions['dm']:
740 f2, flags = args
740 f2, flags = args
741 pmmf.discard(f2)
741 pmmf.discard(f2)
742 pmmf.add(f)
742 pmmf.add(f)
743 for f, args, msg in actions['dg']:
743 for f, args, msg in actions['dg']:
744 pmmf.add(f)
744 pmmf.add(f)
745 for f, args, msg in actions['m']:
745 for f, args, msg in actions['m']:
746 f1, f2, fa, move, anc = args
746 f1, f2, fa, move, anc = args
747 if move:
747 if move:
748 pmmf.discard(f1)
748 pmmf.discard(f1)
749 pmmf.add(f)
749 pmmf.add(f)
750
750
751 # check case-folding collision in provisional merged manifest
751 # check case-folding collision in provisional merged manifest
752 foldmap = {}
752 foldmap = {}
753 for f in sorted(pmmf):
753 for f in sorted(pmmf):
754 fold = util.normcase(f)
754 fold = util.normcase(f)
755 if fold in foldmap:
755 if fold in foldmap:
756 raise error.Abort(_("case-folding collision between %s and %s")
756 raise error.Abort(_("case-folding collision between %s and %s")
757 % (f, foldmap[fold]))
757 % (f, foldmap[fold]))
758 foldmap[fold] = f
758 foldmap[fold] = f
759
759
760 # check case-folding of directories
760 # check case-folding of directories
761 foldprefix = unfoldprefix = lastfull = ''
761 foldprefix = unfoldprefix = lastfull = ''
762 for fold, f in sorted(foldmap.items()):
762 for fold, f in sorted(foldmap.items()):
763 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
763 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
764 # the folded prefix matches but actual casing is different
764 # the folded prefix matches but actual casing is different
765 raise error.Abort(_("case-folding collision between "
765 raise error.Abort(_("case-folding collision between "
766 "%s and directory of %s") % (lastfull, f))
766 "%s and directory of %s") % (lastfull, f))
767 foldprefix = fold + '/'
767 foldprefix = fold + '/'
768 unfoldprefix = f + '/'
768 unfoldprefix = f + '/'
769 lastfull = f
769 lastfull = f
770
770
771 def driverpreprocess(repo, ms, wctx, labels=None):
771 def driverpreprocess(repo, ms, wctx, labels=None):
772 """run the preprocess step of the merge driver, if any
772 """run the preprocess step of the merge driver, if any
773
773
774 This is currently not implemented -- it's an extension point."""
774 This is currently not implemented -- it's an extension point."""
775 return True
775 return True
776
776
777 def driverconclude(repo, ms, wctx, labels=None):
777 def driverconclude(repo, ms, wctx, labels=None):
778 """run the conclude step of the merge driver, if any
778 """run the conclude step of the merge driver, if any
779
779
780 This is currently not implemented -- it's an extension point."""
780 This is currently not implemented -- it's an extension point."""
781 return True
781 return True
782
782
783 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
783 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
784 acceptremote, followcopies):
784 acceptremote, followcopies):
785 """
785 """
786 Merge wctx and p2 with ancestor pa and generate merge action list
786 Merge wctx and p2 with ancestor pa and generate merge action list
787
787
788 branchmerge and force are as passed in to update
788 branchmerge and force are as passed in to update
789 matcher = matcher to filter file lists
789 matcher = matcher to filter file lists
790 acceptremote = accept the incoming changes without prompting
790 acceptremote = accept the incoming changes without prompting
791 """
791 """
792 if matcher is not None and matcher.always():
792 if matcher is not None and matcher.always():
793 matcher = None
793 matcher = None
794
794
795 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
795 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
796
796
797 # manifests fetched in order are going to be faster, so prime the caches
797 # manifests fetched in order are going to be faster, so prime the caches
798 [x.manifest() for x in
798 [x.manifest() for x in
799 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
799 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
800
800
801 if followcopies:
801 if followcopies:
802 ret = copies.mergecopies(repo, wctx, p2, pa)
802 ret = copies.mergecopies(repo, wctx, p2, pa)
803 copy, movewithdir, diverge, renamedelete = ret
803 copy, movewithdir, diverge, renamedelete = ret
804
804
805 repo.ui.note(_("resolving manifests\n"))
805 repo.ui.note(_("resolving manifests\n"))
806 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
806 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
807 % (bool(branchmerge), bool(force), bool(matcher)))
807 % (bool(branchmerge), bool(force), bool(matcher)))
808 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
808 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
809
809
810 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
810 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
811 copied = set(copy.values())
811 copied = set(copy.values())
812 copied.update(movewithdir.values())
812 copied.update(movewithdir.values())
813
813
814 if '.hgsubstate' in m1:
814 if '.hgsubstate' in m1:
815 # check whether sub state is modified
815 # check whether sub state is modified
816 if any(wctx.sub(s).dirty() for s in wctx.substate):
816 if any(wctx.sub(s).dirty() for s in wctx.substate):
817 m1['.hgsubstate'] += '+'
817 m1['.hgsubstate'] += '+'
818
818
819 # Compare manifests
819 # Compare manifests
820 if matcher is not None:
820 if matcher is not None:
821 m1 = m1.matches(matcher)
821 m1 = m1.matches(matcher)
822 m2 = m2.matches(matcher)
822 m2 = m2.matches(matcher)
823 diff = m1.diff(m2)
823 diff = m1.diff(m2)
824
824
825 actions = {}
825 actions = {}
826 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
826 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
827 if n1 and n2: # file exists on both local and remote side
827 if n1 and n2: # file exists on both local and remote side
828 if f not in ma:
828 if f not in ma:
829 fa = copy.get(f, None)
829 fa = copy.get(f, None)
830 if fa is not None:
830 if fa is not None:
831 actions[f] = ('m', (f, f, fa, False, pa.node()),
831 actions[f] = ('m', (f, f, fa, False, pa.node()),
832 "both renamed from " + fa)
832 "both renamed from " + fa)
833 else:
833 else:
834 actions[f] = ('m', (f, f, None, False, pa.node()),
834 actions[f] = ('m', (f, f, None, False, pa.node()),
835 "both created")
835 "both created")
836 else:
836 else:
837 a = ma[f]
837 a = ma[f]
838 fla = ma.flags(f)
838 fla = ma.flags(f)
839 nol = 'l' not in fl1 + fl2 + fla
839 nol = 'l' not in fl1 + fl2 + fla
840 if n2 == a and fl2 == fla:
840 if n2 == a and fl2 == fla:
841 actions[f] = ('k' , (), "remote unchanged")
841 actions[f] = ('k' , (), "remote unchanged")
842 elif n1 == a and fl1 == fla: # local unchanged - use remote
842 elif n1 == a and fl1 == fla: # local unchanged - use remote
843 if n1 == n2: # optimization: keep local content
843 if n1 == n2: # optimization: keep local content
844 actions[f] = ('e', (fl2,), "update permissions")
844 actions[f] = ('e', (fl2,), "update permissions")
845 else:
845 else:
846 actions[f] = ('g', (fl2, False), "remote is newer")
846 actions[f] = ('g', (fl2, False), "remote is newer")
847 elif nol and n2 == a: # remote only changed 'x'
847 elif nol and n2 == a: # remote only changed 'x'
848 actions[f] = ('e', (fl2,), "update permissions")
848 actions[f] = ('e', (fl2,), "update permissions")
849 elif nol and n1 == a: # local only changed 'x'
849 elif nol and n1 == a: # local only changed 'x'
850 actions[f] = ('g', (fl1, False), "remote is newer")
850 actions[f] = ('g', (fl1, False), "remote is newer")
851 else: # both changed something
851 else: # both changed something
852 actions[f] = ('m', (f, f, f, False, pa.node()),
852 actions[f] = ('m', (f, f, f, False, pa.node()),
853 "versions differ")
853 "versions differ")
854 elif n1: # file exists only on local side
854 elif n1: # file exists only on local side
855 if f in copied:
855 if f in copied:
856 pass # we'll deal with it on m2 side
856 pass # we'll deal with it on m2 side
857 elif f in movewithdir: # directory rename, move local
857 elif f in movewithdir: # directory rename, move local
858 f2 = movewithdir[f]
858 f2 = movewithdir[f]
859 if f2 in m2:
859 if f2 in m2:
860 actions[f2] = ('m', (f, f2, None, True, pa.node()),
860 actions[f2] = ('m', (f, f2, None, True, pa.node()),
861 "remote directory rename, both created")
861 "remote directory rename, both created")
862 else:
862 else:
863 actions[f2] = ('dm', (f, fl1),
863 actions[f2] = ('dm', (f, fl1),
864 "remote directory rename - move from " + f)
864 "remote directory rename - move from " + f)
865 elif f in copy:
865 elif f in copy:
866 f2 = copy[f]
866 f2 = copy[f]
867 actions[f] = ('m', (f, f2, f2, False, pa.node()),
867 actions[f] = ('m', (f, f2, f2, False, pa.node()),
868 "local copied/moved from " + f2)
868 "local copied/moved from " + f2)
869 elif f in ma: # clean, a different, no remote
869 elif f in ma: # clean, a different, no remote
870 if n1 != ma[f]:
870 if n1 != ma[f]:
871 if acceptremote:
871 if acceptremote:
872 actions[f] = ('r', None, "remote delete")
872 actions[f] = ('r', None, "remote delete")
873 else:
873 else:
874 actions[f] = ('cd', (f, None, f, False, pa.node()),
874 actions[f] = ('cd', (f, None, f, False, pa.node()),
875 "prompt changed/deleted")
875 "prompt changed/deleted")
876 elif n1[20:] == 'a':
876 elif n1[20:] == 'a':
877 # This extra 'a' is added by working copy manifest to mark
877 # This extra 'a' is added by working copy manifest to mark
878 # the file as locally added. We should forget it instead of
878 # the file as locally added. We should forget it instead of
879 # deleting it.
879 # deleting it.
880 actions[f] = ('f', None, "remote deleted")
880 actions[f] = ('f', None, "remote deleted")
881 else:
881 else:
882 actions[f] = ('r', None, "other deleted")
882 actions[f] = ('r', None, "other deleted")
883 elif n2: # file exists only on remote side
883 elif n2: # file exists only on remote side
884 if f in copied:
884 if f in copied:
885 pass # we'll deal with it on m1 side
885 pass # we'll deal with it on m1 side
886 elif f in movewithdir:
886 elif f in movewithdir:
887 f2 = movewithdir[f]
887 f2 = movewithdir[f]
888 if f2 in m1:
888 if f2 in m1:
889 actions[f2] = ('m', (f2, f, None, False, pa.node()),
889 actions[f2] = ('m', (f2, f, None, False, pa.node()),
890 "local directory rename, both created")
890 "local directory rename, both created")
891 else:
891 else:
892 actions[f2] = ('dg', (f, fl2),
892 actions[f2] = ('dg', (f, fl2),
893 "local directory rename - get from " + f)
893 "local directory rename - get from " + f)
894 elif f in copy:
894 elif f in copy:
895 f2 = copy[f]
895 f2 = copy[f]
896 if f2 in m2:
896 if f2 in m2:
897 actions[f] = ('m', (f2, f, f2, False, pa.node()),
897 actions[f] = ('m', (f2, f, f2, False, pa.node()),
898 "remote copied from " + f2)
898 "remote copied from " + f2)
899 else:
899 else:
900 actions[f] = ('m', (f2, f, f2, True, pa.node()),
900 actions[f] = ('m', (f2, f, f2, True, pa.node()),
901 "remote moved from " + f2)
901 "remote moved from " + f2)
902 elif f not in ma:
902 elif f not in ma:
903 # local unknown, remote created: the logic is described by the
903 # local unknown, remote created: the logic is described by the
904 # following table:
904 # following table:
905 #
905 #
906 # force branchmerge different | action
906 # force branchmerge different | action
907 # n * * | create
907 # n * * | create
908 # y n * | create
908 # y n * | create
909 # y y n | create
909 # y y n | create
910 # y y y | merge
910 # y y y | merge
911 #
911 #
912 # Checking whether the files are different is expensive, so we
912 # Checking whether the files are different is expensive, so we
913 # don't do that when we can avoid it.
913 # don't do that when we can avoid it.
914 if not force:
914 if not force:
915 actions[f] = ('c', (fl2,), "remote created")
915 actions[f] = ('c', (fl2,), "remote created")
916 elif not branchmerge:
916 elif not branchmerge:
917 actions[f] = ('c', (fl2,), "remote created")
917 actions[f] = ('c', (fl2,), "remote created")
918 else:
918 else:
919 actions[f] = ('cm', (fl2, pa.node()),
919 actions[f] = ('cm', (fl2, pa.node()),
920 "remote created, get or merge")
920 "remote created, get or merge")
921 elif n2 != ma[f]:
921 elif n2 != ma[f]:
922 if acceptremote:
922 if acceptremote:
923 actions[f] = ('c', (fl2,), "remote recreating")
923 actions[f] = ('c', (fl2,), "remote recreating")
924 else:
924 else:
925 actions[f] = ('dc', (None, f, f, False, pa.node()),
925 actions[f] = ('dc', (None, f, f, False, pa.node()),
926 "prompt deleted/changed")
926 "prompt deleted/changed")
927
927
928 return actions, diverge, renamedelete
928 return actions, diverge, renamedelete
929
929
930 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
930 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
931 """Resolves false conflicts where the nodeid changed but the content
931 """Resolves false conflicts where the nodeid changed but the content
932 remained the same."""
932 remained the same."""
933
933
934 for f, (m, args, msg) in actions.items():
934 for f, (m, args, msg) in actions.items():
935 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
935 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
936 # local did change but ended up with same content
936 # local did change but ended up with same content
937 actions[f] = 'r', None, "prompt same"
937 actions[f] = 'r', None, "prompt same"
938 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
938 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
939 # remote did change but ended up with same content
939 # remote did change but ended up with same content
940 del actions[f] # don't get = keep local deleted
940 del actions[f] # don't get = keep local deleted
941
941
942 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
942 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
943 acceptremote, followcopies, matcher=None,
943 acceptremote, followcopies, matcher=None,
944 mergeforce=False):
944 mergeforce=False):
945 "Calculate the actions needed to merge mctx into wctx using ancestors"
945 "Calculate the actions needed to merge mctx into wctx using ancestors"
946 if len(ancestors) == 1: # default
946 if len(ancestors) == 1: # default
947 actions, diverge, renamedelete = manifestmerge(
947 actions, diverge, renamedelete = manifestmerge(
948 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
948 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
949 acceptremote, followcopies)
949 acceptremote, followcopies)
950 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
950 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
951
951
952 else: # only when merge.preferancestor=* - the default
952 else: # only when merge.preferancestor=* - the default
953 repo.ui.note(
953 repo.ui.note(
954 _("note: merging %s and %s using bids from ancestors %s\n") %
954 _("note: merging %s and %s using bids from ancestors %s\n") %
955 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
955 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
956
956
957 # Call for bids
957 # Call for bids
958 fbids = {} # mapping filename to bids (action method to list af actions)
958 fbids = {} # mapping filename to bids (action method to list af actions)
959 diverge, renamedelete = None, None
959 diverge, renamedelete = None, None
960 for ancestor in ancestors:
960 for ancestor in ancestors:
961 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
961 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
962 actions, diverge1, renamedelete1 = manifestmerge(
962 actions, diverge1, renamedelete1 = manifestmerge(
963 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
963 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
964 acceptremote, followcopies)
964 acceptremote, followcopies)
965 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
965 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
966
966
967 # Track the shortest set of warning on the theory that bid
967 # Track the shortest set of warning on the theory that bid
968 # merge will correctly incorporate more information
968 # merge will correctly incorporate more information
969 if diverge is None or len(diverge1) < len(diverge):
969 if diverge is None or len(diverge1) < len(diverge):
970 diverge = diverge1
970 diverge = diverge1
971 if renamedelete is None or len(renamedelete) < len(renamedelete1):
971 if renamedelete is None or len(renamedelete) < len(renamedelete1):
972 renamedelete = renamedelete1
972 renamedelete = renamedelete1
973
973
974 for f, a in sorted(actions.iteritems()):
974 for f, a in sorted(actions.iteritems()):
975 m, args, msg = a
975 m, args, msg = a
976 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
976 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
977 if f in fbids:
977 if f in fbids:
978 d = fbids[f]
978 d = fbids[f]
979 if m in d:
979 if m in d:
980 d[m].append(a)
980 d[m].append(a)
981 else:
981 else:
982 d[m] = [a]
982 d[m] = [a]
983 else:
983 else:
984 fbids[f] = {m: [a]}
984 fbids[f] = {m: [a]}
985
985
986 # Pick the best bid for each file
986 # Pick the best bid for each file
987 repo.ui.note(_('\nauction for merging merge bids\n'))
987 repo.ui.note(_('\nauction for merging merge bids\n'))
988 actions = {}
988 actions = {}
989 for f, bids in sorted(fbids.items()):
989 for f, bids in sorted(fbids.items()):
990 # bids is a mapping from action method to list af actions
990 # bids is a mapping from action method to list af actions
991 # Consensus?
991 # Consensus?
992 if len(bids) == 1: # all bids are the same kind of method
992 if len(bids) == 1: # all bids are the same kind of method
993 m, l = bids.items()[0]
993 m, l = bids.items()[0]
994 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
994 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
995 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
995 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
996 actions[f] = l[0]
996 actions[f] = l[0]
997 continue
997 continue
998 # If keep is an option, just do it.
998 # If keep is an option, just do it.
999 if 'k' in bids:
999 if 'k' in bids:
1000 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1000 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1001 actions[f] = bids['k'][0]
1001 actions[f] = bids['k'][0]
1002 continue
1002 continue
1003 # If there are gets and they all agree [how could they not?], do it.
1003 # If there are gets and they all agree [how could they not?], do it.
1004 if 'g' in bids:
1004 if 'g' in bids:
1005 ga0 = bids['g'][0]
1005 ga0 = bids['g'][0]
1006 if all(a == ga0 for a in bids['g'][1:]):
1006 if all(a == ga0 for a in bids['g'][1:]):
1007 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1007 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1008 actions[f] = ga0
1008 actions[f] = ga0
1009 continue
1009 continue
1010 # TODO: Consider other simple actions such as mode changes
1010 # TODO: Consider other simple actions such as mode changes
1011 # Handle inefficient democrazy.
1011 # Handle inefficient democrazy.
1012 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1012 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1013 for m, l in sorted(bids.items()):
1013 for m, l in sorted(bids.items()):
1014 for _f, args, msg in l:
1014 for _f, args, msg in l:
1015 repo.ui.note(' %s -> %s\n' % (msg, m))
1015 repo.ui.note(' %s -> %s\n' % (msg, m))
1016 # Pick random action. TODO: Instead, prompt user when resolving
1016 # Pick random action. TODO: Instead, prompt user when resolving
1017 m, l = bids.items()[0]
1017 m, l = bids.items()[0]
1018 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1018 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1019 (f, m))
1019 (f, m))
1020 actions[f] = l[0]
1020 actions[f] = l[0]
1021 continue
1021 continue
1022 repo.ui.note(_('end of auction\n\n'))
1022 repo.ui.note(_('end of auction\n\n'))
1023
1023
1024 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1024 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1025
1025
1026 if wctx.rev() is None:
1026 if wctx.rev() is None:
1027 fractions = _forgetremoved(wctx, mctx, branchmerge)
1027 fractions = _forgetremoved(wctx, mctx, branchmerge)
1028 actions.update(fractions)
1028 actions.update(fractions)
1029
1029
1030 return actions, diverge, renamedelete
1030 return actions, diverge, renamedelete
1031
1031
1032 def batchremove(repo, actions):
1032 def batchremove(repo, actions):
1033 """apply removes to the working directory
1033 """apply removes to the working directory
1034
1034
1035 yields tuples for progress updates
1035 yields tuples for progress updates
1036 """
1036 """
1037 verbose = repo.ui.verbose
1037 verbose = repo.ui.verbose
1038 unlink = util.unlinkpath
1038 unlink = util.unlinkpath
1039 wjoin = repo.wjoin
1039 wjoin = repo.wjoin
1040 audit = repo.wvfs.audit
1040 audit = repo.wvfs.audit
1041 try:
1042 cwd = os.getcwd()
1043 except OSError as err:
1044 if err.errno != errno.ENOENT:
1045 raise
1046 cwd = None
1041 i = 0
1047 i = 0
1042 for f, args, msg in actions:
1048 for f, args, msg in actions:
1043 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1049 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1044 if verbose:
1050 if verbose:
1045 repo.ui.note(_("removing %s\n") % f)
1051 repo.ui.note(_("removing %s\n") % f)
1046 audit(f)
1052 audit(f)
1047 try:
1053 try:
1048 unlink(wjoin(f), ignoremissing=True)
1054 unlink(wjoin(f), ignoremissing=True)
1049 except OSError as inst:
1055 except OSError as inst:
1050 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1056 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1051 (f, inst.strerror))
1057 (f, inst.strerror))
1052 if i == 100:
1058 if i == 100:
1053 yield i, f
1059 yield i, f
1054 i = 0
1060 i = 0
1055 i += 1
1061 i += 1
1056 if i > 0:
1062 if i > 0:
1057 yield i, f
1063 yield i, f
1064 if cwd:
1065 # cwd was present before we started to remove files
1066 # let's check if it is present after we removed them
1067 try:
1068 os.getcwd()
1069 except OSError as err:
1070 if err.errno != errno.ENOENT:
1071 raise
1072 # Print a warning if cwd was deleted
1073 repo.ui.warn(_("current directory was removed\n"
1074 "(consider changing to repo root: %s)\n") %
1075 repo.root)
1058
1076
1059 def batchget(repo, mctx, actions):
1077 def batchget(repo, mctx, actions):
1060 """apply gets to the working directory
1078 """apply gets to the working directory
1061
1079
1062 mctx is the context to get from
1080 mctx is the context to get from
1063
1081
1064 yields tuples for progress updates
1082 yields tuples for progress updates
1065 """
1083 """
1066 verbose = repo.ui.verbose
1084 verbose = repo.ui.verbose
1067 fctx = mctx.filectx
1085 fctx = mctx.filectx
1068 wwrite = repo.wwrite
1086 wwrite = repo.wwrite
1069 ui = repo.ui
1087 ui = repo.ui
1070 i = 0
1088 i = 0
1071 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1089 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1072 for f, (flags, backup), msg in actions:
1090 for f, (flags, backup), msg in actions:
1073 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1091 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1074 if verbose:
1092 if verbose:
1075 repo.ui.note(_("getting %s\n") % f)
1093 repo.ui.note(_("getting %s\n") % f)
1076
1094
1077 if backup:
1095 if backup:
1078 absf = repo.wjoin(f)
1096 absf = repo.wjoin(f)
1079 orig = scmutil.origpath(ui, repo, absf)
1097 orig = scmutil.origpath(ui, repo, absf)
1080 try:
1098 try:
1081 if repo.wvfs.isfileorlink(f):
1099 if repo.wvfs.isfileorlink(f):
1082 util.rename(absf, orig)
1100 util.rename(absf, orig)
1083 except OSError as e:
1101 except OSError as e:
1084 if e.errno != errno.ENOENT:
1102 if e.errno != errno.ENOENT:
1085 raise
1103 raise
1086
1104
1087 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1105 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1088 repo.wvfs.removedirs(f)
1106 repo.wvfs.removedirs(f)
1089 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1107 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1090 if i == 100:
1108 if i == 100:
1091 yield i, f
1109 yield i, f
1092 i = 0
1110 i = 0
1093 i += 1
1111 i += 1
1094 if i > 0:
1112 if i > 0:
1095 yield i, f
1113 yield i, f
1096
1114
1097 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1115 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1098 """apply the merge action list to the working directory
1116 """apply the merge action list to the working directory
1099
1117
1100 wctx is the working copy context
1118 wctx is the working copy context
1101 mctx is the context to be merged into the working copy
1119 mctx is the context to be merged into the working copy
1102
1120
1103 Return a tuple of counts (updated, merged, removed, unresolved) that
1121 Return a tuple of counts (updated, merged, removed, unresolved) that
1104 describes how many files were affected by the update.
1122 describes how many files were affected by the update.
1105 """
1123 """
1106
1124
1107 updated, merged, removed = 0, 0, 0
1125 updated, merged, removed = 0, 0, 0
1108 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1126 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1109 moves = []
1127 moves = []
1110 for m, l in actions.items():
1128 for m, l in actions.items():
1111 l.sort()
1129 l.sort()
1112
1130
1113 # 'cd' and 'dc' actions are treated like other merge conflicts
1131 # 'cd' and 'dc' actions are treated like other merge conflicts
1114 mergeactions = sorted(actions['cd'])
1132 mergeactions = sorted(actions['cd'])
1115 mergeactions.extend(sorted(actions['dc']))
1133 mergeactions.extend(sorted(actions['dc']))
1116 mergeactions.extend(actions['m'])
1134 mergeactions.extend(actions['m'])
1117 for f, args, msg in mergeactions:
1135 for f, args, msg in mergeactions:
1118 f1, f2, fa, move, anc = args
1136 f1, f2, fa, move, anc = args
1119 if f == '.hgsubstate': # merged internally
1137 if f == '.hgsubstate': # merged internally
1120 continue
1138 continue
1121 if f1 is None:
1139 if f1 is None:
1122 fcl = filemerge.absentfilectx(wctx, fa)
1140 fcl = filemerge.absentfilectx(wctx, fa)
1123 else:
1141 else:
1124 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1142 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1125 fcl = wctx[f1]
1143 fcl = wctx[f1]
1126 if f2 is None:
1144 if f2 is None:
1127 fco = filemerge.absentfilectx(mctx, fa)
1145 fco = filemerge.absentfilectx(mctx, fa)
1128 else:
1146 else:
1129 fco = mctx[f2]
1147 fco = mctx[f2]
1130 actx = repo[anc]
1148 actx = repo[anc]
1131 if fa in actx:
1149 if fa in actx:
1132 fca = actx[fa]
1150 fca = actx[fa]
1133 else:
1151 else:
1134 # TODO: move to absentfilectx
1152 # TODO: move to absentfilectx
1135 fca = repo.filectx(f1, fileid=nullrev)
1153 fca = repo.filectx(f1, fileid=nullrev)
1136 ms.add(fcl, fco, fca, f)
1154 ms.add(fcl, fco, fca, f)
1137 if f1 != f and move:
1155 if f1 != f and move:
1138 moves.append(f1)
1156 moves.append(f1)
1139
1157
1140 audit = repo.wvfs.audit
1158 audit = repo.wvfs.audit
1141 _updating = _('updating')
1159 _updating = _('updating')
1142 _files = _('files')
1160 _files = _('files')
1143 progress = repo.ui.progress
1161 progress = repo.ui.progress
1144
1162
1145 # remove renamed files after safely stored
1163 # remove renamed files after safely stored
1146 for f in moves:
1164 for f in moves:
1147 if os.path.lexists(repo.wjoin(f)):
1165 if os.path.lexists(repo.wjoin(f)):
1148 repo.ui.debug("removing %s\n" % f)
1166 repo.ui.debug("removing %s\n" % f)
1149 audit(f)
1167 audit(f)
1150 util.unlinkpath(repo.wjoin(f))
1168 util.unlinkpath(repo.wjoin(f))
1151
1169
1152 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1170 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1153
1171
1154 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1172 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1155 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1173 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1156
1174
1157 # remove in parallel (must come first)
1175 # remove in parallel (must come first)
1158 z = 0
1176 z = 0
1159 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1177 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1160 for i, item in prog:
1178 for i, item in prog:
1161 z += i
1179 z += i
1162 progress(_updating, z, item=item, total=numupdates, unit=_files)
1180 progress(_updating, z, item=item, total=numupdates, unit=_files)
1163 removed = len(actions['r'])
1181 removed = len(actions['r'])
1164
1182
1165 # get in parallel
1183 # get in parallel
1166 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1184 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1167 for i, item in prog:
1185 for i, item in prog:
1168 z += i
1186 z += i
1169 progress(_updating, z, item=item, total=numupdates, unit=_files)
1187 progress(_updating, z, item=item, total=numupdates, unit=_files)
1170 updated = len(actions['g'])
1188 updated = len(actions['g'])
1171
1189
1172 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1190 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1173 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1191 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1174
1192
1175 # forget (manifest only, just log it) (must come first)
1193 # forget (manifest only, just log it) (must come first)
1176 for f, args, msg in actions['f']:
1194 for f, args, msg in actions['f']:
1177 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1195 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1178 z += 1
1196 z += 1
1179 progress(_updating, z, item=f, total=numupdates, unit=_files)
1197 progress(_updating, z, item=f, total=numupdates, unit=_files)
1180
1198
1181 # re-add (manifest only, just log it)
1199 # re-add (manifest only, just log it)
1182 for f, args, msg in actions['a']:
1200 for f, args, msg in actions['a']:
1183 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1201 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1184 z += 1
1202 z += 1
1185 progress(_updating, z, item=f, total=numupdates, unit=_files)
1203 progress(_updating, z, item=f, total=numupdates, unit=_files)
1186
1204
1187 # re-add/mark as modified (manifest only, just log it)
1205 # re-add/mark as modified (manifest only, just log it)
1188 for f, args, msg in actions['am']:
1206 for f, args, msg in actions['am']:
1189 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1207 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1190 z += 1
1208 z += 1
1191 progress(_updating, z, item=f, total=numupdates, unit=_files)
1209 progress(_updating, z, item=f, total=numupdates, unit=_files)
1192
1210
1193 # keep (noop, just log it)
1211 # keep (noop, just log it)
1194 for f, args, msg in actions['k']:
1212 for f, args, msg in actions['k']:
1195 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1213 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1196 # no progress
1214 # no progress
1197
1215
1198 # directory rename, move local
1216 # directory rename, move local
1199 for f, args, msg in actions['dm']:
1217 for f, args, msg in actions['dm']:
1200 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1218 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1201 z += 1
1219 z += 1
1202 progress(_updating, z, item=f, total=numupdates, unit=_files)
1220 progress(_updating, z, item=f, total=numupdates, unit=_files)
1203 f0, flags = args
1221 f0, flags = args
1204 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1222 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1205 audit(f)
1223 audit(f)
1206 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1224 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1207 util.unlinkpath(repo.wjoin(f0))
1225 util.unlinkpath(repo.wjoin(f0))
1208 updated += 1
1226 updated += 1
1209
1227
1210 # local directory rename, get
1228 # local directory rename, get
1211 for f, args, msg in actions['dg']:
1229 for f, args, msg in actions['dg']:
1212 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1230 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1213 z += 1
1231 z += 1
1214 progress(_updating, z, item=f, total=numupdates, unit=_files)
1232 progress(_updating, z, item=f, total=numupdates, unit=_files)
1215 f0, flags = args
1233 f0, flags = args
1216 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1234 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1217 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1235 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1218 updated += 1
1236 updated += 1
1219
1237
1220 # exec
1238 # exec
1221 for f, args, msg in actions['e']:
1239 for f, args, msg in actions['e']:
1222 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1240 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1223 z += 1
1241 z += 1
1224 progress(_updating, z, item=f, total=numupdates, unit=_files)
1242 progress(_updating, z, item=f, total=numupdates, unit=_files)
1225 flags, = args
1243 flags, = args
1226 audit(f)
1244 audit(f)
1227 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
1245 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
1228 updated += 1
1246 updated += 1
1229
1247
1230 # the ordering is important here -- ms.mergedriver will raise if the merge
1248 # the ordering is important here -- ms.mergedriver will raise if the merge
1231 # driver has changed, and we want to be able to bypass it when overwrite is
1249 # driver has changed, and we want to be able to bypass it when overwrite is
1232 # True
1250 # True
1233 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1251 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1234
1252
1235 if usemergedriver:
1253 if usemergedriver:
1236 ms.commit()
1254 ms.commit()
1237 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1255 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1238 # the driver might leave some files unresolved
1256 # the driver might leave some files unresolved
1239 unresolvedf = set(ms.unresolved())
1257 unresolvedf = set(ms.unresolved())
1240 if not proceed:
1258 if not proceed:
1241 # XXX setting unresolved to at least 1 is a hack to make sure we
1259 # XXX setting unresolved to at least 1 is a hack to make sure we
1242 # error out
1260 # error out
1243 return updated, merged, removed, max(len(unresolvedf), 1)
1261 return updated, merged, removed, max(len(unresolvedf), 1)
1244 newactions = []
1262 newactions = []
1245 for f, args, msg in mergeactions:
1263 for f, args, msg in mergeactions:
1246 if f in unresolvedf:
1264 if f in unresolvedf:
1247 newactions.append((f, args, msg))
1265 newactions.append((f, args, msg))
1248 mergeactions = newactions
1266 mergeactions = newactions
1249
1267
1250 # premerge
1268 # premerge
1251 tocomplete = []
1269 tocomplete = []
1252 for f, args, msg in mergeactions:
1270 for f, args, msg in mergeactions:
1253 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1271 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1254 z += 1
1272 z += 1
1255 progress(_updating, z, item=f, total=numupdates, unit=_files)
1273 progress(_updating, z, item=f, total=numupdates, unit=_files)
1256 if f == '.hgsubstate': # subrepo states need updating
1274 if f == '.hgsubstate': # subrepo states need updating
1257 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1275 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1258 overwrite, labels)
1276 overwrite, labels)
1259 continue
1277 continue
1260 audit(f)
1278 audit(f)
1261 complete, r = ms.preresolve(f, wctx)
1279 complete, r = ms.preresolve(f, wctx)
1262 if not complete:
1280 if not complete:
1263 numupdates += 1
1281 numupdates += 1
1264 tocomplete.append((f, args, msg))
1282 tocomplete.append((f, args, msg))
1265
1283
1266 # merge
1284 # merge
1267 for f, args, msg in tocomplete:
1285 for f, args, msg in tocomplete:
1268 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1286 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1269 z += 1
1287 z += 1
1270 progress(_updating, z, item=f, total=numupdates, unit=_files)
1288 progress(_updating, z, item=f, total=numupdates, unit=_files)
1271 ms.resolve(f, wctx)
1289 ms.resolve(f, wctx)
1272
1290
1273 ms.commit()
1291 ms.commit()
1274
1292
1275 unresolved = ms.unresolvedcount()
1293 unresolved = ms.unresolvedcount()
1276
1294
1277 if usemergedriver and not unresolved and ms.mdstate() != 's':
1295 if usemergedriver and not unresolved and ms.mdstate() != 's':
1278 if not driverconclude(repo, ms, wctx, labels=labels):
1296 if not driverconclude(repo, ms, wctx, labels=labels):
1279 # XXX setting unresolved to at least 1 is a hack to make sure we
1297 # XXX setting unresolved to at least 1 is a hack to make sure we
1280 # error out
1298 # error out
1281 unresolved = max(unresolved, 1)
1299 unresolved = max(unresolved, 1)
1282
1300
1283 ms.commit()
1301 ms.commit()
1284
1302
1285 msupdated, msmerged, msremoved = ms.counts()
1303 msupdated, msmerged, msremoved = ms.counts()
1286 updated += msupdated
1304 updated += msupdated
1287 merged += msmerged
1305 merged += msmerged
1288 removed += msremoved
1306 removed += msremoved
1289
1307
1290 extraactions = ms.actions()
1308 extraactions = ms.actions()
1291 if extraactions:
1309 if extraactions:
1292 mfiles = set(a[0] for a in actions['m'])
1310 mfiles = set(a[0] for a in actions['m'])
1293 for k, acts in extraactions.iteritems():
1311 for k, acts in extraactions.iteritems():
1294 actions[k].extend(acts)
1312 actions[k].extend(acts)
1295 # Remove these files from actions['m'] as well. This is important
1313 # Remove these files from actions['m'] as well. This is important
1296 # because in recordupdates, files in actions['m'] are processed
1314 # because in recordupdates, files in actions['m'] are processed
1297 # after files in other actions, and the merge driver might add
1315 # after files in other actions, and the merge driver might add
1298 # files to those actions via extraactions above. This can lead to a
1316 # files to those actions via extraactions above. This can lead to a
1299 # file being recorded twice, with poor results. This is especially
1317 # file being recorded twice, with poor results. This is especially
1300 # problematic for actions['r'] (currently only possible with the
1318 # problematic for actions['r'] (currently only possible with the
1301 # merge driver in the initial merge process; interrupted merges
1319 # merge driver in the initial merge process; interrupted merges
1302 # don't go through this flow).
1320 # don't go through this flow).
1303 #
1321 #
1304 # The real fix here is to have indexes by both file and action so
1322 # The real fix here is to have indexes by both file and action so
1305 # that when the action for a file is changed it is automatically
1323 # that when the action for a file is changed it is automatically
1306 # reflected in the other action lists. But that involves a more
1324 # reflected in the other action lists. But that involves a more
1307 # complex data structure, so this will do for now.
1325 # complex data structure, so this will do for now.
1308 #
1326 #
1309 # We don't need to do the same operation for 'dc' and 'cd' because
1327 # We don't need to do the same operation for 'dc' and 'cd' because
1310 # those lists aren't consulted again.
1328 # those lists aren't consulted again.
1311 mfiles.difference_update(a[0] for a in acts)
1329 mfiles.difference_update(a[0] for a in acts)
1312
1330
1313 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1331 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1314
1332
1315 progress(_updating, None, total=numupdates, unit=_files)
1333 progress(_updating, None, total=numupdates, unit=_files)
1316
1334
1317 return updated, merged, removed, unresolved
1335 return updated, merged, removed, unresolved
1318
1336
1319 def recordupdates(repo, actions, branchmerge):
1337 def recordupdates(repo, actions, branchmerge):
1320 "record merge actions to the dirstate"
1338 "record merge actions to the dirstate"
1321 # remove (must come first)
1339 # remove (must come first)
1322 for f, args, msg in actions.get('r', []):
1340 for f, args, msg in actions.get('r', []):
1323 if branchmerge:
1341 if branchmerge:
1324 repo.dirstate.remove(f)
1342 repo.dirstate.remove(f)
1325 else:
1343 else:
1326 repo.dirstate.drop(f)
1344 repo.dirstate.drop(f)
1327
1345
1328 # forget (must come first)
1346 # forget (must come first)
1329 for f, args, msg in actions.get('f', []):
1347 for f, args, msg in actions.get('f', []):
1330 repo.dirstate.drop(f)
1348 repo.dirstate.drop(f)
1331
1349
1332 # re-add
1350 # re-add
1333 for f, args, msg in actions.get('a', []):
1351 for f, args, msg in actions.get('a', []):
1334 repo.dirstate.add(f)
1352 repo.dirstate.add(f)
1335
1353
1336 # re-add/mark as modified
1354 # re-add/mark as modified
1337 for f, args, msg in actions.get('am', []):
1355 for f, args, msg in actions.get('am', []):
1338 if branchmerge:
1356 if branchmerge:
1339 repo.dirstate.normallookup(f)
1357 repo.dirstate.normallookup(f)
1340 else:
1358 else:
1341 repo.dirstate.add(f)
1359 repo.dirstate.add(f)
1342
1360
1343 # exec change
1361 # exec change
1344 for f, args, msg in actions.get('e', []):
1362 for f, args, msg in actions.get('e', []):
1345 repo.dirstate.normallookup(f)
1363 repo.dirstate.normallookup(f)
1346
1364
1347 # keep
1365 # keep
1348 for f, args, msg in actions.get('k', []):
1366 for f, args, msg in actions.get('k', []):
1349 pass
1367 pass
1350
1368
1351 # get
1369 # get
1352 for f, args, msg in actions.get('g', []):
1370 for f, args, msg in actions.get('g', []):
1353 if branchmerge:
1371 if branchmerge:
1354 repo.dirstate.otherparent(f)
1372 repo.dirstate.otherparent(f)
1355 else:
1373 else:
1356 repo.dirstate.normal(f)
1374 repo.dirstate.normal(f)
1357
1375
1358 # merge
1376 # merge
1359 for f, args, msg in actions.get('m', []):
1377 for f, args, msg in actions.get('m', []):
1360 f1, f2, fa, move, anc = args
1378 f1, f2, fa, move, anc = args
1361 if branchmerge:
1379 if branchmerge:
1362 # We've done a branch merge, mark this file as merged
1380 # We've done a branch merge, mark this file as merged
1363 # so that we properly record the merger later
1381 # so that we properly record the merger later
1364 repo.dirstate.merge(f)
1382 repo.dirstate.merge(f)
1365 if f1 != f2: # copy/rename
1383 if f1 != f2: # copy/rename
1366 if move:
1384 if move:
1367 repo.dirstate.remove(f1)
1385 repo.dirstate.remove(f1)
1368 if f1 != f:
1386 if f1 != f:
1369 repo.dirstate.copy(f1, f)
1387 repo.dirstate.copy(f1, f)
1370 else:
1388 else:
1371 repo.dirstate.copy(f2, f)
1389 repo.dirstate.copy(f2, f)
1372 else:
1390 else:
1373 # We've update-merged a locally modified file, so
1391 # We've update-merged a locally modified file, so
1374 # we set the dirstate to emulate a normal checkout
1392 # we set the dirstate to emulate a normal checkout
1375 # of that file some time in the past. Thus our
1393 # of that file some time in the past. Thus our
1376 # merge will appear as a normal local file
1394 # merge will appear as a normal local file
1377 # modification.
1395 # modification.
1378 if f2 == f: # file not locally copied/moved
1396 if f2 == f: # file not locally copied/moved
1379 repo.dirstate.normallookup(f)
1397 repo.dirstate.normallookup(f)
1380 if move:
1398 if move:
1381 repo.dirstate.drop(f1)
1399 repo.dirstate.drop(f1)
1382
1400
1383 # directory rename, move local
1401 # directory rename, move local
1384 for f, args, msg in actions.get('dm', []):
1402 for f, args, msg in actions.get('dm', []):
1385 f0, flag = args
1403 f0, flag = args
1386 if branchmerge:
1404 if branchmerge:
1387 repo.dirstate.add(f)
1405 repo.dirstate.add(f)
1388 repo.dirstate.remove(f0)
1406 repo.dirstate.remove(f0)
1389 repo.dirstate.copy(f0, f)
1407 repo.dirstate.copy(f0, f)
1390 else:
1408 else:
1391 repo.dirstate.normal(f)
1409 repo.dirstate.normal(f)
1392 repo.dirstate.drop(f0)
1410 repo.dirstate.drop(f0)
1393
1411
1394 # directory rename, get
1412 # directory rename, get
1395 for f, args, msg in actions.get('dg', []):
1413 for f, args, msg in actions.get('dg', []):
1396 f0, flag = args
1414 f0, flag = args
1397 if branchmerge:
1415 if branchmerge:
1398 repo.dirstate.add(f)
1416 repo.dirstate.add(f)
1399 repo.dirstate.copy(f0, f)
1417 repo.dirstate.copy(f0, f)
1400 else:
1418 else:
1401 repo.dirstate.normal(f)
1419 repo.dirstate.normal(f)
1402
1420
1403 def update(repo, node, branchmerge, force, ancestor=None,
1421 def update(repo, node, branchmerge, force, ancestor=None,
1404 mergeancestor=False, labels=None, matcher=None, mergeforce=False):
1422 mergeancestor=False, labels=None, matcher=None, mergeforce=False):
1405 """
1423 """
1406 Perform a merge between the working directory and the given node
1424 Perform a merge between the working directory and the given node
1407
1425
1408 node = the node to update to, or None if unspecified
1426 node = the node to update to, or None if unspecified
1409 branchmerge = whether to merge between branches
1427 branchmerge = whether to merge between branches
1410 force = whether to force branch merging or file overwriting
1428 force = whether to force branch merging or file overwriting
1411 matcher = a matcher to filter file lists (dirstate not updated)
1429 matcher = a matcher to filter file lists (dirstate not updated)
1412 mergeancestor = whether it is merging with an ancestor. If true,
1430 mergeancestor = whether it is merging with an ancestor. If true,
1413 we should accept the incoming changes for any prompts that occur.
1431 we should accept the incoming changes for any prompts that occur.
1414 If false, merging with an ancestor (fast-forward) is only allowed
1432 If false, merging with an ancestor (fast-forward) is only allowed
1415 between different named branches. This flag is used by rebase extension
1433 between different named branches. This flag is used by rebase extension
1416 as a temporary fix and should be avoided in general.
1434 as a temporary fix and should be avoided in general.
1417 labels = labels to use for base, local and other
1435 labels = labels to use for base, local and other
1418 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1436 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1419 this is True, then 'force' should be True as well.
1437 this is True, then 'force' should be True as well.
1420
1438
1421 The table below shows all the behaviors of the update command
1439 The table below shows all the behaviors of the update command
1422 given the -c and -C or no options, whether the working directory
1440 given the -c and -C or no options, whether the working directory
1423 is dirty, whether a revision is specified, and the relationship of
1441 is dirty, whether a revision is specified, and the relationship of
1424 the parent rev to the target rev (linear, on the same named
1442 the parent rev to the target rev (linear, on the same named
1425 branch, or on another named branch).
1443 branch, or on another named branch).
1426
1444
1427 This logic is tested by test-update-branches.t.
1445 This logic is tested by test-update-branches.t.
1428
1446
1429 -c -C dirty rev | linear same cross
1447 -c -C dirty rev | linear same cross
1430 n n n n | ok (1) x
1448 n n n n | ok (1) x
1431 n n n y | ok ok ok
1449 n n n y | ok ok ok
1432 n n y n | merge (2) (2)
1450 n n y n | merge (2) (2)
1433 n n y y | merge (3) (3)
1451 n n y y | merge (3) (3)
1434 n y * * | discard discard discard
1452 n y * * | discard discard discard
1435 y n y * | (4) (4) (4)
1453 y n y * | (4) (4) (4)
1436 y n n * | ok ok ok
1454 y n n * | ok ok ok
1437 y y * * | (5) (5) (5)
1455 y y * * | (5) (5) (5)
1438
1456
1439 x = can't happen
1457 x = can't happen
1440 * = don't-care
1458 * = don't-care
1441 1 = abort: not a linear update (merge or update --check to force update)
1459 1 = abort: not a linear update (merge or update --check to force update)
1442 2 = abort: uncommitted changes (commit and merge, or update --clean to
1460 2 = abort: uncommitted changes (commit and merge, or update --clean to
1443 discard changes)
1461 discard changes)
1444 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1462 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1445 4 = abort: uncommitted changes (checked in commands.py)
1463 4 = abort: uncommitted changes (checked in commands.py)
1446 5 = incompatible options (checked in commands.py)
1464 5 = incompatible options (checked in commands.py)
1447
1465
1448 Return the same tuple as applyupdates().
1466 Return the same tuple as applyupdates().
1449 """
1467 """
1450
1468
1451 onode = node
1469 onode = node
1452 # If we're doing a partial update, we need to skip updating
1470 # If we're doing a partial update, we need to skip updating
1453 # the dirstate, so make a note of any partial-ness to the
1471 # the dirstate, so make a note of any partial-ness to the
1454 # update here.
1472 # update here.
1455 if matcher is None or matcher.always():
1473 if matcher is None or matcher.always():
1456 partial = False
1474 partial = False
1457 else:
1475 else:
1458 partial = True
1476 partial = True
1459 with repo.wlock():
1477 with repo.wlock():
1460 wc = repo[None]
1478 wc = repo[None]
1461 pl = wc.parents()
1479 pl = wc.parents()
1462 p1 = pl[0]
1480 p1 = pl[0]
1463 pas = [None]
1481 pas = [None]
1464 if ancestor is not None:
1482 if ancestor is not None:
1465 pas = [repo[ancestor]]
1483 pas = [repo[ancestor]]
1466
1484
1467 if node is None:
1485 if node is None:
1468 repo.ui.deprecwarn('update with no target', '3.9')
1486 repo.ui.deprecwarn('update with no target', '3.9')
1469 rev, _mark, _act = destutil.destupdate(repo)
1487 rev, _mark, _act = destutil.destupdate(repo)
1470 node = repo[rev].node()
1488 node = repo[rev].node()
1471
1489
1472 overwrite = force and not branchmerge
1490 overwrite = force and not branchmerge
1473
1491
1474 p2 = repo[node]
1492 p2 = repo[node]
1475 if pas[0] is None:
1493 if pas[0] is None:
1476 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1494 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1477 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1495 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1478 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1496 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1479 else:
1497 else:
1480 pas = [p1.ancestor(p2, warn=branchmerge)]
1498 pas = [p1.ancestor(p2, warn=branchmerge)]
1481
1499
1482 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1500 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1483
1501
1484 ### check phase
1502 ### check phase
1485 if not overwrite:
1503 if not overwrite:
1486 if len(pl) > 1:
1504 if len(pl) > 1:
1487 raise error.Abort(_("outstanding uncommitted merge"))
1505 raise error.Abort(_("outstanding uncommitted merge"))
1488 ms = mergestate.read(repo)
1506 ms = mergestate.read(repo)
1489 if list(ms.unresolved()):
1507 if list(ms.unresolved()):
1490 raise error.Abort(_("outstanding merge conflicts"))
1508 raise error.Abort(_("outstanding merge conflicts"))
1491 if branchmerge:
1509 if branchmerge:
1492 if pas == [p2]:
1510 if pas == [p2]:
1493 raise error.Abort(_("merging with a working directory ancestor"
1511 raise error.Abort(_("merging with a working directory ancestor"
1494 " has no effect"))
1512 " has no effect"))
1495 elif pas == [p1]:
1513 elif pas == [p1]:
1496 if not mergeancestor and p1.branch() == p2.branch():
1514 if not mergeancestor and p1.branch() == p2.branch():
1497 raise error.Abort(_("nothing to merge"),
1515 raise error.Abort(_("nothing to merge"),
1498 hint=_("use 'hg update' "
1516 hint=_("use 'hg update' "
1499 "or check 'hg heads'"))
1517 "or check 'hg heads'"))
1500 if not force and (wc.files() or wc.deleted()):
1518 if not force and (wc.files() or wc.deleted()):
1501 raise error.Abort(_("uncommitted changes"),
1519 raise error.Abort(_("uncommitted changes"),
1502 hint=_("use 'hg status' to list changes"))
1520 hint=_("use 'hg status' to list changes"))
1503 for s in sorted(wc.substate):
1521 for s in sorted(wc.substate):
1504 wc.sub(s).bailifchanged()
1522 wc.sub(s).bailifchanged()
1505
1523
1506 elif not overwrite:
1524 elif not overwrite:
1507 if p1 == p2: # no-op update
1525 if p1 == p2: # no-op update
1508 # call the hooks and exit early
1526 # call the hooks and exit early
1509 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1527 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1510 repo.hook('update', parent1=xp2, parent2='', error=0)
1528 repo.hook('update', parent1=xp2, parent2='', error=0)
1511 return 0, 0, 0, 0
1529 return 0, 0, 0, 0
1512
1530
1513 if pas not in ([p1], [p2]): # nonlinear
1531 if pas not in ([p1], [p2]): # nonlinear
1514 dirty = wc.dirty(missing=True)
1532 dirty = wc.dirty(missing=True)
1515 if dirty or onode is None:
1533 if dirty or onode is None:
1516 # Branching is a bit strange to ensure we do the minimal
1534 # Branching is a bit strange to ensure we do the minimal
1517 # amount of call to obsolete.background.
1535 # amount of call to obsolete.background.
1518 foreground = obsolete.foreground(repo, [p1.node()])
1536 foreground = obsolete.foreground(repo, [p1.node()])
1519 # note: the <node> variable contains a random identifier
1537 # note: the <node> variable contains a random identifier
1520 if repo[node].node() in foreground:
1538 if repo[node].node() in foreground:
1521 pas = [p1] # allow updating to successors
1539 pas = [p1] # allow updating to successors
1522 elif dirty:
1540 elif dirty:
1523 msg = _("uncommitted changes")
1541 msg = _("uncommitted changes")
1524 if onode is None:
1542 if onode is None:
1525 hint = _("commit and merge, or update --clean to"
1543 hint = _("commit and merge, or update --clean to"
1526 " discard changes")
1544 " discard changes")
1527 else:
1545 else:
1528 hint = _("commit or update --clean to discard"
1546 hint = _("commit or update --clean to discard"
1529 " changes")
1547 " changes")
1530 raise error.Abort(msg, hint=hint)
1548 raise error.Abort(msg, hint=hint)
1531 else: # node is none
1549 else: # node is none
1532 msg = _("not a linear update")
1550 msg = _("not a linear update")
1533 hint = _("merge or update --check to force update")
1551 hint = _("merge or update --check to force update")
1534 raise error.Abort(msg, hint=hint)
1552 raise error.Abort(msg, hint=hint)
1535 else:
1553 else:
1536 # Allow jumping branches if clean and specific rev given
1554 # Allow jumping branches if clean and specific rev given
1537 pas = [p1]
1555 pas = [p1]
1538
1556
1539 # deprecated config: merge.followcopies
1557 # deprecated config: merge.followcopies
1540 followcopies = False
1558 followcopies = False
1541 if overwrite:
1559 if overwrite:
1542 pas = [wc]
1560 pas = [wc]
1543 elif pas == [p2]: # backwards
1561 elif pas == [p2]: # backwards
1544 pas = [wc.p1()]
1562 pas = [wc.p1()]
1545 elif not branchmerge and not wc.dirty(missing=True):
1563 elif not branchmerge and not wc.dirty(missing=True):
1546 pass
1564 pass
1547 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1565 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1548 followcopies = True
1566 followcopies = True
1549
1567
1550 ### calculate phase
1568 ### calculate phase
1551 actionbyfile, diverge, renamedelete = calculateupdates(
1569 actionbyfile, diverge, renamedelete = calculateupdates(
1552 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1570 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1553 followcopies, matcher=matcher, mergeforce=mergeforce)
1571 followcopies, matcher=matcher, mergeforce=mergeforce)
1554
1572
1555 # Prompt and create actions. Most of this is in the resolve phase
1573 # Prompt and create actions. Most of this is in the resolve phase
1556 # already, but we can't handle .hgsubstate in filemerge or
1574 # already, but we can't handle .hgsubstate in filemerge or
1557 # subrepo.submerge yet so we have to keep prompting for it.
1575 # subrepo.submerge yet so we have to keep prompting for it.
1558 if '.hgsubstate' in actionbyfile:
1576 if '.hgsubstate' in actionbyfile:
1559 f = '.hgsubstate'
1577 f = '.hgsubstate'
1560 m, args, msg = actionbyfile[f]
1578 m, args, msg = actionbyfile[f]
1561 prompts = filemerge.partextras(labels)
1579 prompts = filemerge.partextras(labels)
1562 prompts['f'] = f
1580 prompts['f'] = f
1563 if m == 'cd':
1581 if m == 'cd':
1564 if repo.ui.promptchoice(
1582 if repo.ui.promptchoice(
1565 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1583 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1566 "use (c)hanged version or (d)elete?"
1584 "use (c)hanged version or (d)elete?"
1567 "$$ &Changed $$ &Delete") % prompts, 0):
1585 "$$ &Changed $$ &Delete") % prompts, 0):
1568 actionbyfile[f] = ('r', None, "prompt delete")
1586 actionbyfile[f] = ('r', None, "prompt delete")
1569 elif f in p1:
1587 elif f in p1:
1570 actionbyfile[f] = ('am', None, "prompt keep")
1588 actionbyfile[f] = ('am', None, "prompt keep")
1571 else:
1589 else:
1572 actionbyfile[f] = ('a', None, "prompt keep")
1590 actionbyfile[f] = ('a', None, "prompt keep")
1573 elif m == 'dc':
1591 elif m == 'dc':
1574 f1, f2, fa, move, anc = args
1592 f1, f2, fa, move, anc = args
1575 flags = p2[f2].flags()
1593 flags = p2[f2].flags()
1576 if repo.ui.promptchoice(
1594 if repo.ui.promptchoice(
1577 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1595 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1578 "use (c)hanged version or leave (d)eleted?"
1596 "use (c)hanged version or leave (d)eleted?"
1579 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1597 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1580 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1598 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1581 else:
1599 else:
1582 del actionbyfile[f]
1600 del actionbyfile[f]
1583
1601
1584 # Convert to dictionary-of-lists format
1602 # Convert to dictionary-of-lists format
1585 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1603 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1586 for f, (m, args, msg) in actionbyfile.iteritems():
1604 for f, (m, args, msg) in actionbyfile.iteritems():
1587 if m not in actions:
1605 if m not in actions:
1588 actions[m] = []
1606 actions[m] = []
1589 actions[m].append((f, args, msg))
1607 actions[m].append((f, args, msg))
1590
1608
1591 if not util.fscasesensitive(repo.path):
1609 if not util.fscasesensitive(repo.path):
1592 # check collision between files only in p2 for clean update
1610 # check collision between files only in p2 for clean update
1593 if (not branchmerge and
1611 if (not branchmerge and
1594 (force or not wc.dirty(missing=True, branch=False))):
1612 (force or not wc.dirty(missing=True, branch=False))):
1595 _checkcollision(repo, p2.manifest(), None)
1613 _checkcollision(repo, p2.manifest(), None)
1596 else:
1614 else:
1597 _checkcollision(repo, wc.manifest(), actions)
1615 _checkcollision(repo, wc.manifest(), actions)
1598
1616
1599 # divergent renames
1617 # divergent renames
1600 for f, fl in sorted(diverge.iteritems()):
1618 for f, fl in sorted(diverge.iteritems()):
1601 repo.ui.warn(_("note: possible conflict - %s was renamed "
1619 repo.ui.warn(_("note: possible conflict - %s was renamed "
1602 "multiple times to:\n") % f)
1620 "multiple times to:\n") % f)
1603 for nf in fl:
1621 for nf in fl:
1604 repo.ui.warn(" %s\n" % nf)
1622 repo.ui.warn(" %s\n" % nf)
1605
1623
1606 # rename and delete
1624 # rename and delete
1607 for f, fl in sorted(renamedelete.iteritems()):
1625 for f, fl in sorted(renamedelete.iteritems()):
1608 repo.ui.warn(_("note: possible conflict - %s was deleted "
1626 repo.ui.warn(_("note: possible conflict - %s was deleted "
1609 "and renamed to:\n") % f)
1627 "and renamed to:\n") % f)
1610 for nf in fl:
1628 for nf in fl:
1611 repo.ui.warn(" %s\n" % nf)
1629 repo.ui.warn(" %s\n" % nf)
1612
1630
1613 ### apply phase
1631 ### apply phase
1614 if not branchmerge: # just jump to the new rev
1632 if not branchmerge: # just jump to the new rev
1615 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1633 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1616 if not partial:
1634 if not partial:
1617 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1635 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1618 # note that we're in the middle of an update
1636 # note that we're in the middle of an update
1619 repo.vfs.write('updatestate', p2.hex())
1637 repo.vfs.write('updatestate', p2.hex())
1620
1638
1621 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1639 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1622
1640
1623 if not partial:
1641 if not partial:
1624 repo.dirstate.beginparentchange()
1642 repo.dirstate.beginparentchange()
1625 repo.setparents(fp1, fp2)
1643 repo.setparents(fp1, fp2)
1626 recordupdates(repo, actions, branchmerge)
1644 recordupdates(repo, actions, branchmerge)
1627 # update completed, clear state
1645 # update completed, clear state
1628 util.unlink(repo.join('updatestate'))
1646 util.unlink(repo.join('updatestate'))
1629
1647
1630 if not branchmerge:
1648 if not branchmerge:
1631 repo.dirstate.setbranch(p2.branch())
1649 repo.dirstate.setbranch(p2.branch())
1632 repo.dirstate.endparentchange()
1650 repo.dirstate.endparentchange()
1633
1651
1634 if not partial:
1652 if not partial:
1635 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1653 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1636 return stats
1654 return stats
1637
1655
1638 def graft(repo, ctx, pctx, labels, keepparent=False):
1656 def graft(repo, ctx, pctx, labels, keepparent=False):
1639 """Do a graft-like merge.
1657 """Do a graft-like merge.
1640
1658
1641 This is a merge where the merge ancestor is chosen such that one
1659 This is a merge where the merge ancestor is chosen such that one
1642 or more changesets are grafted onto the current changeset. In
1660 or more changesets are grafted onto the current changeset. In
1643 addition to the merge, this fixes up the dirstate to include only
1661 addition to the merge, this fixes up the dirstate to include only
1644 a single parent (if keepparent is False) and tries to duplicate any
1662 a single parent (if keepparent is False) and tries to duplicate any
1645 renames/copies appropriately.
1663 renames/copies appropriately.
1646
1664
1647 ctx - changeset to rebase
1665 ctx - changeset to rebase
1648 pctx - merge base, usually ctx.p1()
1666 pctx - merge base, usually ctx.p1()
1649 labels - merge labels eg ['local', 'graft']
1667 labels - merge labels eg ['local', 'graft']
1650 keepparent - keep second parent if any
1668 keepparent - keep second parent if any
1651
1669
1652 """
1670 """
1653 # If we're grafting a descendant onto an ancestor, be sure to pass
1671 # If we're grafting a descendant onto an ancestor, be sure to pass
1654 # mergeancestor=True to update. This does two things: 1) allows the merge if
1672 # mergeancestor=True to update. This does two things: 1) allows the merge if
1655 # the destination is the same as the parent of the ctx (so we can use graft
1673 # the destination is the same as the parent of the ctx (so we can use graft
1656 # to copy commits), and 2) informs update that the incoming changes are
1674 # to copy commits), and 2) informs update that the incoming changes are
1657 # newer than the destination so it doesn't prompt about "remote changed foo
1675 # newer than the destination so it doesn't prompt about "remote changed foo
1658 # which local deleted".
1676 # which local deleted".
1659 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1677 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1660
1678
1661 stats = update(repo, ctx.node(), True, True, pctx.node(),
1679 stats = update(repo, ctx.node(), True, True, pctx.node(),
1662 mergeancestor=mergeancestor, labels=labels)
1680 mergeancestor=mergeancestor, labels=labels)
1663
1681
1664 pother = nullid
1682 pother = nullid
1665 parents = ctx.parents()
1683 parents = ctx.parents()
1666 if keepparent and len(parents) == 2 and pctx in parents:
1684 if keepparent and len(parents) == 2 and pctx in parents:
1667 parents.remove(pctx)
1685 parents.remove(pctx)
1668 pother = parents[0].node()
1686 pother = parents[0].node()
1669
1687
1670 repo.dirstate.beginparentchange()
1688 repo.dirstate.beginparentchange()
1671 repo.setparents(repo['.'].node(), pother)
1689 repo.setparents(repo['.'].node(), pother)
1672 repo.dirstate.write(repo.currenttransaction())
1690 repo.dirstate.write(repo.currenttransaction())
1673 # fix up dirstate for copies and renames
1691 # fix up dirstate for copies and renames
1674 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1692 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1675 repo.dirstate.endparentchange()
1693 repo.dirstate.endparentchange()
1676 return stats
1694 return stats
@@ -1,897 +1,899 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > rebase=
3 > rebase=
4 >
4 >
5 > [phases]
5 > [phases]
6 > publish=False
6 > publish=False
7 >
7 >
8 > [alias]
8 > [alias]
9 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
9 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
10 > EOF
10 > EOF
11
11
12
12
13 $ hg init a
13 $ hg init a
14 $ cd a
14 $ cd a
15 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
15 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
16 adding changesets
16 adding changesets
17 adding manifests
17 adding manifests
18 adding file changes
18 adding file changes
19 added 8 changesets with 7 changes to 7 files (+2 heads)
19 added 8 changesets with 7 changes to 7 files (+2 heads)
20 (run 'hg heads' to see heads, 'hg merge' to merge)
20 (run 'hg heads' to see heads, 'hg merge' to merge)
21 $ hg up tip
21 $ hg up tip
22 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 $ cd ..
23 $ cd ..
24
24
25
25
26 Rebasing
26 Rebasing
27 D onto H - simple rebase:
27 D onto H - simple rebase:
28 (this also tests that editor is invoked if '--edit' is specified, and that we
28 (this also tests that editor is invoked if '--edit' is specified, and that we
29 can abort or warn for colliding untracked files)
29 can abort or warn for colliding untracked files)
30
30
31 $ hg clone -q -u . a a1
31 $ hg clone -q -u . a a1
32 $ cd a1
32 $ cd a1
33
33
34 $ hg tglog
34 $ hg tglog
35 @ 7: 'H'
35 @ 7: 'H'
36 |
36 |
37 | o 6: 'G'
37 | o 6: 'G'
38 |/|
38 |/|
39 o | 5: 'F'
39 o | 5: 'F'
40 | |
40 | |
41 | o 4: 'E'
41 | o 4: 'E'
42 |/
42 |/
43 | o 3: 'D'
43 | o 3: 'D'
44 | |
44 | |
45 | o 2: 'C'
45 | o 2: 'C'
46 | |
46 | |
47 | o 1: 'B'
47 | o 1: 'B'
48 |/
48 |/
49 o 0: 'A'
49 o 0: 'A'
50
50
51
51
52 $ hg status --rev "3^1" --rev 3
52 $ hg status --rev "3^1" --rev 3
53 A D
53 A D
54 $ echo collide > D
54 $ echo collide > D
55 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
55 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
56 rebasing 3:32af7686d403 "D"
56 rebasing 3:32af7686d403 "D"
57 D: replacing untracked file
57 D: replacing untracked file
58 D
58 D
59
59
60
60
61 HG: Enter commit message. Lines beginning with 'HG:' are removed.
61 HG: Enter commit message. Lines beginning with 'HG:' are removed.
62 HG: Leave message empty to abort commit.
62 HG: Leave message empty to abort commit.
63 HG: --
63 HG: --
64 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
64 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
65 HG: branch 'default'
65 HG: branch 'default'
66 HG: added D
66 HG: added D
67 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
67 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
68 $ cat D.orig
68 $ cat D.orig
69 collide
69 collide
70 $ rm D.orig
70 $ rm D.orig
71
71
72 $ hg tglog
72 $ hg tglog
73 o 7: 'D'
73 o 7: 'D'
74 |
74 |
75 @ 6: 'H'
75 @ 6: 'H'
76 |
76 |
77 | o 5: 'G'
77 | o 5: 'G'
78 |/|
78 |/|
79 o | 4: 'F'
79 o | 4: 'F'
80 | |
80 | |
81 | o 3: 'E'
81 | o 3: 'E'
82 |/
82 |/
83 | o 2: 'C'
83 | o 2: 'C'
84 | |
84 | |
85 | o 1: 'B'
85 | o 1: 'B'
86 |/
86 |/
87 o 0: 'A'
87 o 0: 'A'
88
88
89 $ cd ..
89 $ cd ..
90
90
91
91
92 D onto F - intermediate point:
92 D onto F - intermediate point:
93 (this also tests that editor is not invoked if '--edit' is not specified, and
93 (this also tests that editor is not invoked if '--edit' is not specified, and
94 that we can ignore for colliding untracked files)
94 that we can ignore for colliding untracked files)
95
95
96 $ hg clone -q -u . a a2
96 $ hg clone -q -u . a a2
97 $ cd a2
97 $ cd a2
98 $ echo collide > D
98 $ echo collide > D
99
99
100 $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
100 $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
101 rebasing 3:32af7686d403 "D"
101 rebasing 3:32af7686d403 "D"
102 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
102 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
103 $ cat D.orig
103 $ cat D.orig
104 collide
104 collide
105 $ rm D.orig
105 $ rm D.orig
106
106
107 $ hg tglog
107 $ hg tglog
108 o 7: 'D'
108 o 7: 'D'
109 |
109 |
110 | @ 6: 'H'
110 | @ 6: 'H'
111 |/
111 |/
112 | o 5: 'G'
112 | o 5: 'G'
113 |/|
113 |/|
114 o | 4: 'F'
114 o | 4: 'F'
115 | |
115 | |
116 | o 3: 'E'
116 | o 3: 'E'
117 |/
117 |/
118 | o 2: 'C'
118 | o 2: 'C'
119 | |
119 | |
120 | o 1: 'B'
120 | o 1: 'B'
121 |/
121 |/
122 o 0: 'A'
122 o 0: 'A'
123
123
124 $ cd ..
124 $ cd ..
125
125
126
126
127 E onto H - skip of G:
127 E onto H - skip of G:
128 (this also tests that we can overwrite untracked files and don't create backups
128 (this also tests that we can overwrite untracked files and don't create backups
129 if they have the same contents)
129 if they have the same contents)
130
130
131 $ hg clone -q -u . a a3
131 $ hg clone -q -u . a a3
132 $ cd a3
132 $ cd a3
133 $ hg cat -r 4 E | tee E
133 $ hg cat -r 4 E | tee E
134 E
134 E
135
135
136 $ hg rebase -s 4 -d 7
136 $ hg rebase -s 4 -d 7
137 rebasing 4:9520eea781bc "E"
137 rebasing 4:9520eea781bc "E"
138 rebasing 6:eea13746799a "G"
138 rebasing 6:eea13746799a "G"
139 note: rebase of 6:eea13746799a created no changes to commit
139 note: rebase of 6:eea13746799a created no changes to commit
140 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-backup.hg (glob)
140 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-backup.hg (glob)
141 $ f E.orig
141 $ f E.orig
142 E.orig: file not found
142 E.orig: file not found
143
143
144 $ hg tglog
144 $ hg tglog
145 o 6: 'E'
145 o 6: 'E'
146 |
146 |
147 @ 5: 'H'
147 @ 5: 'H'
148 |
148 |
149 o 4: 'F'
149 o 4: 'F'
150 |
150 |
151 | o 3: 'D'
151 | o 3: 'D'
152 | |
152 | |
153 | o 2: 'C'
153 | o 2: 'C'
154 | |
154 | |
155 | o 1: 'B'
155 | o 1: 'B'
156 |/
156 |/
157 o 0: 'A'
157 o 0: 'A'
158
158
159 $ cd ..
159 $ cd ..
160
160
161
161
162 F onto E - rebase of a branching point (skip G):
162 F onto E - rebase of a branching point (skip G):
163
163
164 $ hg clone -q -u . a a4
164 $ hg clone -q -u . a a4
165 $ cd a4
165 $ cd a4
166
166
167 $ hg rebase -s 5 -d 4
167 $ hg rebase -s 5 -d 4
168 rebasing 5:24b6387c8c8c "F"
168 rebasing 5:24b6387c8c8c "F"
169 rebasing 6:eea13746799a "G"
169 rebasing 6:eea13746799a "G"
170 note: rebase of 6:eea13746799a created no changes to commit
170 note: rebase of 6:eea13746799a created no changes to commit
171 rebasing 7:02de42196ebe "H" (tip)
171 rebasing 7:02de42196ebe "H" (tip)
172 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
172 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
173
173
174 $ hg tglog
174 $ hg tglog
175 @ 6: 'H'
175 @ 6: 'H'
176 |
176 |
177 o 5: 'F'
177 o 5: 'F'
178 |
178 |
179 o 4: 'E'
179 o 4: 'E'
180 |
180 |
181 | o 3: 'D'
181 | o 3: 'D'
182 | |
182 | |
183 | o 2: 'C'
183 | o 2: 'C'
184 | |
184 | |
185 | o 1: 'B'
185 | o 1: 'B'
186 |/
186 |/
187 o 0: 'A'
187 o 0: 'A'
188
188
189 $ cd ..
189 $ cd ..
190
190
191
191
192 G onto H - merged revision having a parent in ancestors of target:
192 G onto H - merged revision having a parent in ancestors of target:
193
193
194 $ hg clone -q -u . a a5
194 $ hg clone -q -u . a a5
195 $ cd a5
195 $ cd a5
196
196
197 $ hg rebase -s 6 -d 7
197 $ hg rebase -s 6 -d 7
198 rebasing 6:eea13746799a "G"
198 rebasing 6:eea13746799a "G"
199 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/eea13746799a-883828ed-backup.hg (glob)
199 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/eea13746799a-883828ed-backup.hg (glob)
200
200
201 $ hg tglog
201 $ hg tglog
202 o 7: 'G'
202 o 7: 'G'
203 |\
203 |\
204 | @ 6: 'H'
204 | @ 6: 'H'
205 | |
205 | |
206 | o 5: 'F'
206 | o 5: 'F'
207 | |
207 | |
208 o | 4: 'E'
208 o | 4: 'E'
209 |/
209 |/
210 | o 3: 'D'
210 | o 3: 'D'
211 | |
211 | |
212 | o 2: 'C'
212 | o 2: 'C'
213 | |
213 | |
214 | o 1: 'B'
214 | o 1: 'B'
215 |/
215 |/
216 o 0: 'A'
216 o 0: 'A'
217
217
218 $ cd ..
218 $ cd ..
219
219
220
220
221 F onto B - G maintains E as parent:
221 F onto B - G maintains E as parent:
222
222
223 $ hg clone -q -u . a a6
223 $ hg clone -q -u . a a6
224 $ cd a6
224 $ cd a6
225
225
226 $ hg rebase -s 5 -d 1
226 $ hg rebase -s 5 -d 1
227 rebasing 5:24b6387c8c8c "F"
227 rebasing 5:24b6387c8c8c "F"
228 rebasing 6:eea13746799a "G"
228 rebasing 6:eea13746799a "G"
229 rebasing 7:02de42196ebe "H" (tip)
229 rebasing 7:02de42196ebe "H" (tip)
230 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
230 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
231
231
232 $ hg tglog
232 $ hg tglog
233 @ 7: 'H'
233 @ 7: 'H'
234 |
234 |
235 | o 6: 'G'
235 | o 6: 'G'
236 |/|
236 |/|
237 o | 5: 'F'
237 o | 5: 'F'
238 | |
238 | |
239 | o 4: 'E'
239 | o 4: 'E'
240 | |
240 | |
241 | | o 3: 'D'
241 | | o 3: 'D'
242 | | |
242 | | |
243 +---o 2: 'C'
243 +---o 2: 'C'
244 | |
244 | |
245 o | 1: 'B'
245 o | 1: 'B'
246 |/
246 |/
247 o 0: 'A'
247 o 0: 'A'
248
248
249 $ cd ..
249 $ cd ..
250
250
251
251
252 These will fail (using --source):
252 These will fail (using --source):
253
253
254 G onto F - rebase onto an ancestor:
254 G onto F - rebase onto an ancestor:
255
255
256 $ hg clone -q -u . a a7
256 $ hg clone -q -u . a a7
257 $ cd a7
257 $ cd a7
258
258
259 $ hg rebase -s 6 -d 5
259 $ hg rebase -s 6 -d 5
260 nothing to rebase
260 nothing to rebase
261 [1]
261 [1]
262
262
263 F onto G - rebase onto a descendant:
263 F onto G - rebase onto a descendant:
264
264
265 $ hg rebase -s 5 -d 6
265 $ hg rebase -s 5 -d 6
266 abort: source is ancestor of destination
266 abort: source is ancestor of destination
267 [255]
267 [255]
268
268
269 G onto B - merge revision with both parents not in ancestors of target:
269 G onto B - merge revision with both parents not in ancestors of target:
270
270
271 $ hg rebase -s 6 -d 1
271 $ hg rebase -s 6 -d 1
272 rebasing 6:eea13746799a "G"
272 rebasing 6:eea13746799a "G"
273 abort: cannot use revision 6 as base, result would have 3 parents
273 abort: cannot use revision 6 as base, result would have 3 parents
274 [255]
274 [255]
275
275
276
276
277 These will abort gracefully (using --base):
277 These will abort gracefully (using --base):
278
278
279 G onto G - rebase onto same changeset:
279 G onto G - rebase onto same changeset:
280
280
281 $ hg rebase -b 6 -d 6
281 $ hg rebase -b 6 -d 6
282 nothing to rebase - eea13746799a is both "base" and destination
282 nothing to rebase - eea13746799a is both "base" and destination
283 [1]
283 [1]
284
284
285 G onto F - rebase onto an ancestor:
285 G onto F - rebase onto an ancestor:
286
286
287 $ hg rebase -b 6 -d 5
287 $ hg rebase -b 6 -d 5
288 nothing to rebase
288 nothing to rebase
289 [1]
289 [1]
290
290
291 F onto G - rebase onto a descendant:
291 F onto G - rebase onto a descendant:
292
292
293 $ hg rebase -b 5 -d 6
293 $ hg rebase -b 5 -d 6
294 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
294 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
295 [1]
295 [1]
296
296
297 C onto A - rebase onto an ancestor:
297 C onto A - rebase onto an ancestor:
298
298
299 $ hg rebase -d 0 -s 2
299 $ hg rebase -d 0 -s 2
300 rebasing 2:5fddd98957c8 "C"
300 rebasing 2:5fddd98957c8 "C"
301 rebasing 3:32af7686d403 "D"
301 rebasing 3:32af7686d403 "D"
302 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-f9244fa1-backup.hg (glob)
302 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-f9244fa1-backup.hg (glob)
303 $ hg tglog
303 $ hg tglog
304 o 7: 'D'
304 o 7: 'D'
305 |
305 |
306 o 6: 'C'
306 o 6: 'C'
307 |
307 |
308 | @ 5: 'H'
308 | @ 5: 'H'
309 | |
309 | |
310 | | o 4: 'G'
310 | | o 4: 'G'
311 | |/|
311 | |/|
312 | o | 3: 'F'
312 | o | 3: 'F'
313 |/ /
313 |/ /
314 | o 2: 'E'
314 | o 2: 'E'
315 |/
315 |/
316 | o 1: 'B'
316 | o 1: 'B'
317 |/
317 |/
318 o 0: 'A'
318 o 0: 'A'
319
319
320
320
321 Check rebasing public changeset
321 Check rebasing public changeset
322
322
323 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
323 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
324 $ hg rebase -d 0 -b 6
324 $ hg rebase -d 0 -b 6
325 nothing to rebase
325 nothing to rebase
326 [1]
326 [1]
327 $ hg rebase -d 5 -b 6
327 $ hg rebase -d 5 -b 6
328 abort: can't rebase public changeset e1c4361dd923
328 abort: can't rebase public changeset e1c4361dd923
329 (see 'hg help phases' for details)
329 (see 'hg help phases' for details)
330 [255]
330 [255]
331
331
332 $ hg rebase -d 5 -b 6 --keep
332 $ hg rebase -d 5 -b 6 --keep
333 rebasing 6:e1c4361dd923 "C"
333 rebasing 6:e1c4361dd923 "C"
334 rebasing 7:c9659aac0000 "D" (tip)
334 rebasing 7:c9659aac0000 "D" (tip)
335
335
336 Check rebasing mutable changeset
336 Check rebasing mutable changeset
337 Source phase greater or equal to destination phase: new changeset get the phase of source:
337 Source phase greater or equal to destination phase: new changeset get the phase of source:
338 $ hg id -n
338 $ hg id -n
339 5
339 5
340 $ hg rebase -s9 -d0
340 $ hg rebase -s9 -d0
341 rebasing 9:2b23e52411f4 "D" (tip)
341 rebasing 9:2b23e52411f4 "D" (tip)
342 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-f942decf-backup.hg (glob)
342 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-f942decf-backup.hg (glob)
343 $ hg id -n # check we updated back to parent
343 $ hg id -n # check we updated back to parent
344 5
344 5
345 $ hg log --template "{phase}\n" -r 9
345 $ hg log --template "{phase}\n" -r 9
346 draft
346 draft
347 $ hg rebase -s9 -d1
347 $ hg rebase -s9 -d1
348 rebasing 9:2cb10d0cfc6c "D" (tip)
348 rebasing 9:2cb10d0cfc6c "D" (tip)
349 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-ddb0f256-backup.hg (glob)
349 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-ddb0f256-backup.hg (glob)
350 $ hg log --template "{phase}\n" -r 9
350 $ hg log --template "{phase}\n" -r 9
351 draft
351 draft
352 $ hg phase --force --secret 9
352 $ hg phase --force --secret 9
353 $ hg rebase -s9 -d0
353 $ hg rebase -s9 -d0
354 rebasing 9:c5b12b67163a "D" (tip)
354 rebasing 9:c5b12b67163a "D" (tip)
355 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-4e372053-backup.hg (glob)
355 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-4e372053-backup.hg (glob)
356 $ hg log --template "{phase}\n" -r 9
356 $ hg log --template "{phase}\n" -r 9
357 secret
357 secret
358 $ hg rebase -s9 -d1
358 $ hg rebase -s9 -d1
359 rebasing 9:2a0524f868ac "D" (tip)
359 rebasing 9:2a0524f868ac "D" (tip)
360 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-cefd8574-backup.hg (glob)
360 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-cefd8574-backup.hg (glob)
361 $ hg log --template "{phase}\n" -r 9
361 $ hg log --template "{phase}\n" -r 9
362 secret
362 secret
363 Source phase lower than destination phase: new changeset get the phase of destination:
363 Source phase lower than destination phase: new changeset get the phase of destination:
364 $ hg rebase -s8 -d9
364 $ hg rebase -s8 -d9
365 rebasing 8:6d4f22462821 "C"
365 rebasing 8:6d4f22462821 "C"
366 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-3441f70b-backup.hg (glob)
366 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-3441f70b-backup.hg (glob)
367 $ hg log --template "{phase}\n" -r 'rev(9)'
367 $ hg log --template "{phase}\n" -r 'rev(9)'
368 secret
368 secret
369
369
370 $ cd ..
370 $ cd ..
371
371
372 Test for revset
372 Test for revset
373
373
374 We need a bit different graph
374 We need a bit different graph
375 All destination are B
375 All destination are B
376
376
377 $ hg init ah
377 $ hg init ah
378 $ cd ah
378 $ cd ah
379 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
379 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
380 adding changesets
380 adding changesets
381 adding manifests
381 adding manifests
382 adding file changes
382 adding file changes
383 added 9 changesets with 9 changes to 9 files (+2 heads)
383 added 9 changesets with 9 changes to 9 files (+2 heads)
384 (run 'hg heads' to see heads, 'hg merge' to merge)
384 (run 'hg heads' to see heads, 'hg merge' to merge)
385 $ hg tglog
385 $ hg tglog
386 o 8: 'I'
386 o 8: 'I'
387 |
387 |
388 o 7: 'H'
388 o 7: 'H'
389 |
389 |
390 o 6: 'G'
390 o 6: 'G'
391 |
391 |
392 | o 5: 'F'
392 | o 5: 'F'
393 | |
393 | |
394 | o 4: 'E'
394 | o 4: 'E'
395 |/
395 |/
396 o 3: 'D'
396 o 3: 'D'
397 |
397 |
398 o 2: 'C'
398 o 2: 'C'
399 |
399 |
400 | o 1: 'B'
400 | o 1: 'B'
401 |/
401 |/
402 o 0: 'A'
402 o 0: 'A'
403
403
404 $ cd ..
404 $ cd ..
405
405
406
406
407 Simple case with keep:
407 Simple case with keep:
408
408
409 Source on have two descendant heads but ask for one
409 Source on have two descendant heads but ask for one
410
410
411 $ hg clone -q -u . ah ah1
411 $ hg clone -q -u . ah ah1
412 $ cd ah1
412 $ cd ah1
413 $ hg rebase -r '2::8' -d 1
413 $ hg rebase -r '2::8' -d 1
414 abort: can't remove original changesets with unrebased descendants
414 abort: can't remove original changesets with unrebased descendants
415 (use --keep to keep original changesets)
415 (use --keep to keep original changesets)
416 [255]
416 [255]
417 $ hg rebase -r '2::8' -d 1 -k
417 $ hg rebase -r '2::8' -d 1 -k
418 rebasing 2:c9e50f6cdc55 "C"
418 rebasing 2:c9e50f6cdc55 "C"
419 rebasing 3:ffd453c31098 "D"
419 rebasing 3:ffd453c31098 "D"
420 rebasing 6:3d8a618087a7 "G"
420 rebasing 6:3d8a618087a7 "G"
421 rebasing 7:72434a4e60b0 "H"
421 rebasing 7:72434a4e60b0 "H"
422 rebasing 8:479ddb54a924 "I" (tip)
422 rebasing 8:479ddb54a924 "I" (tip)
423 $ hg tglog
423 $ hg tglog
424 o 13: 'I'
424 o 13: 'I'
425 |
425 |
426 o 12: 'H'
426 o 12: 'H'
427 |
427 |
428 o 11: 'G'
428 o 11: 'G'
429 |
429 |
430 o 10: 'D'
430 o 10: 'D'
431 |
431 |
432 o 9: 'C'
432 o 9: 'C'
433 |
433 |
434 | o 8: 'I'
434 | o 8: 'I'
435 | |
435 | |
436 | o 7: 'H'
436 | o 7: 'H'
437 | |
437 | |
438 | o 6: 'G'
438 | o 6: 'G'
439 | |
439 | |
440 | | o 5: 'F'
440 | | o 5: 'F'
441 | | |
441 | | |
442 | | o 4: 'E'
442 | | o 4: 'E'
443 | |/
443 | |/
444 | o 3: 'D'
444 | o 3: 'D'
445 | |
445 | |
446 | o 2: 'C'
446 | o 2: 'C'
447 | |
447 | |
448 o | 1: 'B'
448 o | 1: 'B'
449 |/
449 |/
450 o 0: 'A'
450 o 0: 'A'
451
451
452
452
453 $ cd ..
453 $ cd ..
454
454
455 Base on have one descendant heads we ask for but common ancestor have two
455 Base on have one descendant heads we ask for but common ancestor have two
456
456
457 $ hg clone -q -u . ah ah2
457 $ hg clone -q -u . ah ah2
458 $ cd ah2
458 $ cd ah2
459 $ hg rebase -r '3::8' -d 1
459 $ hg rebase -r '3::8' -d 1
460 abort: can't remove original changesets with unrebased descendants
460 abort: can't remove original changesets with unrebased descendants
461 (use --keep to keep original changesets)
461 (use --keep to keep original changesets)
462 [255]
462 [255]
463 $ hg rebase -r '3::8' -d 1 --keep
463 $ hg rebase -r '3::8' -d 1 --keep
464 rebasing 3:ffd453c31098 "D"
464 rebasing 3:ffd453c31098 "D"
465 rebasing 6:3d8a618087a7 "G"
465 rebasing 6:3d8a618087a7 "G"
466 rebasing 7:72434a4e60b0 "H"
466 rebasing 7:72434a4e60b0 "H"
467 rebasing 8:479ddb54a924 "I" (tip)
467 rebasing 8:479ddb54a924 "I" (tip)
468 $ hg tglog
468 $ hg tglog
469 o 12: 'I'
469 o 12: 'I'
470 |
470 |
471 o 11: 'H'
471 o 11: 'H'
472 |
472 |
473 o 10: 'G'
473 o 10: 'G'
474 |
474 |
475 o 9: 'D'
475 o 9: 'D'
476 |
476 |
477 | o 8: 'I'
477 | o 8: 'I'
478 | |
478 | |
479 | o 7: 'H'
479 | o 7: 'H'
480 | |
480 | |
481 | o 6: 'G'
481 | o 6: 'G'
482 | |
482 | |
483 | | o 5: 'F'
483 | | o 5: 'F'
484 | | |
484 | | |
485 | | o 4: 'E'
485 | | o 4: 'E'
486 | |/
486 | |/
487 | o 3: 'D'
487 | o 3: 'D'
488 | |
488 | |
489 | o 2: 'C'
489 | o 2: 'C'
490 | |
490 | |
491 o | 1: 'B'
491 o | 1: 'B'
492 |/
492 |/
493 o 0: 'A'
493 o 0: 'A'
494
494
495
495
496 $ cd ..
496 $ cd ..
497
497
498 rebase subset
498 rebase subset
499
499
500 $ hg clone -q -u . ah ah3
500 $ hg clone -q -u . ah ah3
501 $ cd ah3
501 $ cd ah3
502 $ hg rebase -r '3::7' -d 1
502 $ hg rebase -r '3::7' -d 1
503 abort: can't remove original changesets with unrebased descendants
503 abort: can't remove original changesets with unrebased descendants
504 (use --keep to keep original changesets)
504 (use --keep to keep original changesets)
505 [255]
505 [255]
506 $ hg rebase -r '3::7' -d 1 --keep
506 $ hg rebase -r '3::7' -d 1 --keep
507 rebasing 3:ffd453c31098 "D"
507 rebasing 3:ffd453c31098 "D"
508 rebasing 6:3d8a618087a7 "G"
508 rebasing 6:3d8a618087a7 "G"
509 rebasing 7:72434a4e60b0 "H"
509 rebasing 7:72434a4e60b0 "H"
510 $ hg tglog
510 $ hg tglog
511 o 11: 'H'
511 o 11: 'H'
512 |
512 |
513 o 10: 'G'
513 o 10: 'G'
514 |
514 |
515 o 9: 'D'
515 o 9: 'D'
516 |
516 |
517 | o 8: 'I'
517 | o 8: 'I'
518 | |
518 | |
519 | o 7: 'H'
519 | o 7: 'H'
520 | |
520 | |
521 | o 6: 'G'
521 | o 6: 'G'
522 | |
522 | |
523 | | o 5: 'F'
523 | | o 5: 'F'
524 | | |
524 | | |
525 | | o 4: 'E'
525 | | o 4: 'E'
526 | |/
526 | |/
527 | o 3: 'D'
527 | o 3: 'D'
528 | |
528 | |
529 | o 2: 'C'
529 | o 2: 'C'
530 | |
530 | |
531 o | 1: 'B'
531 o | 1: 'B'
532 |/
532 |/
533 o 0: 'A'
533 o 0: 'A'
534
534
535
535
536 $ cd ..
536 $ cd ..
537
537
538 rebase subset with multiple head
538 rebase subset with multiple head
539
539
540 $ hg clone -q -u . ah ah4
540 $ hg clone -q -u . ah ah4
541 $ cd ah4
541 $ cd ah4
542 $ hg rebase -r '3::(7+5)' -d 1
542 $ hg rebase -r '3::(7+5)' -d 1
543 abort: can't remove original changesets with unrebased descendants
543 abort: can't remove original changesets with unrebased descendants
544 (use --keep to keep original changesets)
544 (use --keep to keep original changesets)
545 [255]
545 [255]
546 $ hg rebase -r '3::(7+5)' -d 1 --keep
546 $ hg rebase -r '3::(7+5)' -d 1 --keep
547 rebasing 3:ffd453c31098 "D"
547 rebasing 3:ffd453c31098 "D"
548 rebasing 4:c01897464e7f "E"
548 rebasing 4:c01897464e7f "E"
549 rebasing 5:41bfcc75ed73 "F"
549 rebasing 5:41bfcc75ed73 "F"
550 rebasing 6:3d8a618087a7 "G"
550 rebasing 6:3d8a618087a7 "G"
551 rebasing 7:72434a4e60b0 "H"
551 rebasing 7:72434a4e60b0 "H"
552 $ hg tglog
552 $ hg tglog
553 o 13: 'H'
553 o 13: 'H'
554 |
554 |
555 o 12: 'G'
555 o 12: 'G'
556 |
556 |
557 | o 11: 'F'
557 | o 11: 'F'
558 | |
558 | |
559 | o 10: 'E'
559 | o 10: 'E'
560 |/
560 |/
561 o 9: 'D'
561 o 9: 'D'
562 |
562 |
563 | o 8: 'I'
563 | o 8: 'I'
564 | |
564 | |
565 | o 7: 'H'
565 | o 7: 'H'
566 | |
566 | |
567 | o 6: 'G'
567 | o 6: 'G'
568 | |
568 | |
569 | | o 5: 'F'
569 | | o 5: 'F'
570 | | |
570 | | |
571 | | o 4: 'E'
571 | | o 4: 'E'
572 | |/
572 | |/
573 | o 3: 'D'
573 | o 3: 'D'
574 | |
574 | |
575 | o 2: 'C'
575 | o 2: 'C'
576 | |
576 | |
577 o | 1: 'B'
577 o | 1: 'B'
578 |/
578 |/
579 o 0: 'A'
579 o 0: 'A'
580
580
581
581
582 $ cd ..
582 $ cd ..
583
583
584 More advanced tests
584 More advanced tests
585
585
586 rebase on ancestor with revset
586 rebase on ancestor with revset
587
587
588 $ hg clone -q -u . ah ah5
588 $ hg clone -q -u . ah ah5
589 $ cd ah5
589 $ cd ah5
590 $ hg rebase -r '6::' -d 2
590 $ hg rebase -r '6::' -d 2
591 rebasing 6:3d8a618087a7 "G"
591 rebasing 6:3d8a618087a7 "G"
592 rebasing 7:72434a4e60b0 "H"
592 rebasing 7:72434a4e60b0 "H"
593 rebasing 8:479ddb54a924 "I" (tip)
593 rebasing 8:479ddb54a924 "I" (tip)
594 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-b4f73f31-backup.hg (glob)
594 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-b4f73f31-backup.hg (glob)
595 $ hg tglog
595 $ hg tglog
596 o 8: 'I'
596 o 8: 'I'
597 |
597 |
598 o 7: 'H'
598 o 7: 'H'
599 |
599 |
600 o 6: 'G'
600 o 6: 'G'
601 |
601 |
602 | o 5: 'F'
602 | o 5: 'F'
603 | |
603 | |
604 | o 4: 'E'
604 | o 4: 'E'
605 | |
605 | |
606 | o 3: 'D'
606 | o 3: 'D'
607 |/
607 |/
608 o 2: 'C'
608 o 2: 'C'
609 |
609 |
610 | o 1: 'B'
610 | o 1: 'B'
611 |/
611 |/
612 o 0: 'A'
612 o 0: 'A'
613
613
614 $ cd ..
614 $ cd ..
615
615
616
616
617 rebase with multiple root.
617 rebase with multiple root.
618 We rebase E and G on B
618 We rebase E and G on B
619 We would expect heads are I, F if it was supported
619 We would expect heads are I, F if it was supported
620
620
621 $ hg clone -q -u . ah ah6
621 $ hg clone -q -u . ah ah6
622 $ cd ah6
622 $ cd ah6
623 $ hg rebase -r '(4+6)::' -d 1
623 $ hg rebase -r '(4+6)::' -d 1
624 rebasing 4:c01897464e7f "E"
624 rebasing 4:c01897464e7f "E"
625 rebasing 5:41bfcc75ed73 "F"
625 rebasing 5:41bfcc75ed73 "F"
626 rebasing 6:3d8a618087a7 "G"
626 rebasing 6:3d8a618087a7 "G"
627 rebasing 7:72434a4e60b0 "H"
627 rebasing 7:72434a4e60b0 "H"
628 rebasing 8:479ddb54a924 "I" (tip)
628 rebasing 8:479ddb54a924 "I" (tip)
629 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-aae93a24-backup.hg (glob)
629 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-aae93a24-backup.hg (glob)
630 $ hg tglog
630 $ hg tglog
631 o 8: 'I'
631 o 8: 'I'
632 |
632 |
633 o 7: 'H'
633 o 7: 'H'
634 |
634 |
635 o 6: 'G'
635 o 6: 'G'
636 |
636 |
637 | o 5: 'F'
637 | o 5: 'F'
638 | |
638 | |
639 | o 4: 'E'
639 | o 4: 'E'
640 |/
640 |/
641 | o 3: 'D'
641 | o 3: 'D'
642 | |
642 | |
643 | o 2: 'C'
643 | o 2: 'C'
644 | |
644 | |
645 o | 1: 'B'
645 o | 1: 'B'
646 |/
646 |/
647 o 0: 'A'
647 o 0: 'A'
648
648
649 $ cd ..
649 $ cd ..
650
650
651 More complex rebase with multiple roots
651 More complex rebase with multiple roots
652 each root have a different common ancestor with the destination and this is a detach
652 each root have a different common ancestor with the destination and this is a detach
653
653
654 (setup)
654 (setup)
655
655
656 $ hg clone -q -u . a a8
656 $ hg clone -q -u . a a8
657 $ cd a8
657 $ cd a8
658 $ echo I > I
658 $ echo I > I
659 $ hg add I
659 $ hg add I
660 $ hg commit -m I
660 $ hg commit -m I
661 $ hg up 4
661 $ hg up 4
662 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
662 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
663 $ echo I > J
663 $ echo I > J
664 $ hg add J
664 $ hg add J
665 $ hg commit -m J
665 $ hg commit -m J
666 created new head
666 created new head
667 $ echo I > K
667 $ echo I > K
668 $ hg add K
668 $ hg add K
669 $ hg commit -m K
669 $ hg commit -m K
670 $ hg tglog
670 $ hg tglog
671 @ 10: 'K'
671 @ 10: 'K'
672 |
672 |
673 o 9: 'J'
673 o 9: 'J'
674 |
674 |
675 | o 8: 'I'
675 | o 8: 'I'
676 | |
676 | |
677 | o 7: 'H'
677 | o 7: 'H'
678 | |
678 | |
679 +---o 6: 'G'
679 +---o 6: 'G'
680 | |/
680 | |/
681 | o 5: 'F'
681 | o 5: 'F'
682 | |
682 | |
683 o | 4: 'E'
683 o | 4: 'E'
684 |/
684 |/
685 | o 3: 'D'
685 | o 3: 'D'
686 | |
686 | |
687 | o 2: 'C'
687 | o 2: 'C'
688 | |
688 | |
689 | o 1: 'B'
689 | o 1: 'B'
690 |/
690 |/
691 o 0: 'A'
691 o 0: 'A'
692
692
693 (actual test)
693 (actual test)
694
694
695 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
695 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
696 rebasing 8:e7ec4e813ba6 "I"
696 rebasing 8:e7ec4e813ba6 "I"
697 rebasing 10:23a4ace37988 "K" (tip)
697 rebasing 10:23a4ace37988 "K" (tip)
698 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-b06984b3-backup.hg (glob)
698 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-b06984b3-backup.hg (glob)
699 $ hg log --rev 'children(desc(G))'
699 $ hg log --rev 'children(desc(G))'
700 changeset: 9:adb617877056
700 changeset: 9:adb617877056
701 parent: 6:eea13746799a
701 parent: 6:eea13746799a
702 user: test
702 user: test
703 date: Thu Jan 01 00:00:00 1970 +0000
703 date: Thu Jan 01 00:00:00 1970 +0000
704 summary: I
704 summary: I
705
705
706 changeset: 10:882431a34a0e
706 changeset: 10:882431a34a0e
707 tag: tip
707 tag: tip
708 parent: 6:eea13746799a
708 parent: 6:eea13746799a
709 user: test
709 user: test
710 date: Thu Jan 01 00:00:00 1970 +0000
710 date: Thu Jan 01 00:00:00 1970 +0000
711 summary: K
711 summary: K
712
712
713 $ hg tglog
713 $ hg tglog
714 @ 10: 'K'
714 @ 10: 'K'
715 |
715 |
716 | o 9: 'I'
716 | o 9: 'I'
717 |/
717 |/
718 | o 8: 'J'
718 | o 8: 'J'
719 | |
719 | |
720 | | o 7: 'H'
720 | | o 7: 'H'
721 | | |
721 | | |
722 o---+ 6: 'G'
722 o---+ 6: 'G'
723 |/ /
723 |/ /
724 | o 5: 'F'
724 | o 5: 'F'
725 | |
725 | |
726 o | 4: 'E'
726 o | 4: 'E'
727 |/
727 |/
728 | o 3: 'D'
728 | o 3: 'D'
729 | |
729 | |
730 | o 2: 'C'
730 | o 2: 'C'
731 | |
731 | |
732 | o 1: 'B'
732 | o 1: 'B'
733 |/
733 |/
734 o 0: 'A'
734 o 0: 'A'
735
735
736
736
737 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
737 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
738
738
739 $ cd ..
739 $ cd ..
740 $ hg init cwd-vanish
740 $ hg init cwd-vanish
741 $ cd cwd-vanish
741 $ cd cwd-vanish
742 $ touch initial-file
742 $ touch initial-file
743 $ hg add initial-file
743 $ hg add initial-file
744 $ hg commit -m 'initial commit'
744 $ hg commit -m 'initial commit'
745 $ touch dest-file
745 $ touch dest-file
746 $ hg add dest-file
746 $ hg add dest-file
747 $ hg commit -m 'dest commit'
747 $ hg commit -m 'dest commit'
748 $ hg up 0
748 $ hg up 0
749 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
749 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
750 $ touch other-file
750 $ touch other-file
751 $ hg add other-file
751 $ hg add other-file
752 $ hg commit -m 'first source commit'
752 $ hg commit -m 'first source commit'
753 created new head
753 created new head
754 $ mkdir subdir
754 $ mkdir subdir
755 $ cd subdir
755 $ cd subdir
756 $ touch subfile
756 $ touch subfile
757 $ hg add subfile
757 $ hg add subfile
758 $ hg commit -m 'second source with subdir'
758 $ hg commit -m 'second source with subdir'
759 $ hg rebase -b . -d 1 --traceback
759 $ hg rebase -b . -d 1 --traceback
760 rebasing 2:779a07b1b7a0 "first source commit"
760 rebasing 2:779a07b1b7a0 "first source commit"
761 current directory was removed
762 (consider changing to repo root: $TESTTMP/cwd-vanish)
761 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
763 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
762 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
764 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
763
765
764 Test that rebase is done in topo order (issue5370)
766 Test that rebase is done in topo order (issue5370)
765
767
766 $ cd ..
768 $ cd ..
767 $ hg init order
769 $ hg init order
768 $ cd order
770 $ cd order
769 $ touch a && hg add a && hg ci -m A
771 $ touch a && hg add a && hg ci -m A
770 $ touch b && hg add b && hg ci -m B
772 $ touch b && hg add b && hg ci -m B
771 $ touch c && hg add c && hg ci -m C
773 $ touch c && hg add c && hg ci -m C
772 $ hg up 1
774 $ hg up 1
773 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
775 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
774 $ touch d && hg add d && hg ci -m D
776 $ touch d && hg add d && hg ci -m D
775 created new head
777 created new head
776 $ hg up 2
778 $ hg up 2
777 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
779 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
778 $ touch e && hg add e && hg ci -m E
780 $ touch e && hg add e && hg ci -m E
779 $ hg up 3
781 $ hg up 3
780 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
782 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
781 $ touch f && hg add f && hg ci -m F
783 $ touch f && hg add f && hg ci -m F
782 $ hg up 0
784 $ hg up 0
783 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
785 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
784 $ touch g && hg add g && hg ci -m G
786 $ touch g && hg add g && hg ci -m G
785 created new head
787 created new head
786
788
787 $ hg tglog
789 $ hg tglog
788 @ 6: 'G'
790 @ 6: 'G'
789 |
791 |
790 | o 5: 'F'
792 | o 5: 'F'
791 | |
793 | |
792 | | o 4: 'E'
794 | | o 4: 'E'
793 | | |
795 | | |
794 | o | 3: 'D'
796 | o | 3: 'D'
795 | | |
797 | | |
796 | | o 2: 'C'
798 | | o 2: 'C'
797 | |/
799 | |/
798 | o 1: 'B'
800 | o 1: 'B'
799 |/
801 |/
800 o 0: 'A'
802 o 0: 'A'
801
803
802
804
803 $ hg rebase -s 1 -d 6
805 $ hg rebase -s 1 -d 6
804 rebasing 1:76035bbd54bd "B"
806 rebasing 1:76035bbd54bd "B"
805 rebasing 2:d84f5cfaaf14 "C"
807 rebasing 2:d84f5cfaaf14 "C"
806 rebasing 4:82ae8dc7a9b7 "E"
808 rebasing 4:82ae8dc7a9b7 "E"
807 rebasing 3:ab709c9f7171 "D"
809 rebasing 3:ab709c9f7171 "D"
808 rebasing 5:412b391de760 "F"
810 rebasing 5:412b391de760 "F"
809 saved backup bundle to $TESTTMP/cwd-vanish/order/.hg/strip-backup/76035bbd54bd-e341bc99-backup.hg (glob)
811 saved backup bundle to $TESTTMP/cwd-vanish/order/.hg/strip-backup/76035bbd54bd-e341bc99-backup.hg (glob)
810
812
811 $ hg tglog
813 $ hg tglog
812 o 6: 'F'
814 o 6: 'F'
813 |
815 |
814 o 5: 'D'
816 o 5: 'D'
815 |
817 |
816 | o 4: 'E'
818 | o 4: 'E'
817 | |
819 | |
818 | o 3: 'C'
820 | o 3: 'C'
819 |/
821 |/
820 o 2: 'B'
822 o 2: 'B'
821 |
823 |
822 @ 1: 'G'
824 @ 1: 'G'
823 |
825 |
824 o 0: 'A'
826 o 0: 'A'
825
827
826
828
827 Test experimental revset
829 Test experimental revset
828 ========================
830 ========================
829
831
830 $ cd ..
832 $ cd ..
831
833
832 Make the repo a bit more interresting
834 Make the repo a bit more interresting
833
835
834 $ hg up 1
836 $ hg up 1
835 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
837 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
836 $ echo aaa > aaa
838 $ echo aaa > aaa
837 $ hg add aaa
839 $ hg add aaa
838 $ hg commit -m aaa
840 $ hg commit -m aaa
839 created new head
841 created new head
840 $ hg log -G
842 $ hg log -G
841 @ changeset: 4:5f7bc9025ed2
843 @ changeset: 4:5f7bc9025ed2
842 | tag: tip
844 | tag: tip
843 | parent: 1:58d79cc1cf43
845 | parent: 1:58d79cc1cf43
844 | user: test
846 | user: test
845 | date: Thu Jan 01 00:00:00 1970 +0000
847 | date: Thu Jan 01 00:00:00 1970 +0000
846 | summary: aaa
848 | summary: aaa
847 |
849 |
848 | o changeset: 3:1910d5ff34ea
850 | o changeset: 3:1910d5ff34ea
849 | | user: test
851 | | user: test
850 | | date: Thu Jan 01 00:00:00 1970 +0000
852 | | date: Thu Jan 01 00:00:00 1970 +0000
851 | | summary: second source with subdir
853 | | summary: second source with subdir
852 | |
854 | |
853 | o changeset: 2:82901330b6ef
855 | o changeset: 2:82901330b6ef
854 |/ user: test
856 |/ user: test
855 | date: Thu Jan 01 00:00:00 1970 +0000
857 | date: Thu Jan 01 00:00:00 1970 +0000
856 | summary: first source commit
858 | summary: first source commit
857 |
859 |
858 o changeset: 1:58d79cc1cf43
860 o changeset: 1:58d79cc1cf43
859 | user: test
861 | user: test
860 | date: Thu Jan 01 00:00:00 1970 +0000
862 | date: Thu Jan 01 00:00:00 1970 +0000
861 | summary: dest commit
863 | summary: dest commit
862 |
864 |
863 o changeset: 0:e94b687f7da3
865 o changeset: 0:e94b687f7da3
864 user: test
866 user: test
865 date: Thu Jan 01 00:00:00 1970 +0000
867 date: Thu Jan 01 00:00:00 1970 +0000
866 summary: initial commit
868 summary: initial commit
867
869
868
870
869 Testing from lower head
871 Testing from lower head
870
872
871 $ hg up 3
873 $ hg up 3
872 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
874 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
873 $ hg log -r '_destrebase()'
875 $ hg log -r '_destrebase()'
874 changeset: 4:5f7bc9025ed2
876 changeset: 4:5f7bc9025ed2
875 tag: tip
877 tag: tip
876 parent: 1:58d79cc1cf43
878 parent: 1:58d79cc1cf43
877 user: test
879 user: test
878 date: Thu Jan 01 00:00:00 1970 +0000
880 date: Thu Jan 01 00:00:00 1970 +0000
879 summary: aaa
881 summary: aaa
880
882
881
883
882 Testing from upper head
884 Testing from upper head
883
885
884 $ hg log -r '_destrebase(4)'
886 $ hg log -r '_destrebase(4)'
885 changeset: 3:1910d5ff34ea
887 changeset: 3:1910d5ff34ea
886 user: test
888 user: test
887 date: Thu Jan 01 00:00:00 1970 +0000
889 date: Thu Jan 01 00:00:00 1970 +0000
888 summary: second source with subdir
890 summary: second source with subdir
889
891
890 $ hg up 4
892 $ hg up 4
891 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
893 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
892 $ hg log -r '_destrebase()'
894 $ hg log -r '_destrebase()'
893 changeset: 3:1910d5ff34ea
895 changeset: 3:1910d5ff34ea
894 user: test
896 user: test
895 date: Thu Jan 01 00:00:00 1970 +0000
897 date: Thu Jan 01 00:00:00 1970 +0000
896 summary: second source with subdir
898 summary: second source with subdir
897
899
@@ -1,74 +1,86 b''
1 Test update logic when there are renames or weird same-name cases between dirs
1 Test update logic when there are renames or weird same-name cases between dirs
2 and files
2 and files
3
3
4 Update with local changes across a file rename
4 Update with local changes across a file rename
5
5
6 $ hg init r1 && cd r1
6 $ hg init r1 && cd r1
7
7
8 $ echo a > a
8 $ echo a > a
9 $ hg add a
9 $ hg add a
10 $ hg ci -m a
10 $ hg ci -m a
11
11
12 $ hg mv a b
12 $ hg mv a b
13 $ hg ci -m rename
13 $ hg ci -m rename
14
14
15 $ echo b > b
15 $ echo b > b
16 $ hg ci -m change
16 $ hg ci -m change
17
17
18 $ hg up -q 0
18 $ hg up -q 0
19
19
20 $ echo c > a
20 $ echo c > a
21
21
22 $ hg up
22 $ hg up
23 merging a and b to b
23 merging a and b to b
24 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
24 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
25 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
25 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
26 use 'hg resolve' to retry unresolved file merges
26 use 'hg resolve' to retry unresolved file merges
27 [1]
27 [1]
28
28
29 Test update when local untracked directory exists with the same name as a
29 Test update when local untracked directory exists with the same name as a
30 tracked file in a commit we are updating to
30 tracked file in a commit we are updating to
31 $ hg init r2 && cd r2
31 $ hg init r2 && cd r2
32 $ echo root > root && hg ci -Am root # rev 0
32 $ echo root > root && hg ci -Am root # rev 0
33 adding root
33 adding root
34 $ echo text > name && hg ci -Am "name is a file" # rev 1
34 $ echo text > name && hg ci -Am "name is a file" # rev 1
35 adding name
35 adding name
36 $ hg up 0
36 $ hg up 0
37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
38 $ mkdir name
38 $ mkdir name
39 $ hg up 1
39 $ hg up 1
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41
41
42 Test update when local untracked directory exists with some files in it and has
42 Test update when local untracked directory exists with some files in it and has
43 the same name a tracked file in a commit we are updating to. In future this
43 the same name a tracked file in a commit we are updating to. In future this
44 should be updated to give an friendlier error message, but now we should just
44 should be updated to give an friendlier error message, but now we should just
45 make sure that this does not erase untracked data
45 make sure that this does not erase untracked data
46 $ hg up 0
46 $ hg up 0
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
48 $ mkdir name
48 $ mkdir name
49 $ echo text > name/file
49 $ echo text > name/file
50 $ hg st
50 $ hg st
51 ? name/file
51 ? name/file
52 $ hg up 1
52 $ hg up 1
53 abort: *: '$TESTTMP/r1/r2/name' (glob)
53 abort: *: '$TESTTMP/r1/r2/name' (glob)
54 [255]
54 [255]
55 $ cd ..
55 $ cd ..
56
56
57 #if symlink
57 #if symlink
58
58
59 Test update when two commits have symlinks that point to different folders
59 Test update when two commits have symlinks that point to different folders
60 $ hg init r3 && cd r3
60 $ hg init r3 && cd r3
61 $ echo root > root && hg ci -Am root
61 $ echo root > root && hg ci -Am root
62 adding root
62 adding root
63 $ mkdir folder1 && mkdir folder2
63 $ mkdir folder1 && mkdir folder2
64 $ ln -s folder1 folder
64 $ ln -s folder1 folder
65 $ hg ci -Am "symlink to folder1"
65 $ hg ci -Am "symlink to folder1"
66 adding folder
66 adding folder
67 $ rm folder
67 $ rm folder
68 $ ln -s folder2 folder
68 $ ln -s folder2 folder
69 $ hg ci -Am "symlink to folder2"
69 $ hg ci -Am "symlink to folder2"
70 $ hg up 1
70 $ hg up 1
71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 $ cd ..
72 $ cd ..
73
73
74 #endif
74 #endif
75
76 Test that warning is printed if cwd is deleted during update
77 $ hg init r4 && cd r4
78 $ mkdir dir
79 $ cd dir
80 $ echo a > a
81 $ echo b > b
82 $ hg add a b
83 $ hg ci -m "file and dir"
84 $ hg up -q null
85 current directory was removed
86 (consider changing to repo root: $TESTTMP/r1/r4)
General Comments 0
You need to be logged in to leave comments. Login now