Chapters ▾ 2nd Edition

8.2 سفارشی‌سازی Git (Customizing Git) - ویژگی‌های گیت (Git Attributes)

ویژگی‌های گیت (Git Attributes)

برخی از این تنظیمات را می‌توان برای یک مسیر خاص نیز مشخص کرد، به طوری که گیت فقط آن تنظیمات را برای یک زیرشاخه یا زیرمجموعه‌ای از فایل‌ها اعمال کند. این تنظیمات خاص مسیر، ویژگی‌های گیت (Git attributes) نامیده می‌شوند و یا در فایل .gitattributes در یکی از دایرکتوری‌های شما (معمولاً ریشه پروژه) یا در فایل .git/info/attributes تنظیم می‌شوند، اگر نخواهید فایل ویژگی‌ها به همراه پروژه کامیت شود.

با استفاده از ویژگی‌ها، می‌توانید کارهایی مانند تعیین استراتژی‌های ادغام جداگانه برای فایل‌ها یا دایرکتوری‌های خاص پروژه، اطلاع دادن به گیت درباره نحوه مقایسه فایل‌های غیرمتنی، یا فیلتر کردن محتوا قبل از ثبت یا استخراج آن در گیت، انجام دهید. در این بخش، با برخی از ویژگی‌هایی که می‌توانید روی مسیرهای پروژه گیت خود تنظیم کنید آشنا می‌شوید و چند مثال عملی از کاربرد این قابلیت خواهید دید.

فایل‌های باینری (Binary Files)

یکی از ترفندهای جالبی که می‌توانید با ویژگی‌های گیت انجام دهید، اطلاع دادن به گیت درباره فایل‌هایی است که باینری هستند (در مواردی که گیت خودش نمی‌تواند تشخیص دهد) و دادن دستورالعمل‌های ویژه به گیت برای نحوه مدیریت این فایل‌ها. مثلاً برخی فایل‌های متنی ممکن است به صورت ماشینی ساخته شده باشند و قابلیت مقایسه (diff) نداشته باشند، در حالی که برخی فایل‌های باینری قابلیت مقایسه شدن دارند. شما خواهید آموخت چگونه به گیت بگویید کدام فایل‌ها باینری هستند و کدام نیستند.

شناسایی فایل‌های باینری (Identifying Binary Files)

برخی فایل‌ها شبیه فایل متنی به نظر می‌رسند، اما باید به عنوان داده باینری با آنها رفتار شود. برای مثال، پروژه‌های Xcode در macOS شامل فایلی با پسوند .pbxproj هستند که اساساً یک مجموعه داده JSON (فرمت داده جاوااسکریپت به صورت متن ساده) است که توسط محیط توسعه (IDE) روی دیسک نوشته شده و تنظیمات ساخت و غیره را ذخیره می‌کند. اگرچه این فایل از نظر فنی یک فایل متنی است (چون همه‌چیزش UTF-8 است)، اما نمی‌خواهید آن را به این صورت در نظر بگیرید چون اساساً یک پایگاه داده سبک است – اگر دو نفر آن را تغییر دهند، نمی‌توانید محتوا را با هم ادغام کنید و تفاوت‌ها معمولاً مفید نیستند. این فایل برای استفاده ماشین طراحی شده است. در واقع، می‌خواهید با آن مانند یک فایل باینری رفتار کنید.

برای اینکه گیت تمام فایل‌های pbxproj. را به عنوان داده باینری در نظر بگیرد، خط زیر را به فایل .gitattributes خود اضافه کنید:

*.pbxproj binary

اکنون گیت سعی نمی‌کند این فایل‌ها را تبدیل کند یا مشکلات CRLF را اصلاح کند؛ همچنین وقتی دستور git show یا git diff را روی پروژه اجرا می‌کنید، سعی نمی‌کند تفاوت‌ها را محاسبه یا نمایش دهد.

مقایسه فایل‌های باینری (Diffing Binary Files)

شما همچنین می‌توانید از قابلیت ویژگی‌های گیت برای مقایسه مؤثر فایل‌های باینری استفاده کنید. این کار را با گفتن به گیت درباره چگونگی تبدیل داده‌های باینری به فرمت متنی که بتوان به روش معمول diff مقایسه کرد، انجام می‌دهید.

ابتدا، این تکنیک را برای حل یکی از مشکلات آزاردهنده بشر به کار می‌برید: کنترل نسخه اسناد مایکروسافت ورد. اگر می‌خواهید اسناد ورد را کنترل نسخه کنید، می‌توانید آنها را در مخزن گیت قرار داده و هر از گاهی کامیت کنید؛ اما این چه فایده‌ای دارد؟ اگر به صورت معمولی git diff را اجرا کنید، فقط چیزی شبیه این می‌بینید:

$ git diff
diff --git a/chapter1.docx b/chapter1.docx
index 88839c4..4afcb7c 100644
Binary files a/chapter1.docx and b/chapter1.docx differ

شما نمی‌توانید مستقیماً دو نسخه را مقایسه کنید مگر اینکه آنها را استخراج کرده و دستی بررسی کنید، درست است؟ اما واقعیت این است که می‌توانید این کار را با استفاده از ویژگی‌های گیت به خوبی انجام دهید. خط زیر را در فایل .gitattributes خود قرار دهید:

*.docx diff=word

این به گیت می‌گوید هر فایلی که با این الگو (.docx) مطابقت دارد، هنگام مشاهده تفاوت‌هایی که شامل تغییرات است، باید از فیلتر “word” استفاده کند. فیلتر “word” چیست؟ باید آن را تنظیم کنید. در اینجا گیت را پیکربندی می‌کنید تا از برنامه docx2txt برای تبدیل اسناد ورد به فایل‌های متنی قابل خواندن استفاده کند، سپس آن فایل‌ها را به درستی مقایسه کند.

ابتدا باید docx2txt را نصب کنید؛ می‌توانید آن را از https://sourceforge.net/projects/docx2txt دانلود کنید. دستورالعمل‌های فایل INSTALL را دنبال کنید تا آن را در جایی قرار دهید که شل شما بتواند آن را پیدا کند. سپس، باید یک اسکریپت wrapper بنویسید تا خروجی را به فرمتی تبدیل کند که گیت انتظار دارد. یک فایل در مسیر PATH خود به نام docx2txt بسازید و این محتوا را در آن قرار دهید:

#!/bin/bash
docx2txt.pl "$1" -

فراموش نکنید که به آن فایل دستور chmod a+x بدهید. در نهایت، می‌توانید گیت را پیکربندی کنید که از این اسکریپت استفاده کند:

$ git config diff.word.textconv docx2txt
 حال گیت می‌داند که اگر بخواهد تفاوت (diff) بین دو عکس‌برداری (snapshot) بگیرد و هر یک از فایل‌ها پسوند `.docx` داشته باشند، باید آن‌ها را از طریق فیلتر "`word`" که به برنامه `docx2txt` ارجاع داده شده است، عبور دهد.
این کار عملاً نسخه‌های متنی خوبی از فایل‌های ورد شما ایجاد می‌کند قبل از اینکه بخواهد تفاوت آن‌ها را بگیرد.

مثالی این‌گونه است: فصل اول این کتاب به فرمت ورد تبدیل و در مخزن گیت ثبت شده است. سپس پاراگراف جدیدی اضافه شده است. این چیزی است که git diff نشان می‌دهد:

$ git diff
diff --git a/chapter1.docx b/chapter1.docx
index 0b013ca..ba25db5 100644
--- a/chapter1.docx
+++ b/chapter1.docx
@@ -2,6 +2,7 @@
 This chapter will be about getting started with Git. We will begin at the beginning by explaining some background on version control tools, then move on to how to get Git running on your system and finally how to get it setup to start working with. At the end of this chapter you should understand why Git is around, why you should use it and you should be all setup to do so.
 1.1. About Version Control
 What is "version control", and why should you care? Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. For the examples in this book you will use software source code as the files being version controlled, though in reality you can do this with nearly any type of file on a computer.
+Testing: 1, 2, 3.
 If you are a graphic or web designer and want to keep every version of an image or layout (which you would most certainly want to), a Version Control System (VCS) is a very wise thing to use. It allows you to revert files back to a previous state, revert the entire project back to a previous state, compare changes over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also generally means that if you screw things up or lose files, you can easily recover. In addition, you get all this for very little overhead.
 1.1.1. Local Version Control Systems
 Many people's version-control method of choice is to copy files into another directory (perhaps a time-stamped directory, if they're clever). This approach is very common because it is so simple, but it is also incredibly error prone. It is easy to forget which directory you're in and accidentally write to the wrong file or copy over files you don't mean to.

گیت با موفقیت و به‌طور خلاصه به ما می‌گوید که رشته “Testing: 1, 2, 3.” را اضافه کرده‌ایم که درست است. این کامل نیست – تغییرات قالب‌بندی اینجا نمایش داده نمی‌شوند – اما قطعاً کار می‌کند.

مشکل جالب دیگری که می‌توانید به این روش حل کنید، گرفتن تفاوت بین فایل‌های تصویری است. یکی از راه‌ها این است که فایل‌های تصویری را از طریق فیلتری عبور دهید که اطلاعات EXIF آن‌ها را استخراج کند – متادیتایی که در اکثر فرمت‌های تصویری ثبت می‌شود. اگر برنامه exiftool را دانلود و نصب کنید، می‌توانید تصاویر خود را به متنی درباره متادیتا تبدیل کنید، پس حداقل تفاوت به شما نمای متنی از هر تغییر ایجاد شده را نشان می‌دهد. خط زیر را در فایل .gitattributes خود قرار دهید:

*.png diff=exif

Configure Git to use this tool:

$ git config diff.exif.textconv exiftool

اگر تصویری را در پروژه خود جایگزین کنید و git diff را اجرا کنید، چیزی شبیه به این خواهید دید:

diff --git a/image.png b/image.png
index 88839c4..4afcb7c 100644
--- a/image.png
+++ b/image.png
@@ -1,12 +1,12 @@
 ExifTool Version Number         : 7.74
-File Size                       : 70 kB
-File Modification Date/Time     : 2009:04:21 07:02:45-07:00
+File Size                       : 94 kB
+File Modification Date/Time     : 2009:04:21 07:02:43-07:00
 File Type                       : PNG
 MIME Type                       : image/png
-Image Width                     : 1058
-Image Height                    : 889
+Image Width                     : 1056
+Image Height                    : 827
 Bit Depth                       : 8
 Color Type                      : RGB with Alpha

شما به‌راحتی می‌توانید ببینید که اندازه فایل و ابعاد تصویر هر دو تغییر کرده‌اند.

گسترش کلمات کلیدی (Keyword Expansion)

گسترش کلیدواژه به سبک SVN یا CVS اغلب توسط توسعه‌دهندگانی که به این سیستم‌ها عادت دارند درخواست می‌شود. مشکل اصلی در گیت این است که نمی‌توانید اطلاعات مربوط به کامیت را بعد از کامیت کردن فایل تغییر دهید، چون گیت ابتدا چکسام فایل را محاسبه می‌کند. با این حال، می‌توانید متن را هنگام بیرون کشیدن (checkout) فایل وارد کنید و قبل از اضافه کردن آن به کامیت، دوباره حذفش کنید. ویژگی attributes در گیت دو راه برای انجام این کار به شما می‌دهد.

اول اینکه می‌توانید چکسام SHA-1 یک بلاک داده (blob) را به‌صورت خودکار داخل فیلد $Id$ در فایل تزریق کنید. اگر این ویژگی را روی یک یا چند فایل تنظیم کنید، دفعه بعد که آن شاخه را checkout کنید، گیت این فیلد را با SHA-1 بلاک جایگزین می‌کند. مهم است توجه داشته باشید که این SHA-1 مربوط به کامیت نیست، بلکه مربوط به خود بلاک است. خط زیر را در فایل .gitattributes خود قرار دهید:

*.txt ident

یک $Id$ اضافه کنید به رفرنس تست فایلتان:

$ echo '$Id$' > test.txt

دفعه بعد که این فایل را checkout کنید، گیت SHA-1 بلاک را تزریق می‌کند:

$ rm test.txt
$ git checkout -- test.txt
$ cat test.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $

اما این نتیجه کاربرد محدودی دارد. اگر در CVS یا Subversion از جایگزینی کلیدواژه استفاده کرده‌اید، می‌توانید تاریخ را نیز اضافه کنید – SHA-1 چندان مفید نیست چون کاملاً تصادفی است و نمی‌توانید فقط با نگاه کردن بفهمید کدام SHA-1 قدیمی‌تر یا جدیدتر است.

مشخص شده است که می‌توانید فیلترهای خودتان را برای انجام جایگزینی‌ها روی فایل‌ها هنگام commit/checkout بنویسید. این‌ها به عنوان فیلترهای “clean” و “smudge” شناخته می‌شوند. در فایل .gitattributes، می‌توانید فیلتر را برای مسیرهای خاص تنظیم کنید و سپس اسکریپت‌هایی راه‌اندازی کنید که فایل‌ها را درست قبل از checkout (“smudge”، نگاه کنید به The “smudge” filter is run on checkout) و درست قبل از stage شدن (“clean”، نگاه کنید به The “clean” filter is run when files are staged) پردازش می‌کنند. این فیلترها می‌توانند کارهای جالب و متنوعی انجام دهند.

The “smudge” filter is run on checkout
نمودار 169. The “smudge” filter is run on checkout
The “clean” filter is run when files are staged
نمودار 170. The “clean” filter is run when files are staged

پیام کامیت اصلی این ویژگی، مثالی ساده از اجرای تمام کدهای منبع C شما از طریق برنامه indent قبل از commit را ارائه می‌دهد. می‌توانید این را با تنظیم ویژگی فیلتر در فایل .gitattributes خود برای فیلتر کردن فایل‌های *.c با فیلتر “indent” انجام دهید:

*.c filter=indent

سپس به گیت بگویید که فیلتر “indent” هنگام smudge و clean چه کاری انجام می‌دهد:

$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge cat

در این حالت، وقتی فایل‌هایی که با *.c مطابقت دارند را commit می‌کنید، گیت آن‌ها را قبل از stage شدن از طریق برنامه indent عبور می‌دهد و سپس قبل از اینکه دوباره آن‌ها را روی دیسک بیرون بکشد، از طریق برنامه cat عبور می‌دهد. برنامه cat عملاً کاری انجام نمی‌دهد: همان داده ورودی را بدون تغییر خروجی می‌دهد. این ترکیب عملاً تمام فایل‌های منبع C را قبل از commit از طریق indent فیلتر می‌کند.

مثال جالب دیگر، گسترش کلیدواژه $Date$ به سبک RCS است. برای انجام درست این کار، به اسکریپت کوچکی نیاز دارید که نام فایل را بگیرد، آخرین تاریخ کامیت این پروژه را پیدا کند و تاریخ را در فایل وارد کند. در اینجا یک اسکریپت کوچک روبی است که این کار را انجام می‌دهد:

#! /usr/bin/env ruby
data = STDIN.read
last_date = `git log --pretty=format:"%ad" -1`
puts data.gsub('$Date$', '$Date: ' + last_date.to_s + '$')
 تمام کاری که این اسکریپت انجام می‌دهد این است که جدیدترین تاریخ کامیت را از دستور `git log` می‌گیرد، آن را در هر رشته‌ای از نوع `$Date$` که در ورودی استاندارد می‌بیند جایگزین می‌کند و نتیجه را چاپ می‌کند – این کار باید به سادگی با هر زبانی که به آن مسلط هستید قابل انجام باشد.
می‌توانید این فایل را با نام `expand_date` ذخیره کرده و در مسیر دستیابی سیستم خود قرار دهید.
حالا باید یک فیلتر در Git تعریف کنید (مثلاً آن را `dater` بنامید) و به Git بگویید که هنگام چک‌اوت فایل‌ها از فیلتر `expand_date` برای جایگزینی استفاده کند.
برای پاکسازی این تغییرات هنگام کامیت از یک عبارت Perl استفاده خواهید کرد:
$ git config filter.dater.smudge expand_date
$ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'

این قطعه کد Perl هر چیزی را که در رشته $Date$ می‌بیند پاک می‌کند تا به حالت اولیه برگردد. حال که فیلتر شما آماده است، می‌توانید آن را با تنظیم یک ویژگی Git برای آن فایل که فیلتر جدید را فعال می‌کند و ایجاد فایلی با کلیدواژه $Date$، آزمایش کنید:

date*.txt filter=dater
$ echo '# $Date$' > date_test.txt

اگر این تغییرات را کامیت کنید و دوباره فایل را چک‌اوت کنید، می‌بینید که کلیدواژه به درستی جایگزین شده است:

$ git add date_test.txt .gitattributes
$ git commit -m "Test date expansion in Git"
$ rm date_test.txt
$ git checkout date_test.txt
$ cat date_test.txt
# $Date: Tue Apr 21 07:26:52 2009 -0700$

می‌توانید ببینید این روش چقدر می‌تواند در برنامه‌های سفارشی قدرتمند باشد. اما باید مراقب باشید، چون فایل .gitattributes همراه پروژه کامیت و منتقل می‌شود، اما درایور (در اینجا dater) اینطور نیست، بنابراین در همه جا کار نخواهد کرد. وقتی این فیلترها را طراحی می‌کنید، باید طوری باشند که در صورت شکست، به آرامی عمل کنند و پروژه همچنان به درستی کار کند.

خروجی گرفتن از مخزن شما (Exporting Your Repository)

داده‌های ویژگی Git همچنین به شما امکان می‌دهد هنگام استخراج آرشیو پروژه خود، کارهای جالبی انجام دهید.

نادیده‌گرفتن هنگام خروجی گرفتن (export-ignore)

شما می‌توانید به گیت بگویید که هنگام ساخت آرشیو، برخی فایل‌ها یا پوشه‌ها را صادر نکند. اگر زیرپوشه یا فایلی وجود دارد که نمی‌خواهید در فایل آرشیو قرار بگیرد ولی می‌خواهید در پروژه‌تان ثبت شود، می‌توانید این فایل‌ها را با استفاده از صفت export-ignore مشخص کنید.

برای مثال، فرض کنید چند فایل تست در زیرپوشه‌ای به نام test/ دارید و منطقی نیست که آن‌ها را در خروجی tarball پروژه‌تان وارد کنید. می‌توانید خط زیر را به فایل ویژگی‌های گیت خود (Git attributes file) اضافه کنید:

test/ export-ignore

حالا وقتی دستور git archive را برای ساخت tarball پروژه اجرا کنید، آن پوشه در آرشیو قرار نخواهد گرفت.

جایگزینی هنگام خروجی گرفتن (export-subst)

وقتی فایل‌ها را برای استقرار (deployment) صادر می‌کنید، می‌توانید قالب‌بندی و جایگزینی کلمات کلیدی (keyword-expansion) دستور git log را روی بخش‌های انتخاب‌شده‌ای از فایل‌ها که با صفت export-subst علامت‌گذاری شده‌اند، اعمال کنید.

برای مثال، اگر می‌خواهید فایلی به نام LAST_COMMIT در پروژه خود داشته باشید و اطلاعات متادیتای آخرین کامیت به‌صورت خودکار هنگام اجرای git archive در آن درج شود، می‌توانید فایل‌های .gitattributes و LAST_COMMIT خود را به این صورت تنظیم کنید:

LAST_COMMIT export-subst
$ echo 'Last commit date: $Format:%cd by %aN$' > LAST_COMMIT
$ git add LAST_COMMIT .gitattributes
$ git commit -am 'adding LAST_COMMIT file for archives'

وقتی دستور git archive را اجرا کنید، محتوای فایل آرشیو شده به این صورت خواهد بود:

$ git archive HEAD | tar xCf ../deployment-testing -
$ cat ../deployment-testing/LAST_COMMIT
Last commit date: Tue Apr 21 08:38:48 2009 -0700 by Scott Chacon

جایگزینی‌ها می‌توانند شامل پیام کامیت و هر گونه git notes باشند، و دستور git log می‌تواند به صورت ساده متن را به خط بعدی منتقل کند.

$ echo '$Format:Last commit: %h by %aN at %cd%n%+w(76,6,9)%B$' > LAST_COMMIT
$ git commit -am 'export-subst uses git log'\''s custom formatter

git archive uses git log'\''s `pretty=format:` processor
directly, and strips the surrounding `$Format:` and `$`
markup from the output.
'
$ git archive @ | tar xfO - LAST_COMMIT
Last commit: 312ccc8 by Jim Hill at Fri May 8 09:14:04 2015 -0700
       export-subst uses git log's custom formatter

         git archive uses git log's `pretty=format:` processor directly, and
         strips the surrounding `$Format:` and `$` markup from the output.

آرشیو حاصل برای کارهای استقرار مناسب است، اما مانند هر آرشیو صادر شده‌ای برای توسعه‌های بعدی مناسب نیست.

استراتژی‌های ادغام (Merge Strategies)

شما همچنین می‌توانید از ویژگی‌های Git استفاده کنید تا به Git بگویید برای فایل‌های خاص در پروژه‌تان از استراتژی‌های ادغام متفاوتی استفاده کند. یک گزینه بسیار مفید این است که به Git بگویید وقتی فایل‌های خاصی دارای تعارض هستند، تلاش نکند آن‌ها را ادغام کند، بلکه نسخه شما را در ادغام بر نسخه دیگران ارجح بداند.

این کار زمانی مفید است که یک شاخه در پروژه شما جدا شده یا تخصصی شده باشد، اما شما بخواهید تغییرات آن شاخه را مجدداً ادغام کنید و بخواهید برخی فایل‌ها را نادیده بگیرید. فرض کنید یک فایل تنظیمات پایگاه داده به نام database.xml در دو شاخه متفاوت است و شما می‌خواهید شاخه دیگر را ادغام کنید بدون اینکه فایل پایگاه داده دچار مشکل شود. می‌توانید یک ویژگی به این شکل تنظیم کنید:

database.xml merge=ours

سپس یک استراتژی ادغام ساختگی به نام ours تعریف کنید:

$ git config --global merge.ours.driver true

اگر شاخه دیگر را ادغام کنید، به جای اینکه با فایل database.xml تعارض ادغام داشته باشید، چیزی شبیه به این مشاهده خواهید کرد:

$ git merge topic
Auto-merging database.xml
Merge made by recursive.

در این حالت، فایل database.xml به همان نسخه‌ای که در ابتدا داشتید باقی می‌ماند.

scroll-to-top