##// END OF EJS Templates
clone-bundles: check we generate a valid specification...
marmoute -
r51606:ddc55fb2 default
parent child Browse files
Show More
@@ -1,1081 +1,1082 b''
1 # This software may be used and distributed according to the terms of the
1 # This software may be used and distributed according to the terms of the
2 # GNU General Public License version 2 or any later version.
2 # GNU General Public License version 2 or any later version.
3
3
4 """advertise pre-generated bundles to seed clones
4 """advertise pre-generated bundles to seed clones
5
5
6 "clonebundles" is a server-side extension used to advertise the existence
6 "clonebundles" is a server-side extension used to advertise the existence
7 of pre-generated, externally hosted bundle files to clients that are
7 of pre-generated, externally hosted bundle files to clients that are
8 cloning so that cloning can be faster, more reliable, and require less
8 cloning so that cloning can be faster, more reliable, and require less
9 resources on the server. "pullbundles" is a related feature for sending
9 resources on the server. "pullbundles" is a related feature for sending
10 pre-generated bundle files to clients as part of pull operations.
10 pre-generated bundle files to clients as part of pull operations.
11
11
12 Cloning can be a CPU and I/O intensive operation on servers. Traditionally,
12 Cloning can be a CPU and I/O intensive operation on servers. Traditionally,
13 the server, in response to a client's request to clone, dynamically generates
13 the server, in response to a client's request to clone, dynamically generates
14 a bundle containing the entire repository content and sends it to the client.
14 a bundle containing the entire repository content and sends it to the client.
15 There is no caching on the server and the server will have to redundantly
15 There is no caching on the server and the server will have to redundantly
16 generate the same outgoing bundle in response to each clone request. For
16 generate the same outgoing bundle in response to each clone request. For
17 servers with large repositories or with high clone volume, the load from
17 servers with large repositories or with high clone volume, the load from
18 clones can make scaling the server challenging and costly.
18 clones can make scaling the server challenging and costly.
19
19
20 This extension provides server operators the ability to offload
20 This extension provides server operators the ability to offload
21 potentially expensive clone load to an external service. Pre-generated
21 potentially expensive clone load to an external service. Pre-generated
22 bundles also allow using more CPU intensive compression, reducing the
22 bundles also allow using more CPU intensive compression, reducing the
23 effective bandwidth requirements.
23 effective bandwidth requirements.
24
24
25 Here's how clone bundles work:
25 Here's how clone bundles work:
26
26
27 1. A server operator establishes a mechanism for making bundle files available
27 1. A server operator establishes a mechanism for making bundle files available
28 on a hosting service where Mercurial clients can fetch them.
28 on a hosting service where Mercurial clients can fetch them.
29 2. A manifest file listing available bundle URLs and some optional metadata
29 2. A manifest file listing available bundle URLs and some optional metadata
30 is added to the Mercurial repository on the server.
30 is added to the Mercurial repository on the server.
31 3. A client initiates a clone against a clone bundles aware server.
31 3. A client initiates a clone against a clone bundles aware server.
32 4. The client sees the server is advertising clone bundles and fetches the
32 4. The client sees the server is advertising clone bundles and fetches the
33 manifest listing available bundles.
33 manifest listing available bundles.
34 5. The client filters and sorts the available bundles based on what it
34 5. The client filters and sorts the available bundles based on what it
35 supports and prefers.
35 supports and prefers.
36 6. The client downloads and applies an available bundle from the
36 6. The client downloads and applies an available bundle from the
37 server-specified URL.
37 server-specified URL.
38 7. The client reconnects to the original server and performs the equivalent
38 7. The client reconnects to the original server and performs the equivalent
39 of :hg:`pull` to retrieve all repository data not in the bundle. (The
39 of :hg:`pull` to retrieve all repository data not in the bundle. (The
40 repository could have been updated between when the bundle was created
40 repository could have been updated between when the bundle was created
41 and when the client started the clone.) This may use "pullbundles".
41 and when the client started the clone.) This may use "pullbundles".
42
42
43 Instead of the server generating full repository bundles for every clone
43 Instead of the server generating full repository bundles for every clone
44 request, it generates full bundles once and they are subsequently reused to
44 request, it generates full bundles once and they are subsequently reused to
45 bootstrap new clones. The server may still transfer data at clone time.
45 bootstrap new clones. The server may still transfer data at clone time.
46 However, this is only data that has been added/changed since the bundle was
46 However, this is only data that has been added/changed since the bundle was
47 created. For large, established repositories, this can reduce server load for
47 created. For large, established repositories, this can reduce server load for
48 clones to less than 1% of original.
48 clones to less than 1% of original.
49
49
50 Here's how pullbundles work:
50 Here's how pullbundles work:
51
51
52 1. A manifest file listing available bundles and describing the revisions
52 1. A manifest file listing available bundles and describing the revisions
53 is added to the Mercurial repository on the server.
53 is added to the Mercurial repository on the server.
54 2. A new-enough client informs the server that it supports partial pulls
54 2. A new-enough client informs the server that it supports partial pulls
55 and initiates a pull.
55 and initiates a pull.
56 3. If the server has pull bundles enabled and sees the client advertising
56 3. If the server has pull bundles enabled and sees the client advertising
57 partial pulls, it checks for a matching pull bundle in the manifest.
57 partial pulls, it checks for a matching pull bundle in the manifest.
58 A bundle matches if the format is supported by the client, the client
58 A bundle matches if the format is supported by the client, the client
59 has the required revisions already and needs something from the bundle.
59 has the required revisions already and needs something from the bundle.
60 4. If there is at least one matching bundle, the server sends it to the client.
60 4. If there is at least one matching bundle, the server sends it to the client.
61 5. The client applies the bundle and notices that the server reply was
61 5. The client applies the bundle and notices that the server reply was
62 incomplete. It initiates another pull.
62 incomplete. It initiates another pull.
63
63
64 To work, this extension requires the following of server operators:
64 To work, this extension requires the following of server operators:
65
65
66 * Generating bundle files of repository content (typically periodically,
66 * Generating bundle files of repository content (typically periodically,
67 such as once per day).
67 such as once per day).
68 * Clone bundles: A file server that clients have network access to and that
68 * Clone bundles: A file server that clients have network access to and that
69 Python knows how to talk to through its normal URL handling facility
69 Python knows how to talk to through its normal URL handling facility
70 (typically an HTTP/HTTPS server).
70 (typically an HTTP/HTTPS server).
71 * A process for keeping the bundles manifest in sync with available bundle
71 * A process for keeping the bundles manifest in sync with available bundle
72 files.
72 files.
73
73
74 Strictly speaking, using a static file hosting server isn't required: a server
74 Strictly speaking, using a static file hosting server isn't required: a server
75 operator could use a dynamic service for retrieving bundle data. However,
75 operator could use a dynamic service for retrieving bundle data. However,
76 static file hosting services are simple and scalable and should be sufficient
76 static file hosting services are simple and scalable and should be sufficient
77 for most needs.
77 for most needs.
78
78
79 Bundle files can be generated with the :hg:`bundle` command. Typically
79 Bundle files can be generated with the :hg:`bundle` command. Typically
80 :hg:`bundle --all` is used to produce a bundle of the entire repository.
80 :hg:`bundle --all` is used to produce a bundle of the entire repository.
81
81
82 The bundlespec option `stream` (see :hg:`help bundlespec`)
82 The bundlespec option `stream` (see :hg:`help bundlespec`)
83 can be used to produce a special *streaming clonebundle*, typically using
83 can be used to produce a special *streaming clonebundle*, typically using
84 :hg:`bundle --all --type="none-streamv2"`.
84 :hg:`bundle --all --type="none-streamv2"`.
85 These are bundle files that are extremely efficient
85 These are bundle files that are extremely efficient
86 to produce and consume (read: fast). However, they are larger than
86 to produce and consume (read: fast). However, they are larger than
87 traditional bundle formats and require that clients support the exact set
87 traditional bundle formats and require that clients support the exact set
88 of repository data store formats in use by the repository that created them.
88 of repository data store formats in use by the repository that created them.
89 Typically, a newer server can serve data that is compatible with older clients.
89 Typically, a newer server can serve data that is compatible with older clients.
90 However, *streaming clone bundles* don't have this guarantee. **Server
90 However, *streaming clone bundles* don't have this guarantee. **Server
91 operators need to be aware that newer versions of Mercurial may produce
91 operators need to be aware that newer versions of Mercurial may produce
92 streaming clone bundles incompatible with older Mercurial versions.**
92 streaming clone bundles incompatible with older Mercurial versions.**
93
93
94 A server operator is responsible for creating a ``.hg/clonebundles.manifest``
94 A server operator is responsible for creating a ``.hg/clonebundles.manifest``
95 file containing the list of available bundle files suitable for seeding
95 file containing the list of available bundle files suitable for seeding
96 clones. If this file does not exist, the repository will not advertise the
96 clones. If this file does not exist, the repository will not advertise the
97 existence of clone bundles when clients connect. For pull bundles,
97 existence of clone bundles when clients connect. For pull bundles,
98 ``.hg/pullbundles.manifest`` is used.
98 ``.hg/pullbundles.manifest`` is used.
99
99
100 The manifest file contains a newline (\\n) delimited list of entries.
100 The manifest file contains a newline (\\n) delimited list of entries.
101
101
102 Each line in this file defines an available bundle. Lines have the format:
102 Each line in this file defines an available bundle. Lines have the format:
103
103
104 <URL> [<key>=<value>[ <key>=<value>]]
104 <URL> [<key>=<value>[ <key>=<value>]]
105
105
106 That is, a URL followed by an optional, space-delimited list of key=value
106 That is, a URL followed by an optional, space-delimited list of key=value
107 pairs describing additional properties of this bundle. Both keys and values
107 pairs describing additional properties of this bundle. Both keys and values
108 are URI encoded.
108 are URI encoded.
109
109
110 For pull bundles, the URL is a path under the ``.hg`` directory of the
110 For pull bundles, the URL is a path under the ``.hg`` directory of the
111 repository.
111 repository.
112
112
113 Keys in UPPERCASE are reserved for use by Mercurial and are defined below.
113 Keys in UPPERCASE are reserved for use by Mercurial and are defined below.
114 All non-uppercase keys can be used by site installations. An example use
114 All non-uppercase keys can be used by site installations. An example use
115 for custom properties is to use the *datacenter* attribute to define which
115 for custom properties is to use the *datacenter* attribute to define which
116 data center a file is hosted in. Clients could then prefer a server in the
116 data center a file is hosted in. Clients could then prefer a server in the
117 data center closest to them.
117 data center closest to them.
118
118
119 The following reserved keys are currently defined:
119 The following reserved keys are currently defined:
120
120
121 BUNDLESPEC
121 BUNDLESPEC
122 A "bundle specification" string that describes the type of the bundle.
122 A "bundle specification" string that describes the type of the bundle.
123
123
124 These are string values that are accepted by the "--type" argument of
124 These are string values that are accepted by the "--type" argument of
125 :hg:`bundle`.
125 :hg:`bundle`.
126
126
127 The values are parsed in strict mode, which means they must be of the
127 The values are parsed in strict mode, which means they must be of the
128 "<compression>-<type>" form. See
128 "<compression>-<type>" form. See
129 mercurial.exchange.parsebundlespec() for more details.
129 mercurial.exchange.parsebundlespec() for more details.
130
130
131 :hg:`debugbundle --spec` can be used to print the bundle specification
131 :hg:`debugbundle --spec` can be used to print the bundle specification
132 string for a bundle file. The output of this command can be used verbatim
132 string for a bundle file. The output of this command can be used verbatim
133 for the value of ``BUNDLESPEC`` (it is already escaped).
133 for the value of ``BUNDLESPEC`` (it is already escaped).
134
134
135 Clients will automatically filter out specifications that are unknown or
135 Clients will automatically filter out specifications that are unknown or
136 unsupported so they won't attempt to download something that likely won't
136 unsupported so they won't attempt to download something that likely won't
137 apply.
137 apply.
138
138
139 The actual value doesn't impact client behavior beyond filtering:
139 The actual value doesn't impact client behavior beyond filtering:
140 clients will still sniff the bundle type from the header of downloaded
140 clients will still sniff the bundle type from the header of downloaded
141 files.
141 files.
142
142
143 **Use of this key is highly recommended**, as it allows clients to
143 **Use of this key is highly recommended**, as it allows clients to
144 easily skip unsupported bundles. If this key is not defined, an old
144 easily skip unsupported bundles. If this key is not defined, an old
145 client may attempt to apply a bundle that it is incapable of reading.
145 client may attempt to apply a bundle that it is incapable of reading.
146
146
147 REQUIRESNI
147 REQUIRESNI
148 Whether Server Name Indication (SNI) is required to connect to the URL.
148 Whether Server Name Indication (SNI) is required to connect to the URL.
149 SNI allows servers to use multiple certificates on the same IP. It is
149 SNI allows servers to use multiple certificates on the same IP. It is
150 somewhat common in CDNs and other hosting providers. Older Python
150 somewhat common in CDNs and other hosting providers. Older Python
151 versions do not support SNI. Defining this attribute enables clients
151 versions do not support SNI. Defining this attribute enables clients
152 with older Python versions to filter this entry without experiencing
152 with older Python versions to filter this entry without experiencing
153 an opaque SSL failure at connection time.
153 an opaque SSL failure at connection time.
154
154
155 If this is defined, it is important to advertise a non-SNI fallback
155 If this is defined, it is important to advertise a non-SNI fallback
156 URL or clients running old Python releases may not be able to clone
156 URL or clients running old Python releases may not be able to clone
157 with the clonebundles facility.
157 with the clonebundles facility.
158
158
159 Value should be "true".
159 Value should be "true".
160
160
161 REQUIREDRAM
161 REQUIREDRAM
162 Value specifies expected memory requirements to decode the payload.
162 Value specifies expected memory requirements to decode the payload.
163 Values can have suffixes for common bytes sizes. e.g. "64MB".
163 Values can have suffixes for common bytes sizes. e.g. "64MB".
164
164
165 This key is often used with zstd-compressed bundles using a high
165 This key is often used with zstd-compressed bundles using a high
166 compression level / window size, which can require 100+ MB of memory
166 compression level / window size, which can require 100+ MB of memory
167 to decode.
167 to decode.
168
168
169 heads
169 heads
170 Used for pull bundles. This contains the ``;`` separated changeset
170 Used for pull bundles. This contains the ``;`` separated changeset
171 hashes of the heads of the bundle content.
171 hashes of the heads of the bundle content.
172
172
173 bases
173 bases
174 Used for pull bundles. This contains the ``;`` separated changeset
174 Used for pull bundles. This contains the ``;`` separated changeset
175 hashes of the roots of the bundle content. This can be skipped if
175 hashes of the roots of the bundle content. This can be skipped if
176 the bundle was created without ``--base``.
176 the bundle was created without ``--base``.
177
177
178 Manifests can contain multiple entries. Assuming metadata is defined, clients
178 Manifests can contain multiple entries. Assuming metadata is defined, clients
179 will filter entries from the manifest that they don't support. The remaining
179 will filter entries from the manifest that they don't support. The remaining
180 entries are optionally sorted by client preferences
180 entries are optionally sorted by client preferences
181 (``ui.clonebundleprefers`` config option). The client then attempts
181 (``ui.clonebundleprefers`` config option). The client then attempts
182 to fetch the bundle at the first URL in the remaining list.
182 to fetch the bundle at the first URL in the remaining list.
183
183
184 **Errors when downloading a bundle will fail the entire clone operation:
184 **Errors when downloading a bundle will fail the entire clone operation:
185 clients do not automatically fall back to a traditional clone.** The reason
185 clients do not automatically fall back to a traditional clone.** The reason
186 for this is that if a server is using clone bundles, it is probably doing so
186 for this is that if a server is using clone bundles, it is probably doing so
187 because the feature is necessary to help it scale. In other words, there
187 because the feature is necessary to help it scale. In other words, there
188 is an assumption that clone load will be offloaded to another service and
188 is an assumption that clone load will be offloaded to another service and
189 that the Mercurial server isn't responsible for serving this clone load.
189 that the Mercurial server isn't responsible for serving this clone load.
190 If that other service experiences issues and clients start mass falling back to
190 If that other service experiences issues and clients start mass falling back to
191 the original Mercurial server, the added clone load could overwhelm the server
191 the original Mercurial server, the added clone load could overwhelm the server
192 due to unexpected load and effectively take it offline. Not having clients
192 due to unexpected load and effectively take it offline. Not having clients
193 automatically fall back to cloning from the original server mitigates this
193 automatically fall back to cloning from the original server mitigates this
194 scenario.
194 scenario.
195
195
196 Because there is no automatic Mercurial server fallback on failure of the
196 Because there is no automatic Mercurial server fallback on failure of the
197 bundle hosting service, it is important for server operators to view the bundle
197 bundle hosting service, it is important for server operators to view the bundle
198 hosting service as an extension of the Mercurial server in terms of
198 hosting service as an extension of the Mercurial server in terms of
199 availability and service level agreements: if the bundle hosting service goes
199 availability and service level agreements: if the bundle hosting service goes
200 down, so does the ability for clients to clone. Note: clients will see a
200 down, so does the ability for clients to clone. Note: clients will see a
201 message informing them how to bypass the clone bundles facility when a failure
201 message informing them how to bypass the clone bundles facility when a failure
202 occurs. So server operators should prepare for some people to follow these
202 occurs. So server operators should prepare for some people to follow these
203 instructions when a failure occurs, thus driving more load to the original
203 instructions when a failure occurs, thus driving more load to the original
204 Mercurial server when the bundle hosting service fails.
204 Mercurial server when the bundle hosting service fails.
205
205
206
206
207 inline clonebundles
207 inline clonebundles
208 -------------------
208 -------------------
209
209
210 It is possible to transmit clonebundles inline in case repositories are
210 It is possible to transmit clonebundles inline in case repositories are
211 accessed over SSH. This avoids having to setup an external HTTPS server
211 accessed over SSH. This avoids having to setup an external HTTPS server
212 and results in the same access control as already present for the SSH setup.
212 and results in the same access control as already present for the SSH setup.
213
213
214 Inline clonebundles should be placed into the `.hg/bundle-cache` directory.
214 Inline clonebundles should be placed into the `.hg/bundle-cache` directory.
215 A clonebundle at `.hg/bundle-cache/mybundle.bundle` is referred to
215 A clonebundle at `.hg/bundle-cache/mybundle.bundle` is referred to
216 in the `clonebundles.manifest` file as `peer-bundle-cache://mybundle.bundle`.
216 in the `clonebundles.manifest` file as `peer-bundle-cache://mybundle.bundle`.
217
217
218
218
219 auto-generation of clone bundles
219 auto-generation of clone bundles
220 --------------------------------
220 --------------------------------
221
221
222 It is possible to set Mercurial to automatically re-generate clone bundles when
222 It is possible to set Mercurial to automatically re-generate clone bundles when
223 enough new content is available.
223 enough new content is available.
224
224
225 Mercurial will take care of the process asynchronously. The defined list of
225 Mercurial will take care of the process asynchronously. The defined list of
226 bundle-type will be generated, uploaded, and advertised. Older bundles will get
226 bundle-type will be generated, uploaded, and advertised. Older bundles will get
227 decommissioned as newer ones replace them.
227 decommissioned as newer ones replace them.
228
228
229 Bundles Generation:
229 Bundles Generation:
230 ...................
230 ...................
231
231
232 The extension can generate multiple variants of the clone bundle. Each
232 The extension can generate multiple variants of the clone bundle. Each
233 different variant will be defined by the "bundle-spec" they use::
233 different variant will be defined by the "bundle-spec" they use::
234
234
235 [clone-bundles]
235 [clone-bundles]
236 auto-generate.formats= zstd-v2, gzip-v2
236 auto-generate.formats= zstd-v2, gzip-v2
237
237
238 See `hg help bundlespec` for details about available options.
238 See `hg help bundlespec` for details about available options.
239
239
240 By default, new bundles are generated when 5% of the repository contents or at
240 By default, new bundles are generated when 5% of the repository contents or at
241 least 1000 revisions are not contained in the cached bundles. This option can
241 least 1000 revisions are not contained in the cached bundles. This option can
242 be controlled by the `clone-bundles.trigger.below-bundled-ratio` option
242 be controlled by the `clone-bundles.trigger.below-bundled-ratio` option
243 (default 0.95) and the `clone-bundles.trigger.revs` option (default 1000)::
243 (default 0.95) and the `clone-bundles.trigger.revs` option (default 1000)::
244
244
245 [clone-bundles]
245 [clone-bundles]
246 trigger.below-bundled-ratio=0.95
246 trigger.below-bundled-ratio=0.95
247 trigger.revs=1000
247 trigger.revs=1000
248
248
249 This logic can be manually triggered using the `admin::clone-bundles-refresh`
249 This logic can be manually triggered using the `admin::clone-bundles-refresh`
250 command, or automatically on each repository change if
250 command, or automatically on each repository change if
251 `clone-bundles.auto-generate.on-change` is set to `yes`.
251 `clone-bundles.auto-generate.on-change` is set to `yes`.
252
252
253 [clone-bundles]
253 [clone-bundles]
254 auto-generate.on-change=yes
254 auto-generate.on-change=yes
255 auto-generate.formats= zstd-v2, gzip-v2
255 auto-generate.formats= zstd-v2, gzip-v2
256
256
257 Automatic Inline serving
257 Automatic Inline serving
258 ........................
258 ........................
259
259
260 The simplest way to serve the generated bundle is through the Mercurial
260 The simplest way to serve the generated bundle is through the Mercurial
261 protocol. However it is not the most efficient as request will still be served
261 protocol. However it is not the most efficient as request will still be served
262 by that main server. It is useful in case where authentication is complexe or
262 by that main server. It is useful in case where authentication is complexe or
263 when an efficient mirror system is already in use anyway. See the `inline
263 when an efficient mirror system is already in use anyway. See the `inline
264 clonebundles` section above for details about inline clonebundles
264 clonebundles` section above for details about inline clonebundles
265
265
266 To automatically serve generated bundle through inline clonebundle, simply set
266 To automatically serve generated bundle through inline clonebundle, simply set
267 the following option::
267 the following option::
268
268
269 auto-generate.serve-inline=yes
269 auto-generate.serve-inline=yes
270
270
271 Enabling this option disable the managed upload and serving explained below.
271 Enabling this option disable the managed upload and serving explained below.
272
272
273 Bundles Upload and Serving:
273 Bundles Upload and Serving:
274 ...........................
274 ...........................
275
275
276 This is the most efficient way to serve automatically generated clone bundles,
276 This is the most efficient way to serve automatically generated clone bundles,
277 but requires some setup.
277 but requires some setup.
278
278
279 The generated bundles need to be made available to users through a "public" URL.
279 The generated bundles need to be made available to users through a "public" URL.
280 This should be donne through `clone-bundles.upload-command` configuration. The
280 This should be donne through `clone-bundles.upload-command` configuration. The
281 value of this command should be a shell command. It will have access to the
281 value of this command should be a shell command. It will have access to the
282 bundle file path through the `$HGCB_BUNDLE_PATH` variable. And the expected
282 bundle file path through the `$HGCB_BUNDLE_PATH` variable. And the expected
283 basename in the "public" URL is accessible at::
283 basename in the "public" URL is accessible at::
284
284
285 [clone-bundles]
285 [clone-bundles]
286 upload-command=sftp put $HGCB_BUNDLE_PATH \
286 upload-command=sftp put $HGCB_BUNDLE_PATH \
287 sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME
287 sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME
288
288
289 If the file was already uploaded, the command must still succeed.
289 If the file was already uploaded, the command must still succeed.
290
290
291 After upload, the file should be available at an url defined by
291 After upload, the file should be available at an url defined by
292 `clone-bundles.url-template`.
292 `clone-bundles.url-template`.
293
293
294 [clone-bundles]
294 [clone-bundles]
295 url-template=https://bundles.host/cache/clone-bundles/{basename}
295 url-template=https://bundles.host/cache/clone-bundles/{basename}
296
296
297 Old bundles cleanup:
297 Old bundles cleanup:
298 ....................
298 ....................
299
299
300 When new bundles are generated, the older ones are no longer necessary and can
300 When new bundles are generated, the older ones are no longer necessary and can
301 be removed from storage. This is done through the `clone-bundles.delete-command`
301 be removed from storage. This is done through the `clone-bundles.delete-command`
302 configuration. The command is given the url of the artifact to delete through
302 configuration. The command is given the url of the artifact to delete through
303 the `$HGCB_BUNDLE_URL` environment variable.
303 the `$HGCB_BUNDLE_URL` environment variable.
304
304
305 [clone-bundles]
305 [clone-bundles]
306 delete-command=sftp rm sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME
306 delete-command=sftp rm sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME
307
307
308 If the file was already deleted, the command must still succeed.
308 If the file was already deleted, the command must still succeed.
309 """
309 """
310
310
311
311
312 import os
312 import os
313 import weakref
313 import weakref
314
314
315 from mercurial.i18n import _
315 from mercurial.i18n import _
316
316
317 from mercurial import (
317 from mercurial import (
318 bundlecaches,
318 bundlecaches,
319 commands,
319 commands,
320 error,
320 error,
321 extensions,
321 extensions,
322 localrepo,
322 localrepo,
323 lock,
323 lock,
324 node,
324 node,
325 registrar,
325 registrar,
326 util,
326 util,
327 wireprotov1server,
327 wireprotov1server,
328 )
328 )
329
329
330
330
331 from mercurial.utils import (
331 from mercurial.utils import (
332 procutil,
332 procutil,
333 )
333 )
334
334
335 testedwith = b'ships-with-hg-core'
335 testedwith = b'ships-with-hg-core'
336
336
337
337
338 def capabilities(orig, repo, proto):
338 def capabilities(orig, repo, proto):
339 caps = orig(repo, proto)
339 caps = orig(repo, proto)
340
340
341 # Only advertise if a manifest exists. This does add some I/O to requests.
341 # Only advertise if a manifest exists. This does add some I/O to requests.
342 # But this should be cheaper than a wasted network round trip due to
342 # But this should be cheaper than a wasted network round trip due to
343 # missing file.
343 # missing file.
344 if repo.vfs.exists(bundlecaches.CB_MANIFEST_FILE):
344 if repo.vfs.exists(bundlecaches.CB_MANIFEST_FILE):
345 caps.append(b'clonebundles')
345 caps.append(b'clonebundles')
346 caps.append(b'clonebundles_manifest')
346 caps.append(b'clonebundles_manifest')
347
347
348 return caps
348 return caps
349
349
350
350
351 def extsetup(ui):
351 def extsetup(ui):
352 extensions.wrapfunction(wireprotov1server, b'_capabilities', capabilities)
352 extensions.wrapfunction(wireprotov1server, b'_capabilities', capabilities)
353
353
354
354
355 # logic for bundle auto-generation
355 # logic for bundle auto-generation
356
356
357
357
358 configtable = {}
358 configtable = {}
359 configitem = registrar.configitem(configtable)
359 configitem = registrar.configitem(configtable)
360
360
361 cmdtable = {}
361 cmdtable = {}
362 command = registrar.command(cmdtable)
362 command = registrar.command(cmdtable)
363
363
364 configitem(b'clone-bundles', b'auto-generate.on-change', default=False)
364 configitem(b'clone-bundles', b'auto-generate.on-change', default=False)
365 configitem(b'clone-bundles', b'auto-generate.formats', default=list)
365 configitem(b'clone-bundles', b'auto-generate.formats', default=list)
366 configitem(b'clone-bundles', b'auto-generate.serve-inline', default=False)
366 configitem(b'clone-bundles', b'auto-generate.serve-inline', default=False)
367 configitem(b'clone-bundles', b'trigger.below-bundled-ratio', default=0.95)
367 configitem(b'clone-bundles', b'trigger.below-bundled-ratio', default=0.95)
368 configitem(b'clone-bundles', b'trigger.revs', default=1000)
368 configitem(b'clone-bundles', b'trigger.revs', default=1000)
369
369
370 configitem(b'clone-bundles', b'upload-command', default=None)
370 configitem(b'clone-bundles', b'upload-command', default=None)
371
371
372 configitem(b'clone-bundles', b'delete-command', default=None)
372 configitem(b'clone-bundles', b'delete-command', default=None)
373
373
374 configitem(b'clone-bundles', b'url-template', default=None)
374 configitem(b'clone-bundles', b'url-template', default=None)
375
375
376 configitem(b'devel', b'debug.clonebundles', default=False)
376 configitem(b'devel', b'debug.clonebundles', default=False)
377
377
378
378
379 # category for the post-close transaction hooks
379 # category for the post-close transaction hooks
380 CAT_POSTCLOSE = b"clonebundles-autobundles"
380 CAT_POSTCLOSE = b"clonebundles-autobundles"
381
381
382 # template for bundle file names
382 # template for bundle file names
383 BUNDLE_MASK = (
383 BUNDLE_MASK = (
384 b"full-%(bundle_type)s-%(revs)d_revs-%(tip_short)s_tip-%(op_id)s.hg"
384 b"full-%(bundle_type)s-%(revs)d_revs-%(tip_short)s_tip-%(op_id)s.hg"
385 )
385 )
386
386
387
387
388 # file in .hg/ use to track clonebundles being auto-generated
388 # file in .hg/ use to track clonebundles being auto-generated
389 AUTO_GEN_FILE = b'clonebundles.auto-gen'
389 AUTO_GEN_FILE = b'clonebundles.auto-gen'
390
390
391
391
392 class BundleBase(object):
392 class BundleBase(object):
393 """represents the core of properties that matters for us in a bundle
393 """represents the core of properties that matters for us in a bundle
394
394
395 :bundle_type: the bundlespec (see hg help bundlespec)
395 :bundle_type: the bundlespec (see hg help bundlespec)
396 :revs: the number of revisions in the repo at bundle creation time
396 :revs: the number of revisions in the repo at bundle creation time
397 :tip_rev: the rev-num of the tip revision
397 :tip_rev: the rev-num of the tip revision
398 :tip_node: the node id of the tip-most revision in the bundle
398 :tip_node: the node id of the tip-most revision in the bundle
399
399
400 :ready: True if the bundle is ready to be served
400 :ready: True if the bundle is ready to be served
401 """
401 """
402
402
403 ready = False
403 ready = False
404
404
405 def __init__(self, bundle_type, revs, tip_rev, tip_node):
405 def __init__(self, bundle_type, revs, tip_rev, tip_node):
406 self.bundle_type = bundle_type
406 self.bundle_type = bundle_type
407 self.revs = revs
407 self.revs = revs
408 self.tip_rev = tip_rev
408 self.tip_rev = tip_rev
409 self.tip_node = tip_node
409 self.tip_node = tip_node
410
410
411 def valid_for(self, repo):
411 def valid_for(self, repo):
412 """is this bundle applicable to the current repository
412 """is this bundle applicable to the current repository
413
413
414 This is useful for detecting bundles made irrelevant by stripping.
414 This is useful for detecting bundles made irrelevant by stripping.
415 """
415 """
416 tip_node = node.bin(self.tip_node)
416 tip_node = node.bin(self.tip_node)
417 return repo.changelog.index.get_rev(tip_node) == self.tip_rev
417 return repo.changelog.index.get_rev(tip_node) == self.tip_rev
418
418
419 def __eq__(self, other):
419 def __eq__(self, other):
420 left = (self.ready, self.bundle_type, self.tip_rev, self.tip_node)
420 left = (self.ready, self.bundle_type, self.tip_rev, self.tip_node)
421 right = (other.ready, other.bundle_type, other.tip_rev, other.tip_node)
421 right = (other.ready, other.bundle_type, other.tip_rev, other.tip_node)
422 return left == right
422 return left == right
423
423
424 def __neq__(self, other):
424 def __neq__(self, other):
425 return not self == other
425 return not self == other
426
426
427 def __cmp__(self, other):
427 def __cmp__(self, other):
428 if self == other:
428 if self == other:
429 return 0
429 return 0
430 return -1
430 return -1
431
431
432
432
433 class RequestedBundle(BundleBase):
433 class RequestedBundle(BundleBase):
434 """A bundle that should be generated.
434 """A bundle that should be generated.
435
435
436 Additional attributes compared to BundleBase
436 Additional attributes compared to BundleBase
437 :heads: list of head revisions (as rev-num)
437 :heads: list of head revisions (as rev-num)
438 :op_id: a "unique" identifier for the operation triggering the change
438 :op_id: a "unique" identifier for the operation triggering the change
439 """
439 """
440
440
441 def __init__(self, bundle_type, revs, tip_rev, tip_node, head_revs, op_id):
441 def __init__(self, bundle_type, revs, tip_rev, tip_node, head_revs, op_id):
442 self.head_revs = head_revs
442 self.head_revs = head_revs
443 self.op_id = op_id
443 self.op_id = op_id
444 super(RequestedBundle, self).__init__(
444 super(RequestedBundle, self).__init__(
445 bundle_type,
445 bundle_type,
446 revs,
446 revs,
447 tip_rev,
447 tip_rev,
448 tip_node,
448 tip_node,
449 )
449 )
450
450
451 @property
451 @property
452 def suggested_filename(self):
452 def suggested_filename(self):
453 """A filename that can be used for the generated bundle"""
453 """A filename that can be used for the generated bundle"""
454 data = {
454 data = {
455 b'bundle_type': self.bundle_type,
455 b'bundle_type': self.bundle_type,
456 b'revs': self.revs,
456 b'revs': self.revs,
457 b'heads': self.head_revs,
457 b'heads': self.head_revs,
458 b'tip_rev': self.tip_rev,
458 b'tip_rev': self.tip_rev,
459 b'tip_node': self.tip_node,
459 b'tip_node': self.tip_node,
460 b'tip_short': self.tip_node[:12],
460 b'tip_short': self.tip_node[:12],
461 b'op_id': self.op_id,
461 b'op_id': self.op_id,
462 }
462 }
463 return BUNDLE_MASK % data
463 return BUNDLE_MASK % data
464
464
465 def generate_bundle(self, repo, file_path):
465 def generate_bundle(self, repo, file_path):
466 """generate the bundle at `filepath`"""
466 """generate the bundle at `filepath`"""
467 commands.bundle(
467 commands.bundle(
468 repo.ui,
468 repo.ui,
469 repo,
469 repo,
470 file_path,
470 file_path,
471 base=[b"null"],
471 base=[b"null"],
472 rev=self.head_revs,
472 rev=self.head_revs,
473 type=self.bundle_type,
473 type=self.bundle_type,
474 quiet=True,
474 quiet=True,
475 )
475 )
476
476
477 def generating(self, file_path, hostname=None, pid=None):
477 def generating(self, file_path, hostname=None, pid=None):
478 """return a GeneratingBundle object from this object"""
478 """return a GeneratingBundle object from this object"""
479 if pid is None:
479 if pid is None:
480 pid = os.getpid()
480 pid = os.getpid()
481 if hostname is None:
481 if hostname is None:
482 hostname = lock._getlockprefix()
482 hostname = lock._getlockprefix()
483 return GeneratingBundle(
483 return GeneratingBundle(
484 self.bundle_type,
484 self.bundle_type,
485 self.revs,
485 self.revs,
486 self.tip_rev,
486 self.tip_rev,
487 self.tip_node,
487 self.tip_node,
488 hostname,
488 hostname,
489 pid,
489 pid,
490 file_path,
490 file_path,
491 )
491 )
492
492
493
493
494 class GeneratingBundle(BundleBase):
494 class GeneratingBundle(BundleBase):
495 """A bundle being generated
495 """A bundle being generated
496
496
497 extra attributes compared to BundleBase:
497 extra attributes compared to BundleBase:
498
498
499 :hostname: the hostname of the machine generating the bundle
499 :hostname: the hostname of the machine generating the bundle
500 :pid: the pid of the process generating the bundle
500 :pid: the pid of the process generating the bundle
501 :filepath: the target filename of the bundle
501 :filepath: the target filename of the bundle
502
502
503 These attributes exist to help detect stalled generation processes.
503 These attributes exist to help detect stalled generation processes.
504 """
504 """
505
505
506 ready = False
506 ready = False
507
507
508 def __init__(
508 def __init__(
509 self, bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
509 self, bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
510 ):
510 ):
511 self.hostname = hostname
511 self.hostname = hostname
512 self.pid = pid
512 self.pid = pid
513 self.filepath = filepath
513 self.filepath = filepath
514 super(GeneratingBundle, self).__init__(
514 super(GeneratingBundle, self).__init__(
515 bundle_type, revs, tip_rev, tip_node
515 bundle_type, revs, tip_rev, tip_node
516 )
516 )
517
517
518 @classmethod
518 @classmethod
519 def from_line(cls, line):
519 def from_line(cls, line):
520 """create an object by deserializing a line from AUTO_GEN_FILE"""
520 """create an object by deserializing a line from AUTO_GEN_FILE"""
521 assert line.startswith(b'PENDING-v1 ')
521 assert line.startswith(b'PENDING-v1 ')
522 (
522 (
523 __,
523 __,
524 bundle_type,
524 bundle_type,
525 revs,
525 revs,
526 tip_rev,
526 tip_rev,
527 tip_node,
527 tip_node,
528 hostname,
528 hostname,
529 pid,
529 pid,
530 filepath,
530 filepath,
531 ) = line.split()
531 ) = line.split()
532 hostname = util.urlreq.unquote(hostname)
532 hostname = util.urlreq.unquote(hostname)
533 filepath = util.urlreq.unquote(filepath)
533 filepath = util.urlreq.unquote(filepath)
534 revs = int(revs)
534 revs = int(revs)
535 tip_rev = int(tip_rev)
535 tip_rev = int(tip_rev)
536 pid = int(pid)
536 pid = int(pid)
537 return cls(
537 return cls(
538 bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
538 bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
539 )
539 )
540
540
541 def to_line(self):
541 def to_line(self):
542 """serialize the object to include as a line in AUTO_GEN_FILE"""
542 """serialize the object to include as a line in AUTO_GEN_FILE"""
543 templ = b"PENDING-v1 %s %d %d %s %s %d %s"
543 templ = b"PENDING-v1 %s %d %d %s %s %d %s"
544 data = (
544 data = (
545 self.bundle_type,
545 self.bundle_type,
546 self.revs,
546 self.revs,
547 self.tip_rev,
547 self.tip_rev,
548 self.tip_node,
548 self.tip_node,
549 util.urlreq.quote(self.hostname),
549 util.urlreq.quote(self.hostname),
550 self.pid,
550 self.pid,
551 util.urlreq.quote(self.filepath),
551 util.urlreq.quote(self.filepath),
552 )
552 )
553 return templ % data
553 return templ % data
554
554
555 def __eq__(self, other):
555 def __eq__(self, other):
556 if not super(GeneratingBundle, self).__eq__(other):
556 if not super(GeneratingBundle, self).__eq__(other):
557 return False
557 return False
558 left = (self.hostname, self.pid, self.filepath)
558 left = (self.hostname, self.pid, self.filepath)
559 right = (other.hostname, other.pid, other.filepath)
559 right = (other.hostname, other.pid, other.filepath)
560 return left == right
560 return left == right
561
561
562 def uploaded(self, url, basename):
562 def uploaded(self, url, basename):
563 """return a GeneratedBundle from this object"""
563 """return a GeneratedBundle from this object"""
564 return GeneratedBundle(
564 return GeneratedBundle(
565 self.bundle_type,
565 self.bundle_type,
566 self.revs,
566 self.revs,
567 self.tip_rev,
567 self.tip_rev,
568 self.tip_node,
568 self.tip_node,
569 url,
569 url,
570 basename,
570 basename,
571 )
571 )
572
572
573
573
574 class GeneratedBundle(BundleBase):
574 class GeneratedBundle(BundleBase):
575 """A bundle that is done being generated and can be served
575 """A bundle that is done being generated and can be served
576
576
577 extra attributes compared to BundleBase:
577 extra attributes compared to BundleBase:
578
578
579 :file_url: the url where the bundle is available.
579 :file_url: the url where the bundle is available.
580 :basename: the "basename" used to upload (useful for deletion)
580 :basename: the "basename" used to upload (useful for deletion)
581
581
582 These attributes exist to generate a bundle manifest
582 These attributes exist to generate a bundle manifest
583 (.hg/pullbundles.manifest)
583 (.hg/pullbundles.manifest)
584 """
584 """
585
585
586 ready = True
586 ready = True
587
587
588 def __init__(
588 def __init__(
589 self, bundle_type, revs, tip_rev, tip_node, file_url, basename
589 self, bundle_type, revs, tip_rev, tip_node, file_url, basename
590 ):
590 ):
591 self.file_url = file_url
591 self.file_url = file_url
592 self.basename = basename
592 self.basename = basename
593 super(GeneratedBundle, self).__init__(
593 super(GeneratedBundle, self).__init__(
594 bundle_type, revs, tip_rev, tip_node
594 bundle_type, revs, tip_rev, tip_node
595 )
595 )
596
596
597 @classmethod
597 @classmethod
598 def from_line(cls, line):
598 def from_line(cls, line):
599 """create an object by deserializing a line from AUTO_GEN_FILE"""
599 """create an object by deserializing a line from AUTO_GEN_FILE"""
600 assert line.startswith(b'DONE-v1 ')
600 assert line.startswith(b'DONE-v1 ')
601 (
601 (
602 __,
602 __,
603 bundle_type,
603 bundle_type,
604 revs,
604 revs,
605 tip_rev,
605 tip_rev,
606 tip_node,
606 tip_node,
607 file_url,
607 file_url,
608 basename,
608 basename,
609 ) = line.split()
609 ) = line.split()
610 revs = int(revs)
610 revs = int(revs)
611 tip_rev = int(tip_rev)
611 tip_rev = int(tip_rev)
612 file_url = util.urlreq.unquote(file_url)
612 file_url = util.urlreq.unquote(file_url)
613 return cls(bundle_type, revs, tip_rev, tip_node, file_url, basename)
613 return cls(bundle_type, revs, tip_rev, tip_node, file_url, basename)
614
614
615 def to_line(self):
615 def to_line(self):
616 """serialize the object to include as a line in AUTO_GEN_FILE"""
616 """serialize the object to include as a line in AUTO_GEN_FILE"""
617 templ = b"DONE-v1 %s %d %d %s %s %s"
617 templ = b"DONE-v1 %s %d %d %s %s %s"
618 data = (
618 data = (
619 self.bundle_type,
619 self.bundle_type,
620 self.revs,
620 self.revs,
621 self.tip_rev,
621 self.tip_rev,
622 self.tip_node,
622 self.tip_node,
623 util.urlreq.quote(self.file_url),
623 util.urlreq.quote(self.file_url),
624 self.basename,
624 self.basename,
625 )
625 )
626 return templ % data
626 return templ % data
627
627
628 def manifest_line(self):
628 def manifest_line(self):
629 """serialize the object to include as a line in pullbundles.manifest"""
629 """serialize the object to include as a line in pullbundles.manifest"""
630 templ = b"%s BUNDLESPEC=%s"
630 templ = b"%s BUNDLESPEC=%s"
631 if self.file_url.startswith(b'http'):
631 if self.file_url.startswith(b'http'):
632 templ += b" REQUIRESNI=true"
632 templ += b" REQUIRESNI=true"
633 return templ % (self.file_url, self.bundle_type)
633 return templ % (self.file_url, self.bundle_type)
634
634
635 def __eq__(self, other):
635 def __eq__(self, other):
636 if not super(GeneratedBundle, self).__eq__(other):
636 if not super(GeneratedBundle, self).__eq__(other):
637 return False
637 return False
638 return self.file_url == other.file_url
638 return self.file_url == other.file_url
639
639
640
640
641 def parse_auto_gen(content):
641 def parse_auto_gen(content):
642 """parse the AUTO_GEN_FILE to return a list of Bundle object"""
642 """parse the AUTO_GEN_FILE to return a list of Bundle object"""
643 bundles = []
643 bundles = []
644 for line in content.splitlines():
644 for line in content.splitlines():
645 if line.startswith(b'PENDING-v1 '):
645 if line.startswith(b'PENDING-v1 '):
646 bundles.append(GeneratingBundle.from_line(line))
646 bundles.append(GeneratingBundle.from_line(line))
647 elif line.startswith(b'DONE-v1 '):
647 elif line.startswith(b'DONE-v1 '):
648 bundles.append(GeneratedBundle.from_line(line))
648 bundles.append(GeneratedBundle.from_line(line))
649 return bundles
649 return bundles
650
650
651
651
652 def dumps_auto_gen(bundles):
652 def dumps_auto_gen(bundles):
653 """serialize a list of Bundle as a AUTO_GEN_FILE content"""
653 """serialize a list of Bundle as a AUTO_GEN_FILE content"""
654 lines = []
654 lines = []
655 for b in bundles:
655 for b in bundles:
656 lines.append(b"%s\n" % b.to_line())
656 lines.append(b"%s\n" % b.to_line())
657 lines.sort()
657 lines.sort()
658 return b"".join(lines)
658 return b"".join(lines)
659
659
660
660
661 def read_auto_gen(repo):
661 def read_auto_gen(repo):
662 """read the AUTO_GEN_FILE for the <repo> a list of Bundle object"""
662 """read the AUTO_GEN_FILE for the <repo> a list of Bundle object"""
663 data = repo.vfs.tryread(AUTO_GEN_FILE)
663 data = repo.vfs.tryread(AUTO_GEN_FILE)
664 if not data:
664 if not data:
665 return []
665 return []
666 return parse_auto_gen(data)
666 return parse_auto_gen(data)
667
667
668
668
669 def write_auto_gen(repo, bundles):
669 def write_auto_gen(repo, bundles):
670 """write a list of Bundle objects into the repo's AUTO_GEN_FILE"""
670 """write a list of Bundle objects into the repo's AUTO_GEN_FILE"""
671 assert repo._cb_lock_ref is not None
671 assert repo._cb_lock_ref is not None
672 data = dumps_auto_gen(bundles)
672 data = dumps_auto_gen(bundles)
673 with repo.vfs(AUTO_GEN_FILE, mode=b'wb', atomictemp=True) as f:
673 with repo.vfs(AUTO_GEN_FILE, mode=b'wb', atomictemp=True) as f:
674 f.write(data)
674 f.write(data)
675
675
676
676
677 def generate_manifest(bundles):
677 def generate_manifest(bundles):
678 """write a list of Bundle objects into the repo's AUTO_GEN_FILE"""
678 """write a list of Bundle objects into the repo's AUTO_GEN_FILE"""
679 bundles = list(bundles)
679 bundles = list(bundles)
680 bundles.sort(key=lambda b: b.bundle_type)
680 bundles.sort(key=lambda b: b.bundle_type)
681 lines = []
681 lines = []
682 for b in bundles:
682 for b in bundles:
683 lines.append(b"%s\n" % b.manifest_line())
683 lines.append(b"%s\n" % b.manifest_line())
684 return b"".join(lines)
684 return b"".join(lines)
685
685
686
686
687 def update_ondisk_manifest(repo):
687 def update_ondisk_manifest(repo):
688 """update the clonebundle manifest with latest url"""
688 """update the clonebundle manifest with latest url"""
689 with repo.clonebundles_lock():
689 with repo.clonebundles_lock():
690 bundles = read_auto_gen(repo)
690 bundles = read_auto_gen(repo)
691
691
692 per_types = {}
692 per_types = {}
693 for b in bundles:
693 for b in bundles:
694 if not (b.ready and b.valid_for(repo)):
694 if not (b.ready and b.valid_for(repo)):
695 continue
695 continue
696 current = per_types.get(b.bundle_type)
696 current = per_types.get(b.bundle_type)
697 if current is not None and current.revs >= b.revs:
697 if current is not None and current.revs >= b.revs:
698 continue
698 continue
699 per_types[b.bundle_type] = b
699 per_types[b.bundle_type] = b
700 manifest = generate_manifest(per_types.values())
700 manifest = generate_manifest(per_types.values())
701 with repo.vfs(
701 with repo.vfs(
702 bundlecaches.CB_MANIFEST_FILE, mode=b"wb", atomictemp=True
702 bundlecaches.CB_MANIFEST_FILE, mode=b"wb", atomictemp=True
703 ) as f:
703 ) as f:
704 f.write(manifest)
704 f.write(manifest)
705
705
706
706
707 def update_bundle_list(repo, new_bundles=(), del_bundles=()):
707 def update_bundle_list(repo, new_bundles=(), del_bundles=()):
708 """modify the repo's AUTO_GEN_FILE
708 """modify the repo's AUTO_GEN_FILE
709
709
710 This method also regenerates the clone bundle manifest when needed"""
710 This method also regenerates the clone bundle manifest when needed"""
711 with repo.clonebundles_lock():
711 with repo.clonebundles_lock():
712 bundles = read_auto_gen(repo)
712 bundles = read_auto_gen(repo)
713 if del_bundles:
713 if del_bundles:
714 bundles = [b for b in bundles if b not in del_bundles]
714 bundles = [b for b in bundles if b not in del_bundles]
715 new_bundles = [b for b in new_bundles if b not in bundles]
715 new_bundles = [b for b in new_bundles if b not in bundles]
716 bundles.extend(new_bundles)
716 bundles.extend(new_bundles)
717 write_auto_gen(repo, bundles)
717 write_auto_gen(repo, bundles)
718 all_changed = []
718 all_changed = []
719 all_changed.extend(new_bundles)
719 all_changed.extend(new_bundles)
720 all_changed.extend(del_bundles)
720 all_changed.extend(del_bundles)
721 if any(b.ready for b in all_changed):
721 if any(b.ready for b in all_changed):
722 update_ondisk_manifest(repo)
722 update_ondisk_manifest(repo)
723
723
724
724
725 def cleanup_tmp_bundle(repo, target):
725 def cleanup_tmp_bundle(repo, target):
726 """remove a GeneratingBundle file and entry"""
726 """remove a GeneratingBundle file and entry"""
727 assert not target.ready
727 assert not target.ready
728 with repo.clonebundles_lock():
728 with repo.clonebundles_lock():
729 repo.vfs.tryunlink(target.filepath)
729 repo.vfs.tryunlink(target.filepath)
730 update_bundle_list(repo, del_bundles=[target])
730 update_bundle_list(repo, del_bundles=[target])
731
731
732
732
733 def finalize_one_bundle(repo, target):
733 def finalize_one_bundle(repo, target):
734 """upload a generated bundle and advertise it in the clonebundles.manifest"""
734 """upload a generated bundle and advertise it in the clonebundles.manifest"""
735 with repo.clonebundles_lock():
735 with repo.clonebundles_lock():
736 bundles = read_auto_gen(repo)
736 bundles = read_auto_gen(repo)
737 if target in bundles and target.valid_for(repo):
737 if target in bundles and target.valid_for(repo):
738 result = upload_bundle(repo, target)
738 result = upload_bundle(repo, target)
739 update_bundle_list(repo, new_bundles=[result])
739 update_bundle_list(repo, new_bundles=[result])
740 cleanup_tmp_bundle(repo, target)
740 cleanup_tmp_bundle(repo, target)
741
741
742
742
743 def find_outdated_bundles(repo, bundles):
743 def find_outdated_bundles(repo, bundles):
744 """finds outdated bundles"""
744 """finds outdated bundles"""
745 olds = []
745 olds = []
746 per_types = {}
746 per_types = {}
747 for b in bundles:
747 for b in bundles:
748 if not b.valid_for(repo):
748 if not b.valid_for(repo):
749 olds.append(b)
749 olds.append(b)
750 continue
750 continue
751 l = per_types.setdefault(b.bundle_type, [])
751 l = per_types.setdefault(b.bundle_type, [])
752 l.append(b)
752 l.append(b)
753 for key in sorted(per_types):
753 for key in sorted(per_types):
754 all = per_types[key]
754 all = per_types[key]
755 if len(all) > 1:
755 if len(all) > 1:
756 all.sort(key=lambda b: b.revs, reverse=True)
756 all.sort(key=lambda b: b.revs, reverse=True)
757 olds.extend(all[1:])
757 olds.extend(all[1:])
758 return olds
758 return olds
759
759
760
760
761 def collect_garbage(repo):
761 def collect_garbage(repo):
762 """finds outdated bundles and get them deleted"""
762 """finds outdated bundles and get them deleted"""
763 with repo.clonebundles_lock():
763 with repo.clonebundles_lock():
764 bundles = read_auto_gen(repo)
764 bundles = read_auto_gen(repo)
765 olds = find_outdated_bundles(repo, bundles)
765 olds = find_outdated_bundles(repo, bundles)
766 for o in olds:
766 for o in olds:
767 delete_bundle(repo, o)
767 delete_bundle(repo, o)
768 update_bundle_list(repo, del_bundles=olds)
768 update_bundle_list(repo, del_bundles=olds)
769
769
770
770
771 def upload_bundle(repo, bundle):
771 def upload_bundle(repo, bundle):
772 """upload the result of a GeneratingBundle and return a GeneratedBundle
772 """upload the result of a GeneratingBundle and return a GeneratedBundle
773
773
774 The upload is done using the `clone-bundles.upload-command`
774 The upload is done using the `clone-bundles.upload-command`
775 """
775 """
776 inline = repo.ui.config(b'clone-bundles', b'auto-generate.serve-inline')
776 inline = repo.ui.config(b'clone-bundles', b'auto-generate.serve-inline')
777 basename = repo.vfs.basename(bundle.filepath)
777 basename = repo.vfs.basename(bundle.filepath)
778 if inline:
778 if inline:
779 dest_dir = repo.vfs.join(bundlecaches.BUNDLE_CACHE_DIR)
779 dest_dir = repo.vfs.join(bundlecaches.BUNDLE_CACHE_DIR)
780 repo.vfs.makedirs(dest_dir)
780 repo.vfs.makedirs(dest_dir)
781 dest = repo.vfs.join(dest_dir, basename)
781 dest = repo.vfs.join(dest_dir, basename)
782 util.copyfiles(bundle.filepath, dest, hardlink=True)
782 util.copyfiles(bundle.filepath, dest, hardlink=True)
783 url = bundlecaches.CLONEBUNDLESCHEME + basename
783 url = bundlecaches.CLONEBUNDLESCHEME + basename
784 return bundle.uploaded(url, basename)
784 return bundle.uploaded(url, basename)
785 else:
785 else:
786 cmd = repo.ui.config(b'clone-bundles', b'upload-command')
786 cmd = repo.ui.config(b'clone-bundles', b'upload-command')
787 url = repo.ui.config(b'clone-bundles', b'url-template')
787 url = repo.ui.config(b'clone-bundles', b'url-template')
788 filepath = procutil.shellquote(bundle.filepath)
788 filepath = procutil.shellquote(bundle.filepath)
789 variables = {
789 variables = {
790 b'HGCB_BUNDLE_PATH': filepath,
790 b'HGCB_BUNDLE_PATH': filepath,
791 b'HGCB_BUNDLE_BASENAME': basename,
791 b'HGCB_BUNDLE_BASENAME': basename,
792 }
792 }
793 env = procutil.shellenviron(environ=variables)
793 env = procutil.shellenviron(environ=variables)
794 ret = repo.ui.system(cmd, environ=env)
794 ret = repo.ui.system(cmd, environ=env)
795 if ret:
795 if ret:
796 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
796 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
797 url = (
797 url = (
798 url.decode('utf8')
798 url.decode('utf8')
799 .format(basename=basename.decode('utf8'))
799 .format(basename=basename.decode('utf8'))
800 .encode('utf8')
800 .encode('utf8')
801 )
801 )
802 return bundle.uploaded(url, basename)
802 return bundle.uploaded(url, basename)
803
803
804
804
805 def delete_bundle(repo, bundle):
805 def delete_bundle(repo, bundle):
806 """delete a bundle from storage"""
806 """delete a bundle from storage"""
807 assert bundle.ready
807 assert bundle.ready
808
808
809 inline = bundle.file_url.startswith(bundlecaches.CLONEBUNDLESCHEME)
809 inline = bundle.file_url.startswith(bundlecaches.CLONEBUNDLESCHEME)
810
810
811 if inline:
811 if inline:
812 msg = b'clone-bundles: deleting inline bundle %s\n'
812 msg = b'clone-bundles: deleting inline bundle %s\n'
813 else:
813 else:
814 msg = b'clone-bundles: deleting bundle %s\n'
814 msg = b'clone-bundles: deleting bundle %s\n'
815 msg %= bundle.basename
815 msg %= bundle.basename
816 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
816 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
817 repo.ui.write(msg)
817 repo.ui.write(msg)
818 else:
818 else:
819 repo.ui.debug(msg)
819 repo.ui.debug(msg)
820
820
821 if inline:
821 if inline:
822 inline_path = repo.vfs.join(
822 inline_path = repo.vfs.join(
823 bundlecaches.BUNDLE_CACHE_DIR,
823 bundlecaches.BUNDLE_CACHE_DIR,
824 bundle.basename,
824 bundle.basename,
825 )
825 )
826 util.tryunlink(inline_path)
826 util.tryunlink(inline_path)
827 else:
827 else:
828 cmd = repo.ui.config(b'clone-bundles', b'delete-command')
828 cmd = repo.ui.config(b'clone-bundles', b'delete-command')
829 variables = {
829 variables = {
830 b'HGCB_BUNDLE_URL': bundle.file_url,
830 b'HGCB_BUNDLE_URL': bundle.file_url,
831 b'HGCB_BASENAME': bundle.basename,
831 b'HGCB_BASENAME': bundle.basename,
832 }
832 }
833 env = procutil.shellenviron(environ=variables)
833 env = procutil.shellenviron(environ=variables)
834 ret = repo.ui.system(cmd, environ=env)
834 ret = repo.ui.system(cmd, environ=env)
835 if ret:
835 if ret:
836 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
836 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
837
837
838
838
839 def auto_bundle_needed_actions(repo, bundles, op_id):
839 def auto_bundle_needed_actions(repo, bundles, op_id):
840 """find the list of bundles that need action
840 """find the list of bundles that need action
841
841
842 returns a list of RequestedBundle objects that need to be generated and
842 returns a list of RequestedBundle objects that need to be generated and
843 uploaded."""
843 uploaded."""
844 create_bundles = []
844 create_bundles = []
845 delete_bundles = []
845 delete_bundles = []
846 repo = repo.filtered(b"immutable")
846 repo = repo.filtered(b"immutable")
847 targets = repo.ui.configlist(b'clone-bundles', b'auto-generate.formats')
847 targets = repo.ui.configlist(b'clone-bundles', b'auto-generate.formats')
848 ratio = float(
848 ratio = float(
849 repo.ui.config(b'clone-bundles', b'trigger.below-bundled-ratio')
849 repo.ui.config(b'clone-bundles', b'trigger.below-bundled-ratio')
850 )
850 )
851 abs_revs = repo.ui.configint(b'clone-bundles', b'trigger.revs')
851 abs_revs = repo.ui.configint(b'clone-bundles', b'trigger.revs')
852 revs = len(repo.changelog)
852 revs = len(repo.changelog)
853 generic_data = {
853 generic_data = {
854 'revs': revs,
854 'revs': revs,
855 'head_revs': repo.changelog.headrevs(),
855 'head_revs': repo.changelog.headrevs(),
856 'tip_rev': repo.changelog.tiprev(),
856 'tip_rev': repo.changelog.tiprev(),
857 'tip_node': node.hex(repo.changelog.tip()),
857 'tip_node': node.hex(repo.changelog.tip()),
858 'op_id': op_id,
858 'op_id': op_id,
859 }
859 }
860 for t in targets:
860 for t in targets:
861 t = bundlecaches.parsebundlespec(repo, t, strict=False).as_spec()
861 if new_bundle_needed(repo, bundles, ratio, abs_revs, t, revs):
862 if new_bundle_needed(repo, bundles, ratio, abs_revs, t, revs):
862 data = generic_data.copy()
863 data = generic_data.copy()
863 data['bundle_type'] = t
864 data['bundle_type'] = t
864 b = RequestedBundle(**data)
865 b = RequestedBundle(**data)
865 create_bundles.append(b)
866 create_bundles.append(b)
866 delete_bundles.extend(find_outdated_bundles(repo, bundles))
867 delete_bundles.extend(find_outdated_bundles(repo, bundles))
867 return create_bundles, delete_bundles
868 return create_bundles, delete_bundles
868
869
869
870
870 def new_bundle_needed(repo, bundles, ratio, abs_revs, bundle_type, revs):
871 def new_bundle_needed(repo, bundles, ratio, abs_revs, bundle_type, revs):
871 """consider the current cached content and trigger new bundles if needed"""
872 """consider the current cached content and trigger new bundles if needed"""
872 threshold = max((revs * ratio), (revs - abs_revs))
873 threshold = max((revs * ratio), (revs - abs_revs))
873 for b in bundles:
874 for b in bundles:
874 if not b.valid_for(repo) or b.bundle_type != bundle_type:
875 if not b.valid_for(repo) or b.bundle_type != bundle_type:
875 continue
876 continue
876 if b.revs > threshold:
877 if b.revs > threshold:
877 return False
878 return False
878 return True
879 return True
879
880
880
881
881 def start_one_bundle(repo, bundle):
882 def start_one_bundle(repo, bundle):
882 """start the generation of a single bundle file
883 """start the generation of a single bundle file
883
884
884 the `bundle` argument should be a RequestedBundle object.
885 the `bundle` argument should be a RequestedBundle object.
885
886
886 This data is passed to the `debugmakeclonebundles` "as is".
887 This data is passed to the `debugmakeclonebundles` "as is".
887 """
888 """
888 data = util.pickle.dumps(bundle)
889 data = util.pickle.dumps(bundle)
889 cmd = [procutil.hgexecutable(), b'--cwd', repo.path, INTERNAL_CMD]
890 cmd = [procutil.hgexecutable(), b'--cwd', repo.path, INTERNAL_CMD]
890 env = procutil.shellenviron()
891 env = procutil.shellenviron()
891 msg = b'clone-bundles: starting bundle generation: %s\n'
892 msg = b'clone-bundles: starting bundle generation: %s\n'
892 stdout = None
893 stdout = None
893 stderr = None
894 stderr = None
894 waits = []
895 waits = []
895 record_wait = None
896 record_wait = None
896 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
897 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
897 stdout = procutil.stdout
898 stdout = procutil.stdout
898 stderr = procutil.stderr
899 stderr = procutil.stderr
899 repo.ui.write(msg % bundle.bundle_type)
900 repo.ui.write(msg % bundle.bundle_type)
900 record_wait = waits.append
901 record_wait = waits.append
901 else:
902 else:
902 repo.ui.debug(msg % bundle.bundle_type)
903 repo.ui.debug(msg % bundle.bundle_type)
903 bg = procutil.runbgcommand
904 bg = procutil.runbgcommand
904 bg(
905 bg(
905 cmd,
906 cmd,
906 env,
907 env,
907 stdin_bytes=data,
908 stdin_bytes=data,
908 stdout=stdout,
909 stdout=stdout,
909 stderr=stderr,
910 stderr=stderr,
910 record_wait=record_wait,
911 record_wait=record_wait,
911 )
912 )
912 for f in waits:
913 for f in waits:
913 f()
914 f()
914
915
915
916
916 INTERNAL_CMD = b'debug::internal-make-clone-bundles'
917 INTERNAL_CMD = b'debug::internal-make-clone-bundles'
917
918
918
919
919 @command(INTERNAL_CMD, [], b'')
920 @command(INTERNAL_CMD, [], b'')
920 def debugmakeclonebundles(ui, repo):
921 def debugmakeclonebundles(ui, repo):
921 """Internal command to auto-generate debug bundles"""
922 """Internal command to auto-generate debug bundles"""
922 requested_bundle = util.pickle.load(procutil.stdin)
923 requested_bundle = util.pickle.load(procutil.stdin)
923 procutil.stdin.close()
924 procutil.stdin.close()
924
925
925 collect_garbage(repo)
926 collect_garbage(repo)
926
927
927 fname = requested_bundle.suggested_filename
928 fname = requested_bundle.suggested_filename
928 fpath = repo.vfs.makedirs(b'tmp-bundles')
929 fpath = repo.vfs.makedirs(b'tmp-bundles')
929 fpath = repo.vfs.join(b'tmp-bundles', fname)
930 fpath = repo.vfs.join(b'tmp-bundles', fname)
930 bundle = requested_bundle.generating(fpath)
931 bundle = requested_bundle.generating(fpath)
931 update_bundle_list(repo, new_bundles=[bundle])
932 update_bundle_list(repo, new_bundles=[bundle])
932
933
933 requested_bundle.generate_bundle(repo, fpath)
934 requested_bundle.generate_bundle(repo, fpath)
934
935
935 repo.invalidate()
936 repo.invalidate()
936 finalize_one_bundle(repo, bundle)
937 finalize_one_bundle(repo, bundle)
937
938
938
939
939 def make_auto_bundler(source_repo):
940 def make_auto_bundler(source_repo):
940 reporef = weakref.ref(source_repo)
941 reporef = weakref.ref(source_repo)
941
942
942 def autobundle(tr):
943 def autobundle(tr):
943 repo = reporef()
944 repo = reporef()
944 assert repo is not None
945 assert repo is not None
945 bundles = read_auto_gen(repo)
946 bundles = read_auto_gen(repo)
946 new, __ = auto_bundle_needed_actions(repo, bundles, b"%d_txn" % id(tr))
947 new, __ = auto_bundle_needed_actions(repo, bundles, b"%d_txn" % id(tr))
947 for data in new:
948 for data in new:
948 start_one_bundle(repo, data)
949 start_one_bundle(repo, data)
949 return None
950 return None
950
951
951 return autobundle
952 return autobundle
952
953
953
954
954 def reposetup(ui, repo):
955 def reposetup(ui, repo):
955 """install the two pieces needed for automatic clonebundle generation
956 """install the two pieces needed for automatic clonebundle generation
956
957
957 - add a "post-close" hook that fires bundling when needed
958 - add a "post-close" hook that fires bundling when needed
958 - introduce a clone-bundle lock to let multiple processes meddle with the
959 - introduce a clone-bundle lock to let multiple processes meddle with the
959 state files.
960 state files.
960 """
961 """
961 if not repo.local():
962 if not repo.local():
962 return
963 return
963
964
964 class autobundlesrepo(repo.__class__):
965 class autobundlesrepo(repo.__class__):
965 def transaction(self, *args, **kwargs):
966 def transaction(self, *args, **kwargs):
966 tr = super(autobundlesrepo, self).transaction(*args, **kwargs)
967 tr = super(autobundlesrepo, self).transaction(*args, **kwargs)
967 enabled = repo.ui.configbool(
968 enabled = repo.ui.configbool(
968 b'clone-bundles',
969 b'clone-bundles',
969 b'auto-generate.on-change',
970 b'auto-generate.on-change',
970 )
971 )
971 targets = repo.ui.configlist(
972 targets = repo.ui.configlist(
972 b'clone-bundles', b'auto-generate.formats'
973 b'clone-bundles', b'auto-generate.formats'
973 )
974 )
974 if enabled and targets:
975 if enabled and targets:
975 tr.addpostclose(CAT_POSTCLOSE, make_auto_bundler(self))
976 tr.addpostclose(CAT_POSTCLOSE, make_auto_bundler(self))
976 return tr
977 return tr
977
978
978 @localrepo.unfilteredmethod
979 @localrepo.unfilteredmethod
979 def clonebundles_lock(self, wait=True):
980 def clonebundles_lock(self, wait=True):
980 '''Lock the repository file related to clone bundles'''
981 '''Lock the repository file related to clone bundles'''
981 if not util.safehasattr(self, '_cb_lock_ref'):
982 if not util.safehasattr(self, '_cb_lock_ref'):
982 self._cb_lock_ref = None
983 self._cb_lock_ref = None
983 l = self._currentlock(self._cb_lock_ref)
984 l = self._currentlock(self._cb_lock_ref)
984 if l is not None:
985 if l is not None:
985 l.lock()
986 l.lock()
986 return l
987 return l
987
988
988 l = self._lock(
989 l = self._lock(
989 vfs=self.vfs,
990 vfs=self.vfs,
990 lockname=b"clonebundleslock",
991 lockname=b"clonebundleslock",
991 wait=wait,
992 wait=wait,
992 releasefn=None,
993 releasefn=None,
993 acquirefn=None,
994 acquirefn=None,
994 desc=_(b'repository %s') % self.origroot,
995 desc=_(b'repository %s') % self.origroot,
995 )
996 )
996 self._cb_lock_ref = weakref.ref(l)
997 self._cb_lock_ref = weakref.ref(l)
997 return l
998 return l
998
999
999 repo._wlockfreeprefix.add(AUTO_GEN_FILE)
1000 repo._wlockfreeprefix.add(AUTO_GEN_FILE)
1000 repo._wlockfreeprefix.add(bundlecaches.CB_MANIFEST_FILE)
1001 repo._wlockfreeprefix.add(bundlecaches.CB_MANIFEST_FILE)
1001 repo.__class__ = autobundlesrepo
1002 repo.__class__ = autobundlesrepo
1002
1003
1003
1004
1004 @command(
1005 @command(
1005 b'admin::clone-bundles-refresh',
1006 b'admin::clone-bundles-refresh',
1006 [
1007 [
1007 (
1008 (
1008 b'',
1009 b'',
1009 b'background',
1010 b'background',
1010 False,
1011 False,
1011 _(b'start bundle generation in the background'),
1012 _(b'start bundle generation in the background'),
1012 ),
1013 ),
1013 ],
1014 ],
1014 b'',
1015 b'',
1015 )
1016 )
1016 def cmd_admin_clone_bundles_refresh(
1017 def cmd_admin_clone_bundles_refresh(
1017 ui,
1018 ui,
1018 repo: localrepo.localrepository,
1019 repo: localrepo.localrepository,
1019 background=False,
1020 background=False,
1020 ):
1021 ):
1021 """generate clone bundles according to the configuration
1022 """generate clone bundles according to the configuration
1022
1023
1023 This runs the logic for automatic generation, removing outdated bundles and
1024 This runs the logic for automatic generation, removing outdated bundles and
1024 generating new ones if necessary. See :hg:`help -e clone-bundles` for
1025 generating new ones if necessary. See :hg:`help -e clone-bundles` for
1025 details about how to configure this feature.
1026 details about how to configure this feature.
1026 """
1027 """
1027 debug = repo.ui.configbool(b'devel', b'debug.clonebundles')
1028 debug = repo.ui.configbool(b'devel', b'debug.clonebundles')
1028 bundles = read_auto_gen(repo)
1029 bundles = read_auto_gen(repo)
1029 op_id = b"%d_acbr" % os.getpid()
1030 op_id = b"%d_acbr" % os.getpid()
1030 create, delete = auto_bundle_needed_actions(repo, bundles, op_id)
1031 create, delete = auto_bundle_needed_actions(repo, bundles, op_id)
1031
1032
1032 # if some bundles are scheduled for creation in the background, they will
1033 # if some bundles are scheduled for creation in the background, they will
1033 # deal with garbage collection too, so no need to synchroniously do it.
1034 # deal with garbage collection too, so no need to synchroniously do it.
1034 #
1035 #
1035 # However if no bundles are scheduled for creation, we need to explicitly do
1036 # However if no bundles are scheduled for creation, we need to explicitly do
1036 # it here.
1037 # it here.
1037 if not (background and create):
1038 if not (background and create):
1038 # we clean up outdated bundles before generating new ones to keep the
1039 # we clean up outdated bundles before generating new ones to keep the
1039 # last two versions of the bundle around for a while and avoid having to
1040 # last two versions of the bundle around for a while and avoid having to
1040 # deal with clients that just got served a manifest.
1041 # deal with clients that just got served a manifest.
1041 for o in delete:
1042 for o in delete:
1042 delete_bundle(repo, o)
1043 delete_bundle(repo, o)
1043 update_bundle_list(repo, del_bundles=delete)
1044 update_bundle_list(repo, del_bundles=delete)
1044
1045
1045 if create:
1046 if create:
1046 fpath = repo.vfs.makedirs(b'tmp-bundles')
1047 fpath = repo.vfs.makedirs(b'tmp-bundles')
1047
1048
1048 if background:
1049 if background:
1049 for requested_bundle in create:
1050 for requested_bundle in create:
1050 start_one_bundle(repo, requested_bundle)
1051 start_one_bundle(repo, requested_bundle)
1051 else:
1052 else:
1052 for requested_bundle in create:
1053 for requested_bundle in create:
1053 if debug:
1054 if debug:
1054 msg = b'clone-bundles: starting bundle generation: %s\n'
1055 msg = b'clone-bundles: starting bundle generation: %s\n'
1055 repo.ui.write(msg % requested_bundle.bundle_type)
1056 repo.ui.write(msg % requested_bundle.bundle_type)
1056 fname = requested_bundle.suggested_filename
1057 fname = requested_bundle.suggested_filename
1057 fpath = repo.vfs.join(b'tmp-bundles', fname)
1058 fpath = repo.vfs.join(b'tmp-bundles', fname)
1058 generating_bundle = requested_bundle.generating(fpath)
1059 generating_bundle = requested_bundle.generating(fpath)
1059 update_bundle_list(repo, new_bundles=[generating_bundle])
1060 update_bundle_list(repo, new_bundles=[generating_bundle])
1060 requested_bundle.generate_bundle(repo, fpath)
1061 requested_bundle.generate_bundle(repo, fpath)
1061 result = upload_bundle(repo, generating_bundle)
1062 result = upload_bundle(repo, generating_bundle)
1062 update_bundle_list(repo, new_bundles=[result])
1063 update_bundle_list(repo, new_bundles=[result])
1063 update_ondisk_manifest(repo)
1064 update_ondisk_manifest(repo)
1064 cleanup_tmp_bundle(repo, generating_bundle)
1065 cleanup_tmp_bundle(repo, generating_bundle)
1065
1066
1066
1067
1067 @command(b'admin::clone-bundles-clear', [], b'')
1068 @command(b'admin::clone-bundles-clear', [], b'')
1068 def cmd_admin_clone_bundles_clear(ui, repo: localrepo.localrepository):
1069 def cmd_admin_clone_bundles_clear(ui, repo: localrepo.localrepository):
1069 """remove existing clone bundle caches
1070 """remove existing clone bundle caches
1070
1071
1071 See `hg help admin::clone-bundles-refresh` for details on how to regenerate
1072 See `hg help admin::clone-bundles-refresh` for details on how to regenerate
1072 them.
1073 them.
1073
1074
1074 This command will only affect bundles currently available, it will not
1075 This command will only affect bundles currently available, it will not
1075 affect bundles being asynchronously generated.
1076 affect bundles being asynchronously generated.
1076 """
1077 """
1077 bundles = read_auto_gen(repo)
1078 bundles = read_auto_gen(repo)
1078 delete = [b for b in bundles if b.ready]
1079 delete = [b for b in bundles if b.ready]
1079 for o in delete:
1080 for o in delete:
1080 delete_bundle(repo, o)
1081 delete_bundle(repo, o)
1081 update_bundle_list(repo, del_bundles=delete)
1082 update_bundle_list(repo, del_bundles=delete)
@@ -1,476 +1,490 b''
1
1
2 #require no-reposimplestore no-chg
2 #require no-reposimplestore no-chg
3
3
4 initial setup
4 initial setup
5
5
6 $ hg init server
6 $ hg init server
7 $ cat >> server/.hg/hgrc << EOF
7 $ cat >> server/.hg/hgrc << EOF
8 > [extensions]
8 > [extensions]
9 > clonebundles =
9 > clonebundles =
10 >
10 >
11 > [clone-bundles]
11 > [clone-bundles]
12 > auto-generate.on-change = yes
12 > auto-generate.on-change = yes
13 > auto-generate.formats = v2
13 > auto-generate.formats = v2
14 > upload-command = cp "\$HGCB_BUNDLE_PATH" "$TESTTMP"/final-upload/
14 > upload-command = cp "\$HGCB_BUNDLE_PATH" "$TESTTMP"/final-upload/
15 > delete-command = rm -f "$TESTTMP/final-upload/\$HGCB_BASENAME"
15 > delete-command = rm -f "$TESTTMP/final-upload/\$HGCB_BASENAME"
16 > url-template = file://$TESTTMP/final-upload/{basename}
16 > url-template = file://$TESTTMP/final-upload/{basename}
17 >
17 >
18 > [devel]
18 > [devel]
19 > debug.clonebundles=yes
19 > debug.clonebundles=yes
20 > EOF
20 > EOF
21
21
22 $ mkdir final-upload
22 $ mkdir final-upload
23 $ hg clone server client
23 $ hg clone server client
24 updating to branch default
24 updating to branch default
25 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 $ cd client
26 $ cd client
27
27
28 Test bundles are generated on push
28 Test bundles are generated on push
29 ==================================
29 ==================================
30
30
31 $ touch foo
31 $ touch foo
32 $ hg -q commit -A -m 'add foo'
32 $ hg -q commit -A -m 'add foo'
33 $ touch bar
33 $ touch bar
34 $ hg -q commit -A -m 'add bar'
34 $ hg -q commit -A -m 'add bar'
35 $ hg push
35 $ hg push
36 pushing to $TESTTMP/server
36 pushing to $TESTTMP/server
37 searching for changes
37 searching for changes
38 adding changesets
38 adding changesets
39 adding manifests
39 adding manifests
40 adding file changes
40 adding file changes
41 2 changesets found
41 2 changesets found
42 added 2 changesets with 2 changes to 2 files
42 added 2 changesets with 2 changes to 2 files
43 clone-bundles: starting bundle generation: v2
43 clone-bundles: starting bundle generation: bzip2-v2
44 $ cat ../server/.hg/clonebundles.manifest
44 $ cat ../server/.hg/clonebundles.manifest
45 file:/*/$TESTTMP/final-upload/full-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg BUNDLESPEC=v2 (glob)
45 file:/*/$TESTTMP/final-upload/full-bzip2-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg BUNDLESPEC=bzip2-v2 (glob)
46 $ ls -1 ../final-upload
46 $ ls -1 ../final-upload
47 full-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
47 full-bzip2-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
48 $ ls -1 ../server/.hg/tmp-bundles
48 $ ls -1 ../server/.hg/tmp-bundles
49
49
50 Newer bundles are generated with more pushes
50 Newer bundles are generated with more pushes
51 --------------------------------------------
51 --------------------------------------------
52
52
53 $ touch baz
53 $ touch baz
54 $ hg -q commit -A -m 'add baz'
54 $ hg -q commit -A -m 'add baz'
55 $ touch buz
55 $ touch buz
56 $ hg -q commit -A -m 'add buz'
56 $ hg -q commit -A -m 'add buz'
57 $ hg push
57 $ hg push
58 pushing to $TESTTMP/server
58 pushing to $TESTTMP/server
59 searching for changes
59 searching for changes
60 adding changesets
60 adding changesets
61 adding manifests
61 adding manifests
62 adding file changes
62 adding file changes
63 4 changesets found
63 4 changesets found
64 added 2 changesets with 2 changes to 2 files
64 added 2 changesets with 2 changes to 2 files
65 clone-bundles: starting bundle generation: v2
65 clone-bundles: starting bundle generation: bzip2-v2
66
66
67 $ cat ../server/.hg/clonebundles.manifest
67 $ cat ../server/.hg/clonebundles.manifest
68 file:/*/$TESTTMP/final-upload/full-v2-4_revs-6427147b985a_tip-*_txn.hg BUNDLESPEC=v2 (glob)
68 file:/*/$TESTTMP/final-upload/full-bzip2-v2-4_revs-6427147b985a_tip-*_txn.hg BUNDLESPEC=bzip2-v2 (glob)
69 $ ls -1 ../final-upload
69 $ ls -1 ../final-upload
70 full-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
70 full-bzip2-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
71 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
71 full-bzip2-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
72 $ ls -1 ../server/.hg/tmp-bundles
72 $ ls -1 ../server/.hg/tmp-bundles
73
73
74 Older bundles are cleaned up with more pushes
74 Older bundles are cleaned up with more pushes
75 ---------------------------------------------
75 ---------------------------------------------
76
76
77 $ touch faz
77 $ touch faz
78 $ hg -q commit -A -m 'add faz'
78 $ hg -q commit -A -m 'add faz'
79 $ touch fuz
79 $ touch fuz
80 $ hg -q commit -A -m 'add fuz'
80 $ hg -q commit -A -m 'add fuz'
81 $ hg push
81 $ hg push
82 pushing to $TESTTMP/server
82 pushing to $TESTTMP/server
83 searching for changes
83 searching for changes
84 adding changesets
84 adding changesets
85 adding manifests
85 adding manifests
86 adding file changes
86 adding file changes
87 clone-bundles: deleting bundle full-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
87 clone-bundles: deleting bundle full-bzip2-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
88 6 changesets found
88 6 changesets found
89 added 2 changesets with 2 changes to 2 files
89 added 2 changesets with 2 changes to 2 files
90 clone-bundles: starting bundle generation: v2
90 clone-bundles: starting bundle generation: bzip2-v2
91
91
92 $ cat ../server/.hg/clonebundles.manifest
92 $ cat ../server/.hg/clonebundles.manifest
93 file:/*/$TESTTMP/final-upload/full-v2-6_revs-b1010e95ea00_tip-*_txn.hg BUNDLESPEC=v2 (glob)
93 file:/*/$TESTTMP/final-upload/full-bzip2-v2-6_revs-b1010e95ea00_tip-*_txn.hg BUNDLESPEC=bzip2-v2 (glob)
94 $ ls -1 ../final-upload
94 $ ls -1 ../final-upload
95 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
95 full-bzip2-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
96 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
96 full-bzip2-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
97 $ ls -1 ../server/.hg/tmp-bundles
97 $ ls -1 ../server/.hg/tmp-bundles
98
98
99 Test conditions to get them generated
99 Test conditions to get them generated
100 =====================================
100 =====================================
101
101
102 Check ratio
102 Check ratio
103
103
104 $ cat >> ../server/.hg/hgrc << EOF
104 $ cat >> ../server/.hg/hgrc << EOF
105 > [clone-bundles]
105 > [clone-bundles]
106 > trigger.below-bundled-ratio = 0.5
106 > trigger.below-bundled-ratio = 0.5
107 > EOF
107 > EOF
108 $ touch far
108 $ touch far
109 $ hg -q commit -A -m 'add far'
109 $ hg -q commit -A -m 'add far'
110 $ hg push
110 $ hg push
111 pushing to $TESTTMP/server
111 pushing to $TESTTMP/server
112 searching for changes
112 searching for changes
113 adding changesets
113 adding changesets
114 adding manifests
114 adding manifests
115 adding file changes
115 adding file changes
116 added 1 changesets with 1 changes to 1 files
116 added 1 changesets with 1 changes to 1 files
117 $ cat ../server/.hg/clonebundles.manifest
117 $ cat ../server/.hg/clonebundles.manifest
118 file:/*/$TESTTMP/final-upload/full-v2-6_revs-b1010e95ea00_tip-*_txn.hg BUNDLESPEC=v2 (glob)
118 file:/*/$TESTTMP/final-upload/full-bzip2-v2-6_revs-b1010e95ea00_tip-*_txn.hg BUNDLESPEC=bzip2-v2 (glob)
119 $ ls -1 ../final-upload
119 $ ls -1 ../final-upload
120 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
120 full-bzip2-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
121 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
121 full-bzip2-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
122 $ ls -1 ../server/.hg/tmp-bundles
122 $ ls -1 ../server/.hg/tmp-bundles
123
123
124 Check absolute number of revisions
124 Check absolute number of revisions
125
125
126 $ cat >> ../server/.hg/hgrc << EOF
126 $ cat >> ../server/.hg/hgrc << EOF
127 > [clone-bundles]
127 > [clone-bundles]
128 > trigger.revs = 2
128 > trigger.revs = 2
129 > EOF
129 > EOF
130 $ touch bur
130 $ touch bur
131 $ hg -q commit -A -m 'add bur'
131 $ hg -q commit -A -m 'add bur'
132 $ hg push
132 $ hg push
133 pushing to $TESTTMP/server
133 pushing to $TESTTMP/server
134 searching for changes
134 searching for changes
135 adding changesets
135 adding changesets
136 adding manifests
136 adding manifests
137 adding file changes
137 adding file changes
138 clone-bundles: deleting bundle full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
138 clone-bundles: deleting bundle full-bzip2-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
139 8 changesets found
139 8 changesets found
140 added 1 changesets with 1 changes to 1 files
140 added 1 changesets with 1 changes to 1 files
141 clone-bundles: starting bundle generation: v2
141 clone-bundles: starting bundle generation: bzip2-v2
142 $ cat ../server/.hg/clonebundles.manifest
142 $ cat ../server/.hg/clonebundles.manifest
143 file:/*/$TESTTMP/final-upload/full-v2-8_revs-8353e8af1306_tip-*_txn.hg BUNDLESPEC=v2 (glob)
143 file:/*/$TESTTMP/final-upload/full-bzip2-v2-8_revs-8353e8af1306_tip-*_txn.hg BUNDLESPEC=bzip2-v2 (glob)
144 $ ls -1 ../final-upload
144 $ ls -1 ../final-upload
145 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
145 full-bzip2-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
146 full-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
146 full-bzip2-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
147 $ ls -1 ../server/.hg/tmp-bundles
147 $ ls -1 ../server/.hg/tmp-bundles
148
148
149 (that one would not generate new bundles)
149 (that one would not generate new bundles)
150
150
151 $ touch tur
151 $ touch tur
152 $ hg -q commit -A -m 'add tur'
152 $ hg -q commit -A -m 'add tur'
153 $ hg push
153 $ hg push
154 pushing to $TESTTMP/server
154 pushing to $TESTTMP/server
155 searching for changes
155 searching for changes
156 adding changesets
156 adding changesets
157 adding manifests
157 adding manifests
158 adding file changes
158 adding file changes
159 added 1 changesets with 1 changes to 1 files
159 added 1 changesets with 1 changes to 1 files
160 $ cat ../server/.hg/clonebundles.manifest
160 $ cat ../server/.hg/clonebundles.manifest
161 file:/*/$TESTTMP/final-upload/full-v2-8_revs-8353e8af1306_tip-*_txn.hg BUNDLESPEC=v2 (glob)
161 file:/*/$TESTTMP/final-upload/full-bzip2-v2-8_revs-8353e8af1306_tip-*_txn.hg BUNDLESPEC=bzip2-v2 (glob)
162 $ ls -1 ../final-upload
162 $ ls -1 ../final-upload
163 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
163 full-bzip2-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
164 full-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
164 full-bzip2-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
165 $ ls -1 ../server/.hg/tmp-bundles
165 $ ls -1 ../server/.hg/tmp-bundles
166
166
167 Test generation through the dedicated command
167 Test generation through the dedicated command
168 =============================================
168 =============================================
169
169
170 $ cat >> ../server/.hg/hgrc << EOF
170 $ cat >> ../server/.hg/hgrc << EOF
171 > [clone-bundles]
171 > [clone-bundles]
172 > auto-generate.on-change = no
172 > auto-generate.on-change = no
173 > EOF
173 > EOF
174
174
175 Check the command can generate content when needed
175 Check the command can generate content when needed
176 --------------------------------------------------
176 --------------------------------------------------
177
177
178 Do a push that makes the condition fulfilled,
178 Do a push that makes the condition fulfilled,
179 Yet it should not automatically generate a bundle with
179 Yet it should not automatically generate a bundle with
180 "auto-generate.on-change" not set.
180 "auto-generate.on-change" not set.
181
181
182 $ touch quoi
182 $ touch quoi
183 $ hg -q commit -A -m 'add quoi'
183 $ hg -q commit -A -m 'add quoi'
184
184
185 $ pre_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
185 $ pre_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
186 $ pre_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
186 $ pre_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
187 $ ls -1 ../server/.hg/tmp-bundles
187 $ ls -1 ../server/.hg/tmp-bundles
188
188
189 $ hg push
189 $ hg push
190 pushing to $TESTTMP/server
190 pushing to $TESTTMP/server
191 searching for changes
191 searching for changes
192 adding changesets
192 adding changesets
193 adding manifests
193 adding manifests
194 adding file changes
194 adding file changes
195 added 1 changesets with 1 changes to 1 files
195 added 1 changesets with 1 changes to 1 files
196
196
197 $ post_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
197 $ post_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
198 $ post_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
198 $ post_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
199 $ ls -1 ../server/.hg/tmp-bundles
199 $ ls -1 ../server/.hg/tmp-bundles
200 $ test "$pre_push_manifest" = "$post_push_manifest"
200 $ test "$pre_push_manifest" = "$post_push_manifest"
201 $ test "$pre_push_upload" = "$post_push_upload"
201 $ test "$pre_push_upload" = "$post_push_upload"
202
202
203 Running the command should detect the stale bundles, and do the full automatic
203 Running the command should detect the stale bundles, and do the full automatic
204 generation logic.
204 generation logic.
205
205
206 $ hg -R ../server/ admin::clone-bundles-refresh
206 $ hg -R ../server/ admin::clone-bundles-refresh
207 clone-bundles: deleting bundle full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
207 clone-bundles: deleting bundle full-bzip2-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
208 clone-bundles: starting bundle generation: v2
208 clone-bundles: starting bundle generation: bzip2-v2
209 10 changesets found
209 10 changesets found
210 $ cat ../server/.hg/clonebundles.manifest
210 $ cat ../server/.hg/clonebundles.manifest
211 file:/*/$TESTTMP/final-upload/full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
211 file:/*/$TESTTMP/final-upload/full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
212 $ ls -1 ../final-upload
212 $ ls -1 ../final-upload
213 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
213 full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
214 full-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
214 full-bzip2-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
215 $ ls -1 ../server/.hg/tmp-bundles
215 $ ls -1 ../server/.hg/tmp-bundles
216
216
217 Check the command cleans up older bundles when possible
217 Check the command cleans up older bundles when possible
218 -------------------------------------------------------
218 -------------------------------------------------------
219
219
220 $ hg -R ../server/ admin::clone-bundles-refresh
220 $ hg -R ../server/ admin::clone-bundles-refresh
221 clone-bundles: deleting bundle full-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
221 clone-bundles: deleting bundle full-bzip2-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
222 $ cat ../server/.hg/clonebundles.manifest
222 $ cat ../server/.hg/clonebundles.manifest
223 file:/*/$TESTTMP/final-upload/full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
223 file:/*/$TESTTMP/final-upload/full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
224 $ ls -1 ../final-upload
224 $ ls -1 ../final-upload
225 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
225 full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
226 $ ls -1 ../server/.hg/tmp-bundles
226 $ ls -1 ../server/.hg/tmp-bundles
227
227
228 Nothing is generated when the bundles are sufficiently up to date
228 Nothing is generated when the bundles are sufficiently up to date
229 -----------------------------------------------------------------
229 -----------------------------------------------------------------
230
230
231 $ touch feur
231 $ touch feur
232 $ hg -q commit -A -m 'add feur'
232 $ hg -q commit -A -m 'add feur'
233
233
234 $ pre_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
234 $ pre_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
235 $ pre_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
235 $ pre_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
236 $ ls -1 ../server/.hg/tmp-bundles
236 $ ls -1 ../server/.hg/tmp-bundles
237
237
238 $ hg push
238 $ hg push
239 pushing to $TESTTMP/server
239 pushing to $TESTTMP/server
240 searching for changes
240 searching for changes
241 adding changesets
241 adding changesets
242 adding manifests
242 adding manifests
243 adding file changes
243 adding file changes
244 added 1 changesets with 1 changes to 1 files
244 added 1 changesets with 1 changes to 1 files
245
245
246 $ post_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
246 $ post_push_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
247 $ post_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
247 $ post_push_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
248 $ ls -1 ../server/.hg/tmp-bundles
248 $ ls -1 ../server/.hg/tmp-bundles
249 $ test "$pre_push_manifest" = "$post_push_manifest"
249 $ test "$pre_push_manifest" = "$post_push_manifest"
250 $ test "$pre_push_upload" = "$post_push_upload"
250 $ test "$pre_push_upload" = "$post_push_upload"
251
251
252 $ hg -R ../server/ admin::clone-bundles-refresh
252 $ hg -R ../server/ admin::clone-bundles-refresh
253
253
254 $ post_refresh_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
254 $ post_refresh_manifest=`cat ../server/.hg/clonebundles.manifest|f --sha256 | sed 's/.*=//' | cat`
255 $ post_refresh_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
255 $ post_refresh_upload=`ls -1 ../final-upload|f --sha256 | sed 's/.*=//' | cat`
256 $ ls -1 ../server/.hg/tmp-bundles
256 $ ls -1 ../server/.hg/tmp-bundles
257 $ test "$pre_push_manifest" = "$post_refresh_manifest"
257 $ test "$pre_push_manifest" = "$post_refresh_manifest"
258 $ test "$pre_push_upload" = "$post_refresh_upload"
258 $ test "$pre_push_upload" = "$post_refresh_upload"
259
259
260 Test modification of configuration
260 Test modification of configuration
261 ==================================
261 ==================================
262
262
263 Testing that later runs adapt to configuration changes even if the repository is
263 Testing that later runs adapt to configuration changes even if the repository is
264 unchanged.
264 unchanged.
265
265
266 adding more formats
266 adding more formats
267 -------------------
267 -------------------
268
268
269 bundle for added formats should be generated
269 bundle for added formats should be generated
270
270
271 change configuration
271 change configuration
272
272
273 $ cat >> ../server/.hg/hgrc << EOF
273 $ cat >> ../server/.hg/hgrc << EOF
274 > [clone-bundles]
274 > [clone-bundles]
275 > auto-generate.formats = v1, v2
275 > auto-generate.formats = v1, v2
276 > EOF
276 > EOF
277
277
278 refresh the bundles
278 refresh the bundles
279
279
280 $ hg -R ../server/ admin::clone-bundles-refresh
280 $ hg -R ../server/ admin::clone-bundles-refresh
281 clone-bundles: starting bundle generation: v1
281 clone-bundles: starting bundle generation: bzip2-v1
282 11 changesets found
282 11 changesets found
283
283
284 the bundle for the "new" format should have been added
284 the bundle for the "new" format should have been added
285
285
286 $ cat ../server/.hg/clonebundles.manifest
286 $ cat ../server/.hg/clonebundles.manifest
287 file:/*/$TESTTMP/final-upload/full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
287 file:/*/$TESTTMP/final-upload/full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 (glob)
288 file:/*/$TESTTMP/final-upload/full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
288 file:/*/$TESTTMP/final-upload/full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
289 $ ls -1 ../final-upload
289 $ ls -1 ../final-upload
290 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
290 full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
291 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
291 full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
292 $ ls -1 ../server/.hg/tmp-bundles
292 $ ls -1 ../server/.hg/tmp-bundles
293
293
294 Changing the ratio
294 Changing the ratio
295 ------------------
295 ------------------
296
296
297 Changing the ratio to something that would have triggered a bundle during the last push.
297 Changing the ratio to something that would have triggered a bundle during the last push.
298
298
299 $ cat >> ../server/.hg/hgrc << EOF
299 $ cat >> ../server/.hg/hgrc << EOF
300 > [clone-bundles]
300 > [clone-bundles]
301 > trigger.below-bundled-ratio = 0.95
301 > trigger.below-bundled-ratio = 0.95
302 > EOF
302 > EOF
303
303
304 refresh the bundles
304 refresh the bundles
305
305
306 $ hg -R ../server/ admin::clone-bundles-refresh
306 $ hg -R ../server/ admin::clone-bundles-refresh
307 clone-bundles: starting bundle generation: v2
307 clone-bundles: starting bundle generation: bzip2-v2
308 11 changesets found
308 11 changesets found
309
309
310
310
311 the "outdated' bundle should be refreshed
311 the "outdated' bundle should be refreshed
312
312
313 $ cat ../server/.hg/clonebundles.manifest
313 $ cat ../server/.hg/clonebundles.manifest
314 file:/*/$TESTTMP/final-upload/full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
314 file:/*/$TESTTMP/final-upload/full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 (glob)
315 file:/*/$TESTTMP/final-upload/full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
315 file:/*/$TESTTMP/final-upload/full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
316 $ ls -1 ../final-upload
316 $ ls -1 ../final-upload
317 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
317 full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
318 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
318 full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
319 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
319 full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
320 $ ls -1 ../server/.hg/tmp-bundles
320 $ ls -1 ../server/.hg/tmp-bundles
321
321
322 Test more command options
322 Test more command options
323 =========================
323 =========================
324
324
325 bundle clearing
325 bundle clearing
326 ---------------
326 ---------------
327
327
328 $ hg -R ../server/ admin::clone-bundles-clear
328 $ hg -R ../server/ admin::clone-bundles-clear
329 clone-bundles: deleting bundle full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
329 clone-bundles: deleting bundle full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
330 clone-bundles: deleting bundle full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
330 clone-bundles: deleting bundle full-bzip2-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
331 clone-bundles: deleting bundle full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
331 clone-bundles: deleting bundle full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
332
332
333 Nothing should remain
333 Nothing should remain
334
334
335 $ cat ../server/.hg/clonebundles.manifest
335 $ cat ../server/.hg/clonebundles.manifest
336 $ ls -1 ../final-upload
336 $ ls -1 ../final-upload
337 $ ls -1 ../server/.hg/tmp-bundles
337 $ ls -1 ../server/.hg/tmp-bundles
338
338
339 background generation
339 background generation
340 ---------------------
340 ---------------------
341
341
342 generate bundle using background subprocess
342 generate bundle using background subprocess
343 (since we are in devel mode, the command will still wait for the background
343 (since we are in devel mode, the command will still wait for the background
344 process to end)
344 process to end)
345
345
346 $ hg -R ../server/ admin::clone-bundles-refresh --background
346 $ hg -R ../server/ admin::clone-bundles-refresh --background
347 11 changesets found
347 11 changesets found
348 11 changesets found
348 11 changesets found
349 clone-bundles: starting bundle generation: v1
349 clone-bundles: starting bundle generation: bzip2-v1
350 clone-bundles: starting bundle generation: v2
350 clone-bundles: starting bundle generation: bzip2-v2
351
351
352 bundles should have been generated
352 bundles should have been generated
353
353
354 $ cat ../server/.hg/clonebundles.manifest
354 $ cat ../server/.hg/clonebundles.manifest
355 file:/*/$TESTTMP/final-upload/full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
355 file:/*/$TESTTMP/final-upload/full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 (glob)
356 file:/*/$TESTTMP/final-upload/full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
356 file:/*/$TESTTMP/final-upload/full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
357 $ ls -1 ../final-upload
357 $ ls -1 ../final-upload
358 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
358 full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
359 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
359 full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
360 $ ls -1 ../server/.hg/tmp-bundles
360 $ ls -1 ../server/.hg/tmp-bundles
361
361
362 Test HTTP URL
362 Test HTTP URL
363 =========================
363 =========================
364
364
365 $ hg -R ../server/ admin::clone-bundles-clear
365 $ hg -R ../server/ admin::clone-bundles-clear
366 clone-bundles: deleting bundle full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
366 clone-bundles: deleting bundle full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
367 clone-bundles: deleting bundle full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
367 clone-bundles: deleting bundle full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
368
368
369 $ cat >> ../server/.hg/hgrc << EOF
369 $ cat >> ../server/.hg/hgrc << EOF
370 > [clone-bundles]
370 > [clone-bundles]
371 > url-template = https://example.com/final-upload/{basename}
371 > url-template = https://example.com/final-upload/{basename}
372 > EOF
372 > EOF
373 $ hg -R ../server/ admin::clone-bundles-refresh
373 $ hg -R ../server/ admin::clone-bundles-refresh
374 clone-bundles: starting bundle generation: v1
374 clone-bundles: starting bundle generation: bzip2-v1
375 11 changesets found
375 11 changesets found
376 clone-bundles: starting bundle generation: v2
376 clone-bundles: starting bundle generation: bzip2-v2
377 11 changesets found
377 11 changesets found
378
378
379
379
380 bundles should have been generated with the SNIREQUIRED option
380 bundles should have been generated with the SNIREQUIRED option
381
381
382 $ cat ../server/.hg/clonebundles.manifest
382 $ cat ../server/.hg/clonebundles.manifest
383 https://example.com/final-upload/full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 REQUIRESNI=true (glob)
383 https://example.com/final-upload/full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 REQUIRESNI=true (glob)
384 https://example.com/final-upload/full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 REQUIRESNI=true (glob)
384 https://example.com/final-upload/full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 REQUIRESNI=true (glob)
385
385
386 Test serving them through inline-clone bundle
386 Test serving them through inline-clone bundle
387 =============================================
387 =============================================
388
388
389 $ cat >> ../server/.hg/hgrc << EOF
389 $ cat >> ../server/.hg/hgrc << EOF
390 > [clone-bundles]
390 > [clone-bundles]
391 > auto-generate.serve-inline=yes
391 > auto-generate.serve-inline=yes
392 > EOF
392 > EOF
393 $ hg -R ../server/ admin::clone-bundles-clear
393 $ hg -R ../server/ admin::clone-bundles-clear
394 clone-bundles: deleting bundle full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
394 clone-bundles: deleting bundle full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
395 clone-bundles: deleting bundle full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
395 clone-bundles: deleting bundle full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
396
396
397 initial generation
397 initial generation
398 ------------------
398 ------------------
399
399
400
400
401 $ hg -R ../server/ admin::clone-bundles-refresh
401 $ hg -R ../server/ admin::clone-bundles-refresh
402 clone-bundles: starting bundle generation: v1
402 clone-bundles: starting bundle generation: bzip2-v1
403 11 changesets found
403 11 changesets found
404 clone-bundles: starting bundle generation: v2
404 clone-bundles: starting bundle generation: bzip2-v2
405 11 changesets found
405 11 changesets found
406 $ cat ../server/.hg/clonebundles.manifest
406 $ cat ../server/.hg/clonebundles.manifest
407 peer-bundle-cache://full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
407 peer-bundle-cache://full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 (glob)
408 peer-bundle-cache://full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
408 peer-bundle-cache://full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
409 $ ls -1 ../server/.hg/bundle-cache
409 $ ls -1 ../server/.hg/bundle-cache
410 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
410 full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
411 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
411 full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
412 $ ls -1 ../final-upload
412 $ ls -1 ../final-upload
413
413
414 Regeneration eventually cleanup the old ones
414 Regeneration eventually cleanup the old ones
415 --------------------------------------------
415 --------------------------------------------
416
416
417 create more content
417 create more content
418 $ touch voit
418 $ touch voit
419 $ hg -q commit -A -m 'add voit'
419 $ hg -q commit -A -m 'add voit'
420 $ touch ar
420 $ touch ar
421 $ hg -q commit -A -m 'add ar'
421 $ hg -q commit -A -m 'add ar'
422 $ hg push
422 $ hg push
423 pushing to $TESTTMP/server
423 pushing to $TESTTMP/server
424 searching for changes
424 searching for changes
425 adding changesets
425 adding changesets
426 adding manifests
426 adding manifests
427 adding file changes
427 adding file changes
428 added 2 changesets with 2 changes to 2 files
428 added 2 changesets with 2 changes to 2 files
429
429
430 check first regeneration
430 check first regeneration
431
431
432 $ hg -R ../server/ admin::clone-bundles-refresh
432 $ hg -R ../server/ admin::clone-bundles-refresh
433 clone-bundles: starting bundle generation: v1
433 clone-bundles: starting bundle generation: bzip2-v1
434 13 changesets found
434 13 changesets found
435 clone-bundles: starting bundle generation: v2
435 clone-bundles: starting bundle generation: bzip2-v2
436 13 changesets found
436 13 changesets found
437 $ cat ../server/.hg/clonebundles.manifest
437 $ cat ../server/.hg/clonebundles.manifest
438 peer-bundle-cache://full-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
438 peer-bundle-cache://full-bzip2-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 (glob)
439 peer-bundle-cache://full-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
439 peer-bundle-cache://full-bzip2-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
440 $ ls -1 ../server/.hg/bundle-cache
440 $ ls -1 ../server/.hg/bundle-cache
441 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
441 full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
442 full-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
442 full-bzip2-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
443 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
443 full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
444 full-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
444 full-bzip2-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
445 $ ls -1 ../final-upload
445 $ ls -1 ../final-upload
446
446
447 check first regeneration (should cleanup the one before that last)
447 check first regeneration (should cleanup the one before that last)
448
448
449 $ touch "investi"
449 $ touch "investi"
450 $ hg -q commit -A -m 'add investi'
450 $ hg -q commit -A -m 'add investi'
451 $ touch "lesgisla"
451 $ touch "lesgisla"
452 $ hg -q commit -A -m 'add lesgisla'
452 $ hg -q commit -A -m 'add lesgisla'
453 $ hg push
453 $ hg push
454 pushing to $TESTTMP/server
454 pushing to $TESTTMP/server
455 searching for changes
455 searching for changes
456 adding changesets
456 adding changesets
457 adding manifests
457 adding manifests
458 adding file changes
458 adding file changes
459 added 2 changesets with 2 changes to 2 files
459 added 2 changesets with 2 changes to 2 files
460
460
461 $ hg -R ../server/ admin::clone-bundles-refresh
461 $ hg -R ../server/ admin::clone-bundles-refresh
462 clone-bundles: deleting inline bundle full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
462 clone-bundles: deleting inline bundle full-bzip2-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
463 clone-bundles: deleting inline bundle full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
463 clone-bundles: deleting inline bundle full-bzip2-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
464 clone-bundles: starting bundle generation: v1
464 clone-bundles: starting bundle generation: bzip2-v1
465 15 changesets found
465 15 changesets found
466 clone-bundles: starting bundle generation: v2
466 clone-bundles: starting bundle generation: bzip2-v2
467 15 changesets found
467 15 changesets found
468 $ cat ../server/.hg/clonebundles.manifest
468 $ cat ../server/.hg/clonebundles.manifest
469 peer-bundle-cache://full-v1-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
469 peer-bundle-cache://full-bzip2-v1-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 (glob)
470 peer-bundle-cache://full-v2-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
470 peer-bundle-cache://full-bzip2-v2-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
471 $ ls -1 ../server/.hg/bundle-cache
471 $ ls -1 ../server/.hg/bundle-cache
472 full-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
472 full-bzip2-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
473 full-v1-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
473 full-bzip2-v1-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
474 full-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
474 full-bzip2-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
475 full-v2-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
475 full-bzip2-v2-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
476 $ ls -1 ../final-upload
476 $ ls -1 ../final-upload
477
478 Check the url is correct
479 ------------------------
480
481 $ hg clone -U ssh://user@dummy/server ssh-inline-clone
482 applying clone bundle from peer-bundle-cache://full-bzip2-v1-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
483 adding changesets
484 adding manifests
485 adding file changes
486 added 15 changesets with 15 changes to 15 files
487 finished applying clone bundle
488 searching for changes
489 no changes found
490 15 local changesets published
General Comments 0
You need to be logged in to leave comments. Login now