Skip to content

Commit

Permalink
cli: list new remote branches during git fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdeafbeef committed Feb 17, 2024
1 parent 3500cd8 commit f6dec62
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#1252](https://github.com/martinvonz/jj/issues/1252),
[#2971](https://github.com/martinvonz/jj/issues/2971)). This may become the
default depending on feedback.
* `jj git fetch` now automatically prints new remote branches and tags by default.

### Fixed bugs

Expand Down
2 changes: 1 addition & 1 deletion cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ impl WorkspaceCommandHelper {
return Ok(());
}

print_git_import_stats(ui, &stats)?;
print_git_import_stats(ui, tx.repo(), &stats, false)?;
let mut tx = tx.into_inner();
// Rebase here to show slightly different status message.
let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?;
Expand Down
10 changes: 5 additions & 5 deletions cli/src/commands/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ fn init_git_refs(
if !tx.mut_repo().has_changes() {
return Ok(repo);
}
print_git_import_stats(ui, &stats)?;
print_git_import_stats(ui, repo.as_ref(), &stats, false)?;
if colocated {
// If git.auto-local-branch = true, local branches could be created for
// the imported remote branches.
Expand Down Expand Up @@ -537,7 +537,7 @@ fn cmd_git_fetch(
GitFetchError::InternalGitError(err) => map_git_error(err),
_ => user_error(err),
})?;
print_git_import_stats(ui, &stats.import_stats)?;
print_git_import_stats(ui, tx.repo(), &stats.import_stats, true)?;
}
tx.finish(
ui,
Expand Down Expand Up @@ -726,7 +726,7 @@ fn do_git_clone(
r#"Fetching into new repo in "{}""#,
wc_path.display()
)?;
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo.clone())?;
maybe_add_gitignore(&workspace_command)?;
git_repo.remote(remote_name, source).unwrap();
let mut fetch_tx = workspace_command.start_transaction();
Expand All @@ -751,7 +751,7 @@ fn do_git_clone(
unreachable!("we didn't provide any globs")
}
})?;
print_git_import_stats(ui, &stats.import_stats)?;
print_git_import_stats(ui, repo.as_ref(), &stats.import_stats, true)?;
fetch_tx.finish(ui, "fetch from git remote into empty repo")?;
Ok((workspace_command, stats))
}
Expand Down Expand Up @@ -1190,7 +1190,7 @@ fn cmd_git_import(
// That's why cmd_git_export() doesn't export the HEAD ref.
git::import_head(tx.mut_repo())?;
let stats = git::import_refs(tx.mut_repo(), &command.settings().git_settings())?;
print_git_import_stats(ui, &stats)?;
print_git_import_stats(ui, tx.repo(), &stats, true)?;
tx.finish(ui, "import git refs")?;
Ok(())
}
Expand Down
105 changes: 102 additions & 3 deletions cli/src/git_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ use std::sync::Mutex;
use std::time::Instant;
use std::{error, iter};

use jj_lib::git::{self, FailedRefExport, FailedRefExportReason, GitImportStats};
use itertools::Itertools;
use jj_lib::git::{self, FailedRefExport, FailedRefExportReason, GitImportStats, RefName};
use jj_lib::git_backend::GitBackend;
use jj_lib::repo::{ReadonlyRepo, Repo as _};
use jj_lib::op_store::{RefTarget, RemoteRef};
use jj_lib::repo::{ReadonlyRepo, Repo};
use jj_lib::store::Store;
use jj_lib::workspace::Workspace;
use unicode_width::UnicodeWidthStr;

use crate::cli_util::{user_error, CommandError};
use crate::progress::Progress;
Expand Down Expand Up @@ -172,17 +175,113 @@ pub fn with_remote_git_callbacks<T>(
f(callbacks)
}

pub fn print_git_import_stats(ui: &mut Ui, stats: &GitImportStats) -> Result<(), CommandError> {
pub fn print_git_import_stats(
ui: &mut Ui,
repo: &dyn Repo,
stats: &GitImportStats,
show_ref_stats: bool,
) -> Result<(), CommandError> {
if show_ref_stats {
let refs_stats = stats
.changed_remote_refs
.iter()
.map(|(refn_name, (remote_ref, ref_target))| {
RefStatus::new(refn_name, remote_ref, ref_target, repo)
})
.collect_vec();
let max_width = refs_stats.iter().map(|x| x.ref_name.width()).max();
if let Some(max_width) = max_width {
let stderr = &mut ui.stderr();
for status in refs_stats {
status.output(max_width, stderr)?;
}
}
}

if !stats.abandoned_commits.is_empty() {
writeln!(
ui.stderr(),
"Abandoned {} commits that are no longer reachable.",
stats.abandoned_commits.len()
)?;
}

Ok(())
}

struct RefStatus {
ref_name: String,
tracking_status: TrackingStatus,
import_status: ImportStatus,
}

impl RefStatus {
fn new(
ref_name: &RefName,
remote_ref: &RemoteRef,
ref_target: &RefTarget,
repo: &dyn Repo,
) -> Self {
let mut tracking_status = TrackingStatus::Untracked;
let ref_name = match ref_name {
RefName::RemoteBranch { branch, remote } => {
if repo.view().get_remote_branch(branch, remote).is_tracking() {
tracking_status = TrackingStatus::Tracked;
}
format!("branch: {branch}@{remote}")
}
RefName::Tag(tag) => format!("tag: {tag}"),
RefName::LocalBranch(branch) => format!("branch: {branch}"),
};

let import_status = match (remote_ref.target.is_absent(), ref_target.is_absent()) {
(true, false) => ImportStatus::New,
(false, true) => ImportStatus::Deleted,
_ => ImportStatus::Updated,
};

Self {
ref_name,
tracking_status,
import_status,
}
}

fn output(&self, max_ref_name_len: usize, out: &mut dyn Write) -> std::io::Result<()> {
const MAX_REF_NAME_LEN: usize = 40;
let tracking_status = match self.tracking_status {
TrackingStatus::Tracked => "tracked",
TrackingStatus::Untracked => "untracked",
};

let import_status = match self.import_status {
ImportStatus::New => "new",
ImportStatus::Deleted => "deleted",
ImportStatus::Updated => "updated",
};

// This ensures alignment of the statuses that follow
let padded_ref_name = format!(
"{:width$}",
self.ref_name,
width = std::cmp::min(max_ref_name_len, MAX_REF_NAME_LEN)
);

writeln!(out, "{padded_ref_name} [{import_status}] {tracking_status}")
}
}

enum TrackingStatus {
Tracked,
Untracked,
}

enum ImportStatus {
New,
Deleted,
Updated,
}

pub fn print_failed_git_export(
ui: &Ui,
failed_branches: &[FailedRefExport],
Expand Down
36 changes: 30 additions & 6 deletions cli/tests/test_branch_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,9 @@ fn test_branch_forget_fetched_branch() {
// We can fetch feature1 again.
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: mzyxwzks 9f01a0e0 message
@origin: mzyxwzks 9f01a0e0 message
Expand All @@ -548,7 +550,9 @@ fn test_branch_forget_fetched_branch() {
// Fetch works even without the export-import
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: mzyxwzks 9f01a0e0 message
@origin: mzyxwzks 9f01a0e0 message
Expand All @@ -574,7 +578,9 @@ fn test_branch_forget_fetched_branch() {
// Fetching a moved branch does not create a conflict
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: ooosovrs 38aefb17 (empty) another message
@origin: ooosovrs 38aefb17 (empty) another message
Expand Down Expand Up @@ -687,7 +693,12 @@ fn test_branch_track_untrack() {
);
test_env.add_config("git.auto-local-branch = false");
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] untracked
branch: feature2@origin [new] untracked
branch: main@origin [new] untracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1@origin: sptzoqmo 7b33f629 commit 1
feature2@origin: sptzoqmo 7b33f629 commit 1
Expand Down Expand Up @@ -759,7 +770,12 @@ fn test_branch_track_untrack() {
],
);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [updated] untracked
branch: feature2@origin [updated] untracked
branch: main@origin [updated] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: sptzoqmo 7b33f629 commit 1
feature1@origin: mmqqkyyt 40dabdaf commit 2
Expand Down Expand Up @@ -791,6 +807,10 @@ fn test_branch_track_untrack() {
test_env.add_config("git.auto-local-branch = true");
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [updated] untracked
branch: feature2@origin [updated] untracked
branch: feature3@origin [new] tracked
branch: main@origin [updated] tracked
Abandoned 1 commits that are no longer reachable.
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
Expand Down Expand Up @@ -847,7 +867,11 @@ fn test_branch_track_untrack_patterns() {
// Fetch new commit without auto tracking
test_env.add_config("git.auto-local-branch = false");
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] untracked
branch: feature2@origin [new] untracked
"###);

// Track local branch
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "main"]);
Expand Down
6 changes: 6 additions & 0 deletions cli/tests/test_git_clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fn test_git_clone() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Fetching into new repo in "$TEST_ENV/clone"
branch: main@origin [new] untracked
Working copy now at: uuqppmxq 1f0b881a (empty) (no description set)
Parent commit : mzyxwzks 9f01a0e0 main | message
Added 1 files, modified 0 files, removed 0 files
Expand Down Expand Up @@ -173,6 +174,7 @@ fn test_git_clone_colocate() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Fetching into new repo in "$TEST_ENV/clone"
branch: main@origin [new] untracked
Working copy now at: uuqppmxq 1f0b881a (empty) (no description set)
Parent commit : mzyxwzks 9f01a0e0 main | message
Added 1 files, modified 0 files, removed 0 files
Expand Down Expand Up @@ -328,6 +330,8 @@ fn test_git_clone_remote_default_branch() {
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "clone1"]);
insta::assert_snapshot!(stderr, @r###"
Fetching into new repo in "$TEST_ENV/clone1"
branch: feature1@origin [new] untracked
branch: main@origin [new] untracked
Working copy now at: sqpuoqvx cad212e1 (empty) (no description set)
Parent commit : mzyxwzks 9f01a0e0 feature1 main | message
Added 1 files, modified 0 files, removed 0 files
Expand All @@ -346,6 +350,8 @@ fn test_git_clone_remote_default_branch() {
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "clone2"]);
insta::assert_snapshot!(stderr, @r###"
Fetching into new repo in "$TEST_ENV/clone2"
branch: feature1@origin [new] untracked
branch: main@origin [new] untracked
Working copy now at: pmmvwywv fa729b1e (empty) (no description set)
Parent commit : mzyxwzks 9f01a0e0 feature1@origin main | message
Added 1 files, modified 0 files, removed 0 files
Expand Down
2 changes: 2 additions & 0 deletions cli/tests/test_git_colocated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@ fn test_git_colocated_fetch_deleted_or_moved_branch() {
let (stdout, stderr) = test_env.jj_cmd_ok(&clone_path, &["git", "fetch"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
branch: B_to_delete@origin [deleted] untracked
branch: C_to_move@origin [updated] tracked
Abandoned 2 commits that are no longer reachable.
"###);
// "original C" and "B_to_delete" are abandoned, as the corresponding branches
Expand Down
Loading

0 comments on commit f6dec62

Please sign in to comment.