מדריך: איחסון קבצים גדולים בגיט עם git lfs

10/04/2022
git

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

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

1. החיים עם קבצים גדולים

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

ניצור בשביל הדוגמה מאגר עם קובץ גדול. אני מפעיל את הפקודה:

$ dd if=/dev/random of=myfile.bin bs=1m count=100

ומקבל קובץ בשם myfile.bin בגודל 100 מגה. אחרי זה אני יוצר מאגר גיט ומוסיף את הקובץ למאגר ו-הופ, הפרויקט שלי תופס כבר 200 מגה - כי יש לי עותק אחד של הקובץ הגדול בתיקיית העבודה ועותק נוסף במאגר בתיקיית .git. אם אני יוצר גירסה נוספת של הקובץ ומוסיף גם אותה למאגר התיקיה תעבור כבר את ה 300 מגה.

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

2. אז מה זה git lfs

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

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

בשביל לעבוד עם git lfs אתם צריכים להתקין את התוסף לכל המערכת שלכם מהאתר שלהם: https://git-lfs.github.com/

ואחרי זה בכל ריפו שרוצים לעבוד איתו צריך להפעיל בתוך תיקיית הריפו:

$ git lfs install

3. איך זה עובד

דוגמה? ברור. יצרתי ריפו שיש בו קובץ גדול והפעלתי בתוכו את:

$ git lfs install

בשביל ש git lfs יוכל לעשות את הקסם שלו.

הפקודה הראשונה שצריך להכיר היא git lfs track. לקובץ שלי קוראים myfile.bin ולכן אני מפעיל:

$ git lfs track -f myfile.bin

כדי לסמן ל git lfs שהקובץ myfile.bin הוא קובץ גדול וצריך לטפל בו בהתאם. אפשר גם להעביר ל track תבניות לקבצים (ואז מוותרים על ה -f) למשל:

$ git lfs track "*.bin"

הדבר הזה יוצר קובץ .gitattributes אם לא קיים אצלכם בתיקיה (או מוסיף שורה לקובץ אם כבר קיים), ושם הוא מסמן שהקובץ או התבנית נכנסים למעקב:

$ cat .gitattributes
myfile.bin filter=lfs diff=lfs merge=lfs -text

עכשיו אפשר להוסיף את הקובץ למאגר:

$ git add myfile.bin
$ git commit -m 'initial commit'

ואפילו להכניס גירסה נוספת שלו:

$ dd if=/dev/random of=myfile.bin bs=1m count=100
$ git commit -a -m 'commit 2'

ולזה היו כמה תוצאות מעניינות. הראשונה היא שתיקיית .git/objects שלי ממש קטנה:

$ du -sh .git/objects
 32K    .git/objects

וזה בגלל שהקבצים עצמם נשמרים בתיקיה אחרת:

$ du -sh .git/lfs/objects
200M    .git/lfs/objects

וכשאני מסתכל בתוך הריפוזיטורי על הקובץ:

$ git ls-files --stage -- myfile.bin
100644 e0ffbeb5780cceeeef67522f4bc092751c4e7409 0       myfile.bin

$ git cat-file blob e0ffbeb5780cceeeef67522f4bc092751c4e7409

version https://git-lfs.github.com/spec/v1
oid sha256:fc4d5fa4f28329049cb1c1bdda562a7e6aa3472efbf94cc280f1f2aa9c6790a5
size 104857600

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

כרגע הקבצים הגדולים נמצאים אצלי בתיקיית העבודה וגם כשאני דוחף את הפרויקט שלי לגיטהאב הם יישלחו לשם. הפרויקט לדוגמה נמצא כאן: https://github.com/ynonp/large-files-with-lfs

4. מה הרווחנו

ייתרון ראשון שאנחנו רואים בהתקנת git-lfs הוא שפעולות add ו commit הופכות להרבה הרבה יותר מהירות. הסיבה היא שגיט מנסה לכווץ קבצים שהוא מכניס למאגר, וכיווץ קובץ בינארי ענק הוא פשוט בזבוז זמן. עם git lfs אנחנו חוסכים את הבזבוז הזה.

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

$ GIT_LFS_SKIP_SMUDGE=1 git clone https://github.com/ynonp/large-files-with-lfs.git skip-smudg
e

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

$ cd skip-smudge
$ du -sh .
140K    .

אם ננסה להסתכל על תוכן הקובץ נקבל את אותה גירסה שהיתה שמורה במאגר:

$ cat myfile.bin
version https://git-lfs.github.com/spec/v1
oid sha256:fc4d5fa4f28329049cb1c1bdda562a7e6aa3472efbf94cc280f1f2aa9c6790a5
size 104857600

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

$ git lfs pull

בדוגמה שלנו הפקודה מורידה רק עותק אחד של הקובץ myfile.bin ולא את שני העותקים שיש במאגר.