Add Git Auto-Sync Architecture documentation
This commit is contained in:
225
docs/GIT-AUTO-SYNC-ARCHITECTURE.md
Normal file
225
docs/GIT-AUTO-SYNC-ARCHITECTURE.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
# Git Auto-Sync Architecture - Complete Picture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document explains how the automatic git synchronization works between Gitea (bare repository) and the VPS working copy.
|
||||||
|
|
||||||
|
## Architecture Components
|
||||||
|
|
||||||
|
### 1. **Bare Repository (Gitea Server)**
|
||||||
|
- **Host Path**: `/data/app/gitea/git/repositories/salman/igny8.git`
|
||||||
|
- **Container Path**: `/data/git/repositories/salman/igny8.git`
|
||||||
|
- **Type**: Bare repository (no working tree)
|
||||||
|
- **Purpose**: Stores all commits, branches, and git history
|
||||||
|
- **Access**: Served by Gitea at `https://git.igny8.com/salman/igny8.git`
|
||||||
|
|
||||||
|
### 2. **Working Copy (VPS Deployment)**
|
||||||
|
- **Host Path**: `/data/app/igny8`
|
||||||
|
- **Container Path**: `/deploy/igny8` (mounted from host)
|
||||||
|
- **Type**: Full git repository with working tree
|
||||||
|
- **Purpose**: Live application code that runs on VPS
|
||||||
|
- **Remotes**:
|
||||||
|
- `origin`: `https://git.igny8.com/salman/igny8.git` (HTTPS remote)
|
||||||
|
- `deploy`: `/data/git/repositories/salman/igny8.git` (local bare repo path)
|
||||||
|
|
||||||
|
### 3. **Docker Volume Mount**
|
||||||
|
```yaml
|
||||||
|
# From docker-compose.yml
|
||||||
|
volumes:
|
||||||
|
- ./gitea:/data # Gitea data directory
|
||||||
|
- /data/app/igny8:/deploy/igny8:rw # Mount working copy into container
|
||||||
|
```
|
||||||
|
|
||||||
|
This mount makes the VPS working copy accessible inside the Gitea container at `/deploy/igny8`.
|
||||||
|
|
||||||
|
### 4. **Post-Receive Hook**
|
||||||
|
- **Location**: `/data/app/gitea/git/repositories/salman/igny8.git/hooks/post-receive`
|
||||||
|
- **Trigger**: Automatically runs after every `git push` to Gitea
|
||||||
|
- **Execution Context**: Runs inside Gitea container, in the bare repository directory
|
||||||
|
|
||||||
|
## Complete Flow: Push to Auto-Update
|
||||||
|
|
||||||
|
### Step 1: Developer Pushes to Gitea
|
||||||
|
```bash
|
||||||
|
git push origin main
|
||||||
|
# or
|
||||||
|
git push https://git.igny8.com/salman/igny8.git main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Gitea Receives Push
|
||||||
|
- Push arrives at Gitea server
|
||||||
|
- Gitea validates and stores commits in bare repository
|
||||||
|
- Bare repo is updated: `/data/app/gitea/git/repositories/salman/igny8.git`
|
||||||
|
|
||||||
|
### Step 3: Post-Receive Hook Executes
|
||||||
|
The hook runs automatically with this context:
|
||||||
|
- **Current Directory**: `/data/git/repositories/salman/igny8.git` (bare repo)
|
||||||
|
- **Environment**: Inside Gitea container
|
||||||
|
- **Access**: Can access both bare repo and mounted working copy
|
||||||
|
|
||||||
|
### Step 4: Hook Logic Breakdown
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Define paths
|
||||||
|
DEPLOY_DIR="/deploy/igny8" # Working copy (mounted from host)
|
||||||
|
SOURCE_REPO="$(pwd)" # Bare repo: /data/git/repositories/salman/igny8.git
|
||||||
|
|
||||||
|
# 2. Check if working copy is a git repository
|
||||||
|
if [ -d "$DEPLOY_DIR/.git" ]; then
|
||||||
|
# Working copy exists and is a full git repo
|
||||||
|
|
||||||
|
# 3. Set up git command with explicit paths
|
||||||
|
GIT_CMD="git --git-dir=$DEPLOY_DIR/.git --work-tree=$DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 4. Add 'deploy' remote if it doesn't exist
|
||||||
|
# This points to the bare repo (no network needed, direct file access)
|
||||||
|
if ! $GIT_CMD remote get-url deploy >/dev/null 2>&1; then
|
||||||
|
$GIT_CMD remote add deploy "$SOURCE_REPO"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Fetch latest commits from bare repo
|
||||||
|
# Fetches directly from file system, no HTTPS/SSH needed
|
||||||
|
$GIT_CMD fetch deploy main
|
||||||
|
|
||||||
|
# 6. Reset working tree to latest commit
|
||||||
|
# This updates all files in /data/app/igny8 to match latest commit
|
||||||
|
$GIT_CMD reset --hard remotes/deploy/main
|
||||||
|
|
||||||
|
# 7. Update origin/main tracking branch
|
||||||
|
# This is CRITICAL: tells git that local HEAD matches origin/main
|
||||||
|
# Without this, git status shows "ahead" even though files are synced
|
||||||
|
$GIT_CMD update-ref refs/remotes/origin/main HEAD
|
||||||
|
|
||||||
|
else
|
||||||
|
# First time setup: working copy doesn't exist yet
|
||||||
|
# Checkout files from bare repo to create working copy
|
||||||
|
export GIT_DIR="$SOURCE_REPO"
|
||||||
|
export GIT_WORK_TREE="$DEPLOY_DIR"
|
||||||
|
git checkout -f main
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Result
|
||||||
|
- **Files Updated**: All files in `/data/app/igny8` now match latest commit
|
||||||
|
- **Git Status**: Shows "up to date with origin/main" (no phantom commits)
|
||||||
|
- **Application**: If using volume mounts, containers see changes immediately
|
||||||
|
|
||||||
|
## Why This Design?
|
||||||
|
|
||||||
|
### Problem We Solved
|
||||||
|
1. **Phantom Commits**: Previously, hook updated files but not git metadata, causing git to think local was "ahead"
|
||||||
|
2. **Manual Sync Required**: Had to manually `git pull` after every push
|
||||||
|
3. **Sync Issues**: Working tree and git metadata were out of sync
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
1. **Direct File System Access**: Hook uses `deploy` remote pointing to bare repo path (no network overhead)
|
||||||
|
2. **Complete Sync**: Updates both files AND git metadata (tracking branches)
|
||||||
|
3. **Automatic**: Runs on every push, no manual intervention needed
|
||||||
|
|
||||||
|
## Key Git Concepts Used
|
||||||
|
|
||||||
|
### 1. Bare Repository
|
||||||
|
- Repository without working tree
|
||||||
|
- Only contains `.git` contents (objects, refs, etc.)
|
||||||
|
- Used by servers to store code without checking out files
|
||||||
|
|
||||||
|
### 2. Working Tree
|
||||||
|
- Directory with actual files you can edit
|
||||||
|
- Has `.git` directory with repository metadata
|
||||||
|
- This is what developers work with
|
||||||
|
|
||||||
|
### 3. Remote Tracking Branches
|
||||||
|
- `refs/remotes/origin/main`: Git's record of what `origin/main` was last time we fetched
|
||||||
|
- When we update this to match HEAD, git knows we're in sync
|
||||||
|
- Without updating this, git compares HEAD to stale tracking branch → shows "ahead"
|
||||||
|
|
||||||
|
### 4. Git Reset --hard
|
||||||
|
- Moves HEAD to specified commit
|
||||||
|
- Updates working tree to match that commit
|
||||||
|
- Discards any local changes (force update)
|
||||||
|
|
||||||
|
## File System Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
Host System:
|
||||||
|
├── /data/app/gitea/
|
||||||
|
│ └── git/repositories/salman/igny8.git/ (bare repo)
|
||||||
|
│ ├── objects/ (all commits, trees, blobs)
|
||||||
|
│ ├── refs/ (branches, tags)
|
||||||
|
│ └── hooks/
|
||||||
|
│ └── post-receive (auto-sync hook)
|
||||||
|
│
|
||||||
|
└── /data/app/igny8/ (working copy)
|
||||||
|
├── .git/ (git metadata)
|
||||||
|
│ ├── config (has 'origin' and 'deploy' remotes)
|
||||||
|
│ └── refs/
|
||||||
|
│ └── remotes/
|
||||||
|
│ ├── origin/main (tracking branch)
|
||||||
|
│ └── deploy/main (tracking branch)
|
||||||
|
├── backend/ (actual application files)
|
||||||
|
├── frontend/
|
||||||
|
└── ...
|
||||||
|
|
||||||
|
Inside Gitea Container:
|
||||||
|
├── /data/git/repositories/salman/igny8.git/ (same as host bare repo)
|
||||||
|
└── /deploy/igny8/ (mounted from /data/app/igny8)
|
||||||
|
└── (same contents as host /data/app/igny8)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checking Status
|
||||||
|
|
||||||
|
### On Host VPS
|
||||||
|
```bash
|
||||||
|
cd /data/app/igny8
|
||||||
|
git status # Should show "up to date with origin/main"
|
||||||
|
git log --oneline -5 # See recent commits
|
||||||
|
git remote -v # See remotes (origin + deploy)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hook Logs
|
||||||
|
```bash
|
||||||
|
docker exec gitea cat /data/gitea/log/hooks.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Sync (if needed)
|
||||||
|
```bash
|
||||||
|
cd /data/app/igny8
|
||||||
|
git pull origin main # Pull from HTTPS remote
|
||||||
|
# OR
|
||||||
|
git fetch deploy main # Fetch from local bare repo
|
||||||
|
git reset --hard remotes/deploy/main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: "Your branch is ahead of origin/main"
|
||||||
|
**Cause**: Hook didn't update `refs/remotes/origin/main` tracking branch
|
||||||
|
**Fix**: Hook should run `git update-ref refs/remotes/origin/main HEAD` (already in hook)
|
||||||
|
|
||||||
|
### Issue: Files not updating after push
|
||||||
|
**Check**:
|
||||||
|
1. Hook logs: `docker exec gitea cat /data/gitea/log/hooks.log`
|
||||||
|
2. Hook executable: `ls -la /data/app/gitea/git/repositories/salman/igny8.git/hooks/post-receive`
|
||||||
|
3. Mount exists: `docker exec gitea ls -la /deploy/igny8`
|
||||||
|
|
||||||
|
### Issue: Hook not running
|
||||||
|
**Check**:
|
||||||
|
1. Gitea logs: `docker logs gitea --tail 50`
|
||||||
|
2. Hook file exists and is executable
|
||||||
|
3. Push actually succeeded (check Gitea web UI)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**The Complete Flow:**
|
||||||
|
1. Developer pushes → Gitea bare repo updated
|
||||||
|
2. Post-receive hook triggers automatically
|
||||||
|
3. Hook fetches from bare repo (via `deploy` remote)
|
||||||
|
4. Hook resets working copy to latest commit
|
||||||
|
5. Hook updates tracking branch metadata
|
||||||
|
6. VPS working copy is now in sync
|
||||||
|
7. Application containers see updated files (via volume mounts)
|
||||||
|
|
||||||
|
**Key Innovation:**
|
||||||
|
- Uses local file system path (`deploy` remote) instead of HTTPS
|
||||||
|
- Updates both files AND git metadata
|
||||||
|
- Fully automatic, no manual steps required
|
||||||
|
|
||||||
Reference in New Issue
Block a user