##// END OF EJS Templates
util: drop the 'unpacker' helper...
Pierre-Yves David -
r25211:22f4ce49 default
parent child Browse files
Show More
@@ -1,1252 +1,1252 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 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 The header is followed by the markers. Marker format depend of the version. See
66 The header is followed by the markers. Marker format depend of the version. See
67 comment associated with each format for details.
67 comment associated with each format for details.
68
68
69 """
69 """
70 import struct
70 import struct
71 import util, base85, node, parsers
71 import util, base85, node, parsers
72 import phases
72 import phases
73 from i18n import _
73 from i18n import _
74
74
75 _pack = struct.pack
75 _pack = struct.pack
76 _unpack = struct.unpack
76 _unpack = struct.unpack
77 _calcsize = struct.calcsize
77 _calcsize = struct.calcsize
78 propertycache = util.propertycache
78 propertycache = util.propertycache
79
79
80 # 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.
81 # 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.
82 _enabled = False
82 _enabled = False
83
83
84 # Options for obsolescence
84 # Options for obsolescence
85 createmarkersopt = 'createmarkers'
85 createmarkersopt = 'createmarkers'
86 allowunstableopt = 'allowunstable'
86 allowunstableopt = 'allowunstable'
87 exchangeopt = 'exchange'
87 exchangeopt = 'exchange'
88
88
89 ### obsolescence marker flag
89 ### obsolescence marker flag
90
90
91 ## bumpedfix flag
91 ## bumpedfix flag
92 #
92 #
93 # When a changeset A' succeed to a changeset A which became public, we call A'
93 # When a changeset A' succeed to a changeset A which became public, we call A'
94 # "bumped" because it's a successors of a public changesets
94 # "bumped" because it's a successors of a public changesets
95 #
95 #
96 # o A' (bumped)
96 # o A' (bumped)
97 # |`:
97 # |`:
98 # | o A
98 # | o A
99 # |/
99 # |/
100 # o Z
100 # o Z
101 #
101 #
102 # The way to solve this situation is to create a new changeset Ad as children
102 # The way to solve this situation is to create a new changeset Ad as children
103 # of A. This changeset have the same content than A'. So the diff from A to A'
103 # of A. This changeset have the same content than A'. So the diff from A to A'
104 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
104 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
105 #
105 #
106 # o Ad
106 # o Ad
107 # |`:
107 # |`:
108 # | x A'
108 # | x A'
109 # |'|
109 # |'|
110 # o | A
110 # o | A
111 # |/
111 # |/
112 # o Z
112 # o Z
113 #
113 #
114 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
114 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
115 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
115 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
116 # This flag mean that the successors express the changes between the public and
116 # This flag mean that the successors express the changes between the public and
117 # bumped version and fix the situation, breaking the transitivity of
117 # bumped version and fix the situation, breaking the transitivity of
118 # "bumped" here.
118 # "bumped" here.
119 bumpedfix = 1
119 bumpedfix = 1
120 usingsha256 = 2
120 usingsha256 = 2
121
121
122 ## Parsing and writing of version "0"
122 ## Parsing and writing of version "0"
123 #
123 #
124 # The header is followed by the markers. Each marker is made of:
124 # The header is followed by the markers. Each marker is made of:
125 #
125 #
126 # - 1 uint8 : number of new changesets "N", can be zero.
126 # - 1 uint8 : number of new changesets "N", can be zero.
127 #
127 #
128 # - 1 uint32: metadata size "M" in bytes.
128 # - 1 uint32: metadata size "M" in bytes.
129 #
129 #
130 # - 1 byte: a bit field. It is reserved for flags used in common
130 # - 1 byte: a bit field. It is reserved for flags used in common
131 # obsolete marker operations, to avoid repeated decoding of metadata
131 # obsolete marker operations, to avoid repeated decoding of metadata
132 # entries.
132 # entries.
133 #
133 #
134 # - 20 bytes: obsoleted changeset identifier.
134 # - 20 bytes: obsoleted changeset identifier.
135 #
135 #
136 # - N*20 bytes: new changesets identifiers.
136 # - N*20 bytes: new changesets identifiers.
137 #
137 #
138 # - M bytes: metadata as a sequence of nul-terminated strings. Each
138 # - M bytes: metadata as a sequence of nul-terminated strings. Each
139 # string contains a key and a value, separated by a colon ':', without
139 # string contains a key and a value, separated by a colon ':', without
140 # additional encoding. Keys cannot contain '\0' or ':' and values
140 # additional encoding. Keys cannot contain '\0' or ':' and values
141 # cannot contain '\0'.
141 # cannot contain '\0'.
142 _fm0version = 0
142 _fm0version = 0
143 _fm0fixed = '>BIB20s'
143 _fm0fixed = '>BIB20s'
144 _fm0node = '20s'
144 _fm0node = '20s'
145 _fm0fsize = _calcsize(_fm0fixed)
145 _fm0fsize = _calcsize(_fm0fixed)
146 _fm0fnodesize = _calcsize(_fm0node)
146 _fm0fnodesize = _calcsize(_fm0node)
147
147
148 def _fm0readmarkers(data, off):
148 def _fm0readmarkers(data, off):
149 # Loop on markers
149 # Loop on markers
150 l = len(data)
150 l = len(data)
151 while off + _fm0fsize <= l:
151 while off + _fm0fsize <= l:
152 # read fixed part
152 # read fixed part
153 cur = data[off:off + _fm0fsize]
153 cur = data[off:off + _fm0fsize]
154 off += _fm0fsize
154 off += _fm0fsize
155 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
155 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
156 # read replacement
156 # read replacement
157 sucs = ()
157 sucs = ()
158 if numsuc:
158 if numsuc:
159 s = (_fm0fnodesize * numsuc)
159 s = (_fm0fnodesize * numsuc)
160 cur = data[off:off + s]
160 cur = data[off:off + s]
161 sucs = _unpack(_fm0node * numsuc, cur)
161 sucs = _unpack(_fm0node * numsuc, cur)
162 off += s
162 off += s
163 # read metadata
163 # read metadata
164 # (metadata will be decoded on demand)
164 # (metadata will be decoded on demand)
165 metadata = data[off:off + mdsize]
165 metadata = data[off:off + mdsize]
166 if len(metadata) != mdsize:
166 if len(metadata) != mdsize:
167 raise util.Abort(_('parsing obsolete marker: metadata is too '
167 raise util.Abort(_('parsing obsolete marker: metadata is too '
168 'short, %d bytes expected, got %d')
168 'short, %d bytes expected, got %d')
169 % (mdsize, len(metadata)))
169 % (mdsize, len(metadata)))
170 off += mdsize
170 off += mdsize
171 metadata = _fm0decodemeta(metadata)
171 metadata = _fm0decodemeta(metadata)
172 try:
172 try:
173 when, offset = metadata.pop('date', '0 0').split(' ')
173 when, offset = metadata.pop('date', '0 0').split(' ')
174 date = float(when), int(offset)
174 date = float(when), int(offset)
175 except ValueError:
175 except ValueError:
176 date = (0., 0)
176 date = (0., 0)
177 parents = None
177 parents = None
178 if 'p2' in metadata:
178 if 'p2' in metadata:
179 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
179 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
180 elif 'p1' in metadata:
180 elif 'p1' in metadata:
181 parents = (metadata.pop('p1', None),)
181 parents = (metadata.pop('p1', None),)
182 elif 'p0' in metadata:
182 elif 'p0' in metadata:
183 parents = ()
183 parents = ()
184 if parents is not None:
184 if parents is not None:
185 try:
185 try:
186 parents = tuple(node.bin(p) for p in parents)
186 parents = tuple(node.bin(p) for p in parents)
187 # if parent content is not a nodeid, drop the data
187 # if parent content is not a nodeid, drop the data
188 for p in parents:
188 for p in parents:
189 if len(p) != 20:
189 if len(p) != 20:
190 parents = None
190 parents = None
191 break
191 break
192 except TypeError:
192 except TypeError:
193 # if content cannot be translated to nodeid drop the data.
193 # if content cannot be translated to nodeid drop the data.
194 parents = None
194 parents = None
195
195
196 metadata = tuple(sorted(metadata.iteritems()))
196 metadata = tuple(sorted(metadata.iteritems()))
197
197
198 yield (pre, sucs, flags, metadata, date, parents)
198 yield (pre, sucs, flags, metadata, date, parents)
199
199
200 def _fm0encodeonemarker(marker):
200 def _fm0encodeonemarker(marker):
201 pre, sucs, flags, metadata, date, parents = marker
201 pre, sucs, flags, metadata, date, parents = marker
202 if flags & usingsha256:
202 if flags & usingsha256:
203 raise util.Abort(_('cannot handle sha256 with old obsstore format'))
203 raise util.Abort(_('cannot handle sha256 with old obsstore format'))
204 metadata = dict(metadata)
204 metadata = dict(metadata)
205 time, tz = date
205 time, tz = date
206 metadata['date'] = '%r %i' % (time, tz)
206 metadata['date'] = '%r %i' % (time, tz)
207 if parents is not None:
207 if parents is not None:
208 if not parents:
208 if not parents:
209 # mark that we explicitly recorded no parents
209 # mark that we explicitly recorded no parents
210 metadata['p0'] = ''
210 metadata['p0'] = ''
211 for i, p in enumerate(parents):
211 for i, p in enumerate(parents):
212 metadata['p%i' % (i + 1)] = node.hex(p)
212 metadata['p%i' % (i + 1)] = node.hex(p)
213 metadata = _fm0encodemeta(metadata)
213 metadata = _fm0encodemeta(metadata)
214 numsuc = len(sucs)
214 numsuc = len(sucs)
215 format = _fm0fixed + (_fm0node * numsuc)
215 format = _fm0fixed + (_fm0node * numsuc)
216 data = [numsuc, len(metadata), flags, pre]
216 data = [numsuc, len(metadata), flags, pre]
217 data.extend(sucs)
217 data.extend(sucs)
218 return _pack(format, *data) + metadata
218 return _pack(format, *data) + metadata
219
219
220 def _fm0encodemeta(meta):
220 def _fm0encodemeta(meta):
221 """Return encoded metadata string to string mapping.
221 """Return encoded metadata string to string mapping.
222
222
223 Assume no ':' in key and no '\0' in both key and value."""
223 Assume no ':' in key and no '\0' in both key and value."""
224 for key, value in meta.iteritems():
224 for key, value in meta.iteritems():
225 if ':' in key or '\0' in key:
225 if ':' in key or '\0' in key:
226 raise ValueError("':' and '\0' are forbidden in metadata key'")
226 raise ValueError("':' and '\0' are forbidden in metadata key'")
227 if '\0' in value:
227 if '\0' in value:
228 raise ValueError("':' is forbidden in metadata value'")
228 raise ValueError("':' is forbidden in metadata value'")
229 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
229 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
230
230
231 def _fm0decodemeta(data):
231 def _fm0decodemeta(data):
232 """Return string to string dictionary from encoded version."""
232 """Return string to string dictionary from encoded version."""
233 d = {}
233 d = {}
234 for l in data.split('\0'):
234 for l in data.split('\0'):
235 if l:
235 if l:
236 key, value = l.split(':')
236 key, value = l.split(':')
237 d[key] = value
237 d[key] = value
238 return d
238 return d
239
239
240 ## Parsing and writing of version "1"
240 ## Parsing and writing of version "1"
241 #
241 #
242 # The header is followed by the markers. Each marker is made of:
242 # The header is followed by the markers. Each marker is made of:
243 #
243 #
244 # - uint32: total size of the marker (including this field)
244 # - uint32: total size of the marker (including this field)
245 #
245 #
246 # - float64: date in seconds since epoch
246 # - float64: date in seconds since epoch
247 #
247 #
248 # - int16: timezone offset in minutes
248 # - int16: timezone offset in minutes
249 #
249 #
250 # - uint16: a bit field. It is reserved for flags used in common
250 # - uint16: a bit field. It is reserved for flags used in common
251 # obsolete marker operations, to avoid repeated decoding of metadata
251 # obsolete marker operations, to avoid repeated decoding of metadata
252 # entries.
252 # entries.
253 #
253 #
254 # - uint8: number of successors "N", can be zero.
254 # - uint8: number of successors "N", can be zero.
255 #
255 #
256 # - uint8: number of parents "P", can be zero.
256 # - uint8: number of parents "P", can be zero.
257 #
257 #
258 # 0: parents data stored but no parent,
258 # 0: parents data stored but no parent,
259 # 1: one parent stored,
259 # 1: one parent stored,
260 # 2: two parents stored,
260 # 2: two parents stored,
261 # 3: no parent data stored
261 # 3: no parent data stored
262 #
262 #
263 # - uint8: number of metadata entries M
263 # - uint8: number of metadata entries M
264 #
264 #
265 # - 20 or 32 bytes: precursor changeset identifier.
265 # - 20 or 32 bytes: precursor changeset identifier.
266 #
266 #
267 # - N*(20 or 32) bytes: successors changesets identifiers.
267 # - N*(20 or 32) bytes: successors changesets identifiers.
268 #
268 #
269 # - P*(20 or 32) bytes: parents of the precursors changesets.
269 # - P*(20 or 32) bytes: parents of the precursors changesets.
270 #
270 #
271 # - M*(uint8, uint8): size of all metadata entries (key and value)
271 # - M*(uint8, uint8): size of all metadata entries (key and value)
272 #
272 #
273 # - remaining bytes: the metadata, each (key, value) pair after the other.
273 # - remaining bytes: the metadata, each (key, value) pair after the other.
274 _fm1version = 1
274 _fm1version = 1
275 _fm1fixed = '>IdhHBBB20s'
275 _fm1fixed = '>IdhHBBB20s'
276 _fm1nodesha1 = '20s'
276 _fm1nodesha1 = '20s'
277 _fm1nodesha256 = '32s'
277 _fm1nodesha256 = '32s'
278 _fm1nodesha1size = _calcsize(_fm1nodesha1)
278 _fm1nodesha1size = _calcsize(_fm1nodesha1)
279 _fm1nodesha256size = _calcsize(_fm1nodesha256)
279 _fm1nodesha256size = _calcsize(_fm1nodesha256)
280 _fm1fsize = _calcsize(_fm1fixed)
280 _fm1fsize = _calcsize(_fm1fixed)
281 _fm1parentnone = 3
281 _fm1parentnone = 3
282 _fm1parentshift = 14
282 _fm1parentshift = 14
283 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
283 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
284 _fm1metapair = 'BB'
284 _fm1metapair = 'BB'
285 _fm1metapairsize = _calcsize('BB')
285 _fm1metapairsize = _calcsize('BB')
286
286
287 def _fm1purereadmarkers(data, off):
287 def _fm1purereadmarkers(data, off):
288 # make some global constants local for performance
288 # make some global constants local for performance
289 noneflag = _fm1parentnone
289 noneflag = _fm1parentnone
290 sha2flag = usingsha256
290 sha2flag = usingsha256
291 sha1size = _fm1nodesha1size
291 sha1size = _fm1nodesha1size
292 sha2size = _fm1nodesha256size
292 sha2size = _fm1nodesha256size
293 sha1fmt = _fm1nodesha1
293 sha1fmt = _fm1nodesha1
294 sha2fmt = _fm1nodesha256
294 sha2fmt = _fm1nodesha256
295 metasize = _fm1metapairsize
295 metasize = _fm1metapairsize
296 metafmt = _fm1metapair
296 metafmt = _fm1metapair
297 fsize = _fm1fsize
297 fsize = _fm1fsize
298 unpack = _unpack
298 unpack = _unpack
299
299
300 # Loop on markers
300 # Loop on markers
301 stop = len(data) - _fm1fsize
301 stop = len(data) - _fm1fsize
302 ufixed = util.unpacker(_fm1fixed)
302 ufixed = struct.Struct(_fm1fixed).unpack
303
303
304 while off <= stop:
304 while off <= stop:
305 # read fixed part
305 # read fixed part
306 o1 = off + fsize
306 o1 = off + fsize
307 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
307 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
308
308
309 if flags & sha2flag:
309 if flags & sha2flag:
310 # FIXME: prec was read as a SHA1, needs to be amended
310 # FIXME: prec was read as a SHA1, needs to be amended
311
311
312 # read 0 or more successors
312 # read 0 or more successors
313 if numsuc == 1:
313 if numsuc == 1:
314 o2 = o1 + sha2size
314 o2 = o1 + sha2size
315 sucs = (data[o1:o2],)
315 sucs = (data[o1:o2],)
316 else:
316 else:
317 o2 = o1 + sha2size * numsuc
317 o2 = o1 + sha2size * numsuc
318 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
318 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
319
319
320 # read parents
320 # read parents
321 if numpar == noneflag:
321 if numpar == noneflag:
322 o3 = o2
322 o3 = o2
323 parents = None
323 parents = None
324 elif numpar == 1:
324 elif numpar == 1:
325 o3 = o2 + sha2size
325 o3 = o2 + sha2size
326 parents = (data[o2:o3],)
326 parents = (data[o2:o3],)
327 else:
327 else:
328 o3 = o2 + sha2size * numpar
328 o3 = o2 + sha2size * numpar
329 parents = unpack(sha2fmt * numpar, data[o2:o3])
329 parents = unpack(sha2fmt * numpar, data[o2:o3])
330 else:
330 else:
331 # read 0 or more successors
331 # read 0 or more successors
332 if numsuc == 1:
332 if numsuc == 1:
333 o2 = o1 + sha1size
333 o2 = o1 + sha1size
334 sucs = (data[o1:o2],)
334 sucs = (data[o1:o2],)
335 else:
335 else:
336 o2 = o1 + sha1size * numsuc
336 o2 = o1 + sha1size * numsuc
337 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
337 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
338
338
339 # read parents
339 # read parents
340 if numpar == noneflag:
340 if numpar == noneflag:
341 o3 = o2
341 o3 = o2
342 parents = None
342 parents = None
343 elif numpar == 1:
343 elif numpar == 1:
344 o3 = o2 + sha1size
344 o3 = o2 + sha1size
345 parents = (data[o2:o3],)
345 parents = (data[o2:o3],)
346 else:
346 else:
347 o3 = o2 + sha1size * numpar
347 o3 = o2 + sha1size * numpar
348 parents = unpack(sha1fmt * numpar, data[o2:o3])
348 parents = unpack(sha1fmt * numpar, data[o2:o3])
349
349
350 # read metadata
350 # read metadata
351 off = o3 + metasize * nummeta
351 off = o3 + metasize * nummeta
352 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
352 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
353 metadata = []
353 metadata = []
354 for idx in xrange(0, len(metapairsize), 2):
354 for idx in xrange(0, len(metapairsize), 2):
355 o1 = off + metapairsize[idx]
355 o1 = off + metapairsize[idx]
356 o2 = o1 + metapairsize[idx + 1]
356 o2 = o1 + metapairsize[idx + 1]
357 metadata.append((data[off:o1], data[o1:o2]))
357 metadata.append((data[off:o1], data[o1:o2]))
358 off = o2
358 off = o2
359
359
360 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
360 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
361
361
362 def _fm1encodeonemarker(marker):
362 def _fm1encodeonemarker(marker):
363 pre, sucs, flags, metadata, date, parents = marker
363 pre, sucs, flags, metadata, date, parents = marker
364 # determine node size
364 # determine node size
365 _fm1node = _fm1nodesha1
365 _fm1node = _fm1nodesha1
366 if flags & usingsha256:
366 if flags & usingsha256:
367 _fm1node = _fm1nodesha256
367 _fm1node = _fm1nodesha256
368 numsuc = len(sucs)
368 numsuc = len(sucs)
369 numextranodes = numsuc
369 numextranodes = numsuc
370 if parents is None:
370 if parents is None:
371 numpar = _fm1parentnone
371 numpar = _fm1parentnone
372 else:
372 else:
373 numpar = len(parents)
373 numpar = len(parents)
374 numextranodes += numpar
374 numextranodes += numpar
375 formatnodes = _fm1node * numextranodes
375 formatnodes = _fm1node * numextranodes
376 formatmeta = _fm1metapair * len(metadata)
376 formatmeta = _fm1metapair * len(metadata)
377 format = _fm1fixed + formatnodes + formatmeta
377 format = _fm1fixed + formatnodes + formatmeta
378 # tz is stored in minutes so we divide by 60
378 # tz is stored in minutes so we divide by 60
379 tz = date[1]//60
379 tz = date[1]//60
380 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
380 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
381 data.extend(sucs)
381 data.extend(sucs)
382 if parents is not None:
382 if parents is not None:
383 data.extend(parents)
383 data.extend(parents)
384 totalsize = _calcsize(format)
384 totalsize = _calcsize(format)
385 for key, value in metadata:
385 for key, value in metadata:
386 lk = len(key)
386 lk = len(key)
387 lv = len(value)
387 lv = len(value)
388 data.append(lk)
388 data.append(lk)
389 data.append(lv)
389 data.append(lv)
390 totalsize += lk + lv
390 totalsize += lk + lv
391 data[0] = totalsize
391 data[0] = totalsize
392 data = [_pack(format, *data)]
392 data = [_pack(format, *data)]
393 for key, value in metadata:
393 for key, value in metadata:
394 data.append(key)
394 data.append(key)
395 data.append(value)
395 data.append(value)
396 return ''.join(data)
396 return ''.join(data)
397
397
398 def _fm1readmarkers(data, off):
398 def _fm1readmarkers(data, off):
399 native = getattr(parsers, 'fm1readmarkers', None)
399 native = getattr(parsers, 'fm1readmarkers', None)
400 if not native:
400 if not native:
401 return _fm1purereadmarkers(data, off)
401 return _fm1purereadmarkers(data, off)
402 stop = len(data) - _fm1fsize
402 stop = len(data) - _fm1fsize
403 return native(data, off, stop)
403 return native(data, off, stop)
404
404
405 # mapping to read/write various marker formats
405 # mapping to read/write various marker formats
406 # <version> -> (decoder, encoder)
406 # <version> -> (decoder, encoder)
407 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
407 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
408 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
408 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
409
409
410 @util.nogc
410 @util.nogc
411 def _readmarkers(data):
411 def _readmarkers(data):
412 """Read and enumerate markers from raw data"""
412 """Read and enumerate markers from raw data"""
413 off = 0
413 off = 0
414 diskversion = _unpack('>B', data[off:off + 1])[0]
414 diskversion = _unpack('>B', data[off:off + 1])[0]
415 off += 1
415 off += 1
416 if diskversion not in formats:
416 if diskversion not in formats:
417 raise util.Abort(_('parsing obsolete marker: unknown version %r')
417 raise util.Abort(_('parsing obsolete marker: unknown version %r')
418 % diskversion)
418 % diskversion)
419 return diskversion, formats[diskversion][0](data, off)
419 return diskversion, formats[diskversion][0](data, off)
420
420
421 def encodemarkers(markers, addheader=False, version=_fm0version):
421 def encodemarkers(markers, addheader=False, version=_fm0version):
422 # Kept separate from flushmarkers(), it will be reused for
422 # Kept separate from flushmarkers(), it will be reused for
423 # markers exchange.
423 # markers exchange.
424 encodeone = formats[version][1]
424 encodeone = formats[version][1]
425 if addheader:
425 if addheader:
426 yield _pack('>B', version)
426 yield _pack('>B', version)
427 for marker in markers:
427 for marker in markers:
428 yield encodeone(marker)
428 yield encodeone(marker)
429
429
430
430
431 class marker(object):
431 class marker(object):
432 """Wrap obsolete marker raw data"""
432 """Wrap obsolete marker raw data"""
433
433
434 def __init__(self, repo, data):
434 def __init__(self, repo, data):
435 # the repo argument will be used to create changectx in later version
435 # the repo argument will be used to create changectx in later version
436 self._repo = repo
436 self._repo = repo
437 self._data = data
437 self._data = data
438 self._decodedmeta = None
438 self._decodedmeta = None
439
439
440 def __hash__(self):
440 def __hash__(self):
441 return hash(self._data)
441 return hash(self._data)
442
442
443 def __eq__(self, other):
443 def __eq__(self, other):
444 if type(other) != type(self):
444 if type(other) != type(self):
445 return False
445 return False
446 return self._data == other._data
446 return self._data == other._data
447
447
448 def precnode(self):
448 def precnode(self):
449 """Precursor changeset node identifier"""
449 """Precursor changeset node identifier"""
450 return self._data[0]
450 return self._data[0]
451
451
452 def succnodes(self):
452 def succnodes(self):
453 """List of successor changesets node identifiers"""
453 """List of successor changesets node identifiers"""
454 return self._data[1]
454 return self._data[1]
455
455
456 def parentnodes(self):
456 def parentnodes(self):
457 """Parents of the precursors (None if not recorded)"""
457 """Parents of the precursors (None if not recorded)"""
458 return self._data[5]
458 return self._data[5]
459
459
460 def metadata(self):
460 def metadata(self):
461 """Decoded metadata dictionary"""
461 """Decoded metadata dictionary"""
462 return dict(self._data[3])
462 return dict(self._data[3])
463
463
464 def date(self):
464 def date(self):
465 """Creation date as (unixtime, offset)"""
465 """Creation date as (unixtime, offset)"""
466 return self._data[4]
466 return self._data[4]
467
467
468 def flags(self):
468 def flags(self):
469 """The flags field of the marker"""
469 """The flags field of the marker"""
470 return self._data[2]
470 return self._data[2]
471
471
472 @util.nogc
472 @util.nogc
473 def _addsuccessors(successors, markers):
473 def _addsuccessors(successors, markers):
474 for mark in markers:
474 for mark in markers:
475 successors.setdefault(mark[0], set()).add(mark)
475 successors.setdefault(mark[0], set()).add(mark)
476
476
477 @util.nogc
477 @util.nogc
478 def _addprecursors(precursors, markers):
478 def _addprecursors(precursors, markers):
479 for mark in markers:
479 for mark in markers:
480 for suc in mark[1]:
480 for suc in mark[1]:
481 precursors.setdefault(suc, set()).add(mark)
481 precursors.setdefault(suc, set()).add(mark)
482
482
483 @util.nogc
483 @util.nogc
484 def _addchildren(children, markers):
484 def _addchildren(children, markers):
485 for mark in markers:
485 for mark in markers:
486 parents = mark[5]
486 parents = mark[5]
487 if parents is not None:
487 if parents is not None:
488 for p in parents:
488 for p in parents:
489 children.setdefault(p, set()).add(mark)
489 children.setdefault(p, set()).add(mark)
490
490
491 def _checkinvalidmarkers(markers):
491 def _checkinvalidmarkers(markers):
492 """search for marker with invalid data and raise error if needed
492 """search for marker with invalid data and raise error if needed
493
493
494 Exist as a separated function to allow the evolve extension for a more
494 Exist as a separated function to allow the evolve extension for a more
495 subtle handling.
495 subtle handling.
496 """
496 """
497 for mark in markers:
497 for mark in markers:
498 if node.nullid in mark[1]:
498 if node.nullid in mark[1]:
499 raise util.Abort(_('bad obsolescence marker detected: '
499 raise util.Abort(_('bad obsolescence marker detected: '
500 'invalid successors nullid'))
500 'invalid successors nullid'))
501
501
502 class obsstore(object):
502 class obsstore(object):
503 """Store obsolete markers
503 """Store obsolete markers
504
504
505 Markers can be accessed with two mappings:
505 Markers can be accessed with two mappings:
506 - precursors[x] -> set(markers on precursors edges of x)
506 - precursors[x] -> set(markers on precursors edges of x)
507 - successors[x] -> set(markers on successors edges of x)
507 - successors[x] -> set(markers on successors edges of x)
508 - children[x] -> set(markers on precursors edges of children(x)
508 - children[x] -> set(markers on precursors edges of children(x)
509 """
509 """
510
510
511 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
511 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
512 # prec: nodeid, precursor changesets
512 # prec: nodeid, precursor changesets
513 # succs: tuple of nodeid, successor changesets (0-N length)
513 # succs: tuple of nodeid, successor changesets (0-N length)
514 # flag: integer, flag field carrying modifier for the markers (see doc)
514 # flag: integer, flag field carrying modifier for the markers (see doc)
515 # meta: binary blob, encoded metadata dictionary
515 # meta: binary blob, encoded metadata dictionary
516 # date: (float, int) tuple, date of marker creation
516 # date: (float, int) tuple, date of marker creation
517 # parents: (tuple of nodeid) or None, parents of precursors
517 # parents: (tuple of nodeid) or None, parents of precursors
518 # None is used when no data has been recorded
518 # None is used when no data has been recorded
519
519
520 def __init__(self, sopener, defaultformat=_fm1version, readonly=False):
520 def __init__(self, sopener, defaultformat=_fm1version, readonly=False):
521 # caches for various obsolescence related cache
521 # caches for various obsolescence related cache
522 self.caches = {}
522 self.caches = {}
523 self._all = []
523 self._all = []
524 self.sopener = sopener
524 self.sopener = sopener
525 data = sopener.tryread('obsstore')
525 data = sopener.tryread('obsstore')
526 self._version = defaultformat
526 self._version = defaultformat
527 self._readonly = readonly
527 self._readonly = readonly
528 if data:
528 if data:
529 self._version, markers = _readmarkers(data)
529 self._version, markers = _readmarkers(data)
530 self._addmarkers(markers)
530 self._addmarkers(markers)
531
531
532 def __iter__(self):
532 def __iter__(self):
533 return iter(self._all)
533 return iter(self._all)
534
534
535 def __len__(self):
535 def __len__(self):
536 return len(self._all)
536 return len(self._all)
537
537
538 def __nonzero__(self):
538 def __nonzero__(self):
539 return bool(self._all)
539 return bool(self._all)
540
540
541 def create(self, transaction, prec, succs=(), flag=0, parents=None,
541 def create(self, transaction, prec, succs=(), flag=0, parents=None,
542 date=None, metadata=None):
542 date=None, metadata=None):
543 """obsolete: add a new obsolete marker
543 """obsolete: add a new obsolete marker
544
544
545 * ensuring it is hashable
545 * ensuring it is hashable
546 * check mandatory metadata
546 * check mandatory metadata
547 * encode metadata
547 * encode metadata
548
548
549 If you are a human writing code creating marker you want to use the
549 If you are a human writing code creating marker you want to use the
550 `createmarkers` function in this module instead.
550 `createmarkers` function in this module instead.
551
551
552 return True if a new marker have been added, False if the markers
552 return True if a new marker have been added, False if the markers
553 already existed (no op).
553 already existed (no op).
554 """
554 """
555 if metadata is None:
555 if metadata is None:
556 metadata = {}
556 metadata = {}
557 if date is None:
557 if date is None:
558 if 'date' in metadata:
558 if 'date' in metadata:
559 # as a courtesy for out-of-tree extensions
559 # as a courtesy for out-of-tree extensions
560 date = util.parsedate(metadata.pop('date'))
560 date = util.parsedate(metadata.pop('date'))
561 else:
561 else:
562 date = util.makedate()
562 date = util.makedate()
563 if len(prec) != 20:
563 if len(prec) != 20:
564 raise ValueError(prec)
564 raise ValueError(prec)
565 for succ in succs:
565 for succ in succs:
566 if len(succ) != 20:
566 if len(succ) != 20:
567 raise ValueError(succ)
567 raise ValueError(succ)
568 if prec in succs:
568 if prec in succs:
569 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
569 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
570
570
571 metadata = tuple(sorted(metadata.iteritems()))
571 metadata = tuple(sorted(metadata.iteritems()))
572
572
573 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
573 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
574 return bool(self.add(transaction, [marker]))
574 return bool(self.add(transaction, [marker]))
575
575
576 def add(self, transaction, markers):
576 def add(self, transaction, markers):
577 """Add new markers to the store
577 """Add new markers to the store
578
578
579 Take care of filtering duplicate.
579 Take care of filtering duplicate.
580 Return the number of new marker."""
580 Return the number of new marker."""
581 if self._readonly:
581 if self._readonly:
582 raise util.Abort('creating obsolete markers is not enabled on this '
582 raise util.Abort('creating obsolete markers is not enabled on this '
583 'repo')
583 'repo')
584 known = set(self._all)
584 known = set(self._all)
585 new = []
585 new = []
586 for m in markers:
586 for m in markers:
587 if m not in known:
587 if m not in known:
588 known.add(m)
588 known.add(m)
589 new.append(m)
589 new.append(m)
590 if new:
590 if new:
591 f = self.sopener('obsstore', 'ab')
591 f = self.sopener('obsstore', 'ab')
592 try:
592 try:
593 offset = f.tell()
593 offset = f.tell()
594 transaction.add('obsstore', offset)
594 transaction.add('obsstore', offset)
595 # offset == 0: new file - add the version header
595 # offset == 0: new file - add the version header
596 for bytes in encodemarkers(new, offset == 0, self._version):
596 for bytes in encodemarkers(new, offset == 0, self._version):
597 f.write(bytes)
597 f.write(bytes)
598 finally:
598 finally:
599 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
599 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
600 # call 'filecacheentry.refresh()' here
600 # call 'filecacheentry.refresh()' here
601 f.close()
601 f.close()
602 self._addmarkers(new)
602 self._addmarkers(new)
603 # new marker *may* have changed several set. invalidate the cache.
603 # new marker *may* have changed several set. invalidate the cache.
604 self.caches.clear()
604 self.caches.clear()
605 # records the number of new markers for the transaction hooks
605 # records the number of new markers for the transaction hooks
606 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
606 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
607 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
607 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
608 return len(new)
608 return len(new)
609
609
610 def mergemarkers(self, transaction, data):
610 def mergemarkers(self, transaction, data):
611 """merge a binary stream of markers inside the obsstore
611 """merge a binary stream of markers inside the obsstore
612
612
613 Returns the number of new markers added."""
613 Returns the number of new markers added."""
614 version, markers = _readmarkers(data)
614 version, markers = _readmarkers(data)
615 return self.add(transaction, markers)
615 return self.add(transaction, markers)
616
616
617 @propertycache
617 @propertycache
618 def successors(self):
618 def successors(self):
619 successors = {}
619 successors = {}
620 _addsuccessors(successors, self._all)
620 _addsuccessors(successors, self._all)
621 return successors
621 return successors
622
622
623 @propertycache
623 @propertycache
624 def precursors(self):
624 def precursors(self):
625 precursors = {}
625 precursors = {}
626 _addprecursors(precursors, self._all)
626 _addprecursors(precursors, self._all)
627 return precursors
627 return precursors
628
628
629 @propertycache
629 @propertycache
630 def children(self):
630 def children(self):
631 children = {}
631 children = {}
632 _addchildren(children, self._all)
632 _addchildren(children, self._all)
633 return children
633 return children
634
634
635 def _cached(self, attr):
635 def _cached(self, attr):
636 return attr in self.__dict__
636 return attr in self.__dict__
637
637
638 def _addmarkers(self, markers):
638 def _addmarkers(self, markers):
639 markers = list(markers) # to allow repeated iteration
639 markers = list(markers) # to allow repeated iteration
640 self._all.extend(markers)
640 self._all.extend(markers)
641 if self._cached('successors'):
641 if self._cached('successors'):
642 _addsuccessors(self.successors, markers)
642 _addsuccessors(self.successors, markers)
643 if self._cached('precursors'):
643 if self._cached('precursors'):
644 _addprecursors(self.precursors, markers)
644 _addprecursors(self.precursors, markers)
645 if self._cached('children'):
645 if self._cached('children'):
646 _addchildren(self.children, markers)
646 _addchildren(self.children, markers)
647 _checkinvalidmarkers(markers)
647 _checkinvalidmarkers(markers)
648
648
649 def relevantmarkers(self, nodes):
649 def relevantmarkers(self, nodes):
650 """return a set of all obsolescence markers relevant to a set of nodes.
650 """return a set of all obsolescence markers relevant to a set of nodes.
651
651
652 "relevant" to a set of nodes mean:
652 "relevant" to a set of nodes mean:
653
653
654 - marker that use this changeset as successor
654 - marker that use this changeset as successor
655 - prune marker of direct children on this changeset
655 - prune marker of direct children on this changeset
656 - recursive application of the two rules on precursors of these markers
656 - recursive application of the two rules on precursors of these markers
657
657
658 It is a set so you cannot rely on order."""
658 It is a set so you cannot rely on order."""
659
659
660 pendingnodes = set(nodes)
660 pendingnodes = set(nodes)
661 seenmarkers = set()
661 seenmarkers = set()
662 seennodes = set(pendingnodes)
662 seennodes = set(pendingnodes)
663 precursorsmarkers = self.precursors
663 precursorsmarkers = self.precursors
664 children = self.children
664 children = self.children
665 while pendingnodes:
665 while pendingnodes:
666 direct = set()
666 direct = set()
667 for current in pendingnodes:
667 for current in pendingnodes:
668 direct.update(precursorsmarkers.get(current, ()))
668 direct.update(precursorsmarkers.get(current, ()))
669 pruned = [m for m in children.get(current, ()) if not m[1]]
669 pruned = [m for m in children.get(current, ()) if not m[1]]
670 direct.update(pruned)
670 direct.update(pruned)
671 direct -= seenmarkers
671 direct -= seenmarkers
672 pendingnodes = set([m[0] for m in direct])
672 pendingnodes = set([m[0] for m in direct])
673 seenmarkers |= direct
673 seenmarkers |= direct
674 pendingnodes -= seennodes
674 pendingnodes -= seennodes
675 seennodes |= pendingnodes
675 seennodes |= pendingnodes
676 return seenmarkers
676 return seenmarkers
677
677
678 def commonversion(versions):
678 def commonversion(versions):
679 """Return the newest version listed in both versions and our local formats.
679 """Return the newest version listed in both versions and our local formats.
680
680
681 Returns None if no common version exists.
681 Returns None if no common version exists.
682 """
682 """
683 versions.sort(reverse=True)
683 versions.sort(reverse=True)
684 # search for highest version known on both side
684 # search for highest version known on both side
685 for v in versions:
685 for v in versions:
686 if v in formats:
686 if v in formats:
687 return v
687 return v
688 return None
688 return None
689
689
690 # arbitrary picked to fit into 8K limit from HTTP server
690 # arbitrary picked to fit into 8K limit from HTTP server
691 # you have to take in account:
691 # you have to take in account:
692 # - the version header
692 # - the version header
693 # - the base85 encoding
693 # - the base85 encoding
694 _maxpayload = 5300
694 _maxpayload = 5300
695
695
696 def _pushkeyescape(markers):
696 def _pushkeyescape(markers):
697 """encode markers into a dict suitable for pushkey exchange
697 """encode markers into a dict suitable for pushkey exchange
698
698
699 - binary data is base85 encoded
699 - binary data is base85 encoded
700 - split in chunks smaller than 5300 bytes"""
700 - split in chunks smaller than 5300 bytes"""
701 keys = {}
701 keys = {}
702 parts = []
702 parts = []
703 currentlen = _maxpayload * 2 # ensure we create a new part
703 currentlen = _maxpayload * 2 # ensure we create a new part
704 for marker in markers:
704 for marker in markers:
705 nextdata = _fm0encodeonemarker(marker)
705 nextdata = _fm0encodeonemarker(marker)
706 if (len(nextdata) + currentlen > _maxpayload):
706 if (len(nextdata) + currentlen > _maxpayload):
707 currentpart = []
707 currentpart = []
708 currentlen = 0
708 currentlen = 0
709 parts.append(currentpart)
709 parts.append(currentpart)
710 currentpart.append(nextdata)
710 currentpart.append(nextdata)
711 currentlen += len(nextdata)
711 currentlen += len(nextdata)
712 for idx, part in enumerate(reversed(parts)):
712 for idx, part in enumerate(reversed(parts)):
713 data = ''.join([_pack('>B', _fm0version)] + part)
713 data = ''.join([_pack('>B', _fm0version)] + part)
714 keys['dump%i' % idx] = base85.b85encode(data)
714 keys['dump%i' % idx] = base85.b85encode(data)
715 return keys
715 return keys
716
716
717 def listmarkers(repo):
717 def listmarkers(repo):
718 """List markers over pushkey"""
718 """List markers over pushkey"""
719 if not repo.obsstore:
719 if not repo.obsstore:
720 return {}
720 return {}
721 return _pushkeyescape(sorted(repo.obsstore))
721 return _pushkeyescape(sorted(repo.obsstore))
722
722
723 def pushmarker(repo, key, old, new):
723 def pushmarker(repo, key, old, new):
724 """Push markers over pushkey"""
724 """Push markers over pushkey"""
725 if not key.startswith('dump'):
725 if not key.startswith('dump'):
726 repo.ui.warn(_('unknown key: %r') % key)
726 repo.ui.warn(_('unknown key: %r') % key)
727 return 0
727 return 0
728 if old:
728 if old:
729 repo.ui.warn(_('unexpected old value for %r') % key)
729 repo.ui.warn(_('unexpected old value for %r') % key)
730 return 0
730 return 0
731 data = base85.b85decode(new)
731 data = base85.b85decode(new)
732 lock = repo.lock()
732 lock = repo.lock()
733 try:
733 try:
734 tr = repo.transaction('pushkey: obsolete markers')
734 tr = repo.transaction('pushkey: obsolete markers')
735 try:
735 try:
736 repo.obsstore.mergemarkers(tr, data)
736 repo.obsstore.mergemarkers(tr, data)
737 tr.close()
737 tr.close()
738 return 1
738 return 1
739 finally:
739 finally:
740 tr.release()
740 tr.release()
741 finally:
741 finally:
742 lock.release()
742 lock.release()
743
743
744 def getmarkers(repo, nodes=None):
744 def getmarkers(repo, nodes=None):
745 """returns markers known in a repository
745 """returns markers known in a repository
746
746
747 If <nodes> is specified, only markers "relevant" to those nodes are are
747 If <nodes> is specified, only markers "relevant" to those nodes are are
748 returned"""
748 returned"""
749 if nodes is None:
749 if nodes is None:
750 rawmarkers = repo.obsstore
750 rawmarkers = repo.obsstore
751 else:
751 else:
752 rawmarkers = repo.obsstore.relevantmarkers(nodes)
752 rawmarkers = repo.obsstore.relevantmarkers(nodes)
753
753
754 for markerdata in rawmarkers:
754 for markerdata in rawmarkers:
755 yield marker(repo, markerdata)
755 yield marker(repo, markerdata)
756
756
757 def relevantmarkers(repo, node):
757 def relevantmarkers(repo, node):
758 """all obsolete markers relevant to some revision"""
758 """all obsolete markers relevant to some revision"""
759 for markerdata in repo.obsstore.relevantmarkers(node):
759 for markerdata in repo.obsstore.relevantmarkers(node):
760 yield marker(repo, markerdata)
760 yield marker(repo, markerdata)
761
761
762
762
763 def precursormarkers(ctx):
763 def precursormarkers(ctx):
764 """obsolete marker marking this changeset as a successors"""
764 """obsolete marker marking this changeset as a successors"""
765 for data in ctx.repo().obsstore.precursors.get(ctx.node(), ()):
765 for data in ctx.repo().obsstore.precursors.get(ctx.node(), ()):
766 yield marker(ctx.repo(), data)
766 yield marker(ctx.repo(), data)
767
767
768 def successormarkers(ctx):
768 def successormarkers(ctx):
769 """obsolete marker making this changeset obsolete"""
769 """obsolete marker making this changeset obsolete"""
770 for data in ctx.repo().obsstore.successors.get(ctx.node(), ()):
770 for data in ctx.repo().obsstore.successors.get(ctx.node(), ()):
771 yield marker(ctx.repo(), data)
771 yield marker(ctx.repo(), data)
772
772
773 def allsuccessors(obsstore, nodes, ignoreflags=0):
773 def allsuccessors(obsstore, nodes, ignoreflags=0):
774 """Yield node for every successor of <nodes>.
774 """Yield node for every successor of <nodes>.
775
775
776 Some successors may be unknown locally.
776 Some successors may be unknown locally.
777
777
778 This is a linear yield unsuited to detecting split changesets. It includes
778 This is a linear yield unsuited to detecting split changesets. It includes
779 initial nodes too."""
779 initial nodes too."""
780 remaining = set(nodes)
780 remaining = set(nodes)
781 seen = set(remaining)
781 seen = set(remaining)
782 while remaining:
782 while remaining:
783 current = remaining.pop()
783 current = remaining.pop()
784 yield current
784 yield current
785 for mark in obsstore.successors.get(current, ()):
785 for mark in obsstore.successors.get(current, ()):
786 # ignore marker flagged with specified flag
786 # ignore marker flagged with specified flag
787 if mark[2] & ignoreflags:
787 if mark[2] & ignoreflags:
788 continue
788 continue
789 for suc in mark[1]:
789 for suc in mark[1]:
790 if suc not in seen:
790 if suc not in seen:
791 seen.add(suc)
791 seen.add(suc)
792 remaining.add(suc)
792 remaining.add(suc)
793
793
794 def allprecursors(obsstore, nodes, ignoreflags=0):
794 def allprecursors(obsstore, nodes, ignoreflags=0):
795 """Yield node for every precursors of <nodes>.
795 """Yield node for every precursors of <nodes>.
796
796
797 Some precursors may be unknown locally.
797 Some precursors may be unknown locally.
798
798
799 This is a linear yield unsuited to detecting folded changesets. It includes
799 This is a linear yield unsuited to detecting folded changesets. It includes
800 initial nodes too."""
800 initial nodes too."""
801
801
802 remaining = set(nodes)
802 remaining = set(nodes)
803 seen = set(remaining)
803 seen = set(remaining)
804 while remaining:
804 while remaining:
805 current = remaining.pop()
805 current = remaining.pop()
806 yield current
806 yield current
807 for mark in obsstore.precursors.get(current, ()):
807 for mark in obsstore.precursors.get(current, ()):
808 # ignore marker flagged with specified flag
808 # ignore marker flagged with specified flag
809 if mark[2] & ignoreflags:
809 if mark[2] & ignoreflags:
810 continue
810 continue
811 suc = mark[0]
811 suc = mark[0]
812 if suc not in seen:
812 if suc not in seen:
813 seen.add(suc)
813 seen.add(suc)
814 remaining.add(suc)
814 remaining.add(suc)
815
815
816 def foreground(repo, nodes):
816 def foreground(repo, nodes):
817 """return all nodes in the "foreground" of other node
817 """return all nodes in the "foreground" of other node
818
818
819 The foreground of a revision is anything reachable using parent -> children
819 The foreground of a revision is anything reachable using parent -> children
820 or precursor -> successor relation. It is very similar to "descendant" but
820 or precursor -> successor relation. It is very similar to "descendant" but
821 augmented with obsolescence information.
821 augmented with obsolescence information.
822
822
823 Beware that possible obsolescence cycle may result if complex situation.
823 Beware that possible obsolescence cycle may result if complex situation.
824 """
824 """
825 repo = repo.unfiltered()
825 repo = repo.unfiltered()
826 foreground = set(repo.set('%ln::', nodes))
826 foreground = set(repo.set('%ln::', nodes))
827 if repo.obsstore:
827 if repo.obsstore:
828 # We only need this complicated logic if there is obsolescence
828 # We only need this complicated logic if there is obsolescence
829 # XXX will probably deserve an optimised revset.
829 # XXX will probably deserve an optimised revset.
830 nm = repo.changelog.nodemap
830 nm = repo.changelog.nodemap
831 plen = -1
831 plen = -1
832 # compute the whole set of successors or descendants
832 # compute the whole set of successors or descendants
833 while len(foreground) != plen:
833 while len(foreground) != plen:
834 plen = len(foreground)
834 plen = len(foreground)
835 succs = set(c.node() for c in foreground)
835 succs = set(c.node() for c in foreground)
836 mutable = [c.node() for c in foreground if c.mutable()]
836 mutable = [c.node() for c in foreground if c.mutable()]
837 succs.update(allsuccessors(repo.obsstore, mutable))
837 succs.update(allsuccessors(repo.obsstore, mutable))
838 known = (n for n in succs if n in nm)
838 known = (n for n in succs if n in nm)
839 foreground = set(repo.set('%ln::', known))
839 foreground = set(repo.set('%ln::', known))
840 return set(c.node() for c in foreground)
840 return set(c.node() for c in foreground)
841
841
842
842
843 def successorssets(repo, initialnode, cache=None):
843 def successorssets(repo, initialnode, cache=None):
844 """Return all set of successors of initial nodes
844 """Return all set of successors of initial nodes
845
845
846 The successors set of a changeset A are a group of revisions that succeed
846 The successors set of a changeset A are a group of revisions that succeed
847 A. It succeeds A as a consistent whole, each revision being only a partial
847 A. It succeeds A as a consistent whole, each revision being only a partial
848 replacement. The successors set contains non-obsolete changesets only.
848 replacement. The successors set contains non-obsolete changesets only.
849
849
850 This function returns the full list of successor sets which is why it
850 This function returns the full list of successor sets which is why it
851 returns a list of tuples and not just a single tuple. Each tuple is a valid
851 returns a list of tuples and not just a single tuple. Each tuple is a valid
852 successors set. Not that (A,) may be a valid successors set for changeset A
852 successors set. Not that (A,) may be a valid successors set for changeset A
853 (see below).
853 (see below).
854
854
855 In most cases, a changeset A will have a single element (e.g. the changeset
855 In most cases, a changeset A will have a single element (e.g. the changeset
856 A is replaced by A') in its successors set. Though, it is also common for a
856 A is replaced by A') in its successors set. Though, it is also common for a
857 changeset A to have no elements in its successor set (e.g. the changeset
857 changeset A to have no elements in its successor set (e.g. the changeset
858 has been pruned). Therefore, the returned list of successors sets will be
858 has been pruned). Therefore, the returned list of successors sets will be
859 [(A',)] or [], respectively.
859 [(A',)] or [], respectively.
860
860
861 When a changeset A is split into A' and B', however, it will result in a
861 When a changeset A is split into A' and B', however, it will result in a
862 successors set containing more than a single element, i.e. [(A',B')].
862 successors set containing more than a single element, i.e. [(A',B')].
863 Divergent changesets will result in multiple successors sets, i.e. [(A',),
863 Divergent changesets will result in multiple successors sets, i.e. [(A',),
864 (A'')].
864 (A'')].
865
865
866 If a changeset A is not obsolete, then it will conceptually have no
866 If a changeset A is not obsolete, then it will conceptually have no
867 successors set. To distinguish this from a pruned changeset, the successor
867 successors set. To distinguish this from a pruned changeset, the successor
868 set will only contain itself, i.e. [(A,)].
868 set will only contain itself, i.e. [(A,)].
869
869
870 Finally, successors unknown locally are considered to be pruned (obsoleted
870 Finally, successors unknown locally are considered to be pruned (obsoleted
871 without any successors).
871 without any successors).
872
872
873 The optional `cache` parameter is a dictionary that may contain precomputed
873 The optional `cache` parameter is a dictionary that may contain precomputed
874 successors sets. It is meant to reuse the computation of a previous call to
874 successors sets. It is meant to reuse the computation of a previous call to
875 `successorssets` when multiple calls are made at the same time. The cache
875 `successorssets` when multiple calls are made at the same time. The cache
876 dictionary is updated in place. The caller is responsible for its live
876 dictionary is updated in place. The caller is responsible for its live
877 spawn. Code that makes multiple calls to `successorssets` *must* use this
877 spawn. Code that makes multiple calls to `successorssets` *must* use this
878 cache mechanism or suffer terrible performances.
878 cache mechanism or suffer terrible performances.
879
879
880 """
880 """
881
881
882 succmarkers = repo.obsstore.successors
882 succmarkers = repo.obsstore.successors
883
883
884 # Stack of nodes we search successors sets for
884 # Stack of nodes we search successors sets for
885 toproceed = [initialnode]
885 toproceed = [initialnode]
886 # set version of above list for fast loop detection
886 # set version of above list for fast loop detection
887 # element added to "toproceed" must be added here
887 # element added to "toproceed" must be added here
888 stackedset = set(toproceed)
888 stackedset = set(toproceed)
889 if cache is None:
889 if cache is None:
890 cache = {}
890 cache = {}
891
891
892 # This while loop is the flattened version of a recursive search for
892 # This while loop is the flattened version of a recursive search for
893 # successors sets
893 # successors sets
894 #
894 #
895 # def successorssets(x):
895 # def successorssets(x):
896 # successors = directsuccessors(x)
896 # successors = directsuccessors(x)
897 # ss = [[]]
897 # ss = [[]]
898 # for succ in directsuccessors(x):
898 # for succ in directsuccessors(x):
899 # # product as in itertools cartesian product
899 # # product as in itertools cartesian product
900 # ss = product(ss, successorssets(succ))
900 # ss = product(ss, successorssets(succ))
901 # return ss
901 # return ss
902 #
902 #
903 # But we can not use plain recursive calls here:
903 # But we can not use plain recursive calls here:
904 # - that would blow the python call stack
904 # - that would blow the python call stack
905 # - obsolescence markers may have cycles, we need to handle them.
905 # - obsolescence markers may have cycles, we need to handle them.
906 #
906 #
907 # The `toproceed` list act as our call stack. Every node we search
907 # The `toproceed` list act as our call stack. Every node we search
908 # successors set for are stacked there.
908 # successors set for are stacked there.
909 #
909 #
910 # The `stackedset` is set version of this stack used to check if a node is
910 # The `stackedset` is set version of this stack used to check if a node is
911 # already stacked. This check is used to detect cycles and prevent infinite
911 # already stacked. This check is used to detect cycles and prevent infinite
912 # loop.
912 # loop.
913 #
913 #
914 # successors set of all nodes are stored in the `cache` dictionary.
914 # successors set of all nodes are stored in the `cache` dictionary.
915 #
915 #
916 # After this while loop ends we use the cache to return the successors sets
916 # After this while loop ends we use the cache to return the successors sets
917 # for the node requested by the caller.
917 # for the node requested by the caller.
918 while toproceed:
918 while toproceed:
919 # Every iteration tries to compute the successors sets of the topmost
919 # Every iteration tries to compute the successors sets of the topmost
920 # node of the stack: CURRENT.
920 # node of the stack: CURRENT.
921 #
921 #
922 # There are four possible outcomes:
922 # There are four possible outcomes:
923 #
923 #
924 # 1) We already know the successors sets of CURRENT:
924 # 1) We already know the successors sets of CURRENT:
925 # -> mission accomplished, pop it from the stack.
925 # -> mission accomplished, pop it from the stack.
926 # 2) Node is not obsolete:
926 # 2) Node is not obsolete:
927 # -> the node is its own successors sets. Add it to the cache.
927 # -> the node is its own successors sets. Add it to the cache.
928 # 3) We do not know successors set of direct successors of CURRENT:
928 # 3) We do not know successors set of direct successors of CURRENT:
929 # -> We add those successors to the stack.
929 # -> We add those successors to the stack.
930 # 4) We know successors sets of all direct successors of CURRENT:
930 # 4) We know successors sets of all direct successors of CURRENT:
931 # -> We can compute CURRENT successors set and add it to the
931 # -> We can compute CURRENT successors set and add it to the
932 # cache.
932 # cache.
933 #
933 #
934 current = toproceed[-1]
934 current = toproceed[-1]
935 if current in cache:
935 if current in cache:
936 # case (1): We already know the successors sets
936 # case (1): We already know the successors sets
937 stackedset.remove(toproceed.pop())
937 stackedset.remove(toproceed.pop())
938 elif current not in succmarkers:
938 elif current not in succmarkers:
939 # case (2): The node is not obsolete.
939 # case (2): The node is not obsolete.
940 if current in repo:
940 if current in repo:
941 # We have a valid last successors.
941 # We have a valid last successors.
942 cache[current] = [(current,)]
942 cache[current] = [(current,)]
943 else:
943 else:
944 # Final obsolete version is unknown locally.
944 # Final obsolete version is unknown locally.
945 # Do not count that as a valid successors
945 # Do not count that as a valid successors
946 cache[current] = []
946 cache[current] = []
947 else:
947 else:
948 # cases (3) and (4)
948 # cases (3) and (4)
949 #
949 #
950 # We proceed in two phases. Phase 1 aims to distinguish case (3)
950 # We proceed in two phases. Phase 1 aims to distinguish case (3)
951 # from case (4):
951 # from case (4):
952 #
952 #
953 # For each direct successors of CURRENT, we check whether its
953 # For each direct successors of CURRENT, we check whether its
954 # successors sets are known. If they are not, we stack the
954 # successors sets are known. If they are not, we stack the
955 # unknown node and proceed to the next iteration of the while
955 # unknown node and proceed to the next iteration of the while
956 # loop. (case 3)
956 # loop. (case 3)
957 #
957 #
958 # During this step, we may detect obsolescence cycles: a node
958 # During this step, we may detect obsolescence cycles: a node
959 # with unknown successors sets but already in the call stack.
959 # with unknown successors sets but already in the call stack.
960 # In such a situation, we arbitrary set the successors sets of
960 # In such a situation, we arbitrary set the successors sets of
961 # the node to nothing (node pruned) to break the cycle.
961 # the node to nothing (node pruned) to break the cycle.
962 #
962 #
963 # If no break was encountered we proceed to phase 2.
963 # If no break was encountered we proceed to phase 2.
964 #
964 #
965 # Phase 2 computes successors sets of CURRENT (case 4); see details
965 # Phase 2 computes successors sets of CURRENT (case 4); see details
966 # in phase 2 itself.
966 # in phase 2 itself.
967 #
967 #
968 # Note the two levels of iteration in each phase.
968 # Note the two levels of iteration in each phase.
969 # - The first one handles obsolescence markers using CURRENT as
969 # - The first one handles obsolescence markers using CURRENT as
970 # precursor (successors markers of CURRENT).
970 # precursor (successors markers of CURRENT).
971 #
971 #
972 # Having multiple entry here means divergence.
972 # Having multiple entry here means divergence.
973 #
973 #
974 # - The second one handles successors defined in each marker.
974 # - The second one handles successors defined in each marker.
975 #
975 #
976 # Having none means pruned node, multiple successors means split,
976 # Having none means pruned node, multiple successors means split,
977 # single successors are standard replacement.
977 # single successors are standard replacement.
978 #
978 #
979 for mark in sorted(succmarkers[current]):
979 for mark in sorted(succmarkers[current]):
980 for suc in mark[1]:
980 for suc in mark[1]:
981 if suc not in cache:
981 if suc not in cache:
982 if suc in stackedset:
982 if suc in stackedset:
983 # cycle breaking
983 # cycle breaking
984 cache[suc] = []
984 cache[suc] = []
985 else:
985 else:
986 # case (3) If we have not computed successors sets
986 # case (3) If we have not computed successors sets
987 # of one of those successors we add it to the
987 # of one of those successors we add it to the
988 # `toproceed` stack and stop all work for this
988 # `toproceed` stack and stop all work for this
989 # iteration.
989 # iteration.
990 toproceed.append(suc)
990 toproceed.append(suc)
991 stackedset.add(suc)
991 stackedset.add(suc)
992 break
992 break
993 else:
993 else:
994 continue
994 continue
995 break
995 break
996 else:
996 else:
997 # case (4): we know all successors sets of all direct
997 # case (4): we know all successors sets of all direct
998 # successors
998 # successors
999 #
999 #
1000 # Successors set contributed by each marker depends on the
1000 # Successors set contributed by each marker depends on the
1001 # successors sets of all its "successors" node.
1001 # successors sets of all its "successors" node.
1002 #
1002 #
1003 # Each different marker is a divergence in the obsolescence
1003 # Each different marker is a divergence in the obsolescence
1004 # history. It contributes successors sets distinct from other
1004 # history. It contributes successors sets distinct from other
1005 # markers.
1005 # markers.
1006 #
1006 #
1007 # Within a marker, a successor may have divergent successors
1007 # Within a marker, a successor may have divergent successors
1008 # sets. In such a case, the marker will contribute multiple
1008 # sets. In such a case, the marker will contribute multiple
1009 # divergent successors sets. If multiple successors have
1009 # divergent successors sets. If multiple successors have
1010 # divergent successors sets, a Cartesian product is used.
1010 # divergent successors sets, a Cartesian product is used.
1011 #
1011 #
1012 # At the end we post-process successors sets to remove
1012 # At the end we post-process successors sets to remove
1013 # duplicated entry and successors set that are strict subset of
1013 # duplicated entry and successors set that are strict subset of
1014 # another one.
1014 # another one.
1015 succssets = []
1015 succssets = []
1016 for mark in sorted(succmarkers[current]):
1016 for mark in sorted(succmarkers[current]):
1017 # successors sets contributed by this marker
1017 # successors sets contributed by this marker
1018 markss = [[]]
1018 markss = [[]]
1019 for suc in mark[1]:
1019 for suc in mark[1]:
1020 # cardinal product with previous successors
1020 # cardinal product with previous successors
1021 productresult = []
1021 productresult = []
1022 for prefix in markss:
1022 for prefix in markss:
1023 for suffix in cache[suc]:
1023 for suffix in cache[suc]:
1024 newss = list(prefix)
1024 newss = list(prefix)
1025 for part in suffix:
1025 for part in suffix:
1026 # do not duplicated entry in successors set
1026 # do not duplicated entry in successors set
1027 # first entry wins.
1027 # first entry wins.
1028 if part not in newss:
1028 if part not in newss:
1029 newss.append(part)
1029 newss.append(part)
1030 productresult.append(newss)
1030 productresult.append(newss)
1031 markss = productresult
1031 markss = productresult
1032 succssets.extend(markss)
1032 succssets.extend(markss)
1033 # remove duplicated and subset
1033 # remove duplicated and subset
1034 seen = []
1034 seen = []
1035 final = []
1035 final = []
1036 candidate = sorted(((set(s), s) for s in succssets if s),
1036 candidate = sorted(((set(s), s) for s in succssets if s),
1037 key=lambda x: len(x[1]), reverse=True)
1037 key=lambda x: len(x[1]), reverse=True)
1038 for setversion, listversion in candidate:
1038 for setversion, listversion in candidate:
1039 for seenset in seen:
1039 for seenset in seen:
1040 if setversion.issubset(seenset):
1040 if setversion.issubset(seenset):
1041 break
1041 break
1042 else:
1042 else:
1043 final.append(listversion)
1043 final.append(listversion)
1044 seen.append(setversion)
1044 seen.append(setversion)
1045 final.reverse() # put small successors set first
1045 final.reverse() # put small successors set first
1046 cache[current] = final
1046 cache[current] = final
1047 return cache[initialnode]
1047 return cache[initialnode]
1048
1048
1049 def _knownrevs(repo, nodes):
1049 def _knownrevs(repo, nodes):
1050 """yield revision numbers of known nodes passed in parameters
1050 """yield revision numbers of known nodes passed in parameters
1051
1051
1052 Unknown revisions are silently ignored."""
1052 Unknown revisions are silently ignored."""
1053 torev = repo.changelog.nodemap.get
1053 torev = repo.changelog.nodemap.get
1054 for n in nodes:
1054 for n in nodes:
1055 rev = torev(n)
1055 rev = torev(n)
1056 if rev is not None:
1056 if rev is not None:
1057 yield rev
1057 yield rev
1058
1058
1059 # mapping of 'set-name' -> <function to compute this set>
1059 # mapping of 'set-name' -> <function to compute this set>
1060 cachefuncs = {}
1060 cachefuncs = {}
1061 def cachefor(name):
1061 def cachefor(name):
1062 """Decorator to register a function as computing the cache for a set"""
1062 """Decorator to register a function as computing the cache for a set"""
1063 def decorator(func):
1063 def decorator(func):
1064 assert name not in cachefuncs
1064 assert name not in cachefuncs
1065 cachefuncs[name] = func
1065 cachefuncs[name] = func
1066 return func
1066 return func
1067 return decorator
1067 return decorator
1068
1068
1069 def getrevs(repo, name):
1069 def getrevs(repo, name):
1070 """Return the set of revision that belong to the <name> set
1070 """Return the set of revision that belong to the <name> set
1071
1071
1072 Such access may compute the set and cache it for future use"""
1072 Such access may compute the set and cache it for future use"""
1073 repo = repo.unfiltered()
1073 repo = repo.unfiltered()
1074 if not repo.obsstore:
1074 if not repo.obsstore:
1075 return frozenset()
1075 return frozenset()
1076 if name not in repo.obsstore.caches:
1076 if name not in repo.obsstore.caches:
1077 repo.obsstore.caches[name] = cachefuncs[name](repo)
1077 repo.obsstore.caches[name] = cachefuncs[name](repo)
1078 return repo.obsstore.caches[name]
1078 return repo.obsstore.caches[name]
1079
1079
1080 # To be simple we need to invalidate obsolescence cache when:
1080 # To be simple we need to invalidate obsolescence cache when:
1081 #
1081 #
1082 # - new changeset is added:
1082 # - new changeset is added:
1083 # - public phase is changed
1083 # - public phase is changed
1084 # - obsolescence marker are added
1084 # - obsolescence marker are added
1085 # - strip is used a repo
1085 # - strip is used a repo
1086 def clearobscaches(repo):
1086 def clearobscaches(repo):
1087 """Remove all obsolescence related cache from a repo
1087 """Remove all obsolescence related cache from a repo
1088
1088
1089 This remove all cache in obsstore is the obsstore already exist on the
1089 This remove all cache in obsstore is the obsstore already exist on the
1090 repo.
1090 repo.
1091
1091
1092 (We could be smarter here given the exact event that trigger the cache
1092 (We could be smarter here given the exact event that trigger the cache
1093 clearing)"""
1093 clearing)"""
1094 # only clear cache is there is obsstore data in this repo
1094 # only clear cache is there is obsstore data in this repo
1095 if 'obsstore' in repo._filecache:
1095 if 'obsstore' in repo._filecache:
1096 repo.obsstore.caches.clear()
1096 repo.obsstore.caches.clear()
1097
1097
1098 @cachefor('obsolete')
1098 @cachefor('obsolete')
1099 def _computeobsoleteset(repo):
1099 def _computeobsoleteset(repo):
1100 """the set of obsolete revisions"""
1100 """the set of obsolete revisions"""
1101 obs = set()
1101 obs = set()
1102 getrev = repo.changelog.nodemap.get
1102 getrev = repo.changelog.nodemap.get
1103 getphase = repo._phasecache.phase
1103 getphase = repo._phasecache.phase
1104 for n in repo.obsstore.successors:
1104 for n in repo.obsstore.successors:
1105 rev = getrev(n)
1105 rev = getrev(n)
1106 if rev is not None and getphase(repo, rev):
1106 if rev is not None and getphase(repo, rev):
1107 obs.add(rev)
1107 obs.add(rev)
1108 return obs
1108 return obs
1109
1109
1110 @cachefor('unstable')
1110 @cachefor('unstable')
1111 def _computeunstableset(repo):
1111 def _computeunstableset(repo):
1112 """the set of non obsolete revisions with obsolete parents"""
1112 """the set of non obsolete revisions with obsolete parents"""
1113 revs = [(ctx.rev(), ctx) for ctx in
1113 revs = [(ctx.rev(), ctx) for ctx in
1114 repo.set('(not public()) and (not obsolete())')]
1114 repo.set('(not public()) and (not obsolete())')]
1115 revs.sort(key=lambda x:x[0])
1115 revs.sort(key=lambda x:x[0])
1116 unstable = set()
1116 unstable = set()
1117 for rev, ctx in revs:
1117 for rev, ctx in revs:
1118 # A rev is unstable if one of its parent is obsolete or unstable
1118 # A rev is unstable if one of its parent is obsolete or unstable
1119 # this works since we traverse following growing rev order
1119 # this works since we traverse following growing rev order
1120 if any((x.obsolete() or (x.rev() in unstable))
1120 if any((x.obsolete() or (x.rev() in unstable))
1121 for x in ctx.parents()):
1121 for x in ctx.parents()):
1122 unstable.add(rev)
1122 unstable.add(rev)
1123 return unstable
1123 return unstable
1124
1124
1125 @cachefor('suspended')
1125 @cachefor('suspended')
1126 def _computesuspendedset(repo):
1126 def _computesuspendedset(repo):
1127 """the set of obsolete parents with non obsolete descendants"""
1127 """the set of obsolete parents with non obsolete descendants"""
1128 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1128 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1129 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1129 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1130
1130
1131 @cachefor('extinct')
1131 @cachefor('extinct')
1132 def _computeextinctset(repo):
1132 def _computeextinctset(repo):
1133 """the set of obsolete parents without non obsolete descendants"""
1133 """the set of obsolete parents without non obsolete descendants"""
1134 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1134 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1135
1135
1136
1136
1137 @cachefor('bumped')
1137 @cachefor('bumped')
1138 def _computebumpedset(repo):
1138 def _computebumpedset(repo):
1139 """the set of revs trying to obsolete public revisions"""
1139 """the set of revs trying to obsolete public revisions"""
1140 bumped = set()
1140 bumped = set()
1141 # util function (avoid attribute lookup in the loop)
1141 # util function (avoid attribute lookup in the loop)
1142 phase = repo._phasecache.phase # would be faster to grab the full list
1142 phase = repo._phasecache.phase # would be faster to grab the full list
1143 public = phases.public
1143 public = phases.public
1144 cl = repo.changelog
1144 cl = repo.changelog
1145 torev = cl.nodemap.get
1145 torev = cl.nodemap.get
1146 for ctx in repo.set('(not public()) and (not obsolete())'):
1146 for ctx in repo.set('(not public()) and (not obsolete())'):
1147 rev = ctx.rev()
1147 rev = ctx.rev()
1148 # We only evaluate mutable, non-obsolete revision
1148 # We only evaluate mutable, non-obsolete revision
1149 node = ctx.node()
1149 node = ctx.node()
1150 # (future) A cache of precursors may worth if split is very common
1150 # (future) A cache of precursors may worth if split is very common
1151 for pnode in allprecursors(repo.obsstore, [node],
1151 for pnode in allprecursors(repo.obsstore, [node],
1152 ignoreflags=bumpedfix):
1152 ignoreflags=bumpedfix):
1153 prev = torev(pnode) # unfiltered! but so is phasecache
1153 prev = torev(pnode) # unfiltered! but so is phasecache
1154 if (prev is not None) and (phase(repo, prev) <= public):
1154 if (prev is not None) and (phase(repo, prev) <= public):
1155 # we have a public precursors
1155 # we have a public precursors
1156 bumped.add(rev)
1156 bumped.add(rev)
1157 break # Next draft!
1157 break # Next draft!
1158 return bumped
1158 return bumped
1159
1159
1160 @cachefor('divergent')
1160 @cachefor('divergent')
1161 def _computedivergentset(repo):
1161 def _computedivergentset(repo):
1162 """the set of rev that compete to be the final successors of some revision.
1162 """the set of rev that compete to be the final successors of some revision.
1163 """
1163 """
1164 divergent = set()
1164 divergent = set()
1165 obsstore = repo.obsstore
1165 obsstore = repo.obsstore
1166 newermap = {}
1166 newermap = {}
1167 for ctx in repo.set('(not public()) - obsolete()'):
1167 for ctx in repo.set('(not public()) - obsolete()'):
1168 mark = obsstore.precursors.get(ctx.node(), ())
1168 mark = obsstore.precursors.get(ctx.node(), ())
1169 toprocess = set(mark)
1169 toprocess = set(mark)
1170 seen = set()
1170 seen = set()
1171 while toprocess:
1171 while toprocess:
1172 prec = toprocess.pop()[0]
1172 prec = toprocess.pop()[0]
1173 if prec in seen:
1173 if prec in seen:
1174 continue # emergency cycle hanging prevention
1174 continue # emergency cycle hanging prevention
1175 seen.add(prec)
1175 seen.add(prec)
1176 if prec not in newermap:
1176 if prec not in newermap:
1177 successorssets(repo, prec, newermap)
1177 successorssets(repo, prec, newermap)
1178 newer = [n for n in newermap[prec] if n]
1178 newer = [n for n in newermap[prec] if n]
1179 if len(newer) > 1:
1179 if len(newer) > 1:
1180 divergent.add(ctx.rev())
1180 divergent.add(ctx.rev())
1181 break
1181 break
1182 toprocess.update(obsstore.precursors.get(prec, ()))
1182 toprocess.update(obsstore.precursors.get(prec, ()))
1183 return divergent
1183 return divergent
1184
1184
1185
1185
1186 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
1186 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
1187 """Add obsolete markers between changesets in a repo
1187 """Add obsolete markers between changesets in a repo
1188
1188
1189 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1189 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1190 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1190 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1191 containing metadata for this marker only. It is merged with the global
1191 containing metadata for this marker only. It is merged with the global
1192 metadata specified through the `metadata` argument of this function,
1192 metadata specified through the `metadata` argument of this function,
1193
1193
1194 Trying to obsolete a public changeset will raise an exception.
1194 Trying to obsolete a public changeset will raise an exception.
1195
1195
1196 Current user and date are used except if specified otherwise in the
1196 Current user and date are used except if specified otherwise in the
1197 metadata attribute.
1197 metadata attribute.
1198
1198
1199 This function operates within a transaction of its own, but does
1199 This function operates within a transaction of its own, but does
1200 not take any lock on the repo.
1200 not take any lock on the repo.
1201 """
1201 """
1202 # prepare metadata
1202 # prepare metadata
1203 if metadata is None:
1203 if metadata is None:
1204 metadata = {}
1204 metadata = {}
1205 if 'user' not in metadata:
1205 if 'user' not in metadata:
1206 metadata['user'] = repo.ui.username()
1206 metadata['user'] = repo.ui.username()
1207 tr = repo.transaction('add-obsolescence-marker')
1207 tr = repo.transaction('add-obsolescence-marker')
1208 try:
1208 try:
1209 for rel in relations:
1209 for rel in relations:
1210 prec = rel[0]
1210 prec = rel[0]
1211 sucs = rel[1]
1211 sucs = rel[1]
1212 localmetadata = metadata.copy()
1212 localmetadata = metadata.copy()
1213 if 2 < len(rel):
1213 if 2 < len(rel):
1214 localmetadata.update(rel[2])
1214 localmetadata.update(rel[2])
1215
1215
1216 if not prec.mutable():
1216 if not prec.mutable():
1217 raise util.Abort("cannot obsolete immutable changeset: %s"
1217 raise util.Abort("cannot obsolete immutable changeset: %s"
1218 % prec)
1218 % prec)
1219 nprec = prec.node()
1219 nprec = prec.node()
1220 nsucs = tuple(s.node() for s in sucs)
1220 nsucs = tuple(s.node() for s in sucs)
1221 npare = None
1221 npare = None
1222 if not nsucs:
1222 if not nsucs:
1223 npare = tuple(p.node() for p in prec.parents())
1223 npare = tuple(p.node() for p in prec.parents())
1224 if nprec in nsucs:
1224 if nprec in nsucs:
1225 raise util.Abort("changeset %s cannot obsolete itself" % prec)
1225 raise util.Abort("changeset %s cannot obsolete itself" % prec)
1226 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1226 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1227 date=date, metadata=localmetadata)
1227 date=date, metadata=localmetadata)
1228 repo.filteredrevcache.clear()
1228 repo.filteredrevcache.clear()
1229 tr.close()
1229 tr.close()
1230 finally:
1230 finally:
1231 tr.release()
1231 tr.release()
1232
1232
1233 def isenabled(repo, option):
1233 def isenabled(repo, option):
1234 """Returns True if the given repository has the given obsolete option
1234 """Returns True if the given repository has the given obsolete option
1235 enabled.
1235 enabled.
1236 """
1236 """
1237 result = set(repo.ui.configlist('experimental', 'evolution'))
1237 result = set(repo.ui.configlist('experimental', 'evolution'))
1238 if 'all' in result:
1238 if 'all' in result:
1239 return True
1239 return True
1240
1240
1241 # For migration purposes, temporarily return true if the config hasn't been
1241 # For migration purposes, temporarily return true if the config hasn't been
1242 # set but _enabled is true.
1242 # set but _enabled is true.
1243 if len(result) == 0 and _enabled:
1243 if len(result) == 0 and _enabled:
1244 return True
1244 return True
1245
1245
1246 # createmarkers must be enabled if other options are enabled
1246 # createmarkers must be enabled if other options are enabled
1247 if ((allowunstableopt in result or exchangeopt in result) and
1247 if ((allowunstableopt in result or exchangeopt in result) and
1248 not createmarkersopt in result):
1248 not createmarkersopt in result):
1249 raise util.Abort(_("'createmarkers' obsolete option must be enabled "
1249 raise util.Abort(_("'createmarkers' obsolete option must be enabled "
1250 "if other obsolete options are enabled"))
1250 "if other obsolete options are enabled"))
1251
1251
1252 return option in result
1252 return option in result
@@ -1,2249 +1,2245 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 import i18n
16 import i18n
17 _ = i18n._
17 _ = i18n._
18 import error, osutil, encoding, parsers
18 import error, osutil, encoding, parsers
19 import errno, shutil, sys, tempfile, traceback
19 import errno, shutil, sys, tempfile, traceback
20 import re as remod
20 import re as remod
21 import os, time, datetime, calendar, textwrap, signal, collections
21 import os, time, datetime, calendar, textwrap, signal, collections
22 import imp, socket, urllib, struct
22 import imp, socket, urllib
23 import gc
23 import gc
24
24
25 if os.name == 'nt':
25 if os.name == 'nt':
26 import windows as platform
26 import windows as platform
27 else:
27 else:
28 import posix as platform
28 import posix as platform
29
29
30 cachestat = platform.cachestat
30 cachestat = platform.cachestat
31 checkexec = platform.checkexec
31 checkexec = platform.checkexec
32 checklink = platform.checklink
32 checklink = platform.checklink
33 copymode = platform.copymode
33 copymode = platform.copymode
34 executablepath = platform.executablepath
34 executablepath = platform.executablepath
35 expandglobs = platform.expandglobs
35 expandglobs = platform.expandglobs
36 explainexit = platform.explainexit
36 explainexit = platform.explainexit
37 findexe = platform.findexe
37 findexe = platform.findexe
38 gethgcmd = platform.gethgcmd
38 gethgcmd = platform.gethgcmd
39 getuser = platform.getuser
39 getuser = platform.getuser
40 groupmembers = platform.groupmembers
40 groupmembers = platform.groupmembers
41 groupname = platform.groupname
41 groupname = platform.groupname
42 hidewindow = platform.hidewindow
42 hidewindow = platform.hidewindow
43 isexec = platform.isexec
43 isexec = platform.isexec
44 isowner = platform.isowner
44 isowner = platform.isowner
45 localpath = platform.localpath
45 localpath = platform.localpath
46 lookupreg = platform.lookupreg
46 lookupreg = platform.lookupreg
47 makedir = platform.makedir
47 makedir = platform.makedir
48 nlinks = platform.nlinks
48 nlinks = platform.nlinks
49 normpath = platform.normpath
49 normpath = platform.normpath
50 normcase = platform.normcase
50 normcase = platform.normcase
51 normcasespec = platform.normcasespec
51 normcasespec = platform.normcasespec
52 normcasefallback = platform.normcasefallback
52 normcasefallback = platform.normcasefallback
53 openhardlinks = platform.openhardlinks
53 openhardlinks = platform.openhardlinks
54 oslink = platform.oslink
54 oslink = platform.oslink
55 parsepatchoutput = platform.parsepatchoutput
55 parsepatchoutput = platform.parsepatchoutput
56 pconvert = platform.pconvert
56 pconvert = platform.pconvert
57 popen = platform.popen
57 popen = platform.popen
58 posixfile = platform.posixfile
58 posixfile = platform.posixfile
59 quotecommand = platform.quotecommand
59 quotecommand = platform.quotecommand
60 readpipe = platform.readpipe
60 readpipe = platform.readpipe
61 rename = platform.rename
61 rename = platform.rename
62 removedirs = platform.removedirs
62 removedirs = platform.removedirs
63 samedevice = platform.samedevice
63 samedevice = platform.samedevice
64 samefile = platform.samefile
64 samefile = platform.samefile
65 samestat = platform.samestat
65 samestat = platform.samestat
66 setbinary = platform.setbinary
66 setbinary = platform.setbinary
67 setflags = platform.setflags
67 setflags = platform.setflags
68 setsignalhandler = platform.setsignalhandler
68 setsignalhandler = platform.setsignalhandler
69 shellquote = platform.shellquote
69 shellquote = platform.shellquote
70 spawndetached = platform.spawndetached
70 spawndetached = platform.spawndetached
71 split = platform.split
71 split = platform.split
72 sshargs = platform.sshargs
72 sshargs = platform.sshargs
73 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
73 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
74 statisexec = platform.statisexec
74 statisexec = platform.statisexec
75 statislink = platform.statislink
75 statislink = platform.statislink
76 termwidth = platform.termwidth
76 termwidth = platform.termwidth
77 testpid = platform.testpid
77 testpid = platform.testpid
78 umask = platform.umask
78 umask = platform.umask
79 unlink = platform.unlink
79 unlink = platform.unlink
80 unlinkpath = platform.unlinkpath
80 unlinkpath = platform.unlinkpath
81 username = platform.username
81 username = platform.username
82
82
83 # Python compatibility
83 # Python compatibility
84
84
85 _notset = object()
85 _notset = object()
86
86
87 def safehasattr(thing, attr):
87 def safehasattr(thing, attr):
88 return getattr(thing, attr, _notset) is not _notset
88 return getattr(thing, attr, _notset) is not _notset
89
89
90 def sha1(s=''):
90 def sha1(s=''):
91 '''
91 '''
92 Low-overhead wrapper around Python's SHA support
92 Low-overhead wrapper around Python's SHA support
93
93
94 >>> f = _fastsha1
94 >>> f = _fastsha1
95 >>> a = sha1()
95 >>> a = sha1()
96 >>> a = f()
96 >>> a = f()
97 >>> a.hexdigest()
97 >>> a.hexdigest()
98 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
98 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
99 '''
99 '''
100
100
101 return _fastsha1(s)
101 return _fastsha1(s)
102
102
103 def _fastsha1(s=''):
103 def _fastsha1(s=''):
104 # This function will import sha1 from hashlib or sha (whichever is
104 # This function will import sha1 from hashlib or sha (whichever is
105 # available) and overwrite itself with it on the first call.
105 # available) and overwrite itself with it on the first call.
106 # Subsequent calls will go directly to the imported function.
106 # Subsequent calls will go directly to the imported function.
107 if sys.version_info >= (2, 5):
107 if sys.version_info >= (2, 5):
108 from hashlib import sha1 as _sha1
108 from hashlib import sha1 as _sha1
109 else:
109 else:
110 from sha import sha as _sha1
110 from sha import sha as _sha1
111 global _fastsha1, sha1
111 global _fastsha1, sha1
112 _fastsha1 = sha1 = _sha1
112 _fastsha1 = sha1 = _sha1
113 return _sha1(s)
113 return _sha1(s)
114
114
115 def md5(s=''):
115 def md5(s=''):
116 try:
116 try:
117 from hashlib import md5 as _md5
117 from hashlib import md5 as _md5
118 except ImportError:
118 except ImportError:
119 from md5 import md5 as _md5
119 from md5 import md5 as _md5
120 global md5
120 global md5
121 md5 = _md5
121 md5 = _md5
122 return _md5(s)
122 return _md5(s)
123
123
124 DIGESTS = {
124 DIGESTS = {
125 'md5': md5,
125 'md5': md5,
126 'sha1': sha1,
126 'sha1': sha1,
127 }
127 }
128 # List of digest types from strongest to weakest
128 # List of digest types from strongest to weakest
129 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
129 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
130
130
131 try:
131 try:
132 import hashlib
132 import hashlib
133 DIGESTS.update({
133 DIGESTS.update({
134 'sha512': hashlib.sha512,
134 'sha512': hashlib.sha512,
135 })
135 })
136 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
136 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
137 except ImportError:
137 except ImportError:
138 pass
138 pass
139
139
140 for k in DIGESTS_BY_STRENGTH:
140 for k in DIGESTS_BY_STRENGTH:
141 assert k in DIGESTS
141 assert k in DIGESTS
142
142
143 class digester(object):
143 class digester(object):
144 """helper to compute digests.
144 """helper to compute digests.
145
145
146 This helper can be used to compute one or more digests given their name.
146 This helper can be used to compute one or more digests given their name.
147
147
148 >>> d = digester(['md5', 'sha1'])
148 >>> d = digester(['md5', 'sha1'])
149 >>> d.update('foo')
149 >>> d.update('foo')
150 >>> [k for k in sorted(d)]
150 >>> [k for k in sorted(d)]
151 ['md5', 'sha1']
151 ['md5', 'sha1']
152 >>> d['md5']
152 >>> d['md5']
153 'acbd18db4cc2f85cedef654fccc4a4d8'
153 'acbd18db4cc2f85cedef654fccc4a4d8'
154 >>> d['sha1']
154 >>> d['sha1']
155 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
155 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
156 >>> digester.preferred(['md5', 'sha1'])
156 >>> digester.preferred(['md5', 'sha1'])
157 'sha1'
157 'sha1'
158 """
158 """
159
159
160 def __init__(self, digests, s=''):
160 def __init__(self, digests, s=''):
161 self._hashes = {}
161 self._hashes = {}
162 for k in digests:
162 for k in digests:
163 if k not in DIGESTS:
163 if k not in DIGESTS:
164 raise Abort(_('unknown digest type: %s') % k)
164 raise Abort(_('unknown digest type: %s') % k)
165 self._hashes[k] = DIGESTS[k]()
165 self._hashes[k] = DIGESTS[k]()
166 if s:
166 if s:
167 self.update(s)
167 self.update(s)
168
168
169 def update(self, data):
169 def update(self, data):
170 for h in self._hashes.values():
170 for h in self._hashes.values():
171 h.update(data)
171 h.update(data)
172
172
173 def __getitem__(self, key):
173 def __getitem__(self, key):
174 if key not in DIGESTS:
174 if key not in DIGESTS:
175 raise Abort(_('unknown digest type: %s') % k)
175 raise Abort(_('unknown digest type: %s') % k)
176 return self._hashes[key].hexdigest()
176 return self._hashes[key].hexdigest()
177
177
178 def __iter__(self):
178 def __iter__(self):
179 return iter(self._hashes)
179 return iter(self._hashes)
180
180
181 @staticmethod
181 @staticmethod
182 def preferred(supported):
182 def preferred(supported):
183 """returns the strongest digest type in both supported and DIGESTS."""
183 """returns the strongest digest type in both supported and DIGESTS."""
184
184
185 for k in DIGESTS_BY_STRENGTH:
185 for k in DIGESTS_BY_STRENGTH:
186 if k in supported:
186 if k in supported:
187 return k
187 return k
188 return None
188 return None
189
189
190 class digestchecker(object):
190 class digestchecker(object):
191 """file handle wrapper that additionally checks content against a given
191 """file handle wrapper that additionally checks content against a given
192 size and digests.
192 size and digests.
193
193
194 d = digestchecker(fh, size, {'md5': '...'})
194 d = digestchecker(fh, size, {'md5': '...'})
195
195
196 When multiple digests are given, all of them are validated.
196 When multiple digests are given, all of them are validated.
197 """
197 """
198
198
199 def __init__(self, fh, size, digests):
199 def __init__(self, fh, size, digests):
200 self._fh = fh
200 self._fh = fh
201 self._size = size
201 self._size = size
202 self._got = 0
202 self._got = 0
203 self._digests = dict(digests)
203 self._digests = dict(digests)
204 self._digester = digester(self._digests.keys())
204 self._digester = digester(self._digests.keys())
205
205
206 def read(self, length=-1):
206 def read(self, length=-1):
207 content = self._fh.read(length)
207 content = self._fh.read(length)
208 self._digester.update(content)
208 self._digester.update(content)
209 self._got += len(content)
209 self._got += len(content)
210 return content
210 return content
211
211
212 def validate(self):
212 def validate(self):
213 if self._size != self._got:
213 if self._size != self._got:
214 raise Abort(_('size mismatch: expected %d, got %d') %
214 raise Abort(_('size mismatch: expected %d, got %d') %
215 (self._size, self._got))
215 (self._size, self._got))
216 for k, v in self._digests.items():
216 for k, v in self._digests.items():
217 if v != self._digester[k]:
217 if v != self._digester[k]:
218 # i18n: first parameter is a digest name
218 # i18n: first parameter is a digest name
219 raise Abort(_('%s mismatch: expected %s, got %s') %
219 raise Abort(_('%s mismatch: expected %s, got %s') %
220 (k, v, self._digester[k]))
220 (k, v, self._digester[k]))
221
221
222 try:
222 try:
223 buffer = buffer
223 buffer = buffer
224 except NameError:
224 except NameError:
225 if sys.version_info[0] < 3:
225 if sys.version_info[0] < 3:
226 def buffer(sliceable, offset=0):
226 def buffer(sliceable, offset=0):
227 return sliceable[offset:]
227 return sliceable[offset:]
228 else:
228 else:
229 def buffer(sliceable, offset=0):
229 def buffer(sliceable, offset=0):
230 return memoryview(sliceable)[offset:]
230 return memoryview(sliceable)[offset:]
231
231
232 import subprocess
232 import subprocess
233 closefds = os.name == 'posix'
233 closefds = os.name == 'posix'
234
234
235 def unpacker(fmt):
236 """create a struct unpacker for the specified format"""
237 return struct.Struct(fmt).unpack
238
239 def popen2(cmd, env=None, newlines=False):
235 def popen2(cmd, env=None, newlines=False):
240 # Setting bufsize to -1 lets the system decide the buffer size.
236 # Setting bufsize to -1 lets the system decide the buffer size.
241 # The default for bufsize is 0, meaning unbuffered. This leads to
237 # The default for bufsize is 0, meaning unbuffered. This leads to
242 # poor performance on Mac OS X: http://bugs.python.org/issue4194
238 # poor performance on Mac OS X: http://bugs.python.org/issue4194
243 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
239 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
244 close_fds=closefds,
240 close_fds=closefds,
245 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
241 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
246 universal_newlines=newlines,
242 universal_newlines=newlines,
247 env=env)
243 env=env)
248 return p.stdin, p.stdout
244 return p.stdin, p.stdout
249
245
250 def popen3(cmd, env=None, newlines=False):
246 def popen3(cmd, env=None, newlines=False):
251 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
247 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
252 return stdin, stdout, stderr
248 return stdin, stdout, stderr
253
249
254 def popen4(cmd, env=None, newlines=False):
250 def popen4(cmd, env=None, newlines=False):
255 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
251 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
256 close_fds=closefds,
252 close_fds=closefds,
257 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
253 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
258 stderr=subprocess.PIPE,
254 stderr=subprocess.PIPE,
259 universal_newlines=newlines,
255 universal_newlines=newlines,
260 env=env)
256 env=env)
261 return p.stdin, p.stdout, p.stderr, p
257 return p.stdin, p.stdout, p.stderr, p
262
258
263 def version():
259 def version():
264 """Return version information if available."""
260 """Return version information if available."""
265 try:
261 try:
266 import __version__
262 import __version__
267 return __version__.version
263 return __version__.version
268 except ImportError:
264 except ImportError:
269 return 'unknown'
265 return 'unknown'
270
266
271 # used by parsedate
267 # used by parsedate
272 defaultdateformats = (
268 defaultdateformats = (
273 '%Y-%m-%d %H:%M:%S',
269 '%Y-%m-%d %H:%M:%S',
274 '%Y-%m-%d %I:%M:%S%p',
270 '%Y-%m-%d %I:%M:%S%p',
275 '%Y-%m-%d %H:%M',
271 '%Y-%m-%d %H:%M',
276 '%Y-%m-%d %I:%M%p',
272 '%Y-%m-%d %I:%M%p',
277 '%Y-%m-%d',
273 '%Y-%m-%d',
278 '%m-%d',
274 '%m-%d',
279 '%m/%d',
275 '%m/%d',
280 '%m/%d/%y',
276 '%m/%d/%y',
281 '%m/%d/%Y',
277 '%m/%d/%Y',
282 '%a %b %d %H:%M:%S %Y',
278 '%a %b %d %H:%M:%S %Y',
283 '%a %b %d %I:%M:%S%p %Y',
279 '%a %b %d %I:%M:%S%p %Y',
284 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
280 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
285 '%b %d %H:%M:%S %Y',
281 '%b %d %H:%M:%S %Y',
286 '%b %d %I:%M:%S%p %Y',
282 '%b %d %I:%M:%S%p %Y',
287 '%b %d %H:%M:%S',
283 '%b %d %H:%M:%S',
288 '%b %d %I:%M:%S%p',
284 '%b %d %I:%M:%S%p',
289 '%b %d %H:%M',
285 '%b %d %H:%M',
290 '%b %d %I:%M%p',
286 '%b %d %I:%M%p',
291 '%b %d %Y',
287 '%b %d %Y',
292 '%b %d',
288 '%b %d',
293 '%H:%M:%S',
289 '%H:%M:%S',
294 '%I:%M:%S%p',
290 '%I:%M:%S%p',
295 '%H:%M',
291 '%H:%M',
296 '%I:%M%p',
292 '%I:%M%p',
297 )
293 )
298
294
299 extendeddateformats = defaultdateformats + (
295 extendeddateformats = defaultdateformats + (
300 "%Y",
296 "%Y",
301 "%Y-%m",
297 "%Y-%m",
302 "%b",
298 "%b",
303 "%b %Y",
299 "%b %Y",
304 )
300 )
305
301
306 def cachefunc(func):
302 def cachefunc(func):
307 '''cache the result of function calls'''
303 '''cache the result of function calls'''
308 # XXX doesn't handle keywords args
304 # XXX doesn't handle keywords args
309 if func.func_code.co_argcount == 0:
305 if func.func_code.co_argcount == 0:
310 cache = []
306 cache = []
311 def f():
307 def f():
312 if len(cache) == 0:
308 if len(cache) == 0:
313 cache.append(func())
309 cache.append(func())
314 return cache[0]
310 return cache[0]
315 return f
311 return f
316 cache = {}
312 cache = {}
317 if func.func_code.co_argcount == 1:
313 if func.func_code.co_argcount == 1:
318 # we gain a small amount of time because
314 # we gain a small amount of time because
319 # we don't need to pack/unpack the list
315 # we don't need to pack/unpack the list
320 def f(arg):
316 def f(arg):
321 if arg not in cache:
317 if arg not in cache:
322 cache[arg] = func(arg)
318 cache[arg] = func(arg)
323 return cache[arg]
319 return cache[arg]
324 else:
320 else:
325 def f(*args):
321 def f(*args):
326 if args not in cache:
322 if args not in cache:
327 cache[args] = func(*args)
323 cache[args] = func(*args)
328 return cache[args]
324 return cache[args]
329
325
330 return f
326 return f
331
327
332 class sortdict(dict):
328 class sortdict(dict):
333 '''a simple sorted dictionary'''
329 '''a simple sorted dictionary'''
334 def __init__(self, data=None):
330 def __init__(self, data=None):
335 self._list = []
331 self._list = []
336 if data:
332 if data:
337 self.update(data)
333 self.update(data)
338 def copy(self):
334 def copy(self):
339 return sortdict(self)
335 return sortdict(self)
340 def __setitem__(self, key, val):
336 def __setitem__(self, key, val):
341 if key in self:
337 if key in self:
342 self._list.remove(key)
338 self._list.remove(key)
343 self._list.append(key)
339 self._list.append(key)
344 dict.__setitem__(self, key, val)
340 dict.__setitem__(self, key, val)
345 def __iter__(self):
341 def __iter__(self):
346 return self._list.__iter__()
342 return self._list.__iter__()
347 def update(self, src):
343 def update(self, src):
348 if isinstance(src, dict):
344 if isinstance(src, dict):
349 src = src.iteritems()
345 src = src.iteritems()
350 for k, v in src:
346 for k, v in src:
351 self[k] = v
347 self[k] = v
352 def clear(self):
348 def clear(self):
353 dict.clear(self)
349 dict.clear(self)
354 self._list = []
350 self._list = []
355 def items(self):
351 def items(self):
356 return [(k, self[k]) for k in self._list]
352 return [(k, self[k]) for k in self._list]
357 def __delitem__(self, key):
353 def __delitem__(self, key):
358 dict.__delitem__(self, key)
354 dict.__delitem__(self, key)
359 self._list.remove(key)
355 self._list.remove(key)
360 def pop(self, key, *args, **kwargs):
356 def pop(self, key, *args, **kwargs):
361 dict.pop(self, key, *args, **kwargs)
357 dict.pop(self, key, *args, **kwargs)
362 try:
358 try:
363 self._list.remove(key)
359 self._list.remove(key)
364 except ValueError:
360 except ValueError:
365 pass
361 pass
366 def keys(self):
362 def keys(self):
367 return self._list
363 return self._list
368 def iterkeys(self):
364 def iterkeys(self):
369 return self._list.__iter__()
365 return self._list.__iter__()
370 def iteritems(self):
366 def iteritems(self):
371 for k in self._list:
367 for k in self._list:
372 yield k, self[k]
368 yield k, self[k]
373 def insert(self, index, key, val):
369 def insert(self, index, key, val):
374 self._list.insert(index, key)
370 self._list.insert(index, key)
375 dict.__setitem__(self, key, val)
371 dict.__setitem__(self, key, val)
376
372
377 class lrucachedict(object):
373 class lrucachedict(object):
378 '''cache most recent gets from or sets to this dictionary'''
374 '''cache most recent gets from or sets to this dictionary'''
379 def __init__(self, maxsize):
375 def __init__(self, maxsize):
380 self._cache = {}
376 self._cache = {}
381 self._maxsize = maxsize
377 self._maxsize = maxsize
382 self._order = collections.deque()
378 self._order = collections.deque()
383
379
384 def __getitem__(self, key):
380 def __getitem__(self, key):
385 value = self._cache[key]
381 value = self._cache[key]
386 self._order.remove(key)
382 self._order.remove(key)
387 self._order.append(key)
383 self._order.append(key)
388 return value
384 return value
389
385
390 def __setitem__(self, key, value):
386 def __setitem__(self, key, value):
391 if key not in self._cache:
387 if key not in self._cache:
392 if len(self._cache) >= self._maxsize:
388 if len(self._cache) >= self._maxsize:
393 del self._cache[self._order.popleft()]
389 del self._cache[self._order.popleft()]
394 else:
390 else:
395 self._order.remove(key)
391 self._order.remove(key)
396 self._cache[key] = value
392 self._cache[key] = value
397 self._order.append(key)
393 self._order.append(key)
398
394
399 def __contains__(self, key):
395 def __contains__(self, key):
400 return key in self._cache
396 return key in self._cache
401
397
402 def clear(self):
398 def clear(self):
403 self._cache.clear()
399 self._cache.clear()
404 self._order = collections.deque()
400 self._order = collections.deque()
405
401
406 def lrucachefunc(func):
402 def lrucachefunc(func):
407 '''cache most recent results of function calls'''
403 '''cache most recent results of function calls'''
408 cache = {}
404 cache = {}
409 order = collections.deque()
405 order = collections.deque()
410 if func.func_code.co_argcount == 1:
406 if func.func_code.co_argcount == 1:
411 def f(arg):
407 def f(arg):
412 if arg not in cache:
408 if arg not in cache:
413 if len(cache) > 20:
409 if len(cache) > 20:
414 del cache[order.popleft()]
410 del cache[order.popleft()]
415 cache[arg] = func(arg)
411 cache[arg] = func(arg)
416 else:
412 else:
417 order.remove(arg)
413 order.remove(arg)
418 order.append(arg)
414 order.append(arg)
419 return cache[arg]
415 return cache[arg]
420 else:
416 else:
421 def f(*args):
417 def f(*args):
422 if args not in cache:
418 if args not in cache:
423 if len(cache) > 20:
419 if len(cache) > 20:
424 del cache[order.popleft()]
420 del cache[order.popleft()]
425 cache[args] = func(*args)
421 cache[args] = func(*args)
426 else:
422 else:
427 order.remove(args)
423 order.remove(args)
428 order.append(args)
424 order.append(args)
429 return cache[args]
425 return cache[args]
430
426
431 return f
427 return f
432
428
433 class propertycache(object):
429 class propertycache(object):
434 def __init__(self, func):
430 def __init__(self, func):
435 self.func = func
431 self.func = func
436 self.name = func.__name__
432 self.name = func.__name__
437 def __get__(self, obj, type=None):
433 def __get__(self, obj, type=None):
438 result = self.func(obj)
434 result = self.func(obj)
439 self.cachevalue(obj, result)
435 self.cachevalue(obj, result)
440 return result
436 return result
441
437
442 def cachevalue(self, obj, value):
438 def cachevalue(self, obj, value):
443 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
439 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
444 obj.__dict__[self.name] = value
440 obj.__dict__[self.name] = value
445
441
446 def pipefilter(s, cmd):
442 def pipefilter(s, cmd):
447 '''filter string S through command CMD, returning its output'''
443 '''filter string S through command CMD, returning its output'''
448 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
444 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
449 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
445 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
450 pout, perr = p.communicate(s)
446 pout, perr = p.communicate(s)
451 return pout
447 return pout
452
448
453 def tempfilter(s, cmd):
449 def tempfilter(s, cmd):
454 '''filter string S through a pair of temporary files with CMD.
450 '''filter string S through a pair of temporary files with CMD.
455 CMD is used as a template to create the real command to be run,
451 CMD is used as a template to create the real command to be run,
456 with the strings INFILE and OUTFILE replaced by the real names of
452 with the strings INFILE and OUTFILE replaced by the real names of
457 the temporary files generated.'''
453 the temporary files generated.'''
458 inname, outname = None, None
454 inname, outname = None, None
459 try:
455 try:
460 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
456 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
461 fp = os.fdopen(infd, 'wb')
457 fp = os.fdopen(infd, 'wb')
462 fp.write(s)
458 fp.write(s)
463 fp.close()
459 fp.close()
464 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
460 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
465 os.close(outfd)
461 os.close(outfd)
466 cmd = cmd.replace('INFILE', inname)
462 cmd = cmd.replace('INFILE', inname)
467 cmd = cmd.replace('OUTFILE', outname)
463 cmd = cmd.replace('OUTFILE', outname)
468 code = os.system(cmd)
464 code = os.system(cmd)
469 if sys.platform == 'OpenVMS' and code & 1:
465 if sys.platform == 'OpenVMS' and code & 1:
470 code = 0
466 code = 0
471 if code:
467 if code:
472 raise Abort(_("command '%s' failed: %s") %
468 raise Abort(_("command '%s' failed: %s") %
473 (cmd, explainexit(code)))
469 (cmd, explainexit(code)))
474 fp = open(outname, 'rb')
470 fp = open(outname, 'rb')
475 r = fp.read()
471 r = fp.read()
476 fp.close()
472 fp.close()
477 return r
473 return r
478 finally:
474 finally:
479 try:
475 try:
480 if inname:
476 if inname:
481 os.unlink(inname)
477 os.unlink(inname)
482 except OSError:
478 except OSError:
483 pass
479 pass
484 try:
480 try:
485 if outname:
481 if outname:
486 os.unlink(outname)
482 os.unlink(outname)
487 except OSError:
483 except OSError:
488 pass
484 pass
489
485
490 filtertable = {
486 filtertable = {
491 'tempfile:': tempfilter,
487 'tempfile:': tempfilter,
492 'pipe:': pipefilter,
488 'pipe:': pipefilter,
493 }
489 }
494
490
495 def filter(s, cmd):
491 def filter(s, cmd):
496 "filter a string through a command that transforms its input to its output"
492 "filter a string through a command that transforms its input to its output"
497 for name, fn in filtertable.iteritems():
493 for name, fn in filtertable.iteritems():
498 if cmd.startswith(name):
494 if cmd.startswith(name):
499 return fn(s, cmd[len(name):].lstrip())
495 return fn(s, cmd[len(name):].lstrip())
500 return pipefilter(s, cmd)
496 return pipefilter(s, cmd)
501
497
502 def binary(s):
498 def binary(s):
503 """return true if a string is binary data"""
499 """return true if a string is binary data"""
504 return bool(s and '\0' in s)
500 return bool(s and '\0' in s)
505
501
506 def increasingchunks(source, min=1024, max=65536):
502 def increasingchunks(source, min=1024, max=65536):
507 '''return no less than min bytes per chunk while data remains,
503 '''return no less than min bytes per chunk while data remains,
508 doubling min after each chunk until it reaches max'''
504 doubling min after each chunk until it reaches max'''
509 def log2(x):
505 def log2(x):
510 if not x:
506 if not x:
511 return 0
507 return 0
512 i = 0
508 i = 0
513 while x:
509 while x:
514 x >>= 1
510 x >>= 1
515 i += 1
511 i += 1
516 return i - 1
512 return i - 1
517
513
518 buf = []
514 buf = []
519 blen = 0
515 blen = 0
520 for chunk in source:
516 for chunk in source:
521 buf.append(chunk)
517 buf.append(chunk)
522 blen += len(chunk)
518 blen += len(chunk)
523 if blen >= min:
519 if blen >= min:
524 if min < max:
520 if min < max:
525 min = min << 1
521 min = min << 1
526 nmin = 1 << log2(blen)
522 nmin = 1 << log2(blen)
527 if nmin > min:
523 if nmin > min:
528 min = nmin
524 min = nmin
529 if min > max:
525 if min > max:
530 min = max
526 min = max
531 yield ''.join(buf)
527 yield ''.join(buf)
532 blen = 0
528 blen = 0
533 buf = []
529 buf = []
534 if buf:
530 if buf:
535 yield ''.join(buf)
531 yield ''.join(buf)
536
532
537 Abort = error.Abort
533 Abort = error.Abort
538
534
539 def always(fn):
535 def always(fn):
540 return True
536 return True
541
537
542 def never(fn):
538 def never(fn):
543 return False
539 return False
544
540
545 def nogc(func):
541 def nogc(func):
546 """disable garbage collector
542 """disable garbage collector
547
543
548 Python's garbage collector triggers a GC each time a certain number of
544 Python's garbage collector triggers a GC each time a certain number of
549 container objects (the number being defined by gc.get_threshold()) are
545 container objects (the number being defined by gc.get_threshold()) are
550 allocated even when marked not to be tracked by the collector. Tracking has
546 allocated even when marked not to be tracked by the collector. Tracking has
551 no effect on when GCs are triggered, only on what objects the GC looks
547 no effect on when GCs are triggered, only on what objects the GC looks
552 into. As a workaround, disable GC while building complex (huge)
548 into. As a workaround, disable GC while building complex (huge)
553 containers.
549 containers.
554
550
555 This garbage collector issue have been fixed in 2.7.
551 This garbage collector issue have been fixed in 2.7.
556 """
552 """
557 def wrapper(*args, **kwargs):
553 def wrapper(*args, **kwargs):
558 gcenabled = gc.isenabled()
554 gcenabled = gc.isenabled()
559 gc.disable()
555 gc.disable()
560 try:
556 try:
561 return func(*args, **kwargs)
557 return func(*args, **kwargs)
562 finally:
558 finally:
563 if gcenabled:
559 if gcenabled:
564 gc.enable()
560 gc.enable()
565 return wrapper
561 return wrapper
566
562
567 def pathto(root, n1, n2):
563 def pathto(root, n1, n2):
568 '''return the relative path from one place to another.
564 '''return the relative path from one place to another.
569 root should use os.sep to separate directories
565 root should use os.sep to separate directories
570 n1 should use os.sep to separate directories
566 n1 should use os.sep to separate directories
571 n2 should use "/" to separate directories
567 n2 should use "/" to separate directories
572 returns an os.sep-separated path.
568 returns an os.sep-separated path.
573
569
574 If n1 is a relative path, it's assumed it's
570 If n1 is a relative path, it's assumed it's
575 relative to root.
571 relative to root.
576 n2 should always be relative to root.
572 n2 should always be relative to root.
577 '''
573 '''
578 if not n1:
574 if not n1:
579 return localpath(n2)
575 return localpath(n2)
580 if os.path.isabs(n1):
576 if os.path.isabs(n1):
581 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
577 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
582 return os.path.join(root, localpath(n2))
578 return os.path.join(root, localpath(n2))
583 n2 = '/'.join((pconvert(root), n2))
579 n2 = '/'.join((pconvert(root), n2))
584 a, b = splitpath(n1), n2.split('/')
580 a, b = splitpath(n1), n2.split('/')
585 a.reverse()
581 a.reverse()
586 b.reverse()
582 b.reverse()
587 while a and b and a[-1] == b[-1]:
583 while a and b and a[-1] == b[-1]:
588 a.pop()
584 a.pop()
589 b.pop()
585 b.pop()
590 b.reverse()
586 b.reverse()
591 return os.sep.join((['..'] * len(a)) + b) or '.'
587 return os.sep.join((['..'] * len(a)) + b) or '.'
592
588
593 def mainfrozen():
589 def mainfrozen():
594 """return True if we are a frozen executable.
590 """return True if we are a frozen executable.
595
591
596 The code supports py2exe (most common, Windows only) and tools/freeze
592 The code supports py2exe (most common, Windows only) and tools/freeze
597 (portable, not much used).
593 (portable, not much used).
598 """
594 """
599 return (safehasattr(sys, "frozen") or # new py2exe
595 return (safehasattr(sys, "frozen") or # new py2exe
600 safehasattr(sys, "importers") or # old py2exe
596 safehasattr(sys, "importers") or # old py2exe
601 imp.is_frozen("__main__")) # tools/freeze
597 imp.is_frozen("__main__")) # tools/freeze
602
598
603 # the location of data files matching the source code
599 # the location of data files matching the source code
604 if mainfrozen():
600 if mainfrozen():
605 # executable version (py2exe) doesn't support __file__
601 # executable version (py2exe) doesn't support __file__
606 datapath = os.path.dirname(sys.executable)
602 datapath = os.path.dirname(sys.executable)
607 else:
603 else:
608 datapath = os.path.dirname(__file__)
604 datapath = os.path.dirname(__file__)
609
605
610 i18n.setdatapath(datapath)
606 i18n.setdatapath(datapath)
611
607
612 _hgexecutable = None
608 _hgexecutable = None
613
609
614 def hgexecutable():
610 def hgexecutable():
615 """return location of the 'hg' executable.
611 """return location of the 'hg' executable.
616
612
617 Defaults to $HG or 'hg' in the search path.
613 Defaults to $HG or 'hg' in the search path.
618 """
614 """
619 if _hgexecutable is None:
615 if _hgexecutable is None:
620 hg = os.environ.get('HG')
616 hg = os.environ.get('HG')
621 mainmod = sys.modules['__main__']
617 mainmod = sys.modules['__main__']
622 if hg:
618 if hg:
623 _sethgexecutable(hg)
619 _sethgexecutable(hg)
624 elif mainfrozen():
620 elif mainfrozen():
625 _sethgexecutable(sys.executable)
621 _sethgexecutable(sys.executable)
626 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
622 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
627 _sethgexecutable(mainmod.__file__)
623 _sethgexecutable(mainmod.__file__)
628 else:
624 else:
629 exe = findexe('hg') or os.path.basename(sys.argv[0])
625 exe = findexe('hg') or os.path.basename(sys.argv[0])
630 _sethgexecutable(exe)
626 _sethgexecutable(exe)
631 return _hgexecutable
627 return _hgexecutable
632
628
633 def _sethgexecutable(path):
629 def _sethgexecutable(path):
634 """set location of the 'hg' executable"""
630 """set location of the 'hg' executable"""
635 global _hgexecutable
631 global _hgexecutable
636 _hgexecutable = path
632 _hgexecutable = path
637
633
638 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
634 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
639 '''enhanced shell command execution.
635 '''enhanced shell command execution.
640 run with environment maybe modified, maybe in different dir.
636 run with environment maybe modified, maybe in different dir.
641
637
642 if command fails and onerr is None, return status, else raise onerr
638 if command fails and onerr is None, return status, else raise onerr
643 object as exception.
639 object as exception.
644
640
645 if out is specified, it is assumed to be a file-like object that has a
641 if out is specified, it is assumed to be a file-like object that has a
646 write() method. stdout and stderr will be redirected to out.'''
642 write() method. stdout and stderr will be redirected to out.'''
647 try:
643 try:
648 sys.stdout.flush()
644 sys.stdout.flush()
649 except Exception:
645 except Exception:
650 pass
646 pass
651 def py2shell(val):
647 def py2shell(val):
652 'convert python object into string that is useful to shell'
648 'convert python object into string that is useful to shell'
653 if val is None or val is False:
649 if val is None or val is False:
654 return '0'
650 return '0'
655 if val is True:
651 if val is True:
656 return '1'
652 return '1'
657 return str(val)
653 return str(val)
658 origcmd = cmd
654 origcmd = cmd
659 cmd = quotecommand(cmd)
655 cmd = quotecommand(cmd)
660 if sys.platform == 'plan9' and (sys.version_info[0] == 2
656 if sys.platform == 'plan9' and (sys.version_info[0] == 2
661 and sys.version_info[1] < 7):
657 and sys.version_info[1] < 7):
662 # subprocess kludge to work around issues in half-baked Python
658 # subprocess kludge to work around issues in half-baked Python
663 # ports, notably bichued/python:
659 # ports, notably bichued/python:
664 if not cwd is None:
660 if not cwd is None:
665 os.chdir(cwd)
661 os.chdir(cwd)
666 rc = os.system(cmd)
662 rc = os.system(cmd)
667 else:
663 else:
668 env = dict(os.environ)
664 env = dict(os.environ)
669 env.update((k, py2shell(v)) for k, v in environ.iteritems())
665 env.update((k, py2shell(v)) for k, v in environ.iteritems())
670 env['HG'] = hgexecutable()
666 env['HG'] = hgexecutable()
671 if out is None or out == sys.__stdout__:
667 if out is None or out == sys.__stdout__:
672 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
668 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
673 env=env, cwd=cwd)
669 env=env, cwd=cwd)
674 else:
670 else:
675 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
671 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
676 env=env, cwd=cwd, stdout=subprocess.PIPE,
672 env=env, cwd=cwd, stdout=subprocess.PIPE,
677 stderr=subprocess.STDOUT)
673 stderr=subprocess.STDOUT)
678 while True:
674 while True:
679 line = proc.stdout.readline()
675 line = proc.stdout.readline()
680 if not line:
676 if not line:
681 break
677 break
682 out.write(line)
678 out.write(line)
683 proc.wait()
679 proc.wait()
684 rc = proc.returncode
680 rc = proc.returncode
685 if sys.platform == 'OpenVMS' and rc & 1:
681 if sys.platform == 'OpenVMS' and rc & 1:
686 rc = 0
682 rc = 0
687 if rc and onerr:
683 if rc and onerr:
688 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
684 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
689 explainexit(rc)[0])
685 explainexit(rc)[0])
690 if errprefix:
686 if errprefix:
691 errmsg = '%s: %s' % (errprefix, errmsg)
687 errmsg = '%s: %s' % (errprefix, errmsg)
692 raise onerr(errmsg)
688 raise onerr(errmsg)
693 return rc
689 return rc
694
690
695 def checksignature(func):
691 def checksignature(func):
696 '''wrap a function with code to check for calling errors'''
692 '''wrap a function with code to check for calling errors'''
697 def check(*args, **kwargs):
693 def check(*args, **kwargs):
698 try:
694 try:
699 return func(*args, **kwargs)
695 return func(*args, **kwargs)
700 except TypeError:
696 except TypeError:
701 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
697 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
702 raise error.SignatureError
698 raise error.SignatureError
703 raise
699 raise
704
700
705 return check
701 return check
706
702
707 def copyfile(src, dest, hardlink=False):
703 def copyfile(src, dest, hardlink=False):
708 "copy a file, preserving mode and atime/mtime"
704 "copy a file, preserving mode and atime/mtime"
709 if os.path.lexists(dest):
705 if os.path.lexists(dest):
710 unlink(dest)
706 unlink(dest)
711 # hardlinks are problematic on CIFS, quietly ignore this flag
707 # hardlinks are problematic on CIFS, quietly ignore this flag
712 # until we find a way to work around it cleanly (issue4546)
708 # until we find a way to work around it cleanly (issue4546)
713 if False and hardlink:
709 if False and hardlink:
714 try:
710 try:
715 oslink(src, dest)
711 oslink(src, dest)
716 return
712 return
717 except (IOError, OSError):
713 except (IOError, OSError):
718 pass # fall back to normal copy
714 pass # fall back to normal copy
719 if os.path.islink(src):
715 if os.path.islink(src):
720 os.symlink(os.readlink(src), dest)
716 os.symlink(os.readlink(src), dest)
721 else:
717 else:
722 try:
718 try:
723 shutil.copyfile(src, dest)
719 shutil.copyfile(src, dest)
724 shutil.copymode(src, dest)
720 shutil.copymode(src, dest)
725 except shutil.Error, inst:
721 except shutil.Error, inst:
726 raise Abort(str(inst))
722 raise Abort(str(inst))
727
723
728 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
724 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
729 """Copy a directory tree using hardlinks if possible."""
725 """Copy a directory tree using hardlinks if possible."""
730 num = 0
726 num = 0
731
727
732 if hardlink is None:
728 if hardlink is None:
733 hardlink = (os.stat(src).st_dev ==
729 hardlink = (os.stat(src).st_dev ==
734 os.stat(os.path.dirname(dst)).st_dev)
730 os.stat(os.path.dirname(dst)).st_dev)
735 if hardlink:
731 if hardlink:
736 topic = _('linking')
732 topic = _('linking')
737 else:
733 else:
738 topic = _('copying')
734 topic = _('copying')
739
735
740 if os.path.isdir(src):
736 if os.path.isdir(src):
741 os.mkdir(dst)
737 os.mkdir(dst)
742 for name, kind in osutil.listdir(src):
738 for name, kind in osutil.listdir(src):
743 srcname = os.path.join(src, name)
739 srcname = os.path.join(src, name)
744 dstname = os.path.join(dst, name)
740 dstname = os.path.join(dst, name)
745 def nprog(t, pos):
741 def nprog(t, pos):
746 if pos is not None:
742 if pos is not None:
747 return progress(t, pos + num)
743 return progress(t, pos + num)
748 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
744 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
749 num += n
745 num += n
750 else:
746 else:
751 if hardlink:
747 if hardlink:
752 try:
748 try:
753 oslink(src, dst)
749 oslink(src, dst)
754 except (IOError, OSError):
750 except (IOError, OSError):
755 hardlink = False
751 hardlink = False
756 shutil.copy(src, dst)
752 shutil.copy(src, dst)
757 else:
753 else:
758 shutil.copy(src, dst)
754 shutil.copy(src, dst)
759 num += 1
755 num += 1
760 progress(topic, num)
756 progress(topic, num)
761 progress(topic, None)
757 progress(topic, None)
762
758
763 return hardlink, num
759 return hardlink, num
764
760
765 _winreservednames = '''con prn aux nul
761 _winreservednames = '''con prn aux nul
766 com1 com2 com3 com4 com5 com6 com7 com8 com9
762 com1 com2 com3 com4 com5 com6 com7 com8 com9
767 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
763 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
768 _winreservedchars = ':*?"<>|'
764 _winreservedchars = ':*?"<>|'
769 def checkwinfilename(path):
765 def checkwinfilename(path):
770 r'''Check that the base-relative path is a valid filename on Windows.
766 r'''Check that the base-relative path is a valid filename on Windows.
771 Returns None if the path is ok, or a UI string describing the problem.
767 Returns None if the path is ok, or a UI string describing the problem.
772
768
773 >>> checkwinfilename("just/a/normal/path")
769 >>> checkwinfilename("just/a/normal/path")
774 >>> checkwinfilename("foo/bar/con.xml")
770 >>> checkwinfilename("foo/bar/con.xml")
775 "filename contains 'con', which is reserved on Windows"
771 "filename contains 'con', which is reserved on Windows"
776 >>> checkwinfilename("foo/con.xml/bar")
772 >>> checkwinfilename("foo/con.xml/bar")
777 "filename contains 'con', which is reserved on Windows"
773 "filename contains 'con', which is reserved on Windows"
778 >>> checkwinfilename("foo/bar/xml.con")
774 >>> checkwinfilename("foo/bar/xml.con")
779 >>> checkwinfilename("foo/bar/AUX/bla.txt")
775 >>> checkwinfilename("foo/bar/AUX/bla.txt")
780 "filename contains 'AUX', which is reserved on Windows"
776 "filename contains 'AUX', which is reserved on Windows"
781 >>> checkwinfilename("foo/bar/bla:.txt")
777 >>> checkwinfilename("foo/bar/bla:.txt")
782 "filename contains ':', which is reserved on Windows"
778 "filename contains ':', which is reserved on Windows"
783 >>> checkwinfilename("foo/bar/b\07la.txt")
779 >>> checkwinfilename("foo/bar/b\07la.txt")
784 "filename contains '\\x07', which is invalid on Windows"
780 "filename contains '\\x07', which is invalid on Windows"
785 >>> checkwinfilename("foo/bar/bla ")
781 >>> checkwinfilename("foo/bar/bla ")
786 "filename ends with ' ', which is not allowed on Windows"
782 "filename ends with ' ', which is not allowed on Windows"
787 >>> checkwinfilename("../bar")
783 >>> checkwinfilename("../bar")
788 >>> checkwinfilename("foo\\")
784 >>> checkwinfilename("foo\\")
789 "filename ends with '\\', which is invalid on Windows"
785 "filename ends with '\\', which is invalid on Windows"
790 >>> checkwinfilename("foo\\/bar")
786 >>> checkwinfilename("foo\\/bar")
791 "directory name ends with '\\', which is invalid on Windows"
787 "directory name ends with '\\', which is invalid on Windows"
792 '''
788 '''
793 if path.endswith('\\'):
789 if path.endswith('\\'):
794 return _("filename ends with '\\', which is invalid on Windows")
790 return _("filename ends with '\\', which is invalid on Windows")
795 if '\\/' in path:
791 if '\\/' in path:
796 return _("directory name ends with '\\', which is invalid on Windows")
792 return _("directory name ends with '\\', which is invalid on Windows")
797 for n in path.replace('\\', '/').split('/'):
793 for n in path.replace('\\', '/').split('/'):
798 if not n:
794 if not n:
799 continue
795 continue
800 for c in n:
796 for c in n:
801 if c in _winreservedchars:
797 if c in _winreservedchars:
802 return _("filename contains '%s', which is reserved "
798 return _("filename contains '%s', which is reserved "
803 "on Windows") % c
799 "on Windows") % c
804 if ord(c) <= 31:
800 if ord(c) <= 31:
805 return _("filename contains %r, which is invalid "
801 return _("filename contains %r, which is invalid "
806 "on Windows") % c
802 "on Windows") % c
807 base = n.split('.')[0]
803 base = n.split('.')[0]
808 if base and base.lower() in _winreservednames:
804 if base and base.lower() in _winreservednames:
809 return _("filename contains '%s', which is reserved "
805 return _("filename contains '%s', which is reserved "
810 "on Windows") % base
806 "on Windows") % base
811 t = n[-1]
807 t = n[-1]
812 if t in '. ' and n not in '..':
808 if t in '. ' and n not in '..':
813 return _("filename ends with '%s', which is not allowed "
809 return _("filename ends with '%s', which is not allowed "
814 "on Windows") % t
810 "on Windows") % t
815
811
816 if os.name == 'nt':
812 if os.name == 'nt':
817 checkosfilename = checkwinfilename
813 checkosfilename = checkwinfilename
818 else:
814 else:
819 checkosfilename = platform.checkosfilename
815 checkosfilename = platform.checkosfilename
820
816
821 def makelock(info, pathname):
817 def makelock(info, pathname):
822 try:
818 try:
823 return os.symlink(info, pathname)
819 return os.symlink(info, pathname)
824 except OSError, why:
820 except OSError, why:
825 if why.errno == errno.EEXIST:
821 if why.errno == errno.EEXIST:
826 raise
822 raise
827 except AttributeError: # no symlink in os
823 except AttributeError: # no symlink in os
828 pass
824 pass
829
825
830 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
826 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
831 os.write(ld, info)
827 os.write(ld, info)
832 os.close(ld)
828 os.close(ld)
833
829
834 def readlock(pathname):
830 def readlock(pathname):
835 try:
831 try:
836 return os.readlink(pathname)
832 return os.readlink(pathname)
837 except OSError, why:
833 except OSError, why:
838 if why.errno not in (errno.EINVAL, errno.ENOSYS):
834 if why.errno not in (errno.EINVAL, errno.ENOSYS):
839 raise
835 raise
840 except AttributeError: # no symlink in os
836 except AttributeError: # no symlink in os
841 pass
837 pass
842 fp = posixfile(pathname)
838 fp = posixfile(pathname)
843 r = fp.read()
839 r = fp.read()
844 fp.close()
840 fp.close()
845 return r
841 return r
846
842
847 def fstat(fp):
843 def fstat(fp):
848 '''stat file object that may not have fileno method.'''
844 '''stat file object that may not have fileno method.'''
849 try:
845 try:
850 return os.fstat(fp.fileno())
846 return os.fstat(fp.fileno())
851 except AttributeError:
847 except AttributeError:
852 return os.stat(fp.name)
848 return os.stat(fp.name)
853
849
854 # File system features
850 # File system features
855
851
856 def checkcase(path):
852 def checkcase(path):
857 """
853 """
858 Return true if the given path is on a case-sensitive filesystem
854 Return true if the given path is on a case-sensitive filesystem
859
855
860 Requires a path (like /foo/.hg) ending with a foldable final
856 Requires a path (like /foo/.hg) ending with a foldable final
861 directory component.
857 directory component.
862 """
858 """
863 s1 = os.lstat(path)
859 s1 = os.lstat(path)
864 d, b = os.path.split(path)
860 d, b = os.path.split(path)
865 b2 = b.upper()
861 b2 = b.upper()
866 if b == b2:
862 if b == b2:
867 b2 = b.lower()
863 b2 = b.lower()
868 if b == b2:
864 if b == b2:
869 return True # no evidence against case sensitivity
865 return True # no evidence against case sensitivity
870 p2 = os.path.join(d, b2)
866 p2 = os.path.join(d, b2)
871 try:
867 try:
872 s2 = os.lstat(p2)
868 s2 = os.lstat(p2)
873 if s2 == s1:
869 if s2 == s1:
874 return False
870 return False
875 return True
871 return True
876 except OSError:
872 except OSError:
877 return True
873 return True
878
874
879 try:
875 try:
880 import re2
876 import re2
881 _re2 = None
877 _re2 = None
882 except ImportError:
878 except ImportError:
883 _re2 = False
879 _re2 = False
884
880
885 class _re(object):
881 class _re(object):
886 def _checkre2(self):
882 def _checkre2(self):
887 global _re2
883 global _re2
888 try:
884 try:
889 # check if match works, see issue3964
885 # check if match works, see issue3964
890 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
886 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
891 except ImportError:
887 except ImportError:
892 _re2 = False
888 _re2 = False
893
889
894 def compile(self, pat, flags=0):
890 def compile(self, pat, flags=0):
895 '''Compile a regular expression, using re2 if possible
891 '''Compile a regular expression, using re2 if possible
896
892
897 For best performance, use only re2-compatible regexp features. The
893 For best performance, use only re2-compatible regexp features. The
898 only flags from the re module that are re2-compatible are
894 only flags from the re module that are re2-compatible are
899 IGNORECASE and MULTILINE.'''
895 IGNORECASE and MULTILINE.'''
900 if _re2 is None:
896 if _re2 is None:
901 self._checkre2()
897 self._checkre2()
902 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
898 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
903 if flags & remod.IGNORECASE:
899 if flags & remod.IGNORECASE:
904 pat = '(?i)' + pat
900 pat = '(?i)' + pat
905 if flags & remod.MULTILINE:
901 if flags & remod.MULTILINE:
906 pat = '(?m)' + pat
902 pat = '(?m)' + pat
907 try:
903 try:
908 return re2.compile(pat)
904 return re2.compile(pat)
909 except re2.error:
905 except re2.error:
910 pass
906 pass
911 return remod.compile(pat, flags)
907 return remod.compile(pat, flags)
912
908
913 @propertycache
909 @propertycache
914 def escape(self):
910 def escape(self):
915 '''Return the version of escape corresponding to self.compile.
911 '''Return the version of escape corresponding to self.compile.
916
912
917 This is imperfect because whether re2 or re is used for a particular
913 This is imperfect because whether re2 or re is used for a particular
918 function depends on the flags, etc, but it's the best we can do.
914 function depends on the flags, etc, but it's the best we can do.
919 '''
915 '''
920 global _re2
916 global _re2
921 if _re2 is None:
917 if _re2 is None:
922 self._checkre2()
918 self._checkre2()
923 if _re2:
919 if _re2:
924 return re2.escape
920 return re2.escape
925 else:
921 else:
926 return remod.escape
922 return remod.escape
927
923
928 re = _re()
924 re = _re()
929
925
930 _fspathcache = {}
926 _fspathcache = {}
931 def fspath(name, root):
927 def fspath(name, root):
932 '''Get name in the case stored in the filesystem
928 '''Get name in the case stored in the filesystem
933
929
934 The name should be relative to root, and be normcase-ed for efficiency.
930 The name should be relative to root, and be normcase-ed for efficiency.
935
931
936 Note that this function is unnecessary, and should not be
932 Note that this function is unnecessary, and should not be
937 called, for case-sensitive filesystems (simply because it's expensive).
933 called, for case-sensitive filesystems (simply because it's expensive).
938
934
939 The root should be normcase-ed, too.
935 The root should be normcase-ed, too.
940 '''
936 '''
941 def _makefspathcacheentry(dir):
937 def _makefspathcacheentry(dir):
942 return dict((normcase(n), n) for n in os.listdir(dir))
938 return dict((normcase(n), n) for n in os.listdir(dir))
943
939
944 seps = os.sep
940 seps = os.sep
945 if os.altsep:
941 if os.altsep:
946 seps = seps + os.altsep
942 seps = seps + os.altsep
947 # Protect backslashes. This gets silly very quickly.
943 # Protect backslashes. This gets silly very quickly.
948 seps.replace('\\','\\\\')
944 seps.replace('\\','\\\\')
949 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
945 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
950 dir = os.path.normpath(root)
946 dir = os.path.normpath(root)
951 result = []
947 result = []
952 for part, sep in pattern.findall(name):
948 for part, sep in pattern.findall(name):
953 if sep:
949 if sep:
954 result.append(sep)
950 result.append(sep)
955 continue
951 continue
956
952
957 if dir not in _fspathcache:
953 if dir not in _fspathcache:
958 _fspathcache[dir] = _makefspathcacheentry(dir)
954 _fspathcache[dir] = _makefspathcacheentry(dir)
959 contents = _fspathcache[dir]
955 contents = _fspathcache[dir]
960
956
961 found = contents.get(part)
957 found = contents.get(part)
962 if not found:
958 if not found:
963 # retry "once per directory" per "dirstate.walk" which
959 # retry "once per directory" per "dirstate.walk" which
964 # may take place for each patches of "hg qpush", for example
960 # may take place for each patches of "hg qpush", for example
965 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
961 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
966 found = contents.get(part)
962 found = contents.get(part)
967
963
968 result.append(found or part)
964 result.append(found or part)
969 dir = os.path.join(dir, part)
965 dir = os.path.join(dir, part)
970
966
971 return ''.join(result)
967 return ''.join(result)
972
968
973 def checknlink(testfile):
969 def checknlink(testfile):
974 '''check whether hardlink count reporting works properly'''
970 '''check whether hardlink count reporting works properly'''
975
971
976 # testfile may be open, so we need a separate file for checking to
972 # testfile may be open, so we need a separate file for checking to
977 # work around issue2543 (or testfile may get lost on Samba shares)
973 # work around issue2543 (or testfile may get lost on Samba shares)
978 f1 = testfile + ".hgtmp1"
974 f1 = testfile + ".hgtmp1"
979 if os.path.lexists(f1):
975 if os.path.lexists(f1):
980 return False
976 return False
981 try:
977 try:
982 posixfile(f1, 'w').close()
978 posixfile(f1, 'w').close()
983 except IOError:
979 except IOError:
984 return False
980 return False
985
981
986 f2 = testfile + ".hgtmp2"
982 f2 = testfile + ".hgtmp2"
987 fd = None
983 fd = None
988 try:
984 try:
989 oslink(f1, f2)
985 oslink(f1, f2)
990 # nlinks() may behave differently for files on Windows shares if
986 # nlinks() may behave differently for files on Windows shares if
991 # the file is open.
987 # the file is open.
992 fd = posixfile(f2)
988 fd = posixfile(f2)
993 return nlinks(f2) > 1
989 return nlinks(f2) > 1
994 except OSError:
990 except OSError:
995 return False
991 return False
996 finally:
992 finally:
997 if fd is not None:
993 if fd is not None:
998 fd.close()
994 fd.close()
999 for f in (f1, f2):
995 for f in (f1, f2):
1000 try:
996 try:
1001 os.unlink(f)
997 os.unlink(f)
1002 except OSError:
998 except OSError:
1003 pass
999 pass
1004
1000
1005 def endswithsep(path):
1001 def endswithsep(path):
1006 '''Check path ends with os.sep or os.altsep.'''
1002 '''Check path ends with os.sep or os.altsep.'''
1007 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1003 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1008
1004
1009 def splitpath(path):
1005 def splitpath(path):
1010 '''Split path by os.sep.
1006 '''Split path by os.sep.
1011 Note that this function does not use os.altsep because this is
1007 Note that this function does not use os.altsep because this is
1012 an alternative of simple "xxx.split(os.sep)".
1008 an alternative of simple "xxx.split(os.sep)".
1013 It is recommended to use os.path.normpath() before using this
1009 It is recommended to use os.path.normpath() before using this
1014 function if need.'''
1010 function if need.'''
1015 return path.split(os.sep)
1011 return path.split(os.sep)
1016
1012
1017 def gui():
1013 def gui():
1018 '''Are we running in a GUI?'''
1014 '''Are we running in a GUI?'''
1019 if sys.platform == 'darwin':
1015 if sys.platform == 'darwin':
1020 if 'SSH_CONNECTION' in os.environ:
1016 if 'SSH_CONNECTION' in os.environ:
1021 # handle SSH access to a box where the user is logged in
1017 # handle SSH access to a box where the user is logged in
1022 return False
1018 return False
1023 elif getattr(osutil, 'isgui', None):
1019 elif getattr(osutil, 'isgui', None):
1024 # check if a CoreGraphics session is available
1020 # check if a CoreGraphics session is available
1025 return osutil.isgui()
1021 return osutil.isgui()
1026 else:
1022 else:
1027 # pure build; use a safe default
1023 # pure build; use a safe default
1028 return True
1024 return True
1029 else:
1025 else:
1030 return os.name == "nt" or os.environ.get("DISPLAY")
1026 return os.name == "nt" or os.environ.get("DISPLAY")
1031
1027
1032 def mktempcopy(name, emptyok=False, createmode=None):
1028 def mktempcopy(name, emptyok=False, createmode=None):
1033 """Create a temporary file with the same contents from name
1029 """Create a temporary file with the same contents from name
1034
1030
1035 The permission bits are copied from the original file.
1031 The permission bits are copied from the original file.
1036
1032
1037 If the temporary file is going to be truncated immediately, you
1033 If the temporary file is going to be truncated immediately, you
1038 can use emptyok=True as an optimization.
1034 can use emptyok=True as an optimization.
1039
1035
1040 Returns the name of the temporary file.
1036 Returns the name of the temporary file.
1041 """
1037 """
1042 d, fn = os.path.split(name)
1038 d, fn = os.path.split(name)
1043 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1039 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1044 os.close(fd)
1040 os.close(fd)
1045 # Temporary files are created with mode 0600, which is usually not
1041 # Temporary files are created with mode 0600, which is usually not
1046 # what we want. If the original file already exists, just copy
1042 # what we want. If the original file already exists, just copy
1047 # its mode. Otherwise, manually obey umask.
1043 # its mode. Otherwise, manually obey umask.
1048 copymode(name, temp, createmode)
1044 copymode(name, temp, createmode)
1049 if emptyok:
1045 if emptyok:
1050 return temp
1046 return temp
1051 try:
1047 try:
1052 try:
1048 try:
1053 ifp = posixfile(name, "rb")
1049 ifp = posixfile(name, "rb")
1054 except IOError, inst:
1050 except IOError, inst:
1055 if inst.errno == errno.ENOENT:
1051 if inst.errno == errno.ENOENT:
1056 return temp
1052 return temp
1057 if not getattr(inst, 'filename', None):
1053 if not getattr(inst, 'filename', None):
1058 inst.filename = name
1054 inst.filename = name
1059 raise
1055 raise
1060 ofp = posixfile(temp, "wb")
1056 ofp = posixfile(temp, "wb")
1061 for chunk in filechunkiter(ifp):
1057 for chunk in filechunkiter(ifp):
1062 ofp.write(chunk)
1058 ofp.write(chunk)
1063 ifp.close()
1059 ifp.close()
1064 ofp.close()
1060 ofp.close()
1065 except: # re-raises
1061 except: # re-raises
1066 try: os.unlink(temp)
1062 try: os.unlink(temp)
1067 except OSError: pass
1063 except OSError: pass
1068 raise
1064 raise
1069 return temp
1065 return temp
1070
1066
1071 class atomictempfile(object):
1067 class atomictempfile(object):
1072 '''writable file object that atomically updates a file
1068 '''writable file object that atomically updates a file
1073
1069
1074 All writes will go to a temporary copy of the original file. Call
1070 All writes will go to a temporary copy of the original file. Call
1075 close() when you are done writing, and atomictempfile will rename
1071 close() when you are done writing, and atomictempfile will rename
1076 the temporary copy to the original name, making the changes
1072 the temporary copy to the original name, making the changes
1077 visible. If the object is destroyed without being closed, all your
1073 visible. If the object is destroyed without being closed, all your
1078 writes are discarded.
1074 writes are discarded.
1079 '''
1075 '''
1080 def __init__(self, name, mode='w+b', createmode=None):
1076 def __init__(self, name, mode='w+b', createmode=None):
1081 self.__name = name # permanent name
1077 self.__name = name # permanent name
1082 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1078 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1083 createmode=createmode)
1079 createmode=createmode)
1084 self._fp = posixfile(self._tempname, mode)
1080 self._fp = posixfile(self._tempname, mode)
1085
1081
1086 # delegated methods
1082 # delegated methods
1087 self.write = self._fp.write
1083 self.write = self._fp.write
1088 self.seek = self._fp.seek
1084 self.seek = self._fp.seek
1089 self.tell = self._fp.tell
1085 self.tell = self._fp.tell
1090 self.fileno = self._fp.fileno
1086 self.fileno = self._fp.fileno
1091
1087
1092 def close(self):
1088 def close(self):
1093 if not self._fp.closed:
1089 if not self._fp.closed:
1094 self._fp.close()
1090 self._fp.close()
1095 rename(self._tempname, localpath(self.__name))
1091 rename(self._tempname, localpath(self.__name))
1096
1092
1097 def discard(self):
1093 def discard(self):
1098 if not self._fp.closed:
1094 if not self._fp.closed:
1099 try:
1095 try:
1100 os.unlink(self._tempname)
1096 os.unlink(self._tempname)
1101 except OSError:
1097 except OSError:
1102 pass
1098 pass
1103 self._fp.close()
1099 self._fp.close()
1104
1100
1105 def __del__(self):
1101 def __del__(self):
1106 if safehasattr(self, '_fp'): # constructor actually did something
1102 if safehasattr(self, '_fp'): # constructor actually did something
1107 self.discard()
1103 self.discard()
1108
1104
1109 def makedirs(name, mode=None, notindexed=False):
1105 def makedirs(name, mode=None, notindexed=False):
1110 """recursive directory creation with parent mode inheritance"""
1106 """recursive directory creation with parent mode inheritance"""
1111 try:
1107 try:
1112 makedir(name, notindexed)
1108 makedir(name, notindexed)
1113 except OSError, err:
1109 except OSError, err:
1114 if err.errno == errno.EEXIST:
1110 if err.errno == errno.EEXIST:
1115 return
1111 return
1116 if err.errno != errno.ENOENT or not name:
1112 if err.errno != errno.ENOENT or not name:
1117 raise
1113 raise
1118 parent = os.path.dirname(os.path.abspath(name))
1114 parent = os.path.dirname(os.path.abspath(name))
1119 if parent == name:
1115 if parent == name:
1120 raise
1116 raise
1121 makedirs(parent, mode, notindexed)
1117 makedirs(parent, mode, notindexed)
1122 makedir(name, notindexed)
1118 makedir(name, notindexed)
1123 if mode is not None:
1119 if mode is not None:
1124 os.chmod(name, mode)
1120 os.chmod(name, mode)
1125
1121
1126 def ensuredirs(name, mode=None, notindexed=False):
1122 def ensuredirs(name, mode=None, notindexed=False):
1127 """race-safe recursive directory creation
1123 """race-safe recursive directory creation
1128
1124
1129 Newly created directories are marked as "not to be indexed by
1125 Newly created directories are marked as "not to be indexed by
1130 the content indexing service", if ``notindexed`` is specified
1126 the content indexing service", if ``notindexed`` is specified
1131 for "write" mode access.
1127 for "write" mode access.
1132 """
1128 """
1133 if os.path.isdir(name):
1129 if os.path.isdir(name):
1134 return
1130 return
1135 parent = os.path.dirname(os.path.abspath(name))
1131 parent = os.path.dirname(os.path.abspath(name))
1136 if parent != name:
1132 if parent != name:
1137 ensuredirs(parent, mode, notindexed)
1133 ensuredirs(parent, mode, notindexed)
1138 try:
1134 try:
1139 makedir(name, notindexed)
1135 makedir(name, notindexed)
1140 except OSError, err:
1136 except OSError, err:
1141 if err.errno == errno.EEXIST and os.path.isdir(name):
1137 if err.errno == errno.EEXIST and os.path.isdir(name):
1142 # someone else seems to have won a directory creation race
1138 # someone else seems to have won a directory creation race
1143 return
1139 return
1144 raise
1140 raise
1145 if mode is not None:
1141 if mode is not None:
1146 os.chmod(name, mode)
1142 os.chmod(name, mode)
1147
1143
1148 def readfile(path):
1144 def readfile(path):
1149 fp = open(path, 'rb')
1145 fp = open(path, 'rb')
1150 try:
1146 try:
1151 return fp.read()
1147 return fp.read()
1152 finally:
1148 finally:
1153 fp.close()
1149 fp.close()
1154
1150
1155 def writefile(path, text):
1151 def writefile(path, text):
1156 fp = open(path, 'wb')
1152 fp = open(path, 'wb')
1157 try:
1153 try:
1158 fp.write(text)
1154 fp.write(text)
1159 finally:
1155 finally:
1160 fp.close()
1156 fp.close()
1161
1157
1162 def appendfile(path, text):
1158 def appendfile(path, text):
1163 fp = open(path, 'ab')
1159 fp = open(path, 'ab')
1164 try:
1160 try:
1165 fp.write(text)
1161 fp.write(text)
1166 finally:
1162 finally:
1167 fp.close()
1163 fp.close()
1168
1164
1169 class chunkbuffer(object):
1165 class chunkbuffer(object):
1170 """Allow arbitrary sized chunks of data to be efficiently read from an
1166 """Allow arbitrary sized chunks of data to be efficiently read from an
1171 iterator over chunks of arbitrary size."""
1167 iterator over chunks of arbitrary size."""
1172
1168
1173 def __init__(self, in_iter):
1169 def __init__(self, in_iter):
1174 """in_iter is the iterator that's iterating over the input chunks.
1170 """in_iter is the iterator that's iterating over the input chunks.
1175 targetsize is how big a buffer to try to maintain."""
1171 targetsize is how big a buffer to try to maintain."""
1176 def splitbig(chunks):
1172 def splitbig(chunks):
1177 for chunk in chunks:
1173 for chunk in chunks:
1178 if len(chunk) > 2**20:
1174 if len(chunk) > 2**20:
1179 pos = 0
1175 pos = 0
1180 while pos < len(chunk):
1176 while pos < len(chunk):
1181 end = pos + 2 ** 18
1177 end = pos + 2 ** 18
1182 yield chunk[pos:end]
1178 yield chunk[pos:end]
1183 pos = end
1179 pos = end
1184 else:
1180 else:
1185 yield chunk
1181 yield chunk
1186 self.iter = splitbig(in_iter)
1182 self.iter = splitbig(in_iter)
1187 self._queue = collections.deque()
1183 self._queue = collections.deque()
1188
1184
1189 def read(self, l=None):
1185 def read(self, l=None):
1190 """Read L bytes of data from the iterator of chunks of data.
1186 """Read L bytes of data from the iterator of chunks of data.
1191 Returns less than L bytes if the iterator runs dry.
1187 Returns less than L bytes if the iterator runs dry.
1192
1188
1193 If size parameter is omitted, read everything"""
1189 If size parameter is omitted, read everything"""
1194 left = l
1190 left = l
1195 buf = []
1191 buf = []
1196 queue = self._queue
1192 queue = self._queue
1197 while left is None or left > 0:
1193 while left is None or left > 0:
1198 # refill the queue
1194 # refill the queue
1199 if not queue:
1195 if not queue:
1200 target = 2**18
1196 target = 2**18
1201 for chunk in self.iter:
1197 for chunk in self.iter:
1202 queue.append(chunk)
1198 queue.append(chunk)
1203 target -= len(chunk)
1199 target -= len(chunk)
1204 if target <= 0:
1200 if target <= 0:
1205 break
1201 break
1206 if not queue:
1202 if not queue:
1207 break
1203 break
1208
1204
1209 chunk = queue.popleft()
1205 chunk = queue.popleft()
1210 if left is not None:
1206 if left is not None:
1211 left -= len(chunk)
1207 left -= len(chunk)
1212 if left is not None and left < 0:
1208 if left is not None and left < 0:
1213 queue.appendleft(chunk[left:])
1209 queue.appendleft(chunk[left:])
1214 buf.append(chunk[:left])
1210 buf.append(chunk[:left])
1215 else:
1211 else:
1216 buf.append(chunk)
1212 buf.append(chunk)
1217
1213
1218 return ''.join(buf)
1214 return ''.join(buf)
1219
1215
1220 def filechunkiter(f, size=65536, limit=None):
1216 def filechunkiter(f, size=65536, limit=None):
1221 """Create a generator that produces the data in the file size
1217 """Create a generator that produces the data in the file size
1222 (default 65536) bytes at a time, up to optional limit (default is
1218 (default 65536) bytes at a time, up to optional limit (default is
1223 to read all data). Chunks may be less than size bytes if the
1219 to read all data). Chunks may be less than size bytes if the
1224 chunk is the last chunk in the file, or the file is a socket or
1220 chunk is the last chunk in the file, or the file is a socket or
1225 some other type of file that sometimes reads less data than is
1221 some other type of file that sometimes reads less data than is
1226 requested."""
1222 requested."""
1227 assert size >= 0
1223 assert size >= 0
1228 assert limit is None or limit >= 0
1224 assert limit is None or limit >= 0
1229 while True:
1225 while True:
1230 if limit is None:
1226 if limit is None:
1231 nbytes = size
1227 nbytes = size
1232 else:
1228 else:
1233 nbytes = min(limit, size)
1229 nbytes = min(limit, size)
1234 s = nbytes and f.read(nbytes)
1230 s = nbytes and f.read(nbytes)
1235 if not s:
1231 if not s:
1236 break
1232 break
1237 if limit:
1233 if limit:
1238 limit -= len(s)
1234 limit -= len(s)
1239 yield s
1235 yield s
1240
1236
1241 def makedate(timestamp=None):
1237 def makedate(timestamp=None):
1242 '''Return a unix timestamp (or the current time) as a (unixtime,
1238 '''Return a unix timestamp (or the current time) as a (unixtime,
1243 offset) tuple based off the local timezone.'''
1239 offset) tuple based off the local timezone.'''
1244 if timestamp is None:
1240 if timestamp is None:
1245 timestamp = time.time()
1241 timestamp = time.time()
1246 if timestamp < 0:
1242 if timestamp < 0:
1247 hint = _("check your clock")
1243 hint = _("check your clock")
1248 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1244 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1249 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1245 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1250 datetime.datetime.fromtimestamp(timestamp))
1246 datetime.datetime.fromtimestamp(timestamp))
1251 tz = delta.days * 86400 + delta.seconds
1247 tz = delta.days * 86400 + delta.seconds
1252 return timestamp, tz
1248 return timestamp, tz
1253
1249
1254 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1250 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1255 """represent a (unixtime, offset) tuple as a localized time.
1251 """represent a (unixtime, offset) tuple as a localized time.
1256 unixtime is seconds since the epoch, and offset is the time zone's
1252 unixtime is seconds since the epoch, and offset is the time zone's
1257 number of seconds away from UTC. if timezone is false, do not
1253 number of seconds away from UTC. if timezone is false, do not
1258 append time zone to string."""
1254 append time zone to string."""
1259 t, tz = date or makedate()
1255 t, tz = date or makedate()
1260 if t < 0:
1256 if t < 0:
1261 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1257 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1262 tz = 0
1258 tz = 0
1263 if "%1" in format or "%2" in format or "%z" in format:
1259 if "%1" in format or "%2" in format or "%z" in format:
1264 sign = (tz > 0) and "-" or "+"
1260 sign = (tz > 0) and "-" or "+"
1265 minutes = abs(tz) // 60
1261 minutes = abs(tz) // 60
1266 format = format.replace("%z", "%1%2")
1262 format = format.replace("%z", "%1%2")
1267 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1263 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1268 format = format.replace("%2", "%02d" % (minutes % 60))
1264 format = format.replace("%2", "%02d" % (minutes % 60))
1269 try:
1265 try:
1270 t = time.gmtime(float(t) - tz)
1266 t = time.gmtime(float(t) - tz)
1271 except ValueError:
1267 except ValueError:
1272 # time was out of range
1268 # time was out of range
1273 t = time.gmtime(sys.maxint)
1269 t = time.gmtime(sys.maxint)
1274 s = time.strftime(format, t)
1270 s = time.strftime(format, t)
1275 return s
1271 return s
1276
1272
1277 def shortdate(date=None):
1273 def shortdate(date=None):
1278 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1274 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1279 return datestr(date, format='%Y-%m-%d')
1275 return datestr(date, format='%Y-%m-%d')
1280
1276
1281 def strdate(string, format, defaults=[]):
1277 def strdate(string, format, defaults=[]):
1282 """parse a localized time string and return a (unixtime, offset) tuple.
1278 """parse a localized time string and return a (unixtime, offset) tuple.
1283 if the string cannot be parsed, ValueError is raised."""
1279 if the string cannot be parsed, ValueError is raised."""
1284 def timezone(string):
1280 def timezone(string):
1285 tz = string.split()[-1]
1281 tz = string.split()[-1]
1286 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1282 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1287 sign = (tz[0] == "+") and 1 or -1
1283 sign = (tz[0] == "+") and 1 or -1
1288 hours = int(tz[1:3])
1284 hours = int(tz[1:3])
1289 minutes = int(tz[3:5])
1285 minutes = int(tz[3:5])
1290 return -sign * (hours * 60 + minutes) * 60
1286 return -sign * (hours * 60 + minutes) * 60
1291 if tz == "GMT" or tz == "UTC":
1287 if tz == "GMT" or tz == "UTC":
1292 return 0
1288 return 0
1293 return None
1289 return None
1294
1290
1295 # NOTE: unixtime = localunixtime + offset
1291 # NOTE: unixtime = localunixtime + offset
1296 offset, date = timezone(string), string
1292 offset, date = timezone(string), string
1297 if offset is not None:
1293 if offset is not None:
1298 date = " ".join(string.split()[:-1])
1294 date = " ".join(string.split()[:-1])
1299
1295
1300 # add missing elements from defaults
1296 # add missing elements from defaults
1301 usenow = False # default to using biased defaults
1297 usenow = False # default to using biased defaults
1302 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1298 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1303 found = [True for p in part if ("%"+p) in format]
1299 found = [True for p in part if ("%"+p) in format]
1304 if not found:
1300 if not found:
1305 date += "@" + defaults[part][usenow]
1301 date += "@" + defaults[part][usenow]
1306 format += "@%" + part[0]
1302 format += "@%" + part[0]
1307 else:
1303 else:
1308 # We've found a specific time element, less specific time
1304 # We've found a specific time element, less specific time
1309 # elements are relative to today
1305 # elements are relative to today
1310 usenow = True
1306 usenow = True
1311
1307
1312 timetuple = time.strptime(date, format)
1308 timetuple = time.strptime(date, format)
1313 localunixtime = int(calendar.timegm(timetuple))
1309 localunixtime = int(calendar.timegm(timetuple))
1314 if offset is None:
1310 if offset is None:
1315 # local timezone
1311 # local timezone
1316 unixtime = int(time.mktime(timetuple))
1312 unixtime = int(time.mktime(timetuple))
1317 offset = unixtime - localunixtime
1313 offset = unixtime - localunixtime
1318 else:
1314 else:
1319 unixtime = localunixtime + offset
1315 unixtime = localunixtime + offset
1320 return unixtime, offset
1316 return unixtime, offset
1321
1317
1322 def parsedate(date, formats=None, bias={}):
1318 def parsedate(date, formats=None, bias={}):
1323 """parse a localized date/time and return a (unixtime, offset) tuple.
1319 """parse a localized date/time and return a (unixtime, offset) tuple.
1324
1320
1325 The date may be a "unixtime offset" string or in one of the specified
1321 The date may be a "unixtime offset" string or in one of the specified
1326 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1322 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1327
1323
1328 >>> parsedate(' today ') == parsedate(\
1324 >>> parsedate(' today ') == parsedate(\
1329 datetime.date.today().strftime('%b %d'))
1325 datetime.date.today().strftime('%b %d'))
1330 True
1326 True
1331 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1327 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1332 datetime.timedelta(days=1)\
1328 datetime.timedelta(days=1)\
1333 ).strftime('%b %d'))
1329 ).strftime('%b %d'))
1334 True
1330 True
1335 >>> now, tz = makedate()
1331 >>> now, tz = makedate()
1336 >>> strnow, strtz = parsedate('now')
1332 >>> strnow, strtz = parsedate('now')
1337 >>> (strnow - now) < 1
1333 >>> (strnow - now) < 1
1338 True
1334 True
1339 >>> tz == strtz
1335 >>> tz == strtz
1340 True
1336 True
1341 """
1337 """
1342 if not date:
1338 if not date:
1343 return 0, 0
1339 return 0, 0
1344 if isinstance(date, tuple) and len(date) == 2:
1340 if isinstance(date, tuple) and len(date) == 2:
1345 return date
1341 return date
1346 if not formats:
1342 if not formats:
1347 formats = defaultdateformats
1343 formats = defaultdateformats
1348 date = date.strip()
1344 date = date.strip()
1349
1345
1350 if date == 'now' or date == _('now'):
1346 if date == 'now' or date == _('now'):
1351 return makedate()
1347 return makedate()
1352 if date == 'today' or date == _('today'):
1348 if date == 'today' or date == _('today'):
1353 date = datetime.date.today().strftime('%b %d')
1349 date = datetime.date.today().strftime('%b %d')
1354 elif date == 'yesterday' or date == _('yesterday'):
1350 elif date == 'yesterday' or date == _('yesterday'):
1355 date = (datetime.date.today() -
1351 date = (datetime.date.today() -
1356 datetime.timedelta(days=1)).strftime('%b %d')
1352 datetime.timedelta(days=1)).strftime('%b %d')
1357
1353
1358 try:
1354 try:
1359 when, offset = map(int, date.split(' '))
1355 when, offset = map(int, date.split(' '))
1360 except ValueError:
1356 except ValueError:
1361 # fill out defaults
1357 # fill out defaults
1362 now = makedate()
1358 now = makedate()
1363 defaults = {}
1359 defaults = {}
1364 for part in ("d", "mb", "yY", "HI", "M", "S"):
1360 for part in ("d", "mb", "yY", "HI", "M", "S"):
1365 # this piece is for rounding the specific end of unknowns
1361 # this piece is for rounding the specific end of unknowns
1366 b = bias.get(part)
1362 b = bias.get(part)
1367 if b is None:
1363 if b is None:
1368 if part[0] in "HMS":
1364 if part[0] in "HMS":
1369 b = "00"
1365 b = "00"
1370 else:
1366 else:
1371 b = "0"
1367 b = "0"
1372
1368
1373 # this piece is for matching the generic end to today's date
1369 # this piece is for matching the generic end to today's date
1374 n = datestr(now, "%" + part[0])
1370 n = datestr(now, "%" + part[0])
1375
1371
1376 defaults[part] = (b, n)
1372 defaults[part] = (b, n)
1377
1373
1378 for format in formats:
1374 for format in formats:
1379 try:
1375 try:
1380 when, offset = strdate(date, format, defaults)
1376 when, offset = strdate(date, format, defaults)
1381 except (ValueError, OverflowError):
1377 except (ValueError, OverflowError):
1382 pass
1378 pass
1383 else:
1379 else:
1384 break
1380 break
1385 else:
1381 else:
1386 raise Abort(_('invalid date: %r') % date)
1382 raise Abort(_('invalid date: %r') % date)
1387 # validate explicit (probably user-specified) date and
1383 # validate explicit (probably user-specified) date and
1388 # time zone offset. values must fit in signed 32 bits for
1384 # time zone offset. values must fit in signed 32 bits for
1389 # current 32-bit linux runtimes. timezones go from UTC-12
1385 # current 32-bit linux runtimes. timezones go from UTC-12
1390 # to UTC+14
1386 # to UTC+14
1391 if abs(when) > 0x7fffffff:
1387 if abs(when) > 0x7fffffff:
1392 raise Abort(_('date exceeds 32 bits: %d') % when)
1388 raise Abort(_('date exceeds 32 bits: %d') % when)
1393 if when < 0:
1389 if when < 0:
1394 raise Abort(_('negative date value: %d') % when)
1390 raise Abort(_('negative date value: %d') % when)
1395 if offset < -50400 or offset > 43200:
1391 if offset < -50400 or offset > 43200:
1396 raise Abort(_('impossible time zone offset: %d') % offset)
1392 raise Abort(_('impossible time zone offset: %d') % offset)
1397 return when, offset
1393 return when, offset
1398
1394
1399 def matchdate(date):
1395 def matchdate(date):
1400 """Return a function that matches a given date match specifier
1396 """Return a function that matches a given date match specifier
1401
1397
1402 Formats include:
1398 Formats include:
1403
1399
1404 '{date}' match a given date to the accuracy provided
1400 '{date}' match a given date to the accuracy provided
1405
1401
1406 '<{date}' on or before a given date
1402 '<{date}' on or before a given date
1407
1403
1408 '>{date}' on or after a given date
1404 '>{date}' on or after a given date
1409
1405
1410 >>> p1 = parsedate("10:29:59")
1406 >>> p1 = parsedate("10:29:59")
1411 >>> p2 = parsedate("10:30:00")
1407 >>> p2 = parsedate("10:30:00")
1412 >>> p3 = parsedate("10:30:59")
1408 >>> p3 = parsedate("10:30:59")
1413 >>> p4 = parsedate("10:31:00")
1409 >>> p4 = parsedate("10:31:00")
1414 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1410 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1415 >>> f = matchdate("10:30")
1411 >>> f = matchdate("10:30")
1416 >>> f(p1[0])
1412 >>> f(p1[0])
1417 False
1413 False
1418 >>> f(p2[0])
1414 >>> f(p2[0])
1419 True
1415 True
1420 >>> f(p3[0])
1416 >>> f(p3[0])
1421 True
1417 True
1422 >>> f(p4[0])
1418 >>> f(p4[0])
1423 False
1419 False
1424 >>> f(p5[0])
1420 >>> f(p5[0])
1425 False
1421 False
1426 """
1422 """
1427
1423
1428 def lower(date):
1424 def lower(date):
1429 d = {'mb': "1", 'd': "1"}
1425 d = {'mb': "1", 'd': "1"}
1430 return parsedate(date, extendeddateformats, d)[0]
1426 return parsedate(date, extendeddateformats, d)[0]
1431
1427
1432 def upper(date):
1428 def upper(date):
1433 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1429 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1434 for days in ("31", "30", "29"):
1430 for days in ("31", "30", "29"):
1435 try:
1431 try:
1436 d["d"] = days
1432 d["d"] = days
1437 return parsedate(date, extendeddateformats, d)[0]
1433 return parsedate(date, extendeddateformats, d)[0]
1438 except Abort:
1434 except Abort:
1439 pass
1435 pass
1440 d["d"] = "28"
1436 d["d"] = "28"
1441 return parsedate(date, extendeddateformats, d)[0]
1437 return parsedate(date, extendeddateformats, d)[0]
1442
1438
1443 date = date.strip()
1439 date = date.strip()
1444
1440
1445 if not date:
1441 if not date:
1446 raise Abort(_("dates cannot consist entirely of whitespace"))
1442 raise Abort(_("dates cannot consist entirely of whitespace"))
1447 elif date[0] == "<":
1443 elif date[0] == "<":
1448 if not date[1:]:
1444 if not date[1:]:
1449 raise Abort(_("invalid day spec, use '<DATE'"))
1445 raise Abort(_("invalid day spec, use '<DATE'"))
1450 when = upper(date[1:])
1446 when = upper(date[1:])
1451 return lambda x: x <= when
1447 return lambda x: x <= when
1452 elif date[0] == ">":
1448 elif date[0] == ">":
1453 if not date[1:]:
1449 if not date[1:]:
1454 raise Abort(_("invalid day spec, use '>DATE'"))
1450 raise Abort(_("invalid day spec, use '>DATE'"))
1455 when = lower(date[1:])
1451 when = lower(date[1:])
1456 return lambda x: x >= when
1452 return lambda x: x >= when
1457 elif date[0] == "-":
1453 elif date[0] == "-":
1458 try:
1454 try:
1459 days = int(date[1:])
1455 days = int(date[1:])
1460 except ValueError:
1456 except ValueError:
1461 raise Abort(_("invalid day spec: %s") % date[1:])
1457 raise Abort(_("invalid day spec: %s") % date[1:])
1462 if days < 0:
1458 if days < 0:
1463 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1459 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1464 % date[1:])
1460 % date[1:])
1465 when = makedate()[0] - days * 3600 * 24
1461 when = makedate()[0] - days * 3600 * 24
1466 return lambda x: x >= when
1462 return lambda x: x >= when
1467 elif " to " in date:
1463 elif " to " in date:
1468 a, b = date.split(" to ")
1464 a, b = date.split(" to ")
1469 start, stop = lower(a), upper(b)
1465 start, stop = lower(a), upper(b)
1470 return lambda x: x >= start and x <= stop
1466 return lambda x: x >= start and x <= stop
1471 else:
1467 else:
1472 start, stop = lower(date), upper(date)
1468 start, stop = lower(date), upper(date)
1473 return lambda x: x >= start and x <= stop
1469 return lambda x: x >= start and x <= stop
1474
1470
1475 def shortuser(user):
1471 def shortuser(user):
1476 """Return a short representation of a user name or email address."""
1472 """Return a short representation of a user name or email address."""
1477 f = user.find('@')
1473 f = user.find('@')
1478 if f >= 0:
1474 if f >= 0:
1479 user = user[:f]
1475 user = user[:f]
1480 f = user.find('<')
1476 f = user.find('<')
1481 if f >= 0:
1477 if f >= 0:
1482 user = user[f + 1:]
1478 user = user[f + 1:]
1483 f = user.find(' ')
1479 f = user.find(' ')
1484 if f >= 0:
1480 if f >= 0:
1485 user = user[:f]
1481 user = user[:f]
1486 f = user.find('.')
1482 f = user.find('.')
1487 if f >= 0:
1483 if f >= 0:
1488 user = user[:f]
1484 user = user[:f]
1489 return user
1485 return user
1490
1486
1491 def emailuser(user):
1487 def emailuser(user):
1492 """Return the user portion of an email address."""
1488 """Return the user portion of an email address."""
1493 f = user.find('@')
1489 f = user.find('@')
1494 if f >= 0:
1490 if f >= 0:
1495 user = user[:f]
1491 user = user[:f]
1496 f = user.find('<')
1492 f = user.find('<')
1497 if f >= 0:
1493 if f >= 0:
1498 user = user[f + 1:]
1494 user = user[f + 1:]
1499 return user
1495 return user
1500
1496
1501 def email(author):
1497 def email(author):
1502 '''get email of author.'''
1498 '''get email of author.'''
1503 r = author.find('>')
1499 r = author.find('>')
1504 if r == -1:
1500 if r == -1:
1505 r = None
1501 r = None
1506 return author[author.find('<') + 1:r]
1502 return author[author.find('<') + 1:r]
1507
1503
1508 def ellipsis(text, maxlength=400):
1504 def ellipsis(text, maxlength=400):
1509 """Trim string to at most maxlength (default: 400) columns in display."""
1505 """Trim string to at most maxlength (default: 400) columns in display."""
1510 return encoding.trim(text, maxlength, ellipsis='...')
1506 return encoding.trim(text, maxlength, ellipsis='...')
1511
1507
1512 def unitcountfn(*unittable):
1508 def unitcountfn(*unittable):
1513 '''return a function that renders a readable count of some quantity'''
1509 '''return a function that renders a readable count of some quantity'''
1514
1510
1515 def go(count):
1511 def go(count):
1516 for multiplier, divisor, format in unittable:
1512 for multiplier, divisor, format in unittable:
1517 if count >= divisor * multiplier:
1513 if count >= divisor * multiplier:
1518 return format % (count / float(divisor))
1514 return format % (count / float(divisor))
1519 return unittable[-1][2] % count
1515 return unittable[-1][2] % count
1520
1516
1521 return go
1517 return go
1522
1518
1523 bytecount = unitcountfn(
1519 bytecount = unitcountfn(
1524 (100, 1 << 30, _('%.0f GB')),
1520 (100, 1 << 30, _('%.0f GB')),
1525 (10, 1 << 30, _('%.1f GB')),
1521 (10, 1 << 30, _('%.1f GB')),
1526 (1, 1 << 30, _('%.2f GB')),
1522 (1, 1 << 30, _('%.2f GB')),
1527 (100, 1 << 20, _('%.0f MB')),
1523 (100, 1 << 20, _('%.0f MB')),
1528 (10, 1 << 20, _('%.1f MB')),
1524 (10, 1 << 20, _('%.1f MB')),
1529 (1, 1 << 20, _('%.2f MB')),
1525 (1, 1 << 20, _('%.2f MB')),
1530 (100, 1 << 10, _('%.0f KB')),
1526 (100, 1 << 10, _('%.0f KB')),
1531 (10, 1 << 10, _('%.1f KB')),
1527 (10, 1 << 10, _('%.1f KB')),
1532 (1, 1 << 10, _('%.2f KB')),
1528 (1, 1 << 10, _('%.2f KB')),
1533 (1, 1, _('%.0f bytes')),
1529 (1, 1, _('%.0f bytes')),
1534 )
1530 )
1535
1531
1536 def uirepr(s):
1532 def uirepr(s):
1537 # Avoid double backslash in Windows path repr()
1533 # Avoid double backslash in Windows path repr()
1538 return repr(s).replace('\\\\', '\\')
1534 return repr(s).replace('\\\\', '\\')
1539
1535
1540 # delay import of textwrap
1536 # delay import of textwrap
1541 def MBTextWrapper(**kwargs):
1537 def MBTextWrapper(**kwargs):
1542 class tw(textwrap.TextWrapper):
1538 class tw(textwrap.TextWrapper):
1543 """
1539 """
1544 Extend TextWrapper for width-awareness.
1540 Extend TextWrapper for width-awareness.
1545
1541
1546 Neither number of 'bytes' in any encoding nor 'characters' is
1542 Neither number of 'bytes' in any encoding nor 'characters' is
1547 appropriate to calculate terminal columns for specified string.
1543 appropriate to calculate terminal columns for specified string.
1548
1544
1549 Original TextWrapper implementation uses built-in 'len()' directly,
1545 Original TextWrapper implementation uses built-in 'len()' directly,
1550 so overriding is needed to use width information of each characters.
1546 so overriding is needed to use width information of each characters.
1551
1547
1552 In addition, characters classified into 'ambiguous' width are
1548 In addition, characters classified into 'ambiguous' width are
1553 treated as wide in East Asian area, but as narrow in other.
1549 treated as wide in East Asian area, but as narrow in other.
1554
1550
1555 This requires use decision to determine width of such characters.
1551 This requires use decision to determine width of such characters.
1556 """
1552 """
1557 def _cutdown(self, ucstr, space_left):
1553 def _cutdown(self, ucstr, space_left):
1558 l = 0
1554 l = 0
1559 colwidth = encoding.ucolwidth
1555 colwidth = encoding.ucolwidth
1560 for i in xrange(len(ucstr)):
1556 for i in xrange(len(ucstr)):
1561 l += colwidth(ucstr[i])
1557 l += colwidth(ucstr[i])
1562 if space_left < l:
1558 if space_left < l:
1563 return (ucstr[:i], ucstr[i:])
1559 return (ucstr[:i], ucstr[i:])
1564 return ucstr, ''
1560 return ucstr, ''
1565
1561
1566 # overriding of base class
1562 # overriding of base class
1567 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1563 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1568 space_left = max(width - cur_len, 1)
1564 space_left = max(width - cur_len, 1)
1569
1565
1570 if self.break_long_words:
1566 if self.break_long_words:
1571 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1567 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1572 cur_line.append(cut)
1568 cur_line.append(cut)
1573 reversed_chunks[-1] = res
1569 reversed_chunks[-1] = res
1574 elif not cur_line:
1570 elif not cur_line:
1575 cur_line.append(reversed_chunks.pop())
1571 cur_line.append(reversed_chunks.pop())
1576
1572
1577 # this overriding code is imported from TextWrapper of python 2.6
1573 # this overriding code is imported from TextWrapper of python 2.6
1578 # to calculate columns of string by 'encoding.ucolwidth()'
1574 # to calculate columns of string by 'encoding.ucolwidth()'
1579 def _wrap_chunks(self, chunks):
1575 def _wrap_chunks(self, chunks):
1580 colwidth = encoding.ucolwidth
1576 colwidth = encoding.ucolwidth
1581
1577
1582 lines = []
1578 lines = []
1583 if self.width <= 0:
1579 if self.width <= 0:
1584 raise ValueError("invalid width %r (must be > 0)" % self.width)
1580 raise ValueError("invalid width %r (must be > 0)" % self.width)
1585
1581
1586 # Arrange in reverse order so items can be efficiently popped
1582 # Arrange in reverse order so items can be efficiently popped
1587 # from a stack of chucks.
1583 # from a stack of chucks.
1588 chunks.reverse()
1584 chunks.reverse()
1589
1585
1590 while chunks:
1586 while chunks:
1591
1587
1592 # Start the list of chunks that will make up the current line.
1588 # Start the list of chunks that will make up the current line.
1593 # cur_len is just the length of all the chunks in cur_line.
1589 # cur_len is just the length of all the chunks in cur_line.
1594 cur_line = []
1590 cur_line = []
1595 cur_len = 0
1591 cur_len = 0
1596
1592
1597 # Figure out which static string will prefix this line.
1593 # Figure out which static string will prefix this line.
1598 if lines:
1594 if lines:
1599 indent = self.subsequent_indent
1595 indent = self.subsequent_indent
1600 else:
1596 else:
1601 indent = self.initial_indent
1597 indent = self.initial_indent
1602
1598
1603 # Maximum width for this line.
1599 # Maximum width for this line.
1604 width = self.width - len(indent)
1600 width = self.width - len(indent)
1605
1601
1606 # First chunk on line is whitespace -- drop it, unless this
1602 # First chunk on line is whitespace -- drop it, unless this
1607 # is the very beginning of the text (i.e. no lines started yet).
1603 # is the very beginning of the text (i.e. no lines started yet).
1608 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1604 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1609 del chunks[-1]
1605 del chunks[-1]
1610
1606
1611 while chunks:
1607 while chunks:
1612 l = colwidth(chunks[-1])
1608 l = colwidth(chunks[-1])
1613
1609
1614 # Can at least squeeze this chunk onto the current line.
1610 # Can at least squeeze this chunk onto the current line.
1615 if cur_len + l <= width:
1611 if cur_len + l <= width:
1616 cur_line.append(chunks.pop())
1612 cur_line.append(chunks.pop())
1617 cur_len += l
1613 cur_len += l
1618
1614
1619 # Nope, this line is full.
1615 # Nope, this line is full.
1620 else:
1616 else:
1621 break
1617 break
1622
1618
1623 # The current line is full, and the next chunk is too big to
1619 # The current line is full, and the next chunk is too big to
1624 # fit on *any* line (not just this one).
1620 # fit on *any* line (not just this one).
1625 if chunks and colwidth(chunks[-1]) > width:
1621 if chunks and colwidth(chunks[-1]) > width:
1626 self._handle_long_word(chunks, cur_line, cur_len, width)
1622 self._handle_long_word(chunks, cur_line, cur_len, width)
1627
1623
1628 # If the last chunk on this line is all whitespace, drop it.
1624 # If the last chunk on this line is all whitespace, drop it.
1629 if (self.drop_whitespace and
1625 if (self.drop_whitespace and
1630 cur_line and cur_line[-1].strip() == ''):
1626 cur_line and cur_line[-1].strip() == ''):
1631 del cur_line[-1]
1627 del cur_line[-1]
1632
1628
1633 # Convert current line back to a string and store it in list
1629 # Convert current line back to a string and store it in list
1634 # of all lines (return value).
1630 # of all lines (return value).
1635 if cur_line:
1631 if cur_line:
1636 lines.append(indent + ''.join(cur_line))
1632 lines.append(indent + ''.join(cur_line))
1637
1633
1638 return lines
1634 return lines
1639
1635
1640 global MBTextWrapper
1636 global MBTextWrapper
1641 MBTextWrapper = tw
1637 MBTextWrapper = tw
1642 return tw(**kwargs)
1638 return tw(**kwargs)
1643
1639
1644 def wrap(line, width, initindent='', hangindent=''):
1640 def wrap(line, width, initindent='', hangindent=''):
1645 maxindent = max(len(hangindent), len(initindent))
1641 maxindent = max(len(hangindent), len(initindent))
1646 if width <= maxindent:
1642 if width <= maxindent:
1647 # adjust for weird terminal size
1643 # adjust for weird terminal size
1648 width = max(78, maxindent + 1)
1644 width = max(78, maxindent + 1)
1649 line = line.decode(encoding.encoding, encoding.encodingmode)
1645 line = line.decode(encoding.encoding, encoding.encodingmode)
1650 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1646 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1651 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1647 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1652 wrapper = MBTextWrapper(width=width,
1648 wrapper = MBTextWrapper(width=width,
1653 initial_indent=initindent,
1649 initial_indent=initindent,
1654 subsequent_indent=hangindent)
1650 subsequent_indent=hangindent)
1655 return wrapper.fill(line).encode(encoding.encoding)
1651 return wrapper.fill(line).encode(encoding.encoding)
1656
1652
1657 def iterlines(iterator):
1653 def iterlines(iterator):
1658 for chunk in iterator:
1654 for chunk in iterator:
1659 for line in chunk.splitlines():
1655 for line in chunk.splitlines():
1660 yield line
1656 yield line
1661
1657
1662 def expandpath(path):
1658 def expandpath(path):
1663 return os.path.expanduser(os.path.expandvars(path))
1659 return os.path.expanduser(os.path.expandvars(path))
1664
1660
1665 def hgcmd():
1661 def hgcmd():
1666 """Return the command used to execute current hg
1662 """Return the command used to execute current hg
1667
1663
1668 This is different from hgexecutable() because on Windows we want
1664 This is different from hgexecutable() because on Windows we want
1669 to avoid things opening new shell windows like batch files, so we
1665 to avoid things opening new shell windows like batch files, so we
1670 get either the python call or current executable.
1666 get either the python call or current executable.
1671 """
1667 """
1672 if mainfrozen():
1668 if mainfrozen():
1673 return [sys.executable]
1669 return [sys.executable]
1674 return gethgcmd()
1670 return gethgcmd()
1675
1671
1676 def rundetached(args, condfn):
1672 def rundetached(args, condfn):
1677 """Execute the argument list in a detached process.
1673 """Execute the argument list in a detached process.
1678
1674
1679 condfn is a callable which is called repeatedly and should return
1675 condfn is a callable which is called repeatedly and should return
1680 True once the child process is known to have started successfully.
1676 True once the child process is known to have started successfully.
1681 At this point, the child process PID is returned. If the child
1677 At this point, the child process PID is returned. If the child
1682 process fails to start or finishes before condfn() evaluates to
1678 process fails to start or finishes before condfn() evaluates to
1683 True, return -1.
1679 True, return -1.
1684 """
1680 """
1685 # Windows case is easier because the child process is either
1681 # Windows case is easier because the child process is either
1686 # successfully starting and validating the condition or exiting
1682 # successfully starting and validating the condition or exiting
1687 # on failure. We just poll on its PID. On Unix, if the child
1683 # on failure. We just poll on its PID. On Unix, if the child
1688 # process fails to start, it will be left in a zombie state until
1684 # process fails to start, it will be left in a zombie state until
1689 # the parent wait on it, which we cannot do since we expect a long
1685 # the parent wait on it, which we cannot do since we expect a long
1690 # running process on success. Instead we listen for SIGCHLD telling
1686 # running process on success. Instead we listen for SIGCHLD telling
1691 # us our child process terminated.
1687 # us our child process terminated.
1692 terminated = set()
1688 terminated = set()
1693 def handler(signum, frame):
1689 def handler(signum, frame):
1694 terminated.add(os.wait())
1690 terminated.add(os.wait())
1695 prevhandler = None
1691 prevhandler = None
1696 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1692 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1697 if SIGCHLD is not None:
1693 if SIGCHLD is not None:
1698 prevhandler = signal.signal(SIGCHLD, handler)
1694 prevhandler = signal.signal(SIGCHLD, handler)
1699 try:
1695 try:
1700 pid = spawndetached(args)
1696 pid = spawndetached(args)
1701 while not condfn():
1697 while not condfn():
1702 if ((pid in terminated or not testpid(pid))
1698 if ((pid in terminated or not testpid(pid))
1703 and not condfn()):
1699 and not condfn()):
1704 return -1
1700 return -1
1705 time.sleep(0.1)
1701 time.sleep(0.1)
1706 return pid
1702 return pid
1707 finally:
1703 finally:
1708 if prevhandler is not None:
1704 if prevhandler is not None:
1709 signal.signal(signal.SIGCHLD, prevhandler)
1705 signal.signal(signal.SIGCHLD, prevhandler)
1710
1706
1711 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1707 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1712 """Return the result of interpolating items in the mapping into string s.
1708 """Return the result of interpolating items in the mapping into string s.
1713
1709
1714 prefix is a single character string, or a two character string with
1710 prefix is a single character string, or a two character string with
1715 a backslash as the first character if the prefix needs to be escaped in
1711 a backslash as the first character if the prefix needs to be escaped in
1716 a regular expression.
1712 a regular expression.
1717
1713
1718 fn is an optional function that will be applied to the replacement text
1714 fn is an optional function that will be applied to the replacement text
1719 just before replacement.
1715 just before replacement.
1720
1716
1721 escape_prefix is an optional flag that allows using doubled prefix for
1717 escape_prefix is an optional flag that allows using doubled prefix for
1722 its escaping.
1718 its escaping.
1723 """
1719 """
1724 fn = fn or (lambda s: s)
1720 fn = fn or (lambda s: s)
1725 patterns = '|'.join(mapping.keys())
1721 patterns = '|'.join(mapping.keys())
1726 if escape_prefix:
1722 if escape_prefix:
1727 patterns += '|' + prefix
1723 patterns += '|' + prefix
1728 if len(prefix) > 1:
1724 if len(prefix) > 1:
1729 prefix_char = prefix[1:]
1725 prefix_char = prefix[1:]
1730 else:
1726 else:
1731 prefix_char = prefix
1727 prefix_char = prefix
1732 mapping[prefix_char] = prefix_char
1728 mapping[prefix_char] = prefix_char
1733 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1729 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1734 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1730 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1735
1731
1736 def getport(port):
1732 def getport(port):
1737 """Return the port for a given network service.
1733 """Return the port for a given network service.
1738
1734
1739 If port is an integer, it's returned as is. If it's a string, it's
1735 If port is an integer, it's returned as is. If it's a string, it's
1740 looked up using socket.getservbyname(). If there's no matching
1736 looked up using socket.getservbyname(). If there's no matching
1741 service, util.Abort is raised.
1737 service, util.Abort is raised.
1742 """
1738 """
1743 try:
1739 try:
1744 return int(port)
1740 return int(port)
1745 except ValueError:
1741 except ValueError:
1746 pass
1742 pass
1747
1743
1748 try:
1744 try:
1749 return socket.getservbyname(port)
1745 return socket.getservbyname(port)
1750 except socket.error:
1746 except socket.error:
1751 raise Abort(_("no port number associated with service '%s'") % port)
1747 raise Abort(_("no port number associated with service '%s'") % port)
1752
1748
1753 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1749 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1754 '0': False, 'no': False, 'false': False, 'off': False,
1750 '0': False, 'no': False, 'false': False, 'off': False,
1755 'never': False}
1751 'never': False}
1756
1752
1757 def parsebool(s):
1753 def parsebool(s):
1758 """Parse s into a boolean.
1754 """Parse s into a boolean.
1759
1755
1760 If s is not a valid boolean, returns None.
1756 If s is not a valid boolean, returns None.
1761 """
1757 """
1762 return _booleans.get(s.lower(), None)
1758 return _booleans.get(s.lower(), None)
1763
1759
1764 _hexdig = '0123456789ABCDEFabcdef'
1760 _hexdig = '0123456789ABCDEFabcdef'
1765 _hextochr = dict((a + b, chr(int(a + b, 16)))
1761 _hextochr = dict((a + b, chr(int(a + b, 16)))
1766 for a in _hexdig for b in _hexdig)
1762 for a in _hexdig for b in _hexdig)
1767
1763
1768 def _urlunquote(s):
1764 def _urlunquote(s):
1769 """Decode HTTP/HTML % encoding.
1765 """Decode HTTP/HTML % encoding.
1770
1766
1771 >>> _urlunquote('abc%20def')
1767 >>> _urlunquote('abc%20def')
1772 'abc def'
1768 'abc def'
1773 """
1769 """
1774 res = s.split('%')
1770 res = s.split('%')
1775 # fastpath
1771 # fastpath
1776 if len(res) == 1:
1772 if len(res) == 1:
1777 return s
1773 return s
1778 s = res[0]
1774 s = res[0]
1779 for item in res[1:]:
1775 for item in res[1:]:
1780 try:
1776 try:
1781 s += _hextochr[item[:2]] + item[2:]
1777 s += _hextochr[item[:2]] + item[2:]
1782 except KeyError:
1778 except KeyError:
1783 s += '%' + item
1779 s += '%' + item
1784 except UnicodeDecodeError:
1780 except UnicodeDecodeError:
1785 s += unichr(int(item[:2], 16)) + item[2:]
1781 s += unichr(int(item[:2], 16)) + item[2:]
1786 return s
1782 return s
1787
1783
1788 class url(object):
1784 class url(object):
1789 r"""Reliable URL parser.
1785 r"""Reliable URL parser.
1790
1786
1791 This parses URLs and provides attributes for the following
1787 This parses URLs and provides attributes for the following
1792 components:
1788 components:
1793
1789
1794 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1790 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1795
1791
1796 Missing components are set to None. The only exception is
1792 Missing components are set to None. The only exception is
1797 fragment, which is set to '' if present but empty.
1793 fragment, which is set to '' if present but empty.
1798
1794
1799 If parsefragment is False, fragment is included in query. If
1795 If parsefragment is False, fragment is included in query. If
1800 parsequery is False, query is included in path. If both are
1796 parsequery is False, query is included in path. If both are
1801 False, both fragment and query are included in path.
1797 False, both fragment and query are included in path.
1802
1798
1803 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1799 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1804
1800
1805 Note that for backward compatibility reasons, bundle URLs do not
1801 Note that for backward compatibility reasons, bundle URLs do not
1806 take host names. That means 'bundle://../' has a path of '../'.
1802 take host names. That means 'bundle://../' has a path of '../'.
1807
1803
1808 Examples:
1804 Examples:
1809
1805
1810 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1806 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1811 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1807 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1812 >>> url('ssh://[::1]:2200//home/joe/repo')
1808 >>> url('ssh://[::1]:2200//home/joe/repo')
1813 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1809 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1814 >>> url('file:///home/joe/repo')
1810 >>> url('file:///home/joe/repo')
1815 <url scheme: 'file', path: '/home/joe/repo'>
1811 <url scheme: 'file', path: '/home/joe/repo'>
1816 >>> url('file:///c:/temp/foo/')
1812 >>> url('file:///c:/temp/foo/')
1817 <url scheme: 'file', path: 'c:/temp/foo/'>
1813 <url scheme: 'file', path: 'c:/temp/foo/'>
1818 >>> url('bundle:foo')
1814 >>> url('bundle:foo')
1819 <url scheme: 'bundle', path: 'foo'>
1815 <url scheme: 'bundle', path: 'foo'>
1820 >>> url('bundle://../foo')
1816 >>> url('bundle://../foo')
1821 <url scheme: 'bundle', path: '../foo'>
1817 <url scheme: 'bundle', path: '../foo'>
1822 >>> url(r'c:\foo\bar')
1818 >>> url(r'c:\foo\bar')
1823 <url path: 'c:\\foo\\bar'>
1819 <url path: 'c:\\foo\\bar'>
1824 >>> url(r'\\blah\blah\blah')
1820 >>> url(r'\\blah\blah\blah')
1825 <url path: '\\\\blah\\blah\\blah'>
1821 <url path: '\\\\blah\\blah\\blah'>
1826 >>> url(r'\\blah\blah\blah#baz')
1822 >>> url(r'\\blah\blah\blah#baz')
1827 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1823 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1828 >>> url(r'file:///C:\users\me')
1824 >>> url(r'file:///C:\users\me')
1829 <url scheme: 'file', path: 'C:\\users\\me'>
1825 <url scheme: 'file', path: 'C:\\users\\me'>
1830
1826
1831 Authentication credentials:
1827 Authentication credentials:
1832
1828
1833 >>> url('ssh://joe:xyz@x/repo')
1829 >>> url('ssh://joe:xyz@x/repo')
1834 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1830 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1835 >>> url('ssh://joe@x/repo')
1831 >>> url('ssh://joe@x/repo')
1836 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1832 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1837
1833
1838 Query strings and fragments:
1834 Query strings and fragments:
1839
1835
1840 >>> url('http://host/a?b#c')
1836 >>> url('http://host/a?b#c')
1841 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1837 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1842 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1838 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1843 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1839 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1844 """
1840 """
1845
1841
1846 _safechars = "!~*'()+"
1842 _safechars = "!~*'()+"
1847 _safepchars = "/!~*'()+:\\"
1843 _safepchars = "/!~*'()+:\\"
1848 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
1844 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
1849
1845
1850 def __init__(self, path, parsequery=True, parsefragment=True):
1846 def __init__(self, path, parsequery=True, parsefragment=True):
1851 # We slowly chomp away at path until we have only the path left
1847 # We slowly chomp away at path until we have only the path left
1852 self.scheme = self.user = self.passwd = self.host = None
1848 self.scheme = self.user = self.passwd = self.host = None
1853 self.port = self.path = self.query = self.fragment = None
1849 self.port = self.path = self.query = self.fragment = None
1854 self._localpath = True
1850 self._localpath = True
1855 self._hostport = ''
1851 self._hostport = ''
1856 self._origpath = path
1852 self._origpath = path
1857
1853
1858 if parsefragment and '#' in path:
1854 if parsefragment and '#' in path:
1859 path, self.fragment = path.split('#', 1)
1855 path, self.fragment = path.split('#', 1)
1860 if not path:
1856 if not path:
1861 path = None
1857 path = None
1862
1858
1863 # special case for Windows drive letters and UNC paths
1859 # special case for Windows drive letters and UNC paths
1864 if hasdriveletter(path) or path.startswith(r'\\'):
1860 if hasdriveletter(path) or path.startswith(r'\\'):
1865 self.path = path
1861 self.path = path
1866 return
1862 return
1867
1863
1868 # For compatibility reasons, we can't handle bundle paths as
1864 # For compatibility reasons, we can't handle bundle paths as
1869 # normal URLS
1865 # normal URLS
1870 if path.startswith('bundle:'):
1866 if path.startswith('bundle:'):
1871 self.scheme = 'bundle'
1867 self.scheme = 'bundle'
1872 path = path[7:]
1868 path = path[7:]
1873 if path.startswith('//'):
1869 if path.startswith('//'):
1874 path = path[2:]
1870 path = path[2:]
1875 self.path = path
1871 self.path = path
1876 return
1872 return
1877
1873
1878 if self._matchscheme(path):
1874 if self._matchscheme(path):
1879 parts = path.split(':', 1)
1875 parts = path.split(':', 1)
1880 if parts[0]:
1876 if parts[0]:
1881 self.scheme, path = parts
1877 self.scheme, path = parts
1882 self._localpath = False
1878 self._localpath = False
1883
1879
1884 if not path:
1880 if not path:
1885 path = None
1881 path = None
1886 if self._localpath:
1882 if self._localpath:
1887 self.path = ''
1883 self.path = ''
1888 return
1884 return
1889 else:
1885 else:
1890 if self._localpath:
1886 if self._localpath:
1891 self.path = path
1887 self.path = path
1892 return
1888 return
1893
1889
1894 if parsequery and '?' in path:
1890 if parsequery and '?' in path:
1895 path, self.query = path.split('?', 1)
1891 path, self.query = path.split('?', 1)
1896 if not path:
1892 if not path:
1897 path = None
1893 path = None
1898 if not self.query:
1894 if not self.query:
1899 self.query = None
1895 self.query = None
1900
1896
1901 # // is required to specify a host/authority
1897 # // is required to specify a host/authority
1902 if path and path.startswith('//'):
1898 if path and path.startswith('//'):
1903 parts = path[2:].split('/', 1)
1899 parts = path[2:].split('/', 1)
1904 if len(parts) > 1:
1900 if len(parts) > 1:
1905 self.host, path = parts
1901 self.host, path = parts
1906 else:
1902 else:
1907 self.host = parts[0]
1903 self.host = parts[0]
1908 path = None
1904 path = None
1909 if not self.host:
1905 if not self.host:
1910 self.host = None
1906 self.host = None
1911 # path of file:///d is /d
1907 # path of file:///d is /d
1912 # path of file:///d:/ is d:/, not /d:/
1908 # path of file:///d:/ is d:/, not /d:/
1913 if path and not hasdriveletter(path):
1909 if path and not hasdriveletter(path):
1914 path = '/' + path
1910 path = '/' + path
1915
1911
1916 if self.host and '@' in self.host:
1912 if self.host and '@' in self.host:
1917 self.user, self.host = self.host.rsplit('@', 1)
1913 self.user, self.host = self.host.rsplit('@', 1)
1918 if ':' in self.user:
1914 if ':' in self.user:
1919 self.user, self.passwd = self.user.split(':', 1)
1915 self.user, self.passwd = self.user.split(':', 1)
1920 if not self.host:
1916 if not self.host:
1921 self.host = None
1917 self.host = None
1922
1918
1923 # Don't split on colons in IPv6 addresses without ports
1919 # Don't split on colons in IPv6 addresses without ports
1924 if (self.host and ':' in self.host and
1920 if (self.host and ':' in self.host and
1925 not (self.host.startswith('[') and self.host.endswith(']'))):
1921 not (self.host.startswith('[') and self.host.endswith(']'))):
1926 self._hostport = self.host
1922 self._hostport = self.host
1927 self.host, self.port = self.host.rsplit(':', 1)
1923 self.host, self.port = self.host.rsplit(':', 1)
1928 if not self.host:
1924 if not self.host:
1929 self.host = None
1925 self.host = None
1930
1926
1931 if (self.host and self.scheme == 'file' and
1927 if (self.host and self.scheme == 'file' and
1932 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1928 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1933 raise Abort(_('file:// URLs can only refer to localhost'))
1929 raise Abort(_('file:// URLs can only refer to localhost'))
1934
1930
1935 self.path = path
1931 self.path = path
1936
1932
1937 # leave the query string escaped
1933 # leave the query string escaped
1938 for a in ('user', 'passwd', 'host', 'port',
1934 for a in ('user', 'passwd', 'host', 'port',
1939 'path', 'fragment'):
1935 'path', 'fragment'):
1940 v = getattr(self, a)
1936 v = getattr(self, a)
1941 if v is not None:
1937 if v is not None:
1942 setattr(self, a, _urlunquote(v))
1938 setattr(self, a, _urlunquote(v))
1943
1939
1944 def __repr__(self):
1940 def __repr__(self):
1945 attrs = []
1941 attrs = []
1946 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1942 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1947 'query', 'fragment'):
1943 'query', 'fragment'):
1948 v = getattr(self, a)
1944 v = getattr(self, a)
1949 if v is not None:
1945 if v is not None:
1950 attrs.append('%s: %r' % (a, v))
1946 attrs.append('%s: %r' % (a, v))
1951 return '<url %s>' % ', '.join(attrs)
1947 return '<url %s>' % ', '.join(attrs)
1952
1948
1953 def __str__(self):
1949 def __str__(self):
1954 r"""Join the URL's components back into a URL string.
1950 r"""Join the URL's components back into a URL string.
1955
1951
1956 Examples:
1952 Examples:
1957
1953
1958 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1954 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1959 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1955 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1960 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1956 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1961 'http://user:pw@host:80/?foo=bar&baz=42'
1957 'http://user:pw@host:80/?foo=bar&baz=42'
1962 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1958 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1963 'http://user:pw@host:80/?foo=bar%3dbaz'
1959 'http://user:pw@host:80/?foo=bar%3dbaz'
1964 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1960 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1965 'ssh://user:pw@[::1]:2200//home/joe#'
1961 'ssh://user:pw@[::1]:2200//home/joe#'
1966 >>> str(url('http://localhost:80//'))
1962 >>> str(url('http://localhost:80//'))
1967 'http://localhost:80//'
1963 'http://localhost:80//'
1968 >>> str(url('http://localhost:80/'))
1964 >>> str(url('http://localhost:80/'))
1969 'http://localhost:80/'
1965 'http://localhost:80/'
1970 >>> str(url('http://localhost:80'))
1966 >>> str(url('http://localhost:80'))
1971 'http://localhost:80/'
1967 'http://localhost:80/'
1972 >>> str(url('bundle:foo'))
1968 >>> str(url('bundle:foo'))
1973 'bundle:foo'
1969 'bundle:foo'
1974 >>> str(url('bundle://../foo'))
1970 >>> str(url('bundle://../foo'))
1975 'bundle:../foo'
1971 'bundle:../foo'
1976 >>> str(url('path'))
1972 >>> str(url('path'))
1977 'path'
1973 'path'
1978 >>> str(url('file:///tmp/foo/bar'))
1974 >>> str(url('file:///tmp/foo/bar'))
1979 'file:///tmp/foo/bar'
1975 'file:///tmp/foo/bar'
1980 >>> str(url('file:///c:/tmp/foo/bar'))
1976 >>> str(url('file:///c:/tmp/foo/bar'))
1981 'file:///c:/tmp/foo/bar'
1977 'file:///c:/tmp/foo/bar'
1982 >>> print url(r'bundle:foo\bar')
1978 >>> print url(r'bundle:foo\bar')
1983 bundle:foo\bar
1979 bundle:foo\bar
1984 >>> print url(r'file:///D:\data\hg')
1980 >>> print url(r'file:///D:\data\hg')
1985 file:///D:\data\hg
1981 file:///D:\data\hg
1986 """
1982 """
1987 if self._localpath:
1983 if self._localpath:
1988 s = self.path
1984 s = self.path
1989 if self.scheme == 'bundle':
1985 if self.scheme == 'bundle':
1990 s = 'bundle:' + s
1986 s = 'bundle:' + s
1991 if self.fragment:
1987 if self.fragment:
1992 s += '#' + self.fragment
1988 s += '#' + self.fragment
1993 return s
1989 return s
1994
1990
1995 s = self.scheme + ':'
1991 s = self.scheme + ':'
1996 if self.user or self.passwd or self.host:
1992 if self.user or self.passwd or self.host:
1997 s += '//'
1993 s += '//'
1998 elif self.scheme and (not self.path or self.path.startswith('/')
1994 elif self.scheme and (not self.path or self.path.startswith('/')
1999 or hasdriveletter(self.path)):
1995 or hasdriveletter(self.path)):
2000 s += '//'
1996 s += '//'
2001 if hasdriveletter(self.path):
1997 if hasdriveletter(self.path):
2002 s += '/'
1998 s += '/'
2003 if self.user:
1999 if self.user:
2004 s += urllib.quote(self.user, safe=self._safechars)
2000 s += urllib.quote(self.user, safe=self._safechars)
2005 if self.passwd:
2001 if self.passwd:
2006 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2002 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2007 if self.user or self.passwd:
2003 if self.user or self.passwd:
2008 s += '@'
2004 s += '@'
2009 if self.host:
2005 if self.host:
2010 if not (self.host.startswith('[') and self.host.endswith(']')):
2006 if not (self.host.startswith('[') and self.host.endswith(']')):
2011 s += urllib.quote(self.host)
2007 s += urllib.quote(self.host)
2012 else:
2008 else:
2013 s += self.host
2009 s += self.host
2014 if self.port:
2010 if self.port:
2015 s += ':' + urllib.quote(self.port)
2011 s += ':' + urllib.quote(self.port)
2016 if self.host:
2012 if self.host:
2017 s += '/'
2013 s += '/'
2018 if self.path:
2014 if self.path:
2019 # TODO: similar to the query string, we should not unescape the
2015 # TODO: similar to the query string, we should not unescape the
2020 # path when we store it, the path might contain '%2f' = '/',
2016 # path when we store it, the path might contain '%2f' = '/',
2021 # which we should *not* escape.
2017 # which we should *not* escape.
2022 s += urllib.quote(self.path, safe=self._safepchars)
2018 s += urllib.quote(self.path, safe=self._safepchars)
2023 if self.query:
2019 if self.query:
2024 # we store the query in escaped form.
2020 # we store the query in escaped form.
2025 s += '?' + self.query
2021 s += '?' + self.query
2026 if self.fragment is not None:
2022 if self.fragment is not None:
2027 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2023 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2028 return s
2024 return s
2029
2025
2030 def authinfo(self):
2026 def authinfo(self):
2031 user, passwd = self.user, self.passwd
2027 user, passwd = self.user, self.passwd
2032 try:
2028 try:
2033 self.user, self.passwd = None, None
2029 self.user, self.passwd = None, None
2034 s = str(self)
2030 s = str(self)
2035 finally:
2031 finally:
2036 self.user, self.passwd = user, passwd
2032 self.user, self.passwd = user, passwd
2037 if not self.user:
2033 if not self.user:
2038 return (s, None)
2034 return (s, None)
2039 # authinfo[1] is passed to urllib2 password manager, and its
2035 # authinfo[1] is passed to urllib2 password manager, and its
2040 # URIs must not contain credentials. The host is passed in the
2036 # URIs must not contain credentials. The host is passed in the
2041 # URIs list because Python < 2.4.3 uses only that to search for
2037 # URIs list because Python < 2.4.3 uses only that to search for
2042 # a password.
2038 # a password.
2043 return (s, (None, (s, self.host),
2039 return (s, (None, (s, self.host),
2044 self.user, self.passwd or ''))
2040 self.user, self.passwd or ''))
2045
2041
2046 def isabs(self):
2042 def isabs(self):
2047 if self.scheme and self.scheme != 'file':
2043 if self.scheme and self.scheme != 'file':
2048 return True # remote URL
2044 return True # remote URL
2049 if hasdriveletter(self.path):
2045 if hasdriveletter(self.path):
2050 return True # absolute for our purposes - can't be joined()
2046 return True # absolute for our purposes - can't be joined()
2051 if self.path.startswith(r'\\'):
2047 if self.path.startswith(r'\\'):
2052 return True # Windows UNC path
2048 return True # Windows UNC path
2053 if self.path.startswith('/'):
2049 if self.path.startswith('/'):
2054 return True # POSIX-style
2050 return True # POSIX-style
2055 return False
2051 return False
2056
2052
2057 def localpath(self):
2053 def localpath(self):
2058 if self.scheme == 'file' or self.scheme == 'bundle':
2054 if self.scheme == 'file' or self.scheme == 'bundle':
2059 path = self.path or '/'
2055 path = self.path or '/'
2060 # For Windows, we need to promote hosts containing drive
2056 # For Windows, we need to promote hosts containing drive
2061 # letters to paths with drive letters.
2057 # letters to paths with drive letters.
2062 if hasdriveletter(self._hostport):
2058 if hasdriveletter(self._hostport):
2063 path = self._hostport + '/' + self.path
2059 path = self._hostport + '/' + self.path
2064 elif (self.host is not None and self.path
2060 elif (self.host is not None and self.path
2065 and not hasdriveletter(path)):
2061 and not hasdriveletter(path)):
2066 path = '/' + path
2062 path = '/' + path
2067 return path
2063 return path
2068 return self._origpath
2064 return self._origpath
2069
2065
2070 def islocal(self):
2066 def islocal(self):
2071 '''whether localpath will return something that posixfile can open'''
2067 '''whether localpath will return something that posixfile can open'''
2072 return (not self.scheme or self.scheme == 'file'
2068 return (not self.scheme or self.scheme == 'file'
2073 or self.scheme == 'bundle')
2069 or self.scheme == 'bundle')
2074
2070
2075 def hasscheme(path):
2071 def hasscheme(path):
2076 return bool(url(path).scheme)
2072 return bool(url(path).scheme)
2077
2073
2078 def hasdriveletter(path):
2074 def hasdriveletter(path):
2079 return path and path[1:2] == ':' and path[0:1].isalpha()
2075 return path and path[1:2] == ':' and path[0:1].isalpha()
2080
2076
2081 def urllocalpath(path):
2077 def urllocalpath(path):
2082 return url(path, parsequery=False, parsefragment=False).localpath()
2078 return url(path, parsequery=False, parsefragment=False).localpath()
2083
2079
2084 def hidepassword(u):
2080 def hidepassword(u):
2085 '''hide user credential in a url string'''
2081 '''hide user credential in a url string'''
2086 u = url(u)
2082 u = url(u)
2087 if u.passwd:
2083 if u.passwd:
2088 u.passwd = '***'
2084 u.passwd = '***'
2089 return str(u)
2085 return str(u)
2090
2086
2091 def removeauth(u):
2087 def removeauth(u):
2092 '''remove all authentication information from a url string'''
2088 '''remove all authentication information from a url string'''
2093 u = url(u)
2089 u = url(u)
2094 u.user = u.passwd = None
2090 u.user = u.passwd = None
2095 return str(u)
2091 return str(u)
2096
2092
2097 def isatty(fd):
2093 def isatty(fd):
2098 try:
2094 try:
2099 return fd.isatty()
2095 return fd.isatty()
2100 except AttributeError:
2096 except AttributeError:
2101 return False
2097 return False
2102
2098
2103 timecount = unitcountfn(
2099 timecount = unitcountfn(
2104 (1, 1e3, _('%.0f s')),
2100 (1, 1e3, _('%.0f s')),
2105 (100, 1, _('%.1f s')),
2101 (100, 1, _('%.1f s')),
2106 (10, 1, _('%.2f s')),
2102 (10, 1, _('%.2f s')),
2107 (1, 1, _('%.3f s')),
2103 (1, 1, _('%.3f s')),
2108 (100, 0.001, _('%.1f ms')),
2104 (100, 0.001, _('%.1f ms')),
2109 (10, 0.001, _('%.2f ms')),
2105 (10, 0.001, _('%.2f ms')),
2110 (1, 0.001, _('%.3f ms')),
2106 (1, 0.001, _('%.3f ms')),
2111 (100, 0.000001, _('%.1f us')),
2107 (100, 0.000001, _('%.1f us')),
2112 (10, 0.000001, _('%.2f us')),
2108 (10, 0.000001, _('%.2f us')),
2113 (1, 0.000001, _('%.3f us')),
2109 (1, 0.000001, _('%.3f us')),
2114 (100, 0.000000001, _('%.1f ns')),
2110 (100, 0.000000001, _('%.1f ns')),
2115 (10, 0.000000001, _('%.2f ns')),
2111 (10, 0.000000001, _('%.2f ns')),
2116 (1, 0.000000001, _('%.3f ns')),
2112 (1, 0.000000001, _('%.3f ns')),
2117 )
2113 )
2118
2114
2119 _timenesting = [0]
2115 _timenesting = [0]
2120
2116
2121 def timed(func):
2117 def timed(func):
2122 '''Report the execution time of a function call to stderr.
2118 '''Report the execution time of a function call to stderr.
2123
2119
2124 During development, use as a decorator when you need to measure
2120 During development, use as a decorator when you need to measure
2125 the cost of a function, e.g. as follows:
2121 the cost of a function, e.g. as follows:
2126
2122
2127 @util.timed
2123 @util.timed
2128 def foo(a, b, c):
2124 def foo(a, b, c):
2129 pass
2125 pass
2130 '''
2126 '''
2131
2127
2132 def wrapper(*args, **kwargs):
2128 def wrapper(*args, **kwargs):
2133 start = time.time()
2129 start = time.time()
2134 indent = 2
2130 indent = 2
2135 _timenesting[0] += indent
2131 _timenesting[0] += indent
2136 try:
2132 try:
2137 return func(*args, **kwargs)
2133 return func(*args, **kwargs)
2138 finally:
2134 finally:
2139 elapsed = time.time() - start
2135 elapsed = time.time() - start
2140 _timenesting[0] -= indent
2136 _timenesting[0] -= indent
2141 sys.stderr.write('%s%s: %s\n' %
2137 sys.stderr.write('%s%s: %s\n' %
2142 (' ' * _timenesting[0], func.__name__,
2138 (' ' * _timenesting[0], func.__name__,
2143 timecount(elapsed)))
2139 timecount(elapsed)))
2144 return wrapper
2140 return wrapper
2145
2141
2146 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2142 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2147 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2143 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2148
2144
2149 def sizetoint(s):
2145 def sizetoint(s):
2150 '''Convert a space specifier to a byte count.
2146 '''Convert a space specifier to a byte count.
2151
2147
2152 >>> sizetoint('30')
2148 >>> sizetoint('30')
2153 30
2149 30
2154 >>> sizetoint('2.2kb')
2150 >>> sizetoint('2.2kb')
2155 2252
2151 2252
2156 >>> sizetoint('6M')
2152 >>> sizetoint('6M')
2157 6291456
2153 6291456
2158 '''
2154 '''
2159 t = s.strip().lower()
2155 t = s.strip().lower()
2160 try:
2156 try:
2161 for k, u in _sizeunits:
2157 for k, u in _sizeunits:
2162 if t.endswith(k):
2158 if t.endswith(k):
2163 return int(float(t[:-len(k)]) * u)
2159 return int(float(t[:-len(k)]) * u)
2164 return int(t)
2160 return int(t)
2165 except ValueError:
2161 except ValueError:
2166 raise error.ParseError(_("couldn't parse size: %s") % s)
2162 raise error.ParseError(_("couldn't parse size: %s") % s)
2167
2163
2168 class hooks(object):
2164 class hooks(object):
2169 '''A collection of hook functions that can be used to extend a
2165 '''A collection of hook functions that can be used to extend a
2170 function's behaviour. Hooks are called in lexicographic order,
2166 function's behaviour. Hooks are called in lexicographic order,
2171 based on the names of their sources.'''
2167 based on the names of their sources.'''
2172
2168
2173 def __init__(self):
2169 def __init__(self):
2174 self._hooks = []
2170 self._hooks = []
2175
2171
2176 def add(self, source, hook):
2172 def add(self, source, hook):
2177 self._hooks.append((source, hook))
2173 self._hooks.append((source, hook))
2178
2174
2179 def __call__(self, *args):
2175 def __call__(self, *args):
2180 self._hooks.sort(key=lambda x: x[0])
2176 self._hooks.sort(key=lambda x: x[0])
2181 results = []
2177 results = []
2182 for source, hook in self._hooks:
2178 for source, hook in self._hooks:
2183 results.append(hook(*args))
2179 results.append(hook(*args))
2184 return results
2180 return results
2185
2181
2186 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2182 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2187 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2183 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2188 Skips the 'skip' last entries. By default it will flush stdout first.
2184 Skips the 'skip' last entries. By default it will flush stdout first.
2189 It can be used everywhere and do intentionally not require an ui object.
2185 It can be used everywhere and do intentionally not require an ui object.
2190 Not be used in production code but very convenient while developing.
2186 Not be used in production code but very convenient while developing.
2191 '''
2187 '''
2192 if otherf:
2188 if otherf:
2193 otherf.flush()
2189 otherf.flush()
2194 f.write('%s at:\n' % msg)
2190 f.write('%s at:\n' % msg)
2195 entries = [('%s:%s' % (fn, ln), func)
2191 entries = [('%s:%s' % (fn, ln), func)
2196 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2192 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2197 if entries:
2193 if entries:
2198 fnmax = max(len(entry[0]) for entry in entries)
2194 fnmax = max(len(entry[0]) for entry in entries)
2199 for fnln, func in entries:
2195 for fnln, func in entries:
2200 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2196 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2201 f.flush()
2197 f.flush()
2202
2198
2203 class dirs(object):
2199 class dirs(object):
2204 '''a multiset of directory names from a dirstate or manifest'''
2200 '''a multiset of directory names from a dirstate or manifest'''
2205
2201
2206 def __init__(self, map, skip=None):
2202 def __init__(self, map, skip=None):
2207 self._dirs = {}
2203 self._dirs = {}
2208 addpath = self.addpath
2204 addpath = self.addpath
2209 if safehasattr(map, 'iteritems') and skip is not None:
2205 if safehasattr(map, 'iteritems') and skip is not None:
2210 for f, s in map.iteritems():
2206 for f, s in map.iteritems():
2211 if s[0] != skip:
2207 if s[0] != skip:
2212 addpath(f)
2208 addpath(f)
2213 else:
2209 else:
2214 for f in map:
2210 for f in map:
2215 addpath(f)
2211 addpath(f)
2216
2212
2217 def addpath(self, path):
2213 def addpath(self, path):
2218 dirs = self._dirs
2214 dirs = self._dirs
2219 for base in finddirs(path):
2215 for base in finddirs(path):
2220 if base in dirs:
2216 if base in dirs:
2221 dirs[base] += 1
2217 dirs[base] += 1
2222 return
2218 return
2223 dirs[base] = 1
2219 dirs[base] = 1
2224
2220
2225 def delpath(self, path):
2221 def delpath(self, path):
2226 dirs = self._dirs
2222 dirs = self._dirs
2227 for base in finddirs(path):
2223 for base in finddirs(path):
2228 if dirs[base] > 1:
2224 if dirs[base] > 1:
2229 dirs[base] -= 1
2225 dirs[base] -= 1
2230 return
2226 return
2231 del dirs[base]
2227 del dirs[base]
2232
2228
2233 def __iter__(self):
2229 def __iter__(self):
2234 return self._dirs.iterkeys()
2230 return self._dirs.iterkeys()
2235
2231
2236 def __contains__(self, d):
2232 def __contains__(self, d):
2237 return d in self._dirs
2233 return d in self._dirs
2238
2234
2239 if safehasattr(parsers, 'dirs'):
2235 if safehasattr(parsers, 'dirs'):
2240 dirs = parsers.dirs
2236 dirs = parsers.dirs
2241
2237
2242 def finddirs(path):
2238 def finddirs(path):
2243 pos = path.rfind('/')
2239 pos = path.rfind('/')
2244 while pos != -1:
2240 while pos != -1:
2245 yield path[:pos]
2241 yield path[:pos]
2246 pos = path.rfind('/', 0, pos)
2242 pos = path.rfind('/', 0, pos)
2247
2243
2248 # convenient shortcut
2244 # convenient shortcut
2249 dst = debugstacktrace
2245 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now