היום למדתי: שינוי שם ב git

16/11/2021

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

אני בתיקיה חדשה כותב:

$ git init .
$ echo hello > file1.txt
$ git add .
$ git commit -m 'initial commit'

ועכשיו נשנה לקובץ את השם:

$ git mv file1.txt file2.txt
$ git status

On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    file1.txt -> file2.txt

$ git commit -m 'rename'

ואפשר לראות את שינוי השם גם בצפיה בלוג:

$ git log -p --oneline

25ccbd7 (HEAD -> main) rename
diff --git a/file1.txt b/file2.txt
similarity index 100%
rename from file1.txt
rename to file2.txt
0e6c0e3 initial commit
diff --git a/file1.txt b/file1.txt
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.txt
@@ -0,0 +1 @@
+hello

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

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

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

והטריק להיום הוא המתג --no-renames שאפשר לצרף להרבה פקודות גיט כדי לכבות את מנגנון זיהוי שינויי השם. כך אותו הלוג יראה בלי המנגנון:

$ git log -p --oneline --no-renames
25ccbd7 (HEAD -> main) rename
diff --git a/file1.txt b/file1.txt

deleted file mode 100644
index ce01362..0000000
--- a/file1.txt
+++ /dev/null
@@ -1 +0,0 @@
-hello
diff --git a/file2.txt b/file2.txt

new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file2.txt
@@ -0,0 +1 @@
+hello
0e6c0e3 initial commit
diff --git a/file1.txt b/file1.txt

new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.txt
@@ -0,0 +1 @@
+hello

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

גם diff יכול לקבל אותו טיפול והנה שתי האפשרויות:

$ git diff --name-status HEAD~
R100    file1.txt       file2.txt

$ git diff --name-status --no-renames HEAD~
D       file1.txt
A       file2.txt

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