לאן הולכים הקומיטים אחרי reset?

13/09/2021

טריק מעניין של גיט שהצליח לבלבל אותי כשרק התחלתי לעבוד עם הכלי הוא האופן בו גיט מנהל את הקומיטים שלו. בקצרה בגיט כל המידע נשמר בתור גרף של קומיטים, כאשר כל קומיט "מצביע" על הקומיט שבא לפניו. חוץ מהקומיטים יש גם דבר שנקרא Branch-ים, שאפשר לחשוב עליהם כמו מדבקות או סימניות. תרצו תוכלו להדביק אותם על הקומיטים, וכשיימאס לכם תוכלו להזיז אותם לקומיטים אחרים.

חוץ מה Branch-ים וה Commit-ים יש עוד סימניה חשובה שנקראת HEAD. ה HEAD הוא מצביע ל Branch והוא מסמן לנו מה הקומיט "הנוכחי", כלומר מה הקומיט שעכשיו אנחנו עובדים עליו.

נראה את זה עם משחק על מאגר פשוט. יצרתי מאגר עם שלושה קומיטים שהלוג שלו נראה כך:

* 8131ef2 (HEAD -> main) third commit
* 3a40bc4 second commit
* e8006f8 first commit

בגרף יש שלושה קומיטים, בראנץ אחד בשם main וה HEAD שלנו הוא אותו main.

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

$ git reset --hard HEAD~

ואז נסתכל בלוג:

3a40bc4 (HEAD -> main) second commit
e8006f8 first commit

בום! לאן נעלם הקומיט השלישי?

התשובה הקצרה היא שהוא לא נעלם לשום מקום. בסך הכל הזזתי את main ואיתו את HEAD ולכן כשאני מפעיל עכשיו git log אני מתחיל את הספירה ממקום אחר - מהקומיט השני. לצערנו קומיטים לא שומרים מצביע לקומיט שבא אחריהם (הגרף מכוון הפוך, כלומר כל קומיט יודע רק מי בא לפניו) - וזה אומר שאני בנקודה בחיים בה הקומיט הנוכחי פשוט לא יודע לספר לי איך להגיע לקומיט העדכני ביותר.

אבל זה לא אומר שאותו קומיט נעלם או נמחק.

אם אתם זוכרים את מזהה הקומיט אתם יכולים בקלות לחזור אליו עם:

$ git reset --hard 8131ef2

אפילו אם אתם לא זוכרים את המזהה אתם יכולים בקלות לבדוק מה הוא היה עם:

$ git reflog

3a40bc4 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~
8131ef2 HEAD@{1}: reset: moving to 8131ef2
3a40bc4 (HEAD -> main) HEAD@{2}: reset: moving to main
3a40bc4 (HEAD -> main) HEAD@{3}: reset: moving to HEAD~
8131ef2 HEAD@{4}: commit: third commit
3a40bc4 (HEAD -> main) HEAD@{5}: commit: second commit
e8006f8 HEAD@{6}: commit (initial): first commit

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

$ git reflog expire --expire-unreachable=now --all

וזה ימחק מהיומן כל התיחסות לקומיט 8131ef2, מה שיהפוך את הטריק להרבה יותר מפחיד כיוון שעכשיו יהיה הרבה יותר קשה להגיע לאותו הקומיט (אבל עדיין אפשרי, אם יודעים את המזהה או אם יש לכם מספיק סבלנות לחטט בתיקיית .git כדי למצוא את המזהה).

מחיקה מוחלטת של אותו קומיט אפשרית עם הפקודה:

$ git gc --prune=now

רק אחרי שמחקתם את הקומיט מה reflog כדי שבאמת אי אפשר יהיה להגיע אליו או לכל קומיט אחר שמצביע עליו. אבל מהלך כזה עלול לצאת מתחום ההומור הבריא ולגרום לחברים שלכם להיבהל באמת.