@@ -9,8 +9,8 @@ use std::{fs::File, io::Write, path::PathBuf};
9
9
10
10
use git2:: { Oid , Repository } ;
11
11
use serde:: { Deserialize , Serialize } ;
12
- use snafu:: { OptionExt , ResultExt as _, Snafu } ;
13
- use tracing_indicatif:: IndicatifLayer ;
12
+ use snafu:: { ensure , OptionExt , ResultExt as _, Snafu } ;
13
+ use tracing_indicatif:: { span_ext :: IndicatifSpanExt , IndicatifLayer } ;
14
14
use tracing_subscriber:: { layer:: SubscriberExt as _, util:: SubscriberInitExt as _} ;
15
15
16
16
#[ derive( clap:: Parser ) ]
@@ -149,6 +149,11 @@ enum Cmd {
149
149
/// Refs (such as tags and branches) will be resolved to commit IDs.
150
150
#[ clap( long) ]
151
151
base : String ,
152
+
153
+ /// Assume a fork exists at stackabletech/<repo_name> and push the base ref to it.
154
+ /// The fork URL will be stored in patchable.toml instead of the original upstream.
155
+ #[ clap( long) ]
156
+ forked : bool ,
152
157
} ,
153
158
154
159
/// Shows the patch directory for a given product version
@@ -197,6 +202,23 @@ pub enum Error {
197
202
path : PathBuf ,
198
203
} ,
199
204
205
+ #[ snafu( display( "failed to parse upstream URL {url:?} to extract repository name" ) ) ]
206
+ ParseUpstreamUrl { url : String } ,
207
+ #[ snafu( display( "failed to add temporary fork remote for {url:?}" ) ) ]
208
+ AddForkRemote { source : git2:: Error , url : String } ,
209
+ #[ snafu( display( "failed to push commit {commit} (as {refspec}) to fork {url:?}" ) ) ]
210
+ PushToFork {
211
+ source : git2:: Error ,
212
+ url : String ,
213
+ refspec : String ,
214
+ commit : Oid ,
215
+ } ,
216
+ #[ snafu( display( "failed to delete remote {name}" ) ) ]
217
+ DeleteRemote {
218
+ source : git2:: Error ,
219
+ name : String ,
220
+ } ,
221
+
200
222
#[ snafu( display( "failed to find images repository" ) ) ]
201
223
FindImagesRepo { source : repo:: Error } ,
202
224
#[ snafu( display( "images repository has no work directory" ) ) ]
@@ -287,7 +309,7 @@ fn main() -> Result<()> {
287
309
let base_commit = repo:: resolve_and_fetch_commitish (
288
310
& product_repo,
289
311
& config. base . to_string ( ) ,
290
- & config. upstream ,
312
+ & config. upstream
291
313
)
292
314
. context ( FetchBaseCommitSnafu ) ?;
293
315
let base_branch = ctx. base_branch ( ) ;
@@ -397,7 +419,12 @@ fn main() -> Result<()> {
397
419
) ;
398
420
}
399
421
400
- Cmd :: Init { pv, upstream, base } => {
422
+ Cmd :: Init {
423
+ pv,
424
+ upstream,
425
+ base,
426
+ forked,
427
+ } => {
401
428
let ctx = ProductVersionContext {
402
429
pv,
403
430
images_repo_root,
@@ -414,8 +441,94 @@ fn main() -> Result<()> {
414
441
// --base can be a reference, but patchable.toml should always have a resolved commit id,
415
442
// so that it cannot be changed under our feet (without us knowing so, anyway...).
416
443
tracing:: info!( ?base, "resolving base commit-ish" ) ;
417
- let base_commit = repo:: resolve_and_fetch_commitish ( & product_repo, & base, & upstream)
444
+
445
+ let ( base_commit, upstream) = if forked {
446
+ // Parse e.g. "https://github.com/apache/druid.git" into "druid"
447
+ let repo_name = upstream. split ( '/' ) . last ( ) . map ( |repo| repo. trim_end_matches ( ".git" ) ) . context ( ParseUpstreamUrlSnafu { url : & upstream } ) ?;
448
+
449
+ ensure ! ( !repo_name. is_empty( ) , ParseUpstreamUrlSnafu { url: & upstream } ) ;
450
+
451
+ let fork_url = format ! ( "https://github.com/stackabletech/{}.git" , repo_name) ;
452
+ tracing:: info!( %fork_url, "using fork repository" ) ;
453
+
454
+ // Fetch from original upstream using a temporary remote name
455
+ tracing:: info!( upstream = upstream, %base, "fetching base ref from original upstream" ) ;
456
+ let base_commit_oid = repo:: resolve_and_fetch_commitish (
457
+ & product_repo,
458
+ & base,
459
+ & upstream
460
+ )
418
461
. context ( FetchBaseCommitSnafu ) ?;
462
+
463
+ tracing:: info!( commit = %base_commit_oid, "fetched base commit OID" ) ;
464
+
465
+ // Add fork remote
466
+ let temp_fork_remote = "patchable_fork_push" ;
467
+ let mut fork_remote = product_repo
468
+ . remote ( temp_fork_remote, & fork_url)
469
+ . context ( AddForkRemoteSnafu { url : fork_url. clone ( ) } ) ?;
470
+
471
+ // Push the base commit to the fork
472
+ tracing:: info!( commit = %base_commit_oid, base = base, url = fork_url, "pushing commit to fork" ) ;
473
+ let mut callbacks = git2:: RemoteCallbacks :: new ( ) ;
474
+ callbacks. credentials ( |_url, username_from_url, _allowed_types| {
475
+ git2:: Cred :: credential_helper (
476
+ & git2:: Config :: open_default ( ) . unwrap ( ) , // Use default git config
477
+ _url,
478
+ username_from_url,
479
+ )
480
+ } ) ;
481
+
482
+ // Add progress tracking for push operation
483
+ let span_push = tracing:: info_span!( "pushing" ) ;
484
+ span_push. pb_set_style ( & utils:: progress_bar_style ( ) ) ;
485
+ let _ = span_push. enter ( ) ;
486
+ let mut quant_push = utils:: Quantizer :: percent ( ) ;
487
+ callbacks. push_transfer_progress ( move |current, total, _| {
488
+ if total > 0 {
489
+ quant_push. update_span_progress ( current, total, & span_push) ;
490
+ }
491
+ } ) ;
492
+
493
+ let mut push_options = git2:: PushOptions :: new ( ) ;
494
+ push_options. remote_callbacks ( callbacks) ;
495
+
496
+ // Check if the reference is a tag or branch by inspecting the git repository
497
+ let refspec = {
498
+ let tag_ref = format ! ( "refs/tags/{}" , base) ;
499
+ let is_tag = product_repo
500
+ . find_reference ( & tag_ref)
501
+ . is_ok ( ) ;
502
+
503
+ if is_tag {
504
+ // It's a tag
505
+ format ! ( "{}:refs/tags/{}" , base_commit_oid, base)
506
+ } else {
507
+ // Assume it's a branch as default behavior
508
+ format ! ( "{}:refs/heads/{}" , base_commit_oid, base)
509
+ }
510
+ } ;
511
+
512
+ tracing:: info!( refspec = refspec, "constructed push refspec" ) ;
513
+
514
+ fork_remote
515
+ . push ( & [ & refspec] , Some ( & mut push_options) )
516
+ . context ( PushToForkSnafu {
517
+ url : fork_url. clone ( ) ,
518
+ refspec : & refspec,
519
+ commit : base_commit_oid,
520
+ } ) ?;
521
+
522
+ product_repo. remote_delete ( temp_fork_remote)
523
+ . context ( DeleteRemoteSnafu { name : temp_fork_remote. to_string ( ) } ) ?;
524
+
525
+ tracing:: info!( "successfully pushed base ref to fork" ) ;
526
+
527
+ ( base_commit_oid, fork_url)
528
+ } else {
529
+ ( repo:: resolve_and_fetch_commitish ( & product_repo, & base, & upstream) . context ( FetchBaseCommitSnafu ) ?, upstream)
530
+ } ;
531
+
419
532
tracing:: info!( ?base, base. commit = ?base_commit, "resolved base commit" ) ;
420
533
421
534
tracing:: info!( "saving configuration" ) ;
0 commit comments