• בלוג
  • פעולות אוטומטיות על קבצים עם Git Filters

פעולות אוטומטיות על קבצים עם Git Filters

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

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

1. איך עובד git filter

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

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

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

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

$ git config --edit --global

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

[filter "indent"]
  clean = indent
  smudge = cat

השם indent שבמרכאות אחרי המילה filter הוא שם הפילטר, הפקודה שבאה אחרי המילה clean תופעל על כל קובץ לפני הכנסתו למאגר והפקודה שמופיעה אחרי המילה smudge תפעל אוטומטית על כל קובץ לפני העתקתו מהמאגר לתיקיית העבודה. במקרה שלנו אפשר גם למחוק את הגדרת ה smudge מאחר והפקודה cat משאירה את הקובץ באותו מצב.

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

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

<pattern> filter=<filter-name>

לדוגמא התוכן הבא בקובץ:

*.c filter=indent

יגרום להפעלת הפילטר indent על כל קובץ שהסיומת שלו היא .c.

2. יישור אוטומטי של קוד

נסכם ונראה את הפילטר בפעולה. תחילה וודאו שבקובץ ~/.gitconfig שלכם מופיע הבלוק:

[filter "indent"]
  clean = indent

לאחר מכן ודאו שהפקודה indent מותקנת אצלכם על המכונה באמצעות הפעלת:

$ echo a | indent

ותראו שקיבלתם את הפלט a.

לסיום צרו מאגר גיט ובו קובץ יחיד בשם .gitattributes. בקובץ זה יש לרשום את הטקסט:

*.c filter=indent

עכשיו אנחנו מוכנים - צרו קובץ חדש בשם demo.c ובו כתבו את התוכן הבא:

#include <stdio.h>

int main(int argc, char **argv)
{
printf("Hello world\n");
}

קל לראות שהקובץ לא מיושר ושהפקודה printf היתה צריכה להיכתב עם Tab בהתחלה. הוסיפו את הקובץ למאגר באמצעות:

$ git add demo.c

ובוא נראה מה נוצר לנו באינדקס. הפעילו:

$ git ls-files --stage
100644 42ee34ac1aabefd36e71cf57a6021b452752abca 0   .gitattributes
100644 95a35471748dd29737c4df5e8fccf7f572d35ab7 0   demo.c

ולאחר מכן הציגו את תוכן הקובץ demo.c ששמור באינדקס:

$ git cat-file blob 95a3547
#include <stdio.h>

int 
main(int argc, char **argv)
{
    printf("Hello world\n");
}

והצלחנו! הקובץ שנשמר במאגר עבר יישור באופן אוטומטי.

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

3. התאמה אישית של תיקיית עבודה

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

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

נתחיל עם הגדרת הפילטר. הוסיפו את הבלוק הבא לקובץ ~/.gitconfig שלכם:

[filter "hidecwd"]
    clean = sed "s@$PWD@...@g"
    smudge = sed "s@\\\\.\\\\.\\\\.@$PWD@g"

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

נעבור ל .gitattributes של הפרויקט ונוסיף שם את השורה:

*.ini filter=hidecwd

עכשיו הוסיפו את הקובץ config.ini הבא (החליפו את הנתיב לתיקיית הפרויקט אצלכם על המחשב):

name = ynon
home = /Users/ynonperek/work/courses/git/filters-demos/02-customise-path

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

$ git ls-files --stage
100644 636d6efaef3843155df6fbad17a77d001b2c6051 0   .gitattributes
100644 928bf7ec8f9a63e9f0b752474121f48a58d1d0b9 0   config.ini

$ git cat-file blob 928bf7ec
name = ynon
home = ...

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

4. כתיבת קוד HTML ושמירתו כ Markdown

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

נכתוב שני סקריפטים (כתבתי אותם ב node.js): אחד ממיר מ HTML ל Markdown והשני מבצע את ההמרה ההפוכה. זה הקוד של שניהם:

// html2md.js
#!/usr/local/bin/node
var readline = require('readline');
var TurndownService = require('turndown')

var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

var turndownService = new TurndownService()

rl.on('line', function(line) {
  var markdown = turndownService.turndown(line)
  console.log(markdown)
});
#!/usr/local/bin/node
var showdown  = require('showdown'),
    converter = new showdown.Converter();


var readline = require('readline');
var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

rl.on('line', function(line) {
  var html = converter.makeHtml(line);
  console.log(html);
});

שמרתי את הסקריפטים בקבצים ~/bin/html2md.js ו ~/bin/md2html.js בהתאמה. עכשיו נעבור ל ~/.gitconfig ונוסיף שם את הפילטר:

[filter "md2html"]
  clean = ~/bin/md2html.js
  smudge = ~/bin/html2md.js

בחזרה למאגר ושם נוסיף לקובץ .gitattributes את השורה:

*.html filter=md2html

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

5. סיכום ומידע נוסף

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

https://www.tocode.co.il/blog/2018-09-git-bisect

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

https://www.tocode.co.il/workshops/46

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

https://www.tocode.co.il/bundles/git