Hi everyone, yesterday I have instructions on using ktlint and detekt to check code style, lint for android . Today, continue to improve the code series , I continue to share about how to run the above tools automatically when commit code .
OK, let’s get started!
Git Hooks
In the past, I always wanted to find a tool to help eliminate basic git errors and coding convention before sending PR , like commit messages according to company standards, the code must be formatted in general, the unit test must be run pass. local or lint errors must be handled … find it often so I have to remind you, sometimes remind it to unload. Because everyone’s code is managed on a personal computer, whoever does anything intervenes, I think I will always be hated because of my interest every day, repeating things like “Yeah I know, it is hard, say forever “. And then…
As if I accidentally picked up a secret, I stumbled upon a way to check the keyword commit message rule git
and git hooks
appeared as salvation. With git hooks , all my problems have been completely solved.
What are git hooks?
Like other version control systems, Git also provides us with a way to interfere with some of its special processes with custom scripts , namely
hook
.
How do Git hooks become saviors?
I immediately selected 2 client hook
suitable for checking commit message rules , and running tools / tasks that automatically code style , lint , unit tests before committing .
pre-commit
: This hook is called first, executed before you enter the content for the commit message . This hook is used to check the contents of the committed files. You can write scripts to test code convention , run tests or run static analysis before committing . If the script returns greater than zero, the commit will be aborted.commit-msg
: It is called after we have entered the message content for the commit . Suitable for normalizing commit messages . The only parameter that is received in this hook is the file name containing the commit message . Similarly, if this script returns a result greater than zero, the commit will be canceled.
Setting
Create a folder team-props
with the following structure:
Note: if you change the name or directory structure, the steps below you have to manually search and fix the path to the files according to your directory.
Create script checking commit message ( commit-msg.sh
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token comment">#!/usr/bin/env bash</span> <span class="token keyword">echo</span> <span class="token string">"Checking your commit message"</span> <span class="token operator"><<</span> <span class="token string">comment Bạn có thể comment theo cú pháp này. Bạn có thể thay đổi message regex theo mong muốn. Lưu ý có sự khác nhau regex trên Mac vs Linux. comment</span> commit_regex <span class="token operator">=</span> <span class="token string">'^[b(add|modify|fix|revert|hotfix)b]s.+$'</span> error_msg <span class="token operator">=</span> <span class="token string">"Aborting commit. Your commit message does not follow the commit message rule"</span> <span class="token keyword">if</span> <span class="token operator">!</span> <span class="token function">grep</span> -iqE <span class="token string">" <span class="token variable">$commit_regex</span> "</span> <span class="token string">" <span class="token variable">$1</span> "</span> <span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token keyword">echo</span> <span class="token string">" <span class="token variable">$error_msg</span> "</span> <span class="token operator">></span> <span class="token operator">&</span> 2 <span class="token keyword">exit</span> 1 <span class="token keyword">fi</span> |
For Linux:
1 2 | commit_regex <span class="token operator">=</span> <span class="token string">'^[b(add|modify|fix|revert|hotfix)b]s.+$'</span> |
For Mac:
1 2 | commit_regex <span class="token operator">=</span> <span class="token string">'^[b(?i)(add|modify|fix|revert|hotfix)b]s.+$'</span> |
You can replace regex as desired, currently I am following the rules of the project .
For example, the following commits will be valid:
1 2 3 4 5 6 7 | [Add] Feature A [Modify] Feature B [Fix] Correct layout C [Revert] Feature B [Hotfix] Sometime popup is not dismissed in main page production. ... |
Create scripts that automatically run gradle tasks and Unit tests ( pre-commit.sh
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <span class="token comment">#!/usr/bin/env bash</span> <span class="token keyword">echo</span> <span class="token string">"Running static analysis..."</span> <span class="token keyword">echo</span> <span class="token string">"Start running ktlint"</span> ./gradlew ktlintFormat ktlintCheck --daemon status0 <span class="token operator">=</span> <span class="token variable">$?</span> <span class="token keyword">if</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span> <span class="token string">" <span class="token variable">$status0</span> "</span> <span class="token operator">=</span> 0 <span class="token punctuation">]</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token keyword">echo</span> <span class="token string">"ktlint found no problems."</span> <span class="token keyword">else</span> <span class="token keyword">echo</span> 1 <span class="token operator">></span> <span class="token operator">&</span> 2 <span class="token string">"ktlint found violations, it could not fix. Please check ktlint report at "build/reports/ktlint/ktlint.html" "</span> <span class="token keyword">exit</span> 1 <span class="token keyword">fi</span> <span class="token keyword">echo</span> <span class="token string">"Start running detektCheck"</span> ./gradlew detekt --daemon status1 <span class="token operator">=</span> <span class="token variable">$?</span> <span class="token keyword">if</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span> <span class="token string">" <span class="token variable">$status1</span> "</span> <span class="token operator">=</span> 0 <span class="token punctuation">]</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token keyword">echo</span> <span class="token string">"detekt found no problems."</span> <span class="token keyword">else</span> <span class="token keyword">echo</span> 1 <span class="token operator">></span> <span class="token operator">&</span> 2 <span class="token string">"ktlint found violations. Please check ktlint report at "build/reports/detekt/detekt-report.html" "</span> <span class="token keyword">exit</span> 1 <span class="token keyword">fi</span> <span class="token keyword">echo</span> <span class="token string">"Start running unit test"</span> ./gradlew testDevelopDebug --daemon status2 <span class="token operator">=</span> <span class="token variable">$?</span> <span class="token keyword">if</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span> <span class="token string">" <span class="token variable">$status2</span> "</span> <span class="token operator">=</span> 0 <span class="token punctuation">]</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token keyword">echo</span> <span class="token string">"run unit test success."</span> <span class="token function">git</span> add <span class="token keyword">.</span> <span class="token keyword">else</span> <span class="token keyword">echo</span> 1 <span class="token operator">></span> <span class="token operator">&</span> 2 <span class="token string">"run unit test failure, please re-check"</span> <span class="token keyword">exit</span> 1 <span class="token keyword">fi</span> |
In the above code, I combined the use of gradle tasks in the lesson using ktlint và detekt để kiểm tra code convention và code smell
.
At any point, I stop at that moment. Commit only succeeds when all the above steps succeed.
Note: the syntax of unit run tests is as shown below, you can customize the way the project is operating.
1 2 | <span class="token punctuation">.</span> <span class="token operator">/</span> gradlew test <span class="token punctuation">[</span> Flavor <span class="token punctuation">]</span> <span class="token punctuation">[</span> BuildType <span class="token punctuation">]</span> |
Copy Hooks to git hooks folder
Now let’s create a gradle task to copy hooks into the correct .git/hooks
project directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <span class="token keyword">fun</span> <span class="token function">isLinuxOrMacOs</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">:</span> Boolean <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">isMac</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">isLinux</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">fun</span> <span class="token function">isLinux</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">:</span> Boolean <span class="token punctuation">{</span> <span class="token keyword">return</span> System <span class="token punctuation">.</span> <span class="token function">getProperty</span> <span class="token punctuation">(</span> <span class="token string">"os.name"</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">toLowerCase</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">contains</span> <span class="token punctuation">(</span> <span class="token string">"linux"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">fun</span> <span class="token function">isMac</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">:</span> Boolean <span class="token punctuation">{</span> <span class="token keyword">val</span> osName <span class="token operator">=</span> System <span class="token punctuation">.</span> <span class="token function">getProperty</span> <span class="token punctuation">(</span> <span class="token string">"os.name"</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">toLowerCase</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> osName <span class="token punctuation">.</span> <span class="token function">contains</span> <span class="token punctuation">(</span> <span class="token string">"mac os"</span> <span class="token punctuation">)</span> <span class="token operator">||</span> osName <span class="token punctuation">.</span> <span class="token function">contains</span> <span class="token punctuation">(</span> <span class="token string">"macos"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> tasks <span class="token punctuation">.</span> <span class="token function">register</span> <span class="token punctuation">(</span> <span class="token string">"copyGitHooks"</span> <span class="token punctuation">,</span> Copy <span class="token operator">::</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> description <span class="token operator">=</span> <span class="token string">"Copies the git hooks from team-props/git-hooks to the .git folder."</span> <span class="token keyword">val</span> path <span class="token operator">=</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token function">isLinux</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token string">" <span class="token interpolation variable">$rootDir</span> /team-props/git-hooks/linux"</span> <span class="token keyword">else</span> <span class="token string">" <span class="token interpolation variable">$rootDir</span> /team-props/git-hooks/mac"</span> <span class="token comment">// diff paths from Linux & Mac environment</span> <span class="token function">from</span> <span class="token punctuation">(</span> path <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">include</span> <span class="token punctuation">(</span> <span class="token string">"**/*.sh"</span> <span class="token punctuation">)</span> <span class="token function">rename</span> <span class="token punctuation">(</span> <span class="token string">"(.*).sh"</span> <span class="token punctuation">,</span> <span class="token string">" <span class="token interpolation variable">$1</span> "</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">into</span> <span class="token punctuation">(</span> <span class="token string">" <span class="token interpolation variable">$rootDir</span> /.git/hooks"</span> <span class="token punctuation">)</span> onlyIf <span class="token punctuation">{</span> <span class="token function">isLinuxOrMacOs</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> tasks <span class="token punctuation">.</span> <span class="token function">register</span> <span class="token punctuation">(</span> <span class="token string">"installGitHooks"</span> <span class="token punctuation">,</span> Exec <span class="token operator">::</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> description <span class="token operator">=</span> <span class="token string">"Installs the pre-commit git hooks from team-props/git-hooks."</span> group <span class="token operator">=</span> <span class="token string">"git hooks"</span> workingDir <span class="token operator">=</span> rootDir <span class="token function">setCommandLine</span> <span class="token punctuation">(</span> <span class="token string">"chmod"</span> <span class="token punctuation">,</span> <span class="token string">"-R"</span> <span class="token punctuation">,</span> <span class="token string">"+x"</span> <span class="token punctuation">,</span> <span class="token string">".git/hooks/"</span> <span class="token punctuation">)</span> <span class="token function">dependsOn</span> <span class="token punctuation">(</span> <span class="token string">"copyGitHooks"</span> <span class="token punctuation">)</span> onlyIf <span class="token punctuation">{</span> <span class="token function">isLinuxOrMacOs</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> doLast <span class="token punctuation">{</span> logger <span class="token punctuation">.</span> <span class="token function">info</span> <span class="token punctuation">(</span> <span class="token string">"Git hook installed successfully!"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> afterEvaluate <span class="token punctuation">{</span> tasks <span class="token punctuation">[</span> <span class="token string">"clean"</span> <span class="token punctuation">]</span> <span class="token punctuation">.</span> <span class="token function">dependsOn</span> <span class="token punctuation">(</span> <span class="token string">"installGitHooks"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
In the above code , there will be 2 tasks : copyGitHooks
and installGitHooks
, 2 tasks will automatically run each time you clean the code . Therefore, only if you run the clean project at least once will the git hooks (changes) be copied and run properly.
Once you have created this task , you should add it to the build.gradle
level project :
1 2 3 4 5 6 7 8 9 10 11 12 | buildscript <span class="token punctuation">{</span> <span class="token function">apply</span> <span class="token punctuation">(</span> from <span class="token operator">=</span> <span class="token string">" <span class="token interpolation variable">$rootDir</span> /team-props/git-hooks.gradle.kts"</span> <span class="token punctuation">)</span> repositories <span class="token punctuation">{</span> <span class="token comment">//...</span> <span class="token punctuation">}</span> dependencies <span class="token punctuation">{</span> <span class="token comment">//...</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Test results
Now clean the project once, then try to edit and make a commit . Celebrate if the gradle task is successfully run! (beer) (beer) (beer).
Conclusion
Ok, so I have instructed you to create a small local CI
, help check through the code of your team members for code style, lint, unit test before creating Pull Request . Hopefully the article will benefit everyone.
Reference: I will share the detailed code soon when I have time.
HAPPY CODING!