כששני סוכנים עובדים על פרויקט בו-זמנית, צריך להפריד בין השינויים שהם עוד לא סיימו. צריך גם לסקור כל פיצ'ר לפני שמשלבים אותו עם העבודה שכבר אושרה. נראה איך worktrees נותנים לסוכנים תיקיות עבודה נפרדות, ואיך ענפים עוקבים אחרי הקומיטים של כל פיצ'ר. אחר כך שני סוכנים יוסיפו למשחק שלנו מצב כהה ומקש התחלה מחדש. נסקור כל פיצ'ר ונמזג אותו לתוך main, קודם ב-fast-forward, ואחר כך עם קונפליקט מיזוג שנצטרך לפתור.
בסרטון קודם, סוכן אחד עבד בתיקייה אחת והוסיף למשחק מקש השהיה. הקומיטים שלו יצרו קו היסטוריה אחד, כי סיימנו משימה אחת לפני שהתחלנו את הבאה.
עכשיו אנחנו רוצים ששני סוכנים יעבדו בו-זמנית. כל סוכן צריך קובצי עבודה משלו, כדי שעריכות שהוא עוד לא סיים לא ישנו את הקבצים שהסוכן השני משתמש בהם. צריך גם לעקוב בנפרד אחרי הקומיטים של כל פיצ'ר. ככה אפשר לסקור פיצ'ר אחד בלי שהעבודה של השני מעורבבת בו.
את העבודה שאנחנו מאשרים אנחנו אוספים בענף שנקרא main. כל פיצ'ר נשאר נפרד עד שנחליט למזג אותו לשם. worktree נותן לסוכן תיקיית עבודה נפרדת, וענף עוקב אחרי רצף של עבודה שנכנסה לקומיטים. מיזוג מביא את העבודה של ענף אחר לתוך הענף שנבחר. קודם נראה למה משימות נפרדות עדיין צריכות קובצי עבודה נפרדים.
נניח ששני הסוכנים עובדים באותה תיקייה, ושניהם קוראים את אותה גרסה של קובץ המשחק. סוכן A מתחיל להוסיף מצב כהה, וסוכן B מתחיל להוסיף התחלה מחדש. סוכן B כותב את קוד ההתחלה מחדש שלו לקובץ המשותף. סוכן A עדיין עובד על העותק שהוא קרא קודם, ובעותק הזה אין את קוד ההתחלה מחדש.
עכשיו סוכן A כותב את העותק שלו בחזרה לקובץ המשותף, עם המצב הכהה. בעותק הזה מעולם לא היה קוד ההתחלה מחדש, אז הקוד הזה נעלם. בדוגמה הזו, הכתיבה המאוחרת מחליפה את התוכן הקודם, בלי להזהיר אף אחד מהסוכנים. סוכן B עדיין חושב שקוד ההתחלה מחדש שלו נמצא בקובץ.
סוכן B ממשיך ומוסיף את המקש שאמור לקרוא לפונקציית ההתחלה מחדש. הפונקציה הזו נעלמה, אז המקש מפנה עכשיו לקוד שלא קיים. בקובץ יש גם שינויים של המצב הכהה שסוכן B לא עשה ולא לקח בחשבון. גם אם נתנו לסוכנים משימות נפרדות, הקבצים עדיין משותפים. כל סוכן צריך תיקיית עבודה משלו.
בסרטון קודם קראנו לקבצים שאנחנו פותחים ועורכים עץ העבודה. git תומך בעצי עבודה נוספים, בתיקיות נפרדות שמחוברות לאותו מאגר. כל תיקיית עבודה נוספת כזו נקראת worktree. ניתן לסוכן של המצב הכהה worktree אחד, ולסוכן של ההתחלה מחדש worktree אחר. כל סוכן עורך את הקבצים בתיקייה שלו, כך שכשהוא כותב את קובץ המשחק שלו, הוא לא מחליף את קובץ המשחק של הסוכן השני. התיקייה המקורית שלנו נשארת בלי שינוי, אז עכשיו יש שלוש תיקיות עבודה. צריך גם דרך לעקוב אחרי העבודה של כל פיצ'ר שנכנסה לקומיטים.
ענף הוא רצף של עבודה בקומיטים, שיש לו שם. git מסמן את הקומיט האחרון בענף בשם של הענף. נקרא לענף אחד dark-mode ולענף אחר restart, כדי לעקוב אחרי העבודה של כל פיצ'ר. את main נשמור לעבודה שאנחנו מאשרים. בהתחלה, שלושת שמות הענפים מצביעים על אותו קומיט, הקומיט של ההשהיה. יצירת הענפים האלה עוד לא הוסיפה אף אחד מהפיצ'רים.
יצירת ענף רק מוסיפה שם שמצביע על קומיט קיים. היא לא יוצרת עוד עותק של ההיסטוריה. לכל worktree יש קובצי עבודה ואזור staging משלו. כל סוכן יכול לערוך את הפיצ'ר שלו ולהכניס אותו ל-staging באופן עצמאי, ועדיין שלושת ה-worktrees חולקים מאגר אחד.
עץ העבודה מכיל את הקבצים שהסוכן קורא, עורך ומריץ. הענף מסמן את הקומיט שהעבודה הזו נבנית עליו. עריכה משנה את עץ העבודה, וקומיט מזיז את שם הענף לקומיט החדש. תנו לכל סוכן ענף ו-worktree משלו. ניצור את שניהם לפני שאחד מהסוכנים מתחיל לערוך את המשחק.
לפני שהסוכן מתחיל לעבוד על המצב הכהה, נבקש ממנו ליצור ענף ו-worktree לפיצ'ר. נגיד לו גם לעבוד בתיקייה החדשה הזו.
הסוכן מריץ "git worktree add", עם -b, כדי ליצור את הענף dark-mode. הנתיב קובע את שם התיקייה החדשה, "game-dark", ליד הפרויקט שלנו. הארגומנט האחרון, main, קובע את קומיט ההתחלה.
הפקודה גם עושה checkout לענף החדש לתוך התיקייה הזו. כלומר, היא שמה שם את הקבצים של הענף, מוכנים לעריכה.
הפלט מאשר שה-worktree החדש מתחיל בקומיט של ההשהיה. הסוכן חייב לעבוד בתיקייה הזו כדי שהעריכות שלו יישארו נפרדות.
אותה בקשה נבקש גם בשביל ההתחלה מחדש. הסוכן מריץ שוב "git worktree add", ויוצר את הענף restart ואת התיקייה "game-restart". שתי התיקיות החדשות מתחילות מהקומיט של ההשהיה, כי הענף main מצביע עליו. שני הסוכנים יכולים עכשיו לערוך בו-זמנית, והקבצים בתיקייה שלנו נשארים ללא שינוי.
כשסוכן עושה קומיט בענף שלו, git שומר גרסה חדשה ומזיז את שם הענף לקומיט החדש. שמות הענפים האחרים נשארים במקומם. הסוכן של המצב הכהה שינה שני קבצים, את הלוגיקה של המשחק ואת הצבעים. ב-worktree שלו, הוא מכניס אותם ל-staging עם "git add", ואז מריץ "git commit" עם ההודעה "Add dark mode". הענף dark-mode מצביע עכשיו על הקומיט החדש, שמקושר אחורה לקומיט של ההשהיה. הענפים main ו-restart לא זזו.
גם הסוכן של ההתחלה מחדש מסיים. ב-worktree שלו, הוא מכניס את קובץ המשחק ל-staging עם "git add", ואז מריץ "git commit" עם ההודעה "Add restart control". הענף restart זז לקומיט החדש שלו, שגם הוא מקושר אחורה לקומיט של ההשהיה. הענף dark-mode נשאר על הקומיט שלו, ו-main עדיין מצביע על הקומיט של ההשהיה. שני הפיצ'רים נבנו מאותה גרסת התחלה. קומיט של פיצ'ר בענף שלו לא מוסיף אותו ל-main או לענף של הפיצ'ר השני.
שלושת ה-worktrees משתמשים באותו מאגר, אז אפשר לראות את הקומיטים האלה מכל אחת משלוש התיקיות. ההיסטוריה משותפת, גם אם קובצי העבודה נפרדים. התיקייה שלנו עדיין על main, עם השהיה ובלי אף אחד מהפיצ'רים החדשים. בתיקייה של ההתחלה מחדש יש השהיה והתחלה מחדש, בלי מצב כהה. זה שקומיט מופיע בהיסטוריה לא מכניס את השינויים שלו לתיקייה.
הקומיט של המצב הכהה נשמר, אבל עדיין צריך לסקור את הפיצ'ר לפני שמאשרים אותו לתוך main. נשווה את הקבצים שלו לגרסה שממנה הסוכן התחיל. ב-worktree של המצב הכהה, נריץ בעצמנו "git diff main" ונקרא את הפלט. הפקודה משווה את הקבצים הנוכחיים שבמעקב לקומיט ש-main מסמן. כאן main עדיין מצביע על הקומיט של ההשהיה, שממנו הפיצ'ר הזה התחיל. בקטע הזה רואים שלטקסט המקשים נוסף "D: dark mode". השינוי כולו נוגע בשני קבצים, הלוגיקה של המשחק והצבעים. קראו את השינויים בשני הקבצים לפני שאתם מנסים את המשחק.
אחר כך הריצו את המשחק מה-worktree של המצב הכהה, כדי שתבדקו את הקבצים שבדיוק סקרתם. בדקו שהצבעים משתנים, ש-D מחליף את ערכת הצבעים, וש-P עדיין משהה את המשחק. פיצ'ר מוכן למיזוג כשהשינויים שלו הגיוניים וההתנהגות שלו תואמת את מה שביקשנו. עכשיו אפשר להכניס את העבודה שסקרנו לתוך main.
מיזוג מביא את העבודה שבקומיטים של ענף אחר לתוך הענף הנוכחי. אנחנו רוצים את המצב הכהה ב-main, אז נבקש מהסוכן להריץ את המיזוג בתיקייה המקורית שלנו, שבה עשינו checkout ל-main. בגרף ההיסטוריה, הענף main עדיין מצביע על הקומיט של ההשהיה. הקומיט של המצב הכהה נבנה ישירות מהקומיט הזה, אז הוא כולל את כל מה שיש ב-main, ובנוסף את הפיצ'ר.
הסוכן מריץ "git merge dark-mode". מכיוון שב-main לא נוספו קומיטים משלו, הקומיט של המצב הכהה כבר כולל את כל העבודה שלו. git מזיז את main לקומיט הקיים של המצב הכהה, בלי ליצור קומיט חדש. זה נקרא fast-forward. git גם מעדכן את הקבצים בתיקייה שלנו, אז במשחק שם יש עכשיו מצב כהה. אחרי המיזוג, נסו את המצב הכהה ואת ההשהיה בתיקייה שלנו. פיצ'ר ההתחלה מחדש עדיין נפרד, ואותו נסקור עכשיו.
הסוכן של ההתחלה מחדש התחיל מהקומיט של ההשהיה, אבל הענף main מצביע עכשיו על הקומיט של המצב הכהה. השוואה של restart מול main הייתה מראה גם הבדלים שנובעים מהמצב הכהה. ב-worktree של ההתחלה מחדש, נריץ "git diff" עם המזהה של קומיט ההשהיה, שמופיע בשקף. כך משווים את הקבצים הנוכחיים שבמעקב לגרסה שממנה התחיל הענף restart. ה-diff מוסיף לטקסט המקשים "R: restart", מגדיר את פונקציית ההתחלה מחדש, ומחבר אליה את המקש R. הריצו את המשחק מה-worktree הזה. בדקו ש-R מתחיל את המשחק מחדש, וש-P עדיין משהה וממשיך אותו.
בשני הענפים יש עכשיו עבודה שונה. בענף main יש מצב כהה בלי התחלה מחדש, וב-restart יש התחלה מחדש בלי מצב כהה. אם נזיז את main לקומיט של ההתחלה מחדש, המצב הכהה יישאר מחוץ לגרסה הנוכחית שלו. לכן git לא יכול לשלב את הפיצ'רים ב-fast-forward. הוא צריך גרסה חדשה שכוללת את שניהם.
כדי לשלב את הענפים, git משווה את הגרסה של כל ענף לגרסת ההתחלה המשותפת שלהם, הקומיט של ההשהיה. כך הוא מזהה את השינויים שכל פיצ'ר עשה. git משלב את שתי קבוצות השינויים לגרסה אחת ושומר אותה כקומיט חדש. זה נקרא קומיט מיזוג. רוב המיזוגים מסתיימים בלי קונפליקט. בדוגמה שלנו יש שורת קוד ששני הסוכנים שינו: שני הפיצ'רים שלהם שינו את הטקסט שמסביר את מקשי המשחק. git ישלב את שאר השינויים אוטומטית ויעצור בשורה הזו, כדי שאנחנו נכריע בה.
הסוכן מריץ "git merge restart" בתיקייה המקורית שלנו, על main. git משלב את רוב השינויים אוטומטית. אבל שני הפיצ'רים ערכו את שורת המקשים בצורה שונה, ולכן git לא יכול לשלב את השינויים האלה בעצמו. הוא עוצר ומדווח על קונפליקט מיזוג. קומיט המיזוג עוד לא נוצר, אז צריך לפתור את הקונפליקט לפני שמסיימים את המיזוג.
שני הפיצ'רים התחילו מטקסט מקשים שהופיע בו רק "P: pause". הגרסה ב-main מוסיפה "D: dark mode", והגרסה של restart מוסיפה "R: restart". git לא יכול להחליט איזו גרסה נכונה, ולכן הוא משאיר את ההחלטה לנו. אנחנו רוצים להשאיר את ההשהיה ולהציג את שני המקשים החדשים, אז התוצאה צריכה לשלב את שתי התוספות.
git כותב סמני קונפליקט לתוך קובץ המשחק, בתיקייה המקורית שלנו. שורת הסוגריים המשולשים הראשונה פותחת את הקונפליקט. התווית שלה, "HEAD", מסמנת כאן את הגרסה הנוכחית שלנו ב-main.
שורת סימני השוויון מפרידה בין הגרסה שב-main לגרסה שב-restart. שורת הסוגריים המשולשים האחרונה סוגרת את הקונפליקט, והתווית שלה היא "restart".
הקוד שבין הסמנים מציג את שתי החלופות שצריך ליישב ביניהן. החליפו את כל בלוק הקונפליקט בקוד שאתם רוצים, ומחקו את שורות הסמנים.
אמרו לסוכן לשמור את שני הפיצ'רים ולהציג את שלושת המקשים. ואז קראו את הפתרון שלו, כדי לבדוק שהוא תואם את ההוראה. השורה הפתורה מציגה השהיה, מצב כהה והתחלה מחדש, וסמני הקונפליקט נעלמו. עכשיו נבדוק ששלושת המקשים עובדים במשחק המשולב.
הריצו את המשחק המשולב מהתיקייה שלנו. בדקו ש-P משהה וממשיך, ש-D מחליף את ערכת הצבעים, וש-R מתחיל את המשחק מחדש.
אחרי שבדקנו את המשחק המשולב, נבקש מהסוכן לסיים את המיזוג. הוא מריץ "git add" על קובץ המשחק הפתור, כדי להכניס ל-staging את התוכן שלו ולסמן שהקונפליקט נפתר. אחר כך הסוכן מריץ "git commit" עם ההודעה "Merge restart and dark mode". זה שומר את הגרסה המשולבת כקומיט מיזוג. הורה הוא קומיט שקומיט אחר נבנה ממנו. לקומיט המיזוג הזה יש שני הורים, הקומיט של המצב הכהה והקומיט של ההתחלה מחדש. הענף main מצביע עכשיו על הגרסה המשולבת, עם השהיה, מצב כהה והתחלה מחדש.
אחרי ששני הפיצ'רים מוזגו ונבדקו, נבקש מהסוכן לבדוק אם נשאר בתיקיות של הפיצ'רים משהו שלא נכנס לקומיט. אחרי הבדיקה הזו, הוא מסיר את ה-worktrees שסיימנו איתם ואת שמות הענפים שלהם. הסוכן מריץ "git worktree remove" לכל תיקיית פיצ'ר, ואז "git branch" עם -d לשני ענפי הפיצ'רים. הפקודות מסירות את התיקיות ואת שמות הענפים, אבל הקומיטים שמוזגו נשארים בהיסטוריה של main. אנחנו ממזגים רק את הפיצ'רים שהחלטנו לאשר. פיצ'ר שאנחנו לא רוצים פשוט נשאר מחוץ ל-main.
ההיסטוריה מראה ששני הפיצ'רים מתחילים מהקומיט של ההשהיה ומתאחדים בקומיט המיזוג, שעליו main מצביע עכשיו. כל פיצ'ר נכתב ונסקר ב-worktree משלו לפני שמיזגנו אותו. כך כמה סוכנים יכולים לעבוד על פיצ'רים נפרדים, בזמן שאנחנו מחליטים מה נכנס לפרויקט. תנו לכל סוכן ענף ו-worktree משלו, סקרו את הפיצ'ר שלו, ואז מזגו ובדקו את התוצאה.
נתראה בסרטון הבא.