Skip to content

Commit e04d4e0

Browse files
committed
Implements but base check
1 parent 5da1d95 commit e04d4e0

File tree

5 files changed

+140
-5
lines changed

5 files changed

+140
-5
lines changed

crates/but/src/args.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ For examples see `but rub --help`."
4848
#[clap(long, short = 'r')]
4949
repo: bool,
5050
},
51+
/// Commands for managing the base.
52+
Base(crate::base::Platform),
5153
/// Starts up the MCP server.
5254
Mcp {
5355
/// Starts the internal MCP server which has more granular tools.
@@ -81,6 +83,8 @@ pub enum CommandName {
8183
Status,
8284
#[clap(alias = "rub")]
8385
Rub,
86+
BaseCheck,
87+
BaseUpdate,
8488
#[clap(
8589
alias = "claude-pre-tool",
8690
alias = "claudepretool",

crates/but/src/base/mod.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use std::path::Path;
2+
3+
use colored::Colorize;
4+
use gitbutler_branch_actions::upstream_integration::{
5+
BranchStatus::{Conflicted, Empty, Integrated, SaflyUpdatable},
6+
StackStatuses::{UpToDate, UpdatesRequired},
7+
};
8+
use gitbutler_project::Project;
9+
10+
#[derive(Debug, clap::Parser)]
11+
pub struct Platform {
12+
#[clap(subcommand)]
13+
pub cmd: Subcommands,
14+
}
15+
#[derive(Debug, clap::Subcommand)]
16+
pub enum Subcommands {
17+
/// Fetches remotes from the remote and checks the mergeability of the branches in the workspace.
18+
Check,
19+
/// Updates the worspace (with all applied branches) to include the latest changes from the base branch.
20+
Update,
21+
}
22+
23+
pub fn handle(cmd: &Subcommands, repo_path: &Path, json: bool) -> anyhow::Result<()> {
24+
match cmd {
25+
Subcommands::Check => {
26+
let project = Project::find_by_path(repo_path)?;
27+
if !json {
28+
println!("🔍 Checking base branch status...");
29+
}
30+
let base_branch = but_api::virtual_branches::fetch_from_remotes(
31+
project.id,
32+
Some("auto".to_string()),
33+
)?;
34+
println!("\n📍 Base branch:\t\t{}", base_branch.branch_name);
35+
println!(
36+
"⏫ Upstream commits:\t{} new commits on {}\n",
37+
base_branch.behind, base_branch.branch_name
38+
);
39+
let commits = base_branch.recent_commits.iter().take(3);
40+
for commit in commits {
41+
println!(
42+
"\t{} {}",
43+
&commit.id[..7],
44+
&commit.description.to_string().replace('\n', " ")[..72]
45+
);
46+
}
47+
let hidden_commits = base_branch.behind.saturating_sub(3);
48+
if hidden_commits > 0 {
49+
println!("\t... ({hidden_commits} more - run `but base check --all` to see all)");
50+
}
51+
52+
let status =
53+
but_api::virtual_branches::upstream_integration_statuses(project.id, None)?;
54+
55+
match status {
56+
UpToDate => println!("\n✅ Everything is up to date"),
57+
UpdatesRequired {
58+
worktree_conflicts,
59+
statuses,
60+
} => {
61+
if !worktree_conflicts.is_empty() {
62+
println!(
63+
"\n❗️ There are uncommitted changes in the worktree that may conflict with the updates."
64+
);
65+
}
66+
if !statuses.is_empty() {
67+
println!("\n{}", "Active Branch Status".bold());
68+
for (_id, status) in statuses {
69+
for bs in status.branch_statuses {
70+
let status_icon = match bs.status {
71+
SaflyUpdatable => "✅".to_string(),
72+
Integrated => "🔄".to_string(),
73+
Conflicted { rebasable } => {
74+
if rebasable {
75+
"⚠️".to_string()
76+
} else {
77+
"❗️".to_string()
78+
}
79+
}
80+
Empty => "✅".to_string(),
81+
};
82+
let status_text = match bs.status {
83+
SaflyUpdatable => "Updatable".green(),
84+
Integrated => "Integrated".blue(),
85+
Conflicted { rebasable } => {
86+
if rebasable {
87+
"Conflicted (Rebasable)".yellow()
88+
} else {
89+
"Conflicted (Not Rebasable)".red()
90+
}
91+
}
92+
Empty => "Nothing to do".normal(),
93+
};
94+
println!("\n{} {} ({})", status_icon, bs.name, status_text);
95+
}
96+
}
97+
}
98+
}
99+
}
100+
println!("\nRun `but base update` to update your branches");
101+
Ok(())
102+
}
103+
Subcommands::Update => {
104+
// metrics_if_configured(app_settings, CommandName::Log, p).ok();
105+
Ok(())
106+
}
107+
}
108+
}

crates/but/src/main.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use but_settings::AppSettings;
66
use metrics::{Event, Metrics, Props, metrics_if_configured};
77

88
use but_claude::hooks::OutputAsJson;
9+
mod base;
910
mod command;
1011
mod id;
1112
mod init;
@@ -96,6 +97,19 @@ async fn main() -> Result<()> {
9697
Ok(())
9798
}
9899
},
100+
Subcommands::Base(base::Platform { cmd }) => {
101+
let result = base::handle(cmd, &args.current_dir, args.json);
102+
metrics_if_configured(
103+
app_settings,
104+
match cmd {
105+
base::Subcommands::Check => CommandName::BaseCheck,
106+
base::Subcommands::Update => CommandName::BaseUpdate,
107+
},
108+
props(start, &result),
109+
)
110+
.ok();
111+
Ok(())
112+
}
99113
Subcommands::Log => {
100114
let result = log::commit_graph(&args.current_dir, args.json);
101115
metrics_if_configured(app_settings, CommandName::Log, props(start, &result)).ok();
@@ -120,7 +134,7 @@ async fn main() -> Result<()> {
120134
}
121135
}
122136

123-
fn props<E, T, R>(start: std::time::Instant, result: R) -> Props
137+
pub(crate) fn props<E, T, R>(start: std::time::Instant, result: R) -> Props
124138
where
125139
R: std::ops::Deref<Target = Result<T, E>>,
126140
E: std::fmt::Display,

crates/gitbutler-branch-actions/src/upstream_integration.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ use serde::{Deserialize, Serialize};
2424
#[derive(Serialize, PartialEq, Debug)]
2525
#[serde(rename_all = "camelCase")]
2626
pub struct NameAndStatus {
27-
name: String,
28-
status: BranchStatus,
27+
pub name: String,
28+
pub status: BranchStatus,
2929
}
3030

3131
#[derive(Serialize, PartialEq, Debug)]
3232
#[serde(rename_all = "camelCase")]
3333
pub struct StackStatus {
34-
tree_status: TreeStatus,
35-
branch_statuses: Vec<NameAndStatus>,
34+
pub tree_status: TreeStatus,
35+
pub branch_statuses: Vec<NameAndStatus>,
3636
}
3737

3838
#[derive(Serialize, PartialEq, Debug)]

crates/gitbutler-project/src/project.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ impl Project {
118118
..Default::default()
119119
})
120120
}
121+
/// Finds an existing project by its path. Errors out if not found.
122+
pub fn find_by_path(path: &Path) -> anyhow::Result<Project> {
123+
let projects = crate::list()?;
124+
let project = projects
125+
.into_iter()
126+
.find(|p| p.path == path)
127+
.context("No project found with the given path")?;
128+
Ok(project)
129+
}
121130
}
122131

123132
impl Project {

0 commit comments

Comments
 (0)