@@ -25,29 +25,72 @@ struct ProductVersion {
2525}
2626
2727#[ derive( Deserialize , Serialize ) ]
28- struct ProductVersionConfig {
28+ struct ProductConfig {
2929 upstream : String ,
30+ mirror : Option < String > ,
31+ }
32+
33+ #[ derive( Deserialize , Serialize ) ]
34+ struct ProductVersionConfig {
35+ upstream : Option < String > ,
3036 #[ serde( with = "utils::oid_serde" ) ]
3137 base : Oid ,
3238 mirror : Option < String > ,
3339}
3440
41+ struct MergedProductVersionConfig {
42+ upstream : String ,
43+ base : Oid ,
44+ mirror : Option < String > ,
45+ }
46+
3547struct ProductVersionContext {
3648 pv : ProductVersion ,
3749 images_repo_root : PathBuf ,
3850}
3951
4052impl ProductVersionContext {
41- fn load_config ( & self ) -> Result < ProductVersionConfig > {
42- let path = & self . config_path ( ) ;
53+ fn load_product_config ( & self ) -> Result < ProductConfig > {
54+ let product_config_path = & self . product_config_path ( ) ;
55+
4356 tracing:: info!(
44- config. path = ?path ,
45- "loading config"
57+ config. path = ?product_config_path ,
58+ "loading product-level config"
4659 ) ;
47- toml:: from_str :: < ProductVersionConfig > (
48- & std:: fs:: read_to_string ( path) . context ( LoadConfigSnafu { path } ) ?,
60+
61+ toml:: from_str :: < ProductConfig > ( & std:: fs:: read_to_string ( product_config_path) . context (
62+ LoadConfigSnafu {
63+ path : product_config_path,
64+ } ,
65+ ) ?)
66+ . context ( ParseConfigSnafu {
67+ path : product_config_path,
68+ } )
69+ }
70+
71+ fn load_version_config ( & self ) -> Result < MergedProductVersionConfig > {
72+ // Load product-level config (required)
73+ let product_config = self . load_product_config ( ) ?;
74+
75+ // Load version-level config (optional)
76+ let version_config_path = & self . version_config_path ( ) ;
77+ let loaded_version_config = toml:: from_str :: < ProductVersionConfig > (
78+ & std:: fs:: read_to_string ( version_config_path) . context ( LoadConfigSnafu {
79+ path : version_config_path,
80+ } ) ?,
4981 )
50- . context ( ParseConfigSnafu { path } )
82+ . context ( ParseConfigSnafu {
83+ path : version_config_path,
84+ } ) ?;
85+
86+ // Inherit `upstream` and `mirror` from product-level config if not set in loaded version-level config
87+ Ok ( MergedProductVersionConfig {
88+ upstream : loaded_version_config
89+ . upstream
90+ . unwrap_or ( product_config. upstream ) ,
91+ base : loaded_version_config. base ,
92+ mirror : loaded_version_config. mirror . or ( product_config. mirror ) ,
93+ } )
5194 }
5295
5396 /// The root directory for files related to the product (across all versions).
@@ -63,10 +106,15 @@ impl ProductVersionContext {
63106 }
64107
65108 /// The patchable configuration file for the product version.
66- fn config_path ( & self ) -> PathBuf {
109+ fn version_config_path ( & self ) -> PathBuf {
67110 self . patch_dir ( ) . join ( "patchable.toml" )
68111 }
69112
113+ /// The product-level patchable configuration file
114+ fn product_config_path ( & self ) -> PathBuf {
115+ self . product_dir ( ) . join ( "stackable/patches/patchable.toml" )
116+ }
117+
70118 /// The directory containing all ephemeral data used by patchable for the product (across all versions).
71119 ///
72120 /// Should be gitignored, and can safely be deleted as long as all relevant versions have been `patchable export`ed.
@@ -141,20 +189,11 @@ enum Cmd {
141189 #[ clap( flatten) ]
142190 pv : ProductVersion ,
143191
144- /// The upstream URL (such as https://github.com/apache/druid.git)
145- #[ clap( long) ]
146- upstream : String ,
147-
148192 /// The upstream commit-ish (such as druid-28.0.0) that the patch series applies to
149193 ///
150194 /// Refs (such as tags and branches) will be resolved to commit IDs.
151195 #[ clap( long) ]
152196 base : String ,
153-
154- /// Assume a mirror exists at stackabletech/<repo_name> and push the base ref to it.
155- /// The mirror URL will be stored in patchable.toml, and used instead of the original upstream.
156- #[ clap( long) ]
157- mirror : bool ,
158197 } ,
159198
160199 /// Shows the patch directory for a given product version
@@ -203,8 +242,6 @@ pub enum Error {
203242 path : PathBuf ,
204243 } ,
205244
206- #[ snafu( display( "failed to parse upstream URL {url:?} to extract repository name" ) ) ]
207- ParseUpstreamUrl { url : String } ,
208245 #[ snafu( display( "failed to add temporary mirror remote for {url:?}" ) ) ]
209246 AddMirrorRemote { source : git2:: Error , url : String } ,
210247 #[ snafu( display( "failed to push commit {commit} (as {refspec}) to mirror {url:?}" ) ) ]
@@ -297,15 +334,18 @@ fn main() -> Result<()> {
297334 pv,
298335 images_repo_root,
299336 } ;
300- let config = ctx. load_config ( ) ?;
337+ let config = ctx. load_version_config ( ) ?;
301338 let product_repo_root = ctx. product_repo ( ) ;
302339 let product_repo = repo:: ensure_bare_repo ( & product_repo_root)
303340 . context ( OpenProductRepoForCheckoutSnafu ) ?;
304341
305342 let base_commit = repo:: resolve_and_fetch_commitish (
306343 & product_repo,
307344 & config. base . to_string ( ) ,
308- config. mirror . as_deref ( ) . unwrap_or ( & config. upstream ) ,
345+ config
346+ . mirror
347+ . as_deref ( )
348+ . unwrap_or ( & config. upstream ) ,
309349 )
310350 . context ( FetchBaseCommitSnafu ) ?;
311351 let base_branch = ctx. base_branch ( ) ;
@@ -366,7 +406,7 @@ fn main() -> Result<()> {
366406 pv,
367407 images_repo_root,
368408 } ;
369- let config = ctx. load_config ( ) ?;
409+ let config = ctx. load_version_config ( ) ?;
370410
371411 let product_worktree_root = ctx. worktree_root ( ) ;
372412 tracing:: info!(
@@ -415,12 +455,7 @@ fn main() -> Result<()> {
415455 ) ;
416456 }
417457
418- Cmd :: Init {
419- pv,
420- upstream,
421- base,
422- mirror,
423- } => {
458+ Cmd :: Init { pv, base } => {
424459 let ctx = ProductVersionContext {
425460 pv,
426461 images_repo_root,
@@ -434,77 +469,74 @@ fn main() -> Result<()> {
434469 . in_scope ( || repo:: ensure_bare_repo ( & product_repo_root) )
435470 . context ( OpenProductRepoForCheckoutSnafu ) ?;
436471
472+ let config = ctx. load_product_config ( ) ?;
473+ let upstream = config. upstream ;
474+
437475 // --base can be a reference, but patchable.toml should always have a resolved commit id,
438476 // so that it cannot be changed under our feet (without us knowing so, anyway...).
439477 tracing:: info!( ?base, "resolving base commit-ish" ) ;
440- let base_commit = repo:: resolve_and_fetch_commitish ( & product_repo, & base, & upstream) . context ( FetchBaseCommitSnafu ) ?;
441- let mut upstream_mirror = None ;
442-
443- if mirror {
444- // Parse e.g. "https://github.com/apache/druid.git" into "druid"
445- let repo_name = upstream. split ( '/' ) . last ( ) . map ( |repo| repo. trim_end_matches ( ".git" ) ) . filter ( |name| !name. is_empty ( ) ) . context ( ParseUpstreamUrlSnafu { url : & upstream } ) ?;
446-
447- let mirror_url = format ! ( "https://github.com/stackabletech/{repo_name}.git" ) ;
448- tracing:: info!( mirror_url, "using mirror repository" ) ;
478+ let base_commit = repo:: resolve_and_fetch_commitish ( & product_repo, & base, & upstream)
479+ . context ( FetchBaseCommitSnafu ) ?;
449480
481+ if let Some ( mirror_url) = config. mirror {
450482 // Add mirror remote
451- let mut mirror_remote = product_repo
452- . remote_anonymous ( & mirror_url)
453- . context ( AddMirrorRemoteSnafu { url : mirror_url. clone ( ) } ) ?;
483+ let mut mirror_remote =
484+ product_repo
485+ . remote_anonymous ( & mirror_url)
486+ . context ( AddMirrorRemoteSnafu {
487+ url : mirror_url. clone ( ) ,
488+ } ) ?;
454489
455490 // Push the base commit to the mirror
456491 tracing:: info!( commit = %base_commit, base = base, url = mirror_url, "pushing commit to mirror" ) ;
457492 let mut callbacks = git2:: RemoteCallbacks :: new ( ) ;
458493 callbacks. credentials ( |url, username_from_url, _allowed_types| {
459494 git2:: Cred :: credential_helper (
460- & git2:: Config :: open_default ( ) . unwrap ( ) , // Use default git config
495+ & git2:: Config :: open_default ( )
496+ . expect ( "failed to open default Git configuration" ) , // Use default git config,
461497 url,
462498 username_from_url,
463499 )
464500 } ) ;
465501
466502 // Add progress tracking for push operation
467- let ( span_push, mut quant_push) = utils:: setup_progress_tracking ( tracing:: info_span!( "pushing" ) ) ;
503+ let ( span_push, mut quant_push) =
504+ utils:: setup_progress_tracking ( tracing:: info_span!( "pushing" ) ) ;
468505 let _ = span_push. enter ( ) ;
469506
470507 callbacks. push_transfer_progress ( move |current, total, _| {
471508 if total > 0 {
472- quant_push. update_span_progress (
473- current,
474- total,
475- & span_push,
476- ) ;
509+ quant_push. update_span_progress ( current, total, & span_push) ;
477510 }
478511 } ) ;
479512
480513 let mut push_options = git2:: PushOptions :: new ( ) ;
481514 push_options. remote_callbacks ( callbacks) ;
482515
516+ // Always push the commit as a Git tag named like the value of `base`
483517 let refspec = format ! ( "{base_commit}:refs/tags/{base}" ) ;
484518 tracing:: info!( refspec, "constructed push refspec" ) ;
485519
486520 mirror_remote
487521 . push ( & [ & refspec] , Some ( & mut push_options) )
488522 . context ( PushToMirrorSnafu {
489- url : & mirror_url,
523+ url : mirror_url. clone ( ) ,
490524 refspec : & refspec,
491525 commit : base_commit,
492526 } ) ?;
493527
494528 tracing:: info!( "successfully pushed base ref to mirror" ) ;
495-
496- upstream_mirror = Some ( mirror_url) ;
497529 } ;
498530
499531 tracing:: info!( ?base, base. commit = ?base_commit, "resolved base commit" ) ;
500532
501- tracing:: info!( "saving configuration" ) ;
533+ tracing:: info!( "saving version-level configuration" ) ;
502534 let config = ProductVersionConfig {
503- upstream,
504- mirror : upstream_mirror ,
535+ upstream : None ,
536+ mirror : None ,
505537 base : base_commit,
506538 } ;
507- let config_path = ctx. config_path ( ) ;
539+ let config_path = ctx. version_config_path ( ) ;
508540 if let Some ( config_dir) = config_path. parent ( ) {
509541 std:: fs:: create_dir_all ( config_dir)
510542 . context ( CreatePatchDirSnafu { path : config_dir } ) ?;
0 commit comments