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

مخزن راه دور

توی این درس یاد می‌گیری مخزن محلی‌ات را با یک مخزن راه دور (remote) هماهنگ کنی: origin چیست، چطور با git clone یک پروژه را می‌گیری، با git push -u origin main می‌فرستی و شاخه‌ی upstream را تنظیم می‌کنی، و فرق git fetch (فقط بگیر) و git pull (بگیر و ادغام کن) را می‌فهمی. می‌بینی چرا push گاهی با non-fast-forward رد می‌شود و راهش چیست، شاخه‌ی راه دور را حذف می‌کنی و چند remote را کنار هم می‌گذاری. همه‌چیز با دو برنامه‌نویس شبیه‌سازی‌شده و یک «سرور» واقعی (مخزن bare) تمرین می‌شود. تمرین: یک مخزن را clone کن، تغییر بده و push کن.

مسئله: کار تنها کافی نیست

Section titled “مسئله: کار تنها کافی نیست”

تا الان همه‌چیز روی یک کامپیوتر بود. ولی پروژه‌ی واقعی چند نفر دارد و باید جایی باشد که همه روی آن توافق کنند: یک مخزن مرکزی روی سرور (GitHub، GitLab یا هر سرور SSH). هر نفر یک کلون از آن دارد، روی کلون خودش کار می‌کند و تغییرها را به سرور می‌فرستد (push) و تغییر دیگران را می‌گیرد (pull). اسم این مخزن مرکزی در Git یک remote است و اسم پیش‌فرضش origin.

تشبیه: دفتر مشترک و دفتر شخصی

Section titled “تشبیه: دفتر مشترک و دفتر شخصی”

مخزن راه دور مثل دفتر مشترک تیم روی میز مدیر است و کلون تو نسخه‌ی شخصی دفتر روی میز خودت. هر چه در دفتر شخصی می‌نویسی (commit) فقط مال توست تا وقتی آن را در دفتر مشترک ثبت کنی (push). و برای دیدن کار دیگران باید نسخه‌ی تازه‌ی دفتر مشترک را بگیری (fetch/pull). Git به تو می‌گوید دفتر شخصی‌ات از مشترک چند صفحه عقب یا جلوتر است.

clone یک کپی کامل از سرور می‌گیرد. push commit های محلی تو را به سرور می‌فرستد؛ fetch commit های تازه‌ی سرور را فقط به شاخه‌های remote-tracking (مثل origin/main) می‌آورد؛ و pull همان fetch است به‌اضافه‌ی ادغام در شاخه‌ی فعلی.

برای تمرین واقعی، یک «سرور» و دو برنامه‌نویس می‌سازم. سرور یک مخزن bare است: مخزنی بدون پوشه‌ی کاری که فقط تاریخچه را نگه می‌دارد (همان چیزی که GitHub پشت صحنه دارد). سارا و رضا هر کدام کلون خودشان را دارند:

Terminal window
mkdir -p ~/gitlab/server ~/gitlab/sara && cd ~/gitlab
git init -q --bare server/shop.git
echo "--- ساختار «سرور» (bare: فقط داخل .git، بدون فایل‌های کاری):"
ls server/shop.git | head -6
cd sara && git init -q
git config user.name "Sara"; git config user.email "sara@example.com"
echo "# Shop" > README.md && git add README.md && git commit -qm "Add README"
echo "--- remote های سارا قبل از اتصال:"
git remote -v; echo "(هیچ)"
git remote add origin ~/gitlab/server/shop.git
echo "--- بعد از git remote add origin آدرس:"
git remote -v
خروجی
--- ساختار «سرور» (bare: فقط داخل .git، بدون فایل‌های کاری):
HEAD
branches
config
description
hooks
info
--- remote های سارا قبل از اتصال:
(هیچ)
--- بعد از git remote add origin آدرس:
origin /home/ali/gitlab/server/shop.git (fetch)
origin /home/ali/gitlab/server/shop.git (push)

مثال ۱: اولین push، git push -u origin main

Section titled “مثال ۱: اولین push، git push -u origin main”

سارا commit اول را به سرور می‌فرستد:

Terminal window
cd ~/gitlab/sara
git push -u origin main
echo "--- شاخه‌ی remote-tracking که Git ساخت:"
git branch -a
echo "--- و فایل config مخزن (remote و upstream):"
cat .git/config | tail -7
خروجی
To /home/ali/gitlab/server/shop.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
--- شاخه‌ی remote-tracking که Git ساخت:
* main
remotes/origin/main
--- و فایل config مخزن (remote و upstream):
email = sara@example.com
[remote "origin"]
url = /home/ali/gitlab/server/shop.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

دو چیز نو: origin main یعنی «به remote ی به نام origin، شاخه‌ی main». و -u (--set-upstream) یک رابطه می‌سازد: «شاخه‌ی محلی main با origin/main جفت است». از این به بعد git push و git pull را بدون آرگومان می‌زنی و Git می‌داند کجا. origin/main یک شاخه‌ی remote-tracking است: یک برچسب فقط‌خواندنی که یادآور می‌کند «سرور آخرین بار این‌جا بود». فقط با fetch/pull/push عوض می‌شود. قسمت [branch "main"] در config همین جفت‌شدن را نگه می‌دارد.

مثال ۲: git clone، رضا پروژه را می‌گیرد

Section titled “مثال ۲: git clone، رضا پروژه را می‌گیرد”

رضا یک کلون می‌گیرد؛ clone تاریخچه‌ی کامل را می‌آورد، remote را به اسم origin تنظیم می‌کند و upstream شاخه‌ی اصلی را هم ست می‌کند:

Terminal window
cd ~/gitlab
git clone server/shop.git reza
cd reza
git config user.name "Reza"; git config user.email "reza@example.com"
echo "--- remote ها (clone خودش origin را ساخت):"
git remote -v
echo "--- تاریخچه و شاخه‌ها:"
git log --oneline
git branch -vv
خروجی
Cloning into 'reza'...
done.
--- remote ها (clone خودش origin را ساخت):
origin /home/ali/gitlab/server/shop.git (fetch)
origin /home/ali/gitlab/server/shop.git (push)
--- تاریخچه و شاخه‌ها:
d296f05 Add README
* main d296f05 [origin/main] Add README

git branch -vv (دو v) برای هر شاخه نشان می‌دهد با کدام شاخه‌ی راه دور جفت است: [origin/main]. اگر مخزن آدرس‌های مختلف دارد: git clone آدرس پوشه-مقصد (اسم مقصد اختیاری است). آدرس می‌تواند محلی (مثل این‌جا)، https://github.com/user/repo.git یا git@github.com:user/repo.git (SSH) باشد؛ از درس بعد استفاده می‌کنی.

مثال ۳: نگاه به remote، git remote show

Section titled “مثال ۳: نگاه به remote، git remote show”
Terminal window
cd ~/gitlab/reza
git remote show origin
خروجی
* remote origin
Fetch URL: /home/ali/gitlab/server/shop.git
Push URL: /home/ali/gitlab/server/shop.git
HEAD branch: main
Remote branch:
main tracked
Local branch configured for 'git pull':
main merges with remote main
Local ref configured for 'git push':
main pushes to main (up to date)

این خروجی یک خلاصه‌ی خوب می‌دهد: آدرس‌ها، شاخه‌ی پیش‌فرض سرور (HEAD branch)، شاخه‌های راه دور، و اینکه git pull و git push چه می‌کنند. (برای دیدن این اطلاعات به سرور وصل می‌شود.)

مثال ۴: سارا کار می‌کند و push می‌کند

Section titled “مثال ۴: سارا کار می‌کند و push می‌کند”
Terminal window
cd ~/gitlab/sara
echo "<h1>Shop</h1>" > index.html
git add index.html && git commit -qm "Add homepage"
echo "--- قبل از push، git status می‌گوید جلوتر هستی:"
git status -sb | head -1
git status | sed -n 2p
git push
echo "--- بعد از push:"
git status -sb | head -1
خروجی
--- قبل از push، git status می‌گوید جلوتر هستی:
## main...origin/main [ahead 1]
Your branch is ahead of 'origin/main' by 1 commit.
To /home/ali/gitlab/server/shop.git
d296f05..f1c4836 main -> main
--- بعد از push:
## main...origin/main

ahead 1 (یک commit جلوتر از سرور). و git push ساده کافی بود چون upstream تنظیم شده. توجه: commit محلی با push به سرور می‌رود؛ تا push نکنی، فقط روی کامپیوتر توست.

مثال ۵: git fetch، گرفتن بدون ادغام

Section titled “مثال ۵: git fetch، گرفتن بدون ادغام”

رضا هنوز commit تازه‌ی سارا را ندارد. git fetch آن را دانلود می‌کند، ولی شاخه‌ی کاری‌ات را دست نمی‌زند:

Terminal window
cd ~/gitlab/reza
echo "--- قبل از fetch (رضا هنوز چیزی نمی‌داند):"
git log --oneline
git fetch
echo "--- بعد از fetch: origin/main جلو رفته ولی main نه:"
git branch -vv
git status | sed -n 2p
echo "--- چه چیزی جدید است؟ (commit های origin/main که در main نیست)"
git log main..origin/main --oneline
echo "--- و تغییر:"
git diff main origin/main --stat
خروجی
--- قبل از fetch (رضا هنوز چیزی نمی‌داند):
d296f05 Add README
From /home/ali/gitlab/server/shop
d296f05..f1c4836 main -> origin/main
--- بعد از fetch: origin/main جلو رفته ولی main نه:
* main d296f05 [origin/main: behind 1] Add README
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
--- چه چیزی جدید است؟ (commit های origin/main که در main نیست)
f1c4836 Add homepage
--- و تغییر:
index.html | 1 +
1 file changed, 1 insertion(+)

fetch امن‌ترین عملیات است: هیچ فایلی در پوشه‌ی کاری‌ات عوض نمی‌شود؛ فقط origin/main به‌روز می‌شود تا ببینی چه خبر است. git log main..origin/main «آنچه سرور دارد و من ندارم» را نشان می‌دهد.

مثال ۶: git pull = fetch + ادغام

Section titled “مثال ۶: git pull = fetch + ادغام”

وقتی بررسی‌ات کافی بود، آن را وارد شاخه‌ات کن. git pull دقیقاً «fetch و بعد merge origin/main» است:

Terminal window
cd ~/gitlab/reza
git pull
echo "--- حالا main به‌روز است:"
git log --oneline
ls
خروجی
Updating d296f05..f1c4836
Fast-forward
index.html | 1 +
1 file changed, 1 insertion(+)
create mode 100644 index.html
--- حالا main به‌روز است:
f1c4836 Add homepage
d296f05 Add README
README.md
index.html

چون رضا هیچ commit محلی‌ای نداشت، ادغام fast-forward بود. (پس git pull یعنی git fetch + git merge origin/main؛ برای کنترل بیشتر می‌شود آن دو را جدا زد.)

مثال ۷: وقتی تاریخچه‌ها جدا می‌شوند، rejected

Section titled “مثال ۷: وقتی تاریخچه‌ها جدا می‌شوند، rejected”

حالا هر دو commit می‌زنند؛ رضا زودتر push می‌کند و سارا بعد:

Terminal window
cd ~/gitlab/reza
echo "<footer>2026</footer>" > footer.html && git add footer.html && git commit -qm "Add footer (Reza)"
git push -q
cd ~/gitlab/sara
echo "<style></style>" > style.css && git add style.css && git commit -qm "Add stylesheet (Sara)"
echo "=== push سارا:"
git push 2>&1
خروجی
=== push سارا:
To /home/ali/gitlab/server/shop.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/home/ali/gitlab/server/shop.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Git رد کرد: ! [rejected] main -> main (fetch first). سرور commit ای دارد که سارا ندارد؛ اگر Git قبول می‌کرد، کار رضا پاک می‌شد. پیام هم راه‌حل را می‌گوید: اول git pull (یا git fetch و ادغام). دو راه داری:

Terminal window
cd ~/gitlab/sara
git fetch -q
echo "--- تاریخچه‌ی جدا شده:"
git log --oneline --graph --all
echo "=== راه اول: pull با merge (commit ادغام می‌سازد):"
git pull --no-rebase --no-edit 2>&1 | tail -3
git log --oneline --graph
خروجی
--- تاریخچه‌ی جدا شده:
* 1451421 Add stylesheet (Sara)
| * 9ba5b2e Add footer (Reza)
|/
* f1c4836 Add homepage
* d296f05 Add README
=== راه اول: pull با merge (commit ادغام می‌سازد):
footer.html | 1 +
1 file changed, 1 insertion(+)
create mode 100644 footer.html
* b0fb153 Merge branch 'main' of /home/ali/gitlab/server/shop
|\
| * 9ba5b2e Add footer (Reza)
* | 1451421 Add stylesheet (Sara)
|/
* f1c4836 Add homepage
* d296f05 Add README

نتیجه: یک commit ادغام که دو خط را وصل می‌کند. راه دوم git pull --rebase است: commit های محلی‌ات را روی آخرین کار سرور بازپخش می‌کند و تاریخچه خطی می‌ماند (درس rebase؛ این‌جا commit های سارا هنوز push نشده‌اند، پس rebase کردنشان امن است):

Terminal window
cd ~/gitlab/sara
git reset -q --hard ORIG_HEAD
echo "=== راه دوم: pull --rebase:"
git pull --rebase 2>&1 | tail -2
git log --oneline --graph
git push -q
echo "--- push موفق؛ وضعیت:"
git status -sb | head -1
خروجی
=== راه دوم: pull --rebase:
Successfully rebased and updated refs/heads/main.
* eb19523 Add stylesheet (Sara)
* 9ba5b2e Add footer (Reza)
* f1c4836 Add homepage
* d296f05 Add README
--- push موفق؛ وضعیت:
## main...origin/main

(git reset --hard ORIG_HEAD فقط ادغام قبلی را برای نمایش راه دوم برگرداند.) کدام را انتخاب کنم؟ بستگی به قرارداد تیم دارد: merge تاریخچه‌ی واقعی را نگه می‌دارد؛ --rebase خطی و تمیز می‌کند. می‌شود پیش‌فرض را تنظیم کرد: git config --global pull.rebase true (یا false برای merge، یا pull.ff only که فقط fast-forward را می‌پذیرد و در غیر آن خطا می‌دهد تا خودت تصمیم بگیری).

مثال ۸: شاخه‌های راه دور و پاک‌سازی

Section titled “مثال ۸: شاخه‌های راه دور و پاک‌سازی”
Terminal window
cd ~/gitlab/sara
git switch -qc feature/cart
echo "cart" > cart.txt && git add cart.txt && git commit -qm "Add cart"
echo "--- سارا شاخه‌ی جدید را push می‌کند (با -u):"
git push -u origin feature/cart 2>&1 | tail -3
cd ~/gitlab/reza
git fetch -q
echo "--- رضا fetch کرد؛ شاخه‌های راه دور او:"
git branch -r
cd ~/gitlab/sara
echo "--- سارا شاخه را از سرور حذف می‌کند:"
git push origin --delete feature/cart 2>&1 | tail -1
cd ~/gitlab/reza
git fetch -q
echo "--- رضا دوباره fetch کرد؛ ولی هنوز در فهرستش هست!"
git branch -r
echo "--- fetch --prune شاخه‌های حذف‌شده‌ی سرور را از فهرست محلی پاک می‌کند:"
git fetch --prune
git branch -r
خروجی
--- سارا شاخه‌ی جدید را push می‌کند (با -u):
To /home/ali/gitlab/server/shop.git
* [new branch] feature/cart -> feature/cart
branch 'feature/cart' set up to track 'origin/feature/cart'.
--- رضا fetch کرد؛ شاخه‌های راه دور او:
origin/HEAD -> origin/main
origin/feature/cart
origin/main
--- سارا شاخه را از سرور حذف می‌کند:
- [deleted] feature/cart
--- رضا دوباره fetch کرد؛ ولی هنوز در فهرستش هست!
origin/HEAD -> origin/main
origin/feature/cart
origin/main
--- fetch --prune شاخه‌های حذف‌شده‌ی سرور را از فهرست محلی پاک می‌کند:
From /home/ali/gitlab/server/shop
- [deleted] (none) -> origin/feature/cart
origin/HEAD -> origin/main
origin/main

git push -u origin شاخه شاخه‌ی تازه را روی سرور می‌سازد و جفت می‌کند؛ git push origin --delete شاخه آن را حذف می‌کند. نکته: origin/feature/cart در فهرست رضا بعد از حذف روی سرور هم می‌ماند تا git fetch --prune (یا git remote prune origin) بزند؛ در خروجی fetch --prune یک خط - [deleted] (none) -> origin/feature/cart می‌بینی. این‌طور فهرست شاخه‌هایت پر از شاخه‌های مرده نمی‌شود. (git config --global fetch.prune true همیشه خودکارش می‌کند.)

مثال ۹: چند remote، origin و upstream

Section titled “مثال ۹: چند remote، origin و upstream”

یک مخزن می‌تواند چند remote داشته باشد. حالت معروف: پروژه‌ی متن‌باز را fork کرده‌ای؛ origin نسخه‌ی خودت و upstream پروژه‌ی اصلی است (درس Pull Request):

Terminal window
cd ~/gitlab
git init -q --bare server/original.git
cd reza
git remote add upstream ~/gitlab/server/original.git
git remote -v
echo "--- تغییر نام و حذف remote:"
git remote rename upstream source
git remote
git remote remove source
git remote
echo "--- عوض‌کردن آدرس origin (مثلاً بعد از جابه‌جایی پروژه):"
git remote set-url origin ~/gitlab/server/shop.git
git remote get-url origin
خروجی
origin /home/ali/gitlab/server/shop.git (fetch)
origin /home/ali/gitlab/server/shop.git (push)
upstream /home/ali/gitlab/server/original.git (fetch)
upstream /home/ali/gitlab/server/original.git (push)
--- تغییر نام و حذف remote:
origin
source
origin
--- عوض‌کردن آدرس origin (مثلاً بعد از جابه‌جایی پروژه):
/home/ali/gitlab/server/shop.git

دستورهای مدیریت: git remote add نام آدرس، git remote rename قدیم جدید، git remote remove نام، git remote set-url نام آدرس-جدید (برای عوض‌کردن HTTPS به SSH؛ درس بعد)، و git remote get-url.

پشت پرده: remote فقط یک آدرس با نام است

Section titled “پشت پرده: remote فقط یک آدرس با نام است”

remote چیز مرموزی نیست: یک اسم برای یک آدرس، به‌همراه قاعده‌ای که می‌گوید شاخه‌های سرور به کدام شاخه‌های محلی فقط‌خواندنی نگاشت شوند. این‌ها در .git/config و .git/refs/remotes/ هستند:

Terminal window
cd ~/gitlab/reza
echo "--- config:"
git config --get-regexp '^remote\.origin'
echo "--- شاخه‌های remote-tracking فایل‌های هش‌دارند:"
ls .git/refs/remotes/origin
cat .git/refs/remotes/origin/main
git rev-parse origin/main
echo "--- git fetch دقیقاً چه چیزی را به‌روز می‌کند؟ (اثر آخرین fetch)"
cat .git/FETCH_HEAD | head -2
خروجی
--- config:
remote.origin.url /home/ali/gitlab/server/shop.git
remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
--- شاخه‌های remote-tracking فایل‌های هش‌دارند:
HEAD
main
eb195236d686386ef072b1f17f067c046a3ad748
eb195236d686386ef072b1f17f067c046a3ad748
--- git fetch دقیقاً چه چیزی را به‌روز می‌کند؟ (اثر آخرین fetch)
eb195236d686386ef072b1f17f067c046a3ad748 branch 'main' of /home/ali/gitlab/server/shop

خط fetch = +refs/heads/*:refs/remotes/origin/* نگاشت را تعریف می‌کند: «هر شاخه‌ی سرور refs/heads/X را بگیر و در refs/remotes/origin/X بگذار». و git pull همان fetch + merge FETCH_HEAD است. origin/main مثل شاخه‌ها یک فایل با هش یک commit است، با این تفاوت که خودت نمی‌توانی روی آن commit بزنی؛ فقط وقتی remote را fetch می‌کنی عوض می‌شود.

دستور کار
git clone آدرس [پوشه] کپی کامل مخزن، با تنظیم origin
git remote -v remote ها و آدرس‌ها
git remote add نام آدرس افزودن remote
git remote show origin خلاصه‌ی remote
git push -u origin شاخه ارسال و تنظیم upstream
git push / git pull ارسال / دریافت (بعد از upstream)
git fetch [--prune] دریافت بدون ادغام (و پاک‌کردن شاخه‌های مرده)
git pull --rebase دریافت و بازپخش commit های محلی
git branch -vv / -r / -a شاخه‌ها با upstream / راه دور / همه
git push origin --delete شاخه حذف شاخه‌ی راه دور
git remote set-url نام آدرس عوض‌کردن آدرس
git log main..origin/main آنچه سرور دارد و من ندارم
می‌خواهم… بزن
ببینم سرور چه خبر است (بدون تغییر) git fetch سپس git log main..origin/main
کار همکارها را بگیرم git pull (یا fetch + merge)
کارم را بفرستم git push
push رد شد git pull (یا --rebase) و دوباره git push
Terminal window
mkdir -p ~/gitlab/noup && cd ~/gitlab/noup && git init -q
echo a > f && git add f && git commit -qm "Base"
git remote add origin ~/gitlab/server/shop.git
git pull 2>&1 | head -4
خروجی
From /home/ali/gitlab/server/shop
* [new branch] main -> origin/main
There is no tracking information for the current branch.
Please specify which branch you want to merge with.

There is no tracking information for the current branch: این شاخه‌ی محلی به هیچ شاخه‌ی راه دوری جفت نشده. راه‌حل: git push -u origin main (یا git branch --set-upstream-to=origin/main).

۲) clone در پوشه‌ی غیرخالی

Section titled “۲) clone در پوشه‌ی غیرخالی”
Terminal window
cd ~/gitlab
git clone server/shop.git reza 2>&1 | tail -2
خروجی
fatal: destination path 'reza' already exists and is not an empty directory.

destination path 'reza' already exists and is not an empty directory: Git روی چیزی که هست نمی‌نویسد. راه‌حل: اسم دیگری بده یا پوشه را پاک کن.

۳) force push روی شاخه‌ی مشترک

Section titled “۳) force push روی شاخه‌ی مشترک”

git push --force تاریخچه‌ی سرور را بازنویسی می‌کند و کار همکارها را پاک می‌کند (درس‌های undo و rebase). راه‌حل: git pull --rebase و push معمولی؛ و برای شاخه‌ی شخصی بعد از rebase --force-with-lease.

۴) push نکردن و فکر کردن که کار ذخیره شده

Section titled “۴) push نکردن و فکر کردن که کار ذخیره شده”

commit فقط محلی است. اگر کامپیوترت خراب شود، commit هایی که push نکرده‌ای رفته‌اند. راه‌حل: هر روز git push؛ git status -sb می‌گوید ahead N.

۵) pull بدون نگاه کردن و merge های ناخواسته

Section titled “۵) pull بدون نگاه کردن و merge های ناخواسته”

git pull ممکن است commit ادغام ناخواسته بسازد («Merge branch ‘main’ of …»). راه‌حل: اول git fetch و git log main..origin/main را بخوان؛ یا pull.rebase یا pull.ff only را تنظیم کن.

✎ تمرینآسان

مخزن «سرور» (bare) را clone کن و ثابت کن origin تنظیم شده و تاریخچه‌ی کامل را داری.

دیدن جواب
Terminal window
cd ~/gitlab
git clone -q server/shop.git easy
cd easy
git remote -v
git log --oneline | wc -l
git branch -vv
خروجی
origin /home/ali/gitlab/server/shop.git (fetch)
origin /home/ali/gitlab/server/shop.git (push)
4
* main eb19523 [origin/main] Add stylesheet (Sara)
✎ تمرینمتوسط

تمرین اصلی: یک مخزن را clone کن، تغییر بده و push کن. روی کلون جدید یک فایل CONTRIBUTING.md اضافه کن، commit و push بزن؛ بعد از کلون دیگری git pull بزن و ثابت کن فایل آمد.

دیدن جواب
Terminal window
cd ~/gitlab
git clone -q server/shop.git me && git clone -q server/shop.git friend
cd me
git config user.name "Me"; git config user.email "me@example.com"
echo "How to contribute" > CONTRIBUTING.md
git add CONTRIBUTING.md && git commit -qm "Add contributing guide"
git push -q
echo "--- از دید دوست:"
cd ../friend
git pull -q
ls
git log --oneline -1
خروجی
--- از دید دوست:
CONTRIBUTING.md
README.md
footer.html
index.html
style.css
3040913 Add contributing guide
✎ تمرینسخت

دو کلون بساز که هر دو یک فایل متفاوت را commit کنند؛ اولی push کند، دومی push خود را «رد‌شده» ببیند. با git pull --rebase آن را درست کن و ثابت کن تاریخچه‌ی نهایی خطی است (بدون commit ادغام).

دیدن جواب
Terminal window
cd ~/gitlab
git clone -q server/shop.git a && git clone -q server/shop.git b
for d in a b; do (cd $d && git config user.name "User $d" && git config user.email "$d@example.com"); done
cd a && echo A > a.txt && git add a.txt && git commit -qm "Add a" && git push -q
cd ../b && echo B > b.txt && git add b.txt && git commit -qm "Add b"
echo "--- push دومی:"
git push 2>&1 | head -3
git pull --rebase -q
git push -q
echo "--- تاریخچه‌ی خطی:"
git log --oneline --graph | head -4
خروجی
--- push دومی:
To /home/ali/gitlab/server/shop.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/home/ali/gitlab/server/shop.git'
--- تاریخچه‌ی خطی:
* 3e60e3b Add b
* ff793b3 Add a
* 3040913 Add contributing guide
* eb19523 Add stylesheet (Sara)
⚡ بررسی سریع

فرق git fetch و git pull؟

؟ آزمونک
  1. origin چیست؟

  2. git push -u origin main چه کاری علاوه بر ارسال می‌کند؟

  3. origin/main چیست؟

  4. push تو با «rejected (fetch first)» روبه‌رو شد. یعنی چه؟

  5. git fetch --prune چه می‌کند؟

  • remote یک اسم برای یک آدرس مخزن راه دور است (پیش‌فرض origin)؛ git remote -v نشانشان می‌دهد، git remote add/rename/remove/set-url مدیریتشان می‌کند.
  • git clone آدرس کپی کامل با origin و upstream؛ git push -u origin main اولین بار می‌فرستد و شاخه را جفت می‌کند؛ بعد git push/git pull ساده.
  • git fetch: بگیر بدون ادغام (امن؛ فقط origin/main را به‌روز می‌کند)؛ git pull = fetch + merge؛ git pull --rebase = fetch + rebase.
  • push رد شد (non-fast-forward/fetch first) ← اول git pull (یا --rebase) سپس push؛ --force نه.
  • git branch -vv (upstream و ahead/behind)، -r (راه دور)، git push origin --delete شاخه، git fetch --prune.
  • پشت پرده: remote یک آدرس + نگاشت +refs/heads/*:refs/remotes/origin/*؛ origin/main یک فایل هش‌دار فقط‌خواندنی.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
git clone آدرس [پوشه]کپی کامل
git remote -vremote ها
git remote add origin آدرسافزودن remote
git push -u origin mainارسال و تنظیم upstream
git push / git pullارسال / دریافت
git fetchدریافت بدون ادغام
git log main..origin/mainآنچه سرور دارد و من ندارم
git pull --rebaseدریافت و بازپخش commit های محلی
git branch -vvشاخه‌ها با upstream
git push origin --delete شاخهحذف شاخه‌ی راه دور
git fetch --pruneپاک‌کردن شاخه‌های مرده
git remote set-url origin آدرسعوض‌کردن آدرس