DanLevy.net

קרב: Git Rebase לעומת Merge

שאלה נצחית...

Hero image for קרב: Git Rebase לעומת Merge

קרב מוות: Git Rebase מול (Squash) Merge!

האם עליי לבצע Rebase? או Squash Merge?

מדוע הנושא הזה מעורר להט דתי?

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

גורמים נוספים כוללים ככל הנראה הטיית היכרות והטיית הישרדות (Survivorship Bias) שיכולות לטשטש עוד יותר את ההערכה וההנחות שלנו.

שאלת מפתח: מהי מטרתה של commit ב-git?

  1. האם אתה מבצע commit מוקדם ולעתים קרובות? תוך שימוש בגישת “נקודת ביקורת” או גיבוי?
    • שבו הכל מתועד, אפילו התחלות כוזבות וניסויים? (למשל git commit -am "Updated deps" && git push, חזור באופן קבוע)
    • אולי הודעות commit פחות חשובות לך מהקוד?
  2. או, האם ה-commit שלך הם יצירת אמנות מעוצבת ומסותתת בקפידה?
    • אולי כל commit הוא יחידת עבודה עצמאית ואטומית? (למשל git add package.json && git commit -m "Updated deps")
    • או, אתה פשוט לא יכול לסבול לוגים של commit “מבולגנים”?
    • האם סקירות ה-PR שלך כוללות לעתים קרובות סקירה commit אחר commit?

| 💡 אילו מודלים מנטליים אחרים מגדירים איך אתה רואה commits? אנא יידע אותי @justsml!

האם אתה חושב על git בצורה שמספקת את הערך הרב ביותר לך, לצוות שלך ולארגון שלך?

בהינתן שיש גישות שונות מאוד סביב אסטרטגיית commits, אין פלא שיש כל כך הרבה בלבול לגבי הדרך ה”נכונה” להשתמש ב-git.

תרחיש: יצירת תגית release מתוקנת

הבה נשווה את התהליך של יצירת תגית release תוך השמטת כמה commits אחרונים ב-main.

תגית Git משוחררת מ-main עם 2 ענפי תכונה

הדרך של Rebase

מודל מנטלי: “אני רוצה ליצור גרסה חלופית של היסטוריה קיימת. (למשל, עשיתי טעות לפני 16 merges, ואולי אצטרך שליטה מדויקת כדי לתקן אותה. בנוסף, עלול להיתקע בלולאה אינסופית לכאורה של קונפליקטים ו---continue.)”

  1. קבל את העדכני: git checkout main && git pull
  2. צור ענף חדש: git checkout -b release/hot-newness-and-stuff
  3. התחל rebase אינטראקטיבי וכלול את ה-git ref למקום שאליו אתה רוצה לנסוע אחורה בזמן. git rebase -i HEAD~6 (הערה: HEAD~6 הוא קיצור ‘git ref’ עבור 6 commits ago)
  4. הסר את ה-commit(s) הרצויים על ידי שינוי התחילית ל-drop. שמור וסגור את העורך.
  5. תקן קונפליקטים של מיזוג, git add . && git rebase --continue (אל תעשה git commit).
  6. חזור על הצעד הקודם עד להשלמה.
  7. תייג/דחוף באמצעות התהליך הנוכחי. דוגמה git tag -a v1.2.3 -m 'Release v1.2.3' && git push --tags

יתרונות

חסרונות

דרך המיזוג (Squash)

מודל מנטלי: “אני רוצה שחרור מותאם אישית, החל מנקודה נתונה, וכולל כל ענף/ים רצוי/ים.”

  1. קבל את העדכני ביותר: git checkout main && git pull
  2. צור ענף חדש: git checkout -b release/hot-newness-and-stuff
  3. מזג ענפים ו/או קומיטים רצויים: git merge --no-ff feature/hot-newness bug/fix-123 (השתמש בדגל --no-ff היכן שאפשר.)
  4. תקן כל קונפליקט מיזוג (אם יתעורר.)
  5. תייג/דחוף באמצעות התהליך הנוכחי. דוגמה git tag -a v1.2.3 -m 'Release v1.2.3' && git push --tags

יתרונות

חסרונות

מסקנה

בסופו של דבר, תהליך פשוט יותר עם פחות סיכון צריך לנצח.

למרות שלמשתמשי Rebase אכן יש דרכים לפתור (או להימנע) מהבעיות שלהם, העובדה נשארת: בסופו של דבר תצטרך חגורה שחורה ב-git fu. (למשל, אפילו git push פשוט יכול להוסיף מורכבות: האם זה git push --force או git push --force-with-lease? למה להתעסק עם זה בכלל?)

יש סיבה נוספת לכך שrebasing ליצירת היסטוריה מתוקנת תמיד יהיה בעמדת נחיתות לעומת git merge .... git merge מאפשר ל-CLI של git להפעיל אלגוריתמים מתקדמים כדי להימנע מקונפליקטים על ידי ניתוח ה-HEAD של כל ענף.

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

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

סיכום