/ bin / gitfield-local
gitfield-local
  1  #!/bin/bash
  2  set -euo pipefail
  3  IFS=$'\n\t'
  4  
  5  # ╭─────────────────────────────────────╮
  6  # │          gitfield-local             │
  7  # ╰─────────────────────────────────────╯
  8  # Manages a local bare Git repository as a sacred push target for redundancy.
  9  # Creates and maintains a bare repository in ~/git-local-repos/git-sigil.git
 10  # Generates rich metadata in .gitfield/local.sigil.md and updates .gitfield/push_log.json
 11  # Commands: configure, status, push
 12  
 13  # ╭─────────────────────────────────────╮
 14  # │          CONFIGURATION              │
 15  # ╰─────────────────────────────────────╯
 16  GIT_REMOTE_NAME="local"
 17  REPO_NAME=$(basename "$(pwd)") || REPO_NAME="Unknown"
 18  DEFAULT_NAME="Mark Randall Havens"
 19  DEFAULT_EMAIL="mark.r.havens@gmail.com"
 20  LOCAL_REPO="$HOME/git-local-repos/git-sigil.git"
 21  REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || { echo -e "\e[1;31m[ERROR]\e[0m Not inside a Git repository" >&2; exit 1; }
 22  MARKDOWN_FILE="$REPO_PATH/.gitfield/local.sigil.md"
 23  PUSH_LOG="$REPO_PATH/.gitfield/push_log.json"
 24  SCRIPT_VERSION="1.0"
 25  
 26  # ╭─────────────────────────────────────╮
 27  # │           LOGGING UTILS             │
 28  # ╰─────────────────────────────────────╯
 29  info()  { echo -e "\e[1;34m[INFO]\e[0m $*"; }
 30  warn()  { echo -e "\e[1;33m[WARN]\e[0m $*"; }
 31  error() { echo -e "\e[1;31m[ERROR]\e[0m $*" >&2; exit 1; }
 32  
 33  # ╭─────────────────────────────────────╮
 34  # │        TOOLCHAIN SETUP              │
 35  # ╰─────────────────────────────────────╯
 36  info "Checking for required tools..."
 37  if ! command -v git &>/dev/null; then
 38    info "Installing Git..."
 39    sudo apt update -qq 2>/dev/null || warn "apt update failed, continuing..."
 40    sudo apt install -y git 2>/dev/null || error "Git install failed"
 41  fi
 42  
 43  if ! command -v jq &>/dev/null; then
 44    info "Installing jq for JSON processing..."
 45    sudo apt install -y jq 2>/dev/null || warn "jq install failed, push_log.json updates may fail"
 46  fi
 47  
 48  # ╭─────────────────────────────────────╮
 49  # │        AUTH + IDENTITY              │
 50  # ╰─────────────────────────────────────╯
 51  info "Setting Git identity..."
 52  git config --global user.name "$DEFAULT_NAME" 2>/dev/null || warn "Failed to set git user name"
 53  git config --global user.email "$DEFAULT_EMAIL" 2>/dev/null || warn "Failed to set git user email"
 54  info "Git identity set to: $DEFAULT_NAME <$DEFAULT_EMAIL>"
 55  
 56  # ╭─────────────────────────────────────╮
 57  # │         GIT INIT (IF NEEDED)        │
 58  # ╰─────────────────────────────────────╯
 59  if [ ! -d "$REPO_PATH/.git" ]; then
 60    info "Initializing Git repository..."
 61    git -C "$REPO_PATH" init 2>/dev/null || warn "Git init failed, continuing..."
 62    git -C "$REPO_PATH" add . 2>/dev/null || warn "Nothing to add"
 63    git -C "$REPO_PATH" commit -m "Initial commit" 2>/dev/null || warn "Nothing to commit"
 64  fi
 65  
 66  # ╭─────────────────────────────────────╮
 67  # │     LOCAL REPO CONFIGURATION        │
 68  # ╰─────────────────────────────────────╯
 69  configure() {
 70    info "Configuring local bare repository..."
 71  
 72    # Create and verify local bare repository
 73    if [[ ! -d "$LOCAL_REPO" ]]; then
 74      info "Creating local bare repository: $LOCAL_REPO"
 75      mkdir -p "$LOCAL_REPO" || error "Failed to create $LOCAL_REPO"
 76      git -C "$LOCAL_REPO" init --bare 2>/dev/null || error "Failed to initialize bare repository"
 77    fi
 78  
 79    if ! git -C "$LOCAL_REPO" rev-parse --is-bare-repository >/dev/null 2>&1; then
 80      warn "Local repository $LOCAL_REPO is not a valid bare repository. Reinitializing..."
 81      rm -rf "$LOCAL_REPO" || error "Failed to remove invalid $LOCAL_REPO"
 82      mkdir -p "$LOCAL_REPO" || error "Failed to create $LOCAL_REPO"
 83      git -C "$LOCAL_REPO" init --bare 2>/dev/null || error "Failed to reinitialize bare repository"
 84    fi
 85  
 86    # Set permissions
 87    chmod -R u+rwX "$LOCAL_REPO" 2>/dev/null || warn "Failed to set permissions on $LOCAL_REPO"
 88  
 89    # Configure local remote
 90    REMOTE_URL="file://$LOCAL_REPO"
 91    if ! git -C "$REPO_PATH" remote get-url "$GIT_REMOTE_NAME" &>/dev/null; then
 92      info "Adding local remote: $REMOTE_URL"
 93      git -C "$REPO_PATH" remote add "$GIT_REMOTE_NAME" "$REMOTE_URL" 2>/dev/null || error "Failed to add local remote"
 94    else
 95      current_url=$(git -C "$REPO_PATH" remote get-url "$GIT_REMOTE_NAME")
 96      if [[ "$current_url" != "$REMOTE_URL" ]]; then
 97        warn "Local remote URL is incorrect ($current_url). Updating to $REMOTE_URL"
 98        git -C "$REPO_PATH" remote set-url "$GIT_REMOTE_NAME" "$REMOTE_URL" 2>/dev/null || error "Failed to update local remote URL"
 99      fi
100    fi
101  
102    # Set upstream for current branch
103    DEFAULT_BRANCH=$(git -C "$REPO_PATH" symbolic-ref --short HEAD 2>/dev/null || echo "main")
104    if ! git -C "$REPO_PATH" rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1; then
105      info "Setting upstream for $DEFAULT_BRANCH to $GIT_REMOTE_NAME/$DEFAULT_BRANCH"
106      git -C "$REPO_PATH" push --set-upstream "$GIT_REMOTE_NAME" "$DEFAULT_BRANCH" 2>/dev/null || error "Failed to set upstream"
107    fi
108  
109    info "Local bare repository configured successfully."
110  }
111  
112  # ╭─────────────────────────────────────╮
113  # │         STATUS CHECK                │
114  # ╰─────────────────────────────────────╯
115  status() {
116    info "Checking local repository status..."
117  
118    # Verify local bare repository
119    if [[ -d "$LOCAL_REPO" && $(git -C "$LOCAL_REPO" rev-parse --is-bare-repository 2>/dev/null) == "true" ]]; then
120      info "Local bare repository: $LOCAL_REPO"
121      latest_commit=$(git -C "$LOCAL_REPO" log -1 --format="%h %s (%cr)" 2>/dev/null || echo "No commits")
122      info "Latest commit: $latest_commit"
123    else
124      warn "Local bare repository not found or invalid: $LOCAL_REPO"
125    fi
126  
127    # Check remote configuration
128    if git -C "$REPO_PATH" remote | grep -q "^$GIT_REMOTE_NAME$"; then
129      remote_url=$(git -C "$REPO_PATH" remote get-url "$GIT_REMOTE_NAME")
130      info "Local remote URL: $remote_url"
131    else
132      warn "Local remote not configured."
133    fi
134  
135    # Check working repository status
136    info "Working repository: $REPO_PATH"
137    git -C "$REPO_PATH" status --short 2>/dev/null || warn "Failed to get repository status"
138  }
139  
140  # ╭─────────────────────────────────────╮
141  # │       GIT METADATA SNAPSHOT         │
142  # ╰─────────────────────────────────────╯
143  generate_metadata() {
144    info "Generating metadata: $MARKDOWN_FILE"
145    mkdir -p "$(dirname "$MARKDOWN_FILE")" 2>/dev/null || warn "Failed to create .gitfield directory"
146  
147    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "Unknown")
148    DEFAULT_BRANCH=$(git -C "$REPO_PATH" symbolic-ref --short HEAD 2>/dev/null || echo "Unknown")
149    LATEST_SHA=$(git -C "$REPO_PATH" rev-parse HEAD 2>/dev/null || echo "Unknown")
150    LAST_COMMIT_MSG=$(git -C "$REPO_PATH" log -1 --pretty=format:"%s" 2>/dev/null || echo "Unknown")
151    LAST_COMMIT_DATE=$(git -C "$REPO_PATH" log -1 --pretty=format:"%ad" 2>/dev/null || echo "Unknown")
152    LAST_COMMIT_AUTHOR=$(git -C "$REPO_PATH" log -1 --pretty=format:"%an <%ae>" 2>/dev/null || echo "Unknown")
153    TOTAL_COMMITS=$(git -C "$REPO_PATH" rev-list --count HEAD 2>/dev/null || echo "Unknown")
154    TRACKED_FILES=$(git -C "$REPO_PATH" ls-files 2>/dev/null | wc -l 2>/dev/null || echo "Unknown")
155    UNCOMMITTED=$(if ! git -C "$REPO_PATH" diff --quiet 2>/dev/null || ! git -C "$REPO_PATH" diff --cached --quiet 2>/dev/null; then echo "Yes"; else echo "No"; fi)
156    LATEST_TAG=$(git -C "$REPO_PATH" describe --tags --abbrev=0 2>/dev/null || echo "None")
157    HOSTNAME=$(hostname 2>/dev/null || echo "Unknown")
158    CURRENT_USER=$(whoami 2>/dev/null || echo "Unknown")
159    TIMEZONE=$(date +%Z 2>/dev/null || echo "Unknown")
160    OS_NAME=$(uname -s 2>/dev/null || echo "Unknown")
161    KERNEL_VERSION=$(uname -r 2>/dev/null || echo "Unknown")
162    ARCHITECTURE=$(uname -m 2>/dev/null || echo "Unknown")
163    OS_PRETTY_NAME=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d= -f2 | tr -d '"' || echo "Unknown")
164    DOCKER_CHECK=$(grep -qE '/docker|/lxc' /proc/1/cgroup 2>/dev/null && echo "Yes" || echo "No")
165    WSL_CHECK=$(grep -qi microsoft /proc/version 2>/dev/null && echo "Yes" || echo "No")
166    VM_CHECK=$(systemd-detect-virt 2>/dev/null || echo "Unknown")
167    UPTIME=$(uptime -p 2>/dev/null || echo "Unknown")
168    MAC_ADDR=$(ip link 2>/dev/null | awk '/ether/ {print $2}' | head -n 1 2>/dev/null || echo "Unknown")
169    LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}' 2>/dev/null || echo "Unknown")
170    CPU_MODEL=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ //' 2>/dev/null || echo "Unknown")
171    RAM_GB=$(awk '/MemTotal/ {printf "%.2f", $2/1024/1024}' /proc/meminfo 2>/dev/null || echo "Unknown")
172    WEB_LINK="file://$LOCAL_REPO"
173  
174    cat > "$MARKDOWN_FILE" <<EOF
175  # 🔗 Local Repository Link
176  
177  - **Repo Name**: \`$REPO_NAME\`
178  - **Local User**: \`$CURRENT_USER\`
179  - **Remote URL**: \`$WEB_LINK\`
180  - **Local Repo Path**: \`$REPO_PATH\`
181  - **Remote Label**: \`$GIT_REMOTE_NAME\`
182  - **Default Branch**: \`$DEFAULT_BRANCH\`
183  - **Repo Created**: \`$TIMESTAMP\`
184  
185  ---
186  
187  ## 📦 Commit Info
188  
189  - **This Commit Timestamp**: \`$TIMESTAMP\`
190  - **Last Commit SHA**: \`$LATEST_SHA\`
191  - **Last Commit Message**: \`$LAST_COMMIT_MSG\`
192  - **Last Commit Author**: \`$LAST_COMMIT_AUTHOR\`
193  - **Last Commit Date**: \`$LAST_COMMIT_DATE\`
194  - **This Commit URL**: \`$WEB_LINK\`
195  
196  ---
197  
198  ## 📊 Repo Status
199  
200  - **Total Commits**: \`$TOTAL_COMMITS\`
201  - **Tracked Files**: \`$TRACKED_FILES\`
202  - **Uncommitted Changes**: \`$UNCOMMITTED\`
203  - **Latest Tag**: \`$LATEST_TAG\`
204  
205  ---
206  
207  ## 🧭 Environment
208  
209  - **Host Machine**: \`$HOSTNAME\`
210  - **Current User**: \`$CURRENT_USER\`
211  - **Time Zone**: \`$TIMEZONE\`
212  - **Script Version**: \`v$SCRIPT_VERSION\`
213  
214  ---
215  
216  ## 🧬 Hardware & OS Fingerprint
217  
218  - **OS Name**: \`$OS_NAME\`
219  - **OS Version**: \`$OS_PRETTY_NAME\`
220  - **Kernel Version**: \`$KERNEL_VERSION\`
221  - **Architecture**: \`$ARCHITECTURE\`
222  - **Running in Docker**: \`$DOCKER_CHECK\`
223  - **Running in WSL**: \`$WSL_CHECK\`
224  - **Virtual Machine**: \`$VM_CHECK\`
225  - **System Uptime**: \`$UPTIME\`
226  - **MAC Address**: \`$MAC_ADDR\`
227  - **Local IP**: \`$LOCAL_IP\`
228  - **CPU Model**: \`$CPU_MODEL\`
229  - **Total RAM (GB)**: \`$RAM_GB\`
230  
231  ---
232  
233  _Auto-generated by \`gitfield-local\` push script._
234  EOF
235  
236    # Update push_log.json
237    if command -v jq >/dev/null 2>&1; then
238      jq --arg ts "$TIMESTAMP" \
239         --arg branch "$DEFAULT_BRANCH" \
240         --arg commit "$LATEST_SHA" \
241         --arg msg "$LAST_COMMIT_MSG" \
242         '.local += [{"timestamp": $ts, "branch": $branch, "commit": $commit, "message": $msg}]' \
243         "$PUSH_LOG" > "$PUSH_LOG.tmp" && mv "$PUSH_LOG.tmp" "$PUSH_LOG" 2>/dev/null || warn "Failed to update $PUSH_LOG"
244      info "Updated push log: $PUSH_LOG"
245    else
246      warn "jq not installed. Skipping $PUSH_LOG update."
247    fi
248  }
249  
250  # ╭─────────────────────────────────────╮
251  # │         PUSH TO LOCAL               │
252  # ╰─────────────────────────────────────╯
253  push() {
254    info "Pushing to local bare repository..."
255  
256    # Ensure remote is configured
257    if ! git -C "$REPO_PATH" remote | grep -q "^$GIT_REMOTE_NAME$"; then
258      warn "Local remote not configured. Running configure..."
259      configure
260    fi
261  
262    # Generate metadata
263    generate_metadata
264  
265    # Commit metadata
266    set +e
267    info "Committing metadata file..."
268    git -C "$REPO_PATH" add "$MARKDOWN_FILE" 2>/dev/null || warn "Failed to add metadata file"
269    git -C "$REPO_PATH" commit -m "Local metadata link commit at $TIMESTAMP — $WEB_LINK" 2>/dev/null || warn "No changes to commit"
270    set -e
271  
272    # Push to local remote
273    DEFAULT_BRANCH=$(git -C "$REPO_PATH" symbolic-ref --short HEAD 2>/dev/null || echo "main")
274    set +e
275    info "Pushing to $GIT_REMOTE_NAME/$DEFAULT_BRANCH..."
276    if ! git -C "$REPO_PATH" push "$GIT_REMOTE_NAME" "$DEFAULT_BRANCH" 2>/dev/null; then
277      warn "Push failed. Attempting to recover..."
278      configure
279      git -C "$REPO_PATH" push "$GIT_REMOTE_NAME" "$DEFAULT_BRANCH" 2>/dev/null || error "Failed to push to $GIT_REMOTE_NAME/$DEFAULT_BRANCH after recovery"
280    fi
281    set -e
282  
283    info "✅ Local push complete."
284    echo -e "\n🔗 Local repository: $WEB_LINK\n"
285  }
286  
287  # ╭─────────────────────────────────────╮
288  # │            MAIN EXECUTION           │
289  # ╰─────────────────────────────────────╯
290  main() {
291    case "${1:-push}" in
292      configure)
293        configure
294        ;;
295      status)
296        status
297        ;;
298      push)
299        push
300        ;;
301      *)
302        error "Usage: $0 {configure|status|push}"
303        ;;
304    esac
305  }
306  
307  main "$@"