Chapters ▾ 2nd Edition

10.6 (Git Internals) - پروتکل‌های انتقال (Transfer Protocols)

پروتکل‌های انتقال (Transfer Protocols)

Git می‌تواند داده‌ها را بین دو repository به دو روش اصلی منتقل کند: پروتکل “dumb” و پروتکل “smart”. این بخش به‌طور سریع توضیح می‌دهد که این دو پروتکل اصلی چطور عمل می‌کنند.

پروتکل ساده (The Dumb Protocol)

اگر می‌خواهید یک repository را فقط به صورت read-only از طریق HTTP سرو کنید، به احتمال زیاد از پروتکل dumb استفاده می‌شود. به این پروتکل “dumb” گفته می‌شود چون در سمت سرور هیچ کد اختصاصی مربوط به Git در طول فرآیند انتقال نیاز ندارد؛ فرآیند fetch مجموعه‌ای از درخواست‌های HTTP GET است که در آن کلاینت می‌تواند ساختار repository روی سرور را فرض بگیرد.

یادداشت

این پروتکل امروزه بسیار به‌ندرت استفاده می‌شود. ایمن‌سازی یا خصوصی‌سازی آن سخت است، به همین دلیل بیشتر میزبان‌های Git (چه ابری و چه on-premises) استفاده از آن را رد می‌کنند. به‌طور کلی توصیه می‌شود از پروتکل smart استفاده کنید که کمی جلوتر توضیح داده می‌شود.

بیایید فرآیند http-fetch برای کتابخانه simplegit را دنبال کنیم:

$ git clone http://server/simplegit-progit.git

اولین کاری که این دستور انجام می‌دهد، دریافت فایل info/refs است. این فایل توسط دستور update-server-info نوشته می‌شود، به همین دلیل باید آن را به‌عنوان یک post-receive hook فعال کنید تا انتقال HTTP به‌درستی کار کند:

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

حالا لیستی از remote references و مقادیر SHA-1 دارید. سپس بررسی می‌کنید که HEAD reference چیست تا بدانید در پایان باید چه چیزی را checkout کنید:

=> GET HEAD
ref: refs/heads/master

در اینجا باید پس از پایان فرآیند، branch master را checkout کنید. اکنون آماده شروع فرآیند walking هستید. از آنجا که نقطه شروع شما همان commit object ca82a6 است که در فایل info/refs دیدید، کار را با دریافت آن آغاز می‌کنید:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

یک object دریافت می‌کنید – این object روی سرور به صورت loose format است و شما آن را از طریق یک درخواست ساده HTTP GET گرفته‌اید. می‌توانید آن را با zlib از حالت فشرده خارج کنید، header را جدا کنید و محتوای commit را ببینید:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700

Change version number

سپس باید دو object دیگر دریافت کنید: cfda3b که tree of content مربوط به commit است، و 085bb3 که parent commit است:

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

اینجا commit object بعدی را دارید. حالا tree object را بگیرید:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

اوه – به‌نظر می‌رسد این tree object روی سرور به صورت loose format نیست، بنابراین پاسخ 404 دریافت می‌کنید. دلایل احتمالی این موضوع: object ممکن است در یک alternate repository باشد یا در یک packfile در همین مخزن. Git ابتدا وجود هر alternate را بررسی می‌کند:

=> GET objects/info/http-alternates
(empty file)

اگر لیستی از آدرس‌های alternate برگردانده شود، Git در آن‌ها به دنبال loose files و packfiles می‌گردد – این مکانیزم خوبی برای پروژه‌هایی است که fork یکدیگر هستند تا روی دیسک objects را به‌اشتراک بگذارند. اما چون اینجا هیچ alternate‌ای لیست نشده، object شما باید در یک packfile باشد. برای دیدن اینکه چه packfiles روی سرور موجود است، باید فایل objects/info/packs را بگیرید که لیستی از آن‌ها را دارد (این هم توسط update-server-info تولید می‌شود):

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

روی سرور فقط یک packfile وجود دارد، پس مشخص است object شما داخل آن است، اما باید index file را بررسی کنید تا مطمئن شوید. این بررسی همچنین زمانی مفید است که چندین packfile روی سرور وجود داشته باشد تا بفهمید کدام یک شامل object موردنظر شما است:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

حالا که packfile index را دارید، می‌توانید ببینید که آیا object شما در آن وجود دارد یا نه – چون این index لیست SHA-1s مربوط به objects موجود در packfile و آدرس‌های آن‌ها را دارد. چون object شما در آن وجود دارد، کل packfile را دریافت کنید:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

حالا tree object را دارید، پس ادامه می‌دهید و commits خود را پیمایش می‌کنید. همه آن‌ها نیز داخل همان packfile هستند، بنابراین نیازی به درخواست‌های بیشتر به سرور نیست. Git یک working copy از branch master که در ابتدای کار توسط HEAD reference مشخص شد، checkout می‌کند.

پروتکل هوشمند (The Smart Protocol)

پروتکل dumb ساده است اما کمی ناکارآمد بوده و نمی‌تواند داده‌ای از کلاینت به سرور بنویسد. پروتکل smart روشی رایج‌تر برای انتقال داده است، اما نیاز به پردازشی در سمت ریموت دارد که از Git آگاه باشد – بتواند داده‌های محلی را بخواند، بفهمد کلاینت چه دارد و چه نیاز دارد، و یک packfile سفارشی برای آن تولید کند. دو مجموعه فرآیند برای انتقال داده وجود دارد: یک جفت برای uploading data و یک جفت برای downloading data.

بارگذاری داده (Uploading Data)

برای upload data به یک فرآیند ریموت، Git از فرآیندهای send-pack و receive-pack استفاده می‌کند. فرآیند send-pack روی کلاینت اجرا شده و به فرآیند receive-pack در سمت ریموت متصل می‌شود.

پروتکل امن شل SSH (SSH)

برای مثال، اگر شما در پروژه‌تان دستور git push origin master را اجرا کنید و origin یک آدرس با پروتکل SSH باشد، Git فرآیند send-pack را اجرا کرده و یک اتصال SSH به سرور برقرار می‌کند. سپس سعی می‌کند دستوری روی سرور ریموت اجرا کند که شبیه به این است:

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master□report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000

دستور git-receive-pack بلافاصله یک خط برای هر reference موجود برمی‌گرداند – در این مثال فقط branch master و مقدار SHA-1 آن. اولین خط همچنین شامل لیستی از قابلیت‌های سرور است (اینجا: report-status, delete-refs و چند مورد دیگر از جمله شناسه کلاینت).

داده‌ها به صورت chunks منتقل می‌شوند. هر chunk با یک مقدار ۴ کاراکتری هگزادسیمال شروع می‌شود که طول chunk (شامل همان ۴ بایت اول) را مشخص می‌کند. معمولاً هر chunk شامل یک خط داده و یک linefeed پایانی است. اولین chunk شما با 00a5 شروع می‌شود که در مبنای هگز برابر با 165 است، یعنی طول chunk برابر 165 بایت است. chunk بعدی 0000 است، یعنی سرور لیست references خود را تمام کرده.

حالا که وضعیت سرور مشخص شد، فرآیند send-pack تعیین می‌کند چه commits‌ای دارد که سرور ندارد. برای هر reference که این push قرار است به‌روزرسانی کند، فرآیند send-pack آن اطلاعات را به receive-pack می‌فرستد. برای مثال، اگر شما در حال به‌روزرسانی branch master و اضافه‌کردن branch experiment باشید، پاسخ send-pack چیزی شبیه به این خواهد بود:

0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
	refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
	refs/heads/experiment
0000

Git برای هر reference که به‌روزرسانی می‌کنید یک خط می‌فرستد که شامل طول خط، old SHA-1، new SHA-1 و reference در حال به‌روزرسانی است. اولین خط همچنین قابلیت‌های کلاینت را دارد. مقدار SHA-1 پر از صفر (0000…​) یعنی قبلاً چیزی وجود نداشته – چون دارید یک reference جدید (experiment) اضافه می‌کنید. اگر در حال حذف یک reference باشید، حالت برعکس خواهد بود: سمت راست پر از صفر خواهد بود.

در مرحله بعد، کلاینت یک packfile شامل تمام objectsی که سرور ندارد ارسال می‌کند. در نهایت، سرور با یک پیام موفقیت (یا شکست) پاسخ می‌دهد:

000eunpack ok
پروتکل انتقال ابرمتن امن HTTPS (HTTP(S))

این فرآیند روی HTTP تقریباً مشابه است، فقط handshaking کمی متفاوت است. اتصال با این درخواست شروع می‌شود:

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master□report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

این پایان اولین تبادل client-server است. سپس کلاینت یک درخواست دیگر می‌فرستد، این بار یک POST، با داده‌ای که توسط send-pack تولید شده:

=> POST http://server/simplegit-progit.git/git-receive-pack

درخواست POST شامل خروجی send-pack و packfile به‌عنوان payload است. سرور در پاسخ، موفقیت یا شکست عملیات را مشخص می‌کند.

به خاطر داشته باشید که پروتکل HTTP ممکن است این داده‌ها را در قالب chunked transfer encoding نیز بسته‌بندی کند.

دانلود داده (Downloading Data)

وقتی داده دریافت می‌کنید، فرآیندهای fetch-pack و upload-pack درگیر هستند. کلاینت فرآیند fetch-pack را اجرا می‌کند که به فرآیند upload-pack روی سمت ریموت متصل می‌شود تا مذاکره کند چه داده‌ای باید منتقل شود.

پروتکل امن شل SSH (SSH)

اگر fetch را از طریق SSH انجام دهید، fetch-pack چیزی شبیه این اجرا می‌کند:

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

پس از اتصال fetch-pack، فرآیند upload-pack چیزی شبیه این برمی‌گرداند:

00dfca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master
0000

این بسیار شبیه پاسخی است که receive-pack می‌دهد، اما قابلیت‌ها متفاوت هستند. علاوه بر این، مشخص می‌کند که HEAD به چه چیزی اشاره می‌کند (symref=HEAD:refs/heads/master) تا کلاینت بداند اگر این یک clone باشد، باید چه چیزی را checkout کند.

در این مرحله، فرآیند fetch-pack بررسی می‌کند چه objects‌ای دارد و با ارسال “want” همراه با مقدار SHA-1، اعلام می‌کند که چه چیزی می‌خواهد. سپس با ارسال “have” همراه با مقادیر SHA-1، مشخص می‌کند چه چیزی را دارد. در پایان این لیست، با نوشتن “done” فرآیند upload-pack را شروع می‌کند تا packfile داده‌های موردنیاز ارسال شود:

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
پروتکل انتقال ابرمتن امن (HTTP(S))

handshake در عملیات fetch شامل دو درخواست HTTP است. اولی یک GET به همان endpointی است که در پروتکل dumb استفاده می‌شود:

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed no-done symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

این بسیار شبیه اجرای git-upload-pack از طریق SSH است، اما تبادل دوم به‌عنوان یک درخواست جداگانه انجام می‌شود:

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

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

خلاصه پروتکل‌ها (Protocols Summary)

این بخش یک مرور بسیار ابتدایی از پروتکل‌های انتقال داده بود. پروتکل ویژگی‌های بسیار بیشتری دارد، مثل قابلیت‌های multi_ack یا side-band، اما توضیح آن‌ها خارج از محدوده این کتاب است. ما سعی کردیم حسی کلی از تعاملات بین کلاینت و سرور به شما بدهیم؛ اگر به دانشی بیشتر از این نیاز دارید، احتمالاً باید به Git source code مراجعه کنید.

scroll-to-top