לדלג לתוכן

איך גיט שומר קומיט מתחת למכסה המנוע - מה באמת יש בתיקיית ה-git.

אתם כותבים git commit, ומקבלים מזהה משונה כמו a3f21c9 שמייצג את השינוי שלכם. רוב המתכנתים משתמשים בגיט שנים בלי לדעת מה בדיוק קורה בתוך התיקייה הנסתרת .git. שנוצרת בכל פרויקט. ברגע שמבינים את זה, כל פקודת גיט - branch, merge, rebase - מפסיקה להיות קסם ומתחילה להיות פעולה פשוטה על מבנה נתונים ברור.

גיט לא שומר הבדלים, הוא שומר תמונות מצב

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

שלושת סוגי האובייקטים שמרכיבים כל קומיט

מתחת למכסה המנוע, גיט בנוי משלושה סוגי אובייקטים עיקריים, ששומרים על מבנה בסיס נתונים פשוט שנקרא content-addressable storage - כל תוכן מזוהה לפי hash שמחושב מהתוכן שלו עצמו:

  • Blob. מייצג תוכן של קובץ בודד, בלי שום מידע על שם הקובץ או המיקום שלו - רק התוכן הגולמי. אם שני קבצים שונים בשם מכילים בדיוק אותו תוכן, גיט שומר עבורם blob יחיד, כי ה-hash של שניהם זהה.
  • Tree. מייצג תיקייה. הוא רשימה של רשומות, כשלכל רשומה יש שם קובץ או תיקייה, וה-hash של ה-blob או ה-tree שהיא מצביעה אליו. tree יכול להצביע על tree אחר, וכך נבנה מבנה תיקיות מקונן שלם.
  • Commit. מצביע על tree יחיד שמייצג את שורש הפרויקט באותו רגע, ומחזיק מטא-מידע - מי כתב, מתי, הודעת הקומיט, וחשוב מאוד, מצביע (hash) אל הקומיט הקודם, זה שממנו הוא נוצר.

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

מה זה בעצם ה-hash שכל קומיט מקבל

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

איך זה מסביר פקודות שנראות מסתוריות

עכשיו כשמבינים את המבנה, פקודות שנראו כמו קסם הופכות פשוטות. branch הוא בעצם רק מצביע (pointer) קטן שמחזיק hash של קומיט מסוים - כשאתם יוצרים ענף חדש, גיט פשוט יוצר קובץ קטן חדש עם אותו hash. checkout לקומיט ישן פשוט מציב את תיקיית העבודה שלכם לפי ה-tree שהקומיט הזה מצביע עליו. ו-merge הוא בעצם מציאת קומיט אב משותף בהיסטוריה, והשוואה בין שלושה snapshots - שלכם, של הענף השני, ושל האב המשותף - כדי להחליט מה השתנה בכל צד.

למה שווה להבין את זה

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

קורס התכנות הבסיסי שלנו מלמד גיט מהיסודות, כחלק בלתי נפרד מלכתוב קוד.

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

הצטרפו לקהילה בדיסקורד

לסיכום

גיט לא שומר רשימת שינויים, הוא שומר שרשרת של תמונות מצב מלאות, בנויות משלושה סוגי אובייקטים פשוטים - blob לתוכן, tree למבנה תיקיות, וcommit שמצביע על tree ועל הקומיט הקודם לו. כל זה מזוהה לפי hash של התוכן עצמו, וברגע שמבינים את זה, כל שאר גיט הופך להרבה יותר הגיוני.