@@ -39,6 +39,7 @@ struct ProductConfig {
3939 ///
4040 /// This value is _not_ used by `checkout`, that uses [`ProductVersionConfig::mirror`] instead.
4141 /// `init --mirror` copies this value into [`ProductVersionConfig::mirror`].
42+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
4243 default_mirror : Option < String > ,
4344}
4445
@@ -180,24 +181,10 @@ enum Cmd {
180181 pv : ProductVersion ,
181182 } ,
182183
183- /// Creates a patchable.toml for a given product version
184+ /// Creates patchable.toml configuration files
184185 Init {
185- #[ clap( flatten) ]
186- pv : ProductVersion ,
187-
188- /// The upstream commit-ish (such as druid-28.0.0) that the patch series applies to
189- ///
190- /// Refs (such as tags and branches) will be resolved to commit IDs.
191- #[ clap( long) ]
192- base : String ,
193-
194- /// Mirror the product version to the default mirror repository
195- #[ clap( long) ]
196- mirror : bool ,
197-
198- /// Use SSH for git operations
199- #[ clap( long) ]
200- ssh : bool ,
186+ #[ clap( subcommand) ]
187+ init_type : InitType ,
201188 } ,
202189
203190 /// Shows the patch directory for a given product version
@@ -218,6 +205,41 @@ enum Cmd {
218205 ImagesDir ,
219206}
220207
208+ #[ derive( clap:: Parser ) ]
209+ enum InitType {
210+ /// Creates a patchable.toml for a given product
211+ Product {
212+ /// The product name slug (such as druid)
213+ product : String ,
214+ /// The upstream repository URL (e.g. https://github.com/apache/druid.git)
215+ #[ clap( long) ]
216+ upstream : String ,
217+ /// The default mirror repository URL (e.g. https://github.com/stackabletech/druid.git)
218+ #[ clap( long) ]
219+ default_mirror : Option < String > ,
220+ } ,
221+
222+ /// Creates a patchable.toml for a given product version
223+ Version {
224+ #[ clap( flatten) ]
225+ pv : ProductVersion ,
226+
227+ /// The upstream commit-ish (such as druid-28.0.0) that the patch series applies to
228+ ///
229+ /// Refs (such as tags and branches) will be resolved to commit IDs.
230+ #[ clap( long) ]
231+ base : String ,
232+
233+ /// Mirror the product version to the default mirror repository
234+ #[ clap( long) ]
235+ mirror : bool ,
236+
237+ /// Use SSH for git operations
238+ #[ clap( long) ]
239+ ssh : bool ,
240+ } ,
241+ }
242+
221243#[ derive( Debug , Snafu ) ]
222244pub enum Error {
223245 #[ snafu( display( "failed to configure git logging" ) ) ]
@@ -474,116 +496,167 @@ fn main() -> Result<()> {
474496 ) ;
475497 }
476498
477- Cmd :: Init {
478- pv,
479- base,
480- mirror,
481- ssh,
482- } => {
483- let ctx = ProductVersionContext {
484- pv,
485- images_repo_root,
486- } ;
499+ Cmd :: Init { init_type } => match init_type {
500+ InitType :: Product {
501+ product,
502+ upstream,
503+ default_mirror,
504+ } => {
505+ let product_config_path = ProductVersionContext {
506+ pv : ProductVersion {
507+ product : product. clone ( ) ,
508+ version : "" . to_string ( ) ,
509+ } ,
510+ images_repo_root,
511+ }
512+ . product_config_path ( ) ;
487513
488- let product_repo_root = ctx. product_repo ( ) ;
489- let product_repo = tracing:: info_span!(
490- "finding product repository" ,
491- product. repository = ?product_repo_root,
492- )
493- . in_scope ( || repo:: ensure_bare_repo ( & product_repo_root) )
494- . context ( OpenProductRepoForCheckoutSnafu ) ?;
514+ tracing:: info!(
515+ path = ?product_config_path,
516+ "creating product configuration directory and file"
517+ ) ;
495518
496- let config = ctx. load_product_config ( ) ?;
497- let upstream = if ssh {
498- utils:: rewrite_git_https_url_to_ssh ( & config. upstream ) . context ( UrlRewriteSnafu ) ?
499- } else {
500- config. upstream
501- } ;
519+ let product_config_dir = product_config_path
520+ . parent ( )
521+ . expect ( "product config should have a hard-coded parent" ) ;
522+
523+ std:: fs:: create_dir_all ( product_config_dir) . context ( CreatePatchDirSnafu {
524+ path : product_config_dir,
525+ } ) ?;
502526
503- // --base can be a reference, but patchable.toml should always have a resolved commit id,
504- // so that it cannot be changed under our feet (without us knowing so, anyway...).
505- tracing:: info!( ?base, "resolving base commit-ish" ) ;
506- let base_commit = repo:: resolve_and_fetch_commitish ( & product_repo, & base, & upstream)
507- . context ( FetchBaseCommitSnafu ) ?;
508- tracing:: info!( ?base, base. commit = ?base_commit, "resolved base commit" ) ;
509-
510- let mirror_url = if mirror {
511- let mut mirror_url = config
512- . default_mirror
513- . context ( InitMirrorNotConfiguredSnafu ) ?;
514- if ssh {
515- mirror_url =
516- utils:: rewrite_git_https_url_to_ssh ( & mirror_url) . context ( UrlRewriteSnafu ) ?
527+ let product_config = ProductConfig {
528+ upstream,
529+ default_mirror,
517530 } ;
518- // Add mirror remote
519- let mut mirror_remote =
520- product_repo
521- . remote_anonymous ( & mirror_url)
522- . context ( AddMirrorRemoteSnafu {
523- url : mirror_url. clone ( ) ,
524- } ) ?;
525531
526- // Push the base commit to the mirror
527- tracing:: info!( commit = %base_commit, base = base, url = mirror_url, "pushing commit to mirror" ) ;
528- let mut callbacks = setup_git_credentials ( ) ;
532+ let config_toml =
533+ toml:: to_string_pretty ( & product_config) . context ( SerializeConfigSnafu ) ?;
534+ File :: create_new ( & product_config_path)
535+ . and_then ( |mut f| f. write_all ( config_toml. as_bytes ( ) ) )
536+ . context ( SaveConfigSnafu {
537+ path : & product_config_path,
538+ } ) ?;
539+
540+ tracing:: info!(
541+ config. path = ?product_config_path,
542+ product = product,
543+ "created configuration for product"
544+ ) ;
545+ }
529546
530- // Add progress tracking for push operation
531- let ( span_push, mut quant_push) =
532- utils:: setup_progress_tracking ( tracing:: info_span!( "pushing" ) ) ;
533- let _ = span_push. enter ( ) ;
547+ InitType :: Version {
548+ pv,
549+ base,
550+ mirror,
551+ ssh,
552+ } => {
553+ let ctx = ProductVersionContext {
554+ pv,
555+ images_repo_root,
556+ } ;
534557
535- callbacks. push_transfer_progress ( move |current, total, _| {
536- if total > 0 {
537- quant_push. update_span_progress ( current, total, & span_push) ;
538- }
539- } ) ;
558+ let product_repo_root = ctx. product_repo ( ) ;
559+ let product_repo = tracing:: info_span!(
560+ "finding product repository" ,
561+ product. repository = ?product_repo_root,
562+ )
563+ . in_scope ( || repo:: ensure_bare_repo ( & product_repo_root) )
564+ . context ( OpenProductRepoForCheckoutSnafu ) ?;
540565
541- let mut push_options = git2:: PushOptions :: new ( ) ;
542- push_options. remote_callbacks ( callbacks) ;
566+ let config = ctx. load_product_config ( ) ?;
567+ let upstream = if ssh {
568+ utils:: rewrite_git_https_url_to_ssh ( & config. upstream )
569+ . context ( UrlRewriteSnafu ) ?
570+ } else {
571+ config. upstream
572+ } ;
543573
544- // Always push the commit as a Git tag named like the value of `base`
545- let refspec = format ! ( "{base_commit}:refs/tags/{base}" ) ;
546- tracing:: info!( refspec, "constructed push refspec" ) ;
574+ // --base can be a reference, but patchable.toml should always have a resolved commit id,
575+ // so that it cannot be changed under our feet (without us knowing so, anyway...).
576+ tracing:: info!( ?base, "resolving base commit-ish" ) ;
577+ let base_commit =
578+ repo:: resolve_and_fetch_commitish ( & product_repo, & base, & upstream)
579+ . context ( FetchBaseCommitSnafu ) ?;
580+ tracing:: info!( ?base, base. commit = ?base_commit, "resolved base commit" ) ;
581+
582+ let mirror_url = if mirror {
583+ let mut mirror_url = config
584+ . default_mirror
585+ . context ( InitMirrorNotConfiguredSnafu ) ?;
586+ if ssh {
587+ mirror_url = utils:: rewrite_git_https_url_to_ssh ( & mirror_url)
588+ . context ( UrlRewriteSnafu ) ?
589+ } ;
590+ // Add mirror remote
591+ let mut mirror_remote = product_repo. remote_anonymous ( & mirror_url) . context (
592+ AddMirrorRemoteSnafu {
593+ url : mirror_url. clone ( ) ,
594+ } ,
595+ ) ?;
596+
597+ // Push the base commit to the mirror
598+ tracing:: info!( commit = %base_commit, base = base, url = mirror_url, "pushing commit to mirror" ) ;
599+ let mut callbacks = setup_git_credentials ( ) ;
600+
601+ // Add progress tracking for push operation
602+ let ( span_push, mut quant_push) =
603+ utils:: setup_progress_tracking ( tracing:: info_span!( "pushing" ) ) ;
604+ let _ = span_push. enter ( ) ;
605+
606+ callbacks. push_transfer_progress ( move |current, total, _| {
607+ if total > 0 {
608+ quant_push. update_span_progress ( current, total, & span_push) ;
609+ }
610+ } ) ;
611+
612+ let mut push_options = git2:: PushOptions :: new ( ) ;
613+ push_options. remote_callbacks ( callbacks) ;
614+
615+ // Always push the commit as a Git tag named like the value of `base`
616+ let refspec = format ! ( "{base_commit}:refs/tags/{base}" ) ;
617+ tracing:: info!( refspec, "constructed push refspec" ) ;
618+
619+ mirror_remote
620+ . push ( & [ & refspec] , Some ( & mut push_options) )
621+ . context ( PushToMirrorSnafu {
622+ url : & mirror_url,
623+ refspec : & refspec,
624+ commit : base_commit,
625+ } ) ?;
547626
548- mirror_remote
549- . push ( & [ & refspec] , Some ( & mut push_options) )
550- . context ( PushToMirrorSnafu {
551- url : & mirror_url,
552- refspec : & refspec,
553- commit : base_commit,
554- } ) ?;
627+ tracing:: info!( "successfully pushed base ref to mirror" ) ;
628+ Some ( mirror_url)
629+ } else {
630+ tracing:: warn!(
631+ "this version is not mirrored, re-run with --mirror before merging into main"
632+ ) ;
633+ None
634+ } ;
555635
556- tracing:: info!( "successfully pushed base ref to mirror" ) ;
557- Some ( mirror_url)
558- } else {
559- tracing:: warn!(
560- "this version is not mirrored, re-run with --mirror before merging into main"
561- ) ;
562- None
563- } ;
636+ tracing:: info!( "saving version-level configuration" ) ;
637+ let config = ProductVersionConfig {
638+ base : base_commit,
639+ mirror : mirror_url,
640+ } ;
641+ let config_path = ctx. version_config_path ( ) ;
642+ if let Some ( config_dir) = config_path. parent ( ) {
643+ std:: fs:: create_dir_all ( config_dir)
644+ . context ( CreatePatchDirSnafu { path : config_dir } ) ?;
645+ }
564646
565- tracing:: info!( "saving version-level configuration" ) ;
566- let config = ProductVersionConfig {
567- base : base_commit,
568- mirror : mirror_url,
569- } ;
570- let config_path = ctx. version_config_path ( ) ;
571- if let Some ( config_dir) = config_path. parent ( ) {
572- std:: fs:: create_dir_all ( config_dir)
573- . context ( CreatePatchDirSnafu { path : config_dir } ) ?;
574- }
575- let config_toml = toml:: to_string_pretty ( & config) . context ( SerializeConfigSnafu ) ?;
576- File :: create_new ( & config_path)
577- . and_then ( |mut f| f. write_all ( config_toml. as_bytes ( ) ) )
578- . context ( SaveConfigSnafu { path : & config_path } ) ?;
647+ let config_toml = toml:: to_string_pretty ( & config) . context ( SerializeConfigSnafu ) ?;
648+ File :: create_new ( & config_path)
649+ . and_then ( |mut f| f. write_all ( config_toml. as_bytes ( ) ) )
650+ . context ( SaveConfigSnafu { path : & config_path } ) ?;
579651
580- tracing:: info!(
581- config. path = ?config_path,
582- product = ctx. pv. product,
583- version = ctx. pv. version,
584- "created configuration for product version"
585- ) ;
586- }
652+ tracing:: info!(
653+ config. path = ?config_path,
654+ product = ctx. pv. product,
655+ version = ctx. pv. version,
656+ "created configuration for product version"
657+ ) ;
658+ }
659+ } ,
587660
588661 Cmd :: PatchDir { pv } => {
589662 let ctx = ProductVersionContext {
0 commit comments