@@ -11,8 +11,9 @@ use flate2::write::GzEncoder;
11
11
use flate2:: Compression ;
12
12
use futures:: prelude:: * ;
13
13
use indicatif:: ProgressBar ;
14
- use std:: collections:: HashSet ;
14
+ use std:: collections:: { HashMap , HashSet , VecDeque } ;
15
15
16
+ use std:: hash:: Hash ;
16
17
use std:: io:: { BufReader , Read } ;
17
18
use std:: sync:: Arc ;
18
19
@@ -301,7 +302,7 @@ async fn get_commit_objects_to_sync(
301
302
branch : & Branch ,
302
303
) -> Result < Vec < Commit > , OxenError > {
303
304
let remote_branch = api:: remote:: branches:: get_by_name ( remote_repo, & branch. name ) . await ?;
304
-
305
+ let commit_reader = CommitReader :: new ( local_repo ) ? ;
305
306
let mut commits_to_sync: Vec < Commit > ;
306
307
// TODO: If remote branch does not yet, recreates all commits regardless of shared history.
307
308
// Not a huge deal performance-wise right now, but could be for very commit-heavy repos
@@ -312,7 +313,7 @@ async fn get_commit_objects_to_sync(
312
313
let remote_commit = api:: remote:: commits:: get_by_id ( remote_repo, & remote_branch. commit_id )
313
314
. await ?
314
315
. unwrap ( ) ;
315
- let commit_reader = CommitReader :: new ( local_repo ) ? ;
316
+
316
317
let merger = Merger :: new ( local_repo) ?;
317
318
commits_to_sync =
318
319
merger. list_commits_between_commits ( & commit_reader, & remote_commit, local_commit) ?;
@@ -335,7 +336,36 @@ async fn get_commit_objects_to_sync(
335
336
} else {
336
337
// Branch does not exist on remote yet - get all commits?
337
338
log:: debug!( "get_commit_objects_to_sync remote branch does not exist, getting all commits from local head" ) ;
338
- commits_to_sync = api:: local:: commits:: list_from ( local_repo, & local_commit. id ) ?;
339
+
340
+ // Do the branch thing again
341
+ let branches: Vec < Branch > = api:: remote:: branches:: list ( remote_repo) . await ?;
342
+ let maybe_remote_head =
343
+ find_local_commit_matching_remote_branch ( local_repo, local_commit, & branches) ?;
344
+
345
+ if let Some ( remote_head) = maybe_remote_head {
346
+ let merger = Merger :: new ( local_repo) ?;
347
+ commits_to_sync =
348
+ merger. list_commits_between_commits ( & commit_reader, & remote_head, local_commit) ?;
349
+
350
+ println ! ( "🐂 Getting commit history..." ) ;
351
+ let remote_history =
352
+ api:: remote:: commits:: list_commit_history ( remote_repo, & branch. name )
353
+ . await
354
+ . unwrap_or_else ( |_| vec ! [ ] ) ;
355
+ log:: debug!(
356
+ "get_commit_objects_to_sync calculated {} commits" ,
357
+ commits_to_sync. len( )
358
+ ) ;
359
+
360
+ // Filter out any commits_to_sync that are in the remote_history
361
+ commits_to_sync. retain ( |commit| {
362
+ !remote_history
363
+ . iter ( )
364
+ . any ( |remote_commit| remote_commit. id == commit. id )
365
+ } ) ;
366
+ } else {
367
+ commits_to_sync = api:: local:: commits:: list_from ( local_repo, & local_commit. id ) ?;
368
+ }
339
369
}
340
370
341
371
// Order from BASE to HEAD
@@ -474,6 +504,41 @@ async fn remote_is_ahead_of_local(
474
504
Ok ( !local_head. has_ancestor ( & remote_commit. id , reader) ?)
475
505
}
476
506
507
+ fn find_local_commit_matching_remote_branch (
508
+ local_repo : & LocalRepository ,
509
+ local_head : & Commit ,
510
+ remote_branches : & Vec < Branch > ,
511
+ ) -> Result < Option < Commit > , OxenError > {
512
+ let commit_reader = CommitReader :: new ( local_repo) . unwrap ( ) ;
513
+ let mut branches_map: HashMap < String , String > = HashMap :: new ( ) ;
514
+ for remote_branch in remote_branches {
515
+ branches_map. insert ( remote_branch. commit_id . clone ( ) , remote_branch. name . clone ( ) ) ;
516
+ }
517
+ // let mut current_commit = local_head.clone();
518
+ let mut queue: VecDeque < Commit > = VecDeque :: new ( ) ;
519
+ queue. push_back ( local_head. clone ( ) ) ;
520
+
521
+ while queue. len ( ) > 0 {
522
+ let current_commit = queue. pop_front ( ) . unwrap ( ) ;
523
+ if branches_map. contains_key ( & current_commit. id ) {
524
+ log:: debug!(
525
+ "Found commit {:#?} as head of remote branch {:?}" ,
526
+ current_commit,
527
+ branches_map. get( & current_commit. id)
528
+ ) ;
529
+ return Ok ( Some ( current_commit) ) ;
530
+ }
531
+ for parent_id in current_commit. parent_ids . iter ( ) {
532
+ let parent_commit = commit_reader. get_commit_by_id ( parent_id) ?;
533
+ let Some ( parent_commit) = parent_commit else {
534
+ return Err ( OxenError :: local_parent_link_broken ( & current_commit. id ) ) ;
535
+ } ;
536
+ queue. push_back ( parent_commit) ;
537
+ }
538
+ }
539
+ Ok ( None )
540
+ }
541
+
477
542
async fn cannot_push_incomplete_history (
478
543
local_repo : & LocalRepository ,
479
544
remote_repo : & RemoteRepository ,
@@ -482,46 +547,70 @@ async fn cannot_push_incomplete_history(
482
547
) -> Result < bool , OxenError > {
483
548
log:: debug!( "Checking if we can push incomplete history." ) ;
484
549
match api:: remote:: commits:: list_commit_history ( remote_repo, & branch. name ) . await {
485
- Err ( _) => {
486
- return Ok ( !api:: local:: commits:: commit_history_is_complete (
487
- local_repo, local_head,
488
- ) ) ;
550
+ Err ( e) => {
551
+ log:: debug!( "got err when checking remote history {:?}" , e) ;
552
+ let remote_branches = api:: remote:: branches:: list ( remote_repo) . await ?;
553
+ log:: debug!( "got remote branches {:?}" , remote_branches) ;
554
+
555
+ let maybe_remote_head =
556
+ find_local_commit_matching_remote_branch ( local_repo, local_head, & remote_branches) ?;
557
+
558
+ log:: debug!( "got maybe_remote_head {:?}" , maybe_remote_head) ;
559
+
560
+ let Some ( remote_head) = maybe_remote_head else {
561
+ // No commits in the local branch history match any remote branch head.
562
+ // This is the empty repo case - require a complete history
563
+ return Ok ( !api:: local:: commits:: commit_history_is_complete (
564
+ local_repo, local_head,
565
+ ) ) ;
566
+ } ;
567
+
568
+ // Otherwise, we have a remote head that matches a local commit - get commits between and check if they're synced
569
+ return Ok ( !commits_to_push_are_synced (
570
+ local_repo,
571
+ local_head,
572
+ & remote_head,
573
+ ) ?) ;
489
574
}
490
575
Ok ( remote_history) => {
491
- let remote_head = remote_history. first ( ) . unwrap ( ) ;
492
576
log:: debug!(
493
- "Checking between local head {:?} and remote head {:?} on branch {}" ,
577
+ "got remote history {:#?} on branch {:#?}" ,
578
+ remote_history,
579
+ branch
580
+ ) ;
581
+ let remote_head = remote_history. first ( ) . unwrap ( ) ;
582
+ return Ok ( !commits_to_push_are_synced (
583
+ local_repo,
494
584
local_head,
495
585
remote_head,
496
- branch. name
497
- ) ;
586
+ ) ?) ;
587
+ }
588
+ }
589
+ }
498
590
499
- let commit_reader = CommitReader :: new ( local_repo) ?;
500
- let merger = Merger :: new ( local_repo) ?;
591
+ fn commits_to_push_are_synced (
592
+ local_repo : & LocalRepository ,
593
+ local_head : & Commit ,
594
+ remote_head : & Commit ,
595
+ ) -> Result < bool , OxenError > {
596
+ let commit_reader = CommitReader :: new ( local_repo) ?;
597
+ let merger = Merger :: new ( local_repo) ?;
501
598
502
- let commits_to_push =
503
- merger. list_commits_between_commits ( & commit_reader, remote_head, local_head) ?;
504
-
505
- let commits_to_push: Vec < Commit > = commits_to_push
506
- . into_iter ( )
507
- . filter ( |commit| {
508
- !remote_history
509
- . iter ( )
510
- . any ( |remote_commit| remote_commit. id == commit. id )
511
- } )
512
- . collect ( ) ;
513
-
514
- log:: debug!( "Found the following commits_to_push: {:?}" , commits_to_push) ;
515
- // Ensure all `commits_to_push` are synced
516
- for commit in commits_to_push {
517
- if !index:: commit_sync_status:: commit_is_synced ( local_repo, & commit) {
518
- return Ok ( true ) ;
519
- }
520
- }
599
+ let commits_to_push =
600
+ merger. list_commits_between_commits ( & commit_reader, remote_head, local_head) ?;
601
+
602
+ log:: debug!( "got commits to push {:?}" , commits_to_push) ;
603
+
604
+ for commit in commits_to_push {
605
+ if !index:: commit_sync_status:: commit_is_synced ( local_repo, & commit) {
606
+ log:: debug!( "commit is not synced {:?}" , commit) ;
607
+ return Ok ( false ) ;
521
608
}
522
609
}
523
610
524
- Ok ( false )
611
+ log:: debug!( "commits to push ARE synced!" ) ;
612
+
613
+ Ok ( true )
525
614
}
526
615
527
616
async fn poll_until_synced (
0 commit comments