Git - חלק 7

בחלק השביעי והאחרון של סדרת Git נכיר את תת המודלים של גיט, שיעזרו לנו לפתור את בעיית שיתוף הקוד בין פרויקטים שונים. בנוסף נעבור על reflog - מה קורה אם אנחנו מוחקים commit או ענף שלם ורוצים להחזיר אח”כ את המידע?

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

 

Submodules

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

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

נחזור לדוגמה של אליס ובוב, נניח והם עובדים על שתי פרויקטים נפרדים, נניח אתרים. אליס על aquarium ובוב על pets, והם רוצים לשתף את תקיות ה css וה js בינם. על מנת לבצע את זה יש ליצור שתי תת מודלים. אנחנו יוצרים אותם כמו שניצור repo רגיל, ולאחר מכן אנחנו מוסיפים אותם לפרויקטים כתת מודלים (או בעצם repo בתוך repo). במצב כזה, אם בוב אליס עובדת על פרויקט ה aquarium ומבצעת שינוי קבצי ה css ו/או ה js, היא יכולה לדחוף אותם חזרה לrepo הראשית כדי שבוב יוכל למשוך אותם ולעדכן את השינויים בקבצים הנמצאים בפרויקט שלו.

לאחר שיצרנו repo (אם זה במחשב המקומי שלנו וסיכרנו אותה עם שרת החברה או אחד השירותים, או שיצרנו אותה ישירות באחד השירותים דוגמת GitHub) אנחנו מוסיפים את ה repo כתת מודל ב repo של הפרויקט שלנו בעזרת הפקודה ‘git submodule add git@example.com:css.git’ (במקום ה git@example.com יש להכניס את כתובת ה repo, ואפשר להחליף את ה css בדוגמה לכל שם אחר של תיקייה). הפקודה תשבט (/תעתיק/תכפיל) את הפרויקט לתוך תיקיית ה css ב repo הנוכחי, ואם נריץ לאחר מכן ‘git status’ נראה שהפקודה יצרה קובץ חדש בשם gitmodules. ותיקייה חדשה בשם css. לאחר מכן כמובן שיש לבצע commit לשינויים (נבצע את זה עם הפקודה ‘“git commit -m “Add CSS submodle’) ולאחר מכן נדחוף אותם לשרת שלנו ( או לכל שירות אחר. נבצע זאת עם הפקודה ‘git push’), וזה כל מה שצריך לעשות בשביל להוסיף submodule לפרויקט שלנו.

אם נפתח את הקובץ gitmodules. שנוצר באופן אוטומטי, נראה שיש לו submodule בשם CSS, לאחר מכן הוא מפרט את הנתיב לתיקייה בה יושב ה submodule (במקרה שלנו css), ולבסוף הוא שומר גם את כתובת המקור, או במילים אחרות ה URL של ה repo שהוספנו כ submodule. ואם נוסיף בהמשך עוד תת מודלים כולם יהיו רשומים בתוך הקובץ הזה באותו פורמט שתיארנו כרגע.

אחרי שבוב הוסיף את ה submodule לפרויקט ה pats שלו, בואו נניח שהוא הוא לעשות בו כמה שינויים ולעדכן אותו. אז נלך לתיקיית ה css שלנו עם הפקודה ‘cd css’ ולאחר מכן נעבור לענף master (עם ‘git checkout master’) מכיוון ש submodules לא מתחילים באף ענף, לאחר מכן אנחנו יכולים לעשות שינויים בקבצים (אם נריץ לאחר מכן ‘git status’ נוכל לראות שהתבצעו שינויים), ונבצע commit (עם הפקודה ‘“git commit -a -m “Update bla’), נדחוף את ה submodule (עם הפקודה ‘git push’), ולבסוף יש לדחוף גם את ה repo של פרויקט האב שלנו, ז”א ה pets.

אם רק נדחוף את ה submodule ולא את ה repo של הפרויקט שלנו, ונכתוב ‘git status’ נראה שההמערכת יודעת שיש בפרויקט commits חדשים. אז אנחנו נוסיף את ה css לאזור ההיערכות (‘git add css’) ונבצע commit (בעזרת הפקודה ‘“git commit -m “Update bla in css’), ולבסוף לדחוף את ה repo (עם הפקודה ‘git push’). הסיבה שבגללה אנחנו צריכים לעדכן גם את פרויקט האב היא שלפרויקט האב יש הפניה ל commit בתת המודל שלנו, ואם אנחנו משנים את תת המודל, אנחנו גם צריכים לשנות את המצביע בפרויקט כדי שיצביע ל commit החדש שנמצא ב HEAD של התת מודל. האיור מצד שמאל ממחיש את סדר הפעולות, תחילה הפרויקט שלנו הצביע ל commit השני (שהיה הראשון לפני שעשינו שינוי בתת המודל) ולאחר שעשינו שינוי בתת המודל נוסף ה commit הראשון, ביטלנו את החץ ל commit שכעת השני ולאחר העדכון אנחנו מצביעים ל commit הראשון שהוא ה HEAD בתת המודל.

כעת, בואו נבחן את הפרספקטיבה של מישהו שמתקין repo חדש שיש לו כמה submodules בתוכו. אז נניח שזואי רוצה לשכפל את פרויקט ה pet למחשב שלה. זואי תכתוב ‘git clone git@example.com:pets.git’ ותקבל את ה repo עם קבצי הקוד, עם קובץ ה gitmodules. (עם ההפניה לתת המודלים), אך התיקיות css ו-js יהיו ריקות. התיקיות של התת מודלים יהיו ריקות מכיוון שראשית יש לאתחל אותם וכדי לעשות את זה היא תכתוב ‘git submodule init’ ואז ‘git submodule update’ הפקודות יגרמו למערכת (git) לגשת לקובץ gitmodules. לקרוא אותו ולגשת לאינטרנט כדי לשבט את תת המודלים לפרויקט שלנו. ועכשיו יהיו קבצים בתוך תיקיות ה-css וה-js.

אז בואו נניח שאחרי שזואי ביצעה את כל זה היא עשתה כמה שינויים לתת המודלים ודחפה אותם חזרה (עדכנה את התת מודלים). בפעם הבאה שבוב יבצע ‘git pull’ בפרויקט שלו הוא יבחין שתת המודל css עודכן (כמו שניתן לראות בתמונה משמאל), וגם אם יריץ ‘git status’ נראה כי ישנו commit חדש (ז”א בוצעו שינויים). אם בוב יריץ ‘git diff’ הוא יראה את השינויים שבוצעו בפרויקט האב (ה repo של התת מודל).

כל אלו הם בעצם סימנים לכך שצריך לעדכן את תת המודל, ונבצע זאת עם הפקודה ‘git submodule update’. הפקודה תגרום למערכת לעבור על כל התת המודלים שבפרויקט ולבדוק האם יש commits חדשים ותמשוך אותם ל repo המקומי שלנו במידה ויש. אם לאחר מכן בוב יריץ שוב את הפקודה ‘git status’ הוא יראה שהפרויקט שלו מעודכן.

נניח שוב שזואי צריכה לבצע כמה שינויים בתת המודל של css. היא מקלידה ‘cd css’ ולאחר מכן ‘git status’ ורואה את הקבצים שביצעה בהם שינויים, מוסיפה אותם לאזור ההיערכות (‘git add bla.css’) ואז מבצעת commit (נניח שעדכנו את הפונט ‘“.git commit -m “Update menu font’). אבל יש דבר אחד שהיא שכחה, זוכרים? לעבור לענף כלשהו. חיפוש קצר באינטרנט יגלה שרוב מי שעובד עם תת מודלים נתקל בבעיה לפחות פעם אחת במהלך העבודה, אז איך מתמודדים עם זה?

מה שבעצם יש לנו זה commit שלא נמצא באף ענף. אם ננסה לדחוף (‘git push’) שום דבר לא ידחף בגלל שה commit לא על ענף. כל פעם שאנחנו מריצים ‘git submodule update’ הקוד נמשך מחדש במצב ללא HEAD, ז”א שהוא לא מצביע לאף ענף ספציפי. מה שצריך לעשות זה לקשר את ה commit שעשינו לענף כלשהו. אז דבר ראשון נעבור לענף (‘git checkout master’) ובשלב הזה המערכת תעדכן אתכם (בטקסט המתקבל בשורת הפקודה) עם הודעת אזהרה שיש commit שאנחנו משאירים מאחור, ז”א שהוא לא מקושר לאף ענף, ואף תפרט את SHA והתיאור של ה commit, ואפילו תתן לנו הצעה לפקודה שתיצור ענף חדש שיכלול את ה commit הזה. אך מה שאנחנו רוצים לעשות זה לקשר אותו ל master, לכן נכתוב ‘git merge b6bb78f’ (כמובן המקום b6bb78f כתבו את ה SHA של ה commit הרלוונטי), ואם נריץ כעת ‘git log’ נראה שכעת ה commit חלק מההיסטוריה בענף שלנו (וכעת זואי תוכל לדחוף את תת המודל (‘git push’)).

אך זכרו, זה לא הדבר היחידי שצריך לעשות. אם נחזור לתיקיית האב, זאת של הפרויקט (‘.. cd’) ונכתוב ‘git status’ נראה שיש commits חדשים בענף master בתיקיית css. אז נוסיף את התיקייה לאזור ההיערכות (‘git add css’) ונבצע commit ל repo של הפרויקט (‘“git commit -m “Update menu font in css’), ולבסוף נדחוף את הפרויקט (‘git push’). זכרו תמיד, כשמבצעים שינויים בתת מודלים צריך לדחוף שתי repo, ה repo של תת המודל וה repo של הפרויקט עצמו. אם לדוגמה זואי הייתה שוכחת לדחוף את ה repo של תת המודל והייתה דוחפת אך ורק את ה repo של הפרויקט עצמו, היה נוצר התייחסות ל commit חדש, ובוב היה רואה את ההתייחסות ומנסה לבצע עדכון לתת המודל (‘git submodule update’) אך הוא היה מקבל שגיאה.

אם אתם דואגים לכך שתשכחו לדחוף את תת המודל, יש אפשרות לבטל את הדחיפה עד שלא דחפתם את תת המודל. נבצע זאת עם הפקודה ‘git push –recurse-submodules=check’. ואפילו יש אפשרות לדחוף את שתיהם במקביל, עם הפקודה ‘git push –recurse-submodules=on-demand’. בכל פעם שתכתבו את הפרודה כדי לדחוף את הפרויקט שלכם המערכת תבדוק האם ישנם commits של תת המודלים בפרויקט שלכם שלא נדחפו, ובמידה ויש כאלו היא דחוף אותם באופן אוטומטי.

בשביל תזכורת, זוכרים כיצד אפשר לקצר את הפקודה? (כדי שלא נצטרך לכתוב את הפקודה הארוכה הזאת כל פעם מחדש). עם alias. נכתוב את הפקודה ‘“git config alias.pushAll “push –recurse-submodules=on-demand’ וכעת פשוט כל פעם שנרצה לכתוב את הפקודה נכתוב ‘git pushAll’.

שאלות ותשובות

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

שאלות: אתם בתהליך של בניית 2 פרויקטים במקביל. נניח ומדובר על שתי אתרים שיש להם גלרייה ויחלקו את קוד הג’אווה סקריפט וה css של הגלריה הזו. ייבאו את המודל מהפרויקט של בוב (שנמצא בכתובת “git@pet.com:gallery_js.git”). כעת, כדי ששאר חברי הצוות שעובדים על הפרויקט יוכלו לשתף פעולה, הוסיפו את תת המודל לאזור ההיערכות ובצעו לו commit (בפקודה אחת). אתם עוזרים לחבר להקים את ה repo של הפרויקט אצלו. כבר שיבטתם את הפרויקט, עכשיו אתחלו את תת המודל, ואח”כ עדכנו אותו. ביצעתם כמה commits לתת המודל gallery_css. כשבאתם לדחוף את תת המודל, הבנתם שלא הייתם על אף ענף. צרו ענף חדש בשם temp_changes עם ה commit (נניח שה SHA שלו הוא a7eded4). עכשיו מזגו את הענף temp_changes עם ענף ה master (הניחו כי אתם כבר על master). עכשיו כשהשינויים ב master, דחפו את הענף ל remote כדי לחלוק אותו עם חברי הצוות שעובדים על הפרויקט. בגלל שאתם משתמשים ב submodule וודאו כי אתם משתמשים באופציה שתבטל את הדחיפה במידה וישנו commit שלא דחפתם ב submodule. צריך לדחוף שוב את השינויים ב submoudle. אך הפעם במקום להיכנס לתוך תת המודל ולדחוף אותו ואז את הפרויקט, פשוט דחפו את הפרויקט ואת תת המודל (במידה וישנם commits שלא נדחפו בתת המודל) בפקודה אחת.

תשובות: הפקודה: ‘git submodule add git@pet.com:gallery_js.git’. הפקודה: ‘“git commit -a -m “commit submodule’. תחילה הפקודה: ‘git submodule init’. ולאחר מכן הפקודה: ‘git submodule update’. הפקודה: ‘git branch temp_changes a7eded4’. הפקודה: ‘git merge temp_changes’. הפקודה: ‘git push –recurse-submodules=check’. הפקודה: ‘git push –recurse-submodules=on-demand’.

סיכום ביניים

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

Reflog

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

האמת היא ש git לא מוחקת commit, אף פעם. אוקי, מזל, אז ה commit עם כל המידע קיים, אבל איך מקשרים אותו לענף בחזרה? כמובן שלבדוק את ה log לא יעזור בגלל שהוא לא מקשור לענף, אבל git שומרת עוד log, שנמצא רק באופן מקומי ב repo, ונקרא reflog. אז אם נכתוב ‘git reflog’ נראה משהו שנראה כמו התמונה משמאל.

כשנסתכל ברשימת הפלט נראה שהמערכת (git) יודעת מתי קראנו לפקודה reset ועל איזה commit. שימו לב שכל פעם שה HEAD שלנו זז (אם זה בגלל שיש לנו commit חדש, או שהחלפנו ענפים, או שעשינו ריסט) רשומה חדשה נוספת ל reflog שלנו. בגלל זה יש לנו רשומה עבור כל commit בנפרד, ובנוסף, רשומה עבור פעולת ה reset. ה reflog מראה לנו בתחילת השורה את ה SHA של כל commit, לאחר מכן שם מקוצר שנוכל להשתמש בו רק ב reflog, ולבסוף מה גרם לשורה לזוז (הפעולה + תיאור במילים). (ה {HEAD@{0 זה ה commit הנוכחי בה אתם נצאים).

אז ברשומה השניה ({HEAD@{1) נמצא ה commit שאנחנו רוצים חזרה (זה שלא מחובר לאף ענף). כדי להחזיר אותו חזרה נכתוב ‘git reset –hard 1e62107’ (או כמובן להשתמש בשם קיצור כך ‘{git reset –hard HEAD@{1’). עכשיו אם נריץ ‘git log’ נראה שה commit חזר ללוג, בראש הרשימה (ה HEAD מצביע עליו).

  • זכרו שה reflog נמצא באופן מקומי ב repo שלכם ושומר את ההיסטוריה של רק מה שקורה ב repo המקומי שלכם.

עד כאן ל commitים בודדים, אך מה עם ענפים? נניח שבוב עובד על ענף חדש בשם bla, אבל הוחלט שבסופו של דבר אין בו צורך, אז היא החליטה למחוק אותו (‘git branch -d bla’). והמערכת תיתן לבוב התראה שיש commit בענף, והוא לא מוזג. אבל הוא מודע לעניין, ומעוניין למחוק אותו לגמרי (‘git branch -D bla’). וגם הפעם בוב נזכר רגע אחרי שבעצם הוא היה צריך את הענף והוא צריך אותו בחזרה!

כמו שראינו git לא מוחק commits הוא רק מוחק את הענף, אך ה commits עדיין קיימים. אז אם נמצא את ה commit האחרון פשוט נוכל ליצור ענף חדש שיצביע אל ה commit הזה. בשביל למצוא את ה commit האחרון נחזור אל ה reflog. אך הפעם, בשביל לקבל יותר מידע, נכתוב ‘git log –walk-reflog’ ונקבל כפלט משהו שנראה כמו בתמונה משמאל. נאתר את ה commit שאנחנו רוצים להחזיר (נניח במקרה שלנו שזה השני, {HEAD@{1 עם התיאור Add lol).

כעת ניצור ענף חדש עם קישור ל commit שבחרנו, כך: ‘{git branch bla HEAD@{1’. ועכשיו כדי לראות את זה ניתן נעבור לענף החדש (‘git checkout bla’) ולהריץ עליו ‘git log –oneline’.

שאלות ותשובות

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

שאלות: החלטתם שהפרויקט שלכם זקוק לפיצ’ר כלשהו, בניתם אותו וביצעתם לו commit. אך יש בעיה, השינוי הזה גרם לבאג שגרם לכל הפרויקט לא לעבוד כשורה, נכנסתם ללחץ מהר, בצעו hard reset ל commit שלפני הפיצ’ר (ל commit עם SHA מס’ ab27026). האמת היא שה hard reset היה קצת מוגזם. אתם עדיין רוצים את הפיצ’ר. בואו נסתכל על ה reflog. אנחנו רואים שיש רשומה של ה reset, מצוין! בצעו hard reset ל commit כדי להחזיר אותו (לחצו כאן לתמונה המכילה את הרשימה של ה reflog). החלטתם לבצע ניקוי בית קטן, ואתם ידועים שכל הענפים מוזגו או ננטשו, אז אתם עומדים למחוק אותם כדי לשמור על repo קטן ונקי. התחילו בלמחוק את הענף “lol”. בעצם, הייתם צריכים איזה קובץ קטן ממהפיצ’ר של הענף lol. כתבו פקודה שתוציא כפלט רשימה של כל ה reflog עם פירוט נרחב על כל רשומה. עכשיו כשאתם יודעים מה היה ה commit האחרון בענף שמחקתם, אתם יכולים ליצור ענף חדש ולקשר את ה commit אליו (לענף החדש תקראו newLol, קשרו ל commit בשם {HEAD{1).

תשובות: הפקודה: ‘git reset –hard ab27026’. הפקודה: ‘git reflog’. הפקודה: ‘git reset –hard 8791492’. הפקודה: ‘git branch -D lol’. הפקודה: ‘git log –walk-reflog’. הפקודה: ‘{git branch newLol HEAD{1’.

סיכום

אז זהו, זה הסיכום האחרון. כמובן שיש עוד הרבה לאן להרחיב ב git אך אני חושב שזה הספיק לי (לפחות כרגע). במהלך הלימוד מצאתי כמה מקורות מעניינים שיכולים לעזור לכם: Git Cheatsheet, Git Immersion, Git Turorials (הסברים שימושיים על ה Workflow).

מקווה שמצאתם את הסדרה מועילה, ושהצלחתם לעקוב וללמוד איתי ביחד.