@@ -25,29 +25,72 @@ struct ProductVersion {
25
25
}
26
26
27
27
#[ derive( Deserialize , Serialize ) ]
28
- struct ProductVersionConfig {
28
+ struct ProductConfig {
29
29
upstream : String ,
30
+ mirror : Option < String > ,
31
+ }
32
+
33
+ #[ derive( Deserialize , Serialize ) ]
34
+ struct ProductVersionConfig {
35
+ upstream : Option < String > ,
30
36
#[ serde( with = "utils::oid_serde" ) ]
31
37
base : Oid ,
32
38
mirror : Option < String > ,
33
39
}
34
40
41
+ struct MergedProductVersionConfig {
42
+ upstream : String ,
43
+ base : Oid ,
44
+ mirror : Option < String > ,
45
+ }
46
+
35
47
struct ProductVersionContext {
36
48
pv : ProductVersion ,
37
49
images_repo_root : PathBuf ,
38
50
}
39
51
40
52
impl 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
+
43
56
tracing:: info!(
44
- config. path = ?path ,
45
- "loading config"
57
+ config. path = ?product_config_path ,
58
+ "loading product-level config"
46
59
) ;
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
+ } ) ?,
49
81
)
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
+ } )
51
94
}
52
95
53
96
/// The root directory for files related to the product (across all versions).
@@ -63,10 +106,15 @@ impl ProductVersionContext {
63
106
}
64
107
65
108
/// The patchable configuration file for the product version.
66
- fn config_path ( & self ) -> PathBuf {
109
+ fn version_config_path ( & self ) -> PathBuf {
67
110
self . patch_dir ( ) . join ( "patchable.toml" )
68
111
}
69
112
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
+
70
118
/// The directory containing all ephemeral data used by patchable for the product (across all versions).
71
119
///
72
120
/// 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 {
141
189
#[ clap( flatten) ]
142
190
pv : ProductVersion ,
143
191
144
- /// The upstream URL (such as https://github.com/apache/druid.git)
145
- #[ clap( long) ]
146
- upstream : String ,
147
-
148
192
/// The upstream commit-ish (such as druid-28.0.0) that the patch series applies to
149
193
///
150
194
/// Refs (such as tags and branches) will be resolved to commit IDs.
151
195
#[ clap( long) ]
152
196
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 ,
158
197
} ,
159
198
160
199
/// Shows the patch directory for a given product version
@@ -203,8 +242,6 @@ pub enum Error {
203
242
path : PathBuf ,
204
243
} ,
205
244
206
- #[ snafu( display( "failed to parse upstream URL {url:?} to extract repository name" ) ) ]
207
- ParseUpstreamUrl { url : String } ,
208
245
#[ snafu( display( "failed to add temporary mirror remote for {url:?}" ) ) ]
209
246
AddMirrorRemote { source : git2:: Error , url : String } ,
210
247
#[ snafu( display( "failed to push commit {commit} (as {refspec}) to mirror {url:?}" ) ) ]
@@ -297,15 +334,18 @@ fn main() -> Result<()> {
297
334
pv,
298
335
images_repo_root,
299
336
} ;
300
- let config = ctx. load_config ( ) ?;
337
+ let config = ctx. load_version_config ( ) ?;
301
338
let product_repo_root = ctx. product_repo ( ) ;
302
339
let product_repo = repo:: ensure_bare_repo ( & product_repo_root)
303
340
. context ( OpenProductRepoForCheckoutSnafu ) ?;
304
341
305
342
let base_commit = repo:: resolve_and_fetch_commitish (
306
343
& product_repo,
307
344
& 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 ) ,
309
349
)
310
350
. context ( FetchBaseCommitSnafu ) ?;
311
351
let base_branch = ctx. base_branch ( ) ;
@@ -366,7 +406,7 @@ fn main() -> Result<()> {
366
406
pv,
367
407
images_repo_root,
368
408
} ;
369
- let config = ctx. load_config ( ) ?;
409
+ let config = ctx. load_version_config ( ) ?;
370
410
371
411
let product_worktree_root = ctx. worktree_root ( ) ;
372
412
tracing:: info!(
@@ -415,12 +455,7 @@ fn main() -> Result<()> {
415
455
) ;
416
456
}
417
457
418
- Cmd :: Init {
419
- pv,
420
- upstream,
421
- base,
422
- mirror,
423
- } => {
458
+ Cmd :: Init { pv, base } => {
424
459
let ctx = ProductVersionContext {
425
460
pv,
426
461
images_repo_root,
@@ -434,77 +469,74 @@ fn main() -> Result<()> {
434
469
. in_scope ( || repo:: ensure_bare_repo ( & product_repo_root) )
435
470
. context ( OpenProductRepoForCheckoutSnafu ) ?;
436
471
472
+ let config = ctx. load_product_config ( ) ?;
473
+ let upstream = config. upstream ;
474
+
437
475
// --base can be a reference, but patchable.toml should always have a resolved commit id,
438
476
// so that it cannot be changed under our feet (without us knowing so, anyway...).
439
477
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 ) ?;
449
480
481
+ if let Some ( mirror_url) = config. mirror {
450
482
// 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
+ } ) ?;
454
489
455
490
// Push the base commit to the mirror
456
491
tracing:: info!( commit = %base_commit, base = base, url = mirror_url, "pushing commit to mirror" ) ;
457
492
let mut callbacks = git2:: RemoteCallbacks :: new ( ) ;
458
493
callbacks. credentials ( |url, username_from_url, _allowed_types| {
459
494
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,
461
497
url,
462
498
username_from_url,
463
499
)
464
500
} ) ;
465
501
466
502
// 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" ) ) ;
468
505
let _ = span_push. enter ( ) ;
469
506
470
507
callbacks. push_transfer_progress ( move |current, total, _| {
471
508
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) ;
477
510
}
478
511
} ) ;
479
512
480
513
let mut push_options = git2:: PushOptions :: new ( ) ;
481
514
push_options. remote_callbacks ( callbacks) ;
482
515
516
+ // Always push the commit as a Git tag named like the value of `base`
483
517
let refspec = format ! ( "{base_commit}:refs/tags/{base}" ) ;
484
518
tracing:: info!( refspec, "constructed push refspec" ) ;
485
519
486
520
mirror_remote
487
521
. push ( & [ & refspec] , Some ( & mut push_options) )
488
522
. context ( PushToMirrorSnafu {
489
- url : & mirror_url,
523
+ url : mirror_url. clone ( ) ,
490
524
refspec : & refspec,
491
525
commit : base_commit,
492
526
} ) ?;
493
527
494
528
tracing:: info!( "successfully pushed base ref to mirror" ) ;
495
-
496
- upstream_mirror = Some ( mirror_url) ;
497
529
} ;
498
530
499
531
tracing:: info!( ?base, base. commit = ?base_commit, "resolved base commit" ) ;
500
532
501
- tracing:: info!( "saving configuration" ) ;
533
+ tracing:: info!( "saving version-level configuration" ) ;
502
534
let config = ProductVersionConfig {
503
- upstream,
504
- mirror : upstream_mirror ,
535
+ upstream : None ,
536
+ mirror : None ,
505
537
base : base_commit,
506
538
} ;
507
- let config_path = ctx. config_path ( ) ;
539
+ let config_path = ctx. version_config_path ( ) ;
508
540
if let Some ( config_dir) = config_path. parent ( ) {
509
541
std:: fs:: create_dir_all ( config_dir)
510
542
. context ( CreatePatchDirSnafu { path : config_dir } ) ?;
0 commit comments