חזור לתיקייה: תרגול

3 - גיט

חיפוש בתיקיה
Ctrl K
לחץ כאן או הקש Ctrl K כדי לחפש בכל התיקיה
  • אבני הבניין של Git
  • המאגר - Repository
  • קומיט - Commit
  • מבנה הפקודות של git
  • מצבי קבצים ב-git
  • פקודות לשינוי מצבי קובץ
  • עבודה נכונה עם קומיטים
  • ענפים - Branches
  • עבודה נכונה עם ענפים
  • עבודה מרחוק - Remote
  • עבודה מרחוק עם ענפים
  • רצף עבודה במאגר מרוחק
  • קונפליקטים
  • פתרון קונפליקט
  • ב-rebase
  • פתירת קונפליקטים ב-Rebase
  • הקובץ .gitignore

3 - גיט

02340124

מבוא לתכנות מערכות

רשימות תרגול לקריאה בשבת

גיט

הופק מתוך תקיית התואר של שקד

uni-folder.vercel.app

גיט(GitGitGit) היא מערכת ניהול גרסאות מבוססת קוד פתוח.
מערכת לניהול גרסאות היא מערכת המאפשרת לנו לשמור גרסאות של הקוד שלנו.
למה זה טוב?

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

גיט מאפשר לנוL:

  1. לעקוב אחרי קבצים לאורך זמן
  2. לעבוד על קבצים במקביל עם מפתחים אחרים
  3. להשוות בין גרסאות שונות של אותו קובץ
  4. לחזור חזרה לגרסה ישנה של קבצים במידת הצורך

אבני הבניין של GitGitGit

המאגר - RepositoryRepositoryRepository

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

קומיט - CommitCommitCommit

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

  • מזהה יחודי
  • מצביע לקומיט שהיה לפניו
הערה

זה בצעם רשימה מקושרת

מבנה הפקודות של gitgitgit

git <sub-command> [flags] [files/branches]

כאשר sub−commandsub-commandsub−command היא הפקודה המתאימה לפעולה הספציפית אותה אנו נרצה לבצע והשאר הם ממש כמו ב-linuxlinuxlinux שראינו בתרגול הראשון.

מצבי קבצים ב-gitgitgit

כל קובץ במאגר נמצא באחד מארבעה מצבים:

  1. אם הקובץ לא שונה מאז שביצענו קומיט לשינויים האחרונים הוא יקרא CommitedCommitedCommited.
  2. אם ביצענו שינויים בקובץ ועדיין לא הכנסנו אותם לאף קומיט הוא יקרא ModifiedModifiedModified
  3. אם ביצענו שינויים בקובץ וסימנו אותם בתור שינויים שנרצה לכלול בקומיט הקרוב הוא יקרא StagedStagedStaged
  4. אם גיט לא עוקב אחרי הקובץ הוא יקרא UntrackedUntrackedUntracked.

פקודות לשינוי מצבי קובץ

הפקודה addaddadd משמשת להוספת קבצים לרשימת השינויים שנרצה לכלול בקומיט הקרוב. (הפקודה מעבירה קבצים למצב StagedStagedStaged)

git add file1.c

הפקודה commitcommitcommit יוצרת קומיט חדש הכולל את כל השינויים במאגר שהיו במצב StagedStagedStaged.

git commit -m "Fixing a bug in the main function"

הפקודה statusstatusstatus מספקת לנו מידע על המצב של הקבצים במאגר שלנו. בעזרתה נוכל לראות אילו קבצים במצב modifiedmodifiedmodified ואילו במצב stagedstagedstaged וכו'

git status

הפקודה diffdiffdiff מראה לנו שאת השינויים במאגר בהשוואה לקומיט האחרון.
כדי לראות את כל השינויים בכל הקבצים במצב modifiedmodifiedmodified נכתוב:

git diff

כדי לראות את השינויים בקבצים ספציפיים במצב modifiedmodifiedmodified, למשל בקובץ file1.cfile1.cfile1.c:

git diff file1.c

כדי לראות שינויים בקבצים במצב stagedstagedstaged נכתוב:

git diff --staged

הפקודה logloglog מראה לנו רשימה של כל הקומיטים האחרונים במאגר

git log

נוכל להסתכל על כמה דגלים שימושיים:

  • הדגל -<n> מגביל את מספר הקומיטים המוצגים
  • הדגל --oneline מציג כל קומיט בשורה בודדת ומראה רק את הכותרות
  • הדגל --graph מציג את רצף הקומיטים בצורה של גרף
  • הדגל -p מציג את כל השינויים הכלולים כל קומיט
  • הדגל --stat מציג סיכום מתומצת של השינויים עבור כל קומיט
  • הדגל --all מציג את כל הקומיטים מכל הענפים.
הערה (שימו )

ניתן לשלב דגלים אחד עם השני. למשל:

git log --all --graph --oneline

עבודה נכונה עם קומיטים

כל קומיט או אטומי - מכיל שינוי לוגי אחד של הפרוייקט. למשל:

  • הוספת פיצ'ר קטן אחד
  • תיקון של באג בפרוייקט

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

ענפים - BranchesBranchesBranches

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

הפקודה branchbranchbranch מאפשרת לנו לראות אילו ענפים יש לנו במאגר:

git branch

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

git branch newBranch

הפקודה checkoutcheckoutcheckout מאפשרת לנו לעבור בין ענפים וקומיטים שונים

git checkout branch1

ניתן ליצור ענף ידש וגם לעבור אליו בפקודה יחידה. נעשה זאת ע"י הוספת הדגל -b לפקודה:

git checkout -b branch2

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

git merge feature_branch

את הפקודה נפעיל מהענף אליו נרצה להביא את השינויים ובה נציין את הענף ממנו נרצה להביא את השינויים.

הערה

במידה והענפים מוכלים אחד בשני, הפקודה mergemergemerge לא תיצור קומיט חדש, אלא רק תזיז את הענף אליו אנו מביאים את השינויים כך שיצביע על הקומיט העדכני ביותר.
לשינוי זה קוראים Fast  ForwardFast\; ForwardFastForward.

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

git rebase master

את הפקודה נפעיל מהענף ממנו נרצה להביא את השינויים ובה נציין את שם הענף אליו נרצה להביא את השינויים.

עבודה נכונה עם ענפים

  • נהוג לשמור ענף אחד מרכזי הנקרא mainmainmain או mastermastermaster.
    בדרך כלל לא נעבוד ישירות על הענף הזה אלא נעדכן אותו רק ע"י מיזוג של ענפים אחרים.
  • בכל פעם שנרצה לעשות שינוי במערכת:
    1. נפתח ענף חדש וזמני המיועד לשינויים אותם נרצה להוסיף ונעבור אליו
    2. נוסיף על הענף החדש קומיטים המכילים את השינויים שלנו
    3. נמזג את השינויים לתוך mainmainmain באמצעות פקודת mergemergemerge או rebaserebaserebase
    4. אופיונלי: נמחק את הענף הזמני שיצרנו
  • בפרוייקטים גדולים נהוג להחזיק ענף קבוע הנקרא devdevdev המייצג את המצב התכנית העדכני עבור המפתחים בעוד mainmainmain מייצג את המצב העדכני עבור הלקוח.

עבודה מרחוק - RemoteRemoteRemote

כדי לאפשר עבודה של מספר מפתחים על הפרויקט, כל אחד ממחשב אחר, נארח את המאגר שלנו על שרת גיט(למשל githubgithubgithub)
כל מפתח יחזיק עותק מקומי של המאגר המאוחסן על השרת ומדי פעם יבוצעו עדכונים בין השרת לעותק המקומי.
​מתמ תרגול 3 גיט.svg

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

הפקודה clonecloneclone יוצרת עותק מקומי של מאגר המאוחסן בשרת גיט מרוחק. בפקודה נציין את ה-URLURLURL בו מאוחסן המאגר.

git clone https://github.com/CS234124/GitSimulation..git

עבודה מרחוק עם ענפים

בנוסף לאלו שקיימים במאגר המקומי, עבור כל אחד מהענפים בשרת המרוחק יהיה לנו עותק מקומי.
במאגר המקומי, שמות הענפים יתחילו בשם השרת המרוחק(בדרך כלל originoriginorigin)
בדרך כלל לא נוסיף שינויים ישירות לענפים אלו.

כעת, יכולות להיות לנו 3 גרסאות שונות לכל ענף. למשל, עבור ענף הנקרא mainmainmain:

  1. הענף mainmainmain מקומי(Local  BranchLocal\; BranchLocalBranch) - הגרסה עליה אנו עובדים ולה אנו מוסיפים שינויים
  2. הענף origin/mainorigin/mainorigin/main מקומי(Remote  BranchRemote\; BranchRemoteBranch) - גרסה מקומית המקבילה לענף בשרת המרוחק
  3. הענף mainmainmain בשרת המרוחק - גרסה המשותפת לכל המפתחים בפרוייקט

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

הפקודה fetchfetchfetch מביאה את כל השינויים מהשרת המרוחק אלינו ומעדכנת את כל ה-Remote  BranchesRemote\; BranchesRemoteBranches הנמצאים במאגר המקומי

git fetch

שימו - הפקודה לא משנה את הענפים המקומיים עליהם אנו עובדים.

הפקודה pullpullpull גם מעדכנת את ה-Remote  BranchesRemote\; BranchesRemoteBranches וגם מבצעת Fast  ForwardFast\; ForwardFastForward לענף המקומי עליו אנו נמצאים כך שיהיה מסונכרן עם זה שבשרת המרוחק.

git pull

כלומר, אפשר לחושב על הפקודה pullpullpull כאילו היא מבצעת שני דברים:

git fetch
git merge

כמובן עם הדגלים והקבצים המתאימים.

במידה ויש שינויים חדשים בענף המקומי וגם בזה שנמצא על השרת, לא יהיה ניתן לבצע פקודת pullpullpull.
במקרה זה נוכל להוסיף את הדגל --rebase וכעת הפקודה תבצע fetchfetchfetch ואז rebaserebaserebase של הענף המקומי מעל זה שהבאנו מהשרת המרוחק

git pull --rebase

הפקודה pushpushpush מעדכנת את השרת המרוחק בשינויים שביצענו בענף המקומי.

git push

שימו - פקודת ה-pushpushpush פועלת רק על הענף בו אנו נמצאים בעת ההפעלה
שימו - במידה וקיימים שינויים גם מקומית וגם בשרת המרוחק לא נוכל לבצע pushpushpush ותחילה נדרש לבצע pullpullpull ורק לאחר מכן לבצע pushpushpush.

רצף עבודה במאגר מרוחק

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

  1. נבצע pullpullpull לענף הבסיס ממנו אנו רוצים להתחיל את העבודה
  2. ניצור ענף חדש מקומי ובו נוסיף את כל השינויים שלנו
  3. מדי פעם, נעדכן את העף המקומי:
    1. נבצע pullpullpull לענף הבסיסי.
    2. נבצע rebaserebaserebase לענף עליו אנו עובדים, מעל הבסיס.
  4. נמזג את הענף עליו אנו עובדים אל הבסיס באמצעות mergemergemerge או rebaserebaserebase.
  5. נבצע pushpushpush לענף הבסיס כך שיעודכן בשרת המרוחק.

שימו - למעשה לא הוספנו אף ענף לשרת המרוחק.

קונפליקטים

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

דוגמה

$ git log --all --graph --oneline
* ed2bd95 (branch1) Changed print to "Goodbye"
|  * 5586254 (HEAD -> master) Added "World" to print
|/
* 5e43971 Initial commit

$ git merge branch1
Auto-merging file1.c
CONFLICT (content): Merge conflict in file1.c
Automatic merge failed; fix conflicts and then commit the result.

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

git status

פתרון קונפליקט

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

דוגמה

#include <stdio.h>

int main()
{
<<<<<<< HEAD
	printf("Hello, World!\n");
=======
	printf("Goodbye\n");
>>>>>>> branch1
}

נוכל בנוסף להשתמש בסביבת הפיתוח שלנו שבה לרוב יהיה UIUIUI יותר חביב לפתרון הקונפליקטים.

כאשר אנו מסיימים לפתור את כל הקונפליקטים נסמן לגיט שסיימנו ע"י הוספת הקבצים הרלוונטיים באמצעות הפקודה addaddadd.
לאחר מכן נבצע פקודת commitcommitcommit וייוצר עבורינו Merge  CommitMerge\; CommitMergeCommit המכיל את השינויים הנדרשים לפתרון קונפליקטים.

דוגמה

$ git add file1.c
$ git commit
[master aabc51a] Merge branch 'branch1'

$ git log --all --graph --oneline
*   aabc51a (HEAD -> master) Merge branch 'branch1'
|\
| * ed2bd95 (branch1) Changed print to "Goodbye"
* | 5586254 Added "World" to print
|/
* 5e43971 Initial commit

ב-rebaserebaserebase

גם כאשר נבצע rebaserebaserebase של ענף אחד מעל השני, יכולים להיווצר קונפליקטים. בשונה מ-mergemergemerge, כאן יכולים להיווצר קונפליקטים בכל אחד מה-commitscommitscommits שאנחנו "מזיזים" בנפרד.
במקרה כזה גיט יעצור את ה-rebaserebaserebase בכל commitcommitcommit ויתן לנו הזדמנות לפתור אותם בדומה ל-mergemergemerge ולאחר מכן נוכל להמשיך עם פעולת ה-rebaserebaserebase.

פתירת קונפליקטים ב-RebaseRebaseRebase

  1. גיט תעצור את ה-rebaserebaserebase כאשר היא מוצאת קונפליקט
  2. נפתור את הקונפליקט ע"י עריכת הקבצים הרלוונטיים
  3. "נסמן" לגיט שפתרנו את הקונפליקט ע"י פקודת addaddadd
  4. נמשיך את ה-rebaserebaserebase באמצעות הפקודה: git rebase --continue.

הקובץ .gitignore.gitignore.gitignore

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