spt-mods/docs/git-image-permission-workar...

5.8 KiB

Git 이미지(image) 파일 Operation not permitted 우회(workaround) 문서

배경(background) / 증상(symptom)

일부 환경(macOS 등)에서 git add, git hash-object <path>가 특정 이미지 파일(.png, .jpg 등)을 읽기 위해 열 때(open) 아래 오류로 실패할 수 있습니다.

error: open("path/to/file.png"): Operation not permitted
fatal: could not open 'file.png' for reading: Operation not permitted

이 경우 OS 레벨에서는 파일 읽기가 되지만(Python 등으로는 open() 가능), Git이 경로(path)를 직접 열어 객체(object)를 생성하려는 단계에서 차단될 수 있습니다.

목표(goal)

  • 변경사항(changes) 전체를 커밋(commit) 해야 하는데,
  • Git이 이미지 파일을 직접 열 수 없어서 git add가 실패할 때,
  • Git이 파일을 “직접 열지 않도록” 우회해서 staging/index에 올리는 방법을 제공.

우회(workaround) 개요

핵심 아이디어는 2단계입니다.

  1. 이미지 파일을 제외하고 나머지 파일은 정상적으로 git add로 스테이징(staging)
  2. 이미지 파일은
    • Python이 파일 bytes를 읽고
    • git hash-object -w --stdin으로 stdin(표준입력) 을 통해 blob을 만들어(= Git이 파일 path를 직접 open하지 않음)
    • git update-index --add --cacheinfo ... <path>로 index에 직접 등록

1) 이미지 제외하고 먼저 스테이징(staging)

아래처럼 이미지 확장자를 제외(exclude)하고 git add를 실행합니다.

git add -A -- . \
  ':(exclude)**/*.png' \
  ':(exclude)**/*.jpg' \
  ':(exclude)**/*.jpeg' \
  ':(exclude)**/*.gif' \
  ':(exclude)**/*.webp'

2) 이미지 파일을 stdin + update-index로 스테이징(staging)

아래 스크립트는 untracked(추적 안됨) 이미지 파일을 찾아서, 각 파일을 우회 방식으로 스테이징합니다.

python3 - <<'PY'
import subprocess
from pathlib import Path

image_exts = {'.png', '.jpg', '.jpeg', '.gif', '.webp'}

# repo에 새로 추가된(untracked) 파일 목록
res = subprocess.run(
    ['git', 'ls-files', '--others', '--exclude-standard'],
    check=True,
    capture_output=True,
    text=True,
)
paths = [p for p in res.stdout.splitlines() if p]
img_paths = [p for p in paths if Path(p).suffix.lower() in image_exts]

print(f'found_images={len(img_paths)}')

for p in img_paths:
    data = Path(p).read_bytes()  # OS 레벨 read

    # Git이 file path를 직접 open하지 않도록 stdin으로 blob 작성(write)
    ho = subprocess.run(
        ['git', 'hash-object', '-w', '--stdin'],
        input=data,
        capture_output=True,
    )
    if ho.returncode != 0:
        raise SystemExit(
            f'hash-object failed for {p}: {ho.stderr.decode(errors="replace")}'
        )
    blob = ho.stdout.decode().strip()

    # index에 직접 등록(add): mode 100644 + blob sha + path
    ui = subprocess.run(
        ['git', 'update-index', '--add', '--cacheinfo', '100644', blob, p],
        capture_output=True,
        text=True,
    )
    if ui.returncode != 0:
        raise SystemExit(f'update-index failed for {p}: {ui.stderr}')

print('done')
PY

확인(check)

git status
git diff --staged --stat

3) 커밋(commit)

git commit -m "..."

(선택) 커밋 후 git status에 이미지가 M으로 남아 보일 때

환경에 따라 커밋 후에도 이미지 파일들이 git status에서 modified(M)로 보일 수 있습니다. 이 경우 실제로 내용이 바뀐 게 아니라, Git이 워킹트리(working tree)와 index를 비교하는 과정에서 다시 파일 open/read가 막히거나(또는 메타데이터 차이로) 변경으로 인식할 수 있습니다.

이때는 로컬(local)에서만 해당 파일들을 “변경 무시(assume-unchanged)” 처리해 작업 트리를 깨끗하게 보이게 할 수 있습니다.

assume-unchanged 설정(set)

git update-index --assume-unchanged -- <file...>

예: 현재 M인 파일을 자동으로 찾아 처리하려면:

python3 - <<'PY'
import subprocess

res = subprocess.run(
    ['git', 'status', '--porcelain=v1'],
    check=True,
    capture_output=True,
    text=True,
)

paths = []
for line in res.stdout.splitlines():
    if not line:
        continue
    # format: XY<space>path
    x, y = line[0], line[1]
    path = line[3:]
    if y == 'M':
        paths.append(path)

print('mark_assume_unchanged', len(paths))

chunk_size = 200
for i in range(0, len(paths), chunk_size):
    chunk = paths[i:i + chunk_size]
    subprocess.run(['git', 'update-index', '--assume-unchanged', '--', *chunk], check=True)

print('done')
PY

assume-unchanged 해제(unset)

git update-index --no-assume-unchanged -- <file>

주의사항(warnings)

  • assume-unchanged커밋에 포함되지 않는 로컬 상태(local state) 입니다.
  • 설정 후에는 해당 파일이 실제로 변경돼도 Git이 변경을 잘 감지하지 않을 수 있습니다.
    • 실제로 수정할 일이 생기면, 먼저 --no-assume-unchanged로 해제하고 작업하세요.

FAQ / 트러블슈팅(troubleshooting)

  • Q. 왜 Python은 읽히는데 Git은 못 읽나요?

    • A. 환경/보안 정책에 따라 특정 프로세스/바이너리의 파일 접근이 차단되는 케이스가 있습니다. 여기서는 “Git이 경로로 파일을 여는(open) 방식”이 막혔고, stdin으로 bytes를 전달하는 방식은 통과했습니다.
  • Q. 이미지뿐 아니라 다른 바이너리(binary)도 막히면?

    • A. 동일한 방식으로 확장자(ext)를 늘리거나, 대상 파일 목록을 바꿔서 stdin + update-index로 스테이징할 수 있습니다.