Skip to content

Commit e32d9e8

Browse files
authored
Merge pull request #10464 from gitbutlerapp/kv-branch-55
Implement but new
2 parents f2e5695 + dbd641b commit e32d9e8

File tree

3 files changed

+136
-1
lines changed

3 files changed

+136
-1
lines changed

crates/but/src/args.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ For examples see `but rub --help`."
7878
#[clap(short = 'o', long = "only")]
7979
only: bool,
8080
},
81+
/// Insert a blank commit before the specified commit, or at the top of a stack.
82+
New {
83+
/// Commit ID to insert before, or branch ID to insert at top of stack
84+
target: String,
85+
},
8186
/// Edit the commit message of the specified commit.
8287
#[clap(alias = "desc")]
8388
Describe {
@@ -134,6 +139,8 @@ pub enum CommandName {
134139
Rub,
135140
#[clap(alias = "commit")]
136141
Commit,
142+
#[clap(alias = "new")]
143+
New,
137144
#[clap(alias = "describe")]
138145
Describe,
139146
#[clap(alias = "oplog")]

crates/but/src/commit/mod.rs

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use crate::id::CliId;
12
use crate::status::assignment::FileAssignment;
3+
use anyhow::Result;
24
use bstr::{BString, ByteSlice};
3-
use but_api::{commands::diff, commands::workspace, hex_hash::HexHash};
5+
use but_api::{commands::diff, commands::virtual_branches, commands::workspace, hex_hash::HexHash};
46
use but_core::ui::TreeChange;
57
use but_hunk_assignment::HunkAssignment;
68
use but_settings::AppSettings;
@@ -11,6 +13,127 @@ use std::collections::BTreeMap;
1113
use std::io::{self, Write};
1214
use std::path::Path;
1315

16+
pub(crate) fn insert_blank_commit(repo_path: &Path, _json: bool, target: &str) -> Result<()> {
17+
let project = Project::find_by_path(repo_path)?;
18+
let mut ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
19+
20+
// Resolve the target ID
21+
let cli_ids = CliId::from_str(&mut ctx, target)?;
22+
23+
if cli_ids.is_empty() {
24+
anyhow::bail!("Target '{}' not found", target);
25+
}
26+
27+
if cli_ids.len() > 1 {
28+
anyhow::bail!(
29+
"Target '{}' is ambiguous. Found {} matches",
30+
target,
31+
cli_ids.len()
32+
);
33+
}
34+
35+
let cli_id = &cli_ids[0];
36+
37+
// Determine target commit ID and offset based on CLI ID type
38+
let (target_commit_id, offset, success_message) = match cli_id {
39+
CliId::Commit { oid } => {
40+
// For commits, insert before (offset 0) and use the commit ID directly
41+
(
42+
*oid,
43+
0,
44+
format!(
45+
"Created blank commit before commit {}",
46+
&oid.to_string()[..7]
47+
),
48+
)
49+
}
50+
CliId::Branch { name } => {
51+
// For branches, we need to find the branch and get its head commit
52+
let head_commit_id = find_branch_head_commit(project.id, name)?;
53+
(
54+
head_commit_id,
55+
-1,
56+
format!("Created blank commit at the top of stack '{name}'"),
57+
)
58+
}
59+
_ => {
60+
anyhow::bail!(
61+
"Target must be a commit ID or branch name, not {}",
62+
cli_id.kind()
63+
);
64+
}
65+
};
66+
67+
// Find the stack containing the target commit and insert blank commit
68+
let stack_id = find_stack_containing_commit(project.id, target_commit_id)?;
69+
virtual_branches::insert_blank_commit(
70+
project.id,
71+
stack_id,
72+
Some(target_commit_id.to_string()),
73+
offset,
74+
)?;
75+
println!("{success_message}");
76+
Ok(())
77+
}
78+
79+
fn find_branch_head_commit(
80+
project_id: gitbutler_project::ProjectId,
81+
branch_name: &str,
82+
) -> Result<gix::ObjectId> {
83+
let stack_entries = workspace::stacks(project_id, None)?;
84+
85+
for stack_entry in &stack_entries {
86+
if let Some(stack_id) = stack_entry.id {
87+
let stack_details = workspace::stack_details(project_id, Some(stack_id))?;
88+
89+
if let Some(branch_details) = stack_details
90+
.branch_details
91+
.iter()
92+
.find(|b| b.name == branch_name)
93+
{
94+
// Get the head commit of this branch (prefer regular commits over upstream)
95+
return if let Some(commit) = branch_details.commits.first() {
96+
Ok(commit.id)
97+
} else if let Some(commit) = branch_details.upstream_commits.first() {
98+
Ok(commit.id)
99+
} else {
100+
anyhow::bail!("Branch '{}' has no commits", branch_name);
101+
};
102+
}
103+
}
104+
}
105+
106+
anyhow::bail!("Branch '{}' not found in any stack", branch_name);
107+
}
108+
109+
fn find_stack_containing_commit(
110+
project_id: gitbutler_project::ProjectId,
111+
commit_id: gix::ObjectId,
112+
) -> Result<but_workspace::StackId> {
113+
let stack_entries = workspace::stacks(project_id, None)?;
114+
115+
for stack_entry in &stack_entries {
116+
if let Some(stack_id) = stack_entry.id {
117+
let stack_details = workspace::stack_details(project_id, Some(stack_id))?;
118+
119+
// Check if this commit exists in any branch of this stack
120+
for branch_details in &stack_details.branch_details {
121+
// Check both regular commits and upstream commits
122+
if branch_details.commits.iter().any(|c| c.id == commit_id)
123+
|| branch_details
124+
.upstream_commits
125+
.iter()
126+
.any(|c| c.id == commit_id)
127+
{
128+
return Ok(stack_id);
129+
}
130+
}
131+
}
132+
}
133+
134+
anyhow::bail!("Commit {} not found in any stack", commit_id);
135+
}
136+
14137
pub(crate) fn commit(
15138
repo_path: &Path,
16139
_json: bool,

crates/but/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ async fn main() -> Result<()> {
168168
metrics_if_configured(app_settings, CommandName::Commit, props(start, &result)).ok();
169169
result
170170
}
171+
Subcommands::New { target } => {
172+
let result = commit::insert_blank_commit(&args.current_dir, args.json, target);
173+
metrics_if_configured(app_settings, CommandName::New, props(start, &result)).ok();
174+
result
175+
}
171176
Subcommands::Describe { commit } => {
172177
let result = describe::edit_commit_message(&args.current_dir, args.json, commit);
173178
metrics_if_configured(app_settings, CommandName::Describe, props(start, &result)).ok();

0 commit comments

Comments
 (0)