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