← Back to list

Learn How to Setup Git PreCommit Hook for iOS and Android Projects

A Very Useful Knowledge To Better Control Code Changes For Your Team

Elye - A Dev By Grace in Mobile App Development Publication · 2025-09-11 04:58 · 10 claps · 5.0 min read paywalled
#android-app-development #ios-app-development #mobile-app-development #programming #git
Open on Medium ↗
Wiki topics: 💻 · Programming 🔓 · Open Source

Learning Mobile Development

Learn How to Setup Git PreCommit Hook for iOS and Android Projects

A Very Useful Knowledge To Better Control Code Changes For Your Team

Photo by Calan Hilton on Unsplash

Photo by Calan Hilton on Unsplash

If you work in a team, or even you work yourselves on a mobile project, you want a reminder prior to you get your code committed, the git pre-commit hook will be an awesome tool you want to use.

This is not a common knowledge required by mobile, but it’s really handy. Here I’m building a git pre-commit checks where, if the changes is much larger than a certain threshold, it will warn the developer, and recommend them to break it into smaller chunk before committing.

Of course, it’s a recommendation, which I still allow the developer to proceed (I can make it a mandatory too if I like). It looks like below.

The Basic Knowledge of Git Hooks

When we git init a folder, it will create a .git folder. This will prepare itself to allow one to check in code etc.

At the same time, in the .git folder, it has a hooks folder, that consist of various things one can trigger, as shown below.

One of the example is pre-commit. So if we have this file in this /.git/hooks folder, whatever command in it, will be executed before one git committhe changes, e.g. check the changes, and prompt or even prevent user from proceeding.

The problem is, .git is a folder that is not checked into the git repository. So the content in it is not shared with other fellow developers, or the future you using another machine.

So to address that, we’ll need to have a way to automatically copy whatever script we have elsewhere (a place where we can still store and commit), and place it in .git/hooks folder.

I’ll share with you how this can be done in Android and iOS.

Android: Use Gradle Tasks

The nice thing about Android project is, by default Android Studio uses Gradle, where we can add additional task to copy that script over.

Let’s create a script that does the copying as below.

tasks.register("installGitHooks") {
    description = "Install git pre-commit hook to check for large code changes"
    group = "git"

    doLast {
        val gitHooksDir = File(rootDir, ".git/hooks")
        val preCommitHook = File(gitHooksDir, "pre-commit")
        val preCommitScript = File(rootDir, "scripts/pre-commit")

        if (!gitHooksDir.exists()) {
            throw GradleException("Git hooks directory not found. Make sure this is a git repository.")
        }

        if (!preCommitScript.exists()) {
            throw GradleException("Pre-commit script not found at ${preCommitScript.absolutePath}")
        }

        // Copy the script
        preCommitScript.copyTo(preCommitHook, overwrite = true)

        // Make it executable
        val makeExecutableProcess = ProcessBuilder("chmod", "+x", preCommitHook.absolutePath)
            .directory(rootDir)
            .start()
        makeExecutableProcess.waitFor()

        if (makeExecutableProcess.exitValue() == 0) {
            // Read the actual threshold from the pre-commit script
            val thresholdLine = preCommitScript.readLines().find { it.contains("THRESHOLD=") }
            val threshold = thresholdLine?.substringAfter("THRESHOLD=")?.trim() ?: "100"

            println("✅ Git pre-commit hook installed successfully!")
            println("📍 Location: ${preCommitHook.absolutePath}")
            println("🔍 The hook will check for commits with more than $threshold lines of changes")
        } else {
            throw GradleException("Failed to make pre-commit hook executable")
        }
    }
}

Then, the next important step is, when to copy? To do that, in our app’s build.gradle.kts, we’ll just need to make this task a pre-requisite before some of other steps like, preBuild etc.

tasks.whenTaskAdded {
    if (name in listOf("preBuild", "preDebugBuild", "preReleaseBuild")) {
        dependsOn(":installGitHooks")
    }
}

That’s it. Remember to just start compile your project first, to get the pre-commit hook copied over.

You can get the android full project code here.

iOS: Use Run Script Phase and Manual Copy

For iOS, initially I thought I can so something like Android, where during pre-run, I can execute the following commands, using the Xcode Build Phase Custom Script

# Copy the pre-commit hook script to .git/hooks/pre-commit
if [ -f "${SRCROOT}/scripts/pre-commit" ]; then
  cp "${SRCROOT}/scripts/pre-commit" "${SRCROOT}/.git/hooks/pre-commit"
  chmod +x "${SRCROOT}/.git/hooks/pre-commit"
fi

However, when running it, it fails

Sandbox: cp(67252) deny(1) file-read-data 
/Users/Development/personal/iOS/iOSPreCommitHook/
iOSPreCommitHook/scripts/pre-commit

This error is caused by Xcode’s build system sandboxing, which restricts file system access during build phases. The build phase script cannot access files outside the target’s allowed directories, especially anything in .git or custom folders.

So to work, around at most we can prompt the user if the file is not there, and ask them to manually copy over.

Pre-prompt the user to copy the script

We can do this using the Xcode build phase custom as below

  1. Open your Xcode project.
  2. Select your main app target.
  3. Go to the “Build Phases” tab.
  4. Click the “+” button and choose “New Run Script Phase”.
  5. Add the following shell script to the new phase:
if [ ! -x "${SRCROOT}/.git/hooks/pre-commit" ]; then
  echo "error: .git/hooks/pre-commit hook is missing or not executable. Please run 'sh setup-hooks.sh' to install it."
  exit 1
fi

When you build your app, and if the pre-commit script is not in place, it will stop and ask you to do something.

In our case, we’ll ask the user to just run a script where we’ll progrom to copy the pre-commit over.

Script to copy the Pre-Commit file over

For the setup-hooks.sh script, it’s written as below

#!/bin/sh
# This script installs the pre-commit hook for the project

HOOK_SRC="$(dirname "$0")/pre-commit"
HOOK_DEST="$(git rev-parse --show-toplevel)/.git/hooks/pre-commit"

if [ ! -f "$HOOK_SRC" ]; then
  echo "Error: pre-commit script not found at $HOOK_SRC"
  exit 1
fi

# Create a symlink instead of copying, so updates are automatic
ln -sf "$HOOK_SRC" "$HOOK_DEST"
chmod +x "$HOOK_SRC"
echo "pre-commit hook symlinked to $HOOK_DEST"

Do note, it is not using cp, instead it is using ln -sf, which is copying a symlink version of the file over, instead of the real file. This is important, since developer will not be doing this step everything.

The reason we want just a symlink, in case any new changes to the origin pre-commit file (not in the .git/hooks folder), it will still be executed, without need to re-copy again (since our prompting script above will not prompt the developer anymore).

You can get the full code for the iOS project here.

That’s It

Now you can use the pre-commit to do a lot of userful things, like

  • lint or reformat the code per your guide,
  • or simply just warn the user if their commit potentially will be too huge for review (like the example I have in the two projects link above that you can use… You can understand better how it leverage git diff for the work by checking on this blog).

Special thanks to also this blog, that shed some light on the iOS side of work.


메타데이터
post_id
fee45f4351a3
slug
learn-how-to-setup-git-precommit-hook-for-ios-and-android-projects-fee45f4351a3
url
https://medium.com/mobile-app-development-publication/learn-how-to-setup-git-precommit-hook-for-ios-and-android-projects-fee45f4351a3
canonical_url
https://medium.com/mobile-app-development-publication/learn-how-to-setup-git-precommit-hook-for-ios-and-android-projects-fee45f4351a3
author_url
https://medium.com/@elye-project
status
ok
fetched_at
2026-06-15 20:49:13