גיט, כמו SVN ורוב המערכות לניהול קוד מאפשרות גישה נוחה ל-hooks – מגוון סקריפטים שאפשר לכתוב על מנת ללוות את תהליך הפיתוח ולהטמיע כל מני תהליכים אוטומטיים. בצוות שבו אני עובד ב-HPE למשל, יש לנו git hooks שמבצעים static code analysis למערכת כולה בכל פעם שהמפתח עושה commit. במידה והמפתח חרג מהסטנדרטים שקבענו, ה-commit ייכשל. בכל פעם שהמפתח עושה push, כל ה-unit testing רצות ואם יש כשלון באחת מהן, ה-push לא מצליח כלל. כך אנחנו משיגים כמה מטרות – הרשונה: המתכנת לא צריך לזכור לעשות static code analysis או להריץ את הבדיקות האוטומטיות או משימות נוספות. זה נעשה באופן אוטומטי. המטרה השניה היא מניעה של נפילת ה-build על נושאים שטותיים.
יש כמה דרכים לעבוד עם git hooks, בואו ונדגים עם השיטה הפשוטה ביותר. ניצור איזשהו פרוייקט git באמצעות git init או שנכנס לפרויקט קיים. אם אנחנו משתמשים בחלונות, נוודא שאנחנו מסוגלים לראות קבצים נסתרים. נכנס לתיקיה הראשית של הפרויקט ונוכל לראות שיש תיקיה מסתורית שנקראת git. (שימו לב לנקודה בתחילת השם). הספריה הזו מאוד מעניינת ומכילה מספר תיקיות.
התיקיה המעניינת מבחינתנו היא תיקית hooks. שם יש את כל ה-hooks. אם נכנס לשם נראה שיש שם המון שמות עם סיומת sample. כל שם הוא בעצם hook. כשאני אומר hook אני מתכוון לפעולה מסוימת. למשל אם אני רואה pre-commit.sample אז שם ה-hook הוא pre-commit. לא צריך להיות גאון גדול כדי להסיק שמדובר בפעולות שמתרחשות לפני ה-commit. אם אני רואה pre-push.sample אפשר להניח שמדובר ב-hook ששמו הוא pre-push ומתרחש לפני ה-push.
מה שתוכנת ה-git עושה זה להכנס לתיקית ה-hooks ולהריץ כל קובץ לפי הסדר. אם יהיה שם קובץ pre-commit, כל מה שיש בתוכו ירוץ לפני שכל משתמש בפרויקט יעשה commit.
הכל יותר ברור עם דוגמה, לא? ניצור קובץ בשם pre-commit (ללא סיומת) או שנעיף את הסיומת sample מהקובץ pre-commit.sample. בפנים, אם נציץ, נראה שיש סקריפט שבודק אם המשתמש מכניס קבצים עם שמות לא עם ABC למשל בעברית). כך הסקריפט הזה נראה:
#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat < <\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against --' | wc -c) != 0 then cat < <\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against --
הסקריפט הזה כתוב בשפת BASH. לא צריך להבהל יותר מדי מהקוד הזה וגם לא צריך לנתח אותו. יש המון סקריפטים ב-BASH שעושים המון פעולות רוטיניות על השרת והסקריפט הזה לא יוצא מהכלל. מה שהוא עושה זה לבדוק אם יש אותיות שהן לא ASCII בשמות הקבצים ואם יש - להדפיס הודעת אזהרה ולהכשיל את ה-commit (כלומר להחזיר שגיאה, אם סקריפט כלשהו ב-hooks מחזיר שגיאה, הפעולה לא מתבצעת).
ברגע שאני מכניס את הסקריפט הזה לקובץ pre-commit (ללא סיומת, שימו לב!) כל קומיט שאעשה בפרויקט הספציפי הזה יריץ את הסקריפט. רוצים דוגמה? הנה. בדוגמה הבאה הוספתי קובץ בעברית וניסיתי לעשות commit:
נחמד, לא? הסקריפט הזה כאמור מופיע ב-pre-commit.sample אבל יש עוד סקריפטים נוספים ברשת.
ומה קורה אם אני לא רוצה בהזדמנות ספציפית לעבור דרך ה-hooks? אין שום בעיה, פשוט מוסיפים את הפלאג no-verify--
git commit --no-verify
בצוות שלנו אנחנו לא כותבים hookים ישירות בקובץ הפרוייקט אלא משתמשים ב-grunt על מנת לנהל את ה-git hooks. אבל על כך במאמר אחר.
3 תגובות
שאלה: נניח ואני עובד עם github או כל bare אחר, האם כל מי שיעשה pull/copy לרפו יקבל גם את קבצי הHOOK והאם הם באמת גם יפעלו בהרצה?
התשובה היא לא. כל שינוי שאתה עושה הוא אצלך לוקלית ולא ייכנס ל-git. מה כן אפשר לעשות? כמה דרכים:
1. להכניס את הסקריפטים כחלק מה-repo ולעשות symlink מתיקית ה-git. אל הסקריפטים האלו.
2. אם משתמשים ב-node.js אפשר להשתמש במודול grunt githooks או git-guppy לגאלפ על מנת לאכוף את זה. זה מה שאנחנו עושים בצוות שלנו.
מה לגבי אותו דבר בסביבת וינדוס? או שאני רוצה שה hookיהיה דם מעל לינוקס וגם מעל ווינדוס?