Chapters ▾ 2nd Edition

9.1 گیت و سیستم‌های دیگر (Git and Other Systems) - گیت به‌عنوان کلاینت (Git as a Client)

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

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

گیت به‌عنوان کلاینت (Git as a Client)

تجربهٔ استفاده از گیت برای توسعه‌دهندگان چنان دلپذیر است که بسیاری توانسته‌اند روی ماشین کاری خود از آن استفاده کنند، حتی اگر بقیهٔ تیم‌شان از یک سیستم کنترل نسخهٔ کاملاً متفاوت استفاده کنند. چندین این نوع پل‌ها (bridges) وجود دارد. در اینجا آن‌هایی را پوشش می‌دهیم که بیشترین احتمال مواجهه با آن‌ها را دارید.

گیت و سابورژن (Git and Subversion)

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

یکی از ویژگی‌های برجسته گیت، پل دوجهته‌ای به ساب‌ورژن است به نام git svn. این ابزار به شما اجازه می‌دهد تا از گیت به‌عنوان یک کلاینت معتبر برای یک سرور ساب‌ورژن استفاده کنید، بنابراین می‌توانید از تمام امکانات محلی گیت بهره ببرید و سپس تغییرات را به سرور ساب‌ورژن پوش کنید انگار که محلی از ساب‌ورژن استفاده می‌کنید. این یعنی می‌توانید شاخه‌زنی و ادغام محلی انجام دهید، از منطقهٔ مرحله‌بندی استفاده کنید، ری‌بیس و چری‌پیکینگ به‌کار ببرید و غیره، در حالی که همکارانتان به روش‌های قدیمی و تاریک خود ادامه می‌دهند. این روش خوبی است برای نفوذ آرام گیت به محیط شرکتی و کمک به توسعه‌دهندگان همکار برای افزایش کارایی، در حالی که شما برای تغییر زیرساخت جهت پشتیبانی کامل از گیت تلاش می‌کنید. پل ساب‌ورژن، مادهٔ مخدرِ دروازه‌ای به دنیای DVCS است.

ابزار گیت برای تعامل با Subversion (git svn)

فرمان پایه در گیت برای همهٔ فرمان‌های مرتبط با پل ساب‌ورژن، git svn است. این فرمان مجموعه‌ای از زیرفرمان‌ها را دارد، بنابراین ما رایج‌ترین آن‌ها را هنگام مرور چند گردش‌کار ساده نشان خواهیم داد.

مهم است توجه داشته باشید که وقتی از git svn استفاده می‌کنید، با ساب‌ورژن در تعامل هستید؛ سیستمی که بسیار متفاوت از گیت کار می‌کند. اگرچه می‌توانید شاخه‌زنی و ادغام محلی انجام دهید، معمولاً بهتر است تاریخچه‌تان را تا حد امکان خطی نگه دارید با ری‌بیس کردن کارها، و از کارهایی مانند تعامل هم‌زمان با یک مخزن ریموت گیت پرهیز کنید.

تاریخچه را بازنویسی نکنید و دوباره تلاش به پوش نکنید، و هم‌زمان برای همکاری با دیگر توسعه‌دهندگان گیت به یک مخزن گیت موازی پوش ننمایید. ساب‌ورژن تنها می‌تواند یک تاریخچه خطی داشته باشد و گیج کردن آن بسیار ساده است. اگر با تیمی کار می‌کنید و برخی از اعضا از SVN و برخی دیگر از Git استفاده می‌کنند، مطمئن شوید همه برای همکاری از سرور SVN استفاده می‌کنند — این کار زندگی شما را آسان‌تر خواهد کرد.

راه‌اندازی (Setting Up)

برای نمایش این قابلیت، به یک مخزن معمولی SVN که دسترسی نوشتن به آن دارید نیاز دارید. اگر می‌خواهید این مثال‌ها را کپی کنید، باید یک کپی قابل نوشت از یک مخزن آزمایشی SVN بسازید. برای انجام آسان این کار می‌توانید از ابزاری به نام svnsync استفاده کنید که همراه با Subversion عرضه می‌شود.

برای دنبال کردن مراحل، ابتدا باید یک مخزن محلی جدید Subversion ایجاد کنید:

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

سپس، اجازه دهید همه کاربران بتوانند revprop ها را تغییر دهند — راه ساده این است که یک اسکریپت pre-revprop-change اضافه کنید که همیشه با کد خروجی 0 خاتمه می‌یابد:

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

اکنون می‌توانید این پروژه را با فراخوانی svnsync init با مخازن مبدا و مقصد، به ماشین محلی خود همگام‌سازی کنید.

$ svnsync init file:///tmp/test-svn \
  http://your-svn-server.example.org/svn/

این کار خصوصیات لازم برای اجرای همگام‌سازی را تنظیم می‌کند. سپس می‌توانید با اجرای دستور زیر کد را کلون کنید:

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]

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

شروع به کار (Getting Started)

حالا که یک مخزن Subversion دارید که به آن دسترسی نوشتن دارید، می‌توانید یک جریان کاری معمولی را طی کنید. شما با فرمان git svn clone شروع خواهید کرد که یک مخزن کامل Subversion را به یک مخزن محلی Git وارد می‌کند. به یاد داشته باشید اگر از یک مخزن SVN میزبانی‌شده واقعی وارد می‌کنید، باید file:///tmp/test-svn را با آدرس (URL) مخزن Subversion خود جایگزین کنید:

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

این کار معادل اجرای دو فرمان git svn init و سپس git svn fetch روی آدرسی است که وارد می‌کنید. این کار ممکن است مدتی طول بکشد. برای مثال، اگر پروژهٔ آزمایشی فقط حدود ۷۵ کامیت داشته باشد و پایگاه کد هم خیلی بزرگ نباشد، گیت با این حال باید هر نسخه را یکی‌یکی خارج‌کرده (checkout) و جداگانه کامیت کند. برای پروژه‌ای با صدها یا هزاران کامیت، این فرایند عملاً می‌تواند ساعت‌ها یا حتی روزها طول بکشد.

قسمت -T trunk -b branches -t tags به گیت می‌گوید که این مخزن ساب‌ورژن از قراردادهای پایه‌ای شاخه‌بندی و برچسب‌گذاری پیروی می‌کند. اگر نام trunk، branches یا tags شما متفاوت است، می‌توانید این گزینه‌ها را تغییر دهید. از آنجا که این الگو بسیار رایج است، می‌توانید کل این قسمت را با -s جایگزین کنید که به معنای «ساختار استاندارد» بوده و آن گزینه‌ها را ضمنی در بر می‌گیرد. فرمان زیر معادل است:

$ git svn clone file:///tmp/test-svn -s

در این مرحله باید یک مخزن گیت معتبر داشته باشید که شاخه‌ها و برچسب‌های شما را وارد کرده باشد:

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

توجه کنید که این ابزار چگونه برچسب‌های ساب‌ورژن را به صورت refهای راه‌دور مدیریت می‌کند. با فرمان «ابزاری» گیت یعنی show-ref دقیق‌تر نگاه کنیم:

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

گیت هنگام کلون‌کردن از یک سرور گیت این کار را انجام نمی‌دهد؛ این‌جا نمونه‌ای از ظاهری است که مخزن بعد از یک کلون تازه با برچسب‌ها دارد:

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

گیت برچسب‌ها را مستقیماً در refs/tags دریافت می‌کند، نه این‌که آن‌ها را به‌عنوان شاخه‌های راه‌دور در نظر بگیرد.

بازگرداندن کامیت‌ها به ساب‌ورژن (Committing Back to Subversion)

حالا که یک درخت کاری دارید، می‌توانید روی پروژه کار کنید و کامیت‌های خود را به‌صورت upstream به عقب بفرستید و عملاً از گیت به‌عنوان یک کلاینت SVN استفاده کنید. اگر یکی از فایل‌ها را ویرایش کرده و کامیت کنید، یک کامیت دارید که به‌صورت محلی در گیت وجود دارد اما در سرور ساب‌ورژن وجود ندارد:

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

در گام بعد باید تغییر خود را به سرور upstream بفرستید. دقت کنید که این کار نحوهٔ کار با ساب‌ورژن را تغییر می‌دهد — می‌توانید چندین کامیت را آفلاین انجام داده و سپس همهٔ آن‌ها را یکجا به سرور ساب‌ورژن push کنید. برای ارسال به یک سرور ساب‌ورژن، فرمان git svn dcommit را اجرا می‌کنید:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk
 این کار تمام commitهایی را که شما روی کد سرور Subversion ساخته‌اید می‌گیرد، برای هر کدام یک commit روی Subversion انجام می‌دهد و سپس commit محلی Git شما را بازنویسی می‌کند تا یک شناسهٔ یکتا را در بر داشته باشد.
این مهم است چون یعنی تمام چک‌سوم‌های SHA-1 مربوط به commitهای شما تغییر می‌کنند.
تا حدی به همین دلیل، هم‌زمان کار کردن با نسخه‌های راه دور مبتنی بر Git از پروژه‌هایتان همراه با یک سرور Subversion ایدهٔ خوبی نیست.
اگر به آخرین commit نگاه کنید، می‌توانید `git-svn-id` جدیدی را که اضافه شده می‌بینید:
$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

توجه کنید که چک‌سوم SHA-1 که در ابتدا با 4af61fd شروع می‌شد وقتی شما commit کردید اکنون با 95e0222 شروع می‌شود. اگر می‌خواهید به هر دو سرور Git و سرور Subversion push کنید، ابتدا باید به سرور Subversion dcommit کنید، چون آن عملیات داده‌های commit شما را تغییر می‌دهد.

دریافت تغییرات جدید (Pulling in New Changes)

اگر با توسعه‌دهندگان دیگر کار می‌کنید، در مقطعی یکی از شما push خواهد کرد و سپس دیگری سعی می‌کند تغییری را push کند که تداخل دارد. آن تغییر تا زمانی که کارشان را merge نکنید رد خواهد شد. در git svn این شبیه این نشان داده می‌شود:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

برای رفع این وضعیت می‌توانید دستور git svn rebase را اجرا کنید، که هر تغییری را که روی سرور هست و شما هنوز ندارید می‌کشد و هر کاری که شما انجام داده‌اید را دوباره به‌صورت rebase شده روی آنچه روی سرور است قرار می‌دهد:

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

حال، تمام کارهای شما روی آنچه روی سرور Subversion است قرار گرفته‌اند، پس می‌توانید با موفقیت dcommit کنید:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

توجه کنید که بر خلاف Git که از شما می‌خواهد قبل از push کردن، کارهای upstream را که هنوز به‌صورت محلی ندارید merge کنید، git svn فقط در صورتی شما را مجبور به این کار می‌کند که تغییرات تداخل داشته باشند (که بسیار شبیه رفتار Subversion است). اگر شخص دیگری تغییری روی یک فایل اعمال کند و سپس شما تغییری را روی فایل دیگری push کنید، dcommit شما بدون مشکل عمل خواهد کرد:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...
 به خاطر سپردن این نکته مهم است، زیرا نتیجه وضعیت پروژه‌ای است که هنگام push روی هیچ‌کدام از دو کامپیوتر شما وجود نداشته است.
اگر تغییرات ناسازگار باشند اما تضاد (conflict) نداشته باشند، ممکن است با مشکلاتی روبه‌رو شوید که تشخیص‌شان دشوار است.
این با استفاده از یک سرور Git متفاوت است — در Git می‌توانید وضعیت را در سیستم کلاینت خود به‌طور کامل تست کنید قبل از اینکه آن را منتشر کنید، در حالی که در SVN هرگز نمی‌توانید مطمئن باشید که وضعیت دقیقاً بلافاصله قبل از commit و بعد از commit یکسان است.

همچنین باید این فرمان را اجرا کنید تا تغییرات از سرور Subversion کشیده شوند، حتی اگر خودتان آمادهٔ commit کردن نباشید. می‌توانید با git svn fetch داده‌های جدید را بگیرید، اما git svn rebase هم fetch را انجام می‌دهد و سپس commitهای محلی شما را به‌روز می‌کند.

$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

اجرای گاه‌به‌گاه git svn rebase مطمئن می‌سازد که کد شما همیشه به‌روز است. با این حال، هنگام اجرای این فرمان باید مطمئن باشید که شاخهٔ کاری (working directory) شما پاک است. اگر تغییرات محلی دارید، باید یا کارتان را stash کنید یا موقتاً commit کنید قبل از اجرای git svn rebase — در غیر این صورت، اگر rebase منجر به یک conflict شود فرمان متوقف خواهد شد.

مشکلات شاخه های گیت (Git Branching Issues)

وقتی با یک جریان کاری Git راحت شوید، به احتمال زیاد شاخه‌های موضوعی (topic branches) ایجاد خواهید کرد، روی آن‌ها کار می‌کنید و سپس آن‌ها را merge می‌کنید. اگر از طریق git svn به یک سرور Subversion push می‌کنید، ممکن است بخواهید هر بار کارتان را به جای ادغام شاخه‌ها، روی یک شاخهٔ واحد rebase کنید. دلیل ترجیح rebase این است که Subversion تاریخچه‌ای خطی دارد و با mergeها مثل Git برخورد نمی‌کند، بنابراین git svn هنگام تبدیل اسنپ‌شات‌ها به commitهای Subversion فقط از والد اول پیروی می‌کند.

فرض کنید تاریخچهٔ شما شبیه به این است: شما یک شاخهٔ experiment ایجاد کرده‌اید، دو commit انجام داده‌اید، و سپس آن‌ها را به master merge کرده‌اید. وقتی dcommit انجام می‌دهید، خروجی‌ای شبیه به این می‌بینید:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

اجرای دستور dcommit روی برنچی که تاریخچهٔ مرج‌شده دارد به درستی کار می‌کند، اما وقتی به تاریخچهٔ پروژهٔ Git نگاه می‌کنید، هیچ‌کدام از کامیت‌هایی که روی برنچ experiment ساخته‌اید بازنویسی نشده‌اند — در عوض همهٔ آن تغییرات در نسخهٔ SVNِ یک کامیت مرج واحد ظاهر می‌شوند.

وقتی شخص دیگری آن کار را کلون می‌کند، فقط کامیت مرج را می‌بیند که تمام کارها در آن فشرده شده‌اند، انگار که شما git merge --squash اجرا کرده‌اید؛ آنها اطلاعاتی دربارهٔ منبع یا زمان ایجاد آن کامیت را نمی‌بینند.

شاخه‌بندی در Subversion (Subversion Branching)

شاخه‌بندی در Subversion مثل شاخه‌بندی در Git نیست؛ اگر بتوانید تا حد امکان از آن استفاده نکنید، احتمالاً بهتر است. با این حال، می‌توانید با استفاده از git svn شاخه‌هایی در Subversion ایجاد کرده و روی آن‌ها کامیت کنید.

ایجاد یک شاخهٔ جدید در SVN (Creating a New SVN Branch)

برای ایجاد یک شاخهٔ جدید در Subversion، از دستور git svn branch [new-branch] استفاده می‌کنید:

$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

این معادل دستور svn copy trunk branches/opera در Subversion است و روی سرور Subversion عمل می‌کند. مهم است بدانید که شما را به آن شاخه چک‌اوت نمی‌کند؛ اگر در این مرحله کامیت کنید، آن کامیت به trunk روی سرور خواهد رفت، نه به opera.

تغییر شاخهٔ فعال (Switching Active Branches)

Git با نگاه کردن به سرِ هر یک از شاخه‌های Subversion شما در تاریخچه‌تان تشخیص می‌دهد که dcommitهایتان به کجا می‌روند — باید فقط یکی از آن‌ها وجود داشته باشد، و آن باید آخرین شاخه‌ای باشد که git-svn-id در تاریخچهٔ شاخهٔ فعلی شما دارد.

اگر می‌خواهید هم‌زمان روی بیش از یک شاخه کار کنید، می‌توانید شاخه‌های محلی‌ای را طوری تنظیم کنید که برای dcommit به شاخه‌های مشخص Subversion اشاره کنند و آن‌ها را از کامیت واردشدهٔ Subversion برای آن شاخه شروع کنید. اگر می‌خواهید یک شاخهٔ opera داشته باشید که بتوانید جداگانه رویش کار کنید، می‌توانید اجرا کنید:

$ git branch opera remotes/origin/opera

حالا اگر بخواهید شاخه‌ی opera را درون trunk (شاخه‌ی master شما) ادغام کنید، می‌توانید این کار را با یک git merge معمولی انجام دهید. اما باید یک پیام کامیت توصیفی (با استفاده از -m) بدهید، وگرنه پیام ادغام به‌صورت “Merge branch opera” خواهد بود که مفید نیست.

به خاطر داشته باشید که گرچه برای این عملیات از git merge استفاده می‌کنید و احتمالاً ادغام خیلی ساده‌تر از Subversion خواهد بود (چون گیت به‌صورت خودکار مبنای مناسب ادغام را تشخیص می‌دهد)، این یک کامیت ادغام عادی در گیت نیست. شما باید این داده‌ها را به یک سرور Subversion که قادر به نگهداری یک کامیتی با بیش از یک والد نیست، برگردانید؛ بنابراین بعد از ارسال (push) آن، این‌طور به نظر خواهد رسید که یک کامیت واحد تمام کارهای شاخه‌ی دیگر را زیر یک کامیت فشرده کرده است. بعد از اینکه یک شاخه را در شاخه‌ی دیگر ادغام کردید، به‌سادگی نمی‌توانید برگردید و مثل حالت عادی در گیت روی آن شاخه ادامه‌ی کار بدهید. فرمان dcommit که اجرا می‌کنید هر اطلاعاتی را که نشان دهد کدام شاخه ادغام شده است پاک می‌کند، پس محاسبات بعدی مبنای ادغام اشتباه خواهند شد — dcommit نتیجه‌ی git merge شما را طوری نشان می‌دهد که انگار git merge --squash اجرا شده است. متأسفانه راه خوبی برای اجتناب از این وضعیت وجود ندارد — Subversion قادر به ذخیره‌ی این اطلاعات نیست، بنابراین تا زمانی که از آن به‌عنوان سرور استفاده می‌کنید، همیشه محدودیت‌های آن شما را مختل خواهد کرد. برای جلوگیری از مشکلات، پس از ادغام شاخه در trunk باید شاخه‌ی محلی (در این مثال، opera) را حذف کنید.

دستورات Subversion (Subversion Commands)

مجموعه ابزار git svn تعدادی دستور فراهم می‌کند تا با ارائه‌ی قابلیت‌هایی مشابه آنچه در Subversion داشتید، انتقال به گیت را آسان‌تر کند. در اینجا چند دستور آمده که آنچه Subversion قبلاً ارائه می‌داد را در اختیار شما می‌گذارد.

تاریخچه به سبک SVN (SVN Style History)

اگر به Subversion عادت دارید و می‌خواهید تاریخچه را به قالب خروجی SVN ببینید، می‌توانید با اجرای git svn log تاریخچه‌ی کامیت‌های خود را در قالب‌بندی SVN مشاهده کنید:

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog
 شما باید دو نکتهٔ مهم را دربارهٔ `git svn log` بدانید.
اول اینکه این دستور به‌صورت آفلاین کار می‌کند، بر خلاف دستور واقعی `svn log` که برای گرفتن اطلاعات از سرور Subversion سؤال می‌پرسد.
دوم اینکه تنها کامیت‌هایی را نشان می‌دهد که تا حالا به سرور Subversion ارسال شده‌اند.
کامیت‌های محلی گیت که هنوز ارسال (dcommited) نشده‌اند نمایش داده نمی‌شوند؛ همچنین کامیت‌هایی که دیگران در این بین به سرور Subversion زده‌اند هم نشان داده نمی‌شوند.
این خروجی بیشتر شبیه آخرین وضعیت شناخته‌شدهٔ کامیت‌ها روی سرور Subversion است.
یادداشت‌گذاری در SVN (SVN Annotation)

همان‌طور که دستور git svn log به‌صورت آفلاین رفتار دستور svn log را شبیه‌سازی می‌کند، می‌توانید معادل svn annotate را با اجرای git svn blame [FILE] به‌دست آورید. خروجی شبیه این خواهد بود:

$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal http://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal

دوباره تاکید می‌شود که این خروجی کامیت‌های محلی شما در گیت یا کامیت‌هایی که در این فاصله به Subversion فرستاده شده‌اند را نشان نمی‌دهد.

اطلاعات سرور SVN (SVN Server Information)

همچنین می‌توانید با اجرای git svn info همان نوع اطلاعاتی را که svn info می‌دهد به‌دست آورید:

$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)

این مثل blame و log است از این نظر که آفلاین اجرا می‌شود و تنها تا زمانی به‌روز است که آخرین بار با سرور Subversion ارتباط برقرار کرده‌اید.

نادیده گرفتن آنچه Subversion نادیده می‌گیرد (Ignoring What Subversion Ignores)

اگر مخزن Subversion را کلون کنید و در هر جایی خاصیت‌های svn:ignore تنظیم شده باشند، احتمالاً می‌خواهید فایل‌های .gitignore متناظر را بسازید تا ناخواسته فایل‌هایی را که نباید کامیت شوند، کامیت نکنید. git svn دو دستور برای کمک به این مسئله دارد. اولی git svn create-ignore است که به‌طور خودکار فایل‌های .gitignore متناظر را برایتان ایجاد می‌کند تا کامیت بعدی شما بتواند آن‌ها را شامل شود.

دستور دوم git svn show-ignore است که خطوطی را که باید در یک فایل .gitignore قرار دهید به stdout می‌فرستد تا بتوانید خروجی را به فایل استثنا (exclude) پروژهٔ خود هدایت کنید:

$ git svn show-ignore > .git/info/exclude

به این ترتیب پروژه را با فایل‌های .gitignore پر نمی‌کنید. این گزینه خوب است اگر شما تنها کاربر گیت در یک تیم Subversion هستید و هم‌تیمی‌هایتان نمی‌خواهند فایل‌های .gitignore داخل پروژه باشند.

خلاصهٔ Git-Svn (Git-Svn Summary)

ابزارهای git svn زمانی مفیدند که مجبور به کار با یک سرور Subversion هستید یا در محیط توسعه‌ای قرار دارید که نیاز به اجرای سرور Subversion دارد. با این حال باید آن را همانند یک گیت ناقص درنظر بگیرید، وگرنه در ترجمه بین دو سیستم با مسائلی روبه‌رو می‌شوید که می‌تواند شما و همکارانتان را سردرگم کند. برای جلوگیری از مشکل، سعی کنید از این دستورالعمل‌ها پیروی کنید:

  • یک تاریخچهٔ خطی در گیت نگه دارید که شامل کامیت‌های مرج‌شده توسط git merge نباشد. هر کاری که خارج از شاخهٔ اصلی‌تان انجام می‌دهید را ری‌بیس کنید روی شاخهٔ اصلی؛ آن را مرج نکنید.

  • یک سرور گیت جدا راه‌اندازی نکنید و بر روی آن همکاری نکنید. ممکن است برای تسریع کلون‌ها برای توسعه‌دهندگان جدید یکی داشته باشید، اما چیزی به آن پوش نکنید مگر اینکه در پیام کامیت آن ورودی git-svn-id وجود داشته باشد. حتی ممکن است بخواهید یک هوک pre-receive اضافه کنید که هر پیام کامیت را برای وجود git-svn-id بررسی کند و پوش‌هایی را که شامل کامیت‌هایی بدون این ورودی هستند رد کند.

اگر از این دستورالعمل‌ها پیروی کنید، کار با سرور Subversion قابل تحمل‌تر خواهد بود. با این حال، اگر امکان مهاجرت به یک سرور واقعی گیت وجود دارد، انجام این کار می‌تواند مزایای زیادی برای تیم شما داشته باشد.

گیت و مرکوریال (Git and Mercurial)

جهان DVCS تنها محدود به گیت نیست. در واقع، سیستم‌های دیگری هم در این حوزه وجود دارند که هر کدام دیدگاه خود را دربارهٔ نحوهٔ درست انجام کنترل نسخهٔ توزیع‌شده دارند. علاوه بر گیت، محبوب‌ترینِ آنها مرکوریال است و این دو در بسیاری جهات بسیار شبیه هم‌اند.

خبر خوب این است که اگر رفتار سمت کلاینت گیت را می‌پسندید اما با پروژه‌ای سروکار دارید که کد منبعش با مرکوریال کنترل می‌شود، راهی هست که از گیت به‌عنوان کلاینت برای مخزن میزبانی‌شده با مرکوریال استفاده کنید. از آنجا که گیت از طریق remotes با مخازن سرور ارتباط برقرار می‌کند، تعجب‌آور نیست که این پل به‌صورت یک remote helper پیاده‌سازی شده است. نام این پروژه git-remote-hg است و می‌توانید آن را در https://github.com/felipec/git-remote-hg پیدا کنید.

ابزار گیت برای تعامل با Mercurial (git-remote-hg)

ابتدا باید git-remote-hg را نصب کنید. این در عمل به معنی قرار دادن فایل آن در مسیری است که در PATH شما قرار دارد، به این صورت:

$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

…assuming ~/bin is in your $PATH. Git-remote-hg has one other dependency: the mercurial library for Python. If you have Python installed, this is as simple as:

$ pip install mercurial

اگر پایتون روی سیستم‌تان نصب نیست، به https://www.python.org/ مراجعه کرده و ابتدا آن را نصب کنید.

آخرین چیزی که نیاز دارید، کلاینت مرکوریال است. به https://www.mercurial-scm.org/ بروید و در صورت عدم نصب، آن را نصب کنید.

حالا آماده‌اید. تنها چیزی که لازم دارید یک مخزن مرکوریال است که بتوانید به آن push کنید. خوشبختانه هر مخزن مرکوریال می‌تواند این نقش را ایفا کند، بنابراین ما از مخزن «سلام دنیا» که همه برای یادگیری مرکوریال از آن استفاده می‌کنند، استفاده می‌کنیم:

$ hg clone http://selenic.com/repo/hello /tmp/hello

شروع کار (Getting Started)

حال که یک مخزن «سمت سرور» مناسب داریم، می‌توانیم یک گردش‌کار معمولی را طی کنیم. همان‌طور که خواهید دید، این دو سیستم آن‌قدر به هم شبیه‌اند که اصطکاک چندانی وجود ندارد.

همانند همیشه در گیت، ابتدا clone می‌کنیم:

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard 'hello, world' program
متوجه می‌شوید که کار با مخزن Mercurial از فرمان استاندارد `git clone` استفاده می‌کند. دلیلش این است که git-remote-hg در سطح نسبتاً پایینی کار می‌کند و از مکانیزمی مشابه با نحوه پیاده‌سازی پروتکل HTTP/S گیت (remote helpers) بهره می‌برد. از آنجا که هم Git و هم Mercurial طوری طراحی شده‌اند که هر کلاینت یک نسخه کامل از تاریخچهٔ مخزن داشته باشد، این فرمان یک کلون کامل می‌سازد که شامل تمام تاریخچهٔ پروژه است و این کار را نسبتاً سریع انجام می‌دهد.

فرمان log دو کامیت را نشان می‌دهد که جدیدترینِ آن توسط تعداد زیادی رِف اشاره شده است. معلوم می‌شود بعضی از این‌ها در واقع وجود خارجی ندارند. بیایید نگاهی به محتوای واقعی پوشهٔ .git بیندازیم:

$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files

Git-remote-hg تلاش می‌کند همه‌چیز را به شکلی آشنا برای گیت درآورد، اما در پشت صحنه نگاشت مفهومی بین دو سیستم کمی متفاوت را مدیریت می‌کند. پوشهٔ refs/hg جایی است که رِف‌های واقعیِ راه دور در آن ذخیره می‌شوند. برای مثال، refs/hg/origin/branches/default یک فایل رِف گیت است که شامل SHA-1‌ای است که با "ac7955c" شروع می‌شود و همان کامیتی است که master به آن اشاره می‌کند. بنابراین پوشهٔ refs/hg نوعی مثلِ یک refs/remotes/origin ساختگی است، اما تمایز بین bookmarks و branches را نیز حفظ می‌کند.

فایل notes/hg نقطهٔ شروعِ نحوهٔ نگاشت هش‌های کامیت گیت به شناسه‌های changeset در Mercurial است. بیایید کمی بررسی کنیم:

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

پس refs/notes/hg به یک درخت اشاره می‌کند که در پایگاه دادهٔ اشیاء گیت فهرستی از اشیاء دیگر با نام‌هاست. git ls-tree مد، نوع، هش شیء و نام فایل آیتم‌های داخل یک درخت را نمایش می‌دهد. وقتی عمیق‌تر به یکی از آیتم‌های درخت می‌رویم، می‌بینیم که داخل آن یک blob به نام "ac9117f" (هش SHA-1ِ کامیتی که master به آن اشاره می‌کند) وجود دارد که محتوای آن "0a04b98" است (که شناسهٔ changesetِ Mercurial در رأسِ شاخهٔ default است).

خبر خوب این است که بیشتر اوقات لازم نیست نگران همهٔ این جزئیات باشیم. جریان کار معمولی چندان با کار کردن با یک راه دور گیت تفاوتی نخواهد داشت.

 یک چیز دیگر هست که قبل از ادامه باید به آن رسیدگی کنیم: فایل‌های ignore.
Mercurial و Git مکانیزم بسیار مشابهی برای این کار دارند، ولی احتمالاً شما نمی‌خواهید فایل `.gitignore` را در یک مخزن Mercurial کامیت کنید.
خوشبختانه Git راهی برای نادیده گرفتن فایل‌ها دارد که محلی برای مخزن روی دیسک است، و فرمت Mercurial با Git سازگار است، پس کافی است آن را کپی کنید:
$ cp .hgignore .git/info/exclude

فایل .git/info/exclude دقیقاً مانند یک .gitignore عمل می‌کند، اما در کامیت‌ها گنجانده نمی‌شود.

گردش کار (Workflow)

فرض کنیم کمی کار کرده‌ایم و چند کامیت روی شاخه master ساخته‌ایم و حالا آماده‌ایم که آن را به مخزن راه دور پوش کنیم. در حال حاضر مخزن ما این‌طور به نظر می‌رسد:

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard 'hello, world' program

شاخه master ما دو کامیت جلوتر از origin/master است، اما آن دو کامیت فقط روی ماشین محلی ما وجود دارند. باید ببینیم آیا کس دیگری هم هم‌زمان کار مهمی انجام داده است یا نه:

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program

از آنجا که از فلگ --all استفاده کردیم، رفرنس‌های "notes" که به‌صورت داخلی توسط git-remote-hg استفاده می‌شوند را می‌بینیم، اما می‌توانیم آن‌ها را نادیده بگیریم. بقیه همان چیزی است که انتظار داشتیم؛ origin/master یک کامیت جلو رفته و تاریخچه ما اکنون منشعب شده است. برخلاف سیستم‌های دیگر که در این فصل کار می‌کنیم، Mercurial قادر به مدیریت mergeها است، پس قرار نیست کار خاصی پیچیده انجام دهیم.

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program

عالی. تست‌ها را اجرا می‌کنیم و همه چیز پاس می‌شود، پس آماده‌ایم کارمان را با بقیه تیم به‌اشتراک بگذاریم:

$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master

همین بود! اگر به مخزن Mercurial نگاهی بیندازید، خواهید دید که این همان چیزی را انجام داده که انتظار داشتیم:

$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard 'hello, world' program

چِنج‌ست شماره 2 توسط Mercurial ساخته شده بود، و چِنج‌ست‌های شماره‌های 3 و 4 توسط git-remote-hg ساخته شده‌اند، با پوش کردن کامیت‌هایی که با Git ساخته شده بودند.

شاخه‌ها و بوک‌مارک‌ها (Branches and Bookmarks)

Git فقط یک نوع شاخه دارد: یک ارجاع که هنگام ساختن کامیت‌ها جابجا می‌شود. در Mercurial، این نوع ارجاع «بوک‌مارک» (bookmark) نامیده می‌شود و رفتار آن تا حد زیادی شبیه شاخه در Git است.

مفهوم «شاخه» در مرکوریال سنگین‌تر است. شاخه‌ای که یک changeset روی آن ساخته شده است همراهِ خود changeset ثبت می‌شود؛ یعنی همیشه در تاریخچهٔ مخزن خواهد ماند. در اینجا مثالی از یک commit را می‌بینید که روی شاخهٔ develop ساخته شده است:

$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation

خطی را که با «branch» شروع می‌شود، توجه کنید. گیت واقعاً نمی‌تواند این را بازتولید کند (و نیازی هم ندارد؛ هر دو نوع شاخه را می‌توان به‌صورت یک ref گیت نمایش داد)، اما git-remote-hg باید این تمایز را بفهمد، چون مرکوریال برایش اهمیت دارد.

ایجاد bookmarks در مرکوریال به همان سادگی ایجاد شاخه‌های گیت است. در سمت گیت:

$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA

همین بود. در سمت مرکوریال، شبیه این به نظر می‌رسد:

$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard 'hello, world' program

برچسب جدید [featureA] روی بازبینی ۵ را مشاهده کنید. اینها در سمت گیت دقیقاً مثل شاخه‌های گیت عمل می‌کنند، با یک استثنا: شما نمی‌توانید یک bookmark را از سمت گیت حذف کنید (این محدودیتی از طرف remote helperها است).

شما همچنین می‌توانید روی یک شاخهٔ «سنگین‌وزن» مرکوریال کار کنید: فقط یک شاخه را در فضای نام branches قرار دهید:

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent

در سمت مرکوریال این‌طور دیده می‌شود:

$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]

نام شاخهٔ «permanent» همراه با changeset شمارهٔ ۷ ثبت شده بود.

از سمت گیت، کار کردن با هر یک از این سبک‌های شاخه یکسان است: کافی است مثل همیشه checkout، commit، fetch، merge، pull و push کنید. یک نکته که باید بدانید این است که مرکوریال بازنویسی تاریخچه را پشتیبانی نمی‌کند و تنها افزودن به آن را ممکن می‌سازد. این تصویری است از مخزن مرکوریال ما پس از یک rebase تعاملی و یک force-push:

$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

changesetهای ۸، ۹ و ۱۰ ایجاد شده و به شاخهٔ permanent تعلق دارند، اما changesetهای قدیمی همچنان وجود دارند. این می‌تواند برای هم‌تیمی‌های شما که از مرکوریال استفاده می‌کنند بسیار گیج‌کننده باشد، پس سعی کنید از این کار پرهیز کنید.

خلاصهٔ مرکوریال (Mercurial Summary)

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

گیت و پرفورس (Git and Perforce)

perforce یک سیستم کنترل نسخه بسیار محبوب در محیط‌های شرکتی است. این سیستم از سال ۱۹۹۵ وجود داشته است، که آن را قدیمی‌ترین سیستم پوشش داده شده در این فصل می‌کند. به این ترتیب، با محدودیت‌های زمان خود طراحی شده است؛ فرض می‌کند که شما همیشه به یک سرور مرکزی متصل هستید و فقط یک نسخه روی دیسک محلی نگهداری می‌شود. مطمئناً ویژگی‌ها و محدودیت‌های آن برای چندین مشکل خاص مناسب هستند، اما پروژه‌های زیادی وجود دارند که از Perforce استفاده می‌کنند در حالی که Git در واقع بهتر عمل می‌کند.

اگر می‌خواهید استفاده از پرفورس و گیت را با هم ترکیب کنید، دو گزینه وجود دارد. اولین موردی که بررسی می‌کنیم، پل "Git Fusion" از سازندگان Perforce است که به شما امکان می‌دهد زیردرخت‌های مخزن Perforce خود را به عنوان مخازن Git خواندنی-نوشتنی در معرض دید قرار دهید.

گیت فیوژن (Git Fusion)

محصولی به نام Git Fusion (موجود در https://www.perforce.com/manuals/git-fusion/) ارائه می‌دهد که سرور Perforce را با مخازن Git در سمت سرور همگام‌سازی می‌کند.

راه‌اندازی (Setting Up)

برای مثال‌های ما، ساده‌ترین روش نصب Git Fusion را که دانلود یک ماشین مجازی است که دیمون پرفورس و Git Fusion را اجرا می‌کند، استفاده خواهیم کرد. می‌توانید تصویر ماشین مجازی را از https://www.perforce.com/downloads دریافت کنید و پس از اتمام دانلود، آن را در نرم‌افزار مجازی‌سازی مورد علاقه خود (ما از VirtualBox استفاده خواهیم کرد) وارد کنید.

هنگام راه‌اندازی اولیه دستگاه، از شما می‌خواهد که رمز عبور سه کاربر لینوکس (root، perforce و git) را سفارشی کنید و نامی برای نمونه ارائه دهید که می‌توان از آن برای تمایز این نصب از سایر نصب‌ها در همان شبکه استفاده کرد. وقتی همه اینها کامل شد، این را خواهید دید:

The Git Fusion virtual machine boot screen
نمودار 171. The Git Fusion virtual machine boot screen

باید آدرس IP که اینجا نشان داده شده را یادداشت کنید، بعداً از آن استفاده خواهیم کرد. بعداً، یک کاربر Perforce ایجاد خواهیم کرد. گزینه "ورود" را در پایین انتخاب کنید و اینتر بزنید (یا از طریق SSH به دستگاه متصل شوید) و با نام کاربری "root" وارد شوید. سپس از این دستورات برای ایجاد یک کاربر استفاده کنید:

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

اولین مورد یک ویرایشگر VI را برای سفارشی کردن کاربر باز می‌کند، اما می‌توانید با تایپ :wq و زدن اینتر، مقادیر پیش‌فرض را بپذیرید. دومی از شما می‌خواهد که دو بار رمز عبور وارد کنید. این تمام کاری است که با یک اعلان پوسته باید انجام دهیم، بنابراین از جلسه خارج شوید.

مرحله بعدی که برای ادامه کار باید انجام دهید این است که به گیت بگویید گواهی‌های SSL را تأیید نکند. تصویر گیت فیوژن همراه با گواهی‌نامه ارائه می‌شود، اما این گواهی‌نامه برای دامنه‌ای است که با آدرس IP ماشین مجازی شما مطابقت نخواهد داشت، بنابراین گیت اتصال HTTPS را رد خواهد کرد. اگر این نصب دائمی خواهد بود، برای نصب گواهی‌نامه دیگری به راهنمای Perforce Git Fusion مراجعه کنید؛ برای اهداف مثال ما، این کافی خواهد بود:

$ export GIT_SSL_NO_VERIFY=true

حالا می‌توانیم بررسی کنیم که همه چیز درست کار می‌کند.

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

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

پیکربندی ادغام (Fusion Configuration)

پس از نصب Git Fusion، می‌خواهید پیکربندی را تنظیم کنید. این کار در واقع با استفاده از کلاینت مورد علاقه Perforce شما نسبتاً آسان است؛ فقط دایرکتوری //.git-fusion را در سرور Perforce به فضای کاری خود مپ کنید. ساختار فایل به این صورت است:

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]
│
├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

دایرکتوری objects به صورت داخلی توسط Git Fusion برای نگاشت اشیاء پرفورس به گیت و بالعکس استفاده می‌شود، نیازی نیست با چیزی در آنجا دستکاری کنید. یک فایل پیکربندی جهانی p4gf_config در این دایرکتوری وجود دارد، همچنین یک فایل برای هر مخزن – اینها فایل‌های پیکربندی هستند که نحوه عملکرد Git Fusion را تعیین می‌کنند. بیایید نگاهی به فایل موجود در ریشه بیندازیم:

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

ما در اینجا به معانی این فلگ‌ها نمی‌پردازیم، اما توجه داشته باشید که این فقط یک فایل متنی با فرمت INI است، مشابه آنچه Git برای پیکربندی استفاده می‌کند. این فایل گزینه‌های global را مشخص می‌کند، که بعداً می‌توانند توسط فایل‌های پیکربندی خاص هر مخزن، مثل repos/Talkhouse/p4gf_config، بازنویسی شوند. اگر این فایل را باز کنید، یک بخش [@repo] خواهید دید با برخی تنظیمات که با مقادیر پیش‌فرض global متفاوت هستند. همچنین بخش‌هایی خواهید دید که شبیه به این هستند:

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

این یک نگاشت بین یک شاخه Perforce و یک شاخه Git است. این بخش را می‌توانید هر طور که می‌خواهید نامگذاری کنید، به شرطی که نام آن منحصر به فرد باشد. git-branch-name به شما امکان می‌دهد مسیر یک انبار که تحت گیت دست و پاگیر خواهد بود را به نامی دوستانه‌تر تبدیل کنید. تنظیم view نحوه نقشه‌برداری فایل‌های پرفورس به مخزن گیت را با استفاده از نحو استاندارد نقشه‌برداری نما کنترل می‌کند. بیش از یک نگاشت را می‌توان مشخص کرد، مانند این مثال:

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

به این ترتیب، اگر نقشه‌برداری فضای کاری عادی شما شامل تغییراتی در ساختار دایرکتوری‌ها می‌شود، می‌توانید آن را با یک مخزن گیت تکرار کنید.

آخرین فایلی که بررسی خواهیم کرد، users/p4gf_usermap است که کاربران پرفورس را به کاربران گیت نگاشت می‌کند و ممکن است حتی به آن نیاز نداشته باشید. هنگام تبدیل یک مجموعه تغییرات Perforce به یک commit Git، رفتار پیش‌فرض Git Fusion این است که کاربر Perforce را جستجو کند و از آدرس ایمیل و نام کامل ذخیره‌شده در آنجا برای فیلد نویسنده/کامیت‌کننده در Git استفاده کند. هنگام تبدیل در جهت دیگر، پیش‌فرض این است که کاربر Perforce را با آدرس ایمیل ذخیره‌شده در فیلد نویسنده کامیت گیت جستجو کنید و تغییرات را به عنوان آن کاربر (با اعمال مجوزها) ارسال کنید. در بیشتر موارد، این رفتار کاملاً مناسب خواهد بود، اما فایل نگاشت زیر را در نظر بگیرید:

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

هر خط به فرمت `<user> <email> "<full name>" است و یک نگاشت کاربری ایجاد می‌کند. دو خط اول دو آدرس ایمیل مجزا را به یک حساب کاربری Perforce یکسان نگاشت می‌کنند. این مفید است اگر شما کامیت‌های گیت را تحت چندین آدرس ایمیل مختلف (یا آدرس‌های ایمیل تغییر یافته) ایجاد کرده‌اید، اما می‌خواهید آنها به یک کاربر پرفورس یکسان نگاشت شوند. هنگام ایجاد یک commit گیت از یک changeset پرفورس، اولین خطی که با کاربر پرفورس مطابقت دارد، برای اطلاعات نویسندگی گیت استفاده می‌شود.

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

گردش کار (Workflow)

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

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

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

همانطور که می‌بینید، مخزن ما دقیقاً شبیه هر مخزن گیت دیگری است که ممکن است با آن کار کنید. سه شاخه وجود دارد و گیت به طور مفید یک شاخه محلی master ایجاد کرده است که origin/master را دنبال می‌کند. بیایید کمی کار کنیم و چند کامیت جدید ایجاد کنیم:

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

ما دو کامیت جدید داریم. حالا بیایید بررسی کنیم که آیا کسی دیگری هم کار کرده است:

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

به نظر می‌رسد کسی بوده! از این نما متوجه نمی‌شوید، اما کامیت 6afeb15 در واقع با استفاده از یک کلاینت پرفورس ایجاد شده است. از دیدگاه گیت، این فقط یک کامیت دیگر به نظر می‌رسد، که دقیقاً همین منظور است. بیایید ببینیم سرور پرفورس چگونه با یک کامیت ادغام برخورد می‌کند:

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

گیت فکر می‌کند که کار کرده است. بیایید تاریخچه فایل README را از دیدگاه پرفورس، با استفاده از ویژگی نمودار بازبینی p4v بررسی کنیم:

Perforce revision graph resulting from Git push
نمودار 172. Perforce revision graph resulting from Git push

قبلاً این نما را ندیده‌اید، ممکن است گیج‌کننده به نظر برسد، اما همان مفاهیمی را نشان می‌دهد که یک نمایشگر گرافیکی برای تاریخچه گیت نشان می‌دهد. ما در حال بررسی تاریخچه فایل README هستیم، بنابراین درختچه دایرکتوری در بالا سمت چپ فقط آن فایل را همانطور که در شاخه‌های مختلف ظاهر می‌شود، نشان می‌دهد. در بالا سمت راست، نمودار بصری از نحوه ارتباط نسخه‌های مختلف فایل را داریم و نمای کلی این نمودار در پایین سمت راست قرار دارد. بقیه نما به نمای جزئیات برای بازبینی انتخاب شده (در این مورد 2) اختصاص داده می‌شود.

یک نکته قابل توجه این است که نمودار دقیقاً شبیه نمودار تاریخچه گیت است. Perforce شاخه نامگذاری شده‌ای برای ذخیره کامیت‌های 1 و 2 نداشت، بنابراین یک شاخه "بی‌نام" در دایرکتوری .git-fusion برای نگهداری آن ایجاد کرد. این اتفاق برای شاخه‌های گیت نام‌گذاری شده‌ای که با شاخه پرفورس نام‌گذاری شده‌ای مطابقت ندارند نیز رخ خواهد داد (و می‌توانید بعداً آن‌ها را با استفاده از فایل پیکربندی به یک شاخه پرفورس نگاشت دهید).

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

خلاصه Git-Fusion (Git-Fusion Summary)

اگر به سرور پرفورس خود دسترسی دارید (یا می‌توانید به آن دسترسی پیدا کنید)، گیت فیوژن راهی عالی برای برقراری ارتباط بین گیت و پرفورس است. کمی پیکربندی لازم است، اما منحنی یادگیری خیلی تند نیست. این یکی از معدود بخش‌های این فصل است که در آن هشدارهایی در مورد استفاده از قدرت کامل گیت ظاهر نمی‌شود. این به این معنی نیست که Perforce از هر چیزی که به آن می‌دهید راضی خواهد بود - اگر سعی کنید تاریخچه‌ای را که قبلاً push شده است بازنویسی کنید، Git Fusion آن را رد خواهد کرد - اما Git Fusion بسیار تلاش می‌کند تا حس بومی بودن را القا کند. حتی می‌توانید از زیرماژول‌های گیت استفاده کنید (اگرچه برای کاربران پرفورس عجیب به نظر می‌رسند) و شاخه‌ها را ادغام کنید (این به عنوان یکپارچه‌سازی در سمت پرفورس ثبت می‌شود).

اگر نمی‌توانید مدیر سرور خود را متقاعد کنید که Git Fusion را راه‌اندازی کند، هنوز راهی برای استفاده از این ابزارها با هم وجود دارد.

Git-p4

Git-p4 یک پل دوطرفه بین گیت و پرفورس است. این کاملاً در داخل مخزن گیت شما اجرا می‌شود، بنابراین به هیچ نوع دسترسی به سرور پرفورس (به جز اعتبارنامه‌های کاربری، البته) نیاز نخواهید داشت. Git-p4 به اندازه Git Fusion راه‌حل انعطاف‌پذیر یا کاملی نیست، اما به شما امکان می‌دهد بیشتر کارهایی را که می‌خواهید انجام دهید، بدون اینکه به محیط سرور آسیب برسانید، انجام دهید.

یادداشت

برای کار با git-p4 لازم است ابزار p4 در یکی از مسیرهای موجود در متغیر PATH شما قرار داشته باشد. تا زمان نگارش این متن، این ابزار به‌صورت رایگان در آدرس https://www.perforce.com/downloads/helix-command-line-client-p4 در دسترس است.

تنظیمات اولیه (Setting Up)

برای مثال، سرور Perforce را از OVA مربوط به Git Fusion اجرا خواهیم کرد همان‌طور که بالاتر نشان داده شد، اما از سرور Git Fusion عبور کرده و مستقیماً به کنترل نسخه Perforce متصل می‌شویم.

برای استفاده از کلاینت خط‌دستور p4 (که git-p4 به آن وابسته است)، باید چند متغیر محیطی را تنظیم کنید:

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
شروع کار (Getting Started)

مانند هر چیز دیگری در گیت، اولین فرمان clone است:

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

این کاری انجام می‌دهد که در اصطلاح گیت یک clone "shallow" نامیده می‌شود؛ تنها جدیدترین بازبینی Perforce به گیت وارد می‌شود؛ به یاد داشته باشید که Perforce برای ارائه همه بازبینی‌ها به هر کاربر طراحی نشده است. این برای استفاده از گیت به‌عنوان یک کلاینت Perforce کافی است، اما برای سایر مقاصد کافی نیست.

پس از اتمام، یک مخزن گیت کاملاً کاربردی خواهیم داشت:

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

توجه کنید که یک remote با نام “p4” برای سرور Perforce وجود دارد، اما در بقیه موارد همه‌چیز شبیه یک clone معمولی به نظر می‌رسد. در واقع این کمی گمراه‌کننده است؛ در حقیقت آن remote به‌صورت واقعی وجود ندارد.

$ git remote -v

در این مخزن اصلاً remote‌ای وجود ندارد. Git-p4 تعدادی ref ایجاد کرده که وضعیت سرور را نشان می‌دهند و در خروجی git log شبیه refهای remote به نظر می‌رسند، اما این‌ها توسط خود گیت مدیریت نمی‌شوند و شما نمی‌توانید روی آن‌ها push کنید.

جریان کاری (Workflow)

خب، بیایید کار کنیم. فرض کنیم پیشرفتی در پیاده‌سازی یک قابلیت بسیار مهم داشته‌اید و اکنون آماده‌اید آن را به تیم خود نشان دهید.

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

ما دو commit جدید ساخته‌ایم که آماده ارسال به سرور Perforce هستند. بیایید بررسی کنیم ببینیم امروز کسی دیگر هم کار کرده است یا نه:

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

به نظر می‌رسد که چنین بوده و شاخه‌های master و p4/master از هم فاصله گرفته‌اند. سیستم شاخه‌بندی Perforce اصلاً شبیه گیت نیست، بنابراین ارسال merge commitها بی‌معنی است. Git-p4 توصیه می‌کند که commitهای خود را rebase کنید، و حتی یک میان‌بر برای انجام این کار فراهم می‌کند:

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
 شاید از خروجی بتوانید بفهمید، اما `git p4 rebase` در واقع کوتاه‌شدهٔ `git p4 sync` به دنبال `git rebase p4/master` است.
این فرمان کمی از این هم هوشمندتر است، به‌ویژه هنگام کار با شاخه‌های متعدد، اما این تقریب خوبی است.

حال تاریخچهٔ ما دوباره خطی شده و آماده‌ایم تغییرات‌مان را به Perforce بازگردانیم. فرمان git p4 submit سعی می‌کند برای هر کامیت گیتی که بین p4/master و master قرار دارد یک بازنگری (revision) جدید در Perforce بسازد. اجرای آن ما را داخل ویرایشگر دلخواه‌مان می‌برد و محتوای فایل چیزی شبیه به این خواهد بود:

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

این تقریباً همان محتوایی است که با اجرای p4 submit می‌دیدید، به‌جز مواردی در انتها که git-p4 با لطف اضافه کرده است. Git-p4 تلاش می‌کند هنگام نیاز به نام‌گذاری برای یک کامیت یا changeset، تنظیمات جداگانهٔ Git و Perforce شما را رعایت کند، اما در برخی موارد ممکن است بخواهید آن را بازنویسی کنید. مثلاً اگر کامیت Git که وارد می‌کنید توسط مشارکتی نوشته شده که حساب کاربری Perforce ندارد، ممکن است بخواهید changeset حاصل به‌گونه‌ای نشان بدهد که آن مشارکت‌کننده آن را نوشته (نه شما).

Git-p4 پیام کامیت Git را به‌عنوان محتوای این changeset در Perforce وارد کرده است، بنابراین تنها کاری که باید بکنیم ذخیره و خروج است، دو بار (یک بار برای هر کامیت). خروجی شل حاصل چیزی شبیه به این خواهد بود:

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

نتیجه شبیه به این است که تازه یک git push انجام داده‌ایم، که نزدیک‌ترین تشبیه به آن چیزی است که واقعاً اتفاق افتاده.

توجه کنید که در این فرآیند هر کامیت Git به یک changeset در Perforce تبدیل می‌شود؛ اگر می‌خواهید آن‌ها را فشرده کرده و به یک changeset واحد تبدیل کنید، می‌توانید قبل از اجرای git p4 submit از یک rebase تعاملی استفاده کنید. همچنین توجه داشته باشید که هش‌های SHA-1 همهٔ کامیت‌هایی که به‌عنوان changeset ارسال شده‌اند تغییر کرده‌اند؛ دلیلش این است که git-p4 یک خط به انتهای هر کامیتی که تبدیل می‌کند اضافه می‌کند:

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

چه اتفاقی می‌افتد اگر سعی کنید یک کامیت merge را ارسال کنید؟ بیایید امتحان کنیم. این وضعیتی است که خودمان را در آن قرار داده‌ایم:

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head
 تاریخچه‌ی Git و Perforce بعد از `775a46f` از هم جدا شده‌اند.
سمت Git دو کامیت دارد، سپس یک کامیت merge با headِ Perforce و بعد یک کامیت دیگر.
ما قصد داریم این‌ها را روی یک changeset واحد در سمت Perforce ارسال کنیم.
بیایید ببینیم اگر الآن بخواهیم submit کنیم چه اتفاقی می‌افتد:
$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

فلگ -n مخفف --dry-run است و تلاش می‌کند گزارش دهد که اگر فرمان submit واقعاً اجرا شود چه رخ خواهد داد. در این مورد، به نظر می‌رسد که سه changeset در Perforce ایجاد خواهیم کرد، که متناظر با سه کامیت غیر-مرج هستند که هنوز روی سرور Perforce وجود ندارند. این دقیقاً همان چیزی است که می‌خواهیم، ببینیم نتیجه چگونه خواهد بود:

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

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

شاخه‌بندی (Branching)

اگر پروژه‌ی Perforce شما چندین شاخه دارد، هنوز هم راهی وجود دارد؛ git-p4 می‌تواند آن را به‌گونه‌ای مدیریت کند که حس Git را بدهد. فرض کنید depotِ Perforce شما به این صورت سازمان‌دهی شده است:

//depot
  └── project
      ├── main
      └── dev

و فرض کنید شاخه‌ای به نام dev دارید که view spec آن چنین به‌نظر می‌رسد:

//depot/project/main/... //depot/project/dev/...

git-p4 می‌تواند آن وضعیت را به‌طور خودکار تشخیص دهد و کار درست را انجام دهد:

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

به رشته‌ی “@all” در مسیر depot توجه کنید؛ این به git-p4 می‌گوید که نه تنها آخرین changeset آن زیرشاخه را کلون کند، بلکه همه‌ی changesetهایی را که تاکنون آن مسیرها را لمس کرده‌اند، کلون کند. این نزدیک‌تر به مفهوم clone در Git است، اما اگر روی پروژه‌ای با تاریخچه‌ی طولانی کار می‌کنید، ممکن است مدتی طول بکشد.

فلگ --detect-branches به git-p4 می‌گوید که از branch specهای Perforce برای نگاشت شاخه‌ها به refsهای Git استفاده کند. اگر این نگاشت‌ها روی سرور Perforce حاضر نباشند (که راهی کاملاً معتبر برای استفاده از Perforce است)، می‌توانید به git-p4 بگویید نگاشت شاخه‌ها چیست، و همان نتیجه را خواهید گرفت:

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

تنظیم متغیر پیکربندی git-p4.branchList به مقدار main:dev به git-p4 می‌گوید که «main» و «dev» هر دو شاخه هستند و شاخهٔ دوم فرزندِ شاخهٔ اول است.

اگر اکنون با فرمان git checkout -b dev p4/project/dev شاخهٔ dev را بسازیم و چند کامیت انجام دهیم، git-p4 به‌اندازهٔ کافی هوشمند است که هنگام اجرای git p4 submit هدف را روی شاخهٔ درست بگذارد. متأسفانه git-p4 نمی‌تواند کلون‌های کم‌عمق (shallow) را با چند شاخه ترکیب کند؛ اگر پروژهٔ بزرگی دارید و می‌خواهید روی بیش از یک شاخه کار کنید، باید برای هر شاخه‌ای که می‌خواهید به آن submit کنید یک‌بار git p4 clone اجرا کنید.

برای ایجاد یا ادغام شاخه‌ها باید از یک کلاینت Perforce استفاده کنید. Git-p4 فقط می‌تواند به شاخه‌های موجود همگام‌سازی (sync) و submit انجام دهد، و تنها می‌تواند یک تغییرمجموعهٔ خطی (linear changeset) را در یک زمان منتقل کند. اگر در Git دو شاخه را ادغام کنید و بخواهید تغییرمجموعهٔ جدید را submit کنید، تنها چیزی که ثبت خواهد شد مجموعه‌ای از تغییرات فایل‌ها است؛ فراداده‌ای دربارهٔ اینکه کدام شاخه‌ها در ادغام دخیل بوده‌اند از دست خواهد رفت.

Git and Perforce Summary (خلاصهٔ Git و Perforce)

Git-p4 امکان استفاده از جریان کاری Git با یک سرور Perforce را فراهم می‌کند و در این کار نسبتاً خوب است. با این حال، مهم است که به خاطر داشته باشید Perforce مأمور اصلی نگهداری کد است و شما صرفاً از Git برای کار محلی استفاده می‌کنید. در خصوص اشتراک‌گذاری کامیت‌های Git خیلی محتاط باشید؛ اگر یک مخزن راه دور دارید که دیگران هم از آن استفاده می‌کنند، کامیت‌هایی را که هنوز به سرور Perforce submit نشده‌اند push نکنید.

اگر می‌خواهید آزادانه از هر دو Perforce و Git به‌عنوان کلاینت برای کنترل نسخه استفاده کنید و بتوانید مدیر سرور را قانع کنید آن را نصب کند، Git Fusion استفاده از Git را به‌عنوان یک کلاینت کنترل نسخهٔ درجه‌یک برای سرور Perforce ممکن می‌سازد.

scroll-to-top