Skip to content

Commit 696db7b

Browse files
authored
Merge pull request #195 from blairconrad/create-squash-commits
Add flag to create squash commits
2 parents 1a24dba + c397f92 commit 696db7b

File tree

4 files changed

+137
-2
lines changed

4 files changed

+137
-2
lines changed

Documentation/git-absorb.adoc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ FLAGS
6666
Skip all safety checks as if all --force-* flags were given.
6767
See those flags to understand the full effect of supplying --force.
6868

69+
-s::
70+
--squash::
71+
Create squash commits instead of fixup commits.
72+
+
73+
When this flag is used, "fixup commit" may be read as "squash commit"
74+
throughout the documentation. All configuration relating to fixup
75+
commits will apply to the squash commits instead.
76+
6977
-w::
7078
--whole-file::
7179
Match the first commit touching the same file as the current hunk.
@@ -98,7 +106,7 @@ OPTIONS
98106
Generate completions
99107
[possible values: bash, fish, nushell, zsh, powershell, elvish]
100108

101-
-- <REBASE_OPTIONS>::
109+
\-- <REBASE_OPTIONS>::
102110
Options to pass to git rebase after generating commits.
103111
Must be the last arguments and the `--` must be present.
104112
Only valid when `--and-rebase` is used.
@@ -212,6 +220,22 @@ edit your local or global `.gitconfig` and add the following section:
212220
forceDetach = true
213221
.............................................................................
214222

223+
GENERATE SQUASH COMMITS INSTEAD OF FIXUPS
224+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
225+
226+
By default, git-absorb will generate fixup commits.
227+
To instead generate squash commits, edit your local or global `.gitconfig`
228+
and add the following section:
229+
230+
.............................................................................
231+
[absorb]
232+
createSquashCommits = true
233+
.............................................................................
234+
235+
When this option is set, "fixup commit" may be read as "squash commit"
236+
throughout the documentation. All configuration relating to fixup
237+
commits will apply to the squash commits instead.
238+
215239
GITHUB PROJECT
216240
--------------
217241

src/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ pub const AUTO_STAGE_IF_NOTHING_STAGED_DEFAULT: bool = false;
1919
pub const FIXUP_TARGET_ALWAYS_SHA_CONFIG_NAME: &str = "absorb.fixupTargetAlwaysSHA";
2020
pub const FIXUP_TARGET_ALWAYS_SHA_DEFAULT: bool = false;
2121

22+
pub const CREATE_SQUASH_COMMITS_CONFIG_NAME: &str = "absorb.createSquashCommits";
23+
pub const CREATE_SQUASH_COMMITS_DEFAULT: bool = false;
24+
2225
pub fn unify<'config>(config: &'config Config, repo: &Repository) -> Config<'config> {
2326
Config {
2427
// here, we default to the git config value,
@@ -36,6 +39,12 @@ pub fn unify<'config>(config: &'config Config, repo: &Repository) -> Config<'con
3639
ONE_FIXUP_PER_COMMIT_CONFIG_NAME,
3740
ONE_FIXUP_PER_COMMIT_DEFAULT,
3841
),
42+
squash: config.squash
43+
|| bool_value(
44+
repo,
45+
CREATE_SQUASH_COMMITS_CONFIG_NAME,
46+
CREATE_SQUASH_COMMITS_DEFAULT,
47+
),
3948
force_author: config.force_author
4049
|| bool_value(repo, FORCE_AUTHOR_CONFIG_NAME, FORCE_AUTHOR_DEFAULT),
4150
force_detach: config.force_detach

src/lib.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub struct Config<'a> {
1919
pub rebase_options: &'a Vec<&'a str>,
2020
pub whole_file: bool,
2121
pub one_fixup_per_commit: bool,
22+
pub squash: bool,
2223
pub message: Option<&'a str>,
2324
}
2425

@@ -321,7 +322,8 @@ fn run_with_repo(logger: &slog::Logger, config: &Config, repo: &git2::Repository
321322
.stats()?;
322323
if !config.dry_run {
323324
head_tree = new_head_tree;
324-
let mut message = format!("fixup! {}\n", dest_commit_locator);
325+
let verb = if config.squash { "squash" } else { "fixup" };
326+
let mut message = format!("{}! {}\n", verb, dest_commit_locator);
325327
if let Some(m) = config.message.filter(|m| !m.is_empty()) {
326328
message.push('\n');
327329
message.push_str(m);
@@ -718,6 +720,15 @@ mod tests {
718720
let pre_absorb_ref_commit = ctx.repo.refname_to_id("PRE_ABSORB_HEAD").unwrap();
719721
assert_eq!(pre_absorb_ref_commit, actual_pre_absorb_commit);
720722

723+
assert_eq!(
724+
extract_commit_messages(&ctx.repo),
725+
vec![
726+
"fixup! Initial commit.\n",
727+
"fixup! Initial commit.\n",
728+
"Initial commit.",
729+
]
730+
);
731+
721732
log_utils::assert_log_messages_are(
722733
capturing_logger.visible_logs(),
723734
vec![
@@ -1495,6 +1506,74 @@ mod tests {
14951506
assert!(is_something_in_index);
14961507
}
14971508

1509+
#[test]
1510+
fn squash_flag() {
1511+
let ctx = repo_utils::prepare_and_stage();
1512+
1513+
// run 'git-absorb'
1514+
let mut capturing_logger = log_utils::CapturingLogger::new();
1515+
let config = Config {
1516+
squash: true,
1517+
..DEFAULT_CONFIG
1518+
};
1519+
run_with_repo(&capturing_logger.logger, &config, &ctx.repo).unwrap();
1520+
1521+
assert_eq!(
1522+
extract_commit_messages(&ctx.repo),
1523+
vec![
1524+
"squash! Initial commit.\n",
1525+
"squash! Initial commit.\n",
1526+
"Initial commit.",
1527+
]
1528+
);
1529+
1530+
log_utils::assert_log_messages_are(
1531+
capturing_logger.visible_logs(),
1532+
vec![
1533+
&json!({"level": "INFO", "msg": "committed"}),
1534+
&json!({"level": "INFO", "msg": "committed"}),
1535+
&json!({
1536+
"level": "INFO",
1537+
"msg": "To squash the new commits, rebase:",
1538+
"command": "git rebase --interactive --autosquash --autostash --root",
1539+
}),
1540+
],
1541+
);
1542+
}
1543+
1544+
#[test]
1545+
fn run_with_squash_config_option() {
1546+
let ctx = repo_utils::prepare_and_stage();
1547+
1548+
repo_utils::set_config_flag(&ctx.repo, "absorb.createSquashCommits");
1549+
1550+
// run 'git-absorb'
1551+
let mut capturing_logger = log_utils::CapturingLogger::new();
1552+
run_with_repo(&capturing_logger.logger, &DEFAULT_CONFIG, &ctx.repo).unwrap();
1553+
1554+
assert_eq!(
1555+
extract_commit_messages(&ctx.repo),
1556+
vec![
1557+
"squash! Initial commit.\n",
1558+
"squash! Initial commit.\n",
1559+
"Initial commit.",
1560+
]
1561+
);
1562+
1563+
log_utils::assert_log_messages_are(
1564+
capturing_logger.visible_logs(),
1565+
vec![
1566+
&json!({"level": "INFO", "msg": "committed"}),
1567+
&json!({"level": "INFO", "msg": "committed"}),
1568+
&json!({
1569+
"level": "INFO",
1570+
"msg": "To squash the new commits, rebase:",
1571+
"command": "git rebase --interactive --autosquash --autostash --root",
1572+
}),
1573+
],
1574+
);
1575+
}
1576+
14981577
#[test]
14991578
fn dry_run_flag() {
15001579
let ctx = repo_utils::prepare_and_stage();
@@ -1814,6 +1893,23 @@ mod tests {
18141893
assert_eq!(actual_msg, expected_msg);
18151894
}
18161895

1896+
/// Perform a revwalk from HEAD, extracting the commit messages.
1897+
fn extract_commit_messages(repo: &git2::Repository) -> Vec<String> {
1898+
let mut revwalk = repo.revwalk().unwrap();
1899+
revwalk.push_head().unwrap();
1900+
1901+
let mut messages = Vec::new();
1902+
1903+
for oid in revwalk {
1904+
let commit = repo.find_commit(oid.unwrap()).unwrap();
1905+
if let Some(message) = commit.message() {
1906+
messages.push(message.to_string());
1907+
}
1908+
}
1909+
1910+
messages
1911+
}
1912+
18171913
const DEFAULT_CONFIG: Config = Config {
18181914
dry_run: false,
18191915
force_author: false,
@@ -1823,6 +1919,7 @@ mod tests {
18231919
rebase_options: &Vec::new(),
18241920
whole_file: false,
18251921
one_fixup_per_commit: false,
1922+
squash: false,
18261923
message: None,
18271924
};
18281925
}

src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ struct Cli {
4444
/// Only generate one fixup per commit
4545
#[clap(long, short = 'F')]
4646
one_fixup_per_commit: bool,
47+
/// Create squash commits instead of fixup
48+
#[clap(long, short = 's')]
49+
squash: bool,
4750
/// Commit message body that is given to all fixup commits
4851
#[clap(long, short)]
4952
message: Option<String>,
@@ -62,6 +65,7 @@ fn main() {
6265
gen_completions,
6366
whole_file,
6467
one_fixup_per_commit,
68+
squash,
6569
message,
6670
} = Cli::parse();
6771

@@ -113,6 +117,7 @@ fn main() {
113117
rebase_options: &rebase_options,
114118
whole_file,
115119
one_fixup_per_commit,
120+
squash,
116121
message: message.as_deref(),
117122
},
118123
) {

0 commit comments

Comments
 (0)