פוסט
Git מאפס לגיבור: ענפים, מיזוג וקונפליקטים
חלק 4 במדריך ל-Git: מה זה ענף באמת, fast-forward ו-commit מיזוג, איך מיזוג תלת-כיווני מחליט, ואיך פותרים קונפליקט בלי לחץ.
ענף הוא התווית מחלק 2 שזזה קדימה עם כל commit. בחלק הזה משתמשים בענף כדי לנסות מתכון לפיצה בספר המתכונים בלי לגעת ב-main.
ענפים
git switch -c pizza
עכשיו אנחנו על pizza. ה-commits הולכים לשם, ו-main נשאר במקום:
echo "# Pizza" > pizza.md
git add pizza.md
git commit -m "Add pizza recipe"
echo "Bake 20 minutes." >> pizza.md
git commit -am "Add baking time"
ענף כמעט לא עולה כלום, אז פותחים אחד לכל רעיון.
git branch # רשימת ענפים, * מסמן את הנוכחי
git switch main # חזרה; pizza.md נעלם מהתיקייה
git switch pizza # והוא חוזר
מה זה ענף באמת
ענף הוא לא עותק של הפרויקט. זה קובץ זעיר עם hash אחד של commit:
cat .git/refs/heads/main
cat .git/HEAD
9c1d4e2a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4
ref: refs/heads/pizza
הקובץ הראשון הוא הענף main: hash ותו לא. השני הוא HEAD, שאומר “אני על pizza”.
- יצירת ענף כותבת קובץ קטן אחד.
- מעבר לענף משנה מה HEAD אומר ומחליף את קבצי העבודה באלה של אותו צילום.
- מחיקת ענף מסירה את התווית. ה-commits נשארים, ו-
git reflogיכולה למצוא אותם עד ש-Git מנקה commits שאי אפשר להגיע אליהם, אחרי תקופת חסד.
מיזוג
הפיצה גמורה. מביאים אותה ל-main:
git switch main
git merge pizza
מה שקורה תלוי אם main זז בזמן שלא היינו שם:
- אם
mainלא השתנה, Git עושה fast-forward: מזיז את התוויתmainקדימה. אין commit חדש. - אם בשני הענפים יש commits חדשים, Git יוצר commit של מיזוג עם שני הורים, שקושר את ההיסטוריות.
ואז מנקים:
git branch -d pizza # בטוח: מסרב אם הענף לא מוזג
איך Git ממזג
Git מוצא את בסיס המיזוג (merge base), ה-commit האחרון ששני הענפים חולקים, ומשווה כל ענף מולו:
git merge-base main pizza # מדפיסה את ה-hash של B
Git משלב את מה שהשתנה מ-B ל-C ומ-B ל-D:
- שורה ששונתה בצד אחד בלבד נלקחת כמו שהיא.
- שורה ששונתה בשני הצדדים לאותו דבר נלקחת פעם אחת.
- שורה ששונתה בשני הצדדים באופן שונה היא קונפליקט. Git לא יכול לדעת למה התכוונו.
ברוב המקרים שני הענפים נוגעים במקומות שונים, ולכן מיזוגים בדרך כלל פשוט עובדים.
קונפליקטים
Auto-merging pizza.md
CONFLICT (content): Merge conflict in pizza.md
Automatic merge failed; fix conflicts and then commit the result.
פותחים את הקובץ. Git סימן את שתי הגרסאות:
<<<<<<< HEAD
Bake 20 minutes.
=======
Bake 25 minutes.
>>>>>>> pizza
מה שבין <<<<<<< ל-======= הוא שלנו (main), והשאר שלהם (pizza). מחליטים, מוחקים את הסימונים ומשאירים את מה שרוצים:
Bake 25 minutes.
git add pizza.md
git commit # משלימה את המיזוג
התחרטנו באמצע? git merge --abort מחזירה הכול למה שהיה. קונפליקט הוא לא שגיאה, אלא Git ששואל שאלה שרק אנחנו יכולים לענות עליה.
- ממזגים את
mainלענף שלנו לעתים קרובות (או עושים rebase עליו), כדי שההבדלים לא יצטברו, ושומרים על ענפים קצרי חיים וממוקדים. git statusבזמן קונפליקט מציגה Unmerged paths, הקבצים שעדיין מחכים לנו.git addמסמנת קובץ כפתור.- כדי לקחת צד אחד בשלמותו:
git checkout --ours <קובץ>או--theirs, ואזgit add. בזמן rebase המשמעות מתהפכת: “ours” הוא הענף ש-rebase נעשה עליו, ו-“theirs” הוא ה-commit שלנו שמורץ מחדש. - עורכים (VS Code, JetBrains) מציעים “קבל נוכחי / נכנס” בלחיצה, ו-
git mergetoolפותחת כלי חזותי.