Webux Lab - Blog
Webux Lab Logo

Webux Lab

By Studio Webux

Search

By Claude Code

Last update 2026-03-25

GitBash

Rollback Permission-Only Changes in Git

The Problem

After restoring a backup with rsync, you run git status and see a wall of modified files. You open git diff and realize none of them have actual content changes — only file permission changes (e.g., mode change 100644 => 100755). rsync preserves the permissions from the backup source, which may differ from what git tracks — so every file whose mode changed during the restore shows up as "modified".

Committing these changes is noise. Rolling them back one by one is tedious.

The Script

rollback_permission_changes.sh automates the cleanup. It scans git diff, identifies every file whose only change is a permission/mode change, and restores them to what HEAD says they should be — leaving files with real content changes completely untouched.

bash rollback_permission_changes.sh

Run with --dry-run first to see exactly what would be rolled back:

bash rollback_permission_changes.sh --dry-run

How It Works

The script uses two git diff modes together:

git diff --numstat reports how many lines were added and deleted per file:

0	0	some-script.sh
5	2	another-file.js

A file with 0 additions and 0 deletions has no content change — only a possible mode change.

git diff --summary reports mode changes:

mode change 100644 => 100755 some-script.sh

The script combines both: a file only qualifies for rollback if --summary shows a mode change and --numstat shows zero line changes. If a file has both a permission change and content changes, it is skipped and reported as a warning — you decide what to do with those.

Once the list is built, the script runs:

git checkout HEAD -- <file1> <file2> ...

This restores the file's mode (and content) to exactly what HEAD tracks, discarding the unwanted permission diff.

Example Output

[INFO]  Files with permission-only changes to roll back:
    →  scripts/deploy.sh
    →  bin/run.sh

[INFO]  Successfully rolled back permission changes for 2 file(s).

If there are mixed files (content + permission change):

[WARN]  Skipping files with BOTH permission AND content changes:
    ↷  src/index.ts

The Full Script

#!/usr/bin/env bash
# rollback_permission_changes.sh

set -euo pipefail

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }

if ! git rev-parse --is-inside-work-tree &>/dev/null; then
  error "Not inside a git repository."
  exit 1
fi

DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
  DRY_RUN=true
  warn "Dry-run mode enabled — no changes will be applied."
fi

permission_only=()
skipped_mixed=()

while IFS=$'\t' read -r added deleted file; do
  if git diff --summary -- "$file" | grep -qE 'mode change'; then
    if [[ ("$added" == "0" || "$added" == "-") && ("$deleted" == "0" || "$deleted" == "-") ]]; then
      permission_only+=("$file")
    else
      skipped_mixed+=("$file")
    fi
  fi
done < <(git diff --numstat)

if [[ ${#skipped_mixed[@]} -gt 0 ]]; then
  warn "Skipping files with BOTH permission AND content changes:"
  for f in "${skipped_mixed[@]}"; do echo "    ↷  $f"; done
fi

if [[ ${#permission_only[@]} -eq 0 ]]; then
  info "No purely permission-only changed files to roll back."
  exit 0
fi

info "Files with permission-only changes to roll back:"
for f in "${permission_only[@]}"; do echo "    →  $f"; done
echo ""

if $DRY_RUN; then
  warn "Dry-run: would run: git checkout HEAD -- ${permission_only[*]}"
else
  git checkout HEAD -- "${permission_only[@]}"
  info "Successfully rolled back permission changes for ${#permission_only[@]} file(s)."
fi

When to Use This

  • After running a tool that recursively sets executable bits (build systems, unzip, Docker volume mounts)
  • When a CI environment applies different umask settings than your local machine
  • Anytime git diff is full of mode-only changes you did not intend