Git 히스토리에서 민감한 파일과 커밋 제거


353

Git 프로젝트를 GitHub에 배치하고 싶지만 중요한 데이터가 포함 된 특정 파일 (사용자 이름 및 비밀번호, capistrano의 경우 /config/deploy.rb)이 포함되어 있습니다.

이 파일 이름을 .gitignore에 추가 할 수 있지만 Git 내에서 기록을 제거하지는 않습니다.

또한 /.git 디렉토리를 삭제하여 다시 시작하고 싶지 않습니다.

Git 히스토리에서 특정 파일의 모든 흔적 을 제거하는 방법이 있습니까?



답변:


448

모든 실질적인 목적을 위해 가장 먼저 염려해야 할 것은 암호 변경입니다! git 저장소가 완전히 로컬인지 아니면 다른 곳에 원격 저장소가 있는지는 확실하지 않습니다. 원격이고 다른 사람으로부터 보호되지 않으면 문제가있는 것입니다. 이 문제를 해결하기 전에 해당 리포지토리를 복제 한 사용자는 로컬 컴퓨터에 암호 복사본을 갖게되며 기록에서 나온 "고정"버전으로 강제로 업데이트 할 수있는 방법이 없습니다. 당신이 할 수있는 유일한 안전한 방법은 당신이 사용한 다른 곳으로 암호를 바꾸는 것입니다.


그 방법으로 문제를 해결하는 방법은 다음과 같습니다. GitHub는 그 질문에 정확히 FAQ로 답했습니다 .

Windows 사용자를위한 참고 사항 :이 명령에서 작은 따옴표 대신 큰 따옴표 ( ")를 사용하십시오.

git filter-branch --index-filter \
'git update-index --remove PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' <introduction-revision-sha1>..HEAD
git push --force --verbose --dry-run
git push --force

2019 업데이트 :

이것은 FAQ의 현재 코드입니다.

  git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA" \
  --prune-empty --tag-name-filter cat -- --all
  git push --force --verbose --dry-run
  git push --force

이 코드를 GitHub와 같은 원격 저장소로 푸시하고 다른 사람들이 해당 원격 저장소를 복제 한 후에는 이제 히스토리를 다시 작성하는 상황에 처해 있습니다. 이후에 다른 사람이 최신 변경 사항을 풀다운하면 변경 사항을 빨리 적용 할 수 없으므로 적용 할 수 없다는 메시지가 표시됩니다.

이 문제를 해결하려면 기존 리포지토리를 삭제하고 다시 복제하거나 git-rebase 맨 페이지의 "UPSTREAM REBASE에서 복구"아래의 지침을 따라야합니다 .

: 실행git rebase --interactive


나중에 민감한 정보로 실수로 일부 변경 사항을 커밋했지만 원격 리포지토리로 푸시하기 전에 차리면 더 쉽게 해결할 수 있습니다. 마지막 커밋이 민감한 정보를 추가하는 것이면 민감한 정보를 제거하고 다음을 실행하면됩니다.

git commit -a --amend

이렇게하면 이전에 커밋을 수정하여 전체 파일 제거를 포함하여 새로운 변경 사항을 적용합니다. git rm . 변경 사항이 히스토리로 다시 되돌아가도 여전히 원격 저장소로 푸시되지 않은 경우 대화식 리베이스를 수행 할 수 있습니다.

git rebase -i origin/master

그러면 원격 저장소를 사용하여 마지막 공통 조상 이후로 커밋 한 편집기가 열립니다. 민감한 정보가있는 커밋을 나타내는 행에서 "pick"을 "edit"로 변경하고 저장하고 종료하십시오. 힘내 변경 사항을 안내하고 당신이 할 수있는 자리에 당신을 떠날 것입니다 :

$EDITOR file-to-fix
git commit -a --amend
git rebase --continue

민감한 정보로 변경 될 때마다. 결국 지점에 다시 도착하게되며 새로운 변경 사항을 안전하게 적용 할 수 있습니다.


5
완벽한 친구, 그거 좋은 대답입니다. 당신은 내 하루를 저장합니다.
zzeroo

18
한 비트 만 추가하려면 Windows에서 작은 따옴표 대신 큰 따옴표 ( ")를 사용해야합니다.
ripper234

4
이 작동합니다. 나는 번역에서 길을 잃었다. 여기 명령 대신 링크를 사용했습니다. 또한 Windows 명령은 ripper234에서 언급했듯이 MigDus가 제안한 전체 경로 및 링크가 줄 바꿈 표시기로 붙여 넣은 "\"문자를 포함하지 않는 큰 따옴표가 필요했습니다. 최종 명령은 다음과 같습니다 : git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [프로젝트] [파일]. [ext]"--prune-empty --tag- 이름 필터 고양이 - --all
에릭 스완슨

3
filter-branch코드와 링크 된 github 페이지 의 코드 에는 실질적인 차이가있는 것 같습니다 . 예를 들어 그들의 세 번째 줄 --prune-empty --tag-name-filter cat -- --all. 솔루션이 변경되었거나 누락 된 것이 있습니까?
지리학

2
이 솔루션은 꽤 좋아 보이지만 초기 커밋에서 제거 할 파일을 소개하면 <introduction-revision-sha1>..HEAD작동하지 않습니다. 이후 두 번째 커밋에서 파일을 제거합니다. (커밋 범위에 초기 커밋을 포함시키는 방법은 무엇입니까?) 저장 방법은 다음과 같습니다. help.github.com/articles/… git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' \ --prune-empty --tag-name-filter cat -- --all
white_gecko

91

비밀번호를 변경하는 것은 좋은 생각이지만, 리포지토리에서 비밀번호를 제거하는 과정에서는 Git 리포지토리에서 개인 데이터를 제거하기 위해 보다 빠르고 간단한 대안 인 BFG Repo-Cleaner 를 사용하는 것이 좋습니다 git-filter-branch.

private.txt제거 할 비밀번호 등을 나열 하는 파일을 작성하고 (한 줄에 한 항목 씩) 다음 명령을 실행하십시오.

$ java -jar bfg.jar  --replace-text private.txt  my-repo.git

리포지토리의 임계 ​​값 크기 (기본적으로 1MB) 미만의 모든 파일이 검사되고 일치하는 문자열 ( 최근 커밋에 없는 문자열)이 "*** REMOVED ***"문자열로 바뀝니다. 그런 다음 git gc죽은 데이터를 정리 하는 데 사용할 수 있습니다 .

$ git gc --prune=now --aggressive

BFG는 일반적으로 달리는 것보다 10-50 배 빠르며이 git-filter-branch두 가지 일반적인 사용 사례에 따라 옵션이 단순화되고 조정됩니다.

  • 미친 큰 파일 제거
  • 비밀번호, 자격 증명 및 기타 개인 데이터 제거

전체 공개 : 저는 BFG Repo-Cleaner의 저자입니다.


이것은 옵션이지만 암호를 사용할 때 (예 : 데이터베이스 연결 설정) 응용 프로그램이 손상 될 수 있습니다. 작업 복사본에 암호를 계속 유지하고 .gitignore로 암호를 포함하는 파일을 무시할 수 있기 때문에 현재 허용되는 대답을 선호합니다.
Henridv

6
이것은 바로 큰 승리입니다. 몇 번의 시도 후,이 기능을 사용하여 개인 저장소의 민감한 정보가 포함 된 커밋을 제거하고 개정 된 기록으로 원격 저장소를 매우 강력하게 업데이트 할 수있었습니다. 한 가지 참고 사항은이 커밋이 "보호"된 것으로 간주 되어이 도구로 수정되지 않으므로 민감한 데이터없이 리포 (HEAD)의 팁이 깨끗하게 유지되어야한다는 것입니다. 그렇지 않은 경우 수동으로 청소 / 교체하십시오 git commit. 그렇지 않으면 개발자 도구 상자에서 새 도구에 대해 +1 :)
Matt Borja

1
@Henridv 최근 의견에 따르면 응용 프로그램이 현재 지점의 끝이나 머리에 있다고 가정 할 때 예상대로 응용 프로그램을 중단해서는 안됩니다 (예 : 최신 커밋). 이 도구는 These are your protected commits, and so their contents will NOT be altered나머지 커밋 내역을 탐색하고 수정하는 동안 마지막 커밋에 대해 명시 적으로보고합니다 . 그러나 롤백해야 할 경우, 롤백 ***REMOVED***한 커밋에서 검색 만하면됩니다.
Matt Borja

1
BFG의 경우 +1 (Java를 설치했거나 설치하지 않아도되는 경우) 한 가지 발견은 BFG가 파일이 HEAD에 포함되어 있으면 파일 삭제를 거부한다는 것입니다. 따라서 원하는 파일이 삭제되고 BFG 만 실행되는 커밋을 먼저 수행하는 것이 좋습니다. 그 후 마지막 커밋을 되돌릴 수 있지만 이제는 변경되지 않습니다.
Fr0sT

1
이것은 실제로 정답으로 받아 들여 져야합니다. 상자에 나오는 말을합니다!
gjoris

21

GitHub로 푸시 한 경우 강제 푸시로 충분하지 않은 경우 리포지토리를 삭제하거나 지원 센터에 문의하십시오.

나중에 1 초간 강제로 밀어도 아래 설명 된 것처럼 충분하지 않습니다.

유일하게 유효한 조치는 다음과 같습니다.

  • 비밀번호와 같이 변경 가능한 자격 증명이 유출 된 것은 무엇입니까?

    • 예 : 비밀번호를 즉시 수정하고 더 많은 OAuth 및 API 키 사용을 고려하십시오!
    • 아니오 (알몸 사진) :

      • 리포지토리의 모든 문제가 해결되는지 걱정하십니까?

        • 아니오 : 저장소를 삭제하십시오.
        • 예:

          • 연락처 지원
          • 누출이 당신에게 매우 중요한 경우, 누출 가능성을 줄이기 위해 저장소 가동 중지 시간을 기꺼이 할 수있는 시점 까지 GitHub 지원이 귀하에게 응답하기를 기다리는 동안 비공개로 만드십시오.

다음과 같은 이유로 1 초 후에 강제로 충분하지 않습니다.

그러나 강제 푸시 대신 저장소를 삭제하면 커밋이 API에서도 즉시 사라지고 404를 제공합니다 (예 : https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 이 작품을 동일한 이름으로 다른 저장소를 다시 작성하더라도

이것을 테스트하기 위해 https://github.com/cirosantilli/test-dangling 리포지토리를 만들고 다음 을 수행했습니다.

git init
git remote add origin git@github.com:cirosantilli/test-dangling.git

touch a
git add .
git commit -m 0
git push

touch b
git add .
git commit -m 1
git push

touch c
git rm b
git add .
git commit --amend --no-edit
git push -f

GitHub에서 매달려 커밋을 제거하는 방법 도 참조하십시오 .


20

데이비드 언더 힐 (David Underhill) 의이 스크립트 를 추천 합니다 .

natacado의 필터 브랜치와 함께 다음 명령을 추가하여 뒤에 남는 혼란을 정리합니다.

rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune

전체 스크립트 (David Underhill의 모든 크레딧)

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter \
"git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch
# otherwise leaves behind for a long time
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune

다음으로 변경하면 마지막 두 명령이 더 잘 작동 할 수 있습니다.

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now

1
만료 및 프룬 사용은 올바르지 않습니다. 날짜를 지정하지 않으면 프룬에 대해 2 주보다 오래된 모든 커밋이 기본값으로 설정됩니다. 당신이 원하는 것은 모든 커밋입니다 :git gc --aggressive --prune=now
Adam Parkin

@ 아담 파킨 (Adam Parkin) David Underhill 사이트의 스크립트에서 가져온 것이기 때문에 답변에 코드를 동일하게 남겨 두겠습니다. 잘. 정리하기 전에 만료 명령이 영향을 미치지 않습니까?
Jason Goemaat

1
@ MarkusUnterwaditzer : 푸시 커밋에는 작동하지 않습니다.
Max Beikirch 2018

어쩌면 모든 명령을 답에 넣어야 할 것입니다. 그것은 훨씬 더 일관성이 있고 별도의 게시물을 정신적으로 결합하지 않아도됩니다. :)
Andrew Mao

9

명확하게 : 허용되는 답변이 맞습니다. 먼저 해보십시오. 그러나 일부 사용 사례의 경우 특히 '치명적 : 잘못된 개정-자두 비우기'와 같은 눈에 띄지 않는 오류가 발생하거나 실제로 리포지토리의 역사에 관심이없는 경우 불필요하게 복잡 할 수 있습니다.

대안은 다음과 같습니다.

  1. 프로젝트의 기본 분기에 cd
  2. 민감한 코드 / 파일 제거
  3. rm -rf .git / # 코드에서 모든 자식 정보를 제거하십시오
  4. github로 이동하여 저장소를 삭제하십시오.
  5. 이 안내서에 따라 평소와 같이 코드를 새 저장소로 푸시하십시오-https: //help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

이것은 물론 모든 커밋 히스토리 브랜치와 github repo 및 로컬 git repo 모두에서 문제를 제거합니다. 이것이 용납 할 수없는 경우 다른 방법을 사용해야합니다.

이것을 핵 옵션이라고 부릅니다.


9

당신이 사용할 수있는 git forget-blob .

사용법은 매우 간단 git forget-blob file-to-forget합니다. 여기에서 더 많은 정보를 얻을 수 있습니다

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

그것은 당신의 역사, 참조, 태그 등의 모든 커밋에서 사라질 것입니다.

나는 때때로 같은 문제에 부딪 쳤고,이 포스트와 다른 사람들에게 돌아올 때마다 프로세스를 자동화 한 이유입니다.

Stack Overflow의 기여자에게이 크레딧을 제공 할 수있는 크레딧


8

창문에 내 해결책이 있습니다.

git filter-branch --tree-filter "rm -f 'filedir / filename'"HEAD

git push --force

그렇지 않으면 경로가 올바른지 확인하십시오.

나는 그것이 도움이되기를 바랍니다


8

사용 필터 지점 :

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch *file_path_relative_to_git_repo*' --prune-empty --tag-name-filter cat -- --all

git push origin *branch_name* -f

3

나는 이것을 현재까지 몇 번해야했다. 한 번에 하나의 파일에서만 작동합니다.

  1. 파일을 수정 한 모든 커밋 목록을 가져옵니다. 맨 아래에있는 것이 첫 번째 커밋입니다.

    git log --pretty=oneline --branches -- pathToFile

  2. 히스토리에서 파일을 제거하려면 첫 번째 커밋 sha1과 이전 명령의 파일 경로를 사용하여 다음 명령으로 채우십시오.

    git filter-branch --index-filter 'git rm --cached --ignore-unmatch <path-to-file>' -- <sha1-where-the-file-was-first-added>..


3

따라서 다음과 같이 보입니다.

git rm --cached /config/deploy.rb
echo /config/deploy.rb >> .gitignore

git에서 추적 된 파일에 대한 캐시를 제거하고 해당 파일을 .gitignore목록에 추가 하십시오.


2

내 안드로이드 프로젝트 에서 app / src / main / res / values ​​/ 폴더에 xml 파일로 admob_keys.xml 이있었습니다 . 이 민감한 파일을 제거하기 위해 아래 스크립트를 사용하여 완벽하게 작동했습니다.

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch  app/src/main/res/values/admob_keys.xml' \
--prune-empty --tag-name-filter cat -- --all
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.