use crate::interpret_trailers::complete_line;
use crate::objects::CommitData;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RebaseTodoCmd {
Pick,
Reword,
Fixup,
Squash,
}
impl RebaseTodoCmd {
pub fn as_str(self) -> &'static str {
match self {
RebaseTodoCmd::Pick => "pick",
RebaseTodoCmd::Reword => "reword",
RebaseTodoCmd::Fixup => "fixup",
RebaseTodoCmd::Squash => "squash",
}
}
pub fn parse_word(word: &str) -> Option<Self> {
match word {
"pick" | "p" => Some(RebaseTodoCmd::Pick),
"reword" | "r" => Some(RebaseTodoCmd::Reword),
"fixup" | "f" => Some(RebaseTodoCmd::Fixup),
"squash" | "s" => Some(RebaseTodoCmd::Squash),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FixupMessageMode {
UseCommit,
EditCommit,
}
pub fn commit_subject_single_line(message: &str) -> String {
let mut lines = message.lines();
let Some(first) = lines.next() else {
return String::new();
};
let mut out = first.trim_end().to_string();
for line in lines {
let t = line.trim_end();
if t.is_empty() {
break;
}
if !out.is_empty() {
out.push(' ');
}
out.push_str(t.trim_start());
}
out
}
pub fn skip_fixupish_prefix(subject: &str) -> Option<&str> {
let s = subject.trim_start();
if let Some(rest) = s.strip_prefix("fixup! ") {
return Some(rest);
}
if let Some(rest) = s.strip_prefix("amend! ") {
return Some(rest);
}
if let Some(rest) = s.strip_prefix("squash! ") {
return Some(rest);
}
None
}
pub fn strip_fixupish_chain(mut p: &str) -> &str {
while let Some(rest) = skip_fixupish_prefix(p) {
p = rest;
p = p.trim_start();
}
p
}
pub fn format_autosquash_subject_for_match(message: &str) -> String {
commit_subject_single_line(message)
}
pub fn rebase_todo_command_for_display(cmd: RebaseTodoCmd, commit: &CommitData) -> &'static str {
if cmd == RebaseTodoCmd::Fixup
&& commit
.message
.lines()
.next()
.unwrap_or("")
.trim_start()
.starts_with("amend! ")
{
"fixup -C"
} else {
cmd.as_str()
}
}
pub fn rebase_todo_command_for_display_abbrev(
cmd: RebaseTodoCmd,
commit: &CommitData,
abbrev: bool,
) -> String {
let is_amend_fixup = cmd == RebaseTodoCmd::Fixup
&& commit
.message
.lines()
.next()
.unwrap_or("")
.trim_start()
.starts_with("amend! ");
if !abbrev {
return if is_amend_fixup {
"fixup -C".to_owned()
} else {
cmd.as_str().to_owned()
};
}
let ch = match cmd {
RebaseTodoCmd::Pick => "p",
RebaseTodoCmd::Reword => "r",
RebaseTodoCmd::Fixup => "f",
RebaseTodoCmd::Squash => "s",
};
if is_amend_fixup {
format!("{ch} -C")
} else {
ch.to_owned()
}
}
pub fn message_body_after_subject(message: &str) -> &str {
match message.find('\n') {
Some(i) => &message[i + 1..],
None => "",
}
}
pub fn skip_blank_lines(mut message: &str) -> &str {
loop {
let trimmed = message.trim_start_matches([' ', '\t']);
if trimmed.starts_with('\n') {
message = &trimmed[1..];
continue;
}
return message;
}
}
pub fn fixup_replacement_message(message: &str) -> String {
let subject = message.lines().next().unwrap_or("").trim_start();
if subject.starts_with("amend! ") {
complete_line(skip_blank_lines(message_body_after_subject(message)))
} else {
complete_line(message)
}
}
pub fn first_line_len(body: &str) -> usize {
match body.find('\n') {
Some(i) => i,
None => body.len(),
}
}
pub fn squash_comment_subject_prefix(body: &str, cmd: RebaseTodoCmd, seen_squash: bool) -> usize {
let t = body.trim_start();
if t.starts_with("amend! ") {
return first_line_len(body);
}
if (cmd == RebaseTodoCmd::Squash || seen_squash)
&& (t.starts_with("squash! ") || t.starts_with("fixup! "))
{
return first_line_len(body);
}
0
}
pub fn append_commented(buf: &mut String, text: &str) {
for line in text.lines() {
if line.is_empty() {
buf.push_str("#\n");
} else {
buf.push_str("# ");
buf.push_str(line);
buf.push('\n');
}
}
}
pub fn update_squash_message_for_fixup(buf: &mut String) {
let mut buf1 = String::from("# This is the 1st commit message:\n");
let mut buf2 = String::from("# The 1st commit message will be skipped:\n");
let update_comment_bufs = |b1: &mut String, b2: &mut String, n: usize| {
*b1 = format!("# This is the commit message #{n}:\n");
*b2 = format!("# The commit message #{n} will be skipped:\n");
};
let orig = std::mem::take(buf);
let bytes = orig.as_bytes();
let mut out = String::new();
let mut start = 0usize;
let mut comment_mode = false;
let mut i = 1usize;
let mut s = 0usize;
while s < orig.len() {
if orig[s..].starts_with(buf1.as_str()) {
let off = usize::from(s > start + 1 && bytes[s - 2] == b'\n');
copy_section(&mut out, &orig[start..s - off], comment_mode);
if off == 1 {
out.push('\n');
}
out.push_str(&buf2);
let mut next = s + buf1.len();
if next < orig.len() && bytes[next] == b'\n' {
out.push('\n');
next += 1;
}
start = next;
s = next;
comment_mode = true;
i += 1;
update_comment_bufs(&mut buf1, &mut buf2, i);
} else if orig[s..].starts_with(buf2.as_str()) {
let off = usize::from(s > start + 1 && bytes[s - 2] == b'\n');
copy_section(&mut out, &orig[start..s - off], comment_mode);
start = s - off;
s += buf2.len();
comment_mode = false;
i += 1;
update_comment_bufs(&mut buf1, &mut buf2, i);
} else {
match orig[s..].find('\n') {
Some(rel) => s += rel + 1,
None => break,
}
}
}
copy_section(&mut out, &orig[start..], comment_mode);
*buf = out;
}
pub fn copy_section(out: &mut String, text: &str, comment_mode: bool) {
if !comment_mode || text.is_empty() {
out.push_str(text);
return;
}
for line in text.split_inclusive('\n') {
let content = line.strip_suffix('\n').unwrap_or(line);
if content.starts_with('#') {
out.push_str(line);
} else if content.is_empty() {
out.push('#');
out.push_str(line);
} else {
out.push_str("# ");
out.push_str(line);
}
}
}
pub fn append_skipped_squash_message(buf: &mut String, body: &str, n: usize) {
if !buf.ends_with("\n\n") {
buf.push('\n');
}
buf.push_str("# The commit message #");
buf.push_str(&n.to_string());
buf.push_str(" will be skipped:\n\n");
append_commented(buf, body.trim_end_matches('\n'));
}
pub fn append_nth_squash_message(
buf: &mut String,
body: &str,
cmd: RebaseTodoCmd,
seen_squash: bool,
n: usize,
) {
if !buf.ends_with("\n\n") {
buf.push('\n');
}
buf.push_str("# This is the commit message #");
buf.push_str(&n.to_string());
buf.push_str(":\n\n");
let pre = squash_comment_subject_prefix(body, cmd, seen_squash).min(body.len());
if pre > 0 {
append_commented(buf, &body[..pre]);
let rest = &body[pre..];
let rest = rest.strip_prefix('\n').unwrap_or(rest);
buf.push_str(rest);
return;
}
buf.push_str(&body[pre..]);
}