##// END OF EJS Templates
obsolete: fix error message at marker creation...
Pierre-Yves David -
r17117:217bfb10 default
parent child Browse files
Show More
@@ -1,279 +1,279
1 1 # obsolete.py - obsolete markers handling
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """Obsolete markers handling
10 10
11 11 An obsolete marker maps an old changeset to a list of new
12 12 changesets. If the list of new changesets is empty, the old changeset
13 13 is said to be "killed". Otherwise, the old changeset is being
14 14 "replaced" by the new changesets.
15 15
16 16 Obsolete markers can be used to record and distribute changeset graph
17 17 transformations performed by history rewriting operations, and help
18 18 building new tools to reconciliate conflicting rewriting actions. To
19 19 facilitate conflicts resolution, markers include various annotations
20 20 besides old and news changeset identifiers, such as creation date or
21 21 author name.
22 22
23 23
24 24 Format
25 25 ------
26 26
27 27 Markers are stored in an append-only file stored in
28 28 '.hg/store/obsstore'.
29 29
30 30 The file starts with a version header:
31 31
32 32 - 1 unsigned byte: version number, starting at zero.
33 33
34 34
35 35 The header is followed by the markers. Each marker is made of:
36 36
37 37 - 1 unsigned byte: number of new changesets "R", could be zero.
38 38
39 39 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
40 40
41 41 - 1 byte: a bit field. It is reserved for flags used in obsolete
42 42 markers common operations, to avoid repeated decoding of metadata
43 43 entries.
44 44
45 45 - 20 bytes: obsoleted changeset identifier.
46 46
47 47 - N*20 bytes: new changesets identifiers.
48 48
49 49 - M bytes: metadata as a sequence of nul-terminated strings. Each
50 50 string contains a key and a value, separated by a color ':', without
51 51 additional encoding. Keys cannot contain '\0' or ':' and values
52 52 cannot contain '\0'.
53 53 """
54 54 import struct
55 55 from mercurial import util, base85
56 56 from i18n import _
57 57
58 58 _pack = struct.pack
59 59 _unpack = struct.unpack
60 60
61 61
62 62
63 63 # data used for parsing and writing
64 64 _fmversion = 0
65 65 _fmfixed = '>BIB20s'
66 66 _fmnode = '20s'
67 67 _fmfsize = struct.calcsize(_fmfixed)
68 68 _fnodesize = struct.calcsize(_fmnode)
69 69
70 70 def _readmarkers(data):
71 71 """Read and enumerate markers from raw data"""
72 72 off = 0
73 73 diskversion = _unpack('>B', data[off:off + 1])[0]
74 74 off += 1
75 75 if diskversion != _fmversion:
76 76 raise util.Abort(_('parsing obsolete marker: unknown version %r')
77 77 % diskversion)
78 78
79 79 # Loop on markers
80 80 l = len(data)
81 81 while off + _fmfsize <= l:
82 82 # read fixed part
83 83 cur = data[off:off + _fmfsize]
84 84 off += _fmfsize
85 85 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
86 86 # read replacement
87 87 sucs = ()
88 88 if nbsuc:
89 89 s = (_fnodesize * nbsuc)
90 90 cur = data[off:off + s]
91 91 sucs = _unpack(_fmnode * nbsuc, cur)
92 92 off += s
93 93 # read metadata
94 94 # (metadata will be decoded on demand)
95 95 metadata = data[off:off + mdsize]
96 96 if len(metadata) != mdsize:
97 97 raise util.Abort(_('parsing obsolete marker: metadata is too '
98 98 'short, %d bytes expected, got %d')
99 99 % (len(metadata), mdsize))
100 100 off += mdsize
101 101 yield (pre, sucs, flags, metadata)
102 102
103 103 def encodemeta(meta):
104 104 """Return encoded metadata string to string mapping.
105 105
106 106 Assume no ':' in key and no '\0' in both key and value."""
107 107 for key, value in meta.iteritems():
108 108 if ':' in key or '\0' in key:
109 109 raise ValueError("':' and '\0' are forbidden in metadata key'")
110 110 if '\0' in value:
111 111 raise ValueError("':' are forbidden in metadata value'")
112 112 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
113 113
114 114 def decodemeta(data):
115 115 """Return string to string dictionary from encoded version."""
116 116 d = {}
117 117 for l in data.split('\0'):
118 118 if l:
119 119 key, value = l.split(':')
120 120 d[key] = value
121 121 return d
122 122
123 123 class marker(object):
124 124 """Wrap obsolete marker raw data"""
125 125
126 126 def __init__(self, repo, data):
127 127 # the repo argument will be used to create changectx in later version
128 128 self._repo = repo
129 129 self._data = data
130 130 self._decodedmeta = None
131 131
132 132 def precnode(self):
133 133 """Precursor changeset node identifier"""
134 134 return self._data[0]
135 135
136 136 def succnodes(self):
137 137 """List of successor changesets node identifiers"""
138 138 return self._data[1]
139 139
140 140 def metadata(self):
141 141 """Decoded metadata dictionary"""
142 142 if self._decodedmeta is None:
143 143 self._decodedmeta = decodemeta(self._data[3])
144 144 return self._decodedmeta
145 145
146 146 def date(self):
147 147 """Creation date as (unixtime, offset)"""
148 148 parts = self.metadata()['date'].split(' ')
149 149 return (float(parts[0]), int(parts[1]))
150 150
151 151 class obsstore(object):
152 152 """Store obsolete markers
153 153
154 154 Markers can be accessed with two mappings:
155 155 - precursors: old -> set(new)
156 156 - successors: new -> set(old)
157 157 """
158 158
159 159 def __init__(self):
160 160 self._all = []
161 161 # new markers to serialize
162 162 self._new = []
163 163 self.precursors = {}
164 164 self.successors = {}
165 165
166 166 def __iter__(self):
167 167 return iter(self._all)
168 168
169 169 def __nonzero__(self):
170 170 return bool(self._all)
171 171
172 172 def create(self, prec, succs=(), flag=0, metadata=None):
173 173 """obsolete: add a new obsolete marker
174 174
175 175 * ensuring it is hashable
176 176 * check mandatory metadata
177 177 * encode metadata
178 178 """
179 179 if metadata is None:
180 180 metadata = {}
181 181 if len(prec) != 20:
182 182 raise ValueError(prec)
183 183 for succ in succs:
184 184 if len(succ) != 20:
185 raise ValueError(prec)
185 raise ValueError(succ)
186 186 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
187 187 self.add(marker)
188 188
189 189 def add(self, marker):
190 190 """Add a new marker to the store
191 191
192 192 This marker still needs to be written to disk"""
193 193 self._new.append(marker)
194 194 self._load(marker)
195 195
196 196 def loadmarkers(self, data):
197 197 """Load all markers in data, mark them as known."""
198 198 for marker in _readmarkers(data):
199 199 self._load(marker)
200 200
201 201 def mergemarkers(self, data):
202 202 other = set(_readmarkers(data))
203 203 local = set(self._all)
204 204 new = other - local
205 205 for marker in new:
206 206 self.add(marker)
207 207
208 208 def flushmarkers(self, stream):
209 209 """Write all markers to a stream
210 210
211 211 After this operation, "new" markers are considered "known"."""
212 212 self._writemarkers(stream)
213 213 self._new[:] = []
214 214
215 215 def _load(self, marker):
216 216 self._all.append(marker)
217 217 pre, sucs = marker[:2]
218 218 self.precursors.setdefault(pre, set()).add(marker)
219 219 for suc in sucs:
220 220 self.successors.setdefault(suc, set()).add(marker)
221 221
222 222 def _writemarkers(self, stream=None):
223 223 # Kept separate from flushmarkers(), it will be reused for
224 224 # markers exchange.
225 225 if stream is None:
226 226 final = []
227 227 w = final.append
228 228 else:
229 229 w = stream.write
230 230 w(_pack('>B', _fmversion))
231 231 for marker in self._all:
232 232 pre, sucs, flags, metadata = marker
233 233 nbsuc = len(sucs)
234 234 format = _fmfixed + (_fmnode * nbsuc)
235 235 data = [nbsuc, len(metadata), flags, pre]
236 236 data.extend(sucs)
237 237 w(_pack(format, *data))
238 238 w(metadata)
239 239 if stream is None:
240 240 return ''.join(final)
241 241
242 242 def listmarkers(repo):
243 243 """List markers over pushkey"""
244 244 if not repo.obsstore:
245 245 return {}
246 246 data = repo.obsstore._writemarkers()
247 247 return {'dump': base85.b85encode(data)}
248 248
249 249 def pushmarker(repo, key, old, new):
250 250 """Push markers over pushkey"""
251 251 if key != 'dump':
252 252 repo.ui.warn(_('unknown key: %r') % key)
253 253 return 0
254 254 if old:
255 255 repo.ui.warn(_('unexpected old value') % key)
256 256 return 0
257 257 data = base85.b85decode(new)
258 258 lock = repo.lock()
259 259 try:
260 260 repo.obsstore.mergemarkers(data)
261 261 return 1
262 262 finally:
263 263 lock.release()
264 264
265 265 def allmarkers(repo):
266 266 """all obsolete markers known in a repository"""
267 267 for markerdata in repo.obsstore:
268 268 yield marker(repo, markerdata)
269 269
270 270 def precursormarkers(ctx):
271 271 """obsolete marker making this changeset obsolete"""
272 272 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
273 273 yield marker(ctx._repo, data)
274 274
275 275 def successormarkers(ctx):
276 276 """obsolete marker marking this changeset as a successors"""
277 277 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
278 278 yield marker(ctx._repo, data)
279 279
General Comments 0
You need to be logged in to leave comments. Login now