✕ סגור 
צור קשר
תודה על ההתעניינות .

Thank you! Your submission has been received!

Oops! Something went wrong while submitting the form

דברים שרציתם לדעת על גיט אבל חששתם לשאול

ליאור בר-און
|
קלה
|
Jul 4, 2018
alt="facebook"alt="linkedin"
להרשמה לניוזלטר

רקע:

git (או בעברית: גיט) היא מערכת ניהול גרסאות שתפסה פופולריות עצומה בשנים האחרונות - כל עותק מקומי של המאגר מכיל את כל המידע (כל ההיסטוריה). המידע הזה נשמר בדרך כלל בתת-תיקיה בשם ".git". גיט שומרת אובייקטים מסוג מידע (blob, בדרך כלל קובץ), עץ (tree, מבנה ספריות) וגרסה (commit) (ויקיפדיה).

למה זה חשוב?

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

מעשית, לא ניתן לעבוד בלעדיו: כתבת קוד? אתה רוצה לקרוא קוד? עליך להשתמש בגיט.

מתוך הצורך הבסיסי לשימוש בגיט, קיימת רמת מיומנות בסיסית הנפוצה בקרב אנשי תוכנה - נקרא לה "רמה 2".

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

• נושאים של ה- internals: כיצד גיט ממומש, וכיצד הוא עובד מאחורי הקלעים.

• כל מיני מונחים מוכרים אבל לא מובנים: Fast-Forward, ReReRe, Rebase, או Bisect - היא רשימה מייצגת שאני נתקלתי בה, של מונחים שאנשים רבים שמעו' אבל לא מבינים באמת מה הם אומרים.

האם רמה 2 היא "מספיק טובה" בכדי לעבוד עם גיט?

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

Rebase

rebase הוא דרך חלופית ל- merge על מנת למזג עבודה בין branches.

יש הנחה ששמעתי הגורסת שמשתמשי SVN לשעבר, הרגילים לעבור על branch יחיד (להלן "trunk")  נוטים להשתמש ב- rebase כי הוא מזכיר את הכלי הקודם שלהם.

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

אותו קוד, אותם שינויים - שיטת מיזוגים שונה (בד"כ ה- rebase יהיה ארוך יותר)

Merge - מציג את המצב המסובך כפי שהוא.

Rebase - יוצר מציאות קלה יותר למעקב - אך יש מחירים לייצר אותה.

נניח שיש לנו branch עם 6 commits שאנחנו רוצים למזג ל- master.

כאשר אנו משתמשים ב- merge זו פעולה אחת עם סיכוי מסוים לקונפליקטים. הקונפליקטים נוצרים ע"פ המצב הסופי של ה- branch שלי מול המצב הסופי של ה master.

rebase הוא מורכב יותר: הוא ייקח את ה- branch שלי, commit אחר commit, ויטמיע אותם בראש ה- master branch כאילו רק עכשיו כתבתי אותם. אחד אחרי השני.

הסיכוי לקונפליקטים שצריך לפתור גדל משמעותית: ייתכן שכתבתי קוד ב- commit מס'3 שיצור קונפליקט, אבל הסרתי את הקוד הזה ב- commit מס'5 (המאוחר יותר ב branch).

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

ב- rebase אני צריך לפתור אותו. אם ב- commit מס'4 היה שינוי נוסף בקוד ה זה, ייתכן וייווצר קונפליקט נוסף שיהיה עלי לפתור.

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

מתי זה משתלם?

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

Rebase נפוץ בשימוש יחסית, בפרויקטי Open Source מרובי תורמים זרים - כלומר, תורמים שלא מכירים זה את זה ולכן התקשורת ביניהם היא פחות יעילה. במקרים האלו היסטוריה נקיה יכולה לחסוך הרבה בעיות תקשורת, ולהצדיק את המחיר הנוסף בביצוע rebase.

הערה: בעוד שאת פקודת git merge מפעילים מתוך branch היעד (אליו רוצים למזג), את פקודת git rebase מפעילים מתוך branch המקור, כלומר ה- branch אשר את תוכנו רוצים למזג/"להרכיב" על branch אחר.

Fast Forward

אין סיכוי שלא נתקלתם במונח Fast Forward (להלן FF) בעבודה עם גיט.

"טוב, קרה פה משהו מהיר. נראה שאין בעיות. יופי נמשיך הלאה!" - היא התגובה המקובלת להודעה של גיט שבוצע FF.

בואו נבין טיפה יותר:

בעצם FF הוא אופטימיזציה של גיט על מנת לפשט את ההיסטוריה.

כאשר יש לי branch (למשל: feature branch) שאני רוצה למזג (למשל ל- master) אבל ה- master לא השתנה מאז שהתפצלתי ל- feature branch אפשר לפשט את הדברים.

merge בשלב כזה ל- master הוא כאילו הוספתי את ה- commits שלי ,לא ל- feature branch אלא ישירות ל- master.

התוצאה הרי הייתה זהה.

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

על מנת לבצע FF כל מה שגיט צריך הוא להפנות את המצביע (branch) בשם "master" להצביע לנקודה של המצביע "feature branch".

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

Git Revert

את הפקודה הזו כדאי להכיר כי היא מאוד שימושית ברגעים מסוימים. אני לא בטוח כמה אנשים מודעים אליה. הרבה פעמים אנשים עם ניסיון בכלי version control אחרים נוטים להתבלבל ולחשוב ש- git revert עושה מה שבעצם git reset עושה.

הפקודה git revert HEAD~2 (אנו מכוונים ל commit X, הרי הוא Head פחות 2 צעדים אחורה) - מנסה ליצור commit חדש המסומן כ- X אשר מהווה את ההופכי של X ומבטל את כל הפעולות שנעשו.

לאחר ש- commit X ייווצר, כל התוספות של y ו- z עדיין יהיו תקפות, לא ביטלנו אותן.

מתי הדבר שימושי? למשל כאשר יש בעיה בסביבת production או staging שאנו רוצים לפתור מהר, ואנו יודעים איזה commit אחראי לה. ניתן אח"כ לעשות revert ל- X ולקבל בחזרה את השינויים של X למערכת.

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

ניתן לקרוא ל- git revert -abort (ממש כמו merge) אם הסתבכתם בהתרה שלהם.

revert ניתן גם לעשות מתוך ה- UI של github ולפתוח ממנו מיד Pull Request - בצורה נוחה מאוד.

‍מקור: http://bit.ly/ninja-git

Git ReReRe (ידוע גם כ Re3)

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

השם הלא-שגרתי הוא קיצור ל- Reuse Recorded Resolution או "שימוש-חוזר בפתרונות מוקלטים". שימוש נפוץ בפקודה היא בסיטואציות בהן לא עובדים ב- Continuous Delivery אלא ב- long feature branches. למשל: אני עובד על פיצ'ר במשך שבועיים-שלושה, ורוצה כל יום למשוך שינויים מה- master. לרוע המזל אני נתקל יום אחרי יום באותו ה- merge conflict כי אני עובד על קטע קוד שעובר שוב ושוב שינויים גם על גבי ה- master.

תסריט נפוץ אחר הוא כאשר עובדים עם rebase, ואז ישנם הרבה קונפליקטים דומים. למשל: אני עושה rebase ל- branch עם 10 commits המכיל 4-5 קונפליקטים דומים על אותו האזור בדיוק. אני רוצה שגיט ילמד איך פתרתי את הקונפליקט הפעם הראשונה - ו"יסתדר לבד" בפעמים הבאות.

ReReRe היא גם פקודה וגם קונפיגורציה. אנו אומרים לגיט להקליט את המיזוגים שאנו עושים (בעקבות rebase, merge, cherry-pick וכדומה), בכדי שישמש בהם כ- reference ל- conflict resolution אוטומטי בעתיד.

הפעלת הקונפיגורציה הבסיסית נראית כך:

git config --global rerere.enabled 1

בכל פעולה בה יש מיזוג של קוד (merge, rebase, ועוד) והיה קונפליקט שנפתר על ידי ידנית - גיט ישמור "fingerprint".

ה- fingerprint הוא ספציפי לקובץ מסוים, ומכיל את הקונפליקט: מה היה לפני, בשני ה- branches שביניהם יש קונפליקט - ואיך נראה הקוד לאחר שפתרתי את הקונפליקט.

‍מקור: https://readyspace.com.hk/rerere

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

מצד אחד יש כאן משהו מאוד נוח שיכול לחסוך לי זמן יקר של טיפול בקונפליקטים. מצד שני יש כאן סיכון מסוים:

• גיט יבצע טעות, ויבצע merge שמתאים ל- pattern שהוא זיהה - איך אינו נכון.

• אני ביצעתי טעות ולימדתי את גיט/ReReRe פתרון שגוי - שהוא יחזור עליו עוד מספר פעמים עד שאזהה את הטעות.

האם גיט ReReRe שווה את הסיכון? התשובה כנראה מאוד אינדיבידואלית.

ReReRe מכיל גם מנגנוני תיקון, כמו הפקודה git rerere forget path_spec  המאפשרית לי לתקן למידה לא-טובה, אם זה גם אומר שעלי להשקיע עבודה נוספת או אם המנגנון לא פועל בצורה אוטומטית לגמרי.

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

מאת: ליאור בר און, ארכיטקט ראשי (Chief Architect) בחברת Gett

http://www.softwarearchiblog.com

רקע:

git (או בעברית: גיט) היא מערכת ניהול גרסאות שתפסה פופולריות עצומה בשנים האחרונות - כל עותק מקומי של המאגר מכיל את כל המידע (כל ההיסטוריה). המידע הזה נשמר בדרך כלל בתת-תיקיה בשם ".git". גיט שומרת אובייקטים מסוג מידע (blob, בדרך כלל קובץ), עץ (tree, מבנה ספריות) וגרסה (commit) (ויקיפדיה).

למה זה חשוב?

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

מעשית, לא ניתן לעבוד בלעדיו: כתבת קוד? אתה רוצה לקרוא קוד? עליך להשתמש בגיט.

מתוך הצורך הבסיסי לשימוש בגיט, קיימת רמת מיומנות בסיסית הנפוצה בקרב אנשי תוכנה - נקרא לה "רמה 2".

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

• נושאים של ה- internals: כיצד גיט ממומש, וכיצד הוא עובד מאחורי הקלעים.

• כל מיני מונחים מוכרים אבל לא מובנים: Fast-Forward, ReReRe, Rebase, או Bisect - היא רשימה מייצגת שאני נתקלתי בה, של מונחים שאנשים רבים שמעו' אבל לא מבינים באמת מה הם אומרים.

האם רמה 2 היא "מספיק טובה" בכדי לעבוד עם גיט?

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

Rebase

rebase הוא דרך חלופית ל- merge על מנת למזג עבודה בין branches.

יש הנחה ששמעתי הגורסת שמשתמשי SVN לשעבר, הרגילים לעבור על branch יחיד (להלן "trunk")  נוטים להשתמש ב- rebase כי הוא מזכיר את הכלי הקודם שלהם.

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

אותו קוד, אותם שינויים - שיטת מיזוגים שונה (בד"כ ה- rebase יהיה ארוך יותר)

Merge - מציג את המצב המסובך כפי שהוא.

Rebase - יוצר מציאות קלה יותר למעקב - אך יש מחירים לייצר אותה.

נניח שיש לנו branch עם 6 commits שאנחנו רוצים למזג ל- master.

כאשר אנו משתמשים ב- merge זו פעולה אחת עם סיכוי מסוים לקונפליקטים. הקונפליקטים נוצרים ע"פ המצב הסופי של ה- branch שלי מול המצב הסופי של ה master.

rebase הוא מורכב יותר: הוא ייקח את ה- branch שלי, commit אחר commit, ויטמיע אותם בראש ה- master branch כאילו רק עכשיו כתבתי אותם. אחד אחרי השני.

הסיכוי לקונפליקטים שצריך לפתור גדל משמעותית: ייתכן שכתבתי קוד ב- commit מס'3 שיצור קונפליקט, אבל הסרתי את הקוד הזה ב- commit מס'5 (המאוחר יותר ב branch).

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

ב- rebase אני צריך לפתור אותו. אם ב- commit מס'4 היה שינוי נוסף בקוד ה זה, ייתכן וייווצר קונפליקט נוסף שיהיה עלי לפתור.

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

מתי זה משתלם?

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

Rebase נפוץ בשימוש יחסית, בפרויקטי Open Source מרובי תורמים זרים - כלומר, תורמים שלא מכירים זה את זה ולכן התקשורת ביניהם היא פחות יעילה. במקרים האלו היסטוריה נקיה יכולה לחסוך הרבה בעיות תקשורת, ולהצדיק את המחיר הנוסף בביצוע rebase.

הערה: בעוד שאת פקודת git merge מפעילים מתוך branch היעד (אליו רוצים למזג), את פקודת git rebase מפעילים מתוך branch המקור, כלומר ה- branch אשר את תוכנו רוצים למזג/"להרכיב" על branch אחר.

Fast Forward

אין סיכוי שלא נתקלתם במונח Fast Forward (להלן FF) בעבודה עם גיט.

"טוב, קרה פה משהו מהיר. נראה שאין בעיות. יופי נמשיך הלאה!" - היא התגובה המקובלת להודעה של גיט שבוצע FF.

בואו נבין טיפה יותר:

בעצם FF הוא אופטימיזציה של גיט על מנת לפשט את ההיסטוריה.

כאשר יש לי branch (למשל: feature branch) שאני רוצה למזג (למשל ל- master) אבל ה- master לא השתנה מאז שהתפצלתי ל- feature branch אפשר לפשט את הדברים.

merge בשלב כזה ל- master הוא כאילו הוספתי את ה- commits שלי ,לא ל- feature branch אלא ישירות ל- master.

התוצאה הרי הייתה זהה.

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

על מנת לבצע FF כל מה שגיט צריך הוא להפנות את המצביע (branch) בשם "master" להצביע לנקודה של המצביע "feature branch".

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

Git Revert

את הפקודה הזו כדאי להכיר כי היא מאוד שימושית ברגעים מסוימים. אני לא בטוח כמה אנשים מודעים אליה. הרבה פעמים אנשים עם ניסיון בכלי version control אחרים נוטים להתבלבל ולחשוב ש- git revert עושה מה שבעצם git reset עושה.

הפקודה git revert HEAD~2 (אנו מכוונים ל commit X, הרי הוא Head פחות 2 צעדים אחורה) - מנסה ליצור commit חדש המסומן כ- X אשר מהווה את ההופכי של X ומבטל את כל הפעולות שנעשו.

לאחר ש- commit X ייווצר, כל התוספות של y ו- z עדיין יהיו תקפות, לא ביטלנו אותן.

מתי הדבר שימושי? למשל כאשר יש בעיה בסביבת production או staging שאנו רוצים לפתור מהר, ואנו יודעים איזה commit אחראי לה. ניתן אח"כ לעשות revert ל- X ולקבל בחזרה את השינויים של X למערכת.

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

ניתן לקרוא ל- git revert -abort (ממש כמו merge) אם הסתבכתם בהתרה שלהם.

revert ניתן גם לעשות מתוך ה- UI של github ולפתוח ממנו מיד Pull Request - בצורה נוחה מאוד.

‍מקור: http://bit.ly/ninja-git

Git ReReRe (ידוע גם כ Re3)

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

השם הלא-שגרתי הוא קיצור ל- Reuse Recorded Resolution או "שימוש-חוזר בפתרונות מוקלטים". שימוש נפוץ בפקודה היא בסיטואציות בהן לא עובדים ב- Continuous Delivery אלא ב- long feature branches. למשל: אני עובד על פיצ'ר במשך שבועיים-שלושה, ורוצה כל יום למשוך שינויים מה- master. לרוע המזל אני נתקל יום אחרי יום באותו ה- merge conflict כי אני עובד על קטע קוד שעובר שוב ושוב שינויים גם על גבי ה- master.

תסריט נפוץ אחר הוא כאשר עובדים עם rebase, ואז ישנם הרבה קונפליקטים דומים. למשל: אני עושה rebase ל- branch עם 10 commits המכיל 4-5 קונפליקטים דומים על אותו האזור בדיוק. אני רוצה שגיט ילמד איך פתרתי את הקונפליקט הפעם הראשונה - ו"יסתדר לבד" בפעמים הבאות.

ReReRe היא גם פקודה וגם קונפיגורציה. אנו אומרים לגיט להקליט את המיזוגים שאנו עושים (בעקבות rebase, merge, cherry-pick וכדומה), בכדי שישמש בהם כ- reference ל- conflict resolution אוטומטי בעתיד.

הפעלת הקונפיגורציה הבסיסית נראית כך:

git config --global rerere.enabled 1

בכל פעולה בה יש מיזוג של קוד (merge, rebase, ועוד) והיה קונפליקט שנפתר על ידי ידנית - גיט ישמור "fingerprint".

ה- fingerprint הוא ספציפי לקובץ מסוים, ומכיל את הקונפליקט: מה היה לפני, בשני ה- branches שביניהם יש קונפליקט - ואיך נראה הקוד לאחר שפתרתי את הקונפליקט.

‍מקור: https://readyspace.com.hk/rerere

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

מצד אחד יש כאן משהו מאוד נוח שיכול לחסוך לי זמן יקר של טיפול בקונפליקטים. מצד שני יש כאן סיכון מסוים:

• גיט יבצע טעות, ויבצע merge שמתאים ל- pattern שהוא זיהה - איך אינו נכון.

• אני ביצעתי טעות ולימדתי את גיט/ReReRe פתרון שגוי - שהוא יחזור עליו עוד מספר פעמים עד שאזהה את הטעות.

האם גיט ReReRe שווה את הסיכון? התשובה כנראה מאוד אינדיבידואלית.

ReReRe מכיל גם מנגנוני תיקון, כמו הפקודה git rerere forget path_spec  המאפשרית לי לתקן למידה לא-טובה, אם זה גם אומר שעלי להשקיע עבודה נוספת או אם המנגנון לא פועל בצורה אוטומטית לגמרי.

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

מאת: ליאור בר און, ארכיטקט ראשי (Chief Architect) בחברת Gett

http://www.softwarearchiblog.com

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
ליאור בר-און
בואו נעבוד ביחד
צרו קשר