עבודה עם שינויים

פתרון התנגשויות

כשמיזוג, ריבייס, צ׳רי־פיק או ריוורט נעצרים, כרזה אומרת לכם מה נעצר ובין מה למה — ״ממזג את feature/x לתוך main״, לא סתם ״התנגשות״.

פותר ההתנגשויות

למה זה מתנגש

למה זה מתנגש בכותרת מונה, לכל צד בנפרד, את הקומיטים שנגעו בקובץ הזה מאז שהענפים נפרדו — זהו git log --merge, שקיים ב־git מאז ומעולם ואף אחד לא מוצא.

הקומיטים מכל צד שנגעו בקובץ שבהתנגשות

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

שלושת הפאנלים

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

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

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

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

התמצאות

פתיחת קובץ מנחיתה אתכם על ההתנגשות הראשונה שלו, לא על ראש הקובץ. החיצים ⌃ / ⌄ בכותרת הפלט — או Alt+↑ / Alt+↓ — צועדים דרך השאר, וגוללים את כל שלושת הפאנלים לכל אחת.

בחירה

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

סיוע AI

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

קובצי פרויקט של Xcode

‏project.pbxproj מתנגש יותר מכל קובץ אחר במאגר iOS, וכמעט אף פעם לא מפני שמישהו חלק על מישהו. זהו מילון שטוח אחד של אובייקטים שמפתחותיו מזהים הקסדצימליים בני 24 תווים, ולכן הוספת קובץ אחד כותבת ארבע רשומות: PBXBuildFile, ‏PBXFileReference, שורה ב-children של הקבוצה המכילה ושורה בשלב הבנייה של היעד. שני אנשים שכל אחד מוסיף קובץ כותבים שמונה רשומות שנוחתות על אותן כמה שורות. Git רואה התנגשות; אין באמת מה לפתור.

כשהקובץ המתנגש הוא project.pbxproj, פותר ההתנגשויות קורא את שלוש הגרסאות כפרויקטים ולא כטקסט, ומציע למזג לפי מבנה: להתאים אובייקטים לפי מזהה, לקחת כל תוספת משני הצדדים, לאחד את המערכים children ו-files, ולעצור במה שבאמת נפרד. הרצועה שמעל הפאנלים אומרת מה כל צד הוסיף ומה — אם בכלל — נשאר לך.

כמו ההצעה של ה-AI, זה נוחת בפאנל הפלט ואינו מעלה דבר ל-staging. אתה קורא לפני שאתה שומר.

רצועת המיזוג המבני מעל פאנלי ההתנגשות, בקובץ פרויקט של Xcode

מה הוא מסרב לעשות

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

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

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

לא merge=union

הפתרון שמסתובב לזה הוא *.pbxproj merge=union ב-.gitattributes. הימנע ממנו. איחוד עובד כל עוד השינויים היחידים הם תוספות בלתי תלויות, וברגע ששני אנשים עורכים את אותה הגדרת בנייה הוא פולט את שתי השורות ומייצר קובץ ש-Xcode מסרב לפתוח — בדיוק ברגע שבו הסיכוי שתקרא את ההפרש בעיון הוא הנמוך ביותר. מיזוג מבני נותן את אותה נוחות בלי הכשל הזה.

קובצי נעילה

‏Podfile.lock, Package.resolved, yarn.lock ובני דודיהם מתעדים גרף תלויות שהפותר של מישהו כבר פתר. חצי מפתרון אחד שנתפר לחצי מפתרון אחר הוא גרף שאיש לא פתר: ייתכן שלא יותקן, ואם כן — יותקן משהו שאף אחד משני הענפים לא בדק.

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

רצועת קובץ הנעילה מעל פאנלי ההתנגשות

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

איך להימנע מהן מלכתחילה

מכ״ם ההתנגשויות אומר לכם אילו ענפים יתנגשו עוד לפני שתמזגו אחד מהם.

לתת ל־git לזכור (rerere)

עשו ריבייס לענף ארוך־חיים ותיפגשו באותה התנגשות בכל פעם. rerere — reuse recorded resolution — היא התשובה של git: היא משננת איך יישבתם התנגשות ומשחזרת את התשובה הזו בפעם הבאה שאותה התנגשות בדיוק מופיעה.

הגדרות ← כללי ← שינון פתרונות התנגשות. זה כותב rerere.enabled לתצורת ה־git הגלובלית שלכם, כך ששורת הפקודה מתנהגת בדיוק אותו דבר.

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

שני דברים ששווה לדעת:

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

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

  • rerere לא מבינה כל התנגשות. התנגשויות הוספה/הוספה ומחיקה/שינוי לא מקבלות תמונת מקור, ולכן הן תמיד חוזרות גולמיות. המספר בהגדרות הוא כמה היא באמת מחזיקה, ושכחת הכול מרוקן אותו.

ראו גם: מכ״ם ההתנגשויות · מיזוג וריבייס

שיפור התרגום הזה ב־GitHub