Repository: cocogitto/cocogitto
Pull request #430 (2024-11-28 08:17:14)

feat: ignore fixup commits

cocogitto-bot[bot]: :heavy_check_mark: c91133f16674ccaba02e51c8680b42b53742dadd - Conventional commits check succeeded.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) Report
Attention: Patch coverage is `82.66667%` with `13 lines` in your changes missing coverage. Please review.
> Project coverage is 86.60%. Comparing base [(`426223e`)](https://app.codecov.io/gh/cocogitto/cocogitto/commit/426223e807e0e653afc85cde498a5db976babfa9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) to head [(`c91133f`)](https://app.codecov.io/gh/cocogitto/cocogitto/commit/c91133f16674ccaba02e51c8680b42b53742dadd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto).

| [Files with missing lines](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) | Patch % | Lines |
|---|---|---|
| [src/conventional/commit.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&filepath=src%2Fconventional%2Fcommit.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbnZlbnRpb25hbC9jb21taXQucnM=) | 73.91% | [6 Missing :warning: ](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) |
| [src/bin/cog/main.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&filepath=src%2Fbin%2Fcog%2Fmain.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2Jpbi9jb2cvbWFpbi5ycw==) | 75.00% | [2 Missing :warning: ](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) |
| [src/command/edit.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&filepath=src%2Fcommand%2Fedit.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbW1hbmQvZWRpdC5ycw==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) |
| [src/conventional/bump.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&filepath=src%2Fconventional%2Fbump.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbnZlbnRpb25hbC9idW1wLnJz) | 85.71% | [2 Missing :warning: ](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) |
| [src/command/check.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&filepath=src%2Fcommand%2Fcheck.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbW1hbmQvY2hlY2sucnM=) | 94.44% | [1 Missing :warning: ](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #430      +/-   ##
==========================================
- Coverage   86.68%   86.60%   -0.09%     
==========================================
  Files          49       49              
  Lines        7046     7099      +53     
==========================================
+ Hits         6108     6148      +40     
- Misses        938      951      +13     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cocogitto/cocogitto/pull/430?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto).

cocogitto-bot[bot]: :heavy_check_mark: c91133f16674ccaba02e51c8680b42b53742dadd...2e803b1781973004509f33d4c02aa3e58b7f3c07 - Conventional commits check succeeded.
cocogitto-bot[bot]: :heavy_check_mark: c91133f16674ccaba02e51c8680b42b53742dadd...316740f2c55152fdcb4c0ca24f28dd8d46471dbb - Conventional commits check succeeded.
cocogitto-bot[bot]: :heavy_check_mark: c91133f16674ccaba02e51c8680b42b53742dadd...548cb4f1a8dcd1bc640bccaa2beb5f9bd9e51327 - Conventional commits check succeeded.
cocogitto-bot[bot]: :heavy_check_mark: 4d5b0ddca90e7cceb232eee46ca76ab528c12d1e...3d3754170e05ab26edf33aa648a5815064c5d5a6 - Conventional commits check succeeded.

Patch:
diff --git a/src/bin/cog/main.rs b/src/bin/cog/main.rs
index 3113109..ab7c479 100644
--- a/src/bin/cog/main.rs
+++ b/src/bin/cog/main.rs
@@ -146,6 +146,10 @@ enum Command {
         #[arg(short, long)]
         ignore_merge_commits: bool,
 
+        /// Ignore fixup!, squash! and amend! commit messages
+        #[arg(long)]
+        ignore_fixup_commits: bool,
+
         /// Check commits in the specified range
         #[arg(group = "commit_range")]
         range: Option<String>,
@@ -198,6 +202,10 @@ enum Command {
         /// Ignore merge commit messages
         #[arg(short, long)]
         ignore_merge_commits: bool,
+
+        /// Ignore fixup!, squash! and amend! commit messages
+        #[arg(long)]
+        ignore_fixup_commits: bool,
     },
 
     /// Display a changelog for the given commit oid range
@@ -486,8 +494,10 @@ fn main() -> Result<()> {
             message,
             file,
             ignore_merge_commits,
+            ignore_fixup_commits,
         } => {
             let ignore_merge_commits = ignore_merge_commits || SETTINGS.ignore_merge_commits;
+            let ignore_fixup_commits = ignore_fixup_commits || SETTINGS.ignore_fixup_commits;
             let author = CocoGitto::get()
                 .map(|cogito| cogito.get_committer().unwrap())
                 .ok();
@@ -508,17 +518,29 @@ fn main() -> Result<()> {
                 (Some(_), Some(_)) => unreachable!(),
             };
 
-            conv_commit::verify(author, &commit_message, ignore_merge_commits)?;
+            conv_commit::verify(
+                author,
+                &commit_message,
+                ignore_merge_commits,
+                ignore_fixup_commits,
+            )?;
         }
         Command::Check {
             from_latest_tag,
             ignore_merge_commits,
+            ignore_fixup_commits,
             range,
         } => {
             let cocogitto = CocoGitto::get()?;
             let from_latest_tag = from_latest_tag || SETTINGS.from_latest_tag;
             let ignore_merge_commits = ignore_merge_commits || SETTINGS.ignore_merge_commits;
-            cocogitto.check(from_latest_tag, ignore_merge_commits, range)?;
+            let ignore_fixup_commits = ignore_fixup_commits || SETTINGS.ignore_fixup_commits;
+            cocogitto.check(
+                from_latest_tag,
+                ignore_merge_commits,
+                ignore_fixup_commits,
+                range,
+            )?;
         }
         Command::Edit { from_latest_tag } => {
             let cocogitto = CocoGitto::get()?;
diff --git a/src/command/check.rs b/src/command/check.rs
index 2c360b0..e2c4223 100644
--- a/src/command/check.rs
+++ b/src/command/check.rs
@@ -13,6 +13,7 @@ impl CocoGitto {
         &self,
         check_from_latest_tag: bool,
         ignore_merge_commits: bool,
+        ignore_fixup_commits: bool,
         range: Option<String>,
     ) -> Result<()> {
         let commit_range = if let Some(range) = range {
@@ -26,21 +27,30 @@ impl CocoGitto {
             self.repository.revwalk("..")?
         };
 
-        let errors: Vec<_> = if ignore_merge_commits {
-            commit_range
-                .iter_commits()
-                .filter(|commit| commit.parent_count() <= 1)
-                .map(Commit::from_git_commit)
-                .filter_map(Result::err)
-                .collect()
-        } else {
-            commit_range
-                .iter_commits()
-                .map(Commit::from_git_commit)
-                .filter_map(Result::err)
-                .collect()
+        let ignore_merge_commit_fn = |commit: &git2::Commit| commit.parent_count() <= 1;
+        let ignore_fixup_commit_fn = |commit: &git2::Commit| {
+            !commit.message().unwrap().starts_with("fixup!")
+                && !commit.message().unwrap().starts_with("squash!")
+                && !commit.message().unwrap().starts_with("amend!")
         };
 
+        let errors: Vec<_> = commit_range
+            .iter_commits()
+            .filter(|commit| {
+                if ignore_merge_commits && ignore_fixup_commits {
+                    ignore_merge_commit_fn(commit) && ignore_fixup_commit_fn(commit)
+                } else if ignore_fixup_commits {
+                    ignore_fixup_commit_fn(commit)
+                } else if ignore_merge_commits {
+                    ignore_merge_commit_fn(commit)
+                } else {
+                    true
+                }
+            })
+            .map(Commit::from_git_commit)
+            .filter_map(Result::err)
+            .collect();
+
         if errors.is_empty() {
             let msg = "No errored commits".green();
             info!("{}", msg);
diff --git a/src/command/edit.rs b/src/command/edit.rs
index 9101af0..811d1ab 100644
--- a/src/command/edit.rs
+++ b/src/command/edit.rs
@@ -96,10 +96,12 @@ impl CocoGitto {
 
                         rebase.commit(None, &original_commit.committer(), Some(&new_message))?;
                         let ignore_merge_commit = SETTINGS.ignore_merge_commits;
+                        let ignore_fixup_commit = SETTINGS.ignore_fixup_commits;
                         match verify(
                             self.repository.get_author().ok(),
                             &new_message,
                             ignore_merge_commit,
+                            ignore_fixup_commit,
                         ) {
                             Ok(_) => {
                                 info!("Changed commit message to:\"{}\"", &new_message.trim_end())
diff --git a/src/conventional/bump.rs b/src/conventional/bump.rs
index 80afea4..172075c 100644
--- a/src/conventional/bump.rs
+++ b/src/conventional/bump.rs
@@ -17,6 +17,23 @@ static FILTER_MERGE_COMMITS: Lazy<fn(&&git2::Commit) -> bool> = Lazy::new(|| {
     }
 });
 
+static FILTER_FIXUP_COMMITS: Lazy<fn(&&git2::Commit) -> bool> = Lazy::new(|| {
+    |commit| {
+        if SETTINGS.ignore_fixup_commits {
+            commit
+                .message()
+                .map(|msg| {
+                    !msg.starts_with("fixup!")
+                        || !msg.starts_with("squash!")
+                        || !msg.starts_with("amend!")
+                })
+                .unwrap_or(true)
+        } else {
+            true
+        }
+    }
+});
+
 pub(crate) trait Bump {
     fn manual_bump(&self, version: &str) -> Result<Self, semver::Error>
     where
@@ -146,6 +163,7 @@ impl Tag {
         let commits: Vec<&Git2Commit> = commits
             .iter_commits()
             .filter(&*FILTER_MERGE_COMMITS)
+            .filter(&*FILTER_FIXUP_COMMITS)
             .collect();
 
         let conventional_commits: Vec<Commit> = commits
@@ -180,6 +198,7 @@ impl Tag {
         let commits: Vec<&Git2Commit> = commits
             .iter_commits()
             .filter(&*FILTER_MERGE_COMMITS)
+            .filter(&*FILTER_FIXUP_COMMITS)
             .collect();
 
         let conventional_commits: Vec<Commit> = commits
@@ -213,6 +232,7 @@ impl Tag {
         let commits: Vec<&Git2Commit> = commits
             .iter_commits()
             .filter(&*FILTER_MERGE_COMMITS)
+            .filter(&*FILTER_FIXUP_COMMITS)
             .collect();
 
         let conventional_commits: Vec<Commit> = commits
diff --git a/src/conventional/changelog/release.rs b/src/conventional/changelog/release.rs
index b67398f..3c049e7 100644
--- a/src/conventional/changelog/release.rs
+++ b/src/conventional/changelog/release.rs
@@ -62,6 +62,15 @@ impl TryFrom<CommitIter<'_>> for Release<'_> {
                             true
                         }
                     })
+                    .filter(|(_commit, commit)| {
+                        if SETTINGS.ignore_fixup_commits {
+                            !commit.message().unwrap().starts_with("fixup!")
+                                && !commit.message().unwrap().starts_with("squash!")
+                                && !commit.message().unwrap().starts_with("amend!")
+                        } else {
+                            true
+                        }
+                    })
                     .filter_map(|(_, commit)| match Commit::from_git_commit(commit) {
                         Ok(commit) => {
                             if !commit.should_omit() {
diff --git a/src/conventional/commit.rs b/src/conventional/commit.rs
index 5e914b0..1a1166d 100644
--- a/src/conventional/commit.rs
+++ b/src/conventional/commit.rs
@@ -249,6 +249,7 @@ pub fn verify(
     author: Option<String>,
     message: &str,
     ignore_merge_commit: bool,
+    ignore_fixup_commit: bool,
 ) -> Result<(), Box<ConventionalCommitError>> {
     // Strip away comments from git message before parsing
     let msg: String = message
@@ -264,6 +265,21 @@ pub fn verify(
         return Ok(());
     }
 
+    if msg.starts_with("fixup!") && ignore_fixup_commit {
+        info!("{}", "fixup! commit was ignored".yellow());
+        return Ok(());
+    }
+
+    if msg.starts_with("amend!") && ignore_fixup_commit {
+        info!("{}", "amend! commit was ignored".yellow());
+        return Ok(());
+    }
+
+    if msg.starts_with("squash!") && ignore_fixup_commit {
+        info!("{}", "squash! commit was ignored".yellow());
+        return Ok(());
+    }
+
     let commit = conventional_commit_parser::parse(msg);
 
     match commit {
@@ -378,7 +394,7 @@ mod test {
         let message = "feat(database): add postgresql driver";
 
         // Act
-        let result = verify(Some("toml".into()), message, false);
+        let result = verify(Some("toml".into()), message, false, false);
 
         // Assert
         assert_that!(result).is_ok();
@@ -396,7 +412,7 @@ mod test {
         );
 
         // Act
-        let result = verify(Some("toml".into()), message, false);
+        let result = verify(Some("toml".into()), message, false, false);
 
         // Assert
         assert_that!(result).is_ok();
@@ -408,7 +424,7 @@ mod test {
         let message = "feat add postgresql driver";
 
         // Act
-        let result = verify(Some("toml".into()), message, false);
+        let result = verify(Some("toml".into()), message, false, false);
 
         // Assert
         assert_that!(result).is_err();
@@ -420,7 +436,7 @@ mod test {
         let message = "post: add postgresql driver";
 
         // Act
-        let result = verify(Some("toml".into()), message, false);
+        let result = verify(Some("toml".into()), message, false, false);
 
         // Assert
         assert_that!(result).is_err();
@@ -441,7 +457,7 @@ mod test {
             "
         );
 
-        let outcome = verify(None, message, false);
+        let outcome = verify(None, message, false, false);
 
         assert_that!(outcome).is_ok();
         Ok(())
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index ef15e91..6bfda8e 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -36,6 +36,8 @@ pub struct Settings {
     pub from_latest_tag: bool,
     /// A list of glob patterns to allow bumping only on matching branches.
     pub ignore_merge_commits: bool,
+    /// Silently ignore fixup commits
+    pub ignore_fixup_commits: bool,
     /// Whether to generate a changelog or not during bump.
     pub disable_changelog: bool,
     /// Whether to create a bump commit or not.
@@ -73,6 +75,7 @@ impl Default for Settings {
         Self {
             from_latest_tag: false,
             ignore_merge_commits: false,
+            ignore_fixup_commits: true,
             disable_changelog: false,
             disable_bump_commit: false,
             generate_mono_repository_global_tag: true,


Test patch:
diff --git a/tests/cog_tests/changelog.rs b/tests/cog_tests/changelog.rs
index d0c401c..cf0d659 100644
--- a/tests/cog_tests/changelog.rs
+++ b/tests/cog_tests/changelog.rs
@@ -524,6 +524,56 @@ fn should_ignore_merge_commit() -> Result<()> {
     Ok(())
 }
 
+#[sealed_test]
+fn should_not_ignore_fixup_commit() -> Result<()> {
+    // Arrange
+    git_init()?;
+    run_cmd!(echo "ignore_fixup_commits = false" > cog.toml;)?;
+    git_commit("chore: init")?;
+    let sha = git_commit("feat: first commit")?;
+    run_cmd!(git checkout -b branch1;)?;
+    git_commit("fix: fon branch 2")?;
+    run_cmd!(
+        echo toto > titi;
+        git add .;
+        git commit --fixup $sha;
+    )?;
+
+    // Act
+    Command::cargo_bin("cog")?
+        .arg("changelog")
+        // Assert
+        .assert()
+        .success()
+        .stderr(predicates::str::contains("1 | fixup! feat: first commit"));
+
+    Ok(())
+}
+
+#[sealed_test]
+fn should_ignore_fixup_commit() -> Result<()> {
+    // Arrange
+    git_init()?;
+    git_commit("chore: init")?;
+    let sha = git_commit("feat: first commit")?;
+    run_cmd!(git checkout -b branch1;)?;
+    git_commit("fix: fon branch 2")?;
+    run_cmd!(
+        echo toto > titi;
+        git add .;
+        git commit --fixup $sha;
+    )?;
+
+    // Act
+    Command::cargo_bin("cog")?
+        .arg("changelog")
+        // Assert
+        .assert()
+        .success();
+
+    Ok(())
+}
+
 #[sealed_test]
 /// Test that the `omit_from_changelog` configuration
 /// directive is honored if/when it is specified for
diff --git a/tests/lib_tests/cocogitto.rs b/tests/lib_tests/cocogitto.rs
index 2512362..faf24fe 100644
--- a/tests/lib_tests/cocogitto.rs
+++ b/tests/lib_tests/cocogitto.rs
@@ -43,7 +43,7 @@ fn check_commit_history_ok() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(false, false, None);
+    let check = cocogitto.check(false, false, false, None);
 
     // Assert
     assert_that!(check).is_ok();
@@ -59,7 +59,7 @@ fn check_commit_history_err_with_merge_commit() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(false, false, None);
+    let check = cocogitto.check(false, false, false, None);
 
     // Assert
     assert_that!(check).is_err();
@@ -82,7 +82,7 @@ fn check_commit_history_ok_with_merge_commit_ignored() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(false, true, None);
+    let check = cocogitto.check(false, true, false, None);
 
     // Assert
     assert_that!(check).is_ok();
@@ -99,7 +99,7 @@ fn check_commit_history_err() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(false, false, None);
+    let check = cocogitto.check(false, false, false, None);
 
     // Assert
     assert_that!(check).is_err();
@@ -118,7 +118,7 @@ fn check_commit_ok_from_latest_tag() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(true, false, None);
+    let check = cocogitto.check(true, false, false, None);
 
     // Assert
     assert_that!(check).is_ok();
@@ -136,7 +136,7 @@ fn check_commit_err_from_latest_tag() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(true, false, None);
+    let check = cocogitto.check(true, false, false, None);
 
     // Assert
     assert_that!(check).is_err();
@@ -157,7 +157,7 @@ fn long_commit_summary_does_not_panic() -> Result<()> {
         ..Default::default()
     })?;
 
-    let check = cocogitto.check(false, false, None);
+    let check = cocogitto.check(false, false, false, None);
 
     assert_that!(check.is_ok());
     Ok(())
@@ -173,7 +173,7 @@ fn check_commit_ok_commit_range() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(true, false, Some(range));
+    let check = cocogitto.check(true, false, false, Some(range));
 
     // Assert
     assert_that!(check).is_ok();
@@ -191,7 +191,7 @@ fn check_commit_err_commit_range() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(true, false, Some(range));
+    let check = cocogitto.check(true, false, false, Some(range));
 
     // Assert
     assert_that!(check).is_err();
@@ -208,7 +208,7 @@ fn check_commit_range_err_with_merge_commit() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(false, false, Some(range));
+    let check = cocogitto.check(false, false, false, Some(range));
 
     // Assert
     assert_that!(check).is_err();
@@ -232,7 +232,7 @@ fn check_commit_range_ok_with_merge_commit_ignored() -> Result<()> {
     let cocogitto = CocoGitto::get()?;
 
     // Act
-    let check = cocogitto.check(false, true, Some(range));
+    let check = cocogitto.check(false, true, false, Some(range));
 
     // Assert
     assert_that!(check).is_ok();
diff --git a/tests/lib_tests/init.rs b/tests/lib_tests/init.rs
index 8c9f3ff..41b6a34 100644
--- a/tests/lib_tests/init.rs
+++ b/tests/lib_tests/init.rs
@@ -34,6 +34,5 @@ fn should_skip_initialization_if_repository_exists() -> Result<()> {
     assert_that!(status).contains("On branch master");
     assert_that!(status).contains("Changes to be committed:");
     assert_that!(status).contains("new file:   cog.toml");
-
     Ok(())
 }