##// END OF EJS Templates
obsolete: introduction of obsolete markers...
Pierre-Yves.David@ens-lyon.org -
r17070:ad0d6c2b default
parent child Browse files
Show More
@@ -0,0 +1,175 b''
1 # obsolete.py - obsolete markers handling
2 #
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # Logilab SA <contact@logilab.fr>
5 #
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
8
9 """Obsolete markers handling
10
11 An obsolete marker maps an old changeset to a list of new
12 changesets. If the list of new changesets is empty, the old changeset
13 is said to be "killed". Otherwise, the old changeset is being
14 "replaced" by the new changesets.
15
16 Obsolete markers can be used to record and distribute changeset graph
17 transformations performed by history rewriting operations, and help
18 building new tools to reconciliate conflicting rewriting actions. To
19 facilitate conflicts resolution, markers include various annotations
20 besides old and news changeset identifiers, such as creation date or
21 author name.
22
23
24 Format
25 ------
26
27 Markers are stored in an append-only file stored in
28 '.hg/store/obsstore'.
29
30 The file starts with a version header:
31
32 - 1 unsigned byte: version number, starting at zero.
33
34
35 The header is followed by the markers. Each marker is made of:
36
37 - 1 unsigned byte: number of new changesets "R", could be zero.
38
39 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
40
41 - 1 byte: a bit field. It is reserved for flags used in obsolete
42 markers common operations, to avoid repeated decoding of metadata
43 entries.
44
45 - 20 bytes: obsoleted changeset identifier.
46
47 - N*20 bytes: new changesets identifiers.
48
49 - M bytes: metadata as a sequence of nul-terminated strings. Each
50 string contains a key and a value, separated by a color ':', without
51 additional encoding. Keys cannot contain '\0' or ':' and values
52 cannot contain '\0'.
53 """
54 import struct
55 from mercurial import util
56 from i18n import _
57
58 _pack = struct.pack
59 _unpack = struct.unpack
60
61
62
63 # data used for parsing and writing
64 _fmversion = 0
65 _fmfixed = '>BIB20s'
66 _fmnode = '20s'
67 _fmfsize = struct.calcsize(_fmfixed)
68 _fnodesize = struct.calcsize(_fmnode)
69
70 def _readmarkers(data):
71 """Read and enumerate markers from raw data"""
72 off = 0
73 diskversion = _unpack('>B', data[off:off + 1])[0]
74 off += 1
75 if diskversion != _fmversion:
76 raise util.Abort(_('parsing obsolete marker: unknown version %r')
77 % diskversion)
78
79 # Loop on markers
80 l = len(data)
81 while off + _fmfsize <= l:
82 # read fixed part
83 cur = data[off:off + _fmfsize]
84 off += _fmfsize
85 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
86 # read replacement
87 sucs = ()
88 if nbsuc:
89 s = (_fnodesize * nbsuc)
90 cur = data[off:off + s]
91 sucs = _unpack(_fmnode * nbsuc, cur)
92 off += s
93 # read metadata
94 # (metadata will be decoded on demand)
95 metadata = data[off:off + mdsize]
96 if len(metadata) != mdsize:
97 raise util.Abort(_('parsing obsolete marker: metadata is too '
98 'short, %d bytes expected, got %d')
99 % (len(metadata), mdsize))
100 off += mdsize
101 yield (pre, sucs, flags, metadata)
102
103 def encodemeta(meta):
104 """Return encoded metadata string to string mapping.
105
106 Assume no ':' in key and no '\0' in both key and value."""
107 for key, value in meta.iteritems():
108 if ':' in key or '\0' in key:
109 raise ValueError("':' and '\0' are forbidden in metadata key'")
110 if '\0' in value:
111 raise ValueError("':' are forbidden in metadata value'")
112 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
113
114 def decodemeta(data):
115 """Return string to string dictionary from encoded version."""
116 d = {}
117 for l in data.split('\0'):
118 if l:
119 key, value = l.split(':')
120 d[key] = value
121 return d
122
123 class obsstore(object):
124 """Store obsolete markers
125
126 Markers can be accessed with two mappings:
127 - precursors: old -> set(new)
128 - successors: new -> set(old)
129 """
130
131 def __init__(self):
132 self._all = []
133 # new markers to serialize
134 self._new = []
135 self.precursors = {}
136 self.successors = {}
137
138 def add(self, marker):
139 """Add a new marker to the store
140
141 This marker still needs to be written to disk"""
142 self._new.append(marker)
143 self._load(marker)
144
145 def loadmarkers(self, data):
146 """Load all markers in data, mark them as known."""
147 for marker in _readmarkers(data):
148 self._load(marker)
149
150 def flushmarkers(self, stream):
151 """Write all markers to a stream
152
153 After this operation, "new" markers are considered "known"."""
154 self._writemarkers(stream)
155 self._new[:] = []
156
157 def _load(self, marker):
158 self._all.append(marker)
159 pre, sucs = marker[:2]
160 self.precursors.setdefault(pre, set()).add(marker)
161 for suc in sucs:
162 self.successors.setdefault(suc, set()).add(marker)
163
164 def _writemarkers(self, stream):
165 # Kept separate from flushmarkers(), it will be reused for
166 # markers exchange.
167 stream.write(_pack('>B', _fmversion))
168 for marker in self._all:
169 pre, sucs, flags, metadata = marker
170 nbsuc = len(sucs)
171 format = _fmfixed + (_fmnode * nbsuc)
172 data = [nbsuc, len(metadata), flags, pre]
173 data.extend(sucs)
174 stream.write(_pack(format, *data))
175 stream.write(metadata)
@@ -7,7 +7,7 b''
7
7
8 from node import bin, hex, nullid, nullrev, short
8 from node import bin, hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import repo, changegroup, subrepo, discovery, pushkey
10 import repo, changegroup, subrepo, discovery, pushkey, obsolete
11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
12 import lock, transaction, store, encoding
12 import lock, transaction, store, encoding
13 import scmutil, util, extensions, hook, error, revset
13 import scmutil, util, extensions, hook, error, revset
@@ -192,6 +192,14 b' class localrepository(repo.repository):'
192 def _phasecache(self):
192 def _phasecache(self):
193 return phases.phasecache(self, self._phasedefaults)
193 return phases.phasecache(self, self._phasedefaults)
194
194
195 @storecache('obsstore')
196 def obsstore(self):
197 store = obsolete.obsstore()
198 data = self.sopener.tryread('obsstore')
199 if data:
200 store.loadmarkers(data)
201 return store
202
195 @storecache('00changelog.i')
203 @storecache('00changelog.i')
196 def changelog(self):
204 def changelog(self):
197 c = changelog.changelog(self.sopener)
205 c = changelog.changelog(self.sopener)
@@ -983,6 +991,16 b' class localrepository(repo.repository):'
983 self.store.write()
991 self.store.write()
984 if '_phasecache' in vars(self):
992 if '_phasecache' in vars(self):
985 self._phasecache.write()
993 self._phasecache.write()
994 if 'obsstore' in vars(self) and self.obsstore._new:
995 # XXX: transaction logic should be used here. But for
996 # now rewriting the whole file is good enough.
997 f = self.sopener('obsstore', 'wb', atomictemp=True)
998 try:
999 self.obsstore.flushmarkers(f)
1000 f.close()
1001 except: # re-raises
1002 f.discard()
1003 raise
986 for k, ce in self._filecache.items():
1004 for k, ce in self._filecache.items():
987 if k == 'dirstate':
1005 if k == 'dirstate':
988 continue
1006 continue
General Comments 0
You need to be logged in to leave comments. Login now