In software development, automating routine tasks can significantly boost productivity. One common scenario is monitoring changes to files within a directory, automatically committing those changes to version control, and pushing them to a remote server. In this article, we’ll walk through how to create a Bash script that detects file changes, integrates with Git to track changes, commits those changes, and pushes them to an FTP server using Git-FTP.

The Goal

We want to build a Bash script that:

  1. Monitors a directory for any changes to files, including deep subdirectories.
  2. Excludes hidden files (e.g., .env, .gitignore) from monitoring.
  3. Checks if a file is ignored by Git before adding it to the repository.
  4. Automatically commits changes only when there are files staged for commit.
  5. Pushes the changes to a remote FTP server using git-ftp.

Let’s dive into how we can achieve this step-by-step.

The Core Bash Script

At the core of the solution is a Bash script that uses a combination of built-in tools (find, stat, etc.) and Git commands to detect changes in files and handle version control.

#!/bin/bash

folder="/root/work"

declare -A file_times

# Initialize file modification times (excluding hidden files and subdirectories)
while IFS= read -r -d '' file; do
    # Skip hidden files (those starting with a dot)
    if [[ $(basename "$file") == .* ]]; then
        echo "Skipping hidden file: $(basename "$file")"
        continue
    fi
    file_times["$file"]=$(stat -c %Y "$file")
done < <(find "$folder" -type f -print0)

# Continuously check for changes
while true; do
    change_detected=false
    while IFS= read -r -d '' file; do
        # Skip hidden files (those starting with a dot)
        if [[ $(basename "$file") == .* ]]; then
            echo "Skipping hidden file: $(basename "$file")"
            continue
        fi

        current_time=$(stat -c %Y "$file")
        if [[ ${file_times["$file"]} != "$current_time" ]]; then
            echo "File changed: $(basename "$file")"
            file_times["$file"]=$current_time
            change_detected=true

            # Check if the file is tracked by Git
            if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then
                echo "File is already tracked: $(basename "$file")"
            else
                # Check if the file is ignored by Git
                if git check-ignore "$file" >/dev/null 2>&1; then
                    echo "File is ignored: $(basename "$file")"
                else
                    echo "File is not tracked, adding to Git: $(basename "$file")"
                    git add "$file"
                fi
            fi
        fi
    done < <(find "$folder" -type f -print0)

    # If changes were detected, execute the git commands
    if [ "$change_detected" = true ]; then
        echo "Changes detected, updating repository..."

        # Add all tracked files with changes
        git add -u

        # Check if there are any staged changes to commit
        if ! git diff --cached --exit-code >/dev/null; then
            # Commit the changes if there are files to be committed
            git commit -m "[updated] files have been changed"
        else
            echo "No changes to commit."
        fi

        # Push changes to the FTP server
        git ftp push
    fi

    sleep 2
done

Breaking Down the Script

1. Monitoring File Changes

The script uses the find command to recursively locate all files in the target directory. It initializes the modification times of these files using stat -c %Y "$file", which retrieves the last modification time.

file_times["$file"]=$(stat -c %Y "$file")

It then continuously checks if the modification time of any file has changed. If it has, the script detects this as a change and sets a flag change_detected=true.

2. Excluding Hidden Files

Hidden files (those whose names start with a dot, e.g., .env, .gitignore) are excluded from monitoring. This is achieved by checking the file’s basename and skipping it if it starts with a dot:

if [[ $(basename "$file") == .* ]]; then
    echo "Skipping hidden file: $(basename "$file")"
    continue
fi

This ensures that no hidden files are added or tracked by Git, and no changes in hidden files trigger further actions.

3. Checking If a File Is Tracked by Git

Before adding any changed file to Git, the script checks whether the file is already tracked using the following command:

if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then
    echo "File is already tracked: $(basename "$file")"
else
    # File is not tracked
fi

This prevents redundant git add commands on files that are already being tracked.

4. Checking If a File Is Ignored by Git

If a file is untracked, the script then checks whether it’s listed in .gitignore or any global ignore rules using:

if git check-ignore "$file" >/dev/null 2>&1; then
    echo "File is ignored: $(basename "$file")"
else
    git add "$file"
fi

This ensures that ignored files are not added to Git.

5. Committing Changes Only When Necessary

To avoid unnecessary commits, the script checks if there are any staged changes using:

if ! git diff --cached --exit-code >/dev/null; then
    git commit -m "[updated] files have been changed"
else
    echo "No changes to commit."
fi

This prevents empty commits, ensuring the script only commits when there are actual changes.

6. Pushing Changes via Git-FTP

Once changes are committed, the script pushes them to an FTP server using git ftp push. This assumes git-ftp has already been configured for the repository.

Benefits of the Script

  • Automation: The script runs continuously, detecting changes and automating the process of committing and pushing updates.
  • Efficiency: By checking if files are tracked, ignored, or hidden, the script efficiently handles only the relevant files, reducing unnecessary Git operations.
  • No Empty Commits: The script intelligently commits changes only when there are staged modifications, preventing redundant commits.

Conclusion

This script provides an efficient way to monitor file changes, handle version control using Git, and automate the process of pushing updates to an FTP server. Whether you’re managing a development environment or deploying changes frequently, this approach can save time and reduce manual intervention. By leveraging Bash’s powerful tools and Git integration, we can create a robust and scalable solution to streamline our workflow.