Automating Git Repository Sync: Syncing Two Different Repositories

git
git sync

In many projects, developers maintain multiple Git repositories—one for internal development and another for a client or a secondary system. Keeping these repositories in sync manually can be tedious and error-prone. In this blog, we’ll explore how to automate the synchronization of two different Git repositories using a simple Bash script.

Why Automate Git Sync?

Manually copying changes between repositories is inefficient and prone to mistakes. With an automated script, you can:

✅ Keep two repositories in sync effortlessly
✅ Ensure no files are missed during transfers
✅ Maintain version control history separately
✅ Backup data before syncing for added safety

Understanding the Script

The script follows a structured approach:

  1. Cloning Repositories: If the local directory does not contain a .git folder, it clones the repository.
  2. Creating Backups: Before syncing, it saves backups of both repositories, keeping only the last two versions.
  3. Syncing Files: Uses rsync to mirror changes from the source to the target repository while excluding .git.
  4. Pushing Changes: The script commits the changes and pushes them to the client repository.

Key Commands Used

  • git clone – Clones repositories if they don’t exist locally.
  • rsync -av --delete – Syncs files between repositories while deleting outdated ones.
  • git pull --rebase – Ensures an up-to-date local branch before pushing changes.
  • git stash – Saves uncommitted changes temporarily to prevent conflicts.

Automating the Process

This script can be added as a cron job to run at scheduled intervals, ensuring seamless synchronization.

#!/bin/bash

set -e  # Exit on error
set -x  # Enable verbose mode to show command execution

# Variables
YOUR_GIT_URL="git@gitlab.com:nodejs2483017/tasks.git"
CLIENT_GIT_URL="git@github.com:devprojects2023/nodejs-taskapp.git"
YOUR_LOCAL_DIR="/home/dev/Music/LocalGit"
CLIENT_LOCAL_DIR="/home/dev/Music/ClientGit"
VERBOSE_MODE=true    # Set to true for more detailed output
YOUR_BRANCH="main"   # Your source branch (e.g., main, develop)
CLIENT_BRANCH="main" # Client's target branch (e.g., main, staging)
BACKUP_DIR="/home/dev/Music/backup"

# Function to clone or update the source repository
clone_or_update_source_repo() {
    local repo_url=$1
    local local_dir=$2
    local branch=$3

    if [ ! -d "$local_dir/.git" ]; then
        echo "Cloning $repo_url to $local_dir..."
        rm -rf "$local_dir"
        git clone -b "$branch" "$repo_url" "$local_dir"
    else
        echo "Updating existing repository in $local_dir..."
        cd "$local_dir" || exit
        git fetch origin
        git checkout "$branch"
        git pull --rebase origin "$branch"
    fi
}

# Function to clone the client repository (handle missing branch or empty repo)
clone_client_repo() {
    local repo_url=$1
    local local_dir=$2
    local branch=$3

    echo "Cloning $repo_url to $local_dir (branch: $branch)..."
    # Always remove and reclone to ensure a clean state
    rm -rf "$local_dir"
    
    # Try to clone the specified branch
    if git clone -b "$branch" "$repo_url" "$local_dir" 2>/dev/null; then
        echo "Successfully cloned branch $branch."
    else
        echo "Branch $branch not found. Cloning default branch or initializing..."
        # Clone the repository without specifying a branch (gets default branch)
        git clone "$repo_url" "$local_dir"
        cd "$local_dir" || exit
        # Check if any branches exist
        if [ -z "$(git branch)" ]; then
            echo "No branches found. Initializing empty repository..."
            git checkout --orphan "$branch"
            git commit --allow-empty -m "Initial commit"
            git push origin "$branch"
        else
            # Use the default branch and create the target branch if needed
            git checkout -b "$branch"
            git push origin "$branch"
        fi
    fi
}

# Function to create backups (keeps last 2)
backup_repos() {
    echo "Creating backups..."
    mkdir -p "$BACKUP_DIR"
    local timestamp
    timestamp=$(date '+%Y-%m-%d_%H-%M-%S')

    tar -czf "$BACKUP_DIR/your_repo_backup_$timestamp.tar.gz" -C "$(dirname "$YOUR_LOCAL_DIR")" "$(basename "$YOUR_LOCAL_DIR")"
    tar -czf "$BACKUP_DIR/client_repo_backup_$timestamp.tar.gz" -C "$(dirname "$CLIENT_LOCAL_DIR")" "$(basename "$CLIENT_LOCAL_DIR")"

    # Keep only the last 2 backups
    ls -t "$BACKUP_DIR"/*.tar.gz | tail -n +3 | xargs rm -f 2>/dev/null
}

# Function to sync repositories
sync_repos() {
    echo "Syncing $YOUR_LOCAL_DIR to $CLIENT_LOCAL_DIR..."
    # Sync files, excluding the .git folder in CLIENT_LOCAL_DIR to preserve Git history
    rsync -av --delete "$YOUR_LOCAL_DIR/" "$CLIENT_LOCAL_DIR/" --exclude ".git"
}

# Function to update client repository
push_to_client() {
    echo "Pushing changes to client repository..."
    cd "$CLIENT_LOCAL_DIR" || exit

    if [ ! -d ".git" ]; then
        echo "Error: .git directory not found in $CLIENT_LOCAL_DIR. Cannot push changes."
        exit 1
    fi

    git checkout "$CLIENT_BRANCH" || exit
    git add .
    # Check if there are changes to commit
    if git diff-index --quiet HEAD --; then
        echo "No changes to commit."
    else
        git commit -m "Sync from source repository" || true
        git push origin "$CLIENT_BRANCH" || exit
    fi
}

# Main execution
clone_or_update_source_repo "$YOUR_GIT_URL" "$YOUR_LOCAL_DIR" "$YOUR_BRANCH"
clone_client_repo "$CLIENT_GIT_URL" "$CLIENT_LOCAL_DIR" "$CLIENT_BRANCH"
backup_repos
sync_repos
push_to_client

echo "Sync and push completed successfully."