פורסם ב זה רק קוד, כלים לחיים קלים

הקומיט האולטימאטיבי עם ריבייס אינטראקטיבי

זמן קריאה: 15 דקות

בכנס DevOpsDays Tel Aviv שהייתי בו לפני כמה שבועות, הייתי בהרצאה נהדרת של דפנה רגב שהסבירה לקהל את המכניקה הבסיסית שמאחורי גיט. במהלך ההרצאה היא אמרה משפט שאני סופר מתחברת אליו – שהלוג של גיט צריך להיקרא כמו סיפור.

כשהתחלתי לקודד לפני אי-אילו שנים, העבודה שלי עם גיט נראתה בדיוק ככה –

תהליך העבודה הסטנדרטי של רוב המתכנתים עם גיט

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

לאורך השנים המצב השתפר, אבל עדיין עבדתי עם גיט באופן מאוד מכני ואוטומטי – בראנצ’ חדש, קומיטים, PR ומירג’וג למאסטר.
רק השנה באמת ובתמים גיליתי את כל הכוח שיש לגיט להציע. פתאום הבנתי שכשעובדים איתו באופן מסודר, הוא יכול להיות הרבה יותר מ”סתם” כלי שמחזיק את תמונת המצב הנוכחית של הקוד בחברה. כשעובדים עם גיט באופן מסודר, הלוג שלו באמת ובתמים הופך להיות הסיפור של המוצר שלכם.

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

אז למה לי להשקיע בקומיטים בגיט שלי?

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

מנקודת המבט שלי, יש שתי נקודות משמעותיות שנותנות לי מוטיבציה להשקיע בקומיטים יפים ומסודרים –

כדי לעזור למתכנתות אחרות להבין את הקוד שלי

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

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

https://xkcd.com/1296/

כשהקומיטים ב-PR שלנו נראים כך –

18d7fa7 Add trimming service to main flow
47ad0f3 Add trimming service unitests
04a040c Update integration tests to include new service
0d94faa Rename formattedMessages var to events at main flow
61d5408 Add trimming feature toggle

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

כדי לעזור למתכנתות העתיד להבין את הקוד שלי

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

אתן מסתכלות בהערת הקומיט שבו התווסף הקוד, ורואות שכתוב שם “bug fix”.

אתן מתפטרות.

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

בשביל המודולריות

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

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

  • הטסטים יעברו בהצלחה בעבור כל קומיט בנפרד (לא יהיו טסטים שישברו בקומיט אחד ויתוקנו בקומיט הבא)
  • אנחנו נוכל בקלות להסיר לוגיקה ספציפית בלי לחשוש שאנחנו פוגעות בשאר הפיצ’ר – לדוגמא, אם הוספנו flag לפיצ’ר שלנו שפותח/סוגר אותו ללקוחות, בדרך כלל מדובר בתוספת זמנית שנרצה להסיר לאחר מספר שבועות. יהיה הרבה יותר קל להסיר את ה-flag אם כל מה שנצטרך לעשות הוא revert לקומיט ספציפי, ולא לדחוף קוד חדש.
  • צ’רי פיקינג, ריבייסים רגילים, וכמעט כל תהליך גיט אחר שאנחנו עושות יהיה הרבה יותר קל, כי כל תיקון או שינוי שנצטרך לעשות יקרה רק פעם אחת – על הקומיט הרלוונטי שלו.

השתכנעתי, מה עכשיו?

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

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

שימו לב שאחרי ששינינו קומיטים מקומיים בעזרת ריבייס נצטרך להשתמש ב—force-with-lease כשאנחנו דוחפות את השינויים שלנו כדי לדרוך על השינויים שכבר קיימים ב-remote.
כיוון ש-force push היא פעולה די דראסטית צריך להתייחס אליה ברצינות הראוייה.

לפני שאנחנו ממשיכות, נקודה קריטית אחת – אף פעם אל תשנו קומיטים ציבוריים במאסטר!
כל השינויים שאני מראה בהמשך רלוונטיים אך ורק לעבודה בבראנצ’ שלכן, ורק לקומיטים שאתן הוספתן לבראנצ’ – אם תשנו קומיטים ציבוריים אתן תהרסו עבודה של אחרות שמתבססות על ההיסטוריה המשותפת.

ריבייס אינטרקטיבי

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

הפקודה להתחלת תהליך של ריבייס אינטרקטיבי – 

git rebase -i HEAD~x

המספר x הוא מספר הקומיטים אחורה שאת רוצה לטפל בהם.

ברגע שאת מתחילה את הריבייס, נפתח לך מסך שנראה כך –

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

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

כברירת מחדל, העורך של גיט נפתח ב-vim. אם אתן לא מכירות/ שולטות בתוכנה, אתן יכולות לשנות את את ברירה המחדל, או לחילופין להשקיע זמן קצר בללמוד אותה. אני ממליצה על האפשרות השנייה, כי מדובר בעורך שימושי למדי למתכנתות 1

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

Fixup

אני מתחילה מ-Fixup שהיא הפקודה השימושית ביותר בתהליך העבודה היומיומי שלי.

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

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

כדי לאחד את הקומיט של תיקון הבאג עם הקומיט הראשון אני עושה שני שינויים –
1. אני מזיזה את הקומיט של תיקון הבאג להיות אחרי הקומיט הראשון 2
2. במקום pick אני כותבת f (קיצור ל- fixup).
כשהריבייס ירוץ הוא יאחד ביניהם, והתוצאה תהיה הלוגיקה המתוקנת בקומיט יחיד.

לפני הריבייס
אחרי הריבייס

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

Squash

הפקודה squash אומרת לגיט לאחד את קומיט “התיקון” עם הקומיט שלפניו ולאחד את ההודעות שלהן.

זה מצחיק, המדריך של גיט טוען ש-Fixup היא כמו Squash, אבל לדעתי דווקא Squash היא האחות הפחות שימושית של Fixup. ההבדל בין הפקודות הוא שבעוד ש-Fixup זורקת את ההודעה של הקומיט המאוחר יותר, Squash מאחדת בין ההודעות. 

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

Edit

הפקודה edit אומרת לגיט לעצור את הריבייס ולאפשר לנו לשנות קומיט ספציפי.

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

פקודת edit עובדת ככה – אתן מחליפות את ה-pick ליד הקומיט הרלוונטי ב-edit, וכשהריבייס ירוץ ויגיע לקומיט שלכן הוא יעצור אחריו. כלומר, הריבייס יחזיר אתכן לנקודה בזמן מיד אחרי שעשיתן את הקומיט הבעייתי.

עכשיו, אתן יכולות להשתמש ב-amend בשביל לתקן את הבעיה כך – 

  1. תקנו את שגיאת הכתיב (או כל תיקון אחר שאתן רוצות לעשות).
  2. עשו staging לתיקון שלכן בעזרת git add.
  3. אחדו את השינויים שלכן לקומיט האחרון בעזרת git commit –amend.

כשסיימתן עם התיקונים אתן יכולות לתת לריבייס לרוץ עד סופו על ידי שימוש ב-

git rebase --continue

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

כמו מקודם, כדי לפצל קומיט, החליפו את pick ליד הקומיט ב-e (קיצור ל-edit), וכשהריבייס ירוץ ויגיע לקומיט שלכן הוא יעצור אחריו. עכשיו כדי “לשחרר” מהקומיט את כל השינויים, השתמשו ב-

git reset HEAD^

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

git rebase --continue
לפני הריבייס
כל השינויים הנוכחיים אחרי שהזזתי את ה-HEAD
הקומיטים החדשים אחרי הפיצול

Reword

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

זו פקודה הרבה יותר פשוטה מהקודמות אבל היא עדיין מאוד שימושית. אם תוך כדי עבודה (או אחרי code review) הבנתן שההודעה המקורית שלכן לא מספיק טובה, או שאתן רוצות להוסיף לה עוד פרטים שיסבירו את השינוי שעשיתן בקומיט, זו הפקודה להשתמש בה. החליפו את ה-pick ליד הקומיט הרלוונטי ב-r (קיצור ל-reword), וכשהריבייס ירוץ הוא יעצור כדי לבקש מכן הודעה חדשה.

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

התוצאה היא קומיט חדש עם ההודעה החדשה שיצרנו.


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


הערות:

Featured photo by Daniel Cheung on Unsplash

  1. יש לא מעט אתרים ומשחקים שמטרתן ללמד שליטה ב-vim , דוגמא נחמדה אחת נמצאת כאן. באופן אישי אני פחות אוהבת את הסגנון, ומעדיפה להשתמש ב-cheat sheets.
  2. שימו לב שאני משנה כאן את הסדר של הקומיטים – עוד פעולה שריבייס אינטרקטיבי מאפשר

17 תגובות בנושא “הקומיט האולטימאטיבי עם ריבייס אינטראקטיבי

  1. בקשר ל-fixup vs. squash – עד היום תמיד השתמשתי ב-squash ואז ערכתי את ההודעה המאוחדת, בד”כ פשוט על ידי מחיקה של ההודעות החדשות יותר D-: . אני מניח שאני צריך להשתמש יותר ב-fixup, אז תודה על ההפניה.

    אגב, משהו שכדאי לשקול אם לעשות, כל פעם שסיימנו חתיכת עבודה (ענף של תכונה אחת או תיקון באג) זה פשוט לעשות squash להכל ולכתוב הודעה חדשה שמסבירה את כל השינויים: אם העבודה לא גדולה, אז לראות אותה כ-commit אחד עם הסבר גדול יכול להיות יותר שימושי מערימה של commits קטנים שכל אחד מכילה הודעה יותר טכנית.

    1. אכן, תיקוני באג ודברים קטנים כאלה תמיד יהיו בקומיט יחיד – המון קומיטים מיותרים במקרה כזה יקשו על הקריאה לא ישפרו אותה.
      ההפרדה ל3-4 קומיטים זה כבר לשינויים יותר גדולים, אבל שעדיין קטנים מספיק בשביל להיות בראנצ’ יחיד.

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

  3. אחרי rebase המזהה של כל הקומיטים הבאים משתנה גם הוא, בגלל שהמזהה של קומיע תלוי בקומיט שהיה לפניו

  4. GIT outta here! מי חשבה שיש בלוגים כה מפורטים על גיט ! 🙂 מדליק

  5. יפה, אהבתי וזה גרם לי לנסות שוב.
    מקרה אמיתי: Github dependbot מידי פעם יוצר ערימה של PullRequests, ואני אוהב לקחת את כל השינויים שלו לPR אחד ולהכניס, מרגיש לי יותר מסודר.
    הפעם החלטתי גם לעשות להכל rebase כך שזה גם יהיה קומיט אחד בלבד, ועוד יותר יוריד את הרעש.

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

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

    1. זו שאלה ממש מצוינת, ומזכירה לי שכבר מזמן רציתי לכתוב פוסט על stacked pr שהם שיטת עבודה שאני מאוד אוהבת.
      בגדול, קונפליקטים נפתרים קצת אחרת ב-merge ובריבייס, אם כבר אתה רוצה לעשות squash בסוף, למה לא לעשות ריבייס של כל ה-pr-ים לבראנץ אחד במקום למרג’ג’?

      1. אני חושב שלא הצגתי את העניין נכון… לא מדובר פה בstacked PR (עד כמה שאני מבין).
        מדובר פה ב7 PRים, שכולם נמצאים קומיט אחד אחרי הmaster. כלומר, הם מקבילים, לא טוריים…
        אם היה אפשר לשלוח פה תמונה…

        ולכן אני לא רואה איך אני יכול לעשות להם rebase בלי למרגג אותם קודם. אין range שכולל את כולם.

השאר תגובה