• בלוג
  • איך למחוק קובץ לחלוטין מכל ההיסטוריה של גיט באמצעות git filter branch

איך למחוק קובץ לחלוטין מכל ההיסטוריה של גיט באמצעות git filter branch

22/09/2018
git

פוסט זה כולל טיפ קצר לעבודה יעילה יותר עם Git.
בשביל להבין את גיט לעומק אני ממליץ על קורס גיט שלנו כאן באתר.
בקורס תלמדו איך גיט עובד מתחת למכסה המנוע ואיך להשתמש בו בצורה יעילה מהבסיס ועד לנושאים המתקדמים.

כלל ידוע בגיט הוא שלא כדאי לשמור שם מידע רגיש כי מי יודע מתי תשכחו את תיקיית .git שלכם על איזה שרת ציבורי. אבל לפעמים בכל זאת אנחנו בטעות מוסיפים מידע רגיש לגיט ואז צריך באיזושהי דרך למחוק אותו. ואל תחשבו אפילו על git rm כי הוא אולי ימחק החל מהקומיט הנוכחי אבל עדיין יש לכם את המידע הרגיש בכל ההיסטוריה. בואו נדבר על פקודה הרבה יותר מתוחכמת בשם git filter branch והיכולת המופלאה שלה לשנות את כל ההיסטוריה.

1. איך מוחקים קובץ לגמרי ממאגר git

הבעיה עם מחיקת קובץ מ git היא שגיט שומר את כל ההיסטוריה של הפרויקט לנצח. מספיק שהקובץ היה שמור אי פעם באיזשהו קומיט בשביל שאפשר יהיה לחזור אליו. וזה נהיה יותר גרוע ככל שאתם מתרחקים ממועד הקומיט הראשוני: כי כל קומיט בגיט מחזיק תמונת מצב של כל עץ הפרויקט שלכם נכון לאותה נקודה ולכן כל הקומיטים החדשים שיצרתם מרגע שהכנסתם קובץ בטעות גם יכילו את אותו הקובץ.

לכן הדרך היחידה שלנו להעיף קובץ מהמאגר היא לשנות את כל הקומיטים שכוללים אותו אחד-אחד. וזה בדיוק מה שהפקודה git filter-branch יודעת לעשות.

הפקודה מקבלת סידרה של קומיטים ונותנת לכם הזדמנות לשנות מידע של כל אחד מהקומיטים האלה וליצור קומיט חדש עם המידע המעודכן. במקרה של קובץ שאנחנו לא רוצים בפרויקט אנחנו נמחק את הקובץ מכל קומיט בנפרד (באופן אוטומטי עם filter branch) וניצור סידרה חדשה לגמרי של קומיטים.

בסיום התהליך יהיו לנו קומיטים חדשים לגמרי בלי הקובץ. אחרי שנראה שהכל תקין נוכל למחוק את כל הקומיטים הישנים מהפרויקט ומהלוגים ולנשום לרווחה. כמו בכל מקרה של שינוי ההיסטוריה כדאי מאוד לוודא שאף אחד לא יעשה push עם קומיט שמתבסס על קומיט שנמחק. אם אפשר כדאי לאסוף את כל המתכנתים בפרויקט ולבקש מכולם לעשות clone מחדש אחרי התהליך.

2. הפקודות

נראה דוגמא למחיקה כזו על מאגר. נניח שיש לנו מאגר שהלוג שלו נראה כך:

$ git log --oneline
873aad6 (HEAD -> master) fixed memory overflow
5e7448d ignoring build result
cf315ed removed object file
4a70ef5 add some code
a5e5d6e initial commit

ובקובץ שהוכנס למאגר בקומיט הראשון יש סיסמא שאנחנו רוצים להיפטר ממנה. שם הקובץ הוא secret וכשנסתכל על הקומיט הראשון כבר נוכל למצוא אותו:

$ git ls-tree a5e5d6e
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    demo.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    main.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    player.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    player.h
100644 blob c33c9fe2c454ac0ea0810558fcb1aeed778e2fb7    secret
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    utils.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    utils.h

תוכן הקובץ:

$ git cat-file blob c33c9fe2c454ac0ea0810558fcb1aeed778e2fb7
the password is: 12345

אין טעם לקרוא ל git rm secret. פקודה זו תייצר קומיט חדש בלי הקובץ אבל כל הקומיטים הישנים עדיין יכילו אותו וכל אחד יוכל לחזור להיסטוריה לראות מה הסיסמא.

במקום זה נפעיל את git filter-branch:

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch secret' --prune-empty
Rewrite a5e5d6ef21101ded84727d0ecf441e3e3cac01f3 (1/5) (0 seconds passed, remaining 0 predicted)    rm 'secret'
Rewrite 4a70ef5f55a96b633e02abc742befad2426d9152 (2/5) (0 seconds passed, remaining 0 predicted)    rm 'secret'
Rewrite cf315ed446d7f6680b92c89f7a475ec9292df381 (3/5) (0 seconds passed, remaining 0 predicted)    rm 'secret'
Rewrite 5e7448d82e08ecd9d1715e141110d9cb0710c6e6 (4/5) (0 seconds passed, remaining 0 predicted)    rm 'secret'
Rewrite 873aad68582802dc982cd9c8f4cce210f3216df1 (5/5) (0 seconds passed, remaining 0 predicted)    rm 'secret'

מה קרה כאן? הפקודה git filter-branch עוברת על כל אחד מהקומיטים וכאילו עושה checkout לקומיט זה ואז יכולה להפעיל פקודה מתוך אותו קומיט. הפקודה יכולה לשנות את נתוני הקומיט ואז באופן אוטומטי התוצאה מוכנסת למאגר בתור קומיט חדש.

לפקודה git filter-branch יש די הרבה אפשרויות. האפשרות שאני בחרתי היא index-filter. אפשרות זו נותנת לנו הזדמנות לשנות את ה Staging Area של כל קומיט אבל לא באמת מעתיקה את המידע לתיקיית העבודה. זה רץ יחסית מהר אבל כמובן במקרים יותר מורכבים (של שינוי תוכן של קובץ למשל) כנראה תרצו להשתמש באפשרות tree-filter שמעתיקה גם את כל הקבצים לתיקיית העבודה.

הפקודה שאנחנו מריצים היא:

git rm --cached --ignore-unmatch secret

זוהי פקודת git שמוחקת קובץ מה Staging Area בלי להשפיע על תיקיית העבודה (וזה מצוין כי אין לנו תיקיית עבודה). המתג ignore-unmatch אומר שזה לא נורא אם הקובץ לא נמצא (למשל אם מריצים את הפילטר הזה על קומיט שלא מכיל את הקובץ).

המתג הבא שהעברתי ל filter-branch היה --prune-empty. מתג זה מבטל קומיטים שבעקבות הפילטר הם עכשיו ריקים כלומר אין בהם שינויים לעומת הקומיט שלפניהם. דוגמא קלאסית היא קומיט שמשנה רק את הקובץ הזה שאני מוחק.

אחרי ההפעלה אני מקבל לוג שנראה כך:

$ git log --oneline
bc45b68 (HEAD -> master) fixed memory overflow
aadc869 ignoring build result
74b71d1 removed object file
38752bf add some code
7ef8f24 initial commit

יש פה בדיוק את אותן הודעות אבל כל הקומיטים עכשיו השתנו וקיבלו מזהים אחרים. מבט על אחד הקומיטים האלה יוכיח שהקובץ כבר לא קיים שם:

100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    demo.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    main.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    player.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    player.h
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    utils.c
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    utils.h

3. מחיקת הקומיטים הישנים

אבל עדיין אי אפשר לנשום לרווחה כי כמו שאתם יודעים גיט לא בדיוק אוהב למחוק דברים והקומיטים הישנים עדיין קיימים במאגר. נסו את הפקודה הבאה ותופתעו לגלות שעדיין אפשר לראות את הסיסמא:

$ git cat-file blob c33c9fe2c454ac0ea0810558fcb1aeed778e2fb7
the password is: 12345

גיט שומר את כל המידע גם ב reflog וגם כגיבוי ל filter-branch. הפקודות הבאות מוחקות את כל המידע לחלוטין וכך מנקות את המאגר:

$ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
$ git reflog expire --expire=now --all
$ git gc --prune=now

ועכשיו כל ניסיון להציג את הקובץ או למצוא את הקומיטים הישנים ייכשל.

רוצים לשמוע עוד על filter branch ובכלל על אוטומציה בגיט? בואו לוובינר בשבוע הבא שידבר בדיוק על הנושאים האלה. הרשמה בחינם בקישור:

https://www.tocode.co.il/workshops/46