Repository: carloscuesta/gitmoji-cli (JavaScript)

Issue:
Disable hook if commit message already starts with emoji
### Description of the problem

Context: automating bumping versions of node packages

```sh
❯ gitmoji -i
❯ git commit -a -m "🔖 Bump version to $npm_package_version"
? Choose a gitmoji: 🔖  - Release / Version tags.
? Enter the commit title [18/48]: 🔖 Bump version to 0.1.0
? Enter the commit message: 
[feature/abc 468583a] 🔖 🔖 Bump version to 0.1.0
 2 files changed, 4 insertions(+), 4 deletions(-)
```

Could we detect in the prepare commit message hook that the commit message already contains an emoji and if so skip the emoji prompt?

Or still show the prompt but preselect the result with the emoji from the commit message?

### Solution

```
const commit = (options: CommitOptions): Function => {
  if (options.mode === COMMIT_MODES.HOOK) {
  registerHookInterruptionHandler()
    if (options.message?.startsWith(anyEmoji)) {
        commit(options)
     else {
        
        return cancelIfNeeded().then(() => promptAndCommit(options))
    }
  }

  return promptAndCommit(options)
}
```

### Alternatives

`git commit --no-verify` doesn't seem to prevent the gitmoji hook.

### Additional context

_No response_

### Validations

- [X] Follow our [Code of Conduct](https://github.com/carloscuesta/gitmoji-cli/blob/master/CODE_OF_CONDUCT.md)
- [X] Read the [Contributing Guide](https://github.com/carloscuesta/gitmoji-cli/blob/master/.github/CONTRIBUTING.md).
- [X] Check that there isn't already an issue requesting the same feature.


Discussion:
Hey!

Thanks for opening the issue, I think it makes sense but we should probably aim to support the `--no-verify` option instead of doing this "magic" of skipping the hook if the emoji is provided already 

Feel free to raise a Pull Request! 🙏🏼 
Hey! So I took at stab at this and found out it's not possible to use `--no-verify` for the `prepare-commit-msg` hook (it is ignored) [as mentioned in the docs](https://git-scm.com/docs/githooks#_prepare_commit_msg)

> <img width="681" alt="CleanShot 2023-05-31 at 17 44 37@2x" src="https://github.com/carloscuesta/gitmoji-cli/assets/7629661/59a8078f-5772-4df9-b16c-d927e015abae">

I implemented the PR as an alternative solution to this.

We might want to explore switching from `prepare-commit-msg` to `commit-msg` to support `--no-verify` 
Released in `v8.3.0` 🔖 https://github.com/carloscuesta/gitmoji-cli/releases/tag/v8.3.0



Patch:
diff --git a/src/commands/commit/index.js b/src/commands/commit/index.js
index d47076462..3342cecdf 100644
--- a/src/commands/commit/index.js
+++ b/src/commands/commit/index.js
@@ -6,7 +6,8 @@ import getEmojis from '@utils/getEmojis'
 import COMMIT_MODES from '@constants/commit'
 import withHook, {
   registerHookInterruptionHandler,
-  cancelIfNeeded
+  cancelIfNeeded,
+  skipIfGitmojiIsPresent
 } from './withHook'
 import withClient from './withClient'
 import prompts from './prompts'
@@ -44,6 +45,7 @@ const promptAndCommit = (options: CommitOptions): Function =>
 
 const commit = (options: CommitOptions): Function => {
   if (options.mode === COMMIT_MODES.HOOK) {
+    skipIfGitmojiIsPresent(options)
     registerHookInterruptionHandler()
     return cancelIfNeeded().then(() => promptAndCommit(options))
   }
diff --git a/src/commands/commit/prompts.js b/src/commands/commit/prompts.js
index 33a1a2e7d..0a50fd95a 100644
--- a/src/commands/commit/prompts.js
+++ b/src/commands/commit/prompts.js
@@ -21,9 +21,9 @@ export type Gitmoji = {
 
 export type Answers = {
   gitmoji: string,
-  scope?: string,
+  scope: ?string,
   title: string,
-  message?: string
+  message: ?string
 }
 
 export default (
diff --git a/src/commands/commit/withHook/index.js b/src/commands/commit/withHook/index.js
index f07527bc5..9ed24d405 100644
--- a/src/commands/commit/withHook/index.js
+++ b/src/commands/commit/withHook/index.js
@@ -2,7 +2,9 @@
 import { execa } from 'execa'
 import fs from 'fs'
 
+import getDefaultCommitContent from '@utils/getDefaultCommitContent'
 import { type Answers } from '../prompts'
+import { type CommitOptions } from '../index'
 
 const withHook = (answers: Answers) => {
   try {
@@ -67,4 +69,17 @@ export const cancelIfAmending = (): Promise<void> =>
 export const cancelIfNeeded = (): Promise<void> =>
   cancelIfAmending().then(cancelIfRebasing)
 
+export const skipIfGitmojiIsPresent = (options: CommitOptions) => {
+  const { title } = getDefaultCommitContent(options)
+  const UNICODE_EMOJI_REGEX =
+    /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u
+  const SHORTCODE_EMOJI_REGEX = /:[a-z_]+:/
+
+  if (!title) return
+
+  if (UNICODE_EMOJI_REGEX.test(title) || SHORTCODE_EMOJI_REGEX.test(title)) {
+    process.exit(0)
+  }
+}
+
 export default withHook


Test patch:
diff --git a/test/commands/commit.spec.js b/test/commands/commit.spec.js
index a06bce851..8fcd58d57 100644
--- a/test/commands/commit.spec.js
+++ b/test/commands/commit.spec.js
@@ -14,7 +14,7 @@ import prompts from '@commands/commit/prompts'
 import * as stubs from './stubs'
 import { COMMIT_MESSAGE_SOURCE } from '@commands/commit/withHook/index'
 
-jest.mock('@utils/getDefaultCommitContent')
+jest.mock('@utils/getDefaultCommitContent', () => jest.fn().mockReturnValue({}))
 jest.mock('@utils/getEmojis')
 jest.mock('@utils/isHookCreated')
 jest.mock('@utils/configurationVault')
@@ -319,6 +319,46 @@ describe('commit command', () => {
         expect(process.exit).toHaveBeenCalledWith(0)
       })
     })
+
+    describe('when gitmoji is present in the title', () => {
+      beforeAll(() => {
+        mockProcessExit(new Error('ProcessExit0'))
+        execa.mockReturnValueOnce(Promise.resolve(stubs.gitAbsoluteDir))
+        getEmojis.mockResolvedValue(stubs.gitmojis)
+      })
+
+      describe('as a shortcode', () => {
+        beforeAll(() => {
+          getDefaultCommitContent.mockReturnValueOnce(
+            stubs.gitmojiShortcodeCommitContent
+          )
+        })
+
+        it('should cancel the hook', async () => {
+          try {
+            await commit({ mode: 'hook' })
+          } catch (e) {
+            expect(process.exit).toHaveBeenCalledWith(0)
+          }
+        })
+      })
+
+      describe('as a unicode', () => {
+        beforeAll(() => {
+          getDefaultCommitContent.mockReturnValueOnce(
+            stubs.gitmojiUnicodeCommitContent
+          )
+        })
+
+        it('should cancel the hook', async () => {
+          try {
+            await commit({ mode: 'hook' })
+          } catch (e) {
+            expect(process.exit).toHaveBeenCalledWith(0)
+          }
+        })
+      })
+    })
   })
 
   describe('guard', () => {
diff --git a/test/commands/stubs.js b/test/commands/stubs.js
index de3136e0e..d2e71df37 100644
--- a/test/commands/stubs.js
+++ b/test/commands/stubs.js
@@ -63,4 +63,14 @@ export const defaultCommitContent = {
   message: 'commit message'
 }
 
+export const gitmojiShortcodeCommitContent = {
+  title: ':zap: commit title',
+  message: 'commit message'
+}
+
+export const gitmojiUnicodeCommitContent = {
+  title: '🔥 commit title',
+  message: 'commit message'
+}
+
 export const url = 'https://carloscuesta.me'