##// END OF EJS Templates
obsolete: gather all contents related to format version 0 in a single place...
Pierre-Yves David -
r22612:fdfa40ee default
parent child Browse files
Show More
@@ -1,1004 +1,1007
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 marker handling
9 """Obsolete marker 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 rewrite operations, and help
17 transformations performed by history rewrite operations, and help
18 building new tools to reconcile conflicting rewrite actions. To
18 building new tools to reconcile conflicting rewrite actions. To
19 facilitate conflict resolution, markers include various annotations
19 facilitate conflict 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 The old obsoleted changeset is called a "precursor" and possible
23 The old obsoleted changeset is called a "precursor" and possible
24 replacements are called "successors". Markers that used changeset X as
24 replacements are called "successors". Markers that used changeset X as
25 a precursor are called "successor markers of X" because they hold
25 a precursor are called "successor markers of X" because they hold
26 information about the successors of X. Markers that use changeset Y as
26 information about the successors of X. Markers that use changeset Y as
27 a successors are call "precursor markers of Y" because they hold
27 a successors are call "precursor markers of Y" because they hold
28 information about the precursors of Y.
28 information about the precursors of Y.
29
29
30 Examples:
30 Examples:
31
31
32 - When changeset A is replaced by changeset A', one marker is stored:
32 - When changeset A is replaced by changeset A', one marker is stored:
33
33
34 (A, (A',))
34 (A, (A',))
35
35
36 - When changesets A and B are folded into a new changeset C, two markers are
36 - When changesets A and B are folded into a new changeset C, two markers are
37 stored:
37 stored:
38
38
39 (A, (C,)) and (B, (C,))
39 (A, (C,)) and (B, (C,))
40
40
41 - When changeset A is simply "pruned" from the graph, a marker is created:
41 - When changeset A is simply "pruned" from the graph, a marker is created:
42
42
43 (A, ())
43 (A, ())
44
44
45 - When changeset A is split into B and C, a single marker are used:
45 - When changeset A is split into B and C, a single marker are used:
46
46
47 (A, (C, C))
47 (A, (C, C))
48
48
49 We use a single marker to distinguish the "split" case from the "divergence"
49 We use a single marker to distinguish the "split" case from the "divergence"
50 case. If two independent operations rewrite the same changeset A in to A' and
50 case. If two independent operations rewrite the same changeset A in to A' and
51 A'', we have an error case: divergent rewriting. We can detect it because
51 A'', we have an error case: divergent rewriting. We can detect it because
52 two markers will be created independently:
52 two markers will be created independently:
53
53
54 (A, (B,)) and (A, (C,))
54 (A, (B,)) and (A, (C,))
55
55
56 Format
56 Format
57 ------
57 ------
58
58
59 Markers are stored in an append-only file stored in
59 Markers are stored in an append-only file stored in
60 '.hg/store/obsstore'.
60 '.hg/store/obsstore'.
61
61
62 The file starts with a version header:
62 The file starts with a version header:
63
63
64 - 1 unsigned byte: version number, starting at zero.
64 - 1 unsigned byte: version number, starting at zero.
65
65
66
66 The header is followed by the markers. Marker format depend of the version. See
67 The header is followed by the markers. Each marker is made of:
67 comment associated with each format for details.
68
69 - 1 unsigned byte: number of new changesets "N", can be zero.
70
71 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
72
73 - 1 byte: a bit field. It is reserved for flags used in common
74 obsolete marker operations, to avoid repeated decoding of metadata
75 entries.
76
77 - 20 bytes: obsoleted changeset identifier.
78
79 - N*20 bytes: new changesets identifiers.
80
81 - M bytes: metadata as a sequence of nul-terminated strings. Each
82 string contains a key and a value, separated by a colon ':', without
83 additional encoding. Keys cannot contain '\0' or ':' and values
84 cannot contain '\0'.
85
68
86 """
69 """
87 import struct
70 import struct
88 import util, base85, node
71 import util, base85, node
89 import phases
72 import phases
90 from i18n import _
73 from i18n import _
91
74
92 _pack = struct.pack
75 _pack = struct.pack
93 _unpack = struct.unpack
76 _unpack = struct.unpack
94
77
95 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
78 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
96
79
97 # the obsolete feature is not mature enough to be enabled by default.
80 # the obsolete feature is not mature enough to be enabled by default.
98 # you have to rely on third party extension extension to enable this.
81 # you have to rely on third party extension extension to enable this.
99 _enabled = False
82 _enabled = False
100
83
101 # data used for parsing and writing
102 _fm0version = 0
103 _fm0fixed = '>BIB20s'
104 _fm0node = '20s'
105 _fm0fsize = struct.calcsize(_fm0fixed)
106 _fm0fnodesize = struct.calcsize(_fm0node)
107
108 ### obsolescence marker flag
84 ### obsolescence marker flag
109
85
110 ## bumpedfix flag
86 ## bumpedfix flag
111 #
87 #
112 # When a changeset A' succeed to a changeset A which became public, we call A'
88 # When a changeset A' succeed to a changeset A which became public, we call A'
113 # "bumped" because it's a successors of a public changesets
89 # "bumped" because it's a successors of a public changesets
114 #
90 #
115 # o A' (bumped)
91 # o A' (bumped)
116 # |`:
92 # |`:
117 # | o A
93 # | o A
118 # |/
94 # |/
119 # o Z
95 # o Z
120 #
96 #
121 # The way to solve this situation is to create a new changeset Ad as children
97 # The way to solve this situation is to create a new changeset Ad as children
122 # of A. This changeset have the same content than A'. So the diff from A to A'
98 # of A. This changeset have the same content than A'. So the diff from A to A'
123 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
99 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
124 #
100 #
125 # o Ad
101 # o Ad
126 # |`:
102 # |`:
127 # | x A'
103 # | x A'
128 # |'|
104 # |'|
129 # o | A
105 # o | A
130 # |/
106 # |/
131 # o Z
107 # o Z
132 #
108 #
133 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
109 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
134 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
110 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
135 # This flag mean that the successors express the changes between the public and
111 # This flag mean that the successors express the changes between the public and
136 # bumped version and fix the situation, breaking the transitivity of
112 # bumped version and fix the situation, breaking the transitivity of
137 # "bumped" here.
113 # "bumped" here.
138 bumpedfix = 1
114 bumpedfix = 1
139
115
140 def _readmarkers(data):
116 ## Parsing and writing of version "0"
141 """Read and enumerate markers from raw data"""
117 #
142 off = 0
118 # The header is followed by the markers. Each marker is made of:
143 diskversion = _unpack('>B', data[off:off + 1])[0]
119 #
144 off += 1
120 # - 1 unsigned byte: number of new changesets "N", can be zero.
145 if diskversion not in formats:
121 #
146 raise util.Abort(_('parsing obsolete marker: unknown version %r')
122 # - 1 unsigned 32-bits integer: metadata size "M" in bytes.
147 % diskversion)
123 #
148 return diskversion, formats[diskversion][0](data, off)
124 # - 1 byte: a bit field. It is reserved for flags used in common
149
125 # obsolete marker operations, to avoid repeated decoding of metadata
150 def encodemarkers(markers, addheader=False, version=_fm0version):
126 # entries.
151 # Kept separate from flushmarkers(), it will be reused for
127 #
152 # markers exchange.
128 # - 20 bytes: obsoleted changeset identifier.
153 encodeone = formats[version][1]
129 #
154 if addheader:
130 # - N*20 bytes: new changesets identifiers.
155 yield _pack('>B', _fm0version)
131 #
156 for marker in markers:
132 # - M bytes: metadata as a sequence of nul-terminated strings. Each
157 yield encodeone(marker)
133 # string contains a key and a value, separated by a colon ':', without
134 # additional encoding. Keys cannot contain '\0' or ':' and values
135 # cannot contain '\0'.
136 _fm0version = 0
137 _fm0fixed = '>BIB20s'
138 _fm0node = '20s'
139 _fm0fsize = struct.calcsize(_fm0fixed)
140 _fm0fnodesize = struct.calcsize(_fm0node)
158
141
159 def _fm0readmarkers(data, off=0):
142 def _fm0readmarkers(data, off=0):
160 # Loop on markers
143 # Loop on markers
161 l = len(data)
144 l = len(data)
162 while off + _fm0fsize <= l:
145 while off + _fm0fsize <= l:
163 # read fixed part
146 # read fixed part
164 cur = data[off:off + _fm0fsize]
147 cur = data[off:off + _fm0fsize]
165 off += _fm0fsize
148 off += _fm0fsize
166 nbsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
149 nbsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
167 # read replacement
150 # read replacement
168 sucs = ()
151 sucs = ()
169 if nbsuc:
152 if nbsuc:
170 s = (_fm0fnodesize * nbsuc)
153 s = (_fm0fnodesize * nbsuc)
171 cur = data[off:off + s]
154 cur = data[off:off + s]
172 sucs = _unpack(_fm0node * nbsuc, cur)
155 sucs = _unpack(_fm0node * nbsuc, cur)
173 off += s
156 off += s
174 # read metadata
157 # read metadata
175 # (metadata will be decoded on demand)
158 # (metadata will be decoded on demand)
176 metadata = data[off:off + mdsize]
159 metadata = data[off:off + mdsize]
177 if len(metadata) != mdsize:
160 if len(metadata) != mdsize:
178 raise util.Abort(_('parsing obsolete marker: metadata is too '
161 raise util.Abort(_('parsing obsolete marker: metadata is too '
179 'short, %d bytes expected, got %d')
162 'short, %d bytes expected, got %d')
180 % (mdsize, len(metadata)))
163 % (mdsize, len(metadata)))
181 off += mdsize
164 off += mdsize
182 meta = decodemeta(metadata)
165 meta = decodemeta(metadata)
183 try:
166 try:
184 when, offset = meta.pop('date', '0 0').split(' ')
167 when, offset = meta.pop('date', '0 0').split(' ')
185 date = float(when), int(offset)
168 date = float(when), int(offset)
186 except ValueError:
169 except ValueError:
187 date = (0., 0)
170 date = (0., 0)
188 parents = None
171 parents = None
189 if 'p2' in meta:
172 if 'p2' in meta:
190 parents = (meta.pop('p1', None), meta.pop('p2', None))
173 parents = (meta.pop('p1', None), meta.pop('p2', None))
191 elif 'p1' in meta:
174 elif 'p1' in meta:
192 parents = (meta.pop('p1', None),)
175 parents = (meta.pop('p1', None),)
193 elif 'p0' in meta:
176 elif 'p0' in meta:
194 parents = ()
177 parents = ()
195 if parents is not None:
178 if parents is not None:
196 try:
179 try:
197 parents = tuple(node.bin(p) for p in parents)
180 parents = tuple(node.bin(p) for p in parents)
198 # if parent content is not a nodeid, drop the data
181 # if parent content is not a nodeid, drop the data
199 for p in parents:
182 for p in parents:
200 if len(p) != 20:
183 if len(p) != 20:
201 parents = None
184 parents = None
202 break
185 break
203 except TypeError:
186 except TypeError:
204 # if content cannot be translated to nodeid drop the data.
187 # if content cannot be translated to nodeid drop the data.
205 parents = None
188 parents = None
206
189
207 metadata = encodemeta(meta)
190 metadata = encodemeta(meta)
208
191
209 yield (pre, sucs, flags, metadata, date, parents)
192 yield (pre, sucs, flags, metadata, date, parents)
210
193
211 def _fm0encodeonemarker(marker):
194 def _fm0encodeonemarker(marker):
212 pre, sucs, flags, metadata, date, parents = marker
195 pre, sucs, flags, metadata, date, parents = marker
213 metadata = decodemeta(metadata)
196 metadata = decodemeta(metadata)
214 metadata['date'] = '%d %i' % date
197 metadata['date'] = '%d %i' % date
215 if parents is not None:
198 if parents is not None:
216 if not parents:
199 if not parents:
217 # mark that we explicitly recorded no parents
200 # mark that we explicitly recorded no parents
218 metadata['p0'] = ''
201 metadata['p0'] = ''
219 for i, p in enumerate(parents):
202 for i, p in enumerate(parents):
220 metadata['p%i' % (i + 1)] = node.hex(p)
203 metadata['p%i' % (i + 1)] = node.hex(p)
221 metadata = encodemeta(metadata)
204 metadata = encodemeta(metadata)
222 nbsuc = len(sucs)
205 nbsuc = len(sucs)
223 format = _fm0fixed + (_fm0node * nbsuc)
206 format = _fm0fixed + (_fm0node * nbsuc)
224 data = [nbsuc, len(metadata), flags, pre]
207 data = [nbsuc, len(metadata), flags, pre]
225 data.extend(sucs)
208 data.extend(sucs)
226 return _pack(format, *data) + metadata
209 return _pack(format, *data) + metadata
227
210
228 # mapping to read/write various marker formats
211 # mapping to read/write various marker formats
229 # <version> -> (decoder, encoder)
212 # <version> -> (decoder, encoder)
230 formats = {0: (_fm0readmarkers, _fm0encodeonemarker)}
213 formats = {0: (_fm0readmarkers, _fm0encodeonemarker)}
231
214
215 def _readmarkers(data):
216 """Read and enumerate markers from raw data"""
217 off = 0
218 diskversion = _unpack('>B', data[off:off + 1])[0]
219 off += 1
220 if diskversion not in formats:
221 raise util.Abort(_('parsing obsolete marker: unknown version %r')
222 % diskversion)
223 return diskversion, formats[diskversion][0](data, off)
224
225 def encodemarkers(markers, addheader=False, version=_fm0version):
226 # Kept separate from flushmarkers(), it will be reused for
227 # markers exchange.
228 encodeone = formats[version][1]
229 if addheader:
230 yield _pack('>B', _fm0version)
231 for marker in markers:
232 yield encodeone(marker)
233
234
232 def encodemeta(meta):
235 def encodemeta(meta):
233 """Return encoded metadata string to string mapping.
236 """Return encoded metadata string to string mapping.
234
237
235 Assume no ':' in key and no '\0' in both key and value."""
238 Assume no ':' in key and no '\0' in both key and value."""
236 for key, value in meta.iteritems():
239 for key, value in meta.iteritems():
237 if ':' in key or '\0' in key:
240 if ':' in key or '\0' in key:
238 raise ValueError("':' and '\0' are forbidden in metadata key'")
241 raise ValueError("':' and '\0' are forbidden in metadata key'")
239 if '\0' in value:
242 if '\0' in value:
240 raise ValueError("':' is forbidden in metadata value'")
243 raise ValueError("':' is forbidden in metadata value'")
241 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
244 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
242
245
243 def decodemeta(data):
246 def decodemeta(data):
244 """Return string to string dictionary from encoded version."""
247 """Return string to string dictionary from encoded version."""
245 d = {}
248 d = {}
246 for l in data.split('\0'):
249 for l in data.split('\0'):
247 if l:
250 if l:
248 key, value = l.split(':')
251 key, value = l.split(':')
249 d[key] = value
252 d[key] = value
250 return d
253 return d
251
254
252 class marker(object):
255 class marker(object):
253 """Wrap obsolete marker raw data"""
256 """Wrap obsolete marker raw data"""
254
257
255 def __init__(self, repo, data):
258 def __init__(self, repo, data):
256 # the repo argument will be used to create changectx in later version
259 # the repo argument will be used to create changectx in later version
257 self._repo = repo
260 self._repo = repo
258 self._data = data
261 self._data = data
259 self._decodedmeta = None
262 self._decodedmeta = None
260
263
261 def __hash__(self):
264 def __hash__(self):
262 return hash(self._data)
265 return hash(self._data)
263
266
264 def __eq__(self, other):
267 def __eq__(self, other):
265 if type(other) != type(self):
268 if type(other) != type(self):
266 return False
269 return False
267 return self._data == other._data
270 return self._data == other._data
268
271
269 def precnode(self):
272 def precnode(self):
270 """Precursor changeset node identifier"""
273 """Precursor changeset node identifier"""
271 return self._data[0]
274 return self._data[0]
272
275
273 def succnodes(self):
276 def succnodes(self):
274 """List of successor changesets node identifiers"""
277 """List of successor changesets node identifiers"""
275 return self._data[1]
278 return self._data[1]
276
279
277 def parentnodes(self):
280 def parentnodes(self):
278 """Parents of the precursors (None if not recorded)"""
281 """Parents of the precursors (None if not recorded)"""
279 return self._data[5]
282 return self._data[5]
280
283
281 def metadata(self):
284 def metadata(self):
282 """Decoded metadata dictionary"""
285 """Decoded metadata dictionary"""
283 if self._decodedmeta is None:
286 if self._decodedmeta is None:
284 self._decodedmeta = decodemeta(self._data[3])
287 self._decodedmeta = decodemeta(self._data[3])
285 return self._decodedmeta
288 return self._decodedmeta
286
289
287 def date(self):
290 def date(self):
288 """Creation date as (unixtime, offset)"""
291 """Creation date as (unixtime, offset)"""
289 return self._data[4]
292 return self._data[4]
290
293
291 def flags(self):
294 def flags(self):
292 """The flags field of the marker"""
295 """The flags field of the marker"""
293 return self._data[2]
296 return self._data[2]
294
297
295 class obsstore(object):
298 class obsstore(object):
296 """Store obsolete markers
299 """Store obsolete markers
297
300
298 Markers can be accessed with two mappings:
301 Markers can be accessed with two mappings:
299 - precursors[x] -> set(markers on precursors edges of x)
302 - precursors[x] -> set(markers on precursors edges of x)
300 - successors[x] -> set(markers on successors edges of x)
303 - successors[x] -> set(markers on successors edges of x)
301 - children[x] -> set(markers on precursors edges of children(x)
304 - children[x] -> set(markers on precursors edges of children(x)
302 """
305 """
303
306
304 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
307 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
305 # prec: nodeid, precursor changesets
308 # prec: nodeid, precursor changesets
306 # succs: tuple of nodeid, successor changesets (0-N length)
309 # succs: tuple of nodeid, successor changesets (0-N length)
307 # flag: integer, flag field carrying modifier for the markers (see doc)
310 # flag: integer, flag field carrying modifier for the markers (see doc)
308 # meta: binary blob, encoded metadata dictionary
311 # meta: binary blob, encoded metadata dictionary
309 # date: (float, int) tuple, date of marker creation
312 # date: (float, int) tuple, date of marker creation
310 # parents: (tuple of nodeid) or None, parents of precursors
313 # parents: (tuple of nodeid) or None, parents of precursors
311 # None is used when no data has been recorded
314 # None is used when no data has been recorded
312
315
313 def __init__(self, sopener):
316 def __init__(self, sopener):
314 # caches for various obsolescence related cache
317 # caches for various obsolescence related cache
315 self.caches = {}
318 self.caches = {}
316 self._all = []
319 self._all = []
317 self.precursors = {}
320 self.precursors = {}
318 self.successors = {}
321 self.successors = {}
319 self.children = {}
322 self.children = {}
320 self.sopener = sopener
323 self.sopener = sopener
321 data = sopener.tryread('obsstore')
324 data = sopener.tryread('obsstore')
322 self._version = _fm0version
325 self._version = _fm0version
323 if data:
326 if data:
324 self._version, markers = _readmarkers(data)
327 self._version, markers = _readmarkers(data)
325 self._load(markers)
328 self._load(markers)
326
329
327 def __iter__(self):
330 def __iter__(self):
328 return iter(self._all)
331 return iter(self._all)
329
332
330 def __len__(self):
333 def __len__(self):
331 return len(self._all)
334 return len(self._all)
332
335
333 def __nonzero__(self):
336 def __nonzero__(self):
334 return bool(self._all)
337 return bool(self._all)
335
338
336 def create(self, transaction, prec, succs=(), flag=0, parents=None,
339 def create(self, transaction, prec, succs=(), flag=0, parents=None,
337 date=None, metadata=None):
340 date=None, metadata=None):
338 """obsolete: add a new obsolete marker
341 """obsolete: add a new obsolete marker
339
342
340 * ensuring it is hashable
343 * ensuring it is hashable
341 * check mandatory metadata
344 * check mandatory metadata
342 * encode metadata
345 * encode metadata
343
346
344 If you are a human writing code creating marker you want to use the
347 If you are a human writing code creating marker you want to use the
345 `createmarkers` function in this module instead.
348 `createmarkers` function in this module instead.
346
349
347 return True if a new marker have been added, False if the markers
350 return True if a new marker have been added, False if the markers
348 already existed (no op).
351 already existed (no op).
349 """
352 """
350 if metadata is None:
353 if metadata is None:
351 metadata = {}
354 metadata = {}
352 if date is None:
355 if date is None:
353 if 'date' in metadata:
356 if 'date' in metadata:
354 # as a courtesy for out-of-tree extensions
357 # as a courtesy for out-of-tree extensions
355 date = util.parsedate(metadata.pop('date'))
358 date = util.parsedate(metadata.pop('date'))
356 else:
359 else:
357 date = util.makedate()
360 date = util.makedate()
358 if len(prec) != 20:
361 if len(prec) != 20:
359 raise ValueError(prec)
362 raise ValueError(prec)
360 for succ in succs:
363 for succ in succs:
361 if len(succ) != 20:
364 if len(succ) != 20:
362 raise ValueError(succ)
365 raise ValueError(succ)
363 if prec in succs:
366 if prec in succs:
364 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
367 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
365 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata),
368 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata),
366 date, parents)
369 date, parents)
367 return bool(self.add(transaction, [marker]))
370 return bool(self.add(transaction, [marker]))
368
371
369 def add(self, transaction, markers):
372 def add(self, transaction, markers):
370 """Add new markers to the store
373 """Add new markers to the store
371
374
372 Take care of filtering duplicate.
375 Take care of filtering duplicate.
373 Return the number of new marker."""
376 Return the number of new marker."""
374 if not _enabled:
377 if not _enabled:
375 raise util.Abort('obsolete feature is not enabled on this repo')
378 raise util.Abort('obsolete feature is not enabled on this repo')
376 known = set(self._all)
379 known = set(self._all)
377 new = []
380 new = []
378 for m in markers:
381 for m in markers:
379 if m not in known:
382 if m not in known:
380 known.add(m)
383 known.add(m)
381 new.append(m)
384 new.append(m)
382 if new:
385 if new:
383 f = self.sopener('obsstore', 'ab')
386 f = self.sopener('obsstore', 'ab')
384 try:
387 try:
385 # Whether the file's current position is at the begin or at
388 # Whether the file's current position is at the begin or at
386 # the end after opening a file for appending is implementation
389 # the end after opening a file for appending is implementation
387 # defined. So we must seek to the end before calling tell(),
390 # defined. So we must seek to the end before calling tell(),
388 # or we may get a zero offset for non-zero sized files on
391 # or we may get a zero offset for non-zero sized files on
389 # some platforms (issue3543).
392 # some platforms (issue3543).
390 f.seek(0, _SEEK_END)
393 f.seek(0, _SEEK_END)
391 offset = f.tell()
394 offset = f.tell()
392 transaction.add('obsstore', offset)
395 transaction.add('obsstore', offset)
393 # offset == 0: new file - add the version header
396 # offset == 0: new file - add the version header
394 for bytes in encodemarkers(new, offset == 0, self._version):
397 for bytes in encodemarkers(new, offset == 0, self._version):
395 f.write(bytes)
398 f.write(bytes)
396 finally:
399 finally:
397 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
400 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
398 # call 'filecacheentry.refresh()' here
401 # call 'filecacheentry.refresh()' here
399 f.close()
402 f.close()
400 self._load(new)
403 self._load(new)
401 # new marker *may* have changed several set. invalidate the cache.
404 # new marker *may* have changed several set. invalidate the cache.
402 self.caches.clear()
405 self.caches.clear()
403 # records the number of new markers for the transaction hooks
406 # records the number of new markers for the transaction hooks
404 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
407 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
405 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
408 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
406 return len(new)
409 return len(new)
407
410
408 def mergemarkers(self, transaction, data):
411 def mergemarkers(self, transaction, data):
409 """merge a binary stream of markers inside the obsstore
412 """merge a binary stream of markers inside the obsstore
410
413
411 Returns the number of new markers added."""
414 Returns the number of new markers added."""
412 version, markers = _readmarkers(data)
415 version, markers = _readmarkers(data)
413 return self.add(transaction, markers)
416 return self.add(transaction, markers)
414
417
415 def _load(self, markers):
418 def _load(self, markers):
416 for mark in markers:
419 for mark in markers:
417 self._all.append(mark)
420 self._all.append(mark)
418 pre, sucs = mark[:2]
421 pre, sucs = mark[:2]
419 self.successors.setdefault(pre, set()).add(mark)
422 self.successors.setdefault(pre, set()).add(mark)
420 for suc in sucs:
423 for suc in sucs:
421 self.precursors.setdefault(suc, set()).add(mark)
424 self.precursors.setdefault(suc, set()).add(mark)
422 parents = mark[5]
425 parents = mark[5]
423 if parents is not None:
426 if parents is not None:
424 for p in parents:
427 for p in parents:
425 self.children.setdefault(p, set()).add(mark)
428 self.children.setdefault(p, set()).add(mark)
426 if node.nullid in self.precursors:
429 if node.nullid in self.precursors:
427 raise util.Abort(_('bad obsolescence marker detected: '
430 raise util.Abort(_('bad obsolescence marker detected: '
428 'invalid successors nullid'))
431 'invalid successors nullid'))
429 def relevantmarkers(self, nodes):
432 def relevantmarkers(self, nodes):
430 """return a set of all obsolescence markers relevant to a set of nodes.
433 """return a set of all obsolescence markers relevant to a set of nodes.
431
434
432 "relevant" to a set of nodes mean:
435 "relevant" to a set of nodes mean:
433
436
434 - marker that use this changeset as successor
437 - marker that use this changeset as successor
435 - prune marker of direct children on this changeset
438 - prune marker of direct children on this changeset
436 - recursive application of the two rules on precursors of these markers
439 - recursive application of the two rules on precursors of these markers
437
440
438 It is a set so you cannot rely on order."""
441 It is a set so you cannot rely on order."""
439
442
440 pendingnodes = set(nodes)
443 pendingnodes = set(nodes)
441 seenmarkers = set()
444 seenmarkers = set()
442 seennodes = set(pendingnodes)
445 seennodes = set(pendingnodes)
443 precursorsmarkers = self.precursors
446 precursorsmarkers = self.precursors
444 children = self.children
447 children = self.children
445 while pendingnodes:
448 while pendingnodes:
446 direct = set()
449 direct = set()
447 for current in pendingnodes:
450 for current in pendingnodes:
448 direct.update(precursorsmarkers.get(current, ()))
451 direct.update(precursorsmarkers.get(current, ()))
449 pruned = [m for m in children.get(current, ()) if not m[1]]
452 pruned = [m for m in children.get(current, ()) if not m[1]]
450 direct.update(pruned)
453 direct.update(pruned)
451 direct -= seenmarkers
454 direct -= seenmarkers
452 pendingnodes = set([m[0] for m in direct])
455 pendingnodes = set([m[0] for m in direct])
453 seenmarkers |= direct
456 seenmarkers |= direct
454 pendingnodes -= seennodes
457 pendingnodes -= seennodes
455 seennodes |= pendingnodes
458 seennodes |= pendingnodes
456 return seenmarkers
459 return seenmarkers
457
460
458 def commonversion(versions):
461 def commonversion(versions):
459 """Return the newest version listed in both versions and our local formats.
462 """Return the newest version listed in both versions and our local formats.
460
463
461 Returns None if no common version exists.
464 Returns None if no common version exists.
462 """
465 """
463 versions.sort(reverse=True)
466 versions.sort(reverse=True)
464 # search for highest version known on both side
467 # search for highest version known on both side
465 for v in versions:
468 for v in versions:
466 if v in formats:
469 if v in formats:
467 return v
470 return v
468 return None
471 return None
469
472
470 # arbitrary picked to fit into 8K limit from HTTP server
473 # arbitrary picked to fit into 8K limit from HTTP server
471 # you have to take in account:
474 # you have to take in account:
472 # - the version header
475 # - the version header
473 # - the base85 encoding
476 # - the base85 encoding
474 _maxpayload = 5300
477 _maxpayload = 5300
475
478
476 def _pushkeyescape(markers):
479 def _pushkeyescape(markers):
477 """encode markers into a dict suitable for pushkey exchange
480 """encode markers into a dict suitable for pushkey exchange
478
481
479 - binary data is base85 encoded
482 - binary data is base85 encoded
480 - split in chunks smaller than 5300 bytes"""
483 - split in chunks smaller than 5300 bytes"""
481 keys = {}
484 keys = {}
482 parts = []
485 parts = []
483 currentlen = _maxpayload * 2 # ensure we create a new part
486 currentlen = _maxpayload * 2 # ensure we create a new part
484 for marker in markers:
487 for marker in markers:
485 nextdata = _fm0encodeonemarker(marker)
488 nextdata = _fm0encodeonemarker(marker)
486 if (len(nextdata) + currentlen > _maxpayload):
489 if (len(nextdata) + currentlen > _maxpayload):
487 currentpart = []
490 currentpart = []
488 currentlen = 0
491 currentlen = 0
489 parts.append(currentpart)
492 parts.append(currentpart)
490 currentpart.append(nextdata)
493 currentpart.append(nextdata)
491 currentlen += len(nextdata)
494 currentlen += len(nextdata)
492 for idx, part in enumerate(reversed(parts)):
495 for idx, part in enumerate(reversed(parts)):
493 data = ''.join([_pack('>B', _fm0version)] + part)
496 data = ''.join([_pack('>B', _fm0version)] + part)
494 keys['dump%i' % idx] = base85.b85encode(data)
497 keys['dump%i' % idx] = base85.b85encode(data)
495 return keys
498 return keys
496
499
497 def listmarkers(repo):
500 def listmarkers(repo):
498 """List markers over pushkey"""
501 """List markers over pushkey"""
499 if not repo.obsstore:
502 if not repo.obsstore:
500 return {}
503 return {}
501 return _pushkeyescape(repo.obsstore)
504 return _pushkeyescape(repo.obsstore)
502
505
503 def pushmarker(repo, key, old, new):
506 def pushmarker(repo, key, old, new):
504 """Push markers over pushkey"""
507 """Push markers over pushkey"""
505 if not key.startswith('dump'):
508 if not key.startswith('dump'):
506 repo.ui.warn(_('unknown key: %r') % key)
509 repo.ui.warn(_('unknown key: %r') % key)
507 return 0
510 return 0
508 if old:
511 if old:
509 repo.ui.warn(_('unexpected old value for %r') % key)
512 repo.ui.warn(_('unexpected old value for %r') % key)
510 return 0
513 return 0
511 data = base85.b85decode(new)
514 data = base85.b85decode(new)
512 lock = repo.lock()
515 lock = repo.lock()
513 try:
516 try:
514 tr = repo.transaction('pushkey: obsolete markers')
517 tr = repo.transaction('pushkey: obsolete markers')
515 try:
518 try:
516 repo.obsstore.mergemarkers(tr, data)
519 repo.obsstore.mergemarkers(tr, data)
517 tr.close()
520 tr.close()
518 return 1
521 return 1
519 finally:
522 finally:
520 tr.release()
523 tr.release()
521 finally:
524 finally:
522 lock.release()
525 lock.release()
523
526
524 def getmarkers(repo, nodes=None):
527 def getmarkers(repo, nodes=None):
525 """returns markers known in a repository
528 """returns markers known in a repository
526
529
527 If <nodes> is specified, only markers "relevant" to those nodes are are
530 If <nodes> is specified, only markers "relevant" to those nodes are are
528 returned"""
531 returned"""
529 if nodes is None:
532 if nodes is None:
530 rawmarkers = repo.obsstore
533 rawmarkers = repo.obsstore
531 else:
534 else:
532 rawmarkers = repo.obsstore.relevantmarkers(nodes)
535 rawmarkers = repo.obsstore.relevantmarkers(nodes)
533
536
534 for markerdata in rawmarkers:
537 for markerdata in rawmarkers:
535 yield marker(repo, markerdata)
538 yield marker(repo, markerdata)
536
539
537 def relevantmarkers(repo, node):
540 def relevantmarkers(repo, node):
538 """all obsolete markers relevant to some revision"""
541 """all obsolete markers relevant to some revision"""
539 for markerdata in repo.obsstore.relevantmarkers(node):
542 for markerdata in repo.obsstore.relevantmarkers(node):
540 yield marker(repo, markerdata)
543 yield marker(repo, markerdata)
541
544
542
545
543 def precursormarkers(ctx):
546 def precursormarkers(ctx):
544 """obsolete marker marking this changeset as a successors"""
547 """obsolete marker marking this changeset as a successors"""
545 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
548 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
546 yield marker(ctx._repo, data)
549 yield marker(ctx._repo, data)
547
550
548 def successormarkers(ctx):
551 def successormarkers(ctx):
549 """obsolete marker making this changeset obsolete"""
552 """obsolete marker making this changeset obsolete"""
550 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
553 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
551 yield marker(ctx._repo, data)
554 yield marker(ctx._repo, data)
552
555
553 def allsuccessors(obsstore, nodes, ignoreflags=0):
556 def allsuccessors(obsstore, nodes, ignoreflags=0):
554 """Yield node for every successor of <nodes>.
557 """Yield node for every successor of <nodes>.
555
558
556 Some successors may be unknown locally.
559 Some successors may be unknown locally.
557
560
558 This is a linear yield unsuited to detecting split changesets. It includes
561 This is a linear yield unsuited to detecting split changesets. It includes
559 initial nodes too."""
562 initial nodes too."""
560 remaining = set(nodes)
563 remaining = set(nodes)
561 seen = set(remaining)
564 seen = set(remaining)
562 while remaining:
565 while remaining:
563 current = remaining.pop()
566 current = remaining.pop()
564 yield current
567 yield current
565 for mark in obsstore.successors.get(current, ()):
568 for mark in obsstore.successors.get(current, ()):
566 # ignore marker flagged with specified flag
569 # ignore marker flagged with specified flag
567 if mark[2] & ignoreflags:
570 if mark[2] & ignoreflags:
568 continue
571 continue
569 for suc in mark[1]:
572 for suc in mark[1]:
570 if suc not in seen:
573 if suc not in seen:
571 seen.add(suc)
574 seen.add(suc)
572 remaining.add(suc)
575 remaining.add(suc)
573
576
574 def allprecursors(obsstore, nodes, ignoreflags=0):
577 def allprecursors(obsstore, nodes, ignoreflags=0):
575 """Yield node for every precursors of <nodes>.
578 """Yield node for every precursors of <nodes>.
576
579
577 Some precursors may be unknown locally.
580 Some precursors may be unknown locally.
578
581
579 This is a linear yield unsuited to detecting folded changesets. It includes
582 This is a linear yield unsuited to detecting folded changesets. It includes
580 initial nodes too."""
583 initial nodes too."""
581
584
582 remaining = set(nodes)
585 remaining = set(nodes)
583 seen = set(remaining)
586 seen = set(remaining)
584 while remaining:
587 while remaining:
585 current = remaining.pop()
588 current = remaining.pop()
586 yield current
589 yield current
587 for mark in obsstore.precursors.get(current, ()):
590 for mark in obsstore.precursors.get(current, ()):
588 # ignore marker flagged with specified flag
591 # ignore marker flagged with specified flag
589 if mark[2] & ignoreflags:
592 if mark[2] & ignoreflags:
590 continue
593 continue
591 suc = mark[0]
594 suc = mark[0]
592 if suc not in seen:
595 if suc not in seen:
593 seen.add(suc)
596 seen.add(suc)
594 remaining.add(suc)
597 remaining.add(suc)
595
598
596 def foreground(repo, nodes):
599 def foreground(repo, nodes):
597 """return all nodes in the "foreground" of other node
600 """return all nodes in the "foreground" of other node
598
601
599 The foreground of a revision is anything reachable using parent -> children
602 The foreground of a revision is anything reachable using parent -> children
600 or precursor -> successor relation. It is very similar to "descendant" but
603 or precursor -> successor relation. It is very similar to "descendant" but
601 augmented with obsolescence information.
604 augmented with obsolescence information.
602
605
603 Beware that possible obsolescence cycle may result if complex situation.
606 Beware that possible obsolescence cycle may result if complex situation.
604 """
607 """
605 repo = repo.unfiltered()
608 repo = repo.unfiltered()
606 foreground = set(repo.set('%ln::', nodes))
609 foreground = set(repo.set('%ln::', nodes))
607 if repo.obsstore:
610 if repo.obsstore:
608 # We only need this complicated logic if there is obsolescence
611 # We only need this complicated logic if there is obsolescence
609 # XXX will probably deserve an optimised revset.
612 # XXX will probably deserve an optimised revset.
610 nm = repo.changelog.nodemap
613 nm = repo.changelog.nodemap
611 plen = -1
614 plen = -1
612 # compute the whole set of successors or descendants
615 # compute the whole set of successors or descendants
613 while len(foreground) != plen:
616 while len(foreground) != plen:
614 plen = len(foreground)
617 plen = len(foreground)
615 succs = set(c.node() for c in foreground)
618 succs = set(c.node() for c in foreground)
616 mutable = [c.node() for c in foreground if c.mutable()]
619 mutable = [c.node() for c in foreground if c.mutable()]
617 succs.update(allsuccessors(repo.obsstore, mutable))
620 succs.update(allsuccessors(repo.obsstore, mutable))
618 known = (n for n in succs if n in nm)
621 known = (n for n in succs if n in nm)
619 foreground = set(repo.set('%ln::', known))
622 foreground = set(repo.set('%ln::', known))
620 return set(c.node() for c in foreground)
623 return set(c.node() for c in foreground)
621
624
622
625
623 def successorssets(repo, initialnode, cache=None):
626 def successorssets(repo, initialnode, cache=None):
624 """Return all set of successors of initial nodes
627 """Return all set of successors of initial nodes
625
628
626 The successors set of a changeset A are a group of revisions that succeed
629 The successors set of a changeset A are a group of revisions that succeed
627 A. It succeeds A as a consistent whole, each revision being only a partial
630 A. It succeeds A as a consistent whole, each revision being only a partial
628 replacement. The successors set contains non-obsolete changesets only.
631 replacement. The successors set contains non-obsolete changesets only.
629
632
630 This function returns the full list of successor sets which is why it
633 This function returns the full list of successor sets which is why it
631 returns a list of tuples and not just a single tuple. Each tuple is a valid
634 returns a list of tuples and not just a single tuple. Each tuple is a valid
632 successors set. Not that (A,) may be a valid successors set for changeset A
635 successors set. Not that (A,) may be a valid successors set for changeset A
633 (see below).
636 (see below).
634
637
635 In most cases, a changeset A will have a single element (e.g. the changeset
638 In most cases, a changeset A will have a single element (e.g. the changeset
636 A is replaced by A') in its successors set. Though, it is also common for a
639 A is replaced by A') in its successors set. Though, it is also common for a
637 changeset A to have no elements in its successor set (e.g. the changeset
640 changeset A to have no elements in its successor set (e.g. the changeset
638 has been pruned). Therefore, the returned list of successors sets will be
641 has been pruned). Therefore, the returned list of successors sets will be
639 [(A',)] or [], respectively.
642 [(A',)] or [], respectively.
640
643
641 When a changeset A is split into A' and B', however, it will result in a
644 When a changeset A is split into A' and B', however, it will result in a
642 successors set containing more than a single element, i.e. [(A',B')].
645 successors set containing more than a single element, i.e. [(A',B')].
643 Divergent changesets will result in multiple successors sets, i.e. [(A',),
646 Divergent changesets will result in multiple successors sets, i.e. [(A',),
644 (A'')].
647 (A'')].
645
648
646 If a changeset A is not obsolete, then it will conceptually have no
649 If a changeset A is not obsolete, then it will conceptually have no
647 successors set. To distinguish this from a pruned changeset, the successor
650 successors set. To distinguish this from a pruned changeset, the successor
648 set will only contain itself, i.e. [(A,)].
651 set will only contain itself, i.e. [(A,)].
649
652
650 Finally, successors unknown locally are considered to be pruned (obsoleted
653 Finally, successors unknown locally are considered to be pruned (obsoleted
651 without any successors).
654 without any successors).
652
655
653 The optional `cache` parameter is a dictionary that may contain precomputed
656 The optional `cache` parameter is a dictionary that may contain precomputed
654 successors sets. It is meant to reuse the computation of a previous call to
657 successors sets. It is meant to reuse the computation of a previous call to
655 `successorssets` when multiple calls are made at the same time. The cache
658 `successorssets` when multiple calls are made at the same time. The cache
656 dictionary is updated in place. The caller is responsible for its live
659 dictionary is updated in place. The caller is responsible for its live
657 spawn. Code that makes multiple calls to `successorssets` *must* use this
660 spawn. Code that makes multiple calls to `successorssets` *must* use this
658 cache mechanism or suffer terrible performances.
661 cache mechanism or suffer terrible performances.
659
662
660 """
663 """
661
664
662 succmarkers = repo.obsstore.successors
665 succmarkers = repo.obsstore.successors
663
666
664 # Stack of nodes we search successors sets for
667 # Stack of nodes we search successors sets for
665 toproceed = [initialnode]
668 toproceed = [initialnode]
666 # set version of above list for fast loop detection
669 # set version of above list for fast loop detection
667 # element added to "toproceed" must be added here
670 # element added to "toproceed" must be added here
668 stackedset = set(toproceed)
671 stackedset = set(toproceed)
669 if cache is None:
672 if cache is None:
670 cache = {}
673 cache = {}
671
674
672 # This while loop is the flattened version of a recursive search for
675 # This while loop is the flattened version of a recursive search for
673 # successors sets
676 # successors sets
674 #
677 #
675 # def successorssets(x):
678 # def successorssets(x):
676 # successors = directsuccessors(x)
679 # successors = directsuccessors(x)
677 # ss = [[]]
680 # ss = [[]]
678 # for succ in directsuccessors(x):
681 # for succ in directsuccessors(x):
679 # # product as in itertools cartesian product
682 # # product as in itertools cartesian product
680 # ss = product(ss, successorssets(succ))
683 # ss = product(ss, successorssets(succ))
681 # return ss
684 # return ss
682 #
685 #
683 # But we can not use plain recursive calls here:
686 # But we can not use plain recursive calls here:
684 # - that would blow the python call stack
687 # - that would blow the python call stack
685 # - obsolescence markers may have cycles, we need to handle them.
688 # - obsolescence markers may have cycles, we need to handle them.
686 #
689 #
687 # The `toproceed` list act as our call stack. Every node we search
690 # The `toproceed` list act as our call stack. Every node we search
688 # successors set for are stacked there.
691 # successors set for are stacked there.
689 #
692 #
690 # The `stackedset` is set version of this stack used to check if a node is
693 # The `stackedset` is set version of this stack used to check if a node is
691 # already stacked. This check is used to detect cycles and prevent infinite
694 # already stacked. This check is used to detect cycles and prevent infinite
692 # loop.
695 # loop.
693 #
696 #
694 # successors set of all nodes are stored in the `cache` dictionary.
697 # successors set of all nodes are stored in the `cache` dictionary.
695 #
698 #
696 # After this while loop ends we use the cache to return the successors sets
699 # After this while loop ends we use the cache to return the successors sets
697 # for the node requested by the caller.
700 # for the node requested by the caller.
698 while toproceed:
701 while toproceed:
699 # Every iteration tries to compute the successors sets of the topmost
702 # Every iteration tries to compute the successors sets of the topmost
700 # node of the stack: CURRENT.
703 # node of the stack: CURRENT.
701 #
704 #
702 # There are four possible outcomes:
705 # There are four possible outcomes:
703 #
706 #
704 # 1) We already know the successors sets of CURRENT:
707 # 1) We already know the successors sets of CURRENT:
705 # -> mission accomplished, pop it from the stack.
708 # -> mission accomplished, pop it from the stack.
706 # 2) Node is not obsolete:
709 # 2) Node is not obsolete:
707 # -> the node is its own successors sets. Add it to the cache.
710 # -> the node is its own successors sets. Add it to the cache.
708 # 3) We do not know successors set of direct successors of CURRENT:
711 # 3) We do not know successors set of direct successors of CURRENT:
709 # -> We add those successors to the stack.
712 # -> We add those successors to the stack.
710 # 4) We know successors sets of all direct successors of CURRENT:
713 # 4) We know successors sets of all direct successors of CURRENT:
711 # -> We can compute CURRENT successors set and add it to the
714 # -> We can compute CURRENT successors set and add it to the
712 # cache.
715 # cache.
713 #
716 #
714 current = toproceed[-1]
717 current = toproceed[-1]
715 if current in cache:
718 if current in cache:
716 # case (1): We already know the successors sets
719 # case (1): We already know the successors sets
717 stackedset.remove(toproceed.pop())
720 stackedset.remove(toproceed.pop())
718 elif current not in succmarkers:
721 elif current not in succmarkers:
719 # case (2): The node is not obsolete.
722 # case (2): The node is not obsolete.
720 if current in repo:
723 if current in repo:
721 # We have a valid last successors.
724 # We have a valid last successors.
722 cache[current] = [(current,)]
725 cache[current] = [(current,)]
723 else:
726 else:
724 # Final obsolete version is unknown locally.
727 # Final obsolete version is unknown locally.
725 # Do not count that as a valid successors
728 # Do not count that as a valid successors
726 cache[current] = []
729 cache[current] = []
727 else:
730 else:
728 # cases (3) and (4)
731 # cases (3) and (4)
729 #
732 #
730 # We proceed in two phases. Phase 1 aims to distinguish case (3)
733 # We proceed in two phases. Phase 1 aims to distinguish case (3)
731 # from case (4):
734 # from case (4):
732 #
735 #
733 # For each direct successors of CURRENT, we check whether its
736 # For each direct successors of CURRENT, we check whether its
734 # successors sets are known. If they are not, we stack the
737 # successors sets are known. If they are not, we stack the
735 # unknown node and proceed to the next iteration of the while
738 # unknown node and proceed to the next iteration of the while
736 # loop. (case 3)
739 # loop. (case 3)
737 #
740 #
738 # During this step, we may detect obsolescence cycles: a node
741 # During this step, we may detect obsolescence cycles: a node
739 # with unknown successors sets but already in the call stack.
742 # with unknown successors sets but already in the call stack.
740 # In such a situation, we arbitrary set the successors sets of
743 # In such a situation, we arbitrary set the successors sets of
741 # the node to nothing (node pruned) to break the cycle.
744 # the node to nothing (node pruned) to break the cycle.
742 #
745 #
743 # If no break was encountered we proceed to phase 2.
746 # If no break was encountered we proceed to phase 2.
744 #
747 #
745 # Phase 2 computes successors sets of CURRENT (case 4); see details
748 # Phase 2 computes successors sets of CURRENT (case 4); see details
746 # in phase 2 itself.
749 # in phase 2 itself.
747 #
750 #
748 # Note the two levels of iteration in each phase.
751 # Note the two levels of iteration in each phase.
749 # - The first one handles obsolescence markers using CURRENT as
752 # - The first one handles obsolescence markers using CURRENT as
750 # precursor (successors markers of CURRENT).
753 # precursor (successors markers of CURRENT).
751 #
754 #
752 # Having multiple entry here means divergence.
755 # Having multiple entry here means divergence.
753 #
756 #
754 # - The second one handles successors defined in each marker.
757 # - The second one handles successors defined in each marker.
755 #
758 #
756 # Having none means pruned node, multiple successors means split,
759 # Having none means pruned node, multiple successors means split,
757 # single successors are standard replacement.
760 # single successors are standard replacement.
758 #
761 #
759 for mark in sorted(succmarkers[current]):
762 for mark in sorted(succmarkers[current]):
760 for suc in mark[1]:
763 for suc in mark[1]:
761 if suc not in cache:
764 if suc not in cache:
762 if suc in stackedset:
765 if suc in stackedset:
763 # cycle breaking
766 # cycle breaking
764 cache[suc] = []
767 cache[suc] = []
765 else:
768 else:
766 # case (3) If we have not computed successors sets
769 # case (3) If we have not computed successors sets
767 # of one of those successors we add it to the
770 # of one of those successors we add it to the
768 # `toproceed` stack and stop all work for this
771 # `toproceed` stack and stop all work for this
769 # iteration.
772 # iteration.
770 toproceed.append(suc)
773 toproceed.append(suc)
771 stackedset.add(suc)
774 stackedset.add(suc)
772 break
775 break
773 else:
776 else:
774 continue
777 continue
775 break
778 break
776 else:
779 else:
777 # case (4): we know all successors sets of all direct
780 # case (4): we know all successors sets of all direct
778 # successors
781 # successors
779 #
782 #
780 # Successors set contributed by each marker depends on the
783 # Successors set contributed by each marker depends on the
781 # successors sets of all its "successors" node.
784 # successors sets of all its "successors" node.
782 #
785 #
783 # Each different marker is a divergence in the obsolescence
786 # Each different marker is a divergence in the obsolescence
784 # history. It contributes successors sets distinct from other
787 # history. It contributes successors sets distinct from other
785 # markers.
788 # markers.
786 #
789 #
787 # Within a marker, a successor may have divergent successors
790 # Within a marker, a successor may have divergent successors
788 # sets. In such a case, the marker will contribute multiple
791 # sets. In such a case, the marker will contribute multiple
789 # divergent successors sets. If multiple successors have
792 # divergent successors sets. If multiple successors have
790 # divergent successors sets, a Cartesian product is used.
793 # divergent successors sets, a Cartesian product is used.
791 #
794 #
792 # At the end we post-process successors sets to remove
795 # At the end we post-process successors sets to remove
793 # duplicated entry and successors set that are strict subset of
796 # duplicated entry and successors set that are strict subset of
794 # another one.
797 # another one.
795 succssets = []
798 succssets = []
796 for mark in sorted(succmarkers[current]):
799 for mark in sorted(succmarkers[current]):
797 # successors sets contributed by this marker
800 # successors sets contributed by this marker
798 markss = [[]]
801 markss = [[]]
799 for suc in mark[1]:
802 for suc in mark[1]:
800 # cardinal product with previous successors
803 # cardinal product with previous successors
801 productresult = []
804 productresult = []
802 for prefix in markss:
805 for prefix in markss:
803 for suffix in cache[suc]:
806 for suffix in cache[suc]:
804 newss = list(prefix)
807 newss = list(prefix)
805 for part in suffix:
808 for part in suffix:
806 # do not duplicated entry in successors set
809 # do not duplicated entry in successors set
807 # first entry wins.
810 # first entry wins.
808 if part not in newss:
811 if part not in newss:
809 newss.append(part)
812 newss.append(part)
810 productresult.append(newss)
813 productresult.append(newss)
811 markss = productresult
814 markss = productresult
812 succssets.extend(markss)
815 succssets.extend(markss)
813 # remove duplicated and subset
816 # remove duplicated and subset
814 seen = []
817 seen = []
815 final = []
818 final = []
816 candidate = sorted(((set(s), s) for s in succssets if s),
819 candidate = sorted(((set(s), s) for s in succssets if s),
817 key=lambda x: len(x[1]), reverse=True)
820 key=lambda x: len(x[1]), reverse=True)
818 for setversion, listversion in candidate:
821 for setversion, listversion in candidate:
819 for seenset in seen:
822 for seenset in seen:
820 if setversion.issubset(seenset):
823 if setversion.issubset(seenset):
821 break
824 break
822 else:
825 else:
823 final.append(listversion)
826 final.append(listversion)
824 seen.append(setversion)
827 seen.append(setversion)
825 final.reverse() # put small successors set first
828 final.reverse() # put small successors set first
826 cache[current] = final
829 cache[current] = final
827 return cache[initialnode]
830 return cache[initialnode]
828
831
829 def _knownrevs(repo, nodes):
832 def _knownrevs(repo, nodes):
830 """yield revision numbers of known nodes passed in parameters
833 """yield revision numbers of known nodes passed in parameters
831
834
832 Unknown revisions are silently ignored."""
835 Unknown revisions are silently ignored."""
833 torev = repo.changelog.nodemap.get
836 torev = repo.changelog.nodemap.get
834 for n in nodes:
837 for n in nodes:
835 rev = torev(n)
838 rev = torev(n)
836 if rev is not None:
839 if rev is not None:
837 yield rev
840 yield rev
838
841
839 # mapping of 'set-name' -> <function to compute this set>
842 # mapping of 'set-name' -> <function to compute this set>
840 cachefuncs = {}
843 cachefuncs = {}
841 def cachefor(name):
844 def cachefor(name):
842 """Decorator to register a function as computing the cache for a set"""
845 """Decorator to register a function as computing the cache for a set"""
843 def decorator(func):
846 def decorator(func):
844 assert name not in cachefuncs
847 assert name not in cachefuncs
845 cachefuncs[name] = func
848 cachefuncs[name] = func
846 return func
849 return func
847 return decorator
850 return decorator
848
851
849 def getrevs(repo, name):
852 def getrevs(repo, name):
850 """Return the set of revision that belong to the <name> set
853 """Return the set of revision that belong to the <name> set
851
854
852 Such access may compute the set and cache it for future use"""
855 Such access may compute the set and cache it for future use"""
853 repo = repo.unfiltered()
856 repo = repo.unfiltered()
854 if not repo.obsstore:
857 if not repo.obsstore:
855 return frozenset()
858 return frozenset()
856 if name not in repo.obsstore.caches:
859 if name not in repo.obsstore.caches:
857 repo.obsstore.caches[name] = cachefuncs[name](repo)
860 repo.obsstore.caches[name] = cachefuncs[name](repo)
858 return repo.obsstore.caches[name]
861 return repo.obsstore.caches[name]
859
862
860 # To be simple we need to invalidate obsolescence cache when:
863 # To be simple we need to invalidate obsolescence cache when:
861 #
864 #
862 # - new changeset is added:
865 # - new changeset is added:
863 # - public phase is changed
866 # - public phase is changed
864 # - obsolescence marker are added
867 # - obsolescence marker are added
865 # - strip is used a repo
868 # - strip is used a repo
866 def clearobscaches(repo):
869 def clearobscaches(repo):
867 """Remove all obsolescence related cache from a repo
870 """Remove all obsolescence related cache from a repo
868
871
869 This remove all cache in obsstore is the obsstore already exist on the
872 This remove all cache in obsstore is the obsstore already exist on the
870 repo.
873 repo.
871
874
872 (We could be smarter here given the exact event that trigger the cache
875 (We could be smarter here given the exact event that trigger the cache
873 clearing)"""
876 clearing)"""
874 # only clear cache is there is obsstore data in this repo
877 # only clear cache is there is obsstore data in this repo
875 if 'obsstore' in repo._filecache:
878 if 'obsstore' in repo._filecache:
876 repo.obsstore.caches.clear()
879 repo.obsstore.caches.clear()
877
880
878 @cachefor('obsolete')
881 @cachefor('obsolete')
879 def _computeobsoleteset(repo):
882 def _computeobsoleteset(repo):
880 """the set of obsolete revisions"""
883 """the set of obsolete revisions"""
881 obs = set()
884 obs = set()
882 getrev = repo.changelog.nodemap.get
885 getrev = repo.changelog.nodemap.get
883 getphase = repo._phasecache.phase
886 getphase = repo._phasecache.phase
884 for n in repo.obsstore.successors:
887 for n in repo.obsstore.successors:
885 rev = getrev(n)
888 rev = getrev(n)
886 if rev is not None and getphase(repo, rev):
889 if rev is not None and getphase(repo, rev):
887 obs.add(rev)
890 obs.add(rev)
888 return obs
891 return obs
889
892
890 @cachefor('unstable')
893 @cachefor('unstable')
891 def _computeunstableset(repo):
894 def _computeunstableset(repo):
892 """the set of non obsolete revisions with obsolete parents"""
895 """the set of non obsolete revisions with obsolete parents"""
893 # revset is not efficient enough here
896 # revset is not efficient enough here
894 # we do (obsolete()::) - obsolete() by hand
897 # we do (obsolete()::) - obsolete() by hand
895 obs = getrevs(repo, 'obsolete')
898 obs = getrevs(repo, 'obsolete')
896 if not obs:
899 if not obs:
897 return set()
900 return set()
898 cl = repo.changelog
901 cl = repo.changelog
899 return set(r for r in cl.descendants(obs) if r not in obs)
902 return set(r for r in cl.descendants(obs) if r not in obs)
900
903
901 @cachefor('suspended')
904 @cachefor('suspended')
902 def _computesuspendedset(repo):
905 def _computesuspendedset(repo):
903 """the set of obsolete parents with non obsolete descendants"""
906 """the set of obsolete parents with non obsolete descendants"""
904 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
907 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
905 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
908 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
906
909
907 @cachefor('extinct')
910 @cachefor('extinct')
908 def _computeextinctset(repo):
911 def _computeextinctset(repo):
909 """the set of obsolete parents without non obsolete descendants"""
912 """the set of obsolete parents without non obsolete descendants"""
910 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
913 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
911
914
912
915
913 @cachefor('bumped')
916 @cachefor('bumped')
914 def _computebumpedset(repo):
917 def _computebumpedset(repo):
915 """the set of revs trying to obsolete public revisions"""
918 """the set of revs trying to obsolete public revisions"""
916 bumped = set()
919 bumped = set()
917 # util function (avoid attribute lookup in the loop)
920 # util function (avoid attribute lookup in the loop)
918 phase = repo._phasecache.phase # would be faster to grab the full list
921 phase = repo._phasecache.phase # would be faster to grab the full list
919 public = phases.public
922 public = phases.public
920 cl = repo.changelog
923 cl = repo.changelog
921 torev = cl.nodemap.get
924 torev = cl.nodemap.get
922 obs = getrevs(repo, 'obsolete')
925 obs = getrevs(repo, 'obsolete')
923 for rev in repo:
926 for rev in repo:
924 # We only evaluate mutable, non-obsolete revision
927 # We only evaluate mutable, non-obsolete revision
925 if (public < phase(repo, rev)) and (rev not in obs):
928 if (public < phase(repo, rev)) and (rev not in obs):
926 node = cl.node(rev)
929 node = cl.node(rev)
927 # (future) A cache of precursors may worth if split is very common
930 # (future) A cache of precursors may worth if split is very common
928 for pnode in allprecursors(repo.obsstore, [node],
931 for pnode in allprecursors(repo.obsstore, [node],
929 ignoreflags=bumpedfix):
932 ignoreflags=bumpedfix):
930 prev = torev(pnode) # unfiltered! but so is phasecache
933 prev = torev(pnode) # unfiltered! but so is phasecache
931 if (prev is not None) and (phase(repo, prev) <= public):
934 if (prev is not None) and (phase(repo, prev) <= public):
932 # we have a public precursors
935 # we have a public precursors
933 bumped.add(rev)
936 bumped.add(rev)
934 break # Next draft!
937 break # Next draft!
935 return bumped
938 return bumped
936
939
937 @cachefor('divergent')
940 @cachefor('divergent')
938 def _computedivergentset(repo):
941 def _computedivergentset(repo):
939 """the set of rev that compete to be the final successors of some revision.
942 """the set of rev that compete to be the final successors of some revision.
940 """
943 """
941 divergent = set()
944 divergent = set()
942 obsstore = repo.obsstore
945 obsstore = repo.obsstore
943 newermap = {}
946 newermap = {}
944 for ctx in repo.set('(not public()) - obsolete()'):
947 for ctx in repo.set('(not public()) - obsolete()'):
945 mark = obsstore.precursors.get(ctx.node(), ())
948 mark = obsstore.precursors.get(ctx.node(), ())
946 toprocess = set(mark)
949 toprocess = set(mark)
947 while toprocess:
950 while toprocess:
948 prec = toprocess.pop()[0]
951 prec = toprocess.pop()[0]
949 if prec not in newermap:
952 if prec not in newermap:
950 successorssets(repo, prec, newermap)
953 successorssets(repo, prec, newermap)
951 newer = [n for n in newermap[prec] if n]
954 newer = [n for n in newermap[prec] if n]
952 if len(newer) > 1:
955 if len(newer) > 1:
953 divergent.add(ctx.rev())
956 divergent.add(ctx.rev())
954 break
957 break
955 toprocess.update(obsstore.precursors.get(prec, ()))
958 toprocess.update(obsstore.precursors.get(prec, ()))
956 return divergent
959 return divergent
957
960
958
961
959 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
962 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
960 """Add obsolete markers between changesets in a repo
963 """Add obsolete markers between changesets in a repo
961
964
962 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
965 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
963 tuple. `old` and `news` are changectx. metadata is an optional dictionary
966 tuple. `old` and `news` are changectx. metadata is an optional dictionary
964 containing metadata for this marker only. It is merged with the global
967 containing metadata for this marker only. It is merged with the global
965 metadata specified through the `metadata` argument of this function,
968 metadata specified through the `metadata` argument of this function,
966
969
967 Trying to obsolete a public changeset will raise an exception.
970 Trying to obsolete a public changeset will raise an exception.
968
971
969 Current user and date are used except if specified otherwise in the
972 Current user and date are used except if specified otherwise in the
970 metadata attribute.
973 metadata attribute.
971
974
972 This function operates within a transaction of its own, but does
975 This function operates within a transaction of its own, but does
973 not take any lock on the repo.
976 not take any lock on the repo.
974 """
977 """
975 # prepare metadata
978 # prepare metadata
976 if metadata is None:
979 if metadata is None:
977 metadata = {}
980 metadata = {}
978 if 'user' not in metadata:
981 if 'user' not in metadata:
979 metadata['user'] = repo.ui.username()
982 metadata['user'] = repo.ui.username()
980 tr = repo.transaction('add-obsolescence-marker')
983 tr = repo.transaction('add-obsolescence-marker')
981 try:
984 try:
982 for rel in relations:
985 for rel in relations:
983 prec = rel[0]
986 prec = rel[0]
984 sucs = rel[1]
987 sucs = rel[1]
985 localmetadata = metadata.copy()
988 localmetadata = metadata.copy()
986 if 2 < len(rel):
989 if 2 < len(rel):
987 localmetadata.update(rel[2])
990 localmetadata.update(rel[2])
988
991
989 if not prec.mutable():
992 if not prec.mutable():
990 raise util.Abort("cannot obsolete immutable changeset: %s"
993 raise util.Abort("cannot obsolete immutable changeset: %s"
991 % prec)
994 % prec)
992 nprec = prec.node()
995 nprec = prec.node()
993 nsucs = tuple(s.node() for s in sucs)
996 nsucs = tuple(s.node() for s in sucs)
994 npare = None
997 npare = None
995 if not nsucs:
998 if not nsucs:
996 npare = tuple(p.node() for p in prec.parents())
999 npare = tuple(p.node() for p in prec.parents())
997 if nprec in nsucs:
1000 if nprec in nsucs:
998 raise util.Abort("changeset %s cannot obsolete itself" % prec)
1001 raise util.Abort("changeset %s cannot obsolete itself" % prec)
999 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1002 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1000 date=date, metadata=localmetadata)
1003 date=date, metadata=localmetadata)
1001 repo.filteredrevcache.clear()
1004 repo.filteredrevcache.clear()
1002 tr.close()
1005 tr.close()
1003 finally:
1006 finally:
1004 tr.release()
1007 tr.release()
General Comments 0
You need to be logged in to leave comments. Login now