رفتن به محتوا
LoopX

کار با GitHub

توی این درس یاد می‌گیری GitHub چه چیزی بیشتر از «یک سرور Git» به تو می‌دهد و چطور یک مخزن در آن بسازی و مدیریت کنی: عمومی یا خصوصی، یک README که واقعاً به درد بخورد، LICENSE و .gitignore، و Issues برای ثبت باگ و ایده. می‌فهمی فرق مخزن خالی و مخزن با README چیست و چرا دومی ممکن است خطای refusing to merge unrelated histories بدهد (و با دو مخزن محلی آن را واقعاً می‌سازی و حل می‌کنی). با ابزار gh (مثل gh repo create --private و gh issue create) آشنا می‌شوی. تمرین: یک مخزن خصوصی با README و دو Issue بساز.

مسئله: Git فقط نیمی از ماجراست

Section titled “مسئله: Git فقط نیمی از ماجراست”

Git تاریخچه را نگه می‌دارد و بین کامپیوترها هماهنگ می‌کند. ولی یک تیم چیزهای دیگری هم لازم دارد: جایی برای گفت‌وگو درباره‌ی باگ‌ها و ایده‌ها، بازبینی کد قبل از ادغام، اجرای خودکار تست‌ها، مدیریت دسترسی (چه کسی بنویسد، چه کسی فقط بخواند)، و یک صفحه‌ی معرفی پروژه. GitHub این‌ها را روی یک مخزن Git می‌گذارد: Issues، Pull Request، Actions، Projects، Wiki، Releases و تنظیمات دسترسی. تو هنوز همان Git را داری؛ GitHub لایه‌ی همکاری رویش است.

مخزن Git مثل یک اتاق پر از پرونده است. GitHub آن اتاق را در یک ساختمان اداری می‌گذارد: دفتر ثبت درخواست‌ها (Issues)، اتاق جلسه برای بازبینی (Pull Request)، نگهبان که تعیین می‌کند چه کسی وارد شود (دسترسی‌ها)، و کارگرانی که هر صبح خودکار چیزها را کنترل می‌کنند (Actions). خود پرونده‌ها (کد و تاریخچه) همان‌هایی هستند که قبلاً داشتی.

GitHub روی مخزن Git چند لایه‌ی همکاری اضافه می‌کند. پایه همان Git است؛ مخزن را می‌توانی به هر سرور دیگری هم ببری بدون اینکه تاریخچه عوض شود.

از این درس به بعد بیشتر با ویژگی‌های GitHub سر و کار داریم که به حساب کاربری نیاز دارند و من به حساب تو وارد نمی‌شوم (ورود به حساب کار خودت است). پس هر چه به حساب GitHub وابسته است «نمونه (اجرا نشده)» برچسب دارد و دستورها از مستندات رسمی GitHub و gh گرفته شده‌اند. بخش‌هایی که فقط Git هستند (مثل رفتار مخزن خالی و مخزن با README) را با مخزن‌های bare محلی که نقش GitHub را بازی می‌کنند، واقعاً اجرا و تست کرده‌ام.

مثال ۱ (نمونه): ساختن مخزن در مرورگر

Section titled “مثال ۱ (نمونه): ساختن مخزن در مرورگر”

در GitHub: دکمه‌ی New repository (علامت + بالای صفحه). فرم این گزینه‌ها را دارد:

گزینه معنی توصیه
Owner / Repository name حساب یا سازمان و نام مخزن نام کوتاه، با خط تیره، انگلیسی (my-shop)؛ بدون فاصله
Description یک خط توضیح همیشه بنویس؛ در نتیجه‌ی جست‌وجو دیده می‌شود
Public / Private همه ببینند یا فقط افراد دعوت‌شده کار شخصی و ناتمام: Private؛ نمونه‌کار و متن‌باز: Public
Add a README file یک README.md اولیه می‌سازد بستگی دارد: مثال ۴
Add .gitignore قالب آماده برای یک زبان (مثلاً Node) بله، اگر پروژه‌ی تازه می‌سازی
Choose a license فایل LICENSE برای متن‌باز لازم (مثال ۶)

بعد از ساخت، GitHub صفحه‌ای با دستورهای اتصال نشان می‌دهد؛ مثال ۳ دقیقاً همان‌هاست.

مثال ۲ (نمونه): ساختن مخزن با gh

Section titled “مثال ۲ (نمونه): ساختن مخزن با gh”

gh ابزار خط فرمان رسمی GitHub است (جدا از git؛ باید نصب و با gh auth login وارد حسابت شوی). این دستورها را اجرا نکرده‌ام؛ شکل و گزینه‌ها از مستندات رسمی cli.github.com/manual است:

نمونه (اجرا نشده): gh repo create
# ساخت یک مخزن خصوصی از پوشه‌ی فعلی، اتصال remote به اسم origin و ارسال commit ها
gh repo create my-shop --private --source=. --remote=origin --push
# ساخت یک مخزن عمومی تازه با README و gitignore و license، و کلون کردنش
gh repo create my-shop --public --add-readme --gitignore Node --license mit --clone
# بدون آرگومان: پرسش‌وپاسخ تعاملی
gh repo create

گزینه‌های مهم (از مستندات): --private و --public (و --internal در سازمان‌های Enterprise)، -d/--description، --add-readme، -g/--gitignore قالب، -l/--license نام، -s/--source مسیر (از یک پوشه‌ی موجود)، -r/--remote نام، --push و -c/--clone. و برای وارد شدن به حساب: gh auth login (یک‌بار؛ مرورگر باز می‌شود).

مثال ۳: مخزن خالی و اتصال یک پروژه‌ی موجود

Section titled “مثال ۳: مخزن خالی و اتصال یک پروژه‌ی موجود”

وقتی مخزن را بدون README می‌سازی، GitHub یک مخزن کاملاً خالی (بدون هیچ commit ای) می‌دهد و این دستورها را نشان می‌دهد: «…or push an existing repository from the command line». آن‌ها را عیناً روی یک «GitHub» محلی (مخزن bare) اجرا می‌کنم:

Terminal window
mkdir -p ~/gitlab/github && cd ~/gitlab
git init -q --bare github/my-shop.git
echo "--- «GitHub»: مخزن خالی، هنوز هیچ commit ای ندارد:"
git -C github/my-shop.git log --oneline 2>&1 | head -1
echo "--- پروژه‌ی محلی که از قبل دارم:"
mkdir my-shop && cd my-shop && git init -q
echo "print('shop')" > app.py && git add app.py && git commit -qm "Add app"
git branch -M main
git remote add origin ~/gitlab/github/my-shop.git
git push -u origin main 2>&1 | tail -3
echo "--- حالا «GitHub» پروژه را دارد:"
git -C ~/gitlab/github/my-shop.git log --oneline
خروجی
--- «GitHub»: مخزن خالی، هنوز هیچ commit ای ندارد:
fatal: your current branch 'main' does not have any commits yet
--- پروژه‌ی محلی که از قبل دارم:
To /home/ali/gitlab/github/my-shop.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
--- حالا «GitHub» پروژه را دارد:
af0f74c Add app

سه دستور اصلی: git branch -M main (شاخه‌ی فعلی را به main تغییر نام بده؛ -M یعنی حتی اگر main از قبل بود)، git remote add origin آدرس و git push -u origin main. روی GitHub واقعی آدرس چیزی مثل git@github.com:USER/my-shop.git است (درس قبل).

مثال ۴: مخزن با README و دام «unrelated histories»

Section titled “مثال ۴: مخزن با README و دام «unrelated histories»”

اگر هنگام ساخت مخزن «Add a README file» را بزنی، GitHub همان لحظه یک commit اول (با README.md) در مخزن می‌سازد. حالا اگر یک پروژه‌ی محلی هم داری که تاریخچه‌ی خودش را دارد، دو تاریخچه‌ی بی‌ارتباط داری. این را این‌طور شبیه‌سازی می‌کنم: «GitHub» یک commit README دارد و پروژه‌ی من هم تاریخچه‌ی خودش را:

Terminal window
cd ~/gitlab
git init -q --bare github/with-readme.git
git clone -q github/with-readme.git /tmp/lx-web 2>/dev/null
(cd /tmp/lx-web && git config user.name "GitHub UI" && git config user.email "ui@example.com" && echo "# with-readme" > README.md && git add README.md && git commit -qm "Initial commit" && git push -q origin main)
rm -rf /tmp/lx-web
mkdir -p mine && cd mine && git init -q
echo "print('my code')" > app.py && git add app.py && git commit -qm "Add my code"
git remote add origin ~/gitlab/github/with-readme.git
echo "=== push:"
git push -u origin main 2>&1 | head -4
echo "=== pull:"
git pull origin main --no-rebase 2>&1 | tail -3
خروجی
=== push:
To /home/ali/gitlab/github/with-readme.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/home/ali/gitlab/github/with-readme.git'
hint: Updates were rejected because the remote contains work that you do not
=== pull:
* branch main -> FETCH_HEAD
* [new branch] main -> origin/main
fatal: refusing to merge unrelated histories

دو شکست پشت‌سرهم، و هر دو درست‌اند: ۱) push رد شد چون سرور commit ای دارد (README) که من ندارم. ۲) pull هم می‌گوید refusing to merge unrelated histories: این دو تاریخچه از ریشه‌ی متفاوت شروع شده‌اند (هیچ commit مشترکی ندارند) و Git نمی‌خواهد دو چیز بی‌ارتباط را بی‌خبر یکی کند. دو راه دارد (از بهتر به سریع‌تر):

Terminal window
cd ~/gitlab/mine
echo "=== راه ۱ (فقط اگر عمداً می‌خواهی دو تاریخچه یکی شوند): --allow-unrelated-histories"
git pull origin main --no-rebase --allow-unrelated-histories --no-edit 2>&1 | tail -3
git log --oneline --graph
git push -u origin main 2>&1 | tail -2
خروجی
=== راه ۱ (فقط اگر عمداً می‌خواهی دو تاریخچه یکی شوند): --allow-unrelated-histories
README.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 README.md
* 078fe32 Merge branch 'main' of /home/ali/gitlab/github/with-readme
|\
| * ee57b75 Initial commit
* d37e990 Add my code
ee57b75..078fe32 main -> main
branch 'main' set up to track 'origin/main'.

نتیجه یک commit ادغام با دو والد (هر دو ریشه) است. راه بهتر، که در پروژه‌ی تازه معمولاً همان است: یا مخزن را خالی بساز (مثال ۳) یا اول مخزن GitHub را کلون کن و فایل‌هایت را داخل آن بریز. هر دو یک تاریخچه‌ی واحد دارند:

Terminal window
cd ~/gitlab
git clone -q github/with-readme.git cloned
cd cloned
git config user.name "Ali" && git config user.email "ali@example.com"
echo "print('my code')" > app.py && git add app.py && git commit -qm "Add my code"
git push -q
git log --oneline --graph
خروجی
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
* 078fe32 Merge branch 'main' of /home/ali/gitlab/github/with-readme
|\
| * ee57b75 Initial commit
* d37e990 Add my code

مثال ۵ (نمونه): README.md، صفحه‌ی اول پروژه

Section titled “مثال ۵ (نمونه): README.md، صفحه‌ی اول پروژه”

GitHub فایل README.md را به‌صورت صفحه‌ی اول مخزن نشان می‌دهد (Markdown رندر می‌شود). README خوب به سه سؤال جواب می‌دهد: این چیست؟ چطور اجرا کنم؟ چطور کمک کنم؟ یک قالب کوتاه که می‌شود شروع کرد (فایل را می‌سازم و ببین):

Terminal window
mkdir -p ~/gitlab/readme-demo && cd ~/gitlab/readme-demo && git init -q
cat > README.md <<'EOF'
# My Shop
یک فروشگاه کوچک با Python: سبد خرید، تخفیف و فاکتور.
## نصب و اجرا
~~~bash
git clone git@github.com:USER/my-shop.git
cd my-shop
python3 app.py
~~~
## ساختار پروژه
- `app.py`: نقطه‌ی شروع
- `tests/`: تست‌ها (`python3 -m unittest`)
## مشارکت
Issue باز کن یا یک Pull Request بفرست. راهنمای کامل: `CONTRIBUTING.md`.
## مجوز
MIT
EOF
git add README.md && git commit -qm "Add README"
wc -l README.md
head -4 README.md
خروجی
24 README.md
# My Shop
یک فروشگاه کوچک با Python: سبد خرید، تخفیف و فاکتور.

چک‌لیست README خوب:

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

بلوک کد داخل README را برای اینکه با بلوک کد همین صفحه قاطی نشود با ~~~ نوشتم (Markdown و GitHub هر دو را می‌پذیرند؛ در README واقعی معمولاً ``` می‌نویسند). ظاهر رندرشده‌ی Markdown را فقط در GitHub می‌بینی؛ این‌جا ساختار فایل را ساختم.

بدون LICENSE، پروژه‌ی عمومی طبق قانون «همه‌ی حقوق محفوظ» است؛ یعنی دیگران حق استفاده، تغییر و بازتوزیع ندارند، حتی اگر کدت را ببینند. برای متن‌باز، یک مجوز انتخاب کن. هنگام ساخت مخزن در GitHub (یا با gh repo create --license) متن آماده‌ی آن اضافه می‌شود:

مجوز معنی (خلاصه) برای
MIT هر کاری بکنند؛ فقط اعلان کپی‌رایت و متن مجوز را نگه دارند ساده‌ترین، رایج‌ترین
Apache-2.0 مثل MIT + اعطای صریح حق ثبت اختراع (patent) پروژه‌های بزرگ‌تر یا شرکتی
GPL-3.0 هر نسخه‌ی تغییریافته که منتشر شود باید خودش هم متن‌باز با همین مجوز باشد (copyleft) وقتی می‌خواهی کد باز بماند
(بدون مجوز) همه‌ی حقوق محفوظ کد خصوصی

(این جدول یک خلاصه‌ی عمومی است و مشاوره‌ی حقوقی نیست؛ برای تصمیم مهم، متن کامل مجوز را بخوان.) و .gitignore هم از قالب‌های آماده‌ی GitHub می‌آید (درس .gitignore). فایل‌های خاصی که GitHub می‌شناسد و در پوشه‌ی .github/ می‌گذاری (فایل‌هایی عادی در مخزن‌اند، و GitHub در رابط خودش از آن‌ها استفاده می‌کند):

Terminal window
cd ~/gitlab/readme-demo
mkdir -p .github/ISSUE_TEMPLATE
cat > .github/ISSUE_TEMPLATE/bug_report.md <<'EOF'
---
name: Bug report
about: گزارش یک خطا
labels: bug
---
## چه اتفاقی افتاد؟
## چه انتظاری داشتی؟
## مراحل بازتولید
1.
2.
EOF
cat > .github/PULL_REQUEST_TEMPLATE.md <<'EOF'
## چه چیزی عوض شد؟
## چطور آزمایش کردی؟
EOF
git add .github && git commit -qm "Add issue and PR templates"
git ls-files
خروجی
.github/ISSUE_TEMPLATE/bug_report.md
.github/PULL_REQUEST_TEMPLATE.md
README.md

ISSUE_TEMPLATE/ قالب‌هایی برای باز کردن Issue، و PULL_REQUEST_TEMPLATE.md متن پیش‌فرض Pull Request است (درس بعد). این‌ها فقط فایل در مخزن‌اند؛ GitHub آن‌ها را (روی خودش) هنگام باز کردن Issue یا PR نشان می‌دهد، و من فقط ساخت فایل‌ها را آزمایش کرده‌ام.

مثال ۷: Issues، ثبت و ارجاع

Section titled “مثال ۷: Issues، ثبت و ارجاع”

Issue یک «برگه‌ی کار» است: یک باگ، ایده یا سؤال با عنوان، توضیح، برچسب (label)، مسئول (assignee) و ماه‌نقطه (milestone). هر Issue یک شماره می‌گیرد (#12) و می‌شود در همه‌جا (Issue دیگر، Pull Request، commit) به آن اشاره کرد. اگر در پیام commit بنویسی Fixes #12 (یا Closes #12، Resolves #12)، وقتی آن commit به شاخه‌ی پیش‌فرض برسد GitHub خودکار Issue را می‌بندد. این کلیدواژه‌ها را فقط GitHub تفسیر می‌کند؛ ولی چون خود شماره داخل پیام commit می‌ماند، می‌شود با Git هم ردشان را گرفت:

Terminal window
mkdir -p ~/gitlab/issues && cd ~/gitlab/issues && git init -q
for m in "Add login form (refs #3)" "Fix crash on empty cart. Fixes #7" "Update docs" "Validate email. Closes #3" "Fix typo (#7)"; do echo "$m" >> log.txt; git add log.txt; git commit -qm "$m"; done
echo "--- commit هایی که به Issue ها اشاره دارند:"
git log --oneline --grep='#[0-9]'
echo "--- همه‌ی commit های مربوط به Issue شماره‌ی 3:"
git log --oneline --grep='#3'
echo "--- فهرست یکتای Issue های ذکرشده:"
git log --format=%s | grep -oE '#[0-9]+' | sort -u
خروجی
--- commit هایی که به Issue ها اشاره دارند:
1be0a6b Fix typo (#7)
dd5048a Validate email. Closes #3
2e3e033 Fix crash on empty cart. Fixes #7
f0549a3 Add login form (refs #3)
--- همه‌ی commit های مربوط به Issue شماره‌ی 3:
dd5048a Validate email. Closes #3
f0549a3 Add login form (refs #3)
--- فهرست یکتای Issue های ذکرشده:
#3
#7

با همین git log --grep می‌شود یادداشت انتشار ساخت (درس tag: git log v1.0.0..v1.1.0). پس هر commit را به یک Issue وصل کن؛ شش ماه بعد معلوم است «چرا» این تغییر داده شد.

مثال ۸ (نمونه): gh issue در ترمینال

Section titled “مثال ۸ (نمونه): gh issue در ترمینال”

بدون رفتن به مرورگر، با gh Issue بساز و فهرست کن. این‌ها را اجرا نکرده‌ام (نیاز به ورود به حساب دارند)؛ گزینه‌ها طبق مستندات رسمی است:

نمونه (اجرا نشده): gh issue
# ساخت یک Issue با عنوان، توضیح و برچسب
gh issue create --title "صفحه‌ی ورود" --body "فرم ایمیل و رمز، با اعتبارسنجی" --label enhancement
# ساخت با ویرایشگر (عنوان و متن را آنجا می‌نویسی) یا در مرورگر
gh issue create --editor
gh issue create --web
# فهرست Issue های باز، دیدن یکی، بستنش
gh issue list
gh issue view 12
gh issue close 12 --comment "در PR شماره‌ی 15 درست شد"

گزینه‌های gh issue create (از مستندات): -t/--title، -b/--body، -F/--body-file، -l/--label، -a/--assignee، -m/--milestone، -p/--project، -e/--editor و -w/--web.

مثال ۹ (نمونه): همکاران و نقش‌ها

Section titled “مثال ۹ (نمونه): همکاران و نقش‌ها”

وقتی مخزن خصوصی است فقط تو و کسانی که دعوت می‌کنی آن را می‌بینند (Settings ← Collaborators). برای مخزن یک سازمان (Organization)، نقش‌ها دقیق‌ترند:

نقش چه کاری می‌تواند
Read کد را بخواند، Issue و Discussion باز کند
Triage Issue ها و PR ها را مدیریت کند (برچسب، بستن) بدون تغییر کد
Write به مخزن push کند، PR ادغام کند
Maintain مدیریت مخزن بدون تنظیمات حساس
Admin همه‌چیز: تنظیمات، حذف، دسترسی‌ها

(این نقش‌ها طبق مستندات GitHub برای مخزن‌های سازمان است؛ مخزن شخصی ساده‌تر است: مالک و همکاران با دسترسی نوشتن.) برای کمک‌کردن به پروژه‌ای که دسترسی نوشتن به آن نداری، fork می‌کنی (یک کپی از مخزن در حساب خودت) و از آن PR می‌فرستی (درس بعد).

پشت پرده: GitHub یک سرور Git به‌اضافه‌ی چیزهای دیگر

Section titled “پشت پرده: GitHub یک سرور Git به‌اضافه‌ی چیزهای دیگر”

پشت صحنه‌ی هر مخزن GitHub یک مخزن Git (bare) است، همان‌که در این درس ساختیم، و چیزهای اضافه: یک پایگاه داده برای Issue ها و گفت‌وگوها، یک وب‌سرور، سرویس Actions و… . مخزن Git خودش مستقل است: می‌توانی git clone کنی و روی GitLab یا سرور خودت ببری؛ فقط Issue ها و PR ها (که داده‌ی GitHub‌اند، نه Git) باید جداگانه منتقل شوند. برای همین تاریخچه‌ی Git ات هیچ‌وقت به GitHub وابسته نیست، ولی Issue هایت هستند؛ اگر مهم‌اند، گهگاه خروجی بگیر. یک جزئیات مهم: GitHub برای Pull Request ها شاخه‌های ویژه‌ای در خود مخزن می‌سازد (refs/pull/N/head)، که در درس بعد می‌بینی.

کار رابط وب gh
ساخت مخزن New repository gh repo create نام --private
کلون Code ← Clone gh repo clone USER/REPO
باز کردن مخزن در مرورگر gh repo view --web
ساخت Issue Issues ← New issue gh issue create
فهرست Issue Issues gh issue list
بستن Issue Close issue gh issue close شماره
شکل مخزن چه زمانی اتصال
خالی (بدون README) پروژه‌ی محلی داری git remote add و git push -u origin main
با README از صفر شروع می‌کنی git clone سپس کار کن
هر دو را قاطی کنی unrelated histories

۱) نام شاخه‌ی اشتباه در push

Section titled “۱) نام شاخه‌ی اشتباه در push”
Terminal window
mkdir -p ~/gitlab/branchname && cd ~/gitlab/branchname && git init -q
echo a > f && git add f && git commit -qm "Base"
git init -q --bare ../github/names.git
git remote add origin ~/gitlab/github/names.git
git branch --show-current
git push -u origin master 2>&1 | head -3
خروجی
main
error: src refspec master does not match any
error: failed to push some refs to '/home/ali/gitlab/github/names.git'

error: src refspec master does not match any: شاخه‌ی محلی main است ولی master را push کردی. (دستورهای قدیمی اینترنت اغلب master دارند.) راه‌حل: git branch --show-current را ببین و همان را بفرست؛ یا git branch -M main.

۲) مخزن خصوصی را عمومی بسازی

Section titled “۲) مخزن خصوصی را عمومی بسازی”

وقتی در فرم ساخت مخزن اشتباهاً Public را انتخاب می‌کنی، هر چه push کنی (حتی رمز) برای همه دیده می‌شود. راه‌حل: قبل از اولین push، نوع مخزن را دوباره بخوان؛ و در Settings ← Danger Zone ← Change visibility می‌شود عوضش کرد؛ ولی اگر رمزی لو رفته باید عوض شود (درس .gitignore). GitHub برای push هایی که کلید و توکن‌های شناخته‌شده دارند هشدار یا مسدودسازی (secret scanning) هم دارد؛ ولی به آن تکیه نکن.

اگر هر دو طرف (مخزن GitHub و پروژه‌ی محلی) یک README.md ساخته باشند، بعد از --allow-unrelated-histories یک conflict از نوع add/add می‌گیری (هر دو فایل با همین اسم ساخته‌اند، تمرین سخت). راه‌حل: مثل هر conflict، فایل را با دست ترکیب کن.

«کار نمی‌کند» عنوان نیست. راه‌حل: عنوان کوتاه و دقیق («Cart total is wrong when a coupon is applied»)، و در متن: چه کردی، چه انتظار داشتی، چه شد، نسخه و محیط. قالب ISSUE_TEMPLATE (مثال ۶) همین را تحمیل می‌کند.

۵) فراموش‌کردن .gitignore و LICENSE

Section titled “۵) فراموش‌کردن .gitignore و LICENSE”

مخزن را بدون .gitignore می‌سازی و node_modules را push می‌کنی؛ یا کد عمومی را بدون LICENSE می‌گذاری و کسی قانوناً نمی‌تواند استفاده کند. راه‌حل: هنگام ساخت مخزن هر دو را انتخاب کن.

✎ تمرینآسان

یک README.md برای یک پروژه‌ی فرضی بنویس که سه بخش «توضیح»، «نصب و اجرا» و «مجوز» داشته باشد، commit کن و تعداد خط‌ها را بشمار.

دیدن جواب
Terminal window
mkdir -p ~/gitlab/ex1 && cd ~/gitlab/ex1 && git init -q
printf '# Todo CLI\n\nیک برنامه‌ی ساده‌ی کارهای روزانه.\n\n## نصب و اجرا\n\n python3 todo.py add "خرید نان"\n\n## مجوز\n\nMIT\n' > README.md
git add README.md && git commit -qm "Add README"
wc -l README.md
grep -c '^## ' README.md
خروجی
11 README.md
2
✎ تمرینمتوسط

تمرین اصلی: یک مخزن خصوصی با README و دو Issue بساز. روی GitHub واقعی این کار به حساب نیاز دارد و من اجرایش نکرده‌ام؛ دستورهای gh و معادل مرورگر در پایین است (نمونه). بخش محلی‌اش را (README و commit هایی که به Issue ها اشاره دارند) واقعاً اجرا کردم.

دیدن جواب
نمونه (اجرا نشده): ساخت مخزن خصوصی و دو Issue
gh auth login # یک بار
mkdir my-private-repo && cd my-private-repo && git init -b main
printf '# My Private Repo\n\nتوضیح پروژه\n' > README.md
git add README.md && git commit -m "Add README"
gh repo create my-private-repo --private --source=. --remote=origin --push
gh issue create --title "Add login page" --body "Email and password form" --label enhancement
gh issue create --title "Fix typo in README" --body "The word 'Wellcome' is misspelled" --label bug
gh issue list

بخش محلی که اجرا کردم (README و commit هایی که به Issue ها اشاره دارند):

Terminal window
mkdir -p ~/gitlab/ex2 && cd ~/gitlab/ex2 && git init -q
printf '# My Private Repo\n\nتوضیح پروژه\n' > README.md
git add README.md && git commit -qm "Add README"
echo "login" > login.txt && git add login.txt && git commit -qm "Add login page. Closes #1"
sed -i 's/^توضیح/شرح/' README.md && git commit -qam "Fix README wording. Fixes #2"
git log --oneline
git log --grep='#' --format='%s'
خروجی
6b1da64 Fix README wording. Fixes #2
e93b20f Add login page. Closes #1
8ad8c1a Add README
Fix README wording. Fixes #2
Add login page. Closes #1
✎ تمرینسخت

دو تاریخچه‌ی بی‌ارتباط بساز که هر دو README.md دارند («GitHub» با README و پروژه‌ی محلی با README دیگر). با --allow-unrelated-histories ادغام کن، conflict نوع AA (add/add) را ببین و با ترکیب هر دو README حل کن.

دیدن جواب
Terminal window
cd ~/gitlab
git init -q --bare github/both.git
git clone -q github/both.git /tmp/lx-web2 2>/dev/null
(cd /tmp/lx-web2 && git config user.name "GitHub UI" && git config user.email "ui@example.com" && echo "# From GitHub" > README.md && git add README.md && git commit -qm "Initial commit" && git push -q origin main)
rm -rf /tmp/lx-web2
mkdir -p hard && cd hard && git init -q
echo "# From my laptop" > README.md && git add README.md && git commit -qm "Add my README"
git remote add origin ~/gitlab/github/both.git
git fetch -q
git merge origin/main --allow-unrelated-histories --no-edit 2>&1 | tail -2
git status -s
printf '# Project\n\nFrom GitHub and my laptop (merged).\n' > README.md
git add README.md && git commit -q --no-edit
git log --oneline --graph
خروجی
CONFLICT (add/add): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
AA README.md
* 5f287d2 Merge remote-tracking branch 'origin/main'
|\
| * fbedae3 Initial commit
* 448bf39 Add my README
⚡ بررسی سریع

مخزن را در GitHub با «Add a README file» ساخته‌ای و یک پروژه‌ی محلی با تاریخچه‌ی خودت داری. git pull با چه خطایی روبه‌رو می‌شود و چرا؟

؟ آزمونک
  1. فرق مخزن «خالی» و مخزن «با README» در GitHub؟

  2. اگر در پیام commit بنویسی «Fixes #12» و آن commit به شاخه‌ی پیش‌فرض برسد؟

  3. پروژه‌ی عمومی بدون فایل LICENSE چه وضعیتی دارد؟

  4. دستور gh repo create my-app --private --source=. --push چه می‌کند؟

  5. تاریخچه‌ی Git و Issue ها: اگر از GitHub به سرور دیگری بروی؟

  • GitHub = مخزن Git + Issues، Pull Request، Actions، دسترسی‌ها و صفحه‌ی پروژه؛ تاریخچه‌ی Git مستقل از آن است.
  • ساخت مخزن: عمومی/خصوصی، README، .gitignore، LICENSE. با gh repo create نام --private --source=. --push (نیاز به gh auth login).
  • پروژه‌ی موجود ← مخزن خالی: git branch -M main، git remote add origin آدرس، git push -u origin main. مخزن با README + تاریخچه‌ی محلی ← unrelated histories؛ بهتر: git clone و کار در آن.
  • README خوب: چیست، نصب و اجرا، نمونه، ساختار، مشارکت، مجوز. بدون LICENSE دیگران قانوناً حق استفاده ندارند.
  • Issues: باگ و ایده با برچسب و مسئول؛ در commit با #شماره یا Fixes #12 ارجاع بده (بستن خودکار فقط روی GitHub). git log --grep='#12' ردش را می‌گیرد؛ gh issue create/list/close.
  • نقش‌ها: Read، Triage، Write، Maintain، Admin (سازمان‌ها). رمز و کلید را هرگز push نکن.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
gh auth loginورود gh به حساب (یک‌بار)
gh repo create نام --private --source=. --pushساخت مخزن خصوصی از پوشه‌ی فعلی
git branch -M mainتغییر نام شاخه‌ی فعلی به main
git remote add origin آدرساتصال به مخزن
git push -u origin mainارسال و upstream
git pull --allow-unrelated-historiesادغام دو تاریخچه‌ی بی‌ارتباط (با احتیاط)
gh issue create --title "..." --body "..."ساخت Issue
gh issue list / gh issue close 12فهرست / بستن
git log --grep="#12"commit های مربوط به Issue 12
Fixes #12بستن خودکار Issue با رسیدن commit به شاخه‌ی پیش‌فرض