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