|
1 | 1 | use crate::VirtualBranchesExt;
|
2 |
| -use anyhow::{Context, Result}; |
| 2 | +use anyhow::{bail, Context, Result}; |
3 | 3 | use bstr::{BStr, ByteSlice};
|
4 | 4 | use core::fmt;
|
5 | 5 | use gitbutler_branch::{
|
@@ -445,95 +445,120 @@ pub fn get_branch_listing_details(
|
445 | 445 | local_branch.name().as_bstr()
|
446 | 446 | )
|
447 | 447 | })??;
|
448 |
| - // TODO(ST): implement PartialName from `Cow<'_, FullNameRef>` |
449 |
| - let local_tracking_ref = repo.find_reference(local_tracking_ref_name.as_ref())?; |
| 448 | + let mut local_tracking_ref = repo.find_reference(local_tracking_ref_name.as_ref())?; |
450 | 449 | (
|
451 |
| - // TODO(ST): a way to peel to a specific object type, not just the first one. |
452 |
| - gix_to_git2_oid(local_tracking_ref.into_fully_peeled_id()?), |
| 450 | + gix_to_git2_oid(local_tracking_ref.peel_to_commit()?.id), |
453 | 451 | target.sha,
|
454 | 452 | )
|
455 | 453 | };
|
456 | 454 |
|
457 | 455 | let mut enriched_branches = Vec::new();
|
458 |
| - for branch in branches { |
459 |
| - let other_branch_commit_id = if let Some(virtual_branch) = branch.virtual_branch { |
460 |
| - if virtual_branch.in_workspace { |
461 |
| - default_target_seen_at_last_update |
| 456 | + let diffstats = { |
| 457 | + let (start, start_rx) = std::sync::mpsc::channel::<( |
| 458 | + std::sync::mpsc::Receiver<gix::object::tree::diff::ChangeDetached>, |
| 459 | + std::sync::mpsc::Sender<(usize, usize, usize)>, |
| 460 | + )>(); |
| 461 | + let diffstats = std::thread::Builder::new() |
| 462 | + .name("gitbutler-diff-stats".into()) |
| 463 | + .spawn({ |
| 464 | + let repo = repo.clone(); |
| 465 | + move || -> Result<()> { |
| 466 | + let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?; |
| 467 | + for (change_rx, res_tx) in start_rx { |
| 468 | + let (mut number_of_files, mut lines_added, mut lines_removed) = (0, 0, 0); |
| 469 | + for change in change_rx { |
| 470 | + if let Some(counts) = change |
| 471 | + .attach(&repo, &repo) |
| 472 | + .diff(&mut resource_cache) |
| 473 | + .ok() |
| 474 | + .and_then(|mut platform| platform.line_counts().ok()) |
| 475 | + .flatten() |
| 476 | + { |
| 477 | + number_of_files += 1; |
| 478 | + lines_added += counts.insertions as usize; |
| 479 | + lines_removed += counts.removals as usize; |
| 480 | + } |
| 481 | + // Let's not attempt to reuse the cache as it's only useful if we know the diff repeats |
| 482 | + // over different objects, like when doing rename tracking. |
| 483 | + resource_cache.clear_resource_cache_keep_allocation(); |
| 484 | + } |
| 485 | + if res_tx |
| 486 | + .send((number_of_files, lines_added, lines_removed)) |
| 487 | + .is_err() |
| 488 | + { |
| 489 | + break; |
| 490 | + } |
| 491 | + } |
| 492 | + Ok(()) |
| 493 | + } |
| 494 | + })?; |
| 495 | + for branch in branches { |
| 496 | + let other_branch_commit_id = if let Some(virtual_branch) = branch.virtual_branch { |
| 497 | + if virtual_branch.in_workspace { |
| 498 | + default_target_seen_at_last_update |
| 499 | + } else { |
| 500 | + default_target_current_upstream_commit_id |
| 501 | + } |
462 | 502 | } else {
|
463 | 503 | default_target_current_upstream_commit_id
|
| 504 | + }; |
| 505 | + let Ok(base) = git2_repo.merge_base(other_branch_commit_id, branch.head) else { |
| 506 | + continue; |
| 507 | + }; |
| 508 | + |
| 509 | + let branch_head = git2_to_gix_object_id(branch.head); |
| 510 | + let gix_base = git2_to_gix_object_id(base); |
| 511 | + let base_commit = repo.find_object(gix_base)?.try_into_commit()?; |
| 512 | + let base_tree = base_commit.tree()?; |
| 513 | + let head_tree = repo.find_object(branch_head)?.peel_to_tree()?; |
| 514 | + |
| 515 | + let ((change_tx, change_rx), (res_tx, rex_rx)) = |
| 516 | + (std::sync::mpsc::channel(), std::sync::mpsc::channel()); |
| 517 | + if start.send((change_rx, res_tx)).is_err() { |
| 518 | + bail!("diffing-thread crashed"); |
| 519 | + }; |
| 520 | + base_tree |
| 521 | + .changes()? |
| 522 | + .track_rewrites(None) |
| 523 | + // NOTE: `stats(head_tree)` is also possible, but we have a separate thread for that. |
| 524 | + .for_each_to_obtain_tree(&head_tree, move |change| -> anyhow::Result<Action> { |
| 525 | + change_tx.send(change.detach()).ok(); |
| 526 | + Ok(Action::Continue) |
| 527 | + })?; |
| 528 | + let (number_of_files, lines_added, lines_removed) = rex_rx.recv()?; |
| 529 | + // TODO(ST): make this API nicer, maybe have one that is not based on `ancestors()` but |
| 530 | + // similar to revwalk because it's so common? |
| 531 | + let revwalk = branch_head |
| 532 | + .attach(&repo) |
| 533 | + .ancestors() |
| 534 | + // When allowing to skip branches without a filter, make sure it automatically skips by date! |
| 535 | + .sorting( |
| 536 | + gix::traverse::commit::simple::Sorting::ByCommitTimeNewestFirstCutoffOlderThan { |
| 537 | + seconds: base_commit.time()?.seconds, |
| 538 | + }, |
| 539 | + ) |
| 540 | + .selected(|id| id != gix_base)?; |
| 541 | + let mut num_commits = 0; |
| 542 | + let mut authors = HashSet::new(); |
| 543 | + for commit_info in revwalk { |
| 544 | + let commit_info = commit_info?; |
| 545 | + let commit = repo.find_commit(commit_info.id)?; |
| 546 | + authors.insert(commit.author()?.into()); |
| 547 | + num_commits += 1; |
464 | 548 | }
|
465 |
| - } else { |
466 |
| - default_target_current_upstream_commit_id |
467 |
| - }; |
468 |
| - let Ok(base) = git2_repo.merge_base(other_branch_commit_id, branch.head) else { |
469 |
| - continue; |
470 |
| - }; |
471 |
| - |
472 |
| - let branch_head = git2_to_gix_object_id(branch.head); |
473 |
| - let gix_base = git2_to_gix_object_id(base); |
474 |
| - let base_commit = repo.find_object(gix_base)?.try_into_commit()?; |
475 |
| - let base_tree = base_commit.tree()?; |
476 |
| - let head_tree = repo.find_object(branch_head)?.peel_to_tree()?; |
477 |
| - // TODO(ST): make it easier to get a resource cache preconfigured for different purposes, |
478 |
| - // like tree-tree. Should probably be on the platform and separate? |
479 |
| - let mut resource_cache = repo.diff_resource_cache( |
480 |
| - gix::diff::blob::pipeline::Mode::ToGit, |
481 |
| - gix::diff::blob::pipeline::WorktreeRoots::default(), |
482 |
| - )?; |
483 |
| - let (mut number_of_files, mut lines_added, mut lines_removed) = (0, 0, 0); |
484 |
| - base_tree |
485 |
| - .changes()? |
486 |
| - .track_rewrites(None) |
487 |
| - // TODO(ST): definitely have `stats()` just like `git2`. |
488 |
| - .for_each_to_obtain_tree(&head_tree, |change| -> anyhow::Result<Action> { |
489 |
| - if let Some(counts) = change |
490 |
| - .diff(&mut resource_cache) |
491 |
| - .ok() |
492 |
| - .and_then(|mut platform| platform.line_counts().ok()) |
493 |
| - .flatten() |
494 |
| - { |
495 |
| - number_of_files += 1; |
496 |
| - lines_added += counts.insertions as usize; |
497 |
| - lines_removed += counts.removals as usize; |
498 |
| - } |
499 |
| - // Let's not attempt to reuse the cache as it's only useful if we know the diff repeats |
500 |
| - // over different objects, like when doing rename tracking. |
501 |
| - // TODO(ST): consider not using it unless it's for rename tracking. However, there should |
502 |
| - // be a way to re-use memory for the two objects to compare, at least. |
503 |
| - resource_cache.clear_resource_cache(); |
504 |
| - Ok(Action::Continue) |
505 |
| - })?; |
506 |
| - // TODO(ST): make this API nicer, maybe have one that is not based on `ancestors()` but |
507 |
| - // similar to revwalk because it's so common? |
508 |
| - let revwalk = branch_head |
509 |
| - .attach(&repo) |
510 |
| - .ancestors() |
511 |
| - // When allowing to skip branches without a filter, make sure it automatically skips by date! |
512 |
| - .sorting( |
513 |
| - gix::traverse::commit::simple::Sorting::ByCommitTimeNewestFirstCutoffOlderThan { |
514 |
| - seconds: base_commit.time()?.seconds, |
515 |
| - }, |
516 |
| - ) |
517 |
| - .selected(|id| id != gix_base)?; |
518 |
| - let mut num_commits = 0; |
519 |
| - let mut authors = HashSet::new(); |
520 |
| - for commit_info in revwalk { |
521 |
| - let commit_info = commit_info?; |
522 |
| - // TODO(ST): offer direct `find_<kind>` methods. |
523 |
| - let commit = repo.find_object(commit_info.id)?.try_into_commit()?; |
524 |
| - authors.insert(commit.author()?.into()); |
525 |
| - num_commits += 1; |
| 549 | + let branch_data = BranchListingDetails { |
| 550 | + name: branch.name, |
| 551 | + lines_added, |
| 552 | + lines_removed, |
| 553 | + number_of_files, |
| 554 | + authors: authors.into_iter().collect(), |
| 555 | + number_of_commits: num_commits, |
| 556 | + }; |
| 557 | + enriched_branches.push(branch_data); |
526 | 558 | }
|
527 |
| - let branch_data = BranchListingDetails { |
528 |
| - name: branch.name, |
529 |
| - lines_added, |
530 |
| - lines_removed, |
531 |
| - number_of_files, |
532 |
| - authors: authors.into_iter().collect(), |
533 |
| - number_of_commits: num_commits, |
534 |
| - }; |
535 |
| - enriched_branches.push(branch_data); |
536 |
| - } |
| 559 | + diffstats |
| 560 | + }; |
| 561 | + diffstats.join().expect("no panic")?; |
537 | 562 | Ok(enriched_branches)
|
538 | 563 | }
|
539 | 564 |
|
|
0 commit comments