Mercurial > hg
comparison mercurial/obsolete.py @ 17070:ad0d6c2b3279
obsolete: introduction of obsolete markers
Markers are stored as binary records in a log structured file in
.hg/store/obsstore.
author | Pierre-Yves.David@ens-lyon.org |
---|---|
date | Thu, 07 Jun 2012 19:07:39 +0200 |
parents | |
children | 11f26e2669aa |
comparison
equal
deleted
inserted
replaced
17069:2b1c78674230 | 17070:ad0d6c2b3279 |
---|---|
1 # obsolete.py - obsolete markers handling | |
2 # | |
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org> | |
4 # Logilab SA <contact@logilab.fr> | |
5 # | |
6 # This software may be used and distributed according to the terms of the | |
7 # GNU General Public License version 2 or any later version. | |
8 | |
9 """Obsolete markers handling | |
10 | |
11 An obsolete marker maps an old changeset to a list of new | |
12 changesets. If the list of new changesets is empty, the old changeset | |
13 is said to be "killed". Otherwise, the old changeset is being | |
14 "replaced" by the new changesets. | |
15 | |
16 Obsolete markers can be used to record and distribute changeset graph | |
17 transformations performed by history rewriting operations, and help | |
18 building new tools to reconciliate conflicting rewriting actions. To | |
19 facilitate conflicts resolution, markers include various annotations | |
20 besides old and news changeset identifiers, such as creation date or | |
21 author name. | |
22 | |
23 | |
24 Format | |
25 ------ | |
26 | |
27 Markers are stored in an append-only file stored in | |
28 '.hg/store/obsstore'. | |
29 | |
30 The file starts with a version header: | |
31 | |
32 - 1 unsigned byte: version number, starting at zero. | |
33 | |
34 | |
35 The header is followed by the markers. Each marker is made of: | |
36 | |
37 - 1 unsigned byte: number of new changesets "R", could be zero. | |
38 | |
39 - 1 unsigned 32-bits integer: metadata size "M" in bytes. | |
40 | |
41 - 1 byte: a bit field. It is reserved for flags used in obsolete | |
42 markers common operations, to avoid repeated decoding of metadata | |
43 entries. | |
44 | |
45 - 20 bytes: obsoleted changeset identifier. | |
46 | |
47 - N*20 bytes: new changesets identifiers. | |
48 | |
49 - M bytes: metadata as a sequence of nul-terminated strings. Each | |
50 string contains a key and a value, separated by a color ':', without | |
51 additional encoding. Keys cannot contain '\0' or ':' and values | |
52 cannot contain '\0'. | |
53 """ | |
54 import struct | |
55 from mercurial import util | |
56 from i18n import _ | |
57 | |
58 _pack = struct.pack | |
59 _unpack = struct.unpack | |
60 | |
61 | |
62 | |
63 # data used for parsing and writing | |
64 _fmversion = 0 | |
65 _fmfixed = '>BIB20s' | |
66 _fmnode = '20s' | |
67 _fmfsize = struct.calcsize(_fmfixed) | |
68 _fnodesize = struct.calcsize(_fmnode) | |
69 | |
70 def _readmarkers(data): | |
71 """Read and enumerate markers from raw data""" | |
72 off = 0 | |
73 diskversion = _unpack('>B', data[off:off + 1])[0] | |
74 off += 1 | |
75 if diskversion != _fmversion: | |
76 raise util.Abort(_('parsing obsolete marker: unknown version %r') | |
77 % diskversion) | |
78 | |
79 # Loop on markers | |
80 l = len(data) | |
81 while off + _fmfsize <= l: | |
82 # read fixed part | |
83 cur = data[off:off + _fmfsize] | |
84 off += _fmfsize | |
85 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur) | |
86 # read replacement | |
87 sucs = () | |
88 if nbsuc: | |
89 s = (_fnodesize * nbsuc) | |
90 cur = data[off:off + s] | |
91 sucs = _unpack(_fmnode * nbsuc, cur) | |
92 off += s | |
93 # read metadata | |
94 # (metadata will be decoded on demand) | |
95 metadata = data[off:off + mdsize] | |
96 if len(metadata) != mdsize: | |
97 raise util.Abort(_('parsing obsolete marker: metadata is too ' | |
98 'short, %d bytes expected, got %d') | |
99 % (len(metadata), mdsize)) | |
100 off += mdsize | |
101 yield (pre, sucs, flags, metadata) | |
102 | |
103 def encodemeta(meta): | |
104 """Return encoded metadata string to string mapping. | |
105 | |
106 Assume no ':' in key and no '\0' in both key and value.""" | |
107 for key, value in meta.iteritems(): | |
108 if ':' in key or '\0' in key: | |
109 raise ValueError("':' and '\0' are forbidden in metadata key'") | |
110 if '\0' in value: | |
111 raise ValueError("':' are forbidden in metadata value'") | |
112 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)]) | |
113 | |
114 def decodemeta(data): | |
115 """Return string to string dictionary from encoded version.""" | |
116 d = {} | |
117 for l in data.split('\0'): | |
118 if l: | |
119 key, value = l.split(':') | |
120 d[key] = value | |
121 return d | |
122 | |
123 class obsstore(object): | |
124 """Store obsolete markers | |
125 | |
126 Markers can be accessed with two mappings: | |
127 - precursors: old -> set(new) | |
128 - successors: new -> set(old) | |
129 """ | |
130 | |
131 def __init__(self): | |
132 self._all = [] | |
133 # new markers to serialize | |
134 self._new = [] | |
135 self.precursors = {} | |
136 self.successors = {} | |
137 | |
138 def add(self, marker): | |
139 """Add a new marker to the store | |
140 | |
141 This marker still needs to be written to disk""" | |
142 self._new.append(marker) | |
143 self._load(marker) | |
144 | |
145 def loadmarkers(self, data): | |
146 """Load all markers in data, mark them as known.""" | |
147 for marker in _readmarkers(data): | |
148 self._load(marker) | |
149 | |
150 def flushmarkers(self, stream): | |
151 """Write all markers to a stream | |
152 | |
153 After this operation, "new" markers are considered "known".""" | |
154 self._writemarkers(stream) | |
155 self._new[:] = [] | |
156 | |
157 def _load(self, marker): | |
158 self._all.append(marker) | |
159 pre, sucs = marker[:2] | |
160 self.precursors.setdefault(pre, set()).add(marker) | |
161 for suc in sucs: | |
162 self.successors.setdefault(suc, set()).add(marker) | |
163 | |
164 def _writemarkers(self, stream): | |
165 # Kept separate from flushmarkers(), it will be reused for | |
166 # markers exchange. | |
167 stream.write(_pack('>B', _fmversion)) | |
168 for marker in self._all: | |
169 pre, sucs, flags, metadata = marker | |
170 nbsuc = len(sucs) | |
171 format = _fmfixed + (_fmnode * nbsuc) | |
172 data = [nbsuc, len(metadata), flags, pre] | |
173 data.extend(sucs) | |
174 stream.write(_pack(format, *data)) | |
175 stream.write(metadata) |