-
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)
9.2 گیت و سیستمهای دیگر (Git and Other Systems) - مهاجرت به گیت (Migrating to Git)
مهاجرت به گیت (Migrating to Git)
اگر کدبیس موجودی در یک سیستم کنترل نسخهٔ دیگر دارید ولی تصمیم گرفتهاید از گیت استفاده کنید، باید پروژهٔ خود را بهنوعی مهاجرت دهید. این بخش برخی واردکنندهها (importer) برای سیستمهای رایج را بررسی میکند و سپس نشان میدهد چگونه یک واردکنندهٔ سفارشی بسازید. شما یاد میگیرید چگونه دادهها را از چند سیستم مدیریت پیکربندی (SCM) بزرگتر که بهطور حرفهای استفاده میشوند وارد کنید، زیرا اینها اکثریت کسانی را تشکیل میدهند که در حال مهاجرتاند و همچنین ابزارهای باکیفیت برای آنها بهراحتی در دسترس است.
سیستم کنترل نسخه سابورژن (Subversion)
اگر بخش قبلی درباره استفاده از git svn را خوانده باشید، میتوانید بهراحتی از همان دستورالعملها برای انجام git svn clone یک مخزن استفاده کنید؛ سپس استفاده از سرور Subversion را متوقف کرده، به یک سرور Git جدید منتقل شوید و شروع به استفاده از آن کنید. اگر میخواهید تاریخچهی تغییرات حفظ شود، میتوانید این کار را به سرعتی انجام دهید که دادهها را از سرور Subversion بیرون بکشید (که ممکن است مدتی طول بکشد).
با این حال، واردسازی کامل نیست؛ و چون این کار طول میکشد، بهتر است آن را درست انجام دهید. اولین مشکل مربوط به اطلاعات نویسنده است. در Subversion، هر کسی که کامیت میکند، یک کاربر روی سیستم دارد که در اطلاعات کامیت ثبت میشود. مثالهای بخش قبلی، schacon را در برخی مکانها نشان میدهند، مانند خروجی blame و git svn log. اگر میخواهید این اطلاعات به دادههای نویسندهی بهتر در Git نگاشت شود، نیاز به یک نگاشت از کاربران Subversion به نویسندگان Git دارید. یک فایل به نام users.txt ایجاد کنید که این نگاشت را با فرمت زیر داشته باشد:
schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>
برای دریافت فهرستی از نام نویسندگانی که SVN استفاده میکند، میتوانید این را اجرا کنید:
$ svn log --xml --quiet | grep author | sort -u | \
perl -pe 's/.*>(.*?)<.*/$1 = /'
این خروجی را در قالب XML تولید میکند، سپس فقط خطوط حاوی اطلاعات نویسنده را نگه میدارد، تکراریها را حذف میکند و تگهای XML را پاک میکند.
واضح است که این تنها روی سیستمی کار میکند که grep
، sort
و perl
نصب باشند.
سپس خروجی را به فایل users.txt
هدایت کنید تا بتوانید داده معادل کاربر Git را در کنار هر ورودی اضافه کنید.
یادداشت
|
اگر این را روی یک ماشین ویندوزی امتحان میکنید، در این نقطه با مشکل مواجه خواهید شد. مایکروسافت راهنماییها و نمونههای خوبی را در https://learn.microsoft.com/en-us/azure/devops/repos/git/perform-migration-from-svn-to-git ارائه کرده است. |
میتوانید این فایل را به git svn
بدهید تا در نگاشت دقیقتر دادههای نویسنده کمک کند.
همچنین میتوانید به git svn
بگویید متادیتایی را که Subversion معمولاً وارد میکند، شامل نکند، با ارسال --no-metadata
به دستور clone
یا init
.
متادیتا شامل git-svn-id
در هر پیام کمیت است که Git هنگام واردسازی تولید میکند.
این میتواند لاگ Git شما را حجیم کند و کمی نامشخص سازد.
یادداشت
|
نیاز دارید متادیتا را نگه دارید وقتی میخواهید کمیتهایی را که در مخزن Git انجام شدهاند، به مخزن SVN اصلی بازآینه (mirror) کنید.
اگر نمیخواهید همگامسازی در لاگ کمیت شما ظاهر شود، میتوانید پارامتر |
این باعث میشود دستور import
شما به این شکل باشد:
$ git svn clone http://my-project.googlecode.com/svn/ \
--authors-file=users.txt --no-metadata --prefix "" -s my_project
$ cd my_project
حالا باید یک واردسازی Subversion مرتبتر در پوشه my_project
داشته باشید.
بهجای کمیتهایی که به این شکل بودند:
commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-
be05-5f7a86268029
آنها اینگونه به نظر میرسند:
commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
نه تنها فیلد Author بسیار بهتر است، بلکه git-svn-id
هم دیگر وجود ندارد.
همچنین باید کمی پاکسازی پس از واردسازی انجام دهید.
برای یک مورد، باید ارجاعات عجیبوغریب را که git svn
تنظیم کرده پاک کنید.
ابتدا تگها را جابهجا میکنید تا تگهای واقعی Git شوند نه شاخههای راهدور عجیب، و سپس مابقی شاخهها را طوری جابهجا میکنید که محلی شوند.
برای منتقل کردن تگها به تگهای واقعی Git، اجرا کنید:
$ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done
این دستور، ارجاعهایی را که شاخههای ریموت بودند و با refs/remotes/tags/
شروع میشدند، گرفته و آنها را به تگهای واقعی (سبک/lightweight) تبدیل میکند.
سپس، بقیهی ارجاعها زیر refs/remotes
را به شاخههای محلی منتقل کنید:
$ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done
ممکن است پیش بیاید که برخی شاخههای اضافه ببینید که با @xxx ختم شدهاند (که xxx یک عدد است)، در حالی که در Subversion تنها یک شاخه مشاهده میکنید. این در واقع یک ویژگی Subversion به نام "peg-revisions" است، که معادلی نحوی (syntax) در Git ندارد. بنابراین، git svn به سادگی شماره نسخه SVN را به نام شاخه اضافه میکند، درست همانطور که اگر در SVN میخواستید به peg-revision آن شاخه اشاره کنید، مینوشتید. اگر دیگر نیازی به peg-revisions ندارید، به سادگی آنها را حذف کنید:
$ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done
اکنون همهی شاخههای قدیمی، شاخههای واقعی Git هستند و همهی تگهای قدیمی، تگهای واقعی Git شدهاند.
یک نکتهی آخر برای پاکسازی باقی مانده است.
متأسفانه، git svn
یک شاخهی اضافه به نام trunk
ایجاد میکند که به شاخهی پیشفرض Subversion نگاشت میشود، اما ارجاع trunk
به همان مکانی اشاره میکند که master اشاره دارد.
از آنجا که master
بیشتر با سبک Git همخوانی دارد، روش حذف این شاخهی اضافه به صورت زیر است:
$ git branch -d trunk
آخرین کاری که باید انجام دهید، اضافه کردن سرور Git جدید خود به عنوان یک ریموت و انجام push به آن است. در اینجا یک مثال برای اضافه کردن سرور به عنوان ریموت آورده شده است:
$ git remote add origin git@my-git-server:myrepository.git
از آنجا که میخواهید تمام شاخهها و تگهای خود را به سرور منتقل کنید، اکنون میتوانید این دستور را اجرا کنید:
$ git push origin --all
$ git push origin --tags
تمام شاخهها و تگهای شما باید در سرور Git جدیدتان قرار داشته باشند، بهطوری که واردسازی بهصورت مرتب و تمیز انجام شده باشد.
سیستم کنترل نسخهی توزیعشده Mercurial (Mercurial)
از آنجا که Mercurial و Git مدلهای نسبتاً مشابهی برای نمایش نسخهها دارند و Git تا حدی انعطافپذیرتر است، تبدیل یک مخزن از Mercurial به Git نسبتاً سرراست است و از ابزاری به نام "hg-fast-export" استفاده میکند که باید یک نسخه از آن را داشته باشید:
$ git clone https://github.com/frej/fast-export.git
اولین گام در تبدیل، گرفتن یک کلون کامل از مخزن Mercurial است که میخواهید تبدیل کنید:
$ hg clone <remote repo URL> /tmp/hg-repo
گام بعدی ایجاد یک فایل نگاشت نویسنده است.
Mercurial در مورد مقداری که در فیلد نویسنده برای changesetها قرار میدهد کمی بزرگسروتر از Git است، بنابراین این زمان خوبی است برای پاکسازی.
تولید این فایل با یک فرمان تکخطی در شل bash
انجام میشود:
$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors
این کار چند ثانیه طول میکشد، بسته به طول تاریخچه پروژهتان، و پس از آن فایل /tmp/authors
چیزی شبیه به این خواهد بود:
bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>
در این مثال، یک فرد واحد (باب) changesetهایی را تحت چهار نام مختلف ایجاد کرده است؛ یکی از آنها در واقع درست به نظر میرسد و یکی از آنها برای یک commit در Git کاملاً نامعتبر خواهد بود.
Hg-fast-export به ما اجازه میدهد این را با تبدیل هر خط به یک قاعده اصلاح کنیم: "<input>"="<output>"
، که یک <input>
را به یک <output>
نگاشت میکند.
درون رشتههای <input>
و <output>
، تمام توالیهای فرار (escape sequences) که توسط انکودینگ Python string_escape
شناخته میشوند پشتیبانی میشوند.
اگر فایل نگاشت نویسنده شامل یک <input>
مطابق نباشد، آن نویسنده بدون تغییر به Git ارسال خواهد شد.
اگر همه نامهای کاربری درست به نظر برسند، اصلاً به این فایل نیازی نخواهیم داشت.
در این مثال، میخواهیم فایل ما شبیه به این باشد:
"bob"="Bob Jones <bob@company.com>"
"bob@localhost"="Bob Jones <bob@company.com>"
"bob <bob@company.com>"="Bob Jones <bob@company.com>"
"bob jones <bob <AT> company <DOT> com>"="Bob Jones <bob@company.com>"
همین نوع فایل نگاشت را میتوان برای تغییر نام شاخهها و تگها هم استفاده کرد، وقتی نام Mercurial توسط Git مجاز نیست.
گام بعدی ایجاد مخزن جدید Git و اجرای اسکریپت export است:
$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
پرچم `-r` به `hg-fast-export` میگوید مخزن مرکوریال که میخواهیم تبدیل کنیم کجا قرار دارد، و پرچم `-A` به آن میگوید فایل نگاشت نویسندهها کجا قرار دارد (فایلهای نگاشت شاخه و تگ بهترتیب با پرچمهای `-B` و `-T` مشخص میشوند). اسکریپت تغییرمجموعههای مرکوریال را پارس کرده و آنها را به اسکریپتی برای قابلیت `fast-import` گیت تبدیل میکند (که کمی بعد بهطور مفصل دربارهاش صحبت خواهیم کرد). این کار کمی زمان میبرد (اگرچه خیلی سریعتر از زمانی است که بخواهید از طریق شبکه انجامش دهید)، و خروجی نسبتاً پرحرف است:
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 120000
Total objects: 115032 ( 208171 duplicates )
blobs : 40504 ( 205320 duplicates 26117 deltas of 39602 attempts)
trees : 52320 ( 2851 duplicates 47467 deltas of 47599 attempts)
commits: 22208 ( 0 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 109 ( 2 loads )
marks: 1048576 ( 22208 unique )
atoms: 1952
Memory total: 7860 KiB
pools: 2235 KiB
objects: 5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 90430
pack_report: pack_mmap_calls = 46771
pack_report: pack_open_windows = 1 / 1
pack_report: pack_mapped = 340852700 / 340852700
---------------------------------------------------------------------
$ git shortlog -sn
369 Bob Jones
365 Joe Smith
تقریباً همین بود. تمام تگهای مرکوریال به تگهای گیت تبدیل شدهاند و شاخهها و بوکمارکهای مرکوریال به شاخههای گیت تبدیل شدهاند. حالا آمادهاید که مخزن را به محل جدیدش روی سرور ارسال کنید:
$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all
سیستم کنترل نسخهی توزیعشده Perforce (Perforce)
سیستم بعدی که برای بررسی وارد کردن دادهها مورد توجه قرار میدهیم، Perforce است. همانطور که پیشتر اشاره شد، دو روش اصلی برای ارتباط بین Git و Perforce وجود دارد: git-p4 Perforce Git Fusion
ادغام Perforce و Git (Perforce Git Fusion)
Perforce Git Fusion این فرآیند را بهطور چشمگیری ساده میکند. کافی است تنظیمات پروژه، نقشههای کاربری و شاخهها را تنها با استفاده از یک فایل پیکربندی (همانطور که در بخش گیت فیوژن (Git Fusion) توضیح داده شده است) انجام دهید و سپس مخزن را کلون کنید.
Git Fusion یک مخزن گیت ایجاد میکند که درست مانند یک مخزن بومی Git عمل میکند. شما میتوانید در صورت تمایل این مخزن را به یک میزبان Git بومی ارسال کنید. همچنین این امکان وجود دارد که حتی از Perforce بهعنوان میزبان Git خود استفاده نمایید.
ابزار گیت برای تعامل با Perforce (Git-p4)
برای استفاده از git-p4 بهعنوان ابزار واردات، ابتدا باید محیط خود را بهدرستی تنظیم کنید. بهعنوان مثال، اگر بخواهید پروژه Jam را از Perforce Public Depot وارد کنید، لازم است متغیر محیطی P4PORT را بهدرستی تنظیم کنید تا به مخزن Perforce اشاره کند. این کار را میتوانید با دستور زیر انجام دهید:
$ export P4PORT=public.perforce.com:1666
یادداشت
|
برای دنبال کردن این آموزش، نیاز به یک پایگاه داده Perforce دارید تا بتوانید به آن متصل شوید. ما برای مثالها از پایگاه داده عمومی public.perforce.com استفاده میکنیم، اما شما میتوانید از هر پایگاه دادهای که به آن دسترسی دارید بهره ببرید. |
دستور git p4 clone
را برای وارد کردن پروژه Jam از سرور Perforce اجرا کنید، با ارائه مسیر مخزن و پروژه و مسیری که میخواهید پروژه را در آن وارد کنید:
$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)
این پروژه خاص تنها یک شاخه دارد، اما اگر پروژه شما چند شاخه دارد یا شاخهها با نمای شاخهها (یا فقط مجموعهای از دایرکتوریها) پیکربندی شدهاند، میتوانید از گزینهی --detect-branches
در دستور git p4 clone
استفاده کنید تا تمام شاخههای پروژه وارد شوند.
برای اطلاعات بیشتر در این زمینه، به بخش شاخهبندی (Branching) مراجعه کنید.
در این مرحله، تقریباً فرآیند وارد کردن پروژه به پایان رسیده است.
با رفتن به دایرکتوری p4import
و اجرای دستور git log
میتوانید تاریخچه و تغییرات وارد شده پروژه را مشاهده کنید.
$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
[git-p4: depot-paths = "//public/jam/src/": change = 8068]
commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
[git-p4: depot-paths = "//public/jam/src/": change = 7304]
شما میتوانید مشاهده کنید که git-p4
یک شناسه در هر پیغام commit باقی گذاشته است. نگهداری این شناسه مشکلی ندارد، در صورتی که بخواهید شماره تغییر Perforce را بعداً ارجاع دهید. اما اگر تمایل دارید شناسه را حذف کنید، اکنون زمان مناسبی برای این کار است – قبل از شروع به انجام کار روی مخزن جدید. شما میتوانید از دستور git filter-branch
برای حذف گروهی شناسهها استفاده کنید:
$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten
اگر دستور git log
را اجرا کنید، میتوانید مشاهده کنید که تمام چکسامهای SHA-1 برای commitها تغییر کردهاند، اما رشتههای git-p4
دیگر در پیغامهای commit وجود ندارند:
$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
واردات شما آماده است تا به سرور جدید Git منتقل شود.
یک واردکننده سفارشی (A Custom Importer)
اگر سیستم شما جزو موارد بالا نیست، باید به دنبال یک واردکننده در اینترنت بگردید — واردکنندههای باکیفیت برای بسیاری از سیستمهای دیگر هم موجود است، از جمله CVS، ClearCase، Visual SourceSafe، حتی یک فهرست از آرشیوها.
اگر هیچیک از این ابزارها برای شما کارساز نبود، یا ابزار شما نامأنوستر است، یا به هر دلیل به فرایند وارد کردن سفارشیتری نیاز دارید، باید از git fast-import
استفاده کنید.
این دستورالعمل، دستورهای سادهای را از stdin میخواند تا دادههای مشخص گیت را بنویسد.
ایجاد اشیای گیت به این روش بسیار آسانتر است تا اجرای دستورات خام گیت یا تلاش برای نوشتن اشیای خام (برای اطلاعات بیشتر به (Git Internals) مراجعه کنید).
به این ترتیب میتوانید یک اسکریپت واردکننده بنویسید که اطلاعات لازم را از سیستمی که میخواهید وارد کنید بخواند و دستورالعملهای ساده را به stdout چاپ کند.
سپس میتوانید این برنامه را اجرا کرده و خروجی آن را به git fast-import
منتقل کنید.
برای نشاندادن سریع، یک واردکننده ساده مینویسید.
فرض کنیم در دایرکتوری current
کار میکنید، پروژهتان را گهگاه با کپی کردن دایرکتوری به یک پوشه پشتیبان زماندار back_YYYY_MM_DD
پشتیبانگیری میکنید، و میخواهید این را به گیت وارد کنید.
ساختار دایرکتوری شما به این شکل است:
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current
برای وارد کردن یک دایرکتوری به گیت، باید مرور کنید که گیت دادههایش را چگونه ذخیره میکند.
همانطور که احتمالاً به یاد دارید، گیت اساساً یک فهرست پیوندی از اشیای commit است که به یک تصویر (snapshot) از محتوا اشاره میکنند.
تمام کاری که باید بکنید این است که به fast-import
بگویید تصویربرداریهای محتوا چه هستند، دادههای commit چه چیزی را به آنها اشاره میکنند، و ترتیب آنها چیست.
استراتژی شما این خواهد بود که تکتک تصاویر را بررسی کرده و commitهایی با محتوای هر دایرکتوری ایجاد کنید و هر commit را به commit قبلی پیوند دهید.
همانطور که در <<_an_example_git_enforced_policy>> انجام دادیم، این را هم به روبی مینویسیم، چون همان زبانی است که معمولاً با آن کار میکنیم و خواندنش ساده است. شما میتوانید این مثال را بهراحتی با هر زبانی که با آن آشنا هستید بنویسید — فقط باید اطلاعات مناسب را به stdout چاپ کند. و اگر روی ویندوز اجرا میکنید، باید دقت ویژهای کنید که در انتهای خطوط CR (carriage return) اضافه نکنید — git fast-import نسبت به خطها بسیار دقیق است و فقط Line Feed (LF) را میپذیرد، نه ترکیب CRLF که ویندوز استفاده میکند.
برای شروع، به دایرکتوری مقصد میروید و هر زیرپوشه را شناسایی میکنید؛ هر کدام از این زیرپوشهها یک شاتنِپ است که میخواهید بهعنوان یک commit وارد کنید. به هر زیرپوشه میروید و فرمانهای لازم برای اکسپورت آن را چاپ میکنید. حلقهٔ اصلی پایهای شما شبیه این است:
last_mark = nil
# loop through the directories
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
شما در هر دایرکتوری تابع print_export
را اجرا میکنید که manifest و mark شاتنِپ قبلی را میگیرد و manifest و mark این شاتنِپ را برمیگرداند؛ به این ترتیب میتوانید آنها را درست به هم پیوند دهید.
«Mark» اصطلاح fast-import
برای شناسهای است که به یک commit میدهید؛ هرگاه commit میسازید، به هر کدام یک mark میدهید که بتوانید از سایر commitها به آن ارجاع دهید.
پس اولین کاری که در متد print_export
باید انجام دهید این است که از نام دایرکتوری یک mark بسازید:
mark = convert_dir_to_mark(dir)
این کار را با ساختن آرایهای از دایرکتوریها و استفاده از مقدار اندیس بهعنوان mark انجام میدهید، زیرا mark باید یک عدد صحیح باشد. متد شما شبیه زیر خواهد بود:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
end
حالا که نمایش عددی commit را دارید، به یک تاریخ برای متادیتای commit نیاز دارید.
از آنجا که تاریخ در نام دایرکتوری بیان شده است، آن را استخراج خواهید کرد.
خط بعدی در فایل print_export
شما چنین است:
date = convert_dir_to_date(dir)
که در آن convert_dir_to_date
بهصورت زیر تعریف شده است:
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
این مقدار یک عدد صحیح برای تاریخ هر دایرکتوری برمیگرداند. آخرین تکهٔ اطلاعات متا که برای هر commit نیاز دارید، دادههای committer است که آن را در یک متغیر سراسری بهصورت ثابت قرار میدهید:
$author = 'John Doe <john@example.com>'
حالا آمادهاید که چاپ دادههای commit برای واردکنندهتان را شروع کنید. اطلاعات اولیه بیان میکند که شما یک شیء commit را تعریف میکنید و روی کدام شاخه است، سپس مارکی که تولید کردهاید، اطلاعات committer و پیام commit و در ادامه commit قبلی، در صورت وجود، آمده است. کد به این شکل است:
# print the import information
puts 'commit refs/heads/master'
puts 'mark :' + mark
puts "committer #{$author} #{date} -0700"
export_data('imported from ' + dir)
puts 'from :' + last_mark if last_mark
شما ناحیه زمانی را بهصورت ثابت (-0700) قرار میدهید چون این کار ساده است. اگر از سیستم دیگری وارد میکنید، باید ناحیه زمانی را بهصورت آفست مشخص کنید. پیام commit باید به قالب ویژهای بیان شود:
data (size)\n(contents)
قالب شامل واژه data، اندازه دادهای که باید خوانده شود، یک خط جدید و در نهایت خود داده است. از آنجا که بعداً هم باید از همان قالب برای مشخص کردن محتویات فایلها استفاده کنید، یک متد کمکی به نام export_data ایجاد میکنید:
def export_data(string)
print "data #{string.size}\n#{string}"
end
تنها کاری که باقی میماند مشخص کردن محتویات فایل برای هر snapshot است. این کار آسان است، چون هر کدام را در یک دایرکتوری دارید — میتوانید دستور deleteall را چاپ کنید و سپس محتوای هر فایل در دایرکتوری را خروجی دهید. سپس Git هر snapshot را بهدرستی ثبت خواهد کرد:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
توجه: چون بسیاری از سیستمها بازنگریهایشان را بهعنوان تفاوتها بین یک commit و commit بعدی میدانند، fast-import همچنین میتواند دستوراتی همراه هر commit بپذیرد که مشخص کند کدام فایلها افزوده، حذف یا تغییر کردهاند و محتوای جدید چیست. میتوانید اختلافات بین snapshotها را محاسبه کنید و تنها همین دادهها را ارائه دهید، اما انجام این کار پیچیدهتر است — بهتر است تمام دادهها را به Git بدهید و بگذارید خودش آنها را حل کند. اگر این روش برای دادههای شما مناسبتر است، صفحه man برای fast-import را برای جزئیات نحوه ارائه دادهها به این شکل بررسی کنید.
قالب برای فهرست کردن محتوای جدید فایل یا مشخص کردن فایل تغییریافته با محتوای جدید به شرح زیر است:
M 644 inline path/to/file
data (size)
(file contents)
در اینجا، 644 حالت دسترسی است (اگر فایلهای اجرایی دارید باید این موضوع را تشخیص دهید و 755 را مشخص کنید)، و inline میگوید محتوای فایل بلافاصله بعد از این خط فهرست خواهد شد. متد inline_data شما به این شکل است:
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
شما از متد export_data
که قبلاً تعریف کردهاید استفاده میکنید، زیرا همانطور است که دادههای پیام کامیت خود را مشخص کرده بودید.
آخرین کاری که باید انجام دهید این است که نشانگر جاری (current mark) را بازگردانید تا بتوان آن را به تکرار بعدی منتقل کرد:
return mark
یادداشت
|
اگر روی ویندوز اجرا میکنید باید یک گام اضافی اضافه کنید.
همانطور که پیشتر گفته شد، ویندوز از CRLF برای کاراکترهای پایان خط استفاده میکند در حالی که
|
همین بود. در اینجا اسکریپت بهطور کامل آمده است:
#!/usr/bin/env ruby
$stdout.binmode
$author = "John Doe <john@example.com>"
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir)+1).to_s
end
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
def export_data(string)
print "data #{string.size}\n#{string}"
end
def inline_data(file, code='M', mode='644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
def print_export(dir, last_mark)
date = convert_dir_to_date(dir)
mark = convert_dir_to_mark(dir)
puts 'commit refs/heads/master'
puts "mark :#{mark}"
puts "committer #{$author} #{date} -0700"
export_data("imported from #{dir}")
puts "from :#{last_mark}" if last_mark
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
mark
end
# Loop through the directories
last_mark = nil
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
اگر این اسکریپت را اجرا کنید، محتوایی شبیه به این به دست خواهید آورد:
$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <john@example.com> 1388649600 -0700
data 29
imported from back_2014_01_02deleteall
M 644 inline README.md
data 28
# Hello
This is my readme.
commit refs/heads/master
mark :2
committer John Doe <john@example.com> 1388822400 -0700
data 29
imported from back_2014_01_04from :1
deleteall
M 644 inline main.rb
data 34
#!/bin/env ruby
puts "Hey there"
M 644 inline README.md
(...)
برای اجرای ایمپورتر، این خروجی را هنگام قرار داشتن در پوشه گیت که میخواهید در آن ایمپورت کنید، به git fast-import
پایپ کنید.
برای شروع میتوانید یک پوشه جدید بسازید و در آن git init
را اجرا کنید، سپس اسکریپت را اجرا نمایید:
$ git init
Initialized empty Git repository in /opt/import_to/.git/
$ ruby import.rb /opt/import_from | git fast-import
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 5000
Total objects: 13 ( 6 duplicates )
blobs : 5 ( 4 duplicates 3 deltas of 5 attempts)
trees : 4 ( 1 duplicates 0 deltas of 4 attempts)
commits: 4 ( 1 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 1 ( 1 loads )
marks: 1024 ( 5 unique )
atoms: 2
Memory total: 2344 KiB
pools: 2110 KiB
objects: 234 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 10
pack_report: pack_mmap_calls = 5
pack_report: pack_open_windows = 2 / 2
pack_report: pack_mapped = 1457 / 1457
---------------------------------------------------------------------
همانطور که میبینید، وقتی با موفقیت انجام شد، آمار متعددی از آنچه انجام شده به شما میدهد.
در این مورد، مجموعاً 13 شی برای 4 کامیت در 1 شاخه وارد شده است.
اکنون میتوانید با اجرای git log
تاریخچه جدید خود را ببینید:
$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <john@example.com>
Date: Tue Jul 29 19:39:04 2014 -0700
imported from current
commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <john@example.com>
Date: Mon Feb 3 01:00:00 2014 -0700
imported from back_2014_02_03
این هم — یک مخزن گیت تمیز و مرتب. مهم است بدانید که هیچ چیز چکاوت نشده است — در ابتدا فایلهایی در دایرکتوری کاری خود ندارید. برای دریافت آنها باید شاخه خود را به جایی که اکنون master قرار دارد ریست کنید:
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb
با ابزار fast-import
میتوانید کارهای بیشتری انجام دهید — حالتهای مختلف، دادههای باینری، شاخهها و ادغامهای متعدد، تگها، نمایش پیشرفت و موارد دیگر.
چندین مثال از سناریوهای پیچیدهتر در دایرکتوری contrib/fast-import
در سورس کد گیت موجود است.