207 |
207 |
208 It is possible to set Mercurial to automatically re-generate clone bundles when |
208 It is possible to set Mercurial to automatically re-generate clone bundles when |
209 new content is available. |
209 new content is available. |
210 |
210 |
211 Mercurial will take care of the process asynchronously. The defined list of |
211 Mercurial will take care of the process asynchronously. The defined list of |
212 bundle type will be generated, uploaded, and advertised. |
212 bundle-type will be generated, uploaded, and advertised. Older bundles will get |
|
213 decommissioned as newer ones replace them. |
213 |
214 |
214 Bundles Generation: |
215 Bundles Generation: |
215 ................... |
216 ................... |
216 |
217 |
217 The extension can generate multiple variants of the clone bundle. Each |
218 The extension can generate multiple variants of the clone bundle. Each |
233 |
234 |
234 [clone-bundles] |
235 [clone-bundles] |
235 upload-command=sftp put $HGCB_BUNDLE_PATH \ |
236 upload-command=sftp put $HGCB_BUNDLE_PATH \ |
236 sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME |
237 sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME |
237 |
238 |
|
239 If the file was already uploaded, the command must still succeed. |
|
240 |
238 After upload, the file should be available at an url defined by |
241 After upload, the file should be available at an url defined by |
239 `clone-bundles.url-template`. |
242 `clone-bundles.url-template`. |
240 |
243 |
241 [clone-bundles] |
244 [clone-bundles] |
242 url-template=https://bundles.host/cache/clone-bundles/{basename} |
245 url-template=https://bundles.host/cache/clone-bundles/{basename} |
|
246 |
|
247 Old bundles cleanup: |
|
248 .................... |
|
249 |
|
250 When new bundles are generated, the older ones are no longer necessary and can |
|
251 be removed from storage. This is done through the `clone-bundles.delete-command` |
|
252 configuration. The command is given the url of the artifact to delete through |
|
253 the `$HGCB_BUNDLE_URL` environment variable. |
|
254 |
|
255 [clone-bundles] |
|
256 delete-command=sftp rm sftp://bundles.host/clone-bundles/$HGCB_BUNDLE_BASENAME |
|
257 |
|
258 If the file was already deleted, the command must still succeed. |
243 """ |
259 """ |
244 |
260 |
245 |
261 |
246 import os |
262 import os |
247 import weakref |
263 import weakref |
296 |
312 |
297 configitem(b'clone-bundles', b'auto-generate.formats', default=list) |
313 configitem(b'clone-bundles', b'auto-generate.formats', default=list) |
298 |
314 |
299 |
315 |
300 configitem(b'clone-bundles', b'upload-command', default=None) |
316 configitem(b'clone-bundles', b'upload-command', default=None) |
|
317 |
|
318 configitem(b'clone-bundles', b'delete-command', default=None) |
301 |
319 |
302 configitem(b'clone-bundles', b'url-template', default=None) |
320 configitem(b'clone-bundles', b'url-template', default=None) |
303 |
321 |
304 configitem(b'devel', b'debug.clonebundles', default=False) |
322 configitem(b'devel', b'debug.clonebundles', default=False) |
305 |
323 |
664 result = upload_bundle(repo, target) |
682 result = upload_bundle(repo, target) |
665 update_bundle_list(repo, new_bundles=[result]) |
683 update_bundle_list(repo, new_bundles=[result]) |
666 cleanup_tmp_bundle(repo, target) |
684 cleanup_tmp_bundle(repo, target) |
667 |
685 |
668 |
686 |
|
687 def find_outdated_bundles(repo, bundles): |
|
688 """finds outdated bundles""" |
|
689 olds = [] |
|
690 per_types = {} |
|
691 for b in bundles: |
|
692 if not b.valid_for(repo): |
|
693 olds.append(b) |
|
694 continue |
|
695 l = per_types.setdefault(b.bundle_type, []) |
|
696 l.append(b) |
|
697 for key in sorted(per_types): |
|
698 all = per_types[key] |
|
699 if len(all) > 1: |
|
700 all.sort(key=lambda b: b.revs, reverse=True) |
|
701 olds.extend(all[1:]) |
|
702 return olds |
|
703 |
|
704 |
|
705 def collect_garbage(repo): |
|
706 """finds outdated bundles and get them deleted""" |
|
707 with repo.clonebundles_lock(): |
|
708 bundles = read_auto_gen(repo) |
|
709 olds = find_outdated_bundles(repo, bundles) |
|
710 for o in olds: |
|
711 delete_bundle(repo, o) |
|
712 update_bundle_list(repo, del_bundles=olds) |
|
713 |
|
714 |
669 def upload_bundle(repo, bundle): |
715 def upload_bundle(repo, bundle): |
670 """upload the result of a GeneratingBundle and return a GeneratedBundle |
716 """upload the result of a GeneratingBundle and return a GeneratedBundle |
671 |
717 |
672 The upload is done using the `clone-bundles.upload-command` |
718 The upload is done using the `clone-bundles.upload-command` |
673 """ |
719 """ |
689 .encode('utf8') |
735 .encode('utf8') |
690 ) |
736 ) |
691 return bundle.uploaded(url, basename) |
737 return bundle.uploaded(url, basename) |
692 |
738 |
693 |
739 |
|
740 def delete_bundle(repo, bundle): |
|
741 """delete a bundle from storage""" |
|
742 assert bundle.ready |
|
743 msg = b'clone-bundles: deleting bundle %s\n' |
|
744 msg %= bundle.basename |
|
745 if repo.ui.configbool(b'devel', b'debug.clonebundles'): |
|
746 repo.ui.write(msg) |
|
747 else: |
|
748 repo.ui.debug(msg) |
|
749 |
|
750 cmd = repo.ui.config(b'clone-bundles', b'delete-command') |
|
751 variables = { |
|
752 b'HGCB_BUNDLE_URL': bundle.file_url, |
|
753 b'HGCB_BASENAME': bundle.basename, |
|
754 } |
|
755 env = procutil.shellenviron(environ=variables) |
|
756 ret = repo.ui.system(cmd, environ=env) |
|
757 if ret: |
|
758 raise error.Abort(b"command returned status %d: %s" % (ret, cmd)) |
|
759 |
|
760 |
694 def auto_bundle_needed_actions(repo, bundles, op_id): |
761 def auto_bundle_needed_actions(repo, bundles, op_id): |
695 """find the list of bundles that need action |
762 """find the list of bundles that need action |
696 |
763 |
697 returns a list of RequestedBundle objects that need to be generated and |
764 returns a list of RequestedBundle objects that need to be generated and |
698 uploaded.""" |
765 uploaded.""" |
699 create_bundles = [] |
766 create_bundles = [] |
|
767 delete_bundles = [] |
700 repo = repo.filtered(b"immutable") |
768 repo = repo.filtered(b"immutable") |
701 targets = repo.ui.configlist(b'clone-bundles', b'auto-generate.formats') |
769 targets = repo.ui.configlist(b'clone-bundles', b'auto-generate.formats') |
702 revs = len(repo.changelog) |
770 revs = len(repo.changelog) |
703 generic_data = { |
771 generic_data = { |
704 'revs': revs, |
772 'revs': revs, |
710 for t in targets: |
778 for t in targets: |
711 data = generic_data.copy() |
779 data = generic_data.copy() |
712 data['bundle_type'] = t |
780 data['bundle_type'] = t |
713 b = RequestedBundle(**data) |
781 b = RequestedBundle(**data) |
714 create_bundles.append(b) |
782 create_bundles.append(b) |
715 return create_bundles |
783 delete_bundles.extend(find_outdated_bundles(repo, bundles)) |
|
784 return create_bundles, delete_bundles |
716 |
785 |
717 |
786 |
718 def start_one_bundle(repo, bundle): |
787 def start_one_bundle(repo, bundle): |
719 """start the generation of a single bundle file |
788 """start the generation of a single bundle file |
720 |
789 |
757 def debugmakeclonebundles(ui, repo): |
826 def debugmakeclonebundles(ui, repo): |
758 """Internal command to auto-generate debug bundles""" |
827 """Internal command to auto-generate debug bundles""" |
759 requested_bundle = util.pickle.load(procutil.stdin) |
828 requested_bundle = util.pickle.load(procutil.stdin) |
760 procutil.stdin.close() |
829 procutil.stdin.close() |
761 |
830 |
|
831 collect_garbage(repo) |
|
832 |
762 fname = requested_bundle.suggested_filename |
833 fname = requested_bundle.suggested_filename |
763 fpath = repo.vfs.makedirs(b'tmp-bundles') |
834 fpath = repo.vfs.makedirs(b'tmp-bundles') |
764 fpath = repo.vfs.join(b'tmp-bundles', fname) |
835 fpath = repo.vfs.join(b'tmp-bundles', fname) |
765 bundle = requested_bundle.generating(fpath) |
836 bundle = requested_bundle.generating(fpath) |
766 update_bundle_list(repo, new_bundles=[bundle]) |
837 update_bundle_list(repo, new_bundles=[bundle]) |
776 |
847 |
777 def autobundle(tr): |
848 def autobundle(tr): |
778 repo = reporef() |
849 repo = reporef() |
779 assert repo is not None |
850 assert repo is not None |
780 bundles = read_auto_gen(repo) |
851 bundles = read_auto_gen(repo) |
781 new = auto_bundle_needed_actions(repo, bundles, b"%d_txn" % id(tr)) |
852 new, __ = auto_bundle_needed_actions(repo, bundles, b"%d_txn" % id(tr)) |
782 for data in new: |
853 for data in new: |
783 start_one_bundle(repo, data) |
854 start_one_bundle(repo, data) |
784 return None |
855 return None |
785 |
856 |
786 return autobundle |
857 return autobundle |