Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/but-rules/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ anyhow = "1.0.98"
itertools.workspace = true
serde.workspace = true
regex = "1.11.1"
gix = { workspace = true }
chrono = { version = "0.4.41", features = [] }
serde_regex = "1.1.0"
serde_json = "1.0.142"
Expand Down
63 changes: 62 additions & 1 deletion crates/but-rules/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use but_graph::VirtualBranchesTomlMetadata;
use but_hunk_assignment::{HunkAssignment, assign, assignments_to_requests};
use but_hunk_dependency::ui::HunkDependencies;
use but_workspace::{StackId, StacksFilter, ui::StackEntry};
use but_workspace::{DiffSpec, StackId, StacksFilter, commit_engine, ui::StackEntry};
use gitbutler_command_context::CommandContext;
use itertools::Itertools;
use std::str::FromStr;
Expand All @@ -26,6 +26,9 @@ pub fn process_workspace_rules(
matches!(
&r.action,
super::Action::Explicit(super::Operation::Assign { .. })
) || matches!(
&r.action,
super::Action::Explicit(super::Operation::Amend { .. })
)
})
.collect_vec();
Expand Down Expand Up @@ -60,6 +63,10 @@ pub fn process_workspace_rules(
handle_assign(ctx, assignments, dependencies.as_ref()).unwrap_or_default();
}
}
super::Action::Explicit(super::Operation::Amend { change_id }) => {
let assignments = matching(assignments, rule.filters.clone());
handle_amend(ctx, assignments, change_id).unwrap_or_default();
}
_ => continue,
};
}
Expand Down Expand Up @@ -137,6 +144,60 @@ fn handle_assign(
}
}

fn handle_amend(
ctx: &mut CommandContext,
assignments: Vec<HunkAssignment>,
change_id: String,
) -> anyhow::Result<()> {
let changes: Vec<DiffSpec> = assignments.into_iter().map(|a| a.into()).collect();
let project = ctx.project();
let mut guard = project.exclusive_worktree_access();
let repo = but_core::open_repo_for_merging(project.worktree_path())?;

let meta = VirtualBranchesTomlMetadata::from_path(
ctx.project().gb_dir().join("virtual_branches.toml"),
)?;
let ref_info_options = but_workspace::ref_info::Options {
expensive_commit_info: true,
traversal: meta.graph_options(),
};
let info = but_workspace::head_info(&repo, &meta, ref_info_options)?;
let mut commit_id: Option<gix::ObjectId> = None;
'outer: for stack in info.stacks {
for segment in stack.segments {
for commit in segment.commits {
if Some(change_id.clone()) == commit.change_id.map(|c| c.to_string()) {
commit_id = Some(commit.id);
break 'outer;
}
}
}
}

let commit_id = commit_id.ok_or_else(|| {
anyhow::anyhow!(
"No commit with Change-Id {} found in the current workspace",
change_id
)
})?;

commit_engine::create_commit_and_update_refs_with_project(
&repo,
project,
None,
commit_engine::Destination::AmendCommit {
commit_id,
// TODO: Expose this in the UI for 'edit message' functionality.
new_message: None,
},
None,
changes,
ctx.app_settings().context_lines,
guard.write_permission(),
)?;
Ok(())
}

fn matching(wt_assignments: &[HunkAssignment], filters: Vec<Filter>) -> Vec<HunkAssignment> {
if filters.is_empty() {
return wt_assignments.to_vec();
Expand Down
12 changes: 10 additions & 2 deletions crates/but-rules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ impl WorkspaceRule {
}
}

pub fn target_commit_id(&self) -> Option<String> {
if let Action::Explicit(Operation::Amend { change_id }) = &self.action {
Some(change_id.clone())
} else {
None
}
}

pub fn id(&self) -> String {
self.id.clone()
}
Expand Down Expand Up @@ -139,7 +147,7 @@ pub enum Operation {
/// Assign the matched changes to a specific stack ID.
Assign { target: StackTarget },
/// Amend the matched changes into a specific commit.
Amend { commit_id: String },
Amend { change_id: String },
/// Create a new commit with the matched changes on a specific branch.
NewCommit { branch_name: String },
}
Expand Down Expand Up @@ -292,7 +300,7 @@ pub fn list_rules(ctx: &mut CommandContext) -> anyhow::Result<Vec<WorkspaceRule>
Ok(rules)
}

fn process_rules(ctx: &mut CommandContext) -> anyhow::Result<()> {
pub fn process_rules(ctx: &mut CommandContext) -> anyhow::Result<()> {
let wt_changes = but_core::diff::worktree_changes(&ctx.gix_repo()?)?;

let dependencies = hunk_dependencies_for_workspace_changes_by_worktree_dir(
Expand Down
3 changes: 3 additions & 0 deletions crates/but/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ anyhow.workspace = true
rmcp.workspace = true
command-group = { version = "5.0.1", features = ["with-tokio"] }
sysinfo = "0.36.0"
regex = "1.11.1"
gitbutler-project.workspace = true
gix.workspace = true
but-core.workspace = true
Expand All @@ -42,9 +43,11 @@ but-hunk-assignment.workspace = true
but-hunk-dependency.workspace = true
but-claude.workspace = true
but-tools.workspace = true
but-rules.workspace = true
gitbutler-command-context.workspace = true
gitbutler-serde.workspace = true
gitbutler-stack.workspace = true
gitbutler-commit.workspace = true
gitbutler-branch-actions.workspace = true
gitbutler-branch.workspace = true
gitbutler-secret.workspace = true
Expand Down
8 changes: 8 additions & 0 deletions crates/but/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ For examples see `but rub --help`."
/// The target entity to combine with the source
target: String,
},
/// Creates or removes a rule for auto-assigning or auto-comitting
Mark {
/// The target entity that will be marked
target: String,
/// Deletes a mark
#[clap(long, short = 'd')]
delete: bool,
},
/// Starts up the MCP server.
Mcp {
/// Starts the internal MCP server which has more granular tools.
Expand Down
29 changes: 23 additions & 6 deletions crates/but/src/log/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@ use crate::id::CliId;
pub(crate) fn commit_graph(repo_path: &Path, _json: bool) -> anyhow::Result<()> {
let project = Project::from_path(repo_path).expect("Failed to create project from path");
let ctx = &mut CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
but_rules::process_rules(ctx).ok(); // TODO: this is doing double work (dependencies can be reused)
let stacks = stacks(ctx)?
.iter()
.filter_map(|s| s.id.map(|id| stack_details(ctx, id)))
.filter_map(|s| s.id.map(|id| stack_details(ctx, id).map(|d| (id, d))))
.filter_map(Result::ok)
.collect::<Vec<_>>();

let mut nesting = 0;
for (i, stack) in stacks.iter().enumerate() {
for (i, (stack_id, stack)) in stacks.iter().enumerate() {
let marked = crate::mark::stack_marked(ctx, *stack_id).unwrap_or_default();
let mut mark = if marked {
Some("◀ Marked ▶".red().bold())
} else {
None
};
let mut second_consecutive = false;
let mut stacked = false;
for branch in stack.branch_details.iter() {
Expand All @@ -45,13 +52,15 @@ pub(crate) fn commit_graph(repo_path: &Path, _json: bool) -> anyhow::Result<()>
.underline()
.blue();
println!(
"{}{}{} [{}] {}",
"{}{}{} [{}] {} {}",
"│ ".repeat(nesting),
extra_space,
line,
branch.name.to_string().green().bold(),
id
id,
mark.clone().unwrap_or_default()
);
mark = None; // show this on the first branch in the stack
for (j, commit) in branch.upstream_commits.iter().enumerate() {
let time_string = chrono::DateTime::from_timestamp_millis(commit.created_at as i64)
.ok_or(anyhow::anyhow!("Could not parse timestamp"))?
Expand Down Expand Up @@ -83,6 +92,13 @@ pub(crate) fn commit_graph(repo_path: &Path, _json: bool) -> anyhow::Result<()>
}
}
for commit in branch.commits.iter() {
let marked =
crate::mark::commit_marked(ctx, commit.id.to_string()).unwrap_or_default();
let mark = if marked {
Some("◀ Marked ▶".red().bold())
} else {
None
};
let state_str = match commit.state {
but_workspace::ui::CommitState::LocalOnly => "{local}".normal(),
but_workspace::ui::CommitState::LocalAndRemote(_) => "{pushed}".cyan(),
Expand All @@ -98,14 +114,15 @@ pub(crate) fn commit_graph(repo_path: &Path, _json: bool) -> anyhow::Result<()>
.format("%Y-%m-%d %H:%M:%S")
.to_string();
println!(
"{}● {}{} {} {} {} {}",
"{}● {}{} {} {} {} {} {}",
"│ ".repeat(nesting),
&commit.id.to_string()[..2].blue().underline(),
&commit.id.to_string()[2..7].blue(),
state_str,
conflicted_str,
commit.author.name,
time_string.dimmed()
time_string.dimmed(),
mark.clone().unwrap_or_default()
);
println!(
"{}│ {}",
Expand Down
10 changes: 10 additions & 0 deletions crates/but/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use but_claude::hooks::OutputAsJson;
mod command;
mod id;
mod log;
mod mark;
mod mcp;
mod mcp_internal;
mod metrics;
Expand Down Expand Up @@ -98,6 +99,15 @@ async fn main() -> Result<()> {
metrics_if_configured(app_settings, CommandName::Rub, props(start, &result)).ok();
Ok(())
}
Subcommands::Mark { target, delete } => {
let result = mark::handle(&args.current_dir, args.json, target, *delete)
.context("Can't mark this. Taaaa-na-na-na. Can't mark this.");
if let Err(e) = &result {
eprintln!("{} {}", e, e.root_cause());
}
metrics_if_configured(app_settings, CommandName::Rub, props(start, &result)).ok();
Ok(())
}
}
}

Expand Down
Loading
Loading