##// END OF EJS Templates
obsolete: fix decoding error message arguments
Pierre-Yves David -
r17253:67f56ff5 stable
parent child Browse files
Show More
@@ -1,301 +1,301 b''
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 % (len(metadata), mdsize))
99 % (mdsize, len(metadata)))
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, sopener):
160 160 self._all = []
161 161 # new markers to serialize
162 162 self.precursors = {}
163 163 self.successors = {}
164 164 self.sopener = sopener
165 165 data = sopener.tryread('obsstore')
166 166 if data:
167 167 self._load(_readmarkers(data))
168 168
169 169 def __iter__(self):
170 170 return iter(self._all)
171 171
172 172 def __nonzero__(self):
173 173 return bool(self._all)
174 174
175 175 def create(self, transaction, prec, succs=(), flag=0, metadata=None):
176 176 """obsolete: add a new obsolete marker
177 177
178 178 * ensuring it is hashable
179 179 * check mandatory metadata
180 180 * encode metadata
181 181 """
182 182 if metadata is None:
183 183 metadata = {}
184 184 if len(prec) != 20:
185 185 raise ValueError(prec)
186 186 for succ in succs:
187 187 if len(succ) != 20:
188 188 raise ValueError(succ)
189 189 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
190 190 self.add(transaction, [marker])
191 191
192 192 def add(self, transaction, markers):
193 193 """Add new markers to the store
194 194
195 195 Take care of filtering duplicate.
196 196 Return the number of new marker."""
197 197 new = [m for m in markers if m not in self._all]
198 198 if new:
199 199 f = self.sopener('obsstore', 'ab')
200 200 try:
201 201 # Whether the file's current position is at the begin or at
202 202 # the end after opening a file for appending is implementation
203 203 # defined. So we must seek to the end before calling tell(),
204 204 # or we may get a zero offset for non-zero sized files on
205 205 # some platforms (issue3543).
206 206 f.seek(0, 2) # os.SEEK_END
207 207 offset = f.tell()
208 208 transaction.add('obsstore', offset)
209 209 # offset == 0: new file - add the version header
210 210 for bytes in _encodemarkers(new, offset == 0):
211 211 f.write(bytes)
212 212 finally:
213 213 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
214 214 # call 'filecacheentry.refresh()' here
215 215 f.close()
216 216 self._load(new)
217 217 return len(new)
218 218
219 219 def mergemarkers(self, transation, data):
220 220 markers = _readmarkers(data)
221 221 self.add(transation, markers)
222 222
223 223 def _load(self, markers):
224 224 for mark in markers:
225 225 self._all.append(mark)
226 226 pre, sucs = mark[:2]
227 227 self.precursors.setdefault(pre, set()).add(mark)
228 228 for suc in sucs:
229 229 self.successors.setdefault(suc, set()).add(mark)
230 230
231 231 def _encodemarkers(markers, addheader=False):
232 232 # Kept separate from flushmarkers(), it will be reused for
233 233 # markers exchange.
234 234 if addheader:
235 235 yield _pack('>B', _fmversion)
236 236 for marker in markers:
237 237 pre, sucs, flags, metadata = marker
238 238 nbsuc = len(sucs)
239 239 format = _fmfixed + (_fmnode * nbsuc)
240 240 data = [nbsuc, len(metadata), flags, pre]
241 241 data.extend(sucs)
242 242 yield _pack(format, *data)
243 243 yield metadata
244 244
245 245 def listmarkers(repo):
246 246 """List markers over pushkey"""
247 247 if not repo.obsstore:
248 248 return {}
249 249 markers = _encodemarkers(repo.obsstore, True)
250 250 return {'dump': base85.b85encode(''.join(markers))}
251 251
252 252 def pushmarker(repo, key, old, new):
253 253 """Push markers over pushkey"""
254 254 if key != 'dump':
255 255 repo.ui.warn(_('unknown key: %r') % key)
256 256 return 0
257 257 if old:
258 258 repo.ui.warn(_('unexpected old value') % key)
259 259 return 0
260 260 data = base85.b85decode(new)
261 261 lock = repo.lock()
262 262 try:
263 263 tr = repo.transaction('pushkey: obsolete markers')
264 264 try:
265 265 repo.obsstore.mergemarkers(tr, data)
266 266 tr.close()
267 267 return 1
268 268 finally:
269 269 tr.release()
270 270 finally:
271 271 lock.release()
272 272
273 273 def allmarkers(repo):
274 274 """all obsolete markers known in a repository"""
275 275 for markerdata in repo.obsstore:
276 276 yield marker(repo, markerdata)
277 277
278 278 def precursormarkers(ctx):
279 279 """obsolete marker making this changeset obsolete"""
280 280 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
281 281 yield marker(ctx._repo, data)
282 282
283 283 def successormarkers(ctx):
284 284 """obsolete marker marking this changeset as a successors"""
285 285 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
286 286 yield marker(ctx._repo, data)
287 287
288 288 def anysuccessors(obsstore, node):
289 289 """Yield every successor of <node>
290 290
291 291 This this a linear yield unsuitable to detect splitted changeset."""
292 292 remaining = set([node])
293 293 seen = set(remaining)
294 294 while remaining:
295 295 current = remaining.pop()
296 296 yield current
297 297 for mark in obsstore.precursors.get(current, ()):
298 298 for suc in mark[1]:
299 299 if suc not in seen:
300 300 seen.add(suc)
301 301 remaining.add(suc)
General Comments 0
You need to be logged in to leave comments. Login now