##// END OF EJS Templates
copies: also encode p[12]copies destination as index into "files" list...
Martin von Zweigbergk -
r42619:a1f87294 default
parent child Browse files
Show More
@@ -1,664 +1,668
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 bin,
12 bin,
13 hex,
13 hex,
14 nullid,
14 nullid,
15 )
15 )
16 from .thirdparty import (
16 from .thirdparty import (
17 attr,
17 attr,
18 )
18 )
19
19
20 from . import (
20 from . import (
21 encoding,
21 encoding,
22 error,
22 error,
23 pycompat,
23 pycompat,
24 revlog,
24 revlog,
25 util,
25 util,
26 )
26 )
27 from .utils import (
27 from .utils import (
28 dateutil,
28 dateutil,
29 stringutil,
29 stringutil,
30 )
30 )
31
31
32 _defaultextra = {'branch': 'default'}
32 _defaultextra = {'branch': 'default'}
33
33
34 def _string_escape(text):
34 def _string_escape(text):
35 """
35 """
36 >>> from .pycompat import bytechr as chr
36 >>> from .pycompat import bytechr as chr
37 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
37 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
38 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
38 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
39 >>> s
39 >>> s
40 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
40 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
41 >>> res = _string_escape(s)
41 >>> res = _string_escape(s)
42 >>> s == _string_unescape(res)
42 >>> s == _string_unescape(res)
43 True
43 True
44 """
44 """
45 # subset of the string_escape codec
45 # subset of the string_escape codec
46 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
46 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
47 return text.replace('\0', '\\0')
47 return text.replace('\0', '\\0')
48
48
49 def _string_unescape(text):
49 def _string_unescape(text):
50 if '\\0' in text:
50 if '\\0' in text:
51 # fix up \0 without getting into trouble with \\0
51 # fix up \0 without getting into trouble with \\0
52 text = text.replace('\\\\', '\\\\\n')
52 text = text.replace('\\\\', '\\\\\n')
53 text = text.replace('\\0', '\0')
53 text = text.replace('\\0', '\0')
54 text = text.replace('\n', '')
54 text = text.replace('\n', '')
55 return stringutil.unescapestr(text)
55 return stringutil.unescapestr(text)
56
56
57 def decodeextra(text):
57 def decodeextra(text):
58 """
58 """
59 >>> from .pycompat import bytechr as chr
59 >>> from .pycompat import bytechr as chr
60 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
60 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
61 ... ).items())
61 ... ).items())
62 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
62 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
63 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
63 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
64 ... b'baz': chr(92) + chr(0) + b'2'})
64 ... b'baz': chr(92) + chr(0) + b'2'})
65 ... ).items())
65 ... ).items())
66 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
66 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
67 """
67 """
68 extra = _defaultextra.copy()
68 extra = _defaultextra.copy()
69 for l in text.split('\0'):
69 for l in text.split('\0'):
70 if l:
70 if l:
71 k, v = _string_unescape(l).split(':', 1)
71 k, v = _string_unescape(l).split(':', 1)
72 extra[k] = v
72 extra[k] = v
73 return extra
73 return extra
74
74
75 def encodeextra(d):
75 def encodeextra(d):
76 # keys must be sorted to produce a deterministic changelog entry
76 # keys must be sorted to produce a deterministic changelog entry
77 items = [
77 items = [
78 _string_escape('%s:%s' % (k, pycompat.bytestr(d[k])))
78 _string_escape('%s:%s' % (k, pycompat.bytestr(d[k])))
79 for k in sorted(d)
79 for k in sorted(d)
80 ]
80 ]
81 return "\0".join(items)
81 return "\0".join(items)
82
82
83 def encodecopies(copies):
83 def encodecopies(files, copies):
84 items = [
84 items = []
85 '%s\0%s' % (k, copies[k])
85 for i, dst in enumerate(files):
86 for k in sorted(copies)
86 if dst in copies:
87 ]
87 items.append('%d\0%s' % (i, copies[dst]))
88 if len(items) != len(copies):
89 raise error.ProgrammingError('some copy targets missing from file list')
88 return "\n".join(items)
90 return "\n".join(items)
89
91
90 def decodecopies(data):
92 def decodecopies(files, data):
91 try:
93 try:
92 copies = {}
94 copies = {}
93 for l in data.split('\n'):
95 for l in data.split('\n'):
94 k, v = l.split('\0')
96 strindex, src = l.split('\0')
95 copies[k] = v
97 i = int(strindex)
98 dst = files[i]
99 copies[dst] = src
96 return copies
100 return copies
97 except ValueError:
101 except (ValueError, IndexError):
98 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
102 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
99 # used different syntax for the value.
103 # used different syntax for the value.
100 return None
104 return None
101
105
102 def encodefileindices(files, subset):
106 def encodefileindices(files, subset):
103 subset = set(subset)
107 subset = set(subset)
104 indices = []
108 indices = []
105 for i, f in enumerate(files):
109 for i, f in enumerate(files):
106 if f in subset:
110 if f in subset:
107 indices.append('%d' % i)
111 indices.append('%d' % i)
108 return '\0'.join(indices)
112 return '\0'.join(indices)
109
113
110 def decodefileindices(files, data):
114 def decodefileindices(files, data):
111 try:
115 try:
112 subset = []
116 subset = []
113 for strindex in data.split('\0'):
117 for strindex in data.split('\0'):
114 i = int(strindex)
118 i = int(strindex)
115 if i < 0 or i >= len(files):
119 if i < 0 or i >= len(files):
116 return None
120 return None
117 subset.append(files[i])
121 subset.append(files[i])
118 return subset
122 return subset
119 except (ValueError, IndexError):
123 except (ValueError, IndexError):
120 # Perhaps someone had chosen the same key name (e.g. "added") and
124 # Perhaps someone had chosen the same key name (e.g. "added") and
121 # used different syntax for the value.
125 # used different syntax for the value.
122 return None
126 return None
123
127
124 def stripdesc(desc):
128 def stripdesc(desc):
125 """strip trailing whitespace and leading and trailing empty lines"""
129 """strip trailing whitespace and leading and trailing empty lines"""
126 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
130 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
127
131
128 class appender(object):
132 class appender(object):
129 '''the changelog index must be updated last on disk, so we use this class
133 '''the changelog index must be updated last on disk, so we use this class
130 to delay writes to it'''
134 to delay writes to it'''
131 def __init__(self, vfs, name, mode, buf):
135 def __init__(self, vfs, name, mode, buf):
132 self.data = buf
136 self.data = buf
133 fp = vfs(name, mode)
137 fp = vfs(name, mode)
134 self.fp = fp
138 self.fp = fp
135 self.offset = fp.tell()
139 self.offset = fp.tell()
136 self.size = vfs.fstat(fp).st_size
140 self.size = vfs.fstat(fp).st_size
137 self._end = self.size
141 self._end = self.size
138
142
139 def end(self):
143 def end(self):
140 return self._end
144 return self._end
141 def tell(self):
145 def tell(self):
142 return self.offset
146 return self.offset
143 def flush(self):
147 def flush(self):
144 pass
148 pass
145
149
146 @property
150 @property
147 def closed(self):
151 def closed(self):
148 return self.fp.closed
152 return self.fp.closed
149
153
150 def close(self):
154 def close(self):
151 self.fp.close()
155 self.fp.close()
152
156
153 def seek(self, offset, whence=0):
157 def seek(self, offset, whence=0):
154 '''virtual file offset spans real file and data'''
158 '''virtual file offset spans real file and data'''
155 if whence == 0:
159 if whence == 0:
156 self.offset = offset
160 self.offset = offset
157 elif whence == 1:
161 elif whence == 1:
158 self.offset += offset
162 self.offset += offset
159 elif whence == 2:
163 elif whence == 2:
160 self.offset = self.end() + offset
164 self.offset = self.end() + offset
161 if self.offset < self.size:
165 if self.offset < self.size:
162 self.fp.seek(self.offset)
166 self.fp.seek(self.offset)
163
167
164 def read(self, count=-1):
168 def read(self, count=-1):
165 '''only trick here is reads that span real file and data'''
169 '''only trick here is reads that span real file and data'''
166 ret = ""
170 ret = ""
167 if self.offset < self.size:
171 if self.offset < self.size:
168 s = self.fp.read(count)
172 s = self.fp.read(count)
169 ret = s
173 ret = s
170 self.offset += len(s)
174 self.offset += len(s)
171 if count > 0:
175 if count > 0:
172 count -= len(s)
176 count -= len(s)
173 if count != 0:
177 if count != 0:
174 doff = self.offset - self.size
178 doff = self.offset - self.size
175 self.data.insert(0, "".join(self.data))
179 self.data.insert(0, "".join(self.data))
176 del self.data[1:]
180 del self.data[1:]
177 s = self.data[0][doff:doff + count]
181 s = self.data[0][doff:doff + count]
178 self.offset += len(s)
182 self.offset += len(s)
179 ret += s
183 ret += s
180 return ret
184 return ret
181
185
182 def write(self, s):
186 def write(self, s):
183 self.data.append(bytes(s))
187 self.data.append(bytes(s))
184 self.offset += len(s)
188 self.offset += len(s)
185 self._end += len(s)
189 self._end += len(s)
186
190
187 def __enter__(self):
191 def __enter__(self):
188 self.fp.__enter__()
192 self.fp.__enter__()
189 return self
193 return self
190
194
191 def __exit__(self, *args):
195 def __exit__(self, *args):
192 return self.fp.__exit__(*args)
196 return self.fp.__exit__(*args)
193
197
194 def _divertopener(opener, target):
198 def _divertopener(opener, target):
195 """build an opener that writes in 'target.a' instead of 'target'"""
199 """build an opener that writes in 'target.a' instead of 'target'"""
196 def _divert(name, mode='r', checkambig=False):
200 def _divert(name, mode='r', checkambig=False):
197 if name != target:
201 if name != target:
198 return opener(name, mode)
202 return opener(name, mode)
199 return opener(name + ".a", mode)
203 return opener(name + ".a", mode)
200 return _divert
204 return _divert
201
205
202 def _delayopener(opener, target, buf):
206 def _delayopener(opener, target, buf):
203 """build an opener that stores chunks in 'buf' instead of 'target'"""
207 """build an opener that stores chunks in 'buf' instead of 'target'"""
204 def _delay(name, mode='r', checkambig=False):
208 def _delay(name, mode='r', checkambig=False):
205 if name != target:
209 if name != target:
206 return opener(name, mode)
210 return opener(name, mode)
207 return appender(opener, name, mode, buf)
211 return appender(opener, name, mode, buf)
208 return _delay
212 return _delay
209
213
210 @attr.s
214 @attr.s
211 class _changelogrevision(object):
215 class _changelogrevision(object):
212 # Extensions might modify _defaultextra, so let the constructor below pass
216 # Extensions might modify _defaultextra, so let the constructor below pass
213 # it in
217 # it in
214 extra = attr.ib()
218 extra = attr.ib()
215 manifest = attr.ib(default=nullid)
219 manifest = attr.ib(default=nullid)
216 user = attr.ib(default='')
220 user = attr.ib(default='')
217 date = attr.ib(default=(0, 0))
221 date = attr.ib(default=(0, 0))
218 files = attr.ib(default=attr.Factory(list))
222 files = attr.ib(default=attr.Factory(list))
219 filesadded = attr.ib(default=None)
223 filesadded = attr.ib(default=None)
220 filesremoved = attr.ib(default=None)
224 filesremoved = attr.ib(default=None)
221 p1copies = attr.ib(default=None)
225 p1copies = attr.ib(default=None)
222 p2copies = attr.ib(default=None)
226 p2copies = attr.ib(default=None)
223 description = attr.ib(default='')
227 description = attr.ib(default='')
224
228
225 class changelogrevision(object):
229 class changelogrevision(object):
226 """Holds results of a parsed changelog revision.
230 """Holds results of a parsed changelog revision.
227
231
228 Changelog revisions consist of multiple pieces of data, including
232 Changelog revisions consist of multiple pieces of data, including
229 the manifest node, user, and date. This object exposes a view into
233 the manifest node, user, and date. This object exposes a view into
230 the parsed object.
234 the parsed object.
231 """
235 """
232
236
233 __slots__ = (
237 __slots__ = (
234 r'_offsets',
238 r'_offsets',
235 r'_text',
239 r'_text',
236 )
240 )
237
241
238 def __new__(cls, text):
242 def __new__(cls, text):
239 if not text:
243 if not text:
240 return _changelogrevision(extra=_defaultextra)
244 return _changelogrevision(extra=_defaultextra)
241
245
242 self = super(changelogrevision, cls).__new__(cls)
246 self = super(changelogrevision, cls).__new__(cls)
243 # We could return here and implement the following as an __init__.
247 # We could return here and implement the following as an __init__.
244 # But doing it here is equivalent and saves an extra function call.
248 # But doing it here is equivalent and saves an extra function call.
245
249
246 # format used:
250 # format used:
247 # nodeid\n : manifest node in ascii
251 # nodeid\n : manifest node in ascii
248 # user\n : user, no \n or \r allowed
252 # user\n : user, no \n or \r allowed
249 # time tz extra\n : date (time is int or float, timezone is int)
253 # time tz extra\n : date (time is int or float, timezone is int)
250 # : extra is metadata, encoded and separated by '\0'
254 # : extra is metadata, encoded and separated by '\0'
251 # : older versions ignore it
255 # : older versions ignore it
252 # files\n\n : files modified by the cset, no \n or \r allowed
256 # files\n\n : files modified by the cset, no \n or \r allowed
253 # (.*) : comment (free text, ideally utf-8)
257 # (.*) : comment (free text, ideally utf-8)
254 #
258 #
255 # changelog v0 doesn't use extra
259 # changelog v0 doesn't use extra
256
260
257 nl1 = text.index('\n')
261 nl1 = text.index('\n')
258 nl2 = text.index('\n', nl1 + 1)
262 nl2 = text.index('\n', nl1 + 1)
259 nl3 = text.index('\n', nl2 + 1)
263 nl3 = text.index('\n', nl2 + 1)
260
264
261 # The list of files may be empty. Which means nl3 is the first of the
265 # The list of files may be empty. Which means nl3 is the first of the
262 # double newline that precedes the description.
266 # double newline that precedes the description.
263 if text[nl3 + 1:nl3 + 2] == '\n':
267 if text[nl3 + 1:nl3 + 2] == '\n':
264 doublenl = nl3
268 doublenl = nl3
265 else:
269 else:
266 doublenl = text.index('\n\n', nl3 + 1)
270 doublenl = text.index('\n\n', nl3 + 1)
267
271
268 self._offsets = (nl1, nl2, nl3, doublenl)
272 self._offsets = (nl1, nl2, nl3, doublenl)
269 self._text = text
273 self._text = text
270
274
271 return self
275 return self
272
276
273 @property
277 @property
274 def manifest(self):
278 def manifest(self):
275 return bin(self._text[0:self._offsets[0]])
279 return bin(self._text[0:self._offsets[0]])
276
280
277 @property
281 @property
278 def user(self):
282 def user(self):
279 off = self._offsets
283 off = self._offsets
280 return encoding.tolocal(self._text[off[0] + 1:off[1]])
284 return encoding.tolocal(self._text[off[0] + 1:off[1]])
281
285
282 @property
286 @property
283 def _rawdate(self):
287 def _rawdate(self):
284 off = self._offsets
288 off = self._offsets
285 dateextra = self._text[off[1] + 1:off[2]]
289 dateextra = self._text[off[1] + 1:off[2]]
286 return dateextra.split(' ', 2)[0:2]
290 return dateextra.split(' ', 2)[0:2]
287
291
288 @property
292 @property
289 def _rawextra(self):
293 def _rawextra(self):
290 off = self._offsets
294 off = self._offsets
291 dateextra = self._text[off[1] + 1:off[2]]
295 dateextra = self._text[off[1] + 1:off[2]]
292 fields = dateextra.split(' ', 2)
296 fields = dateextra.split(' ', 2)
293 if len(fields) != 3:
297 if len(fields) != 3:
294 return None
298 return None
295
299
296 return fields[2]
300 return fields[2]
297
301
298 @property
302 @property
299 def date(self):
303 def date(self):
300 raw = self._rawdate
304 raw = self._rawdate
301 time = float(raw[0])
305 time = float(raw[0])
302 # Various tools did silly things with the timezone.
306 # Various tools did silly things with the timezone.
303 try:
307 try:
304 timezone = int(raw[1])
308 timezone = int(raw[1])
305 except ValueError:
309 except ValueError:
306 timezone = 0
310 timezone = 0
307
311
308 return time, timezone
312 return time, timezone
309
313
310 @property
314 @property
311 def extra(self):
315 def extra(self):
312 raw = self._rawextra
316 raw = self._rawextra
313 if raw is None:
317 if raw is None:
314 return _defaultextra
318 return _defaultextra
315
319
316 return decodeextra(raw)
320 return decodeextra(raw)
317
321
318 @property
322 @property
319 def files(self):
323 def files(self):
320 off = self._offsets
324 off = self._offsets
321 if off[2] == off[3]:
325 if off[2] == off[3]:
322 return []
326 return []
323
327
324 return self._text[off[2] + 1:off[3]].split('\n')
328 return self._text[off[2] + 1:off[3]].split('\n')
325
329
326 @property
330 @property
327 def filesadded(self):
331 def filesadded(self):
328 rawindices = self.extra.get('filesadded')
332 rawindices = self.extra.get('filesadded')
329 return rawindices and decodefileindices(self.files, rawindices)
333 return rawindices and decodefileindices(self.files, rawindices)
330
334
331 @property
335 @property
332 def filesremoved(self):
336 def filesremoved(self):
333 rawindices = self.extra.get('filesremoved')
337 rawindices = self.extra.get('filesremoved')
334 return rawindices and decodefileindices(self.files, rawindices)
338 return rawindices and decodefileindices(self.files, rawindices)
335
339
336 @property
340 @property
337 def p1copies(self):
341 def p1copies(self):
338 rawcopies = self.extra.get('p1copies')
342 rawcopies = self.extra.get('p1copies')
339 return rawcopies and decodecopies(rawcopies)
343 return rawcopies and decodecopies(self.files, rawcopies)
340
344
341 @property
345 @property
342 def p2copies(self):
346 def p2copies(self):
343 rawcopies = self.extra.get('p2copies')
347 rawcopies = self.extra.get('p2copies')
344 return rawcopies and decodecopies(rawcopies)
348 return rawcopies and decodecopies(self.files, rawcopies)
345
349
346 @property
350 @property
347 def description(self):
351 def description(self):
348 return encoding.tolocal(self._text[self._offsets[3] + 2:])
352 return encoding.tolocal(self._text[self._offsets[3] + 2:])
349
353
350 class changelog(revlog.revlog):
354 class changelog(revlog.revlog):
351 def __init__(self, opener, trypending=False):
355 def __init__(self, opener, trypending=False):
352 """Load a changelog revlog using an opener.
356 """Load a changelog revlog using an opener.
353
357
354 If ``trypending`` is true, we attempt to load the index from a
358 If ``trypending`` is true, we attempt to load the index from a
355 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
359 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
356 The ``00changelog.i.a`` file contains index (and possibly inline
360 The ``00changelog.i.a`` file contains index (and possibly inline
357 revision) data for a transaction that hasn't been finalized yet.
361 revision) data for a transaction that hasn't been finalized yet.
358 It exists in a separate file to facilitate readers (such as
362 It exists in a separate file to facilitate readers (such as
359 hooks processes) accessing data before a transaction is finalized.
363 hooks processes) accessing data before a transaction is finalized.
360 """
364 """
361 if trypending and opener.exists('00changelog.i.a'):
365 if trypending and opener.exists('00changelog.i.a'):
362 indexfile = '00changelog.i.a'
366 indexfile = '00changelog.i.a'
363 else:
367 else:
364 indexfile = '00changelog.i'
368 indexfile = '00changelog.i'
365
369
366 datafile = '00changelog.d'
370 datafile = '00changelog.d'
367 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
371 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
368 checkambig=True, mmaplargeindex=True)
372 checkambig=True, mmaplargeindex=True)
369
373
370 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
374 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
371 # changelogs don't benefit from generaldelta.
375 # changelogs don't benefit from generaldelta.
372
376
373 self.version &= ~revlog.FLAG_GENERALDELTA
377 self.version &= ~revlog.FLAG_GENERALDELTA
374 self._generaldelta = False
378 self._generaldelta = False
375
379
376 # Delta chains for changelogs tend to be very small because entries
380 # Delta chains for changelogs tend to be very small because entries
377 # tend to be small and don't delta well with each. So disable delta
381 # tend to be small and don't delta well with each. So disable delta
378 # chains.
382 # chains.
379 self._storedeltachains = False
383 self._storedeltachains = False
380
384
381 self._realopener = opener
385 self._realopener = opener
382 self._delayed = False
386 self._delayed = False
383 self._delaybuf = None
387 self._delaybuf = None
384 self._divert = False
388 self._divert = False
385 self.filteredrevs = frozenset()
389 self.filteredrevs = frozenset()
386
390
387 def tiprev(self):
391 def tiprev(self):
388 for i in pycompat.xrange(len(self) -1, -2, -1):
392 for i in pycompat.xrange(len(self) -1, -2, -1):
389 if i not in self.filteredrevs:
393 if i not in self.filteredrevs:
390 return i
394 return i
391
395
392 def tip(self):
396 def tip(self):
393 """filtered version of revlog.tip"""
397 """filtered version of revlog.tip"""
394 return self.node(self.tiprev())
398 return self.node(self.tiprev())
395
399
396 def __contains__(self, rev):
400 def __contains__(self, rev):
397 """filtered version of revlog.__contains__"""
401 """filtered version of revlog.__contains__"""
398 return (0 <= rev < len(self)
402 return (0 <= rev < len(self)
399 and rev not in self.filteredrevs)
403 and rev not in self.filteredrevs)
400
404
401 def __iter__(self):
405 def __iter__(self):
402 """filtered version of revlog.__iter__"""
406 """filtered version of revlog.__iter__"""
403 if len(self.filteredrevs) == 0:
407 if len(self.filteredrevs) == 0:
404 return revlog.revlog.__iter__(self)
408 return revlog.revlog.__iter__(self)
405
409
406 def filterediter():
410 def filterediter():
407 for i in pycompat.xrange(len(self)):
411 for i in pycompat.xrange(len(self)):
408 if i not in self.filteredrevs:
412 if i not in self.filteredrevs:
409 yield i
413 yield i
410
414
411 return filterediter()
415 return filterediter()
412
416
413 def revs(self, start=0, stop=None):
417 def revs(self, start=0, stop=None):
414 """filtered version of revlog.revs"""
418 """filtered version of revlog.revs"""
415 for i in super(changelog, self).revs(start, stop):
419 for i in super(changelog, self).revs(start, stop):
416 if i not in self.filteredrevs:
420 if i not in self.filteredrevs:
417 yield i
421 yield i
418
422
419 def reachableroots(self, minroot, heads, roots, includepath=False):
423 def reachableroots(self, minroot, heads, roots, includepath=False):
420 return self.index.reachableroots2(minroot, heads, roots, includepath)
424 return self.index.reachableroots2(minroot, heads, roots, includepath)
421
425
422 def _checknofilteredinrevs(self, revs):
426 def _checknofilteredinrevs(self, revs):
423 """raise the appropriate error if 'revs' contains a filtered revision
427 """raise the appropriate error if 'revs' contains a filtered revision
424
428
425 This returns a version of 'revs' to be used thereafter by the caller.
429 This returns a version of 'revs' to be used thereafter by the caller.
426 In particular, if revs is an iterator, it is converted into a set.
430 In particular, if revs is an iterator, it is converted into a set.
427 """
431 """
428 safehasattr = util.safehasattr
432 safehasattr = util.safehasattr
429 if safehasattr(revs, '__next__'):
433 if safehasattr(revs, '__next__'):
430 # Note that inspect.isgenerator() is not true for iterators,
434 # Note that inspect.isgenerator() is not true for iterators,
431 revs = set(revs)
435 revs = set(revs)
432
436
433 filteredrevs = self.filteredrevs
437 filteredrevs = self.filteredrevs
434 if safehasattr(revs, 'first'): # smartset
438 if safehasattr(revs, 'first'): # smartset
435 offenders = revs & filteredrevs
439 offenders = revs & filteredrevs
436 else:
440 else:
437 offenders = filteredrevs.intersection(revs)
441 offenders = filteredrevs.intersection(revs)
438
442
439 for rev in offenders:
443 for rev in offenders:
440 raise error.FilteredIndexError(rev)
444 raise error.FilteredIndexError(rev)
441 return revs
445 return revs
442
446
443 def headrevs(self, revs=None):
447 def headrevs(self, revs=None):
444 if revs is None and self.filteredrevs:
448 if revs is None and self.filteredrevs:
445 try:
449 try:
446 return self.index.headrevsfiltered(self.filteredrevs)
450 return self.index.headrevsfiltered(self.filteredrevs)
447 # AttributeError covers non-c-extension environments and
451 # AttributeError covers non-c-extension environments and
448 # old c extensions without filter handling.
452 # old c extensions without filter handling.
449 except AttributeError:
453 except AttributeError:
450 return self._headrevs()
454 return self._headrevs()
451
455
452 if self.filteredrevs:
456 if self.filteredrevs:
453 revs = self._checknofilteredinrevs(revs)
457 revs = self._checknofilteredinrevs(revs)
454 return super(changelog, self).headrevs(revs)
458 return super(changelog, self).headrevs(revs)
455
459
456 def strip(self, *args, **kwargs):
460 def strip(self, *args, **kwargs):
457 # XXX make something better than assert
461 # XXX make something better than assert
458 # We can't expect proper strip behavior if we are filtered.
462 # We can't expect proper strip behavior if we are filtered.
459 assert not self.filteredrevs
463 assert not self.filteredrevs
460 super(changelog, self).strip(*args, **kwargs)
464 super(changelog, self).strip(*args, **kwargs)
461
465
462 def rev(self, node):
466 def rev(self, node):
463 """filtered version of revlog.rev"""
467 """filtered version of revlog.rev"""
464 r = super(changelog, self).rev(node)
468 r = super(changelog, self).rev(node)
465 if r in self.filteredrevs:
469 if r in self.filteredrevs:
466 raise error.FilteredLookupError(hex(node), self.indexfile,
470 raise error.FilteredLookupError(hex(node), self.indexfile,
467 _('filtered node'))
471 _('filtered node'))
468 return r
472 return r
469
473
470 def node(self, rev):
474 def node(self, rev):
471 """filtered version of revlog.node"""
475 """filtered version of revlog.node"""
472 if rev in self.filteredrevs:
476 if rev in self.filteredrevs:
473 raise error.FilteredIndexError(rev)
477 raise error.FilteredIndexError(rev)
474 return super(changelog, self).node(rev)
478 return super(changelog, self).node(rev)
475
479
476 def linkrev(self, rev):
480 def linkrev(self, rev):
477 """filtered version of revlog.linkrev"""
481 """filtered version of revlog.linkrev"""
478 if rev in self.filteredrevs:
482 if rev in self.filteredrevs:
479 raise error.FilteredIndexError(rev)
483 raise error.FilteredIndexError(rev)
480 return super(changelog, self).linkrev(rev)
484 return super(changelog, self).linkrev(rev)
481
485
482 def parentrevs(self, rev):
486 def parentrevs(self, rev):
483 """filtered version of revlog.parentrevs"""
487 """filtered version of revlog.parentrevs"""
484 if rev in self.filteredrevs:
488 if rev in self.filteredrevs:
485 raise error.FilteredIndexError(rev)
489 raise error.FilteredIndexError(rev)
486 return super(changelog, self).parentrevs(rev)
490 return super(changelog, self).parentrevs(rev)
487
491
488 def flags(self, rev):
492 def flags(self, rev):
489 """filtered version of revlog.flags"""
493 """filtered version of revlog.flags"""
490 if rev in self.filteredrevs:
494 if rev in self.filteredrevs:
491 raise error.FilteredIndexError(rev)
495 raise error.FilteredIndexError(rev)
492 return super(changelog, self).flags(rev)
496 return super(changelog, self).flags(rev)
493
497
494 def delayupdate(self, tr):
498 def delayupdate(self, tr):
495 "delay visibility of index updates to other readers"
499 "delay visibility of index updates to other readers"
496
500
497 if not self._delayed:
501 if not self._delayed:
498 if len(self) == 0:
502 if len(self) == 0:
499 self._divert = True
503 self._divert = True
500 if self._realopener.exists(self.indexfile + '.a'):
504 if self._realopener.exists(self.indexfile + '.a'):
501 self._realopener.unlink(self.indexfile + '.a')
505 self._realopener.unlink(self.indexfile + '.a')
502 self.opener = _divertopener(self._realopener, self.indexfile)
506 self.opener = _divertopener(self._realopener, self.indexfile)
503 else:
507 else:
504 self._delaybuf = []
508 self._delaybuf = []
505 self.opener = _delayopener(self._realopener, self.indexfile,
509 self.opener = _delayopener(self._realopener, self.indexfile,
506 self._delaybuf)
510 self._delaybuf)
507 self._delayed = True
511 self._delayed = True
508 tr.addpending('cl-%i' % id(self), self._writepending)
512 tr.addpending('cl-%i' % id(self), self._writepending)
509 tr.addfinalize('cl-%i' % id(self), self._finalize)
513 tr.addfinalize('cl-%i' % id(self), self._finalize)
510
514
511 def _finalize(self, tr):
515 def _finalize(self, tr):
512 "finalize index updates"
516 "finalize index updates"
513 self._delayed = False
517 self._delayed = False
514 self.opener = self._realopener
518 self.opener = self._realopener
515 # move redirected index data back into place
519 # move redirected index data back into place
516 if self._divert:
520 if self._divert:
517 assert not self._delaybuf
521 assert not self._delaybuf
518 tmpname = self.indexfile + ".a"
522 tmpname = self.indexfile + ".a"
519 nfile = self.opener.open(tmpname)
523 nfile = self.opener.open(tmpname)
520 nfile.close()
524 nfile.close()
521 self.opener.rename(tmpname, self.indexfile, checkambig=True)
525 self.opener.rename(tmpname, self.indexfile, checkambig=True)
522 elif self._delaybuf:
526 elif self._delaybuf:
523 fp = self.opener(self.indexfile, 'a', checkambig=True)
527 fp = self.opener(self.indexfile, 'a', checkambig=True)
524 fp.write("".join(self._delaybuf))
528 fp.write("".join(self._delaybuf))
525 fp.close()
529 fp.close()
526 self._delaybuf = None
530 self._delaybuf = None
527 self._divert = False
531 self._divert = False
528 # split when we're done
532 # split when we're done
529 self._enforceinlinesize(tr)
533 self._enforceinlinesize(tr)
530
534
531 def _writepending(self, tr):
535 def _writepending(self, tr):
532 "create a file containing the unfinalized state for pretxnchangegroup"
536 "create a file containing the unfinalized state for pretxnchangegroup"
533 if self._delaybuf:
537 if self._delaybuf:
534 # make a temporary copy of the index
538 # make a temporary copy of the index
535 fp1 = self._realopener(self.indexfile)
539 fp1 = self._realopener(self.indexfile)
536 pendingfilename = self.indexfile + ".a"
540 pendingfilename = self.indexfile + ".a"
537 # register as a temp file to ensure cleanup on failure
541 # register as a temp file to ensure cleanup on failure
538 tr.registertmp(pendingfilename)
542 tr.registertmp(pendingfilename)
539 # write existing data
543 # write existing data
540 fp2 = self._realopener(pendingfilename, "w")
544 fp2 = self._realopener(pendingfilename, "w")
541 fp2.write(fp1.read())
545 fp2.write(fp1.read())
542 # add pending data
546 # add pending data
543 fp2.write("".join(self._delaybuf))
547 fp2.write("".join(self._delaybuf))
544 fp2.close()
548 fp2.close()
545 # switch modes so finalize can simply rename
549 # switch modes so finalize can simply rename
546 self._delaybuf = None
550 self._delaybuf = None
547 self._divert = True
551 self._divert = True
548 self.opener = _divertopener(self._realopener, self.indexfile)
552 self.opener = _divertopener(self._realopener, self.indexfile)
549
553
550 if self._divert:
554 if self._divert:
551 return True
555 return True
552
556
553 return False
557 return False
554
558
555 def _enforceinlinesize(self, tr, fp=None):
559 def _enforceinlinesize(self, tr, fp=None):
556 if not self._delayed:
560 if not self._delayed:
557 revlog.revlog._enforceinlinesize(self, tr, fp)
561 revlog.revlog._enforceinlinesize(self, tr, fp)
558
562
559 def read(self, node):
563 def read(self, node):
560 """Obtain data from a parsed changelog revision.
564 """Obtain data from a parsed changelog revision.
561
565
562 Returns a 6-tuple of:
566 Returns a 6-tuple of:
563
567
564 - manifest node in binary
568 - manifest node in binary
565 - author/user as a localstr
569 - author/user as a localstr
566 - date as a 2-tuple of (time, timezone)
570 - date as a 2-tuple of (time, timezone)
567 - list of files
571 - list of files
568 - commit message as a localstr
572 - commit message as a localstr
569 - dict of extra metadata
573 - dict of extra metadata
570
574
571 Unless you need to access all fields, consider calling
575 Unless you need to access all fields, consider calling
572 ``changelogrevision`` instead, as it is faster for partial object
576 ``changelogrevision`` instead, as it is faster for partial object
573 access.
577 access.
574 """
578 """
575 c = changelogrevision(self.revision(node))
579 c = changelogrevision(self.revision(node))
576 return (
580 return (
577 c.manifest,
581 c.manifest,
578 c.user,
582 c.user,
579 c.date,
583 c.date,
580 c.files,
584 c.files,
581 c.description,
585 c.description,
582 c.extra
586 c.extra
583 )
587 )
584
588
585 def changelogrevision(self, nodeorrev):
589 def changelogrevision(self, nodeorrev):
586 """Obtain a ``changelogrevision`` for a node or revision."""
590 """Obtain a ``changelogrevision`` for a node or revision."""
587 return changelogrevision(self.revision(nodeorrev))
591 return changelogrevision(self.revision(nodeorrev))
588
592
589 def readfiles(self, node):
593 def readfiles(self, node):
590 """
594 """
591 short version of read that only returns the files modified by the cset
595 short version of read that only returns the files modified by the cset
592 """
596 """
593 text = self.revision(node)
597 text = self.revision(node)
594 if not text:
598 if not text:
595 return []
599 return []
596 last = text.index("\n\n")
600 last = text.index("\n\n")
597 l = text[:last].split('\n')
601 l = text[:last].split('\n')
598 return l[3:]
602 return l[3:]
599
603
600 def add(self, manifest, files, desc, transaction, p1, p2,
604 def add(self, manifest, files, desc, transaction, p1, p2,
601 user, date=None, extra=None, p1copies=None, p2copies=None,
605 user, date=None, extra=None, p1copies=None, p2copies=None,
602 filesadded=None, filesremoved=None):
606 filesadded=None, filesremoved=None):
603 # Convert to UTF-8 encoded bytestrings as the very first
607 # Convert to UTF-8 encoded bytestrings as the very first
604 # thing: calling any method on a localstr object will turn it
608 # thing: calling any method on a localstr object will turn it
605 # into a str object and the cached UTF-8 string is thus lost.
609 # into a str object and the cached UTF-8 string is thus lost.
606 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
610 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
607
611
608 user = user.strip()
612 user = user.strip()
609 # An empty username or a username with a "\n" will make the
613 # An empty username or a username with a "\n" will make the
610 # revision text contain two "\n\n" sequences -> corrupt
614 # revision text contain two "\n\n" sequences -> corrupt
611 # repository since read cannot unpack the revision.
615 # repository since read cannot unpack the revision.
612 if not user:
616 if not user:
613 raise error.StorageError(_("empty username"))
617 raise error.StorageError(_("empty username"))
614 if "\n" in user:
618 if "\n" in user:
615 raise error.StorageError(_("username %r contains a newline")
619 raise error.StorageError(_("username %r contains a newline")
616 % pycompat.bytestr(user))
620 % pycompat.bytestr(user))
617
621
618 desc = stripdesc(desc)
622 desc = stripdesc(desc)
619
623
620 if date:
624 if date:
621 parseddate = "%d %d" % dateutil.parsedate(date)
625 parseddate = "%d %d" % dateutil.parsedate(date)
622 else:
626 else:
623 parseddate = "%d %d" % dateutil.makedate()
627 parseddate = "%d %d" % dateutil.makedate()
624 if extra:
628 if extra:
625 branch = extra.get("branch")
629 branch = extra.get("branch")
626 if branch in ("default", ""):
630 if branch in ("default", ""):
627 del extra["branch"]
631 del extra["branch"]
628 elif branch in (".", "null", "tip"):
632 elif branch in (".", "null", "tip"):
629 raise error.StorageError(_('the name \'%s\' is reserved')
633 raise error.StorageError(_('the name \'%s\' is reserved')
630 % branch)
634 % branch)
631 extrasentries = p1copies, p2copies, filesadded, filesremoved
635 extrasentries = p1copies, p2copies, filesadded, filesremoved
632 if extra is None and any(x is not None for x in extrasentries):
636 if extra is None and any(x is not None for x in extrasentries):
633 extra = {}
637 extra = {}
638 sortedfiles = sorted(files)
634 if p1copies is not None:
639 if p1copies is not None:
635 extra['p1copies'] = encodecopies(p1copies)
640 extra['p1copies'] = encodecopies(sortedfiles, p1copies)
636 if p2copies is not None:
641 if p2copies is not None:
637 extra['p2copies'] = encodecopies(p2copies)
642 extra['p2copies'] = encodecopies(sortedfiles, p2copies)
638 sortedfiles = sorted(files)
639 if filesadded is not None:
643 if filesadded is not None:
640 extra['filesadded'] = encodefileindices(sortedfiles, filesadded)
644 extra['filesadded'] = encodefileindices(sortedfiles, filesadded)
641 if filesremoved is not None:
645 if filesremoved is not None:
642 extra['filesremoved'] = encodefileindices(sortedfiles, filesremoved)
646 extra['filesremoved'] = encodefileindices(sortedfiles, filesremoved)
643
647
644 if extra:
648 if extra:
645 extra = encodeextra(extra)
649 extra = encodeextra(extra)
646 parseddate = "%s %s" % (parseddate, extra)
650 parseddate = "%s %s" % (parseddate, extra)
647 l = [hex(manifest), user, parseddate] + sortedfiles + ["", desc]
651 l = [hex(manifest), user, parseddate] + sortedfiles + ["", desc]
648 text = "\n".join(l)
652 text = "\n".join(l)
649 return self.addrevision(text, transaction, len(self), p1, p2)
653 return self.addrevision(text, transaction, len(self), p1, p2)
650
654
651 def branchinfo(self, rev):
655 def branchinfo(self, rev):
652 """return the branch name and open/close state of a revision
656 """return the branch name and open/close state of a revision
653
657
654 This function exists because creating a changectx object
658 This function exists because creating a changectx object
655 just to access this is costly."""
659 just to access this is costly."""
656 extra = self.read(rev)[5]
660 extra = self.read(rev)[5]
657 return encoding.tolocal(extra.get("branch")), 'close' in extra
661 return encoding.tolocal(extra.get("branch")), 'close' in extra
658
662
659 def _nodeduplicatecallback(self, transaction, node):
663 def _nodeduplicatecallback(self, transaction, node):
660 # keep track of revisions that got "re-added", eg: unbunde of know rev.
664 # keep track of revisions that got "re-added", eg: unbunde of know rev.
661 #
665 #
662 # We track them in a list to preserve their order from the source bundle
666 # We track them in a list to preserve their order from the source bundle
663 duplicates = transaction.changes.setdefault('revduplicates', [])
667 duplicates = transaction.changes.setdefault('revduplicates', [])
664 duplicates.append(self.rev(node))
668 duplicates.append(self.rev(node))
@@ -1,183 +1,183
1
1
2 $ cat >> $HGRCPATH << EOF
2 $ cat >> $HGRCPATH << EOF
3 > [experimental]
3 > [experimental]
4 > copies.write-to=changeset-only
4 > copies.write-to=changeset-only
5 > copies.read-from=changeset-only
5 > copies.read-from=changeset-only
6 > [alias]
6 > [alias]
7 > changesetcopies = log -r . -T 'files: {files}
7 > changesetcopies = log -r . -T 'files: {files}
8 > {extras % "{ifcontains("files", key, "{key}: {value}\n")}"}
8 > {extras % "{ifcontains("files", key, "{key}: {value}\n")}"}
9 > {extras % "{ifcontains("copies", key, "{key}: {value}\n")}"}'
9 > {extras % "{ifcontains("copies", key, "{key}: {value}\n")}"}'
10 > showcopies = log -r . -T '{file_copies % "{source} -> {name}\n"}'
10 > showcopies = log -r . -T '{file_copies % "{source} -> {name}\n"}'
11 > [extensions]
11 > [extensions]
12 > rebase =
12 > rebase =
13 > EOF
13 > EOF
14
14
15 Check that copies are recorded correctly
15 Check that copies are recorded correctly
16
16
17 $ hg init repo
17 $ hg init repo
18 $ cd repo
18 $ cd repo
19 $ echo a > a
19 $ echo a > a
20 $ hg add a
20 $ hg add a
21 $ hg ci -m initial
21 $ hg ci -m initial
22 $ hg cp a b
22 $ hg cp a b
23 $ hg cp a c
23 $ hg cp a c
24 $ hg cp a d
24 $ hg cp a d
25 $ hg ci -m 'copy a to b, c, and d'
25 $ hg ci -m 'copy a to b, c, and d'
26 $ hg changesetcopies
26 $ hg changesetcopies
27 files: b c d
27 files: b c d
28 filesadded: 0\x001\x002 (esc)
28 filesadded: 0\x001\x002 (esc)
29
29
30 p1copies: b\x00a (esc)
30 p1copies: 0\x00a (esc)
31 c\x00a (esc)
31 1\x00a (esc)
32 d\x00a (esc)
32 2\x00a (esc)
33 $ hg showcopies
33 $ hg showcopies
34 a -> b
34 a -> b
35 a -> c
35 a -> c
36 a -> d
36 a -> d
37 $ hg showcopies --config experimental.copies.read-from=compatibility
37 $ hg showcopies --config experimental.copies.read-from=compatibility
38 a -> b
38 a -> b
39 a -> c
39 a -> c
40 a -> d
40 a -> d
41 $ hg showcopies --config experimental.copies.read-from=filelog-only
41 $ hg showcopies --config experimental.copies.read-from=filelog-only
42
42
43 Check that renames are recorded correctly
43 Check that renames are recorded correctly
44
44
45 $ hg mv b b2
45 $ hg mv b b2
46 $ hg ci -m 'rename b to b2'
46 $ hg ci -m 'rename b to b2'
47 $ hg changesetcopies
47 $ hg changesetcopies
48 files: b b2
48 files: b b2
49 filesadded: 1
49 filesadded: 1
50 filesremoved: 0
50 filesremoved: 0
51
51
52 p1copies: b2\x00b (esc)
52 p1copies: 1\x00b (esc)
53 $ hg showcopies
53 $ hg showcopies
54 b -> b2
54 b -> b2
55
55
56 Rename onto existing file. This should get recorded in the changeset files list and in the extras,
56 Rename onto existing file. This should get recorded in the changeset files list and in the extras,
57 even though there is no filelog entry.
57 even though there is no filelog entry.
58
58
59 $ hg cp b2 c --force
59 $ hg cp b2 c --force
60 $ hg st --copies
60 $ hg st --copies
61 M c
61 M c
62 b2
62 b2
63 $ hg debugindex c
63 $ hg debugindex c
64 rev linkrev nodeid p1 p2
64 rev linkrev nodeid p1 p2
65 0 1 b789fdd96dc2 000000000000 000000000000
65 0 1 b789fdd96dc2 000000000000 000000000000
66 $ hg ci -m 'move b onto d'
66 $ hg ci -m 'move b onto d'
67 $ hg changesetcopies
67 $ hg changesetcopies
68 files: c
68 files: c
69
69
70 p1copies: c\x00b2 (esc)
70 p1copies: 0\x00b2 (esc)
71 $ hg showcopies
71 $ hg showcopies
72 b2 -> c
72 b2 -> c
73 $ hg debugindex c
73 $ hg debugindex c
74 rev linkrev nodeid p1 p2
74 rev linkrev nodeid p1 p2
75 0 1 b789fdd96dc2 000000000000 000000000000
75 0 1 b789fdd96dc2 000000000000 000000000000
76
76
77 Create a merge commit with copying done during merge.
77 Create a merge commit with copying done during merge.
78
78
79 $ hg co 0
79 $ hg co 0
80 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
80 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
81 $ hg cp a e
81 $ hg cp a e
82 $ hg cp a f
82 $ hg cp a f
83 $ hg ci -m 'copy a to e and f'
83 $ hg ci -m 'copy a to e and f'
84 created new head
84 created new head
85 $ hg merge 3
85 $ hg merge 3
86 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 (branch merge, don't forget to commit)
87 (branch merge, don't forget to commit)
88 File 'a' exists on both sides, so 'g' could be recorded as being from p1 or p2, but we currently
88 File 'a' exists on both sides, so 'g' could be recorded as being from p1 or p2, but we currently
89 always record it as being from p1
89 always record it as being from p1
90 $ hg cp a g
90 $ hg cp a g
91 File 'd' exists only in p2, so 'h' should be from p2
91 File 'd' exists only in p2, so 'h' should be from p2
92 $ hg cp d h
92 $ hg cp d h
93 File 'f' exists only in p1, so 'i' should be from p1
93 File 'f' exists only in p1, so 'i' should be from p1
94 $ hg cp f i
94 $ hg cp f i
95 $ hg ci -m 'merge'
95 $ hg ci -m 'merge'
96 $ hg changesetcopies
96 $ hg changesetcopies
97 files: g h i
97 files: g h i
98 filesadded: 0\x001\x002 (esc)
98 filesadded: 0\x001\x002 (esc)
99
99
100 p1copies: g\x00a (esc)
100 p1copies: 0\x00a (esc)
101 i\x00f (esc)
101 2\x00f (esc)
102 p2copies: h\x00d (esc)
102 p2copies: 1\x00d (esc)
103 $ hg showcopies
103 $ hg showcopies
104 a -> g
104 a -> g
105 d -> h
105 d -> h
106 f -> i
106 f -> i
107
107
108 Test writing to both changeset and filelog
108 Test writing to both changeset and filelog
109
109
110 $ hg cp a j
110 $ hg cp a j
111 $ hg ci -m 'copy a to j' --config experimental.copies.write-to=compatibility
111 $ hg ci -m 'copy a to j' --config experimental.copies.write-to=compatibility
112 $ hg changesetcopies
112 $ hg changesetcopies
113 files: j
113 files: j
114 filesadded: 0
114 filesadded: 0
115 filesremoved:
115 filesremoved:
116
116
117 p1copies: j\x00a (esc)
117 p1copies: 0\x00a (esc)
118 p2copies:
118 p2copies:
119 $ hg debugdata j 0
119 $ hg debugdata j 0
120 \x01 (esc)
120 \x01 (esc)
121 copy: a
121 copy: a
122 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
122 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
123 \x01 (esc)
123 \x01 (esc)
124 a
124 a
125 $ hg showcopies
125 $ hg showcopies
126 a -> j
126 a -> j
127 $ hg showcopies --config experimental.copies.read-from=compatibility
127 $ hg showcopies --config experimental.copies.read-from=compatibility
128 a -> j
128 a -> j
129 $ hg showcopies --config experimental.copies.read-from=filelog-only
129 $ hg showcopies --config experimental.copies.read-from=filelog-only
130 a -> j
130 a -> j
131 The entries should be written to extras even if they're empty (so the client
131 The entries should be written to extras even if they're empty (so the client
132 won't have to fall back to reading from filelogs)
132 won't have to fall back to reading from filelogs)
133 $ echo x >> j
133 $ echo x >> j
134 $ hg ci -m 'modify j' --config experimental.copies.write-to=compatibility
134 $ hg ci -m 'modify j' --config experimental.copies.write-to=compatibility
135 $ hg changesetcopies
135 $ hg changesetcopies
136 files: j
136 files: j
137 filesadded:
137 filesadded:
138 filesremoved:
138 filesremoved:
139
139
140 p1copies:
140 p1copies:
141 p2copies:
141 p2copies:
142
142
143 Test writing only to filelog
143 Test writing only to filelog
144
144
145 $ hg cp a k
145 $ hg cp a k
146 $ hg ci -m 'copy a to k' --config experimental.copies.write-to=filelog-only
146 $ hg ci -m 'copy a to k' --config experimental.copies.write-to=filelog-only
147 $ hg changesetcopies
147 $ hg changesetcopies
148 files: k
148 files: k
149
149
150 $ hg debugdata k 0
150 $ hg debugdata k 0
151 \x01 (esc)
151 \x01 (esc)
152 copy: a
152 copy: a
153 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
153 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
154 \x01 (esc)
154 \x01 (esc)
155 a
155 a
156 $ hg showcopies
156 $ hg showcopies
157 $ hg showcopies --config experimental.copies.read-from=compatibility
157 $ hg showcopies --config experimental.copies.read-from=compatibility
158 a -> k
158 a -> k
159 $ hg showcopies --config experimental.copies.read-from=filelog-only
159 $ hg showcopies --config experimental.copies.read-from=filelog-only
160 a -> k
160 a -> k
161
161
162 $ cd ..
162 $ cd ..
163
163
164 Test rebasing a commit with copy information
164 Test rebasing a commit with copy information
165
165
166 $ hg init rebase-rename
166 $ hg init rebase-rename
167 $ cd rebase-rename
167 $ cd rebase-rename
168 $ echo a > a
168 $ echo a > a
169 $ hg ci -Aqm 'add a'
169 $ hg ci -Aqm 'add a'
170 $ echo a2 > a
170 $ echo a2 > a
171 $ hg ci -m 'modify a'
171 $ hg ci -m 'modify a'
172 $ hg co -q 0
172 $ hg co -q 0
173 $ hg mv a b
173 $ hg mv a b
174 $ hg ci -qm 'rename a to b'
174 $ hg ci -qm 'rename a to b'
175 $ hg rebase -d 1 --config rebase.experimental.inmemory=yes
175 $ hg rebase -d 1 --config rebase.experimental.inmemory=yes
176 rebasing 2:acfc33f3aa6d "rename a to b" (tip)
176 rebasing 2:fc7287ac5b9b "rename a to b" (tip)
177 merging a and b to b
177 merging a and b to b
178 saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/acfc33f3aa6d-81d0180d-rebase.hg
178 saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/fc7287ac5b9b-8f2a95ec-rebase.hg
179 $ hg st --change . --copies
179 $ hg st --change . --copies
180 A b
180 A b
181 a
181 a
182 R a
182 R a
183 $ cd ..
183 $ cd ..
@@ -1,647 +1,647
1 #testcases filelog compatibility changeset
1 #testcases filelog compatibility changeset
2
2
3 $ cat >> $HGRCPATH << EOF
3 $ cat >> $HGRCPATH << EOF
4 > [extensions]
4 > [extensions]
5 > rebase=
5 > rebase=
6 > [alias]
6 > [alias]
7 > l = log -G -T '{rev} {desc}\n{files}\n'
7 > l = log -G -T '{rev} {desc}\n{files}\n'
8 > EOF
8 > EOF
9
9
10 #if compatibility
10 #if compatibility
11 $ cat >> $HGRCPATH << EOF
11 $ cat >> $HGRCPATH << EOF
12 > [experimental]
12 > [experimental]
13 > copies.read-from = compatibility
13 > copies.read-from = compatibility
14 > EOF
14 > EOF
15 #endif
15 #endif
16
16
17 #if changeset
17 #if changeset
18 $ cat >> $HGRCPATH << EOF
18 $ cat >> $HGRCPATH << EOF
19 > [experimental]
19 > [experimental]
20 > copies.read-from = changeset-only
20 > copies.read-from = changeset-only
21 > copies.write-to = changeset-only
21 > copies.write-to = changeset-only
22 > EOF
22 > EOF
23 #endif
23 #endif
24
24
25 $ REPONUM=0
25 $ REPONUM=0
26 $ newrepo() {
26 $ newrepo() {
27 > cd $TESTTMP
27 > cd $TESTTMP
28 > REPONUM=`expr $REPONUM + 1`
28 > REPONUM=`expr $REPONUM + 1`
29 > hg init repo-$REPONUM
29 > hg init repo-$REPONUM
30 > cd repo-$REPONUM
30 > cd repo-$REPONUM
31 > }
31 > }
32
32
33 Simple rename case
33 Simple rename case
34 $ newrepo
34 $ newrepo
35 $ echo x > x
35 $ echo x > x
36 $ hg ci -Aqm 'add x'
36 $ hg ci -Aqm 'add x'
37 $ hg mv x y
37 $ hg mv x y
38 $ hg debugp1copies
38 $ hg debugp1copies
39 x -> y
39 x -> y
40 $ hg debugp2copies
40 $ hg debugp2copies
41 $ hg ci -m 'rename x to y'
41 $ hg ci -m 'rename x to y'
42 $ hg l
42 $ hg l
43 @ 1 rename x to y
43 @ 1 rename x to y
44 | x y
44 | x y
45 o 0 add x
45 o 0 add x
46 x
46 x
47 $ hg debugp1copies -r 1
47 $ hg debugp1copies -r 1
48 x -> y
48 x -> y
49 $ hg debugpathcopies 0 1
49 $ hg debugpathcopies 0 1
50 x -> y
50 x -> y
51 $ hg debugpathcopies 1 0
51 $ hg debugpathcopies 1 0
52 y -> x
52 y -> x
53 Test filtering copies by path. We do filtering by destination.
53 Test filtering copies by path. We do filtering by destination.
54 $ hg debugpathcopies 0 1 x
54 $ hg debugpathcopies 0 1 x
55 $ hg debugpathcopies 1 0 x
55 $ hg debugpathcopies 1 0 x
56 y -> x
56 y -> x
57 $ hg debugpathcopies 0 1 y
57 $ hg debugpathcopies 0 1 y
58 x -> y
58 x -> y
59 $ hg debugpathcopies 1 0 y
59 $ hg debugpathcopies 1 0 y
60
60
61 Copy a file onto another file
61 Copy a file onto another file
62 $ newrepo
62 $ newrepo
63 $ echo x > x
63 $ echo x > x
64 $ echo y > y
64 $ echo y > y
65 $ hg ci -Aqm 'add x and y'
65 $ hg ci -Aqm 'add x and y'
66 $ hg cp -f x y
66 $ hg cp -f x y
67 $ hg debugp1copies
67 $ hg debugp1copies
68 x -> y
68 x -> y
69 $ hg debugp2copies
69 $ hg debugp2copies
70 $ hg ci -m 'copy x onto y'
70 $ hg ci -m 'copy x onto y'
71 $ hg l
71 $ hg l
72 @ 1 copy x onto y
72 @ 1 copy x onto y
73 | y
73 | y
74 o 0 add x and y
74 o 0 add x and y
75 x y
75 x y
76 $ hg debugp1copies -r 1
76 $ hg debugp1copies -r 1
77 x -> y
77 x -> y
78 Incorrectly doesn't show the rename
78 Incorrectly doesn't show the rename
79 $ hg debugpathcopies 0 1
79 $ hg debugpathcopies 0 1
80
80
81 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
81 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
82 produce a new filelog entry. The changeset's "files" entry should still list the file.
82 produce a new filelog entry. The changeset's "files" entry should still list the file.
83 $ newrepo
83 $ newrepo
84 $ echo x > x
84 $ echo x > x
85 $ echo x > x2
85 $ echo x > x2
86 $ hg ci -Aqm 'add x and x2 with same content'
86 $ hg ci -Aqm 'add x and x2 with same content'
87 $ hg cp -f x x2
87 $ hg cp -f x x2
88 $ hg ci -m 'copy x onto x2'
88 $ hg ci -m 'copy x onto x2'
89 $ hg l
89 $ hg l
90 @ 1 copy x onto x2
90 @ 1 copy x onto x2
91 | x2
91 | x2
92 o 0 add x and x2 with same content
92 o 0 add x and x2 with same content
93 x x2
93 x x2
94 $ hg debugp1copies -r 1
94 $ hg debugp1copies -r 1
95 x -> x2
95 x -> x2
96 Incorrectly doesn't show the rename
96 Incorrectly doesn't show the rename
97 $ hg debugpathcopies 0 1
97 $ hg debugpathcopies 0 1
98
98
99 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
99 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
100 $ newrepo
100 $ newrepo
101 $ echo x > x
101 $ echo x > x
102 $ hg ci -Aqm 'add x'
102 $ hg ci -Aqm 'add x'
103 $ hg cp x y
103 $ hg cp x y
104 $ hg ci -m 'copy x to y'
104 $ hg ci -m 'copy x to y'
105 $ hg rm y
105 $ hg rm y
106 $ hg ci -m 'remove y'
106 $ hg ci -m 'remove y'
107 $ hg cp -f x y
107 $ hg cp -f x y
108 $ hg ci -m 'copy x onto y (again)'
108 $ hg ci -m 'copy x onto y (again)'
109 $ hg l
109 $ hg l
110 @ 3 copy x onto y (again)
110 @ 3 copy x onto y (again)
111 | y
111 | y
112 o 2 remove y
112 o 2 remove y
113 | y
113 | y
114 o 1 copy x to y
114 o 1 copy x to y
115 | y
115 | y
116 o 0 add x
116 o 0 add x
117 x
117 x
118 $ hg debugp1copies -r 3
118 $ hg debugp1copies -r 3
119 x -> y
119 x -> y
120 $ hg debugpathcopies 0 3
120 $ hg debugpathcopies 0 3
121 x -> y
121 x -> y
122
122
123 Rename file in a loop: x->y->z->x
123 Rename file in a loop: x->y->z->x
124 $ newrepo
124 $ newrepo
125 $ echo x > x
125 $ echo x > x
126 $ hg ci -Aqm 'add x'
126 $ hg ci -Aqm 'add x'
127 $ hg mv x y
127 $ hg mv x y
128 $ hg debugp1copies
128 $ hg debugp1copies
129 x -> y
129 x -> y
130 $ hg debugp2copies
130 $ hg debugp2copies
131 $ hg ci -m 'rename x to y'
131 $ hg ci -m 'rename x to y'
132 $ hg mv y z
132 $ hg mv y z
133 $ hg ci -m 'rename y to z'
133 $ hg ci -m 'rename y to z'
134 $ hg mv z x
134 $ hg mv z x
135 $ hg ci -m 'rename z to x'
135 $ hg ci -m 'rename z to x'
136 $ hg l
136 $ hg l
137 @ 3 rename z to x
137 @ 3 rename z to x
138 | x z
138 | x z
139 o 2 rename y to z
139 o 2 rename y to z
140 | y z
140 | y z
141 o 1 rename x to y
141 o 1 rename x to y
142 | x y
142 | x y
143 o 0 add x
143 o 0 add x
144 x
144 x
145 $ hg debugpathcopies 0 3
145 $ hg debugpathcopies 0 3
146
146
147 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
147 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
148 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
148 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
149 $ newrepo
149 $ newrepo
150 $ echo x > x
150 $ echo x > x
151 $ hg ci -Aqm 'add x'
151 $ hg ci -Aqm 'add x'
152 $ hg mv x y
152 $ hg mv x y
153 $ hg ci -m 'rename x to y'
153 $ hg ci -m 'rename x to y'
154 $ hg rm y
154 $ hg rm y
155 $ hg ci -qm 'remove y'
155 $ hg ci -qm 'remove y'
156 $ echo x > y
156 $ echo x > y
157 $ hg ci -Aqm 'add back y'
157 $ hg ci -Aqm 'add back y'
158 $ hg l
158 $ hg l
159 @ 3 add back y
159 @ 3 add back y
160 | y
160 | y
161 o 2 remove y
161 o 2 remove y
162 | y
162 | y
163 o 1 rename x to y
163 o 1 rename x to y
164 | x y
164 | x y
165 o 0 add x
165 o 0 add x
166 x
166 x
167 $ hg debugp1copies -r 3
167 $ hg debugp1copies -r 3
168 $ hg debugpathcopies 0 3
168 $ hg debugpathcopies 0 3
169
169
170 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
170 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
171 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
171 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
172 to the first commit that added the file. We should still report the copy as being from x2.
172 to the first commit that added the file. We should still report the copy as being from x2.
173 $ newrepo
173 $ newrepo
174 $ echo x > x
174 $ echo x > x
175 $ echo x > x2
175 $ echo x > x2
176 $ hg ci -Aqm 'add x and x2 with same content'
176 $ hg ci -Aqm 'add x and x2 with same content'
177 $ hg cp x z
177 $ hg cp x z
178 $ hg ci -qm 'copy x to z'
178 $ hg ci -qm 'copy x to z'
179 $ hg rm z
179 $ hg rm z
180 $ hg ci -m 'remove z'
180 $ hg ci -m 'remove z'
181 $ hg cp x2 z
181 $ hg cp x2 z
182 $ hg ci -m 'copy x2 to z'
182 $ hg ci -m 'copy x2 to z'
183 $ hg l
183 $ hg l
184 @ 3 copy x2 to z
184 @ 3 copy x2 to z
185 | z
185 | z
186 o 2 remove z
186 o 2 remove z
187 | z
187 | z
188 o 1 copy x to z
188 o 1 copy x to z
189 | z
189 | z
190 o 0 add x and x2 with same content
190 o 0 add x and x2 with same content
191 x x2
191 x x2
192 $ hg debugp1copies -r 3
192 $ hg debugp1copies -r 3
193 x2 -> z
193 x2 -> z
194 $ hg debugpathcopies 0 3
194 $ hg debugpathcopies 0 3
195 x2 -> z
195 x2 -> z
196
196
197 Create x and y, then rename them both to the same name, but on different sides of a fork
197 Create x and y, then rename them both to the same name, but on different sides of a fork
198 $ newrepo
198 $ newrepo
199 $ echo x > x
199 $ echo x > x
200 $ echo y > y
200 $ echo y > y
201 $ hg ci -Aqm 'add x and y'
201 $ hg ci -Aqm 'add x and y'
202 $ hg mv x z
202 $ hg mv x z
203 $ hg ci -qm 'rename x to z'
203 $ hg ci -qm 'rename x to z'
204 $ hg co -q 0
204 $ hg co -q 0
205 $ hg mv y z
205 $ hg mv y z
206 $ hg ci -qm 'rename y to z'
206 $ hg ci -qm 'rename y to z'
207 $ hg l
207 $ hg l
208 @ 2 rename y to z
208 @ 2 rename y to z
209 | y z
209 | y z
210 | o 1 rename x to z
210 | o 1 rename x to z
211 |/ x z
211 |/ x z
212 o 0 add x and y
212 o 0 add x and y
213 x y
213 x y
214 $ hg debugpathcopies 1 2
214 $ hg debugpathcopies 1 2
215 z -> x
215 z -> x
216 y -> z
216 y -> z
217
217
218 Fork renames x to y on one side and removes x on the other
218 Fork renames x to y on one side and removes x on the other
219 $ newrepo
219 $ newrepo
220 $ echo x > x
220 $ echo x > x
221 $ hg ci -Aqm 'add x'
221 $ hg ci -Aqm 'add x'
222 $ hg mv x y
222 $ hg mv x y
223 $ hg ci -m 'rename x to y'
223 $ hg ci -m 'rename x to y'
224 $ hg co -q 0
224 $ hg co -q 0
225 $ hg rm x
225 $ hg rm x
226 $ hg ci -m 'remove x'
226 $ hg ci -m 'remove x'
227 created new head
227 created new head
228 $ hg l
228 $ hg l
229 @ 2 remove x
229 @ 2 remove x
230 | x
230 | x
231 | o 1 rename x to y
231 | o 1 rename x to y
232 |/ x y
232 |/ x y
233 o 0 add x
233 o 0 add x
234 x
234 x
235 $ hg debugpathcopies 1 2
235 $ hg debugpathcopies 1 2
236
236
237 Copies via null revision (there shouldn't be any)
237 Copies via null revision (there shouldn't be any)
238 $ newrepo
238 $ newrepo
239 $ echo x > x
239 $ echo x > x
240 $ hg ci -Aqm 'add x'
240 $ hg ci -Aqm 'add x'
241 $ hg cp x y
241 $ hg cp x y
242 $ hg ci -m 'copy x to y'
242 $ hg ci -m 'copy x to y'
243 $ hg co -q null
243 $ hg co -q null
244 $ echo x > x
244 $ echo x > x
245 $ hg ci -Aqm 'add x (again)'
245 $ hg ci -Aqm 'add x (again)'
246 $ hg l
246 $ hg l
247 @ 2 add x (again)
247 @ 2 add x (again)
248 x
248 x
249 o 1 copy x to y
249 o 1 copy x to y
250 | y
250 | y
251 o 0 add x
251 o 0 add x
252 x
252 x
253 $ hg debugpathcopies 1 2
253 $ hg debugpathcopies 1 2
254 $ hg debugpathcopies 2 1
254 $ hg debugpathcopies 2 1
255
255
256 Merge rename from other branch
256 Merge rename from other branch
257 $ newrepo
257 $ newrepo
258 $ echo x > x
258 $ echo x > x
259 $ hg ci -Aqm 'add x'
259 $ hg ci -Aqm 'add x'
260 $ hg mv x y
260 $ hg mv x y
261 $ hg ci -m 'rename x to y'
261 $ hg ci -m 'rename x to y'
262 $ hg co -q 0
262 $ hg co -q 0
263 $ echo z > z
263 $ echo z > z
264 $ hg ci -Aqm 'add z'
264 $ hg ci -Aqm 'add z'
265 $ hg merge -q 1
265 $ hg merge -q 1
266 $ hg debugp1copies
266 $ hg debugp1copies
267 $ hg debugp2copies
267 $ hg debugp2copies
268 $ hg ci -m 'merge rename from p2'
268 $ hg ci -m 'merge rename from p2'
269 $ hg l
269 $ hg l
270 @ 3 merge rename from p2
270 @ 3 merge rename from p2
271 |\ x
271 |\ x
272 | o 2 add z
272 | o 2 add z
273 | | z
273 | | z
274 o | 1 rename x to y
274 o | 1 rename x to y
275 |/ x y
275 |/ x y
276 o 0 add x
276 o 0 add x
277 x
277 x
278 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
278 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
279 merges, so...
279 merges, so...
280 $ hg debugp1copies -r 3
280 $ hg debugp1copies -r 3
281 $ hg debugp2copies -r 3
281 $ hg debugp2copies -r 3
282 $ hg debugpathcopies 0 3
282 $ hg debugpathcopies 0 3
283 x -> y
283 x -> y
284 $ hg debugpathcopies 1 2
284 $ hg debugpathcopies 1 2
285 y -> x
285 y -> x
286 $ hg debugpathcopies 1 3
286 $ hg debugpathcopies 1 3
287 $ hg debugpathcopies 2 3
287 $ hg debugpathcopies 2 3
288 x -> y
288 x -> y
289
289
290 Copy file from either side in a merge
290 Copy file from either side in a merge
291 $ newrepo
291 $ newrepo
292 $ echo x > x
292 $ echo x > x
293 $ hg ci -Aqm 'add x'
293 $ hg ci -Aqm 'add x'
294 $ hg co -q null
294 $ hg co -q null
295 $ echo y > y
295 $ echo y > y
296 $ hg ci -Aqm 'add y'
296 $ hg ci -Aqm 'add y'
297 $ hg merge -q 0
297 $ hg merge -q 0
298 $ hg cp y z
298 $ hg cp y z
299 $ hg debugp1copies
299 $ hg debugp1copies
300 y -> z
300 y -> z
301 $ hg debugp2copies
301 $ hg debugp2copies
302 $ hg ci -m 'copy file from p1 in merge'
302 $ hg ci -m 'copy file from p1 in merge'
303 $ hg co -q 1
303 $ hg co -q 1
304 $ hg merge -q 0
304 $ hg merge -q 0
305 $ hg cp x z
305 $ hg cp x z
306 $ hg debugp1copies
306 $ hg debugp1copies
307 $ hg debugp2copies
307 $ hg debugp2copies
308 x -> z
308 x -> z
309 $ hg ci -qm 'copy file from p2 in merge'
309 $ hg ci -qm 'copy file from p2 in merge'
310 $ hg l
310 $ hg l
311 @ 3 copy file from p2 in merge
311 @ 3 copy file from p2 in merge
312 |\ z
312 |\ z
313 +---o 2 copy file from p1 in merge
313 +---o 2 copy file from p1 in merge
314 | |/ z
314 | |/ z
315 | o 1 add y
315 | o 1 add y
316 | y
316 | y
317 o 0 add x
317 o 0 add x
318 x
318 x
319 $ hg debugp1copies -r 2
319 $ hg debugp1copies -r 2
320 y -> z
320 y -> z
321 $ hg debugp2copies -r 2
321 $ hg debugp2copies -r 2
322 $ hg debugpathcopies 1 2
322 $ hg debugpathcopies 1 2
323 y -> z
323 y -> z
324 $ hg debugpathcopies 0 2
324 $ hg debugpathcopies 0 2
325 $ hg debugp1copies -r 3
325 $ hg debugp1copies -r 3
326 $ hg debugp2copies -r 3
326 $ hg debugp2copies -r 3
327 x -> z
327 x -> z
328 $ hg debugpathcopies 1 3
328 $ hg debugpathcopies 1 3
329 $ hg debugpathcopies 0 3
329 $ hg debugpathcopies 0 3
330 x -> z
330 x -> z
331
331
332 Copy file that exists on both sides of the merge, same content on both sides
332 Copy file that exists on both sides of the merge, same content on both sides
333 $ newrepo
333 $ newrepo
334 $ echo x > x
334 $ echo x > x
335 $ hg ci -Aqm 'add x on branch 1'
335 $ hg ci -Aqm 'add x on branch 1'
336 $ hg co -q null
336 $ hg co -q null
337 $ echo x > x
337 $ echo x > x
338 $ hg ci -Aqm 'add x on branch 2'
338 $ hg ci -Aqm 'add x on branch 2'
339 $ hg merge -q 0
339 $ hg merge -q 0
340 $ hg cp x z
340 $ hg cp x z
341 $ hg debugp1copies
341 $ hg debugp1copies
342 x -> z
342 x -> z
343 $ hg debugp2copies
343 $ hg debugp2copies
344 $ hg ci -qm 'merge'
344 $ hg ci -qm 'merge'
345 $ hg l
345 $ hg l
346 @ 2 merge
346 @ 2 merge
347 |\ z
347 |\ z
348 | o 1 add x on branch 2
348 | o 1 add x on branch 2
349 | x
349 | x
350 o 0 add x on branch 1
350 o 0 add x on branch 1
351 x
351 x
352 $ hg debugp1copies -r 2
352 $ hg debugp1copies -r 2
353 x -> z
353 x -> z
354 $ hg debugp2copies -r 2
354 $ hg debugp2copies -r 2
355 It's a little weird that it shows up on both sides
355 It's a little weird that it shows up on both sides
356 $ hg debugpathcopies 1 2
356 $ hg debugpathcopies 1 2
357 x -> z
357 x -> z
358 $ hg debugpathcopies 0 2
358 $ hg debugpathcopies 0 2
359 x -> z (filelog !)
359 x -> z (filelog !)
360
360
361 Copy file that exists on both sides of the merge, different content
361 Copy file that exists on both sides of the merge, different content
362 $ newrepo
362 $ newrepo
363 $ echo branch1 > x
363 $ echo branch1 > x
364 $ hg ci -Aqm 'add x on branch 1'
364 $ hg ci -Aqm 'add x on branch 1'
365 $ hg co -q null
365 $ hg co -q null
366 $ echo branch2 > x
366 $ echo branch2 > x
367 $ hg ci -Aqm 'add x on branch 2'
367 $ hg ci -Aqm 'add x on branch 2'
368 $ hg merge -q 0
368 $ hg merge -q 0
369 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
369 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
370 [1]
370 [1]
371 $ echo resolved > x
371 $ echo resolved > x
372 $ hg resolve -m x
372 $ hg resolve -m x
373 (no more unresolved files)
373 (no more unresolved files)
374 $ hg cp x z
374 $ hg cp x z
375 $ hg debugp1copies
375 $ hg debugp1copies
376 x -> z
376 x -> z
377 $ hg debugp2copies
377 $ hg debugp2copies
378 $ hg ci -qm 'merge'
378 $ hg ci -qm 'merge'
379 $ hg l
379 $ hg l
380 @ 2 merge
380 @ 2 merge
381 |\ x z
381 |\ x z
382 | o 1 add x on branch 2
382 | o 1 add x on branch 2
383 | x
383 | x
384 o 0 add x on branch 1
384 o 0 add x on branch 1
385 x
385 x
386 $ hg debugp1copies -r 2
386 $ hg debugp1copies -r 2
387 x -> z (changeset !)
387 x -> z (changeset !)
388 $ hg debugp2copies -r 2
388 $ hg debugp2copies -r 2
389 x -> z (no-changeset !)
389 x -> z (no-changeset !)
390 $ hg debugpathcopies 1 2
390 $ hg debugpathcopies 1 2
391 x -> z (changeset !)
391 x -> z (changeset !)
392 $ hg debugpathcopies 0 2
392 $ hg debugpathcopies 0 2
393 x -> z (no-changeset !)
393 x -> z (no-changeset !)
394
394
395 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
395 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
396 of the merge to the merge should include the copy from the other side.
396 of the merge to the merge should include the copy from the other side.
397 $ newrepo
397 $ newrepo
398 $ echo x > x
398 $ echo x > x
399 $ hg ci -Aqm 'add x'
399 $ hg ci -Aqm 'add x'
400 $ hg cp x y
400 $ hg cp x y
401 $ hg ci -qm 'copy x to y'
401 $ hg ci -qm 'copy x to y'
402 $ hg co -q 0
402 $ hg co -q 0
403 $ hg cp x z
403 $ hg cp x z
404 $ hg ci -qm 'copy x to z'
404 $ hg ci -qm 'copy x to z'
405 $ hg merge -q 1
405 $ hg merge -q 1
406 $ hg ci -m 'merge copy x->y and copy x->z'
406 $ hg ci -m 'merge copy x->y and copy x->z'
407 $ hg l
407 $ hg l
408 @ 3 merge copy x->y and copy x->z
408 @ 3 merge copy x->y and copy x->z
409 |\
409 |\
410 | o 2 copy x to z
410 | o 2 copy x to z
411 | | z
411 | | z
412 o | 1 copy x to y
412 o | 1 copy x to y
413 |/ y
413 |/ y
414 o 0 add x
414 o 0 add x
415 x
415 x
416 $ hg debugp1copies -r 3
416 $ hg debugp1copies -r 3
417 $ hg debugp2copies -r 3
417 $ hg debugp2copies -r 3
418 $ hg debugpathcopies 2 3
418 $ hg debugpathcopies 2 3
419 x -> y
419 x -> y
420 $ hg debugpathcopies 1 3
420 $ hg debugpathcopies 1 3
421 x -> z
421 x -> z
422
422
423 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
423 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
424 first side should not include the y->z rename since y didn't exist in the merge base.
424 first side should not include the y->z rename since y didn't exist in the merge base.
425 $ newrepo
425 $ newrepo
426 $ echo x > x
426 $ echo x > x
427 $ hg ci -Aqm 'add x'
427 $ hg ci -Aqm 'add x'
428 $ hg cp x y
428 $ hg cp x y
429 $ hg ci -qm 'copy x to y'
429 $ hg ci -qm 'copy x to y'
430 $ hg co -q 0
430 $ hg co -q 0
431 $ echo y > y
431 $ echo y > y
432 $ hg ci -Aqm 'add y'
432 $ hg ci -Aqm 'add y'
433 $ hg mv y z
433 $ hg mv y z
434 $ hg ci -m 'rename y to z'
434 $ hg ci -m 'rename y to z'
435 $ hg merge -q 1
435 $ hg merge -q 1
436 $ hg ci -m 'merge'
436 $ hg ci -m 'merge'
437 $ hg l
437 $ hg l
438 @ 4 merge
438 @ 4 merge
439 |\
439 |\
440 | o 3 rename y to z
440 | o 3 rename y to z
441 | | y z
441 | | y z
442 | o 2 add y
442 | o 2 add y
443 | | y
443 | | y
444 o | 1 copy x to y
444 o | 1 copy x to y
445 |/ y
445 |/ y
446 o 0 add x
446 o 0 add x
447 x
447 x
448 $ hg debugp1copies -r 3
448 $ hg debugp1copies -r 3
449 y -> z
449 y -> z
450 $ hg debugp2copies -r 3
450 $ hg debugp2copies -r 3
451 $ hg debugpathcopies 2 3
451 $ hg debugpathcopies 2 3
452 y -> z
452 y -> z
453 $ hg debugpathcopies 1 3
453 $ hg debugpathcopies 1 3
454
454
455 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
455 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
456 other side.
456 other side.
457 $ newrepo
457 $ newrepo
458 $ echo x > x
458 $ echo x > x
459 $ echo y > y
459 $ echo y > y
460 $ hg ci -Aqm 'add x and y'
460 $ hg ci -Aqm 'add x and y'
461 $ hg mv x z
461 $ hg mv x z
462 $ hg ci -qm 'rename x to z'
462 $ hg ci -qm 'rename x to z'
463 $ hg co -q 0
463 $ hg co -q 0
464 $ hg mv y z
464 $ hg mv y z
465 $ hg ci -qm 'rename y to z'
465 $ hg ci -qm 'rename y to z'
466 $ echo z >> z
466 $ echo z >> z
467 $ hg ci -m 'modify z'
467 $ hg ci -m 'modify z'
468 $ hg merge -q 1
468 $ hg merge -q 1
469 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
469 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
470 [1]
470 [1]
471 $ echo z > z
471 $ echo z > z
472 $ hg resolve -qm z
472 $ hg resolve -qm z
473 $ hg ci -m 'merge 1 into 3'
473 $ hg ci -m 'merge 1 into 3'
474 Try merging the other direction too
474 Try merging the other direction too
475 $ hg co -q 1
475 $ hg co -q 1
476 $ hg merge -q 3
476 $ hg merge -q 3
477 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
477 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
478 [1]
478 [1]
479 $ echo z > z
479 $ echo z > z
480 $ hg resolve -qm z
480 $ hg resolve -qm z
481 $ hg ci -m 'merge 3 into 1'
481 $ hg ci -m 'merge 3 into 1'
482 created new head
482 created new head
483 $ hg l
483 $ hg l
484 @ 5 merge 3 into 1
484 @ 5 merge 3 into 1
485 |\ y z
485 |\ y z
486 +---o 4 merge 1 into 3
486 +---o 4 merge 1 into 3
487 | |/ x z
487 | |/ x z
488 | o 3 modify z
488 | o 3 modify z
489 | | z
489 | | z
490 | o 2 rename y to z
490 | o 2 rename y to z
491 | | y z
491 | | y z
492 o | 1 rename x to z
492 o | 1 rename x to z
493 |/ x z
493 |/ x z
494 o 0 add x and y
494 o 0 add x and y
495 x y
495 x y
496 $ hg debugpathcopies 1 4
496 $ hg debugpathcopies 1 4
497 $ hg debugpathcopies 2 4
497 $ hg debugpathcopies 2 4
498 $ hg debugpathcopies 0 4
498 $ hg debugpathcopies 0 4
499 x -> z (filelog !)
499 x -> z (filelog !)
500 y -> z (compatibility !)
500 y -> z (compatibility !)
501 $ hg debugpathcopies 1 5
501 $ hg debugpathcopies 1 5
502 $ hg debugpathcopies 2 5
502 $ hg debugpathcopies 2 5
503 $ hg debugpathcopies 0 5
503 $ hg debugpathcopies 0 5
504 x -> z
504 x -> z
505
505
506
506
507 Test for a case in fullcopytracing algorithm where neither of the merging csets
507 Test for a case in fullcopytracing algorithm where neither of the merging csets
508 is a descendant of the merge base. This test reflects that the algorithm
508 is a descendant of the merge base. This test reflects that the algorithm
509 correctly finds the copies:
509 correctly finds the copies:
510
510
511 $ cat >> $HGRCPATH << EOF
511 $ cat >> $HGRCPATH << EOF
512 > [experimental]
512 > [experimental]
513 > evolution.createmarkers=True
513 > evolution.createmarkers=True
514 > evolution.allowunstable=True
514 > evolution.allowunstable=True
515 > EOF
515 > EOF
516
516
517 $ newrepo
517 $ newrepo
518 $ echo a > a
518 $ echo a > a
519 $ hg add a
519 $ hg add a
520 $ hg ci -m "added a"
520 $ hg ci -m "added a"
521 $ echo b > b
521 $ echo b > b
522 $ hg add b
522 $ hg add b
523 $ hg ci -m "added b"
523 $ hg ci -m "added b"
524
524
525 $ hg mv b b1
525 $ hg mv b b1
526 $ hg ci -m "rename b to b1"
526 $ hg ci -m "rename b to b1"
527
527
528 $ hg up ".^"
528 $ hg up ".^"
529 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
529 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
530 $ echo d > d
530 $ echo d > d
531 $ hg add d
531 $ hg add d
532 $ hg ci -m "added d"
532 $ hg ci -m "added d"
533 created new head
533 created new head
534
534
535 $ echo baba >> b
535 $ echo baba >> b
536 $ hg ci --amend -m "added d, modified b"
536 $ hg ci --amend -m "added d, modified b"
537
537
538 $ hg l --hidden
538 $ hg l --hidden
539 @ 4 added d, modified b
539 @ 4 added d, modified b
540 | b d
540 | b d
541 | x 3 added d
541 | x 3 added d
542 |/ d
542 |/ d
543 | o 2 rename b to b1
543 | o 2 rename b to b1
544 |/ b b1
544 |/ b b1
545 o 1 added b
545 o 1 added b
546 | b
546 | b
547 o 0 added a
547 o 0 added a
548 a
548 a
549
549
550 Grafting revision 4 on top of revision 2, showing that it respect the rename:
550 Grafting revision 4 on top of revision 2, showing that it respect the rename:
551
551
552 $ hg up 2 -q
552 $ hg up 2 -q
553 $ hg graft -r 4 --base 3 --hidden
553 $ hg graft -r 4 --base 3 --hidden
554 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
554 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
555 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
555 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
556 merging b1 and b to b1
556 merging b1 and b to b1
557
557
558 $ hg l -l1 -p
558 $ hg l -l1 -p
559 @ 5 added d, modified b
559 @ 5 added d, modified b
560 | b1
560 | b1
561 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
561 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
562 ~ diff -r df722b7fe2d5 -r ba3ddbbdfd96 b1 (changeset !)
562 ~ diff -r 0a0ed3b3251c -r d544fb655520 b1 (changeset !)
563 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
563 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
564 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
564 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
565 @@ -1,1 +1,2 @@
565 @@ -1,1 +1,2 @@
566 b
566 b
567 +baba
567 +baba
568
568
569 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
569 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
570 merging csets is a descendant of the base.
570 merging csets is a descendant of the base.
571 -------------------------------------------------------------------------------------------------
571 -------------------------------------------------------------------------------------------------
572
572
573 $ newrepo
573 $ newrepo
574 $ echo a > a
574 $ echo a > a
575 $ hg add a
575 $ hg add a
576 $ hg ci -m "added a"
576 $ hg ci -m "added a"
577 $ echo b > b
577 $ echo b > b
578 $ hg add b
578 $ hg add b
579 $ hg ci -m "added b"
579 $ hg ci -m "added b"
580
580
581 $ echo foobar > willconflict
581 $ echo foobar > willconflict
582 $ hg add willconflict
582 $ hg add willconflict
583 $ hg ci -m "added willconflict"
583 $ hg ci -m "added willconflict"
584 $ echo c > c
584 $ echo c > c
585 $ hg add c
585 $ hg add c
586 $ hg ci -m "added c"
586 $ hg ci -m "added c"
587
587
588 $ hg l
588 $ hg l
589 @ 3 added c
589 @ 3 added c
590 | c
590 | c
591 o 2 added willconflict
591 o 2 added willconflict
592 | willconflict
592 | willconflict
593 o 1 added b
593 o 1 added b
594 | b
594 | b
595 o 0 added a
595 o 0 added a
596 a
596 a
597
597
598 $ hg up ".^^"
598 $ hg up ".^^"
599 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
599 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
600 $ echo d > d
600 $ echo d > d
601 $ hg add d
601 $ hg add d
602 $ hg ci -m "added d"
602 $ hg ci -m "added d"
603 created new head
603 created new head
604
604
605 $ echo barfoo > willconflict
605 $ echo barfoo > willconflict
606 $ hg add willconflict
606 $ hg add willconflict
607 $ hg ci --amend -m "added willconflict and d"
607 $ hg ci --amend -m "added willconflict and d"
608
608
609 $ hg l
609 $ hg l
610 @ 5 added willconflict and d
610 @ 5 added willconflict and d
611 | d willconflict
611 | d willconflict
612 | o 3 added c
612 | o 3 added c
613 | | c
613 | | c
614 | o 2 added willconflict
614 | o 2 added willconflict
615 |/ willconflict
615 |/ willconflict
616 o 1 added b
616 o 1 added b
617 | b
617 | b
618 o 0 added a
618 o 0 added a
619 a
619 a
620
620
621 $ hg rebase -r . -d 2 -t :other
621 $ hg rebase -r . -d 2 -t :other
622 rebasing 5:5018b1509e94 "added willconflict and d" (tip) (no-changeset !)
622 rebasing 5:5018b1509e94 "added willconflict and d" (tip) (no-changeset !)
623 rebasing 5:619047c26bf8 "added willconflict and d" (tip) (changeset !)
623 rebasing 5:619047c26bf8 "added willconflict and d" (tip) (changeset !)
624
624
625 $ hg up 3 -q
625 $ hg up 3 -q
626 $ hg l --hidden
626 $ hg l --hidden
627 o 6 added willconflict and d
627 o 6 added willconflict and d
628 | d willconflict
628 | d willconflict
629 | x 5 added willconflict and d
629 | x 5 added willconflict and d
630 | | d willconflict
630 | | d willconflict
631 | | x 4 added d
631 | | x 4 added d
632 | |/ d
632 | |/ d
633 +---@ 3 added c
633 +---@ 3 added c
634 | | c
634 | | c
635 o | 2 added willconflict
635 o | 2 added willconflict
636 |/ willconflict
636 |/ willconflict
637 o 1 added b
637 o 1 added b
638 | b
638 | b
639 o 0 added a
639 o 0 added a
640 a
640 a
641
641
642 Now if we trigger a merge between revision 3 and 6 using base revision 4,
642 Now if we trigger a merge between revision 3 and 6 using base revision 4,
643 neither of the merging csets will be a descendant of the base revision:
643 neither of the merging csets will be a descendant of the base revision:
644
644
645 $ hg graft -r 6 --base 4 --hidden -t :other
645 $ hg graft -r 6 --base 4 --hidden -t :other
646 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
646 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
647 grafting 6:9ddc6fb3b691 "added willconflict and d" (tip) (changeset !)
647 grafting 6:9ddc6fb3b691 "added willconflict and d" (tip) (changeset !)
General Comments 0
You need to be logged in to leave comments. Login now