Repository: cocogitto/cocogitto
Pull request #319 (2023-09-27 15:18:50)

feat: Allow users to disable changelog generation
Passing locally.

Had to add a new allow here https://github.com/cocogitto/cocogitto/pull/319/files#diff-9e57936c883548f522247f66eb2d1dc466ac71ec81d9b81dfc587f06e956473dR156, not terribly happy about that but it felt like a much bigger refactor to pull out the arguments.

cocogitto-bot[bot]: :heavy_check_mark: 5c20683 - Conventional commits check succeeded.
cocogitto-bot[bot]: :heavy_check_mark: d343281 - Conventional commits check succeeded.
cocogitto-bot[bot]: :heavy_check_mark: 7ad1110 - Conventional commits check succeeded.
oknozor: > Had to add a new allow here https://github.com/cocogitto/cocogitto/pull/319/files#diff-9e57936c883548f522247f66eb2d1dc466ac71ec81d9b81dfc587f06e956473dR156, not terribly happy about that but it felt like a much bigger refactor to pull out the arguments.

Yep I will need to address this at some point but for now don't worry about it
oknozor: You need to fix the formatting with `cargo fmt --all`. Thanks for the quick PR by the way : ) 
cocogitto-bot[bot]: :heavy_check_mark: a2280ea - Conventional commits check succeeded.
PurpleBooth: I am a little confused about the failing test, do you have any insight as to why that is happening?
cocogitto-bot[bot]: :heavy_check_mark: f674027 - Conventional commits check succeeded.
cocogitto-bot[bot]: :heavy_check_mark: 94628a6 - Conventional commits check succeeded.
cocogitto-bot[bot]: :heavy_check_mark: aa4d360 - Conventional commits check succeeded.
PurpleBooth: Think I wasn't running the tests properly first time! Should be good now
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) Report
> Merging [#319](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) (aa4d360) into [main](https://app.codecov.io/gh/cocogitto/cocogitto/commit/5b34ee4d534059f09a09d384fa57b40a3351bc66?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) (5b34ee4) will **increase** coverage by `0.05%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main     #319      +/-   ##
==========================================
+ Coverage   85.29%   85.34%   +0.05%     
==========================================
  Files          45       45              
  Lines        6283     6319      +36     
==========================================
+ Hits         5359     5393      +34     
- Misses        924      926       +2     
```


| [Files](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto) | Coverage Δ | |
|---|---|---|
| [src/command/bump/monorepo.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbW1hbmQvYnVtcC9tb25vcmVwby5ycw==) | `92.05% <100.00%> (+0.14%)` | :arrow_up: |
| [src/command/bump/package.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbW1hbmQvYnVtcC9wYWNrYWdlLnJz) | `92.68% <100.00%> (+0.57%)` | :arrow_up: |
| [src/command/bump/standard.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbW1hbmQvYnVtcC9zdGFuZGFyZC5ycw==) | `97.26% <100.00%> (+0.11%)` | :arrow_up: |
| [src/command/commit.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbW1hbmQvY29tbWl0LnJz) | `97.22% <100.00%> (ø)` | |
| [src/command/init.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2NvbW1hbmQvaW5pdC5ycw==) | `80.95% <100.00%> (ø)` | |
| [src/git/commit.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2dpdC9jb21taXQucnM=) | `98.15% <100.00%> (+0.24%)` | :arrow_up: |
| [src/git/monorepo.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2dpdC9tb25vcmVwby5ycw==) | `100.00% <100.00%> (ø)` | |
| [src/git/repository.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2dpdC9yZXBvc2l0b3J5LnJz) | `97.74% <100.00%> (ø)` | |
| [src/git/revspec.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL2dpdC9yZXZzcGVjLnJz) | `95.58% <100.00%> (-0.37%)` | :arrow_down: |
| [src/settings/mod.rs](https://app.codecov.io/gh/cocogitto/cocogitto/pull/319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto#diff-c3JjL3NldHRpbmdzL21vZC5ycw==) | `75.39% <100.00%> (+0.09%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocogitto)

oknozor: All good :) can you make a PR to update the docs at https://github.com/cocogitto/website

Patch:
diff --git a/src/bin/cog/main.rs b/src/bin/cog/main.rs
index 20b2fd2..f0444ad 100644
--- a/src/bin/cog/main.rs
+++ b/src/bin/cog/main.rs
@@ -306,6 +306,9 @@ enum Command {
         /// Install all git-hooks
         #[arg(short, long, group = "git-hooks")]
         all: bool,
+        /// Overwrite existing git-hooks
+        #[arg(short, long)]
+        overwrite: bool,
     },
 
     /// Generate shell completions
@@ -560,16 +563,17 @@ fn main() -> Result<()> {
         Command::InstallHook {
             hook_type: hook_types,
             all,
+            overwrite,
         } => {
             let cocogitto = CocoGitto::get()?;
             if all {
-                cocogitto.install_all_hooks()?;
+                cocogitto.install_all_hooks(overwrite)?;
                 return Ok(());
             };
 
             let hook_types = hook_types.into_iter().map(GitHookType::from).collect();
 
-            cocogitto.install_git_hooks(hook_types)?;
+            cocogitto.install_git_hooks(overwrite, hook_types)?;
         }
         Command::GenerateCompletions { shell } => {
             clap_complete::generate(shell, &mut Cli::command(), "cog", &mut std::io::stdout());
diff --git a/src/command/bump/monorepo.rs b/src/command/bump/monorepo.rs
index aa856c9..b2c444d 100644
--- a/src/command/bump/monorepo.rs
+++ b/src/command/bump/monorepo.rs
@@ -122,6 +122,7 @@ impl CocoGitto {
         self.repository.commit(
             &format!("chore(version): bump packages {}", skip_ci_pattern),
             sign,
+            true,
         )?;
 
         for bump in &bumps {
@@ -219,23 +220,25 @@ impl CocoGitto {
             })
         }
 
-        let pattern = self.get_revspec_for_tag(&old)?;
-        let changelog =
-            self.get_monorepo_global_changelog_with_target_version(pattern, tag.clone())?;
+        if !SETTINGS.disable_changelog {
+            let pattern = self.get_revspec_for_tag(&old)?;
+            let changelog =
+                self.get_monorepo_global_changelog_with_target_version(pattern, tag.clone())?;
 
-        changelog.pretty_print_bump_summary()?;
+            changelog.pretty_print_bump_summary()?;
 
-        let path = settings::changelog_path();
-        let template = SETTINGS.get_monorepo_changelog_template()?;
+            let path = settings::changelog_path();
+            let template = SETTINGS.get_monorepo_changelog_template()?;
 
-        changelog.write_to_file(
-            path,
-            template,
-            ReleaseType::MonoRepo(MonoRepoContext {
-                package_lock: false,
-                packages: template_context,
-            }),
-        )?;
+            changelog.write_to_file(
+                path,
+                template,
+                ReleaseType::MonoRepo(MonoRepoContext {
+                    package_lock: false,
+                    packages: template_context,
+                }),
+            )?;
+        }
 
         let current = self.repository.get_latest_tag().map(HookVersion::new).ok();
         let next_version = HookVersion::new(tag.clone());
@@ -263,6 +266,7 @@ impl CocoGitto {
                 next_version.prefixed_tag, skip_ci_pattern
             ),
             sign,
+            true,
         )?;
 
         for bump in &bumps {
@@ -347,23 +351,25 @@ impl CocoGitto {
             })
         }
 
-        let pattern = self.get_revspec_for_tag(&old)?;
-        let changelog =
-            self.get_monorepo_global_changelog_with_target_version(pattern, tag.clone())?;
+        if !SETTINGS.disable_changelog {
+            let pattern = self.get_revspec_for_tag(&old)?;
+            let changelog =
+                self.get_monorepo_global_changelog_with_target_version(pattern, tag.clone())?;
 
-        changelog.pretty_print_bump_summary()?;
+            changelog.pretty_print_bump_summary()?;
 
-        let path = settings::changelog_path();
-        let template = SETTINGS.get_monorepo_changelog_template()?;
+            let path = settings::changelog_path();
+            let template = SETTINGS.get_monorepo_changelog_template()?;
 
-        changelog.write_to_file(
-            path,
-            template,
-            ReleaseType::MonoRepo(MonoRepoContext {
-                package_lock: true,
-                packages: template_context,
-            }),
-        )?;
+            changelog.write_to_file(
+                path,
+                template,
+                ReleaseType::MonoRepo(MonoRepoContext {
+                    package_lock: true,
+                    packages: template_context,
+                }),
+            )?;
+        }
 
         let current = self.repository.get_latest_tag().map(HookVersion::new).ok();
         let next_version = HookVersion::new(tag.clone());
@@ -387,6 +393,7 @@ impl CocoGitto {
                 next_version.prefixed_tag, skip_ci_pattern
             ),
             sign,
+            true,
         )?;
 
         if let Some(msg_tmpl) = annotated {
diff --git a/src/command/bump/package.rs b/src/command/bump/package.rs
index 877967d..0024e05 100644
--- a/src/command/bump/package.rs
+++ b/src/command/bump/package.rs
@@ -50,17 +50,18 @@ impl CocoGitto {
             return Ok(());
         }
 
-        let pattern = self.get_revspec_for_tag(&current_tag)?;
+        if !SETTINGS.disable_changelog {
+            let pattern = self.get_revspec_for_tag(&current_tag)?;
+            let changelog =
+                self.get_package_changelog_with_target_version(pattern, tag.clone(), package_name)?;
 
-        let changelog =
-            self.get_package_changelog_with_target_version(pattern, tag.clone(), package_name)?;
+            changelog.pretty_print_bump_summary()?;
 
-        changelog.pretty_print_bump_summary()?;
-
-        let path = package.changelog_path();
-        let template = SETTINGS.get_package_changelog_template()?;
-        let additional_context = ReleaseType::Package(PackageContext { package_name });
-        changelog.write_to_file(path, template, additional_context)?;
+            let path = package.changelog_path();
+            let template = SETTINGS.get_package_changelog_template()?;
+            let additional_context = ReleaseType::Package(PackageContext { package_name });
+            changelog.write_to_file(path, template, additional_context)?;
+        }
 
         let current = self
             .repository
@@ -88,8 +89,11 @@ impl CocoGitto {
 
         let skip_ci_pattern = skip_ci.unwrap_or(SETTINGS.skip_ci.clone().unwrap_or_default());
 
-        self.repository
-            .commit(&format!("chore(version): {tag} {}", skip_ci_pattern), sign)?;
+        self.repository.commit(
+            &format!("chore(version): {tag} {}", skip_ci_pattern),
+            sign,
+            true,
+        )?;
 
         if let Some(msg_tmpl) = annotated {
             let mut context = tera::Context::new();
diff --git a/src/command/bump/standard.rs b/src/command/bump/standard.rs
index 1ff3850..7a0ffe7 100644
--- a/src/command/bump/standard.rs
+++ b/src/command/bump/standard.rs
@@ -49,13 +49,16 @@ impl CocoGitto {
         }
 
         let pattern = self.get_revspec_for_tag(&current_tag)?;
-        let changelog = self.get_changelog_with_target_version(pattern, tag.clone())?;
-        changelog.pretty_print_bump_summary()?;
 
-        let path = settings::changelog_path();
-        let template = SETTINGS.get_changelog_template()?;
+        if !SETTINGS.disable_changelog {
+            let changelog = self.get_changelog_with_target_version(pattern, tag.clone())?;
+            changelog.pretty_print_bump_summary()?;
 
-        changelog.write_to_file(path, template, ReleaseType::Standard)?;
+            let path = settings::changelog_path();
+            let template = SETTINGS.get_changelog_template()?;
+
+            changelog.write_to_file(path, template, ReleaseType::Standard)?;
+        }
 
         let current = self.repository.get_latest_tag().map(HookVersion::new).ok();
 
@@ -81,6 +84,7 @@ impl CocoGitto {
                 next_version.prefixed_tag, skip_ci_pattern
             ),
             sign,
+            true,
         )?;
 
         if let Some(msg_tmpl) = annotated {
diff --git a/src/command/commit.rs b/src/command/commit.rs
index b19d791..559cdb2 100644
--- a/src/command/commit.rs
+++ b/src/command/commit.rs
@@ -45,7 +45,7 @@ impl CocoGitto {
         let sign = sign || self.repository.gpg_sign();
         fs::write(self.prepare_edit_message_path(), &conventional_message)?;
         self.run_commit_hook(CommitMessage)?;
-        let oid = self.repository.commit(&conventional_message, sign)?;
+        let oid = self.repository.commit(&conventional_message, sign, false)?;
 
         // Pretty print a conventional commit summary
         let commit = self.repository.0.find_commit(oid)?;
diff --git a/src/command/git_hooks.rs b/src/command/git_hooks.rs
index d2c5c69..b68b387 100644
--- a/src/command/git_hooks.rs
+++ b/src/command/git_hooks.rs
@@ -4,7 +4,7 @@ use crate::{CocoGitto, SETTINGS};
 use anyhow::{anyhow, Result};
 
 impl CocoGitto {
-    pub fn install_all_hooks(&self) -> Result<()> {
+    pub fn install_all_hooks(&self, overwrite_existing_hooks: bool) -> Result<()> {
         let repodir = &self
             .repository
             .get_repo_dir()
@@ -12,13 +12,17 @@ impl CocoGitto {
             .to_path_buf();
 
         for (hook_type, hook) in SETTINGS.git_hooks.iter() {
-            install_git_hook(repodir, hook_type, hook)?;
+            install_git_hook(repodir, overwrite_existing_hooks, hook_type, hook)?;
         }
 
         Ok(())
     }
 
-    pub fn install_git_hooks(&self, hook_types: Vec<GitHookType>) -> Result<()> {
+    pub fn install_git_hooks(
+        &self,
+        overwrite_existing_hooks: bool,
+        hook_types: Vec<GitHookType>,
+    ) -> Result<()> {
         let repodir = &self
             .repository
             .get_repo_dir()
@@ -30,7 +34,7 @@ impl CocoGitto {
                 .git_hooks
                 .get(&hook_type)
                 .ok_or(anyhow!("git-hook {hook_type} was not found in cog.toml"))?;
-            install_git_hook(repodir, &hook_type, hook)?
+            install_git_hook(repodir, overwrite_existing_hooks, &hook_type, hook)?
         }
 
         Ok(())
diff --git a/src/command/init.rs b/src/command/init.rs
index 857728d..9ba4a34 100644
--- a/src/command/init.rs
+++ b/src/command/init.rs
@@ -57,7 +57,7 @@ pub fn init<S: AsRef<Path> + ?Sized>(path: &S) -> anyhow::Result<()> {
 
     if is_init_commit {
         let sign = repository.gpg_sign();
-        repository.commit("chore: initial commit", sign)?;
+        repository.commit("chore: initial commit", sign, false)?;
     }
 
     Ok(())
diff --git a/src/git/commit.rs b/src/git/commit.rs
index 99b19fb..a03e222 100644
--- a/src/git/commit.rs
+++ b/src/git/commit.rs
@@ -5,21 +5,27 @@ use std::io::Write;
 use std::process::{Command, Stdio};
 
 impl Repository {
-    pub(crate) fn commit(&self, message: &str, sign: bool) -> Result<Oid, Git2Error> {
+    pub(crate) fn commit(
+        &self,
+        message: &str,
+        sign: bool,
+        allow_empty_delta: bool,
+    ) -> Result<Oid, Git2Error> {
         let sig = self.0.signature()?;
         let tree_id = self.0.index()?.write_tree()?;
         let tree = self.0.find_tree(tree_id)?;
         let is_empty = self.0.head().is_err();
         let has_delta = self.get_diff(false).is_some();
+        let has_delta_or_allowed_empty = has_delta || allow_empty_delta;
 
-        if !is_empty && has_delta {
+        if !is_empty && has_delta_or_allowed_empty {
             let head = &self.0.head()?;
             let head_target = head.target().expect("Cannot get HEAD target");
             let tip = &self.0.find_commit(head_target)?;
 
             self.commit_or_signed_commit(&sig, message, &tree, &[tip], sign)
                 .map_err(Git2Error::from)
-        } else if is_empty && has_delta {
+        } else if is_empty && has_delta_or_allowed_empty {
             // First repo commit
             self.commit_or_signed_commit(&sig, message, &tree, &[], sign)
                 .map_err(Git2Error::from)
@@ -121,7 +127,7 @@ mod test {
         let repo = Repository::open(".")?;
 
         // Act
-        let oid = repo.commit("feat: a test commit", false);
+        let oid = repo.commit("feat: a test commit", false, false);
 
         // Assert
         assert_that!(oid).is_ok();
@@ -148,7 +154,26 @@ mod test {
         let repo = Repository::open(".")?;
 
         // Act
-        let oid = repo.commit("feat: a test commit", true);
+        let oid = repo.commit("feat: a test commit", true, false);
+
+        // Assert
+        assert_that!(oid).is_ok();
+        Ok(())
+    }
+
+    #[sealed_test]
+    fn create_empty_commit() -> Result<()> {
+        init_builtin_logger();
+
+        // Arrange
+        run_cmd!(
+            git init;
+        )?;
+
+        let repo = Repository::open(".")?;
+
+        // Act
+        let oid = repo.commit("feat: a test commit", false, true);
 
         // Assert
         assert_that!(oid).is_ok();
@@ -168,7 +193,7 @@ mod test {
         let repo = Repository::open(".").expect("could not open git repository");
 
         // Act
-        let oid = repo.commit("feat: a test commit", false);
+        let oid = repo.commit("feat: a test commit", false, false);
 
         // Assert
         assert_that!(oid).is_ok();
@@ -180,7 +205,7 @@ mod test {
         let repo = Repository::init(".")?;
 
         // Act
-        let oid = repo.commit("feat: a test commit", false);
+        let oid = repo.commit("feat: a test commit", false, false);
 
         // Assert
         assert_that!(oid).is_err();
@@ -198,7 +223,7 @@ mod test {
         let repo = Repository::open(".")?;
 
         // Act
-        let oid = repo.commit("feat: a test commit", false);
+        let oid = repo.commit("feat: a test commit", false, false);
 
         // Assert
         assert_that!(oid).is_err();
diff --git a/src/git/hook.rs b/src/git/hook.rs
index f86030b..02b7579 100644
--- a/src/git/hook.rs
+++ b/src/git/hook.rs
@@ -7,11 +7,16 @@ use std::path::Path;
 use crate::settings::{GitHook, GitHookType};
 use anyhow::Result;
 
-pub fn install_git_hook(repodir: &Path, hook_type: &GitHookType, hook: &GitHook) -> Result<()> {
+pub fn install_git_hook(
+    repodir: &Path,
+    overwrite_existing_hooks: bool,
+    hook_type: &GitHookType,
+    hook: &GitHook,
+) -> Result<()> {
     let hook_path = repodir.join(".git/hooks");
     let hook_path = hook_path.join::<&str>((*hook_type).into());
 
-    if hook_path.exists() {
+    if !overwrite_existing_hooks && hook_path.exists() {
         let mut answer = String::new();
         println!(
             "Git hook `{}` exists. (Overwrite Y/n)",
@@ -87,7 +92,7 @@ exit 1"#
         let cog = CocoGitto::get()?;
 
         // Act
-        cog.install_git_hooks(vec![GitHookType::CommitMsg])?;
+        cog.install_git_hooks(true, vec![GitHookType::CommitMsg])?;
 
         // Assert
         assert_that!(Path::new(".git/hooks/commit-msg")).exists();
@@ -138,7 +143,7 @@ exit 1"#
         let cog = CocoGitto::get()?;
 
         // Act
-        cog.install_all_hooks()?;
+        cog.install_all_hooks(true)?;
 
         // Assert
         assert_that!(Path::new(".git/hooks/commit-msg")).exists();
@@ -150,4 +155,71 @@ exit 1"#
         assert_that!(hook).is_equal_to("echo toto\n".to_string());
         Ok(())
     }
+
+    #[sealed_test]
+    fn overwrite_pre_commit_hook() -> Result<()> {
+        // Arrange
+        run_cmd!(git init)?;
+        let mut git_hooks = HashMap::new();
+        let hooks_script = r#"
+if cog check; then
+    exit 0
+fi
+
+echo "Invalid commits were found, force push with '--no-verify'"
+exit 1"#
+            .to_string();
+
+        git_hooks.insert(
+            GitHookType::CommitMsg,
+            GitHook::Script {
+                script: hooks_script.clone(),
+            },
+        );
+
+        let settings = Settings {
+            git_hooks,
+            ..Default::default()
+        };
+
+        let settings = toml::to_string(&settings)?;
+        fs::write("cog.toml", settings)?;
+
+        let cog = CocoGitto::get()?;
+
+        // Act
+        cog.install_git_hooks(false, vec![GitHookType::CommitMsg])?;
+
+        // Assert
+        assert_that!(Path::new(".git/hooks/commit-msg")).exists();
+        let hooks = fs::read_to_string(".git/hooks/commit-msg")?;
+        assert_that!(hooks).is_equal_to(&hooks_script);
+        assert_that!(Path::new(".git/hooks/pre-push")).does_not_exist();
+
+        // Prepare: create empty file
+        fs::write(".git/hooks/commit-msg", "")?;
+
+        // Act 2: reinstall hooks with overwrite true
+        cog.install_git_hooks(true, vec![GitHookType::CommitMsg])?;
+
+        // Assert
+        assert_that!(Path::new(".git/hooks/commit-msg")).exists();
+        let hooks = fs::read_to_string(".git/hooks/commit-msg")?;
+        assert_that!(hooks).is_equal_to(&hooks_script);
+        assert_that!(Path::new(".git/hooks/pre-push")).does_not_exist();
+
+        // Prepare: create empty file
+        fs::write(".git/hooks/commit-msg", "")?;
+
+        // Act 3: reinstall hooks without overwrite true
+        cog.install_git_hooks(false, vec![GitHookType::CommitMsg])?;
+
+        // Assert: file must still be empty
+        assert_that!(Path::new(".git/hooks/commit-msg")).exists();
+        let hooks = fs::read_to_string(".git/hooks/commit-msg")?;
+        assert_that!(hooks).is_empty();
+        assert_that!(Path::new(".git/hooks/pre-push")).does_not_exist();
+
+        Ok(())
+    }
 }
diff --git a/src/git/monorepo.rs b/src/git/monorepo.rs
index 804c448..2a4dc0a 100644
--- a/src/git/monorepo.rs
+++ b/src/git/monorepo.rs
@@ -51,7 +51,7 @@ mod test {
         )?;
 
         let repo = Repository::open(".")?;
-        repo.commit("chore: init", false)?;
+        repo.commit("chore: init", false, false)?;
 
         run_cmd!(
             mkdir one;
diff --git a/src/git/repository.rs b/src/git/repository.rs
index fd80816..f3ee888 100644
--- a/src/git/repository.rs
+++ b/src/git/repository.rs
@@ -148,7 +148,7 @@ mod test {
             git add .;
         )?;
         let repo = Repository::open(".")?;
-        let commit_oid = repo.commit("first commit", false)?;
+        let commit_oid = repo.commit("first commit", false, false)?;
 
         // Act
         let oid = repo.get_head_commit_oid();
@@ -180,7 +180,7 @@ mod test {
             git add .;
         )?;
         let repo = Repository::open(".")?;
-        let commit_oid = repo.commit("first commit", false)?;
+        let commit_oid = repo.commit("first commit", false, false)?;
 
         // Act
         let head = repo.get_head_commit().map(|head| head.id());
@@ -218,7 +218,7 @@ mod test {
         )?;
         let repo = Repository::open(".")?;
 
-        repo.commit("first commit", false)?;
+        repo.commit("first commit", false, false)?;
 
         // Act
         let head = repo.get_head();
@@ -256,7 +256,7 @@ mod test {
             git add .;
         )?;
         let repo = Repository::open(".")?;
-        repo.commit("hello one", false)?;
+        repo.commit("hello one", false, false)?;
 
         // Act
         let shorthand = repo.get_branch_shorthand();
diff --git a/src/git/revspec.rs b/src/git/revspec.rs
index 88f3616..a7bf092 100644
--- a/src/git/revspec.rs
+++ b/src/git/revspec.rs
@@ -472,7 +472,7 @@ mod test {
             git add .;
         )?;
 
-        let start = repo.commit("chore: init", false)?;
+        let start = repo.commit("chore: init", false, false)?;
 
         run_cmd!(
             git init;
@@ -480,7 +480,7 @@ mod test {
             git add .;
         )?;
 
-        let _end = repo.commit("chore: 1.0.0", false)?;
+        let _end = repo.commit("chore: 1.0.0", false, false)?;
 
         // Create an annotated tag
         let head = repo.get_head_commit().unwrap();
@@ -494,7 +494,7 @@ mod test {
             git add .;
         )?;
 
-        repo.commit("feat: a commit", false)?;
+        repo.commit("feat: a commit", false, false)?;
 
         let commit_range = repo.get_commit_range(&RevspecPattern::from_str("..1.0.0")?)?;
 
@@ -559,7 +559,7 @@ mod test {
             git add .;
         )?;
 
-        let start = repo.commit("chore: init", false)?;
+        let start = repo.commit("chore: init", false, false)?;
 
         run_cmd!(
             git commit --allow-empty -m "chore: 1.0.0";
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index f9d061c..354751e 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -32,6 +32,7 @@ pub enum HookType {
 pub struct Settings {
     pub from_latest_tag: bool,
     pub ignore_merge_commits: bool,
+    pub disable_changelog: bool,
     pub generate_mono_repository_global_tag: bool,
     pub monorepo_version_separator: Option<String>,
     pub branch_whitelist: Vec<String>,
@@ -54,6 +55,7 @@ impl Default for Settings {
         Self {
             from_latest_tag: false,
             ignore_merge_commits: false,
+            disable_changelog: false,
             generate_mono_repository_global_tag: true,
             monorepo_version_separator: None,
             branch_whitelist: vec![],


Test patch:
diff --git a/tests/cog_tests/bump.rs b/tests/cog_tests/bump.rs
index b8aef10..62ef35e 100644
--- a/tests/cog_tests/bump.rs
+++ b/tests/cog_tests/bump.rs
@@ -120,6 +120,66 @@ fn auto_bump_with_prefix() -> Result<()> {
     Ok(())
 }
 
+#[sealed_test]
+fn disable_changelog_disables_changelog_generation() -> Result<()> {
+    git_init()?;
+    git_add("disable_changelog = true", "cog.toml")?;
+    git_commit("chore: first commit")?;
+    git_commit("feat: add a feature commit")?;
+    git_tag("1.0.0")?;
+    git_commit("feat: add another feature commit")?;
+    Command::cargo_bin("cog")?
+        .arg("bump")
+        .arg("--auto")
+        .assert()
+        .success();
+
+    assert_that!(Path::new("CHANGELOG.md")).does_not_exist();
+    assert_tag_exists("1.0.0")?;
+    assert_tag_exists("1.1.0")?;
+    Ok(())
+}
+
+#[sealed_test]
+fn disable_changelog_disables_changelog_generation_for_monorepos() -> Result<()> {
+    let mut settings = Settings {
+        disable_changelog: true,
+        ..Default::default()
+    };
+    init_monorepo(&mut settings)?;
+    Command::cargo_bin("cog")?
+        .arg("bump")
+        .arg("--auto")
+        .assert()
+        .success();
+
+    assert_that!(Path::new("CHANGELOG.md")).does_not_exist();
+    assert_tag_exists("0.1.0")?;
+    assert_tag_exists("one-0.1.0")?;
+    Ok(())
+}
+
+#[sealed_test]
+fn disable_changelog_disables_changelog_generation_for_packages() -> Result<()> {
+    let mut settings = Settings {
+        disable_changelog: true,
+        ..Default::default()
+    };
+    init_monorepo(&mut settings)?;
+
+    Command::cargo_bin("cog")?
+        .arg("bump")
+        .arg("--auto")
+        .arg("--package")
+        .arg("one")
+        .assert()
+        .success();
+
+    assert_that!(Path::new("CHANGELOG.md")).does_not_exist();
+    assert_tag_exists("one-0.1.0")?;
+    Ok(())
+}
+
 #[sealed_test]
 fn auto_bump_patch_from_latest_tag() -> Result<()> {
     git_init()?;
diff --git a/tests/cog_tests/changelog.rs b/tests/cog_tests/changelog.rs
index 3118470..eca728f 100644
--- a/tests/cog_tests/changelog.rs
+++ b/tests/cog_tests/changelog.rs
@@ -361,6 +361,48 @@ fn get_changelog_from_tag_to_tagged_head() -> Result<()> {
     Ok(())
 }
 
+#[sealed_test]
+fn get_changelog_is_unaffected_by_disable_changelog() -> Result<()> {
+    // Arrange
+    git_init()?;
+
+    let cog_toml = indoc!("disable_changelog = true");
+
+    run_cmd!(echo $cog_toml > cog.toml;)?;
+    git_commit("chore: init")?;
+    let commit_one = git_commit("feat: start")?;
+    let commit_two = git_commit("feat: feature 1")?;
+    git_tag("1.0.0")?;
+
+    // Act
+    let changelog = Command::cargo_bin("cog")?
+        .arg("changelog")
+        // Assert
+        .assert()
+        .success();
+
+    let changelog = changelog.get_output();
+    let changelog = String::from_utf8_lossy(&changelog.stdout);
+    let today = Utc::now().date_naive();
+
+    assert_eq!(
+        changelog.as_ref(),
+        formatdoc!(
+            "## 1.0.0 - {today}
+                #### Features
+                - feature 1 - ({commit_two}) - Tom
+                - start - ({commit_one}) - Tom
+
+
+                ",
+            today = today,
+            commit_one = &commit_one[0..7],
+            commit_two = &commit_two[0..7],
+        )
+    );
+    Ok(())
+}
+
 #[sealed_test]
 fn get_changelog_with_custom_template() -> Result<()> {
     // Arrange
diff --git a/tests/lib_tests/bump.rs b/tests/lib_tests/bump.rs
index ac80f92..f01db86 100644
--- a/tests/lib_tests/bump.rs
+++ b/tests/lib_tests/bump.rs
@@ -1,3 +1,4 @@
+use crate::helpers::*;
 use anyhow::Result;
 use cmd_lib::run_cmd;
 use cocogitto::settings::{MonoRepoPackage, Settings};
@@ -5,6 +6,7 @@ use cocogitto::{conventional::version::IncrementCommand, CocoGitto};
 use sealed_test::prelude::*;
 use speculoos::prelude::*;
 use std::collections::HashMap;
+use std::path::Path;
 use std::path::PathBuf;
 
 use crate::helpers::*;
@@ -118,6 +120,38 @@ fn monorepo_bump_manual_ok() -> Result<()> {
     assert_tag_exists("1.0.0")?;
     Ok(())
 }
+#[sealed_test]
+fn monorepo_bump_manual_disable_changelog_ok() -> Result<()> {
+    // Arrange
+    let mut settings = Settings {
+        disable_changelog: true,
+        ..Default::default()
+    };
+
+    init_monorepo(&mut settings)?;
+    run_cmd!(
+        git tag "one-0.1.0";
+    )?;
+
+    let mut cocogitto = CocoGitto::get()?;
+
+    // Act
+    let result = cocogitto.create_monorepo_version(
+        IncrementCommand::Major,
+        None,
+        None,
+        None,
+        false,
+        None,
+        false,
+    );
+
+    // Assert
+    assert_that!(result).is_ok();
+    assert_tag_exists("1.0.0")?;
+    assert_that!(Path::new("CHANGELOG.md")).does_not_exist();
+    Ok(())
+}
 
 #[sealed_test]
 fn monorepo_with_tag_prefix_bump_ok() -> Result<()> {