-
1. شروع به کار (getting started)
-
2. مقدمات گیت (git basics chapter)
- 2.1 گرفتن یک مخزن گیت (Getting a Git Repository)
- 2.2 ثبت تغییرات در مخزن (Recording Changes to the Repository)
- 2.3 مشاهده تاریخچه کامیتها (Viewing the Commit History)
- 2.4 بازگرداندن تغییرات (Undoing Things)
- 2.5 کار کردن با ریموت ها (Working with Remotes)
- 2.6 تگ کردن (Tagging)
- 2.7 نام مستعار گیت (Git Aliases)
- 2.8 خلاصه (summary)
-
3. انشعابگیری در گیت (Git Branching)
-
4. گیت روی سرور (Git on the server)
- 4.1 پروتکلها (The Protocols)
- 4.2 راهاندازی گیت روی یک سرور (Getting Git on a Server)
- 4.3 ایجاد کلید عمومی SSH شما (Generating Your SSH Public Key)
- 4.4 نصب و راهاندازی سرور (Setting up server)
- 4.5 سرویسدهنده گیت (Git Daemon)
- 4.6 HTTP هوشمند (Smart HTTP)
- 4.7 گیتوب (GitWeb)
- 4.8 گیتلب (GitLab)
- 4.9 گزینههای میزبانی شخص ثالث (Third Party Hosted Options)
- 4.10 خلاصه (Summary)
-
5. گیت توزیعشده (Distributed git)
-
6. GitHub (گیت هاب)
-
7. ابزارهای گیت (Git Tools)
- 7.1 انتخاب بازبینی (Revision Selection)
- 7.2 مرحلهبندی تعاملی (Interactive Staging)
- 7.3 ذخیره موقت و پاکسازی (Stashing and Cleaning)
- 7.4 Signing Your Work (امضای کارهای شما)
- 7.5 جستجو (Searching)
- 7.6 بازنویسی تاریخچه (Rewriting History)
- 7.7 بازنشانی به زبان ساده (Reset Demystified)
- 7.8 ادغام پیشرفته (Advanced Merging)
- 7.9 بازاستفاده خودکار از حل تضادها (Rerere)
- 7.10 اشکالزدایی با گیت (Debugging with Git)
- 7.11 سابماژول ها (Submodules)
- 7.12 بستهبندی (Bundling)
- 7.13 جایگزینی (Replace)
- 7.14 ذخیرهسازی اطلاعات ورود (Credential Storage)
- 7.15 خلاصه (Summary)
-
8. سفارشیسازی Git (Customizing Git)
-
9. گیت و سیستمهای دیگر (Git and Other Systems)
-
10. (Git Internals)
- 10.1 ابزارها و دستورات سطح پایین (Plumbing and Porcelain)
- 10.2 اشیا گیت (Git Objects)
- 10.3 مراجع گیت (Git References)
- 10.4 فایلهای بسته (Packfiles)
- 10.5 نگاشت (The Refspec)
- 10.6 پروتکلهای انتقال (Transfer Protocols)
- 10.7 نگهداری و بازیابی دادهها (Maintenance and Data Recovery)
- 10.8 متغیرهای محیطی (Environment Variables)
- 10.9 (Summary)
-
A1. پیوست A: گیت در محیطهای دیگر (Git in Other Environments)
- A1.1 رابط های گرافیکی (Graphical Interfaces)
- A1.2 Git در ویژوال استودیو (Git in Visual Studio)
- A1.3 Git در Visual Studio Code (Git in Visual Studio Code)
- A1.4 Git در IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine (Git in IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine)
- A1.5 Git در Sublime Text (Git in Sublime Text)
- A1.6 گیت در بش (Git in Bash)
- A1.7 Git در Zsh (Git in Zsh)
- A1.8 Git در PowerShell (Git in PowerShell)
- A1.9 خلاصه (Summary)
-
A2. پیوست B: گنجاندن گیت در برنامههای شما (Embedding Git in your Applications)
-
A3. پیوست C: دستورات گیت (Git Commands)
- A3.1 تنظیم و پیکربندی (Setup and Config)
- A3.2 گرفتن و ایجاد پروژهها (Getting and Creating Projects)
- A3.3 نمونهبرداری پایهای (Basic Snapshotting)
- A3.4 انشعابگیری و ادغام (Branching and Merging)
- A3.5 بهاشتراکگذاری و بهروزرسانی پروژهها (Sharing and Updating Projects)
- A3.6 بازرسی و مقایسه (Inspection and Comparison)
- A3.7 عیبیابی (Debugging)
- A3.8 اعمال تغییرات به صورت پچ (Patching)
- A3.9 ایمیل (Email)
- A3.10 سیستمهای خارجی (External Systems)
- A3.11 مدیریت (Administration)
- A3.12 دستورات سطح پایین گیت (Plumbing Commands)
10.7 (Git Internals) - نگهداری و بازیابی دادهها (Maintenance and Data Recovery)
نگهداری و بازیابی دادهها (Maintenance and Data Recovery)
گاهی اوقات لازم است کمی پاکسازی انجام دهید — مثلاً مخزنی را فشردهتر کنید، یک مخزن وارد شده را مرتب کنید یا کار از دسترفتهای را بازیابی کنید. این بخش به برخی از این سناریوها میپردازد.
نگهداری (Maintenance)
گاهی Git بهطور خودکار فرمانی به نام "auto gc" را اجرا میکند. در بیشتر مواقع این فرمان کاری انجام نمیدهد. با این حال، اگر شیءهای جدا (شیءهایی که در یک packfile نیستند) زیاد شوند یا تعداد packfileها بیش از حد شود، Git یک فرمان کامل git gc
را اجرا میکند. "gc" مخفف garbage collect است و این فرمان چند کار انجام میدهد: همهٔ شیءهای جدا را جمعآوری کرده و در packfileها قرار میدهد، packfileها را در یک packfile بزرگتر تجمیع میکند، و اشیائی را که از هیچ commitی قابل دسترسی نیستند و چند ماهه شدهاند حذف میکند.
میتوانید بهصورت دستی auto gc
را اینگونه اجرا کنید:
$ git gc --auto
باز هم، معمولاً این کار فایدهای ندارد. برای اینکه Git واقعا فرمان gc
را اجرا کند باید در حدود ۷۰۰۰ شیء جدا یا بیشتر یا بیش از ۵۰ packfile داشته باشید. میتوانید این محدودیتها را بهترتیب با تنظیمات پیکربندی gc.auto
و gc.autopacklimit
تغییر دهید.
کار دیگری که gc
انجام میدهد این است که مراجع شما را در یک فایل واحد بستهبندی میکند. فرض کنید مخزن شما شامل شاخهها و برچسبهای زیر باشد:
$ find .git/refs -type f
.git/refs/heads/experiment
.git/refs/heads/master
.git/refs/tags/v1.0
.git/refs/tags/v1.1
اگر git gc
را اجرا کنید، دیگر این فایلها را در دایرکتوری refs
نخواهید دید. Git برای کارایی آنها را به فایلی بهنام .git/packed-refs
منتقل میکند که شبیه به این است:
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9
اگر یک مرجع را بهروز کنید، Git این فایل را ویرایش نمیکند بلکه بهجای آن یک فایل جدید در refs/heads
مینویسد. برای بهدست آوردن SHA-1 مناسب یک مرجع، Git ابتدا آن مرجع را در دایرکتوری refs
جستوجو میکند و سپس در صورت عدم یافتن، بهعنوان پشتیبان فایل packed-refs
را بررسی میکند. پس اگر نتوانستید مرجعی را در دایرکتوری refs
پیدا کنید، احتمالاً در فایل packed-refs
شما قرار دارد.
به خط آخر فایل که با ^
شروع میشود دقت کنید. این یعنی تگ بالای آن یک annotated tag است و آن خط اشاره به commitای دارد که آن annotated tag به آن اشاره میکند.
بازیابی دادهها (Data Recovery)
در مقطعی از مسیر یادگیری Git ممکن است بهطور تصادفی یک commit را از دست بدهید. معمولاً این اتفاق وقتی رخ میدهد که یک شاخه را با force حذف میکنید در حالی که روی آن کار داشتهاید و بعداً متوجه میشوید که به آن شاخه نیاز داشتهاید؛ یا وقتی که یک شاخه را با hard reset به عقب برمیگردانید و بهاینترتیب commitهایی را رها میکنید که از بعضیِ آنها چیزی لازم داشتید. اگر چنین وضعیتی پیش بیاید، چگونه میتوانید commitهای از دست رفته را بازیابی کنید؟
در اینجا یک مثال نشان داده شده که شاخه master را در مخزن تست شما به یک commit قدیمیتر hard-reset میکند و سپس commitهای از دست رفته را بازیابی مینماید. ابتدا بیایید مروری بر وضعیت فعلی مخزن داشته باشیم:
$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b Modify repo.rb a bit
484a59275031909e19aadb7c92262719cfcdf19a Create repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit
اکنون شاخه master را به commit میانی برگردانید:
$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef Third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit
بهاینترتیب عملاً دو commit بالایی را از دست دادهاید — هیچ شاخهای وجود ندارد که آن commitها از طریق آن قابل دسترسی باشند. شما باید آخرین شناسه SHA-1 commit را پیدا کنید و سپس شاخهای بسازید که به آن اشاره کند. نکته کار پیدا کردن آخرین SHA-1 است — قطعاً آن را حفظ نکردهاید، درست است؟
اغلب سریعترین راه استفاده از ابزاری به نام git reflog است. زمانی که کار میکنید، Git بیصدا هر بار که HEAD را تغییر میدهید، وضعیت آن را ثبت میکند. هر بار که commit میزنید یا شاخه را تغییر میدهید، reflog بهروزرسانی میشود. دستور git update-ref نیز reflog را بهروزرسانی میکند، که این خود دلیل دیگری است برای استفاده از reflog بهجای نوشتن مستقیم مقدار SHA-1 در فایلهای ref که در مراجع گیت (Git References) مورد بحث قرار گرفت. هر زمان میخواهید میتوانید با اجرای git reflog ببینید قبلاً کجا بودهاید:
$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: Modify repo.rb a bit
484a592 HEAD@{2}: commit: Create repo.rb
در اینجا میتوانیم دو commit را که قبلاً چکاوت شدهاند ببینیم، اما اطلاعات زیادی نمایش داده نشده است. برای دیدن همان اطلاعات به شکلی بسیار مفیدتر، میتوانید git log -g را اجرا کنید که خروجی معمولی log را برای reflog به شما میدهد.
$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:22:37 2009 -0700
Third commit
commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
Modify repo.rb a bit
به نظر میرسد commit پایینی همانی است که از دست دادهاید، پس میتوانید با ساختن یک شاخه جدید روی آن، آن را بازیابی کنید. برای مثال، میتوانید شاخهای به نام recover-branch را از آن commit (ab1afef) شروع کنید:
$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b Modify repo.rb a bit
484a59275031909e19aadb7c92262719cfcdf19a Create repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit
خوب — حالا یک شاخه به نام `recover-branch` داری که همان جایی است که شاخهٔ `master` قبلاً قرار داشت و دو کمیت اول دوباره قابل دسترسی شدهاند. حالا فرض کن به دلایلی آن داده در reflog نبوده — میتوانی این را با حذف `recover-branch` و پاک کردن reflog شبیهسازی کنی. در این صورت دو کمیت اول دیگر توسط هیچ چیزی قابل دسترسی نیستند:
$ git branch -D recover-branch
$ rm -Rf .git/logs/
از آنجا که دادههای reflog در پوشهٔ .git/logs/
نگهداری میشوند، عملاً reflog ندارید.
در این مرحله چگونه میتوانی آن کمیت را بازیابی کنی؟
یکی از روشها استفاده از ابزار git fsck
است که دیتابیس را از نظر یکپارچگی بررسی میکند.
اگر آن را با گزینهٔ --full
اجرا کنی، همهٔ اشیائی را نشان میدهد که توسط هیچ شیء دیگری اشاره نشدهاند:
$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18/18), done.
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293
در این مثال میتوانی کمیت گمشدهات را بعد از عبارت “dangling commit” ببینی. میتوانی آن را همانطور بازیابی کنی: با اضافه کردن یک شاخه که به آن SHA-1 اشاره کند.
حذف اشیاء (Removing Objects)
دربارهٔ گیت چیزهای خوب زیادی هست، اما یک ویژگی که میتواند مشکلساز شود این است که یک git clone
تمام تاریخچهٔ پروژه را دانلود میکند، از جمله هر نسخه از هر فایل.
این مسئله وقتی کد منبع خالص است مشکل چندانی ایجاد نمیکند، زیرا گیت در فشردهسازی این دادهها بسیار بهینه است.
با این حال، اگر در هر نقطهای از تاریخچهٔ پروژه کسی یک فایل بسیار حجیم اضافه کرده باشد، تمام کلونهای بعدی برای همیشه مجبور به دانلود آن فایل بزرگ خواهند بود، حتی اگر در کمیت بعدی از پروژه حذف شده باشد.
از آنجا که آن فایل از تاریخچه قابل دسترسی است، همیشه آنجا خواهد ماند.
این میتواند هنگام تبدیل مخازن Subversion یا Perforce به گیت مشکل بزرگی ایجاد کند. چون در آن سیستمها تمام تاریخچه را دانلود نمیکنید، این نوع اضافهکردن تبعات کمی دارد. اگر از یک سیستم دیگر ایمپورت کردهای یا به هر دلیلی متوجه شدهای که مخزنات بسیار بزرگتر از حد انتظار است، اینجا روش یافتن و حذف اشیاء بزرگ را میبینی.
هشدار: این تکنیک برای commit history مخرب است. این روش باعث میشود تمام commit objectها از اولین treeای که باید برای حذف یک فایل بزرگ تغییر داده شود، بازنویسی شوند. اگر این کار را بلافاصله بعد از import انجام دهید، قبل از اینکه کسی شروع به base کردن کار خود روی commit کند، مشکلی نیست – در غیر این صورت، باید به تمام contributors اطلاع دهید که آنها باید کارشان را روی commits جدید شما rebase کنند.
برای نشان دادن این موضوع، شما یک فایل بزرگ را به test repository خود اضافه میکنید، در commit بعدی آن را حذف میکنید، آن را پیدا میکنید و سپس به طور دائمی از repository حذف میکنید.
ابتدا، یک object بزرگ به history خود اضافه کنید:
$ curl -L https://www.kernel.org/pub/software/scm/git/git-2.1.0.tar.gz > git.tgz
$ git add git.tgz
$ git commit -m 'Add git tarball'
[master 7b30847] Add git tarball
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 git.tgz
اوه – شما نمیخواستید یک tarball حجیم به پروژه اضافه کنید. بهتر است از شر آن خلاص شوید:
$ git rm git.tgz
rm 'git.tgz'
$ git commit -m 'Oops - remove large tarball'
[master dadf725] Oops - remove large tarball
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 git.tgz
حالا، روی دیتابیس خود gc
اجرا کنید و ببینید چه مقدار فضا استفاده کردهاید:
$ git gc
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)
میتوانید دستور count-objects
را اجرا کنید تا سریع ببینید چه مقدار فضا در حال استفاده است:
$ git count-objects -v
count: 7
size: 32
in-pack: 17
packs: 1
size-pack: 4868
prune-packable: 0
garbage: 0
size-garbage: 0
ورودی size-pack
اندازهی packfileهای شما را برحسب kilobyte نشان میدهد، پس شما تقریباً ۵MB استفاده کردهاید.
قبل از آخرین commit، شما چیزی نزدیک به 2K استفاده میکردید – مشخص است که حذف فایل از commit قبلی آن را از history حذف نکرد.
هر بار که کسی این repository را clone کند، باید کل ۵MB را clone کند فقط برای گرفتن این پروژهی کوچک، چون شما به اشتباه یک فایل بزرگ اضافه کردهاید.
بیایید از شرش خلاص شویم.
ابتدا باید آن را پیدا کنید.
در این مورد، شما میدانید آن فایل چیست.
اما فرض کنید نمیدانستید؛ چطور میتوانستید تشخیص دهید چه فایل یا فایلهایی اینقدر فضا اشغال کردهاند؟
اگر git gc
اجرا کنید، همهی objectها داخل یک packfile قرار میگیرند؛ میتوانید objectهای بزرگ را با اجرای دستور plumbing دیگری به نام git verify-pack
شناسایی کنید و روی فیلد سوم خروجی (یعنی اندازه فایل) sort کنید.
همچنین میتوانید خروجی را با دستور tail
فیلتر کنید چون فقط به چند فایل بزرگ آخر علاقه دارید:
$ git verify-pack -v .git/objects/pack/pack-29…69.idx \
| sort -k 3 -n \
| tail -3
dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 22044 5792 4977696
82c99a3e86bb1267b236a4b6eff7868d97489af1 blob 4975916 4976258 1438
Object بزرگ در پایین است: ۵MB.
برای اینکه بفهمید این فایل چیست، از دستور rev-list
استفاده میکنید، که قبلاً در بخش اجبار به قالب خاص پیام کامیت (Enforcing a Specific Commit-Message Format) مختصری با آن آشنا شدید.
اگر گزینه --objects
را به rev-list
بدهید، تمام commit SHA-1ها و همچنین blob SHA-1ها را همراه با مسیر فایلهای مربوطه لیست میکند.
میتوانید از این برای پیدا کردن نام blob خود استفاده کنید:
$ git rev-list --objects --all | grep 82c99a3
82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz
حالا، باید این فایل را از همه treeهای گذشته حذف کنید. بهراحتی میتوانید ببینید چه commitهایی این فایل را تغییر دادهاند:
$ git log --oneline --branches -- git.tgz
dadf725 Oops - remove large tarball
7b30847 Add git tarball
شما باید تمام commitهایی که downstream از 7b30847
هستند را بازنویسی کنید تا این فایل به طور کامل از Git history حذف شود.
برای این کار، از filter-branch
استفاده میکنید که قبلاً در بخش بازنویسی تاریخچه (Rewriting History) دیدید:
$ git filter-branch --index-filter \
'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz'
Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2)
Ref 'refs/heads/master' was rewritten
گزینهی --index-filter
شبیه به --tree-filter
است که در بخش بازنویسی تاریخچه (Rewriting History) استفاده شد، با این تفاوت که بهجای اجرای دستوری که فایلهای checkoutشده روی disk را تغییر میدهد، هر بار staging area یا index را تغییر میدهید.
بهجای اینکه یک فایل خاص را با چیزی مثل rm file
حذف کنید، باید آن را با git rm --cached
حذف کنید – باید از index حذف شود، نه از disk.
دلیل این روش، سرعت است – چون Git مجبور نیست هر نسخه را روی disk checkout کند، فرآیند بسیار سریعتر خواهد بود.
میتوانید همین کار را با --tree-filter
هم انجام دهید اگر بخواهید.
گزینهی --ignore-unmatch
برای git rm
باعث میشود اگر الگوی مورد نظر شما وجود نداشت، خطا ندهد.
در نهایت، به filter-branch
میگویید که history را فقط از commit 7b30847
به بعد بازنویسی کند، چون میدانید مشکل از آنجا شروع شده.
در غیر این صورت، از ابتدا شروع میکند و بیدلیل زمان بیشتری میگیرد.
History شما دیگر حاوی reference به آن فایل نیست.
اما reflog شما و مجموعه جدیدی از refs که Git هنگام اجرای filter-branch
در .git/refs/original
ایجاد کرده همچنان آن را دارند، بنابراین باید آنها را حذف کنید و سپس دیتابیس را دوباره repack کنید.
باید هر چیزی که به آن commitهای قدیمی اشاره دارد را پاک کنید قبل از اینکه repack انجام دهید:
$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 1), reused 12 (delta 0)
ببینیم چقدر فضا ذخیره کردید.
$ git count-objects -v
count: 11
size: 4904
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0
اندازهی packed repository به 8K کاهش یافته که بسیار بهتر از ۵MB است.
از مقدار size میتوانید ببینید که object بزرگ همچنان در loose objects وجود دارد، پس کاملاً حذف نشده؛ اما هنگام push یا clone بعدی منتقل نخواهد شد، که همین مهم است.
اگر واقعاً بخواهید، میتوانید با اجرای git prune
همراه با گزینهی --expire
آن object را کاملاً حذف کنید:
$ git prune --expire now
$ git count-objects -v
count: 0
size: 0
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0