שלום אורח התחבר

הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

מעדיפים לקרוא מהטלגרם? בקרו אותנו ב:@tocodeil

או הזינו את כתובת המייל וקבלו את הפוסט היומי בכל בוקר אליכם לתיבה:

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

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

% git log --oneline --graph
*   5dc5b62 (HEAD -> master) merged
|\
| * 9630f65 (funnytext) add answer
| * 862a455 add some funny text
* | c99a456 made some changes
|/
* 7042a02 initial commit

הענף funnytext כולל שני קומיטים שנוצרו מתוך 7042a02, וענף master כולל גם הוא שני קומיטים שיצאו מתוך 7042a02, הראשון קומיט רגיל והשני הוא Merge Commit. הקומיט השני הוא זה שמעניין אותנו.

במצב כזה של מיזוג כל קומיט חדש שנוסיף לענף funnytext יחייב אותנו לפתור את הקונפליקטים של המיזוג פעם שניה. במילים אחרות תוספת של קומיט ל funnytext ו merge יראו כך:

*   f64231d (HEAD -> master) fixed conflicts
|\
| * ad878a8 (funnytext) FIXED joke
* |   5dc5b62 merged
|\ \
| |/
| * 9630f65 add answer
| * 862a455 add some funny text
* | c99a456 made some changes
|/
* 7042a02 initial commit

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

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

% git log --oneline --graph
*   5dc5b62 (HEAD -> master) merged
|\
| * 9630f65 (funnytext) add answer
| * 862a455 add some funny text
* | c99a456 made some changes
|/
* 7042a02 initial commit

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

% git switch funnytext
% git reset --hard master

וקיבלנו לוג הרבה יותר פשוט:

*   5dc5b62 (HEAD -> master, funnytext) merged
|\
| * 9630f65 add answer
| * 862a455 add some funny text
* | c99a456 made some changes
|/
* 7042a02 initial commit

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

הגיעה אליי שאלה מעניינת הבוקר במייל וחשבתי לשתף את התשובה כאן לטובת כולם. והשאלה: מה ההבדל בין הפקודה git rm --cached לבין הפקודה git reset, מתי אשתמש בכל אחת?

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

הפקודה git add לוקחת קובץ מתיקיית העבודה ומעתיקה אותו לאותה תיקיה זמנית של "הכנה לקומיט" שקראתי לה Staging Area. הפקודה git reset לוקחת קובץ מהריפוזיטורי ומעתיקה אותו לאותה תיקיה זמנית. לכן אפשר לחשוב על שתי פקודות אלה בתור הפכים. נכין רגע מאגר לדוגמא:

$ mkdir demo
$ cd demo
$ git init
$ echo one > demo.txt
$ git add demo.txt
$ git commit -m 'commit 1'

אז כשאני יוצר קובץ חדש בתיקיית העבודה אני יכול להשתמש ב git add כדי להוסיף אותו לתיקיה הזמנית:

$ echo two > two.txt
$ git add two.txt 
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt
100644 f719efd430d52bcfc8566a43b2eb655688d38871 0   two.txt

או בפקודה git reset כדי להעתיק את הגירסא מהריפוזיטורי לתיקיה הזמנית מה שיגרום למחיקת הקובץ משם:

$ git reset two.txt
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

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

לעומתה הפקודה git rm הרבה יותר דומה לפקודה git add, רק שבעוד ש git add מעתיקה קובץ מתיקיית העבודה ל Staging Area, תפקיד הפקודה git rm הוא להעתיק את ה"אין קובץ", כלומר למחוק קובץ מה Staging Area.

נזכור שהדרך הרגילה שלנו להוציא קובץ מה Staging Area היא לדרוס את הקובץ ולהחליף אותו בגירסא ששמורה בריפוזיטורי, וזה מה שעושה git reset. אבל אם אנחנו רוצים למחוק קובץ לחלוטין זה לא באמת יעזור להעתיק את הגירסא מהריפוזיטורי. במילים אחרות בדוגמא שלנו הקובץ demo.txt נמצא ב Staging Area וגם ב Repository. אם אפעיל git reset עליו לא יקרה כלום:

$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

$ git reset demo.txt
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

בכל מקרה הוא נשאר ב Staging Area וייכנס לקומיט הבא. הפקודה git rm היא הדרך שלנו להעתיק את העובדה שהקובץ לא קיים אל ה Staging Area. שימו לב להשפעה שלה על המאגר שלנו:

$ git rm demo.txt
$ git ls-files --stage
$ ls
two.txt

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

ומה לגבי git rm --cached ? אם הגעתם עד לפה הסיפור שלו הוא הכי פשוט מכולם. לפעמים אנחנו תקועים עם קובץ או תיקייה שאנחנו רוצים להוציא מהריפוזיטורי אבל להשאיר אצלנו בתיקיית העבודה. דוגמא קלאסית היא תיקיית node_modules: בטעות הוספנו תיקיה זו למאגר ועכשיו אנחנו רוצים להוציא אותה, אבל התיקיה כן עוזרת לי בעבודה השוטפת ולכן אני לא רוצה למחוק אותה מהדיסק. הפעלת git rm -r node_modules לא באה בחשבון כיוון שהיא תביא למחיקת התיקיה מהדיסק. אבל, אם נוסיף את --cached נקבל גירסא של git rm שלא מוחקת את הקובץ או התיקיה מהדיסק אלא רק מה Staging Area.

בחזרה למאגר הדוגמא נחזיר את הקובץ demo.txt מהריפוזיטורי עם:

$ git restore -s HEAD demo.txt
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

ועכשיו נמחק אותו רק מה Staging Area בלי למחוק אותו מתיקיית העבודה:

$ git rm --cached demo.txt
$ ls
demo.txt two.txt
$ git ls-files --stage

בעבודה עם גיט אנחנו אוהבים ליצור Feature Branch עבור כל פיצ'ר שאנחנו צריכים, ובסיום העבודה להפעיל merge כדי למזג את כל הקומיטים של ה Feature Branch בחזרה לענף הראשי. בפרויקטים גדולים לפעמים משתלם לכווץ את כל הקומיטים מהפיצ'ר בראנצ' לקומיט יחיד כדי שיהיה יותר קל להסתכל בהיסטוריה.

נניח בשביל הדוגמא ש master הוא הענף הראשי ו dev הוא ענף פיצ'ר קטן, והקומיטים שלהם נראים כך:

$ git log --oneline master
b4ff474 (HEAD -> master) c4
9da14d1 c3
146d73d c2
e82851f c1

$ git log --oneline dev
433746a (dev) c7
8324f82 c6
da5ccb8 c5
146d73d c2
e82851f c1

במצב כזה merge רגיל יחבר את כל הקומיטים כך שנקבל את עץ הפרויקט הבא:

$ git merge dev
$ git log --oneline --graph

*   9b80e8d (HEAD -> master) merge commit
|\  
| * 433746a (dev) c7
| * 8324f82 c6
| * da5ccb8 c5
* | b4ff474 c4
* | 9da14d1 c3
|/  
* 146d73d c2
* e82851f c1

הקומיטים c7, c6 ו-c5 שבוצעו ב Feature Branch עכשיו נמצאים איתנו ויישארו בעץ הפרויקט לנצח. דרך אחת לוותר עליהם היא "לכווץ" אותם לפני ביצוע המיזוג:

  1. נעבור לענף dev

  2. נחזיר את HEAD אחורה ל C2 בלי לשנות את הקבצים בתיקיית העבודה (עם פקודת reset). הקומיט C2 הוא הקומיט המשותף האחרון בין dev ל master ולכן זה בעצם הקומיט שהתחיל את הפיצול. החזרה אליו תאפשר לנו "לאחד" את קומיטים C5, C6 ו C7 לקומיט חדש יחיד.

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

אחרי פעולה זו ב dev יהיה רק קומיט יחיד (הקומיט שעשינו בסעיף 3), וכשנמזג אותו חזרה ל master נראה בהיסטוריה רק את הקומיט היחיד הזה.

לגיט יש קיצור דרך לרצף הזה שעושה משהו אפילו יותר נחמד: הפקודה git merge --squash תיקח את כל הקבצים מענף dev למקום הנוכחי בלי להזיז את HEAD, בלי לעשות קומיט ובלי לעדכן את MERGE_HEAD. הדבר השלישי אומר שהקומיט הבא לא יהיה Merge Commit עם שני הורים אלא קומיט רגיל.

בקוד זה נראה כך:

$ git merge --squash dev
$ git log --oneline --graph

* b4ff474 (HEAD -> master) c4
* 9da14d1 c3
* 146d73d c2
* e82851f c1

עכשיו אפשר לעשות קומיט ולקבל:

$ git add .
$ git commit -m 'C8'
$ git log --oneline --graph

* 08f5e15 (HEAD -> master) C8
* b4ff474 c4
* 9da14d1 c3
* 146d73d c2
* e82851f c1

וקיבלנו מיזוג של Feature Branch לענף הראשי תוך מחיקת ההיסטוריה של ה Feature Branch ושמירה על לוג לינארי בענף הראשי.

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

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

מתקינים את הספריה עם:

$ pip install dulwich --global-option="--pure"

ומתוך תוכנית פייתון נוכל עכשיו לכתוב:

from dulwich.repo import Repo
r = Repo('.')
last_commit_id = r.head().decode('ascii')

result_filename = f'result.{last_commit_id}.txt'

with open(result_filename, 'w') as f:
    f.write('Hello World\n')

כדי לקבל את קובץ התוצאות עם מזהה הקומיט האחרון.

ראיתי השבוע חבר מקליד את הפקודה הבאה בגיט כדי לצאת ממצב מביך:

$ git reset --hard origin/master

בואו נראה מה היא עושה כדי להבין אם היא יכולה לעזור גם לכם.

מתחילים בתיעוד של גיט. יש לגיט ריסט שלושה מצבי פעולה:

       git reset [-q] [<tree-ish>] [--] <paths>...
       git reset (--patch | -p) [<tree-ish>] [--] [<paths>...]
       git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<comm
it>]

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

In the third form, set the current branch head (HEAD) to
<commit>, optionally modifying index and working tree to match.

לכן הדבר הראשון שריסט יעשה הוא לשנות את HEAD כך שיצביע על הקומיט שעובר בפרמטר האחרון. הקומיט הזה במקרה שלנו הוא origin/master שזה אומר הקומיט שאנחנו חושבים שהשרת קורא לו master. זה אומר שאם עשינו כבר קומיטים אצלנו ב master אנחנו הולכים לחזור לנקודה לפני הקומיטים שלנו. מצד שני פקודת reset לא פונה לשרת לכן אם מישהו אחר עשה push לשרת אנחנו לא נדע מזה. אנחנו נלך למקום ש origin/master היה בו בפעם האחרונה שפנינו לשרת.

אפשר לראות את הנקודה הזאת עם:

$ git log -1 origin/master

או אם מה שמעניין אתכם זה רק ה Commit Hash תוכלו להפעיל:

$ git rev-parse origin/master
a08935913801409ef7437ba9038d4a0580f1f6c5

ועם פקודה דומה נוכל לגלות אם ה master שלנו כבר התקדם מהקומיט הזה או שהוא עדיין שם:

$ git rev-parse master
a08935913801409ef7437ba9038d4a0580f1f6c5

דרך אחרת היא פשוט להפעיל status. אם ה master שלכם לא תואם ל origin/master תקבלו על זה הודעה בשורה השניה של הפלט. במקרה שלי הם תואמים אז יש לנו:

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

בחזרה ל reset - אז הדבר הראשון שיקרה זה ש HEAD יעבור להצביע על הקומיט שנקרא origin/master. הדבר השני ש reset עושה זה לעדכן את הקבצים בתיקיית העבודה. המתג --hard אומר:

Resets the index and working tree. Any changes to tracked files in the working tree
since <commit> are discarded.

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

לסיכום הפקודה git reset --hard origin/master עושה את הדברים הבאים:

  1. היא בודקת מה מזהה הקומיט של הענף origin/master.

  2. היא מזיזה את הענף הנוכחי כך שיצביע גם הוא לאותו קומיט. יחד איתו יזוז גם ה HEAD.

  3. היא מאפסת את כל הקבצים ב Staging Area ולוקחת את הגירסא מהריפוזיטורי.

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

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

וקטנה לסיום- אם הפעלתם את git reset --hard origin/master בטעות ופתאום נעלמו לכם הקומיטים שעשיתם, אתם תמיד יכולים לגשת ל git reflog כדי למצוא את הקומיטים האבודים. אחרי שמצאתם את הקומיט ש master הצביע עליו קודם, תוכלו להשתמש ב git reset נוסף כדי לחזור אליו.

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

  1. שהמערכת תהיה מבוזרת.

  2. שהמערכת תהיה אמינה.

  3. שהמערכת תעבוד מהר.

הטענה שלו היתה שב 2005 הוא לא אף מערכת שעמדה ב-3 התנאים, ולכן הוא החליט לכתוב מערכת ניהול גירסאות בעצמו. או כמו שלינוס אמר אז בהתיחס למערכות הקיימות:

I can build something better in two weeks -- Linus Torvalds

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

המשך קריאה...

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

המשך קריאה...

אני מקווה שאתם מכירים את ההמלצה לחלק את השינויים שלכם לקומיטים לפי נושאים, כך שכל קומיט יתאים ל Issue במערכת ניהול ה Issues שלכם. אבל מה עושים אם שכחתם את עצמכם בהתלהבות של הקידוד והתעוררתם שעתיים אחרי עם 5 שינויים מ-5 נושאים שונים באותו קובץ?

אל תדאגו - אני יודע גיט.

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

# My Cool Project

This is a demo project

ועכשיו נוסיף לו שני בלוקים של טקסט שאמורים להיות מפוצלים ל-2 קומיטים שונים:

# My Cool Project

This is a demo project

# Installation
you need to download this app and install it if you want to use it

fixing first issue
fixing second issue
fixing third issue

במקום לעשות cut-and-paste עם קובץ נוסף אפשר פשוט להפעיל את git add במצב אינטרקטיבי:

$ git add --interactive -p

diff --git a/readme.txt b/readme.txt
index d3ef36a..a53fb88 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,3 +1,12 @@
 # My Cool Project

 This is a demo project
+
+# Installation
+you need to download this app and install it if you want to use it
+
+fixing first issue
+fixing second issue
+fixing third issue
+
+

Stage this hunk [y,n,q,a,d,e,?]? 

גיט מזהה את השינויים ושואל אולי מה לעשות. כרגע כל השינויים צמודים אחד לשני ומבחינת גיט זה נראה כמו שינוי יחיד. אני רוצה לקחת את הבלוק שמתחיל ב Installation ולשמור אותו בתור קומיט משלו ולכן אני לוחץ e כלומר edit (במצבים בהם הבלוקים לא היו רציפים גיט היה מבצע את הפיצול בצורה אוטומטית). בתגובה git יפתח את ה EDITOR המועדף עליי ויציג לי מסך עריכה שנראה כך:

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,3 +1,12 @@
 # My Cool Project

 This is a demo project
+
+# Installation
+you need to download this app and install it if you want to use it
+
+fixing first issue
+fixing second issue
+fixing third issue
+
+
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.

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

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,3 +1,12 @@
 # My Cool Project

 This is a demo project
+
+# Installation
+you need to download this app and install it if you want to use it
+
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# 
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

שמירה ויציאה ותראו את הסטטוס:

$ git status
On branch main
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   readme.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   readme.txt

הקובץ readme.txt מופיע גם בתור משהו שייכנס לקומיט הבא וגם בתור שינויים שלא ייכנסו לקומיט הבא. הפעלת diff תראה לנו את ההסבר:

$ git diff

diff --git a/readme.txt b/readme.txt
index 6076e63..a53fb88 100644
--- a/readme.txt
+++ b/readme.txt
@@ -5,3 +5,8 @@ This is a demo project
 # Installation
 you need to download this app and install it if you want to use it

+fixing first issue
+fixing second issue
+fixing third issue
+
+

בלוק הטקסט השני, שמסומן ב +, נמצא אצלי בתיקיית העבודה אבל לא ב Staging. לעומתו הבלוק שמעליו כן נמצא ב Staging area ולכן לא מופיע ב diff.

נפעיל קומיט כדי לשמור את השינוי:

$ git commit -m 'add installation instructions' 

ובסטטוס הבא נראה שהקובץ עדיין ב Modified אבל הפעם רק החלק השני שלו. עכשיו אפשר להמשיך ל add ו commit נוספים כדי להכניס את בלוק הטקסט השני:

$ git add .
$ git commit -m 'fixed more issues'

ובסוף הלוג יכלול שני קומיטים עבור שני השינויים שלנו, שהתרחשו באותו זמן ובאותו הקובץ:

commit 3612ce51ab22876a6337b56050a1f16bff9ea9e9 (HEAD -> main)
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Dec 23 14:49:12 2018 +0200

    fixed more issues

commit b4c25f7ac3cb7fdd3d8101750ff03cb2b3d7b5e1
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Dec 23 14:47:38 2018 +0200

    add installation instructions

אוהבים גיט ורוצים ללמוד להשתמש בו טוב יותר? אני חושב שתהנו מקורס Git Hero שיש לנו כאן באתר שכולל מעל שש שעות של הסברים, דוגמאות וטיפים לשימוש חכם יותר ב Git. פרטים והרשמה בקישור https://www.tocode.co.il/bundles/git

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

המשך קריאה...

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

המשך קריאה...