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