##// END OF EJS Templates
clonebundles: adds a auto-generate.serve-inline option...
marmoute -
r51604:40638610 default
parent child Browse files
Show More
@@ -1,1039 +1,1081 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
258 ........................
259
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
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
264 clonebundles` section above for details about inline clonebundles
265
266 To automatically serve generated bundle through inline clonebundle, simply set
267 the following option::
268
269 auto-generate.serve-inline=yes
270
271 Enabling this option disable the managed upload and serving explained below.
272
257 Bundles Upload and Serving:
273 Bundles Upload and Serving:
258 ...........................
274 ...........................
259
275
276 This is the most efficient way to serve automatically generated clone bundles,
277 but requires some setup.
278
260 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.
261 This should be donne through `clone-bundles.upload-command` configuration. The
280 This should be donne through `clone-bundles.upload-command` configuration. The
262 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
263 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
264 basename in the "public" URL is accessible at::
283 basename in the "public" URL is accessible at::
265
284
266 [clone-bundles]
285 [clone-bundles]
267 upload-command=sftp put $HGCB_BUNDLE_PATH \
286 upload-command=sftp put $HGCB_BUNDLE_PATH \
268 sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME
287 sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME
269
288
270 If the file was already uploaded, the command must still succeed.
289 If the file was already uploaded, the command must still succeed.
271
290
272 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
273 `clone-bundles.url-template`.
292 `clone-bundles.url-template`.
274
293
275 [clone-bundles]
294 [clone-bundles]
276 url-template=https://bundles.host/cache/clone-bundles/{basename}
295 url-template=https://bundles.host/cache/clone-bundles/{basename}
277
296
278 Old bundles cleanup:
297 Old bundles cleanup:
279 ....................
298 ....................
280
299
281 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
282 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`
283 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
284 the `$HGCB_BUNDLE_URL` environment variable.
303 the `$HGCB_BUNDLE_URL` environment variable.
285
304
286 [clone-bundles]
305 [clone-bundles]
287 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
288
307
289 If the file was already deleted, the command must still succeed.
308 If the file was already deleted, the command must still succeed.
290 """
309 """
291
310
292
311
293 import os
312 import os
294 import weakref
313 import weakref
295
314
296 from mercurial.i18n import _
315 from mercurial.i18n import _
297
316
298 from mercurial import (
317 from mercurial import (
299 bundlecaches,
318 bundlecaches,
300 commands,
319 commands,
301 error,
320 error,
302 extensions,
321 extensions,
303 localrepo,
322 localrepo,
304 lock,
323 lock,
305 node,
324 node,
306 registrar,
325 registrar,
307 util,
326 util,
308 wireprotov1server,
327 wireprotov1server,
309 )
328 )
310
329
311
330
312 from mercurial.utils import (
331 from mercurial.utils import (
313 procutil,
332 procutil,
314 )
333 )
315
334
316 testedwith = b'ships-with-hg-core'
335 testedwith = b'ships-with-hg-core'
317
336
318
337
319 def capabilities(orig, repo, proto):
338 def capabilities(orig, repo, proto):
320 caps = orig(repo, proto)
339 caps = orig(repo, proto)
321
340
322 # 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.
323 # 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
324 # missing file.
343 # missing file.
325 if repo.vfs.exists(bundlecaches.CB_MANIFEST_FILE):
344 if repo.vfs.exists(bundlecaches.CB_MANIFEST_FILE):
326 caps.append(b'clonebundles')
345 caps.append(b'clonebundles')
327 caps.append(b'clonebundles_manifest')
346 caps.append(b'clonebundles_manifest')
328
347
329 return caps
348 return caps
330
349
331
350
332 def extsetup(ui):
351 def extsetup(ui):
333 extensions.wrapfunction(wireprotov1server, b'_capabilities', capabilities)
352 extensions.wrapfunction(wireprotov1server, b'_capabilities', capabilities)
334
353
335
354
336 # logic for bundle auto-generation
355 # logic for bundle auto-generation
337
356
338
357
339 configtable = {}
358 configtable = {}
340 configitem = registrar.configitem(configtable)
359 configitem = registrar.configitem(configtable)
341
360
342 cmdtable = {}
361 cmdtable = {}
343 command = registrar.command(cmdtable)
362 command = registrar.command(cmdtable)
344
363
345 configitem(b'clone-bundles', b'auto-generate.on-change', default=False)
364 configitem(b'clone-bundles', b'auto-generate.on-change', default=False)
346 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)
347 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)
348 configitem(b'clone-bundles', b'trigger.revs', default=1000)
368 configitem(b'clone-bundles', b'trigger.revs', default=1000)
349
369
350 configitem(b'clone-bundles', b'upload-command', default=None)
370 configitem(b'clone-bundles', b'upload-command', default=None)
351
371
352 configitem(b'clone-bundles', b'delete-command', default=None)
372 configitem(b'clone-bundles', b'delete-command', default=None)
353
373
354 configitem(b'clone-bundles', b'url-template', default=None)
374 configitem(b'clone-bundles', b'url-template', default=None)
355
375
356 configitem(b'devel', b'debug.clonebundles', default=False)
376 configitem(b'devel', b'debug.clonebundles', default=False)
357
377
358
378
359 # category for the post-close transaction hooks
379 # category for the post-close transaction hooks
360 CAT_POSTCLOSE = b"clonebundles-autobundles"
380 CAT_POSTCLOSE = b"clonebundles-autobundles"
361
381
362 # template for bundle file names
382 # template for bundle file names
363 BUNDLE_MASK = (
383 BUNDLE_MASK = (
364 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"
365 )
385 )
366
386
367
387
368 # file in .hg/ use to track clonebundles being auto-generated
388 # file in .hg/ use to track clonebundles being auto-generated
369 AUTO_GEN_FILE = b'clonebundles.auto-gen'
389 AUTO_GEN_FILE = b'clonebundles.auto-gen'
370
390
371
391
372 class BundleBase(object):
392 class BundleBase(object):
373 """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
374
394
375 :bundle_type: the bundlespec (see hg help bundlespec)
395 :bundle_type: the bundlespec (see hg help bundlespec)
376 :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
377 :tip_rev: the rev-num of the tip revision
397 :tip_rev: the rev-num of the tip revision
378 :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
379
399
380 :ready: True if the bundle is ready to be served
400 :ready: True if the bundle is ready to be served
381 """
401 """
382
402
383 ready = False
403 ready = False
384
404
385 def __init__(self, bundle_type, revs, tip_rev, tip_node):
405 def __init__(self, bundle_type, revs, tip_rev, tip_node):
386 self.bundle_type = bundle_type
406 self.bundle_type = bundle_type
387 self.revs = revs
407 self.revs = revs
388 self.tip_rev = tip_rev
408 self.tip_rev = tip_rev
389 self.tip_node = tip_node
409 self.tip_node = tip_node
390
410
391 def valid_for(self, repo):
411 def valid_for(self, repo):
392 """is this bundle applicable to the current repository
412 """is this bundle applicable to the current repository
393
413
394 This is useful for detecting bundles made irrelevant by stripping.
414 This is useful for detecting bundles made irrelevant by stripping.
395 """
415 """
396 tip_node = node.bin(self.tip_node)
416 tip_node = node.bin(self.tip_node)
397 return repo.changelog.index.get_rev(tip_node) == self.tip_rev
417 return repo.changelog.index.get_rev(tip_node) == self.tip_rev
398
418
399 def __eq__(self, other):
419 def __eq__(self, other):
400 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)
401 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)
402 return left == right
422 return left == right
403
423
404 def __neq__(self, other):
424 def __neq__(self, other):
405 return not self == other
425 return not self == other
406
426
407 def __cmp__(self, other):
427 def __cmp__(self, other):
408 if self == other:
428 if self == other:
409 return 0
429 return 0
410 return -1
430 return -1
411
431
412
432
413 class RequestedBundle(BundleBase):
433 class RequestedBundle(BundleBase):
414 """A bundle that should be generated.
434 """A bundle that should be generated.
415
435
416 Additional attributes compared to BundleBase
436 Additional attributes compared to BundleBase
417 :heads: list of head revisions (as rev-num)
437 :heads: list of head revisions (as rev-num)
418 :op_id: a "unique" identifier for the operation triggering the change
438 :op_id: a "unique" identifier for the operation triggering the change
419 """
439 """
420
440
421 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):
422 self.head_revs = head_revs
442 self.head_revs = head_revs
423 self.op_id = op_id
443 self.op_id = op_id
424 super(RequestedBundle, self).__init__(
444 super(RequestedBundle, self).__init__(
425 bundle_type,
445 bundle_type,
426 revs,
446 revs,
427 tip_rev,
447 tip_rev,
428 tip_node,
448 tip_node,
429 )
449 )
430
450
431 @property
451 @property
432 def suggested_filename(self):
452 def suggested_filename(self):
433 """A filename that can be used for the generated bundle"""
453 """A filename that can be used for the generated bundle"""
434 data = {
454 data = {
435 b'bundle_type': self.bundle_type,
455 b'bundle_type': self.bundle_type,
436 b'revs': self.revs,
456 b'revs': self.revs,
437 b'heads': self.head_revs,
457 b'heads': self.head_revs,
438 b'tip_rev': self.tip_rev,
458 b'tip_rev': self.tip_rev,
439 b'tip_node': self.tip_node,
459 b'tip_node': self.tip_node,
440 b'tip_short': self.tip_node[:12],
460 b'tip_short': self.tip_node[:12],
441 b'op_id': self.op_id,
461 b'op_id': self.op_id,
442 }
462 }
443 return BUNDLE_MASK % data
463 return BUNDLE_MASK % data
444
464
445 def generate_bundle(self, repo, file_path):
465 def generate_bundle(self, repo, file_path):
446 """generate the bundle at `filepath`"""
466 """generate the bundle at `filepath`"""
447 commands.bundle(
467 commands.bundle(
448 repo.ui,
468 repo.ui,
449 repo,
469 repo,
450 file_path,
470 file_path,
451 base=[b"null"],
471 base=[b"null"],
452 rev=self.head_revs,
472 rev=self.head_revs,
453 type=self.bundle_type,
473 type=self.bundle_type,
454 quiet=True,
474 quiet=True,
455 )
475 )
456
476
457 def generating(self, file_path, hostname=None, pid=None):
477 def generating(self, file_path, hostname=None, pid=None):
458 """return a GeneratingBundle object from this object"""
478 """return a GeneratingBundle object from this object"""
459 if pid is None:
479 if pid is None:
460 pid = os.getpid()
480 pid = os.getpid()
461 if hostname is None:
481 if hostname is None:
462 hostname = lock._getlockprefix()
482 hostname = lock._getlockprefix()
463 return GeneratingBundle(
483 return GeneratingBundle(
464 self.bundle_type,
484 self.bundle_type,
465 self.revs,
485 self.revs,
466 self.tip_rev,
486 self.tip_rev,
467 self.tip_node,
487 self.tip_node,
468 hostname,
488 hostname,
469 pid,
489 pid,
470 file_path,
490 file_path,
471 )
491 )
472
492
473
493
474 class GeneratingBundle(BundleBase):
494 class GeneratingBundle(BundleBase):
475 """A bundle being generated
495 """A bundle being generated
476
496
477 extra attributes compared to BundleBase:
497 extra attributes compared to BundleBase:
478
498
479 :hostname: the hostname of the machine generating the bundle
499 :hostname: the hostname of the machine generating the bundle
480 :pid: the pid of the process generating the bundle
500 :pid: the pid of the process generating the bundle
481 :filepath: the target filename of the bundle
501 :filepath: the target filename of the bundle
482
502
483 These attributes exist to help detect stalled generation processes.
503 These attributes exist to help detect stalled generation processes.
484 """
504 """
485
505
486 ready = False
506 ready = False
487
507
488 def __init__(
508 def __init__(
489 self, bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
509 self, bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
490 ):
510 ):
491 self.hostname = hostname
511 self.hostname = hostname
492 self.pid = pid
512 self.pid = pid
493 self.filepath = filepath
513 self.filepath = filepath
494 super(GeneratingBundle, self).__init__(
514 super(GeneratingBundle, self).__init__(
495 bundle_type, revs, tip_rev, tip_node
515 bundle_type, revs, tip_rev, tip_node
496 )
516 )
497
517
498 @classmethod
518 @classmethod
499 def from_line(cls, line):
519 def from_line(cls, line):
500 """create an object by deserializing a line from AUTO_GEN_FILE"""
520 """create an object by deserializing a line from AUTO_GEN_FILE"""
501 assert line.startswith(b'PENDING-v1 ')
521 assert line.startswith(b'PENDING-v1 ')
502 (
522 (
503 __,
523 __,
504 bundle_type,
524 bundle_type,
505 revs,
525 revs,
506 tip_rev,
526 tip_rev,
507 tip_node,
527 tip_node,
508 hostname,
528 hostname,
509 pid,
529 pid,
510 filepath,
530 filepath,
511 ) = line.split()
531 ) = line.split()
512 hostname = util.urlreq.unquote(hostname)
532 hostname = util.urlreq.unquote(hostname)
513 filepath = util.urlreq.unquote(filepath)
533 filepath = util.urlreq.unquote(filepath)
514 revs = int(revs)
534 revs = int(revs)
515 tip_rev = int(tip_rev)
535 tip_rev = int(tip_rev)
516 pid = int(pid)
536 pid = int(pid)
517 return cls(
537 return cls(
518 bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
538 bundle_type, revs, tip_rev, tip_node, hostname, pid, filepath
519 )
539 )
520
540
521 def to_line(self):
541 def to_line(self):
522 """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"""
523 templ = b"PENDING-v1 %s %d %d %s %s %d %s"
543 templ = b"PENDING-v1 %s %d %d %s %s %d %s"
524 data = (
544 data = (
525 self.bundle_type,
545 self.bundle_type,
526 self.revs,
546 self.revs,
527 self.tip_rev,
547 self.tip_rev,
528 self.tip_node,
548 self.tip_node,
529 util.urlreq.quote(self.hostname),
549 util.urlreq.quote(self.hostname),
530 self.pid,
550 self.pid,
531 util.urlreq.quote(self.filepath),
551 util.urlreq.quote(self.filepath),
532 )
552 )
533 return templ % data
553 return templ % data
534
554
535 def __eq__(self, other):
555 def __eq__(self, other):
536 if not super(GeneratingBundle, self).__eq__(other):
556 if not super(GeneratingBundle, self).__eq__(other):
537 return False
557 return False
538 left = (self.hostname, self.pid, self.filepath)
558 left = (self.hostname, self.pid, self.filepath)
539 right = (other.hostname, other.pid, other.filepath)
559 right = (other.hostname, other.pid, other.filepath)
540 return left == right
560 return left == right
541
561
542 def uploaded(self, url, basename):
562 def uploaded(self, url, basename):
543 """return a GeneratedBundle from this object"""
563 """return a GeneratedBundle from this object"""
544 return GeneratedBundle(
564 return GeneratedBundle(
545 self.bundle_type,
565 self.bundle_type,
546 self.revs,
566 self.revs,
547 self.tip_rev,
567 self.tip_rev,
548 self.tip_node,
568 self.tip_node,
549 url,
569 url,
550 basename,
570 basename,
551 )
571 )
552
572
553
573
554 class GeneratedBundle(BundleBase):
574 class GeneratedBundle(BundleBase):
555 """A bundle that is done being generated and can be served
575 """A bundle that is done being generated and can be served
556
576
557 extra attributes compared to BundleBase:
577 extra attributes compared to BundleBase:
558
578
559 :file_url: the url where the bundle is available.
579 :file_url: the url where the bundle is available.
560 :basename: the "basename" used to upload (useful for deletion)
580 :basename: the "basename" used to upload (useful for deletion)
561
581
562 These attributes exist to generate a bundle manifest
582 These attributes exist to generate a bundle manifest
563 (.hg/pullbundles.manifest)
583 (.hg/pullbundles.manifest)
564 """
584 """
565
585
566 ready = True
586 ready = True
567
587
568 def __init__(
588 def __init__(
569 self, bundle_type, revs, tip_rev, tip_node, file_url, basename
589 self, bundle_type, revs, tip_rev, tip_node, file_url, basename
570 ):
590 ):
571 self.file_url = file_url
591 self.file_url = file_url
572 self.basename = basename
592 self.basename = basename
573 super(GeneratedBundle, self).__init__(
593 super(GeneratedBundle, self).__init__(
574 bundle_type, revs, tip_rev, tip_node
594 bundle_type, revs, tip_rev, tip_node
575 )
595 )
576
596
577 @classmethod
597 @classmethod
578 def from_line(cls, line):
598 def from_line(cls, line):
579 """create an object by deserializing a line from AUTO_GEN_FILE"""
599 """create an object by deserializing a line from AUTO_GEN_FILE"""
580 assert line.startswith(b'DONE-v1 ')
600 assert line.startswith(b'DONE-v1 ')
581 (
601 (
582 __,
602 __,
583 bundle_type,
603 bundle_type,
584 revs,
604 revs,
585 tip_rev,
605 tip_rev,
586 tip_node,
606 tip_node,
587 file_url,
607 file_url,
588 basename,
608 basename,
589 ) = line.split()
609 ) = line.split()
590 revs = int(revs)
610 revs = int(revs)
591 tip_rev = int(tip_rev)
611 tip_rev = int(tip_rev)
592 file_url = util.urlreq.unquote(file_url)
612 file_url = util.urlreq.unquote(file_url)
593 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)
594
614
595 def to_line(self):
615 def to_line(self):
596 """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"""
597 templ = b"DONE-v1 %s %d %d %s %s %s"
617 templ = b"DONE-v1 %s %d %d %s %s %s"
598 data = (
618 data = (
599 self.bundle_type,
619 self.bundle_type,
600 self.revs,
620 self.revs,
601 self.tip_rev,
621 self.tip_rev,
602 self.tip_node,
622 self.tip_node,
603 util.urlreq.quote(self.file_url),
623 util.urlreq.quote(self.file_url),
604 self.basename,
624 self.basename,
605 )
625 )
606 return templ % data
626 return templ % data
607
627
608 def manifest_line(self):
628 def manifest_line(self):
609 """serialize the object to include as a line in pullbundles.manifest"""
629 """serialize the object to include as a line in pullbundles.manifest"""
610 templ = b"%s BUNDLESPEC=%s"
630 templ = b"%s BUNDLESPEC=%s"
611 if self.file_url.startswith(b'http'):
631 if self.file_url.startswith(b'http'):
612 templ += b" REQUIRESNI=true"
632 templ += b" REQUIRESNI=true"
613 return templ % (self.file_url, self.bundle_type)
633 return templ % (self.file_url, self.bundle_type)
614
634
615 def __eq__(self, other):
635 def __eq__(self, other):
616 if not super(GeneratedBundle, self).__eq__(other):
636 if not super(GeneratedBundle, self).__eq__(other):
617 return False
637 return False
618 return self.file_url == other.file_url
638 return self.file_url == other.file_url
619
639
620
640
621 def parse_auto_gen(content):
641 def parse_auto_gen(content):
622 """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"""
623 bundles = []
643 bundles = []
624 for line in content.splitlines():
644 for line in content.splitlines():
625 if line.startswith(b'PENDING-v1 '):
645 if line.startswith(b'PENDING-v1 '):
626 bundles.append(GeneratingBundle.from_line(line))
646 bundles.append(GeneratingBundle.from_line(line))
627 elif line.startswith(b'DONE-v1 '):
647 elif line.startswith(b'DONE-v1 '):
628 bundles.append(GeneratedBundle.from_line(line))
648 bundles.append(GeneratedBundle.from_line(line))
629 return bundles
649 return bundles
630
650
631
651
632 def dumps_auto_gen(bundles):
652 def dumps_auto_gen(bundles):
633 """serialize a list of Bundle as a AUTO_GEN_FILE content"""
653 """serialize a list of Bundle as a AUTO_GEN_FILE content"""
634 lines = []
654 lines = []
635 for b in bundles:
655 for b in bundles:
636 lines.append(b"%s\n" % b.to_line())
656 lines.append(b"%s\n" % b.to_line())
637 lines.sort()
657 lines.sort()
638 return b"".join(lines)
658 return b"".join(lines)
639
659
640
660
641 def read_auto_gen(repo):
661 def read_auto_gen(repo):
642 """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"""
643 data = repo.vfs.tryread(AUTO_GEN_FILE)
663 data = repo.vfs.tryread(AUTO_GEN_FILE)
644 if not data:
664 if not data:
645 return []
665 return []
646 return parse_auto_gen(data)
666 return parse_auto_gen(data)
647
667
648
668
649 def write_auto_gen(repo, bundles):
669 def write_auto_gen(repo, bundles):
650 """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"""
651 assert repo._cb_lock_ref is not None
671 assert repo._cb_lock_ref is not None
652 data = dumps_auto_gen(bundles)
672 data = dumps_auto_gen(bundles)
653 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:
654 f.write(data)
674 f.write(data)
655
675
656
676
657 def generate_manifest(bundles):
677 def generate_manifest(bundles):
658 """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"""
659 bundles = list(bundles)
679 bundles = list(bundles)
660 bundles.sort(key=lambda b: b.bundle_type)
680 bundles.sort(key=lambda b: b.bundle_type)
661 lines = []
681 lines = []
662 for b in bundles:
682 for b in bundles:
663 lines.append(b"%s\n" % b.manifest_line())
683 lines.append(b"%s\n" % b.manifest_line())
664 return b"".join(lines)
684 return b"".join(lines)
665
685
666
686
667 def update_ondisk_manifest(repo):
687 def update_ondisk_manifest(repo):
668 """update the clonebundle manifest with latest url"""
688 """update the clonebundle manifest with latest url"""
669 with repo.clonebundles_lock():
689 with repo.clonebundles_lock():
670 bundles = read_auto_gen(repo)
690 bundles = read_auto_gen(repo)
671
691
672 per_types = {}
692 per_types = {}
673 for b in bundles:
693 for b in bundles:
674 if not (b.ready and b.valid_for(repo)):
694 if not (b.ready and b.valid_for(repo)):
675 continue
695 continue
676 current = per_types.get(b.bundle_type)
696 current = per_types.get(b.bundle_type)
677 if current is not None and current.revs >= b.revs:
697 if current is not None and current.revs >= b.revs:
678 continue
698 continue
679 per_types[b.bundle_type] = b
699 per_types[b.bundle_type] = b
680 manifest = generate_manifest(per_types.values())
700 manifest = generate_manifest(per_types.values())
681 with repo.vfs(
701 with repo.vfs(
682 bundlecaches.CB_MANIFEST_FILE, mode=b"wb", atomictemp=True
702 bundlecaches.CB_MANIFEST_FILE, mode=b"wb", atomictemp=True
683 ) as f:
703 ) as f:
684 f.write(manifest)
704 f.write(manifest)
685
705
686
706
687 def update_bundle_list(repo, new_bundles=(), del_bundles=()):
707 def update_bundle_list(repo, new_bundles=(), del_bundles=()):
688 """modify the repo's AUTO_GEN_FILE
708 """modify the repo's AUTO_GEN_FILE
689
709
690 This method also regenerates the clone bundle manifest when needed"""
710 This method also regenerates the clone bundle manifest when needed"""
691 with repo.clonebundles_lock():
711 with repo.clonebundles_lock():
692 bundles = read_auto_gen(repo)
712 bundles = read_auto_gen(repo)
693 if del_bundles:
713 if del_bundles:
694 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]
695 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]
696 bundles.extend(new_bundles)
716 bundles.extend(new_bundles)
697 write_auto_gen(repo, bundles)
717 write_auto_gen(repo, bundles)
698 all_changed = []
718 all_changed = []
699 all_changed.extend(new_bundles)
719 all_changed.extend(new_bundles)
700 all_changed.extend(del_bundles)
720 all_changed.extend(del_bundles)
701 if any(b.ready for b in all_changed):
721 if any(b.ready for b in all_changed):
702 update_ondisk_manifest(repo)
722 update_ondisk_manifest(repo)
703
723
704
724
705 def cleanup_tmp_bundle(repo, target):
725 def cleanup_tmp_bundle(repo, target):
706 """remove a GeneratingBundle file and entry"""
726 """remove a GeneratingBundle file and entry"""
707 assert not target.ready
727 assert not target.ready
708 with repo.clonebundles_lock():
728 with repo.clonebundles_lock():
709 repo.vfs.tryunlink(target.filepath)
729 repo.vfs.tryunlink(target.filepath)
710 update_bundle_list(repo, del_bundles=[target])
730 update_bundle_list(repo, del_bundles=[target])
711
731
712
732
713 def finalize_one_bundle(repo, target):
733 def finalize_one_bundle(repo, target):
714 """upload a generated bundle and advertise it in the clonebundles.manifest"""
734 """upload a generated bundle and advertise it in the clonebundles.manifest"""
715 with repo.clonebundles_lock():
735 with repo.clonebundles_lock():
716 bundles = read_auto_gen(repo)
736 bundles = read_auto_gen(repo)
717 if target in bundles and target.valid_for(repo):
737 if target in bundles and target.valid_for(repo):
718 result = upload_bundle(repo, target)
738 result = upload_bundle(repo, target)
719 update_bundle_list(repo, new_bundles=[result])
739 update_bundle_list(repo, new_bundles=[result])
720 cleanup_tmp_bundle(repo, target)
740 cleanup_tmp_bundle(repo, target)
721
741
722
742
723 def find_outdated_bundles(repo, bundles):
743 def find_outdated_bundles(repo, bundles):
724 """finds outdated bundles"""
744 """finds outdated bundles"""
725 olds = []
745 olds = []
726 per_types = {}
746 per_types = {}
727 for b in bundles:
747 for b in bundles:
728 if not b.valid_for(repo):
748 if not b.valid_for(repo):
729 olds.append(b)
749 olds.append(b)
730 continue
750 continue
731 l = per_types.setdefault(b.bundle_type, [])
751 l = per_types.setdefault(b.bundle_type, [])
732 l.append(b)
752 l.append(b)
733 for key in sorted(per_types):
753 for key in sorted(per_types):
734 all = per_types[key]
754 all = per_types[key]
735 if len(all) > 1:
755 if len(all) > 1:
736 all.sort(key=lambda b: b.revs, reverse=True)
756 all.sort(key=lambda b: b.revs, reverse=True)
737 olds.extend(all[1:])
757 olds.extend(all[1:])
738 return olds
758 return olds
739
759
740
760
741 def collect_garbage(repo):
761 def collect_garbage(repo):
742 """finds outdated bundles and get them deleted"""
762 """finds outdated bundles and get them deleted"""
743 with repo.clonebundles_lock():
763 with repo.clonebundles_lock():
744 bundles = read_auto_gen(repo)
764 bundles = read_auto_gen(repo)
745 olds = find_outdated_bundles(repo, bundles)
765 olds = find_outdated_bundles(repo, bundles)
746 for o in olds:
766 for o in olds:
747 delete_bundle(repo, o)
767 delete_bundle(repo, o)
748 update_bundle_list(repo, del_bundles=olds)
768 update_bundle_list(repo, del_bundles=olds)
749
769
750
770
751 def upload_bundle(repo, bundle):
771 def upload_bundle(repo, bundle):
752 """upload the result of a GeneratingBundle and return a GeneratedBundle
772 """upload the result of a GeneratingBundle and return a GeneratedBundle
753
773
754 The upload is done using the `clone-bundles.upload-command`
774 The upload is done using the `clone-bundles.upload-command`
755 """
775 """
756 cmd = repo.ui.config(b'clone-bundles', b'upload-command')
776 inline = repo.ui.config(b'clone-bundles', b'auto-generate.serve-inline')
757 url = repo.ui.config(b'clone-bundles', b'url-template')
758 basename = repo.vfs.basename(bundle.filepath)
777 basename = repo.vfs.basename(bundle.filepath)
759 filepath = procutil.shellquote(bundle.filepath)
778 if inline:
760 variables = {
779 dest_dir = repo.vfs.join(bundlecaches.BUNDLE_CACHE_DIR)
761 b'HGCB_BUNDLE_PATH': filepath,
780 repo.vfs.makedirs(dest_dir)
762 b'HGCB_BUNDLE_BASENAME': basename,
781 dest = repo.vfs.join(dest_dir, basename)
763 }
782 util.copyfiles(bundle.filepath, dest, hardlink=True)
764 env = procutil.shellenviron(environ=variables)
783 url = bundlecaches.CLONEBUNDLESCHEME + basename
765 ret = repo.ui.system(cmd, environ=env)
784 return bundle.uploaded(url, basename)
766 if ret:
785 else:
767 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
786 cmd = repo.ui.config(b'clone-bundles', b'upload-command')
768 url = (
787 url = repo.ui.config(b'clone-bundles', b'url-template')
769 url.decode('utf8')
788 filepath = procutil.shellquote(bundle.filepath)
770 .format(basename=basename.decode('utf8'))
789 variables = {
771 .encode('utf8')
790 b'HGCB_BUNDLE_PATH': filepath,
772 )
791 b'HGCB_BUNDLE_BASENAME': basename,
773 return bundle.uploaded(url, basename)
792 }
793 env = procutil.shellenviron(environ=variables)
794 ret = repo.ui.system(cmd, environ=env)
795 if ret:
796 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
797 url = (
798 url.decode('utf8')
799 .format(basename=basename.decode('utf8'))
800 .encode('utf8')
801 )
802 return bundle.uploaded(url, basename)
774
803
775
804
776 def delete_bundle(repo, bundle):
805 def delete_bundle(repo, bundle):
777 """delete a bundle from storage"""
806 """delete a bundle from storage"""
778 assert bundle.ready
807 assert bundle.ready
779 msg = b'clone-bundles: deleting bundle %s\n'
808
809 inline = bundle.file_url.startswith(bundlecaches.CLONEBUNDLESCHEME)
810
811 if inline:
812 msg = b'clone-bundles: deleting inline bundle %s\n'
813 else:
814 msg = b'clone-bundles: deleting bundle %s\n'
780 msg %= bundle.basename
815 msg %= bundle.basename
781 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
816 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
782 repo.ui.write(msg)
817 repo.ui.write(msg)
783 else:
818 else:
784 repo.ui.debug(msg)
819 repo.ui.debug(msg)
785
820
786 cmd = repo.ui.config(b'clone-bundles', b'delete-command')
821 if inline:
787 variables = {
822 inline_path = repo.vfs.join(
788 b'HGCB_BUNDLE_URL': bundle.file_url,
823 bundlecaches.BUNDLE_CACHE_DIR,
789 b'HGCB_BASENAME': bundle.basename,
824 bundle.basename,
790 }
825 )
791 env = procutil.shellenviron(environ=variables)
826 util.tryunlink(inline_path)
792 ret = repo.ui.system(cmd, environ=env)
827 else:
793 if ret:
828 cmd = repo.ui.config(b'clone-bundles', b'delete-command')
794 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
829 variables = {
830 b'HGCB_BUNDLE_URL': bundle.file_url,
831 b'HGCB_BASENAME': bundle.basename,
832 }
833 env = procutil.shellenviron(environ=variables)
834 ret = repo.ui.system(cmd, environ=env)
835 if ret:
836 raise error.Abort(b"command returned status %d: %s" % (ret, cmd))
795
837
796
838
797 def auto_bundle_needed_actions(repo, bundles, op_id):
839 def auto_bundle_needed_actions(repo, bundles, op_id):
798 """find the list of bundles that need action
840 """find the list of bundles that need action
799
841
800 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
801 uploaded."""
843 uploaded."""
802 create_bundles = []
844 create_bundles = []
803 delete_bundles = []
845 delete_bundles = []
804 repo = repo.filtered(b"immutable")
846 repo = repo.filtered(b"immutable")
805 targets = repo.ui.configlist(b'clone-bundles', b'auto-generate.formats')
847 targets = repo.ui.configlist(b'clone-bundles', b'auto-generate.formats')
806 ratio = float(
848 ratio = float(
807 repo.ui.config(b'clone-bundles', b'trigger.below-bundled-ratio')
849 repo.ui.config(b'clone-bundles', b'trigger.below-bundled-ratio')
808 )
850 )
809 abs_revs = repo.ui.configint(b'clone-bundles', b'trigger.revs')
851 abs_revs = repo.ui.configint(b'clone-bundles', b'trigger.revs')
810 revs = len(repo.changelog)
852 revs = len(repo.changelog)
811 generic_data = {
853 generic_data = {
812 'revs': revs,
854 'revs': revs,
813 'head_revs': repo.changelog.headrevs(),
855 'head_revs': repo.changelog.headrevs(),
814 'tip_rev': repo.changelog.tiprev(),
856 'tip_rev': repo.changelog.tiprev(),
815 'tip_node': node.hex(repo.changelog.tip()),
857 'tip_node': node.hex(repo.changelog.tip()),
816 'op_id': op_id,
858 'op_id': op_id,
817 }
859 }
818 for t in targets:
860 for t in targets:
819 if new_bundle_needed(repo, bundles, ratio, abs_revs, t, revs):
861 if new_bundle_needed(repo, bundles, ratio, abs_revs, t, revs):
820 data = generic_data.copy()
862 data = generic_data.copy()
821 data['bundle_type'] = t
863 data['bundle_type'] = t
822 b = RequestedBundle(**data)
864 b = RequestedBundle(**data)
823 create_bundles.append(b)
865 create_bundles.append(b)
824 delete_bundles.extend(find_outdated_bundles(repo, bundles))
866 delete_bundles.extend(find_outdated_bundles(repo, bundles))
825 return create_bundles, delete_bundles
867 return create_bundles, delete_bundles
826
868
827
869
828 def new_bundle_needed(repo, bundles, ratio, abs_revs, bundle_type, revs):
870 def new_bundle_needed(repo, bundles, ratio, abs_revs, bundle_type, revs):
829 """consider the current cached content and trigger new bundles if needed"""
871 """consider the current cached content and trigger new bundles if needed"""
830 threshold = max((revs * ratio), (revs - abs_revs))
872 threshold = max((revs * ratio), (revs - abs_revs))
831 for b in bundles:
873 for b in bundles:
832 if not b.valid_for(repo) or b.bundle_type != bundle_type:
874 if not b.valid_for(repo) or b.bundle_type != bundle_type:
833 continue
875 continue
834 if b.revs > threshold:
876 if b.revs > threshold:
835 return False
877 return False
836 return True
878 return True
837
879
838
880
839 def start_one_bundle(repo, bundle):
881 def start_one_bundle(repo, bundle):
840 """start the generation of a single bundle file
882 """start the generation of a single bundle file
841
883
842 the `bundle` argument should be a RequestedBundle object.
884 the `bundle` argument should be a RequestedBundle object.
843
885
844 This data is passed to the `debugmakeclonebundles` "as is".
886 This data is passed to the `debugmakeclonebundles` "as is".
845 """
887 """
846 data = util.pickle.dumps(bundle)
888 data = util.pickle.dumps(bundle)
847 cmd = [procutil.hgexecutable(), b'--cwd', repo.path, INTERNAL_CMD]
889 cmd = [procutil.hgexecutable(), b'--cwd', repo.path, INTERNAL_CMD]
848 env = procutil.shellenviron()
890 env = procutil.shellenviron()
849 msg = b'clone-bundles: starting bundle generation: %s\n'
891 msg = b'clone-bundles: starting bundle generation: %s\n'
850 stdout = None
892 stdout = None
851 stderr = None
893 stderr = None
852 waits = []
894 waits = []
853 record_wait = None
895 record_wait = None
854 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
896 if repo.ui.configbool(b'devel', b'debug.clonebundles'):
855 stdout = procutil.stdout
897 stdout = procutil.stdout
856 stderr = procutil.stderr
898 stderr = procutil.stderr
857 repo.ui.write(msg % bundle.bundle_type)
899 repo.ui.write(msg % bundle.bundle_type)
858 record_wait = waits.append
900 record_wait = waits.append
859 else:
901 else:
860 repo.ui.debug(msg % bundle.bundle_type)
902 repo.ui.debug(msg % bundle.bundle_type)
861 bg = procutil.runbgcommand
903 bg = procutil.runbgcommand
862 bg(
904 bg(
863 cmd,
905 cmd,
864 env,
906 env,
865 stdin_bytes=data,
907 stdin_bytes=data,
866 stdout=stdout,
908 stdout=stdout,
867 stderr=stderr,
909 stderr=stderr,
868 record_wait=record_wait,
910 record_wait=record_wait,
869 )
911 )
870 for f in waits:
912 for f in waits:
871 f()
913 f()
872
914
873
915
874 INTERNAL_CMD = b'debug::internal-make-clone-bundles'
916 INTERNAL_CMD = b'debug::internal-make-clone-bundles'
875
917
876
918
877 @command(INTERNAL_CMD, [], b'')
919 @command(INTERNAL_CMD, [], b'')
878 def debugmakeclonebundles(ui, repo):
920 def debugmakeclonebundles(ui, repo):
879 """Internal command to auto-generate debug bundles"""
921 """Internal command to auto-generate debug bundles"""
880 requested_bundle = util.pickle.load(procutil.stdin)
922 requested_bundle = util.pickle.load(procutil.stdin)
881 procutil.stdin.close()
923 procutil.stdin.close()
882
924
883 collect_garbage(repo)
925 collect_garbage(repo)
884
926
885 fname = requested_bundle.suggested_filename
927 fname = requested_bundle.suggested_filename
886 fpath = repo.vfs.makedirs(b'tmp-bundles')
928 fpath = repo.vfs.makedirs(b'tmp-bundles')
887 fpath = repo.vfs.join(b'tmp-bundles', fname)
929 fpath = repo.vfs.join(b'tmp-bundles', fname)
888 bundle = requested_bundle.generating(fpath)
930 bundle = requested_bundle.generating(fpath)
889 update_bundle_list(repo, new_bundles=[bundle])
931 update_bundle_list(repo, new_bundles=[bundle])
890
932
891 requested_bundle.generate_bundle(repo, fpath)
933 requested_bundle.generate_bundle(repo, fpath)
892
934
893 repo.invalidate()
935 repo.invalidate()
894 finalize_one_bundle(repo, bundle)
936 finalize_one_bundle(repo, bundle)
895
937
896
938
897 def make_auto_bundler(source_repo):
939 def make_auto_bundler(source_repo):
898 reporef = weakref.ref(source_repo)
940 reporef = weakref.ref(source_repo)
899
941
900 def autobundle(tr):
942 def autobundle(tr):
901 repo = reporef()
943 repo = reporef()
902 assert repo is not None
944 assert repo is not None
903 bundles = read_auto_gen(repo)
945 bundles = read_auto_gen(repo)
904 new, __ = auto_bundle_needed_actions(repo, bundles, b"%d_txn" % id(tr))
946 new, __ = auto_bundle_needed_actions(repo, bundles, b"%d_txn" % id(tr))
905 for data in new:
947 for data in new:
906 start_one_bundle(repo, data)
948 start_one_bundle(repo, data)
907 return None
949 return None
908
950
909 return autobundle
951 return autobundle
910
952
911
953
912 def reposetup(ui, repo):
954 def reposetup(ui, repo):
913 """install the two pieces needed for automatic clonebundle generation
955 """install the two pieces needed for automatic clonebundle generation
914
956
915 - add a "post-close" hook that fires bundling when needed
957 - add a "post-close" hook that fires bundling when needed
916 - introduce a clone-bundle lock to let multiple processes meddle with the
958 - introduce a clone-bundle lock to let multiple processes meddle with the
917 state files.
959 state files.
918 """
960 """
919 if not repo.local():
961 if not repo.local():
920 return
962 return
921
963
922 class autobundlesrepo(repo.__class__):
964 class autobundlesrepo(repo.__class__):
923 def transaction(self, *args, **kwargs):
965 def transaction(self, *args, **kwargs):
924 tr = super(autobundlesrepo, self).transaction(*args, **kwargs)
966 tr = super(autobundlesrepo, self).transaction(*args, **kwargs)
925 enabled = repo.ui.configbool(
967 enabled = repo.ui.configbool(
926 b'clone-bundles',
968 b'clone-bundles',
927 b'auto-generate.on-change',
969 b'auto-generate.on-change',
928 )
970 )
929 targets = repo.ui.configlist(
971 targets = repo.ui.configlist(
930 b'clone-bundles', b'auto-generate.formats'
972 b'clone-bundles', b'auto-generate.formats'
931 )
973 )
932 if enabled and targets:
974 if enabled and targets:
933 tr.addpostclose(CAT_POSTCLOSE, make_auto_bundler(self))
975 tr.addpostclose(CAT_POSTCLOSE, make_auto_bundler(self))
934 return tr
976 return tr
935
977
936 @localrepo.unfilteredmethod
978 @localrepo.unfilteredmethod
937 def clonebundles_lock(self, wait=True):
979 def clonebundles_lock(self, wait=True):
938 '''Lock the repository file related to clone bundles'''
980 '''Lock the repository file related to clone bundles'''
939 if not util.safehasattr(self, '_cb_lock_ref'):
981 if not util.safehasattr(self, '_cb_lock_ref'):
940 self._cb_lock_ref = None
982 self._cb_lock_ref = None
941 l = self._currentlock(self._cb_lock_ref)
983 l = self._currentlock(self._cb_lock_ref)
942 if l is not None:
984 if l is not None:
943 l.lock()
985 l.lock()
944 return l
986 return l
945
987
946 l = self._lock(
988 l = self._lock(
947 vfs=self.vfs,
989 vfs=self.vfs,
948 lockname=b"clonebundleslock",
990 lockname=b"clonebundleslock",
949 wait=wait,
991 wait=wait,
950 releasefn=None,
992 releasefn=None,
951 acquirefn=None,
993 acquirefn=None,
952 desc=_(b'repository %s') % self.origroot,
994 desc=_(b'repository %s') % self.origroot,
953 )
995 )
954 self._cb_lock_ref = weakref.ref(l)
996 self._cb_lock_ref = weakref.ref(l)
955 return l
997 return l
956
998
957 repo._wlockfreeprefix.add(AUTO_GEN_FILE)
999 repo._wlockfreeprefix.add(AUTO_GEN_FILE)
958 repo._wlockfreeprefix.add(bundlecaches.CB_MANIFEST_FILE)
1000 repo._wlockfreeprefix.add(bundlecaches.CB_MANIFEST_FILE)
959 repo.__class__ = autobundlesrepo
1001 repo.__class__ = autobundlesrepo
960
1002
961
1003
962 @command(
1004 @command(
963 b'admin::clone-bundles-refresh',
1005 b'admin::clone-bundles-refresh',
964 [
1006 [
965 (
1007 (
966 b'',
1008 b'',
967 b'background',
1009 b'background',
968 False,
1010 False,
969 _(b'start bundle generation in the background'),
1011 _(b'start bundle generation in the background'),
970 ),
1012 ),
971 ],
1013 ],
972 b'',
1014 b'',
973 )
1015 )
974 def cmd_admin_clone_bundles_refresh(
1016 def cmd_admin_clone_bundles_refresh(
975 ui,
1017 ui,
976 repo: localrepo.localrepository,
1018 repo: localrepo.localrepository,
977 background=False,
1019 background=False,
978 ):
1020 ):
979 """generate clone bundles according to the configuration
1021 """generate clone bundles according to the configuration
980
1022
981 This runs the logic for automatic generation, removing outdated bundles and
1023 This runs the logic for automatic generation, removing outdated bundles and
982 generating new ones if necessary. See :hg:`help -e clone-bundles` for
1024 generating new ones if necessary. See :hg:`help -e clone-bundles` for
983 details about how to configure this feature.
1025 details about how to configure this feature.
984 """
1026 """
985 debug = repo.ui.configbool(b'devel', b'debug.clonebundles')
1027 debug = repo.ui.configbool(b'devel', b'debug.clonebundles')
986 bundles = read_auto_gen(repo)
1028 bundles = read_auto_gen(repo)
987 op_id = b"%d_acbr" % os.getpid()
1029 op_id = b"%d_acbr" % os.getpid()
988 create, delete = auto_bundle_needed_actions(repo, bundles, op_id)
1030 create, delete = auto_bundle_needed_actions(repo, bundles, op_id)
989
1031
990 # if some bundles are scheduled for creation in the background, they will
1032 # if some bundles are scheduled for creation in the background, they will
991 # deal with garbage collection too, so no need to synchroniously do it.
1033 # deal with garbage collection too, so no need to synchroniously do it.
992 #
1034 #
993 # However if no bundles are scheduled for creation, we need to explicitly do
1035 # However if no bundles are scheduled for creation, we need to explicitly do
994 # it here.
1036 # it here.
995 if not (background and create):
1037 if not (background and create):
996 # we clean up outdated bundles before generating new ones to keep the
1038 # we clean up outdated bundles before generating new ones to keep the
997 # last two versions of the bundle around for a while and avoid having to
1039 # last two versions of the bundle around for a while and avoid having to
998 # deal with clients that just got served a manifest.
1040 # deal with clients that just got served a manifest.
999 for o in delete:
1041 for o in delete:
1000 delete_bundle(repo, o)
1042 delete_bundle(repo, o)
1001 update_bundle_list(repo, del_bundles=delete)
1043 update_bundle_list(repo, del_bundles=delete)
1002
1044
1003 if create:
1045 if create:
1004 fpath = repo.vfs.makedirs(b'tmp-bundles')
1046 fpath = repo.vfs.makedirs(b'tmp-bundles')
1005
1047
1006 if background:
1048 if background:
1007 for requested_bundle in create:
1049 for requested_bundle in create:
1008 start_one_bundle(repo, requested_bundle)
1050 start_one_bundle(repo, requested_bundle)
1009 else:
1051 else:
1010 for requested_bundle in create:
1052 for requested_bundle in create:
1011 if debug:
1053 if debug:
1012 msg = b'clone-bundles: starting bundle generation: %s\n'
1054 msg = b'clone-bundles: starting bundle generation: %s\n'
1013 repo.ui.write(msg % requested_bundle.bundle_type)
1055 repo.ui.write(msg % requested_bundle.bundle_type)
1014 fname = requested_bundle.suggested_filename
1056 fname = requested_bundle.suggested_filename
1015 fpath = repo.vfs.join(b'tmp-bundles', fname)
1057 fpath = repo.vfs.join(b'tmp-bundles', fname)
1016 generating_bundle = requested_bundle.generating(fpath)
1058 generating_bundle = requested_bundle.generating(fpath)
1017 update_bundle_list(repo, new_bundles=[generating_bundle])
1059 update_bundle_list(repo, new_bundles=[generating_bundle])
1018 requested_bundle.generate_bundle(repo, fpath)
1060 requested_bundle.generate_bundle(repo, fpath)
1019 result = upload_bundle(repo, generating_bundle)
1061 result = upload_bundle(repo, generating_bundle)
1020 update_bundle_list(repo, new_bundles=[result])
1062 update_bundle_list(repo, new_bundles=[result])
1021 update_ondisk_manifest(repo)
1063 update_ondisk_manifest(repo)
1022 cleanup_tmp_bundle(repo, generating_bundle)
1064 cleanup_tmp_bundle(repo, generating_bundle)
1023
1065
1024
1066
1025 @command(b'admin::clone-bundles-clear', [], b'')
1067 @command(b'admin::clone-bundles-clear', [], b'')
1026 def cmd_admin_clone_bundles_clear(ui, repo: localrepo.localrepository):
1068 def cmd_admin_clone_bundles_clear(ui, repo: localrepo.localrepository):
1027 """remove existing clone bundle caches
1069 """remove existing clone bundle caches
1028
1070
1029 See `hg help admin::clone-bundles-refresh` for details on how to regenerate
1071 See `hg help admin::clone-bundles-refresh` for details on how to regenerate
1030 them.
1072 them.
1031
1073
1032 This command will only affect bundles currently available, it will not
1074 This command will only affect bundles currently available, it will not
1033 affect bundles being asynchronously generated.
1075 affect bundles being asynchronously generated.
1034 """
1076 """
1035 bundles = read_auto_gen(repo)
1077 bundles = read_auto_gen(repo)
1036 delete = [b for b in bundles if b.ready]
1078 delete = [b for b in bundles if b.ready]
1037 for o in delete:
1079 for o in delete:
1038 delete_bundle(repo, o)
1080 delete_bundle(repo, o)
1039 update_bundle_list(repo, del_bundles=delete)
1081 update_bundle_list(repo, del_bundles=delete)
@@ -1,384 +1,476 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: 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-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg BUNDLESPEC=v2 (glob)
46 $ ls -1 ../final-upload
46 $ ls -1 ../final-upload
47 full-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
47 full-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: 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-v2-4_revs-6427147b985a_tip-*_txn.hg BUNDLESPEC=v2 (glob)
69 $ ls -1 ../final-upload
69 $ ls -1 ../final-upload
70 full-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
70 full-v2-2_revs-aaff8d2ffbbf_tip-*_txn.hg (glob)
71 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
71 full-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-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: 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-v2-6_revs-b1010e95ea00_tip-*_txn.hg BUNDLESPEC=v2 (glob)
94 $ ls -1 ../final-upload
94 $ ls -1 ../final-upload
95 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
95 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
96 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
96 full-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-v2-6_revs-b1010e95ea00_tip-*_txn.hg BUNDLESPEC=v2 (glob)
119 $ ls -1 ../final-upload
119 $ ls -1 ../final-upload
120 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
120 full-v2-4_revs-6427147b985a_tip-*_txn.hg (glob)
121 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
121 full-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-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: 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-v2-8_revs-8353e8af1306_tip-*_txn.hg BUNDLESPEC=v2 (glob)
144 $ ls -1 ../final-upload
144 $ ls -1 ../final-upload
145 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
145 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
146 full-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
146 full-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-v2-8_revs-8353e8af1306_tip-*_txn.hg BUNDLESPEC=v2 (glob)
162 $ ls -1 ../final-upload
162 $ ls -1 ../final-upload
163 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
163 full-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
164 full-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
164 full-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-v2-6_revs-b1010e95ea00_tip-*_txn.hg (glob)
208 clone-bundles: starting bundle generation: v2
208 clone-bundles: starting bundle generation: 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-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
212 $ ls -1 ../final-upload
212 $ ls -1 ../final-upload
213 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
213 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
214 full-v2-8_revs-8353e8af1306_tip-*_txn.hg (glob)
214 full-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-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-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
224 $ ls -1 ../final-upload
224 $ ls -1 ../final-upload
225 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
225 full-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: 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-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
288 file:/*/$TESTTMP/final-upload/full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
288 file:/*/$TESTTMP/final-upload/full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
289 $ ls -1 ../final-upload
289 $ ls -1 ../final-upload
290 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
290 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
291 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
291 full-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: 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-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
315 file:/*/$TESTTMP/final-upload/full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
315 file:/*/$TESTTMP/final-upload/full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
316 $ ls -1 ../final-upload
316 $ ls -1 ../final-upload
317 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
317 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
318 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
318 full-v2-10_revs-3b6f57f17d70_tip-*_acbr.hg (glob)
319 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
319 full-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-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-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-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: v1
350 clone-bundles: starting bundle generation: v2
350 clone-bundles: starting bundle generation: 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-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
356 file:/*/$TESTTMP/final-upload/full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
356 file:/*/$TESTTMP/final-upload/full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
357 $ ls -1 ../final-upload
357 $ ls -1 ../final-upload
358 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
358 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
359 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
359 full-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-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-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: v1
375 11 changesets found
375 11 changesets found
376 clone-bundles: starting bundle generation: v2
376 clone-bundles: starting bundle generation: 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-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=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-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 REQUIRESNI=true (glob)
385
386 Test serving them through inline-clone bundle
387 =============================================
388
389 $ cat >> ../server/.hg/hgrc << EOF
390 > [clone-bundles]
391 > auto-generate.serve-inline=yes
392 > EOF
393 $ hg -R ../server/ admin::clone-bundles-clear
394 clone-bundles: deleting bundle full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
395 clone-bundles: deleting bundle full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
396
397 initial generation
398 ------------------
399
400
401 $ hg -R ../server/ admin::clone-bundles-refresh
402 clone-bundles: starting bundle generation: v1
403 11 changesets found
404 clone-bundles: starting bundle generation: v2
405 11 changesets found
406 $ cat ../server/.hg/clonebundles.manifest
407 peer-bundle-cache://full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
408 peer-bundle-cache://full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
409 $ ls -1 ../server/.hg/bundle-cache
410 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
411 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
412 $ ls -1 ../final-upload
413
414 Regeneration eventually cleanup the old ones
415 --------------------------------------------
416
417 create more content
418 $ touch voit
419 $ hg -q commit -A -m 'add voit'
420 $ touch ar
421 $ hg -q commit -A -m 'add ar'
422 $ hg push
423 pushing to $TESTTMP/server
424 searching for changes
425 adding changesets
426 adding manifests
427 adding file changes
428 added 2 changesets with 2 changes to 2 files
429
430 check first regeneration
431
432 $ hg -R ../server/ admin::clone-bundles-refresh
433 clone-bundles: starting bundle generation: v1
434 13 changesets found
435 clone-bundles: starting bundle generation: v2
436 13 changesets found
437 $ cat ../server/.hg/clonebundles.manifest
438 peer-bundle-cache://full-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
439 peer-bundle-cache://full-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
440 $ ls -1 ../server/.hg/bundle-cache
441 full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
442 full-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
443 full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
444 full-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
445 $ ls -1 ../final-upload
446
447 check first regeneration (should cleanup the one before that last)
448
449 $ touch "investi"
450 $ hg -q commit -A -m 'add investi'
451 $ touch "lesgisla"
452 $ hg -q commit -A -m 'add lesgisla'
453 $ hg push
454 pushing to $TESTTMP/server
455 searching for changes
456 adding changesets
457 adding manifests
458 adding file changes
459 added 2 changesets with 2 changes to 2 files
460
461 $ hg -R ../server/ admin::clone-bundles-refresh
462 clone-bundles: deleting inline bundle full-v1-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
463 clone-bundles: deleting inline bundle full-v2-11_revs-4226b1cd5fda_tip-*_acbr.hg (glob)
464 clone-bundles: starting bundle generation: v1
465 15 changesets found
466 clone-bundles: starting bundle generation: v2
467 15 changesets found
468 $ cat ../server/.hg/clonebundles.manifest
469 peer-bundle-cache://full-v1-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=v1 (glob)
470 peer-bundle-cache://full-v2-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=v2 (glob)
471 $ ls -1 ../server/.hg/bundle-cache
472 full-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
473 full-v1-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
474 full-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
475 full-v2-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
476 $ ls -1 ../final-upload
General Comments 0
You need to be logged in to leave comments. Login now