rebase؛ بازپخش commitها
Rebase: replaying commits and rewriting history
rebase commitهای قدیمی را بلند نمیکند و جای دیگری نمیگذارد؛ تغییرهایشان را روی پایهٔ تازه دوباره اجرا میکند و commitهای جدید میسازد. همین یک نکته تقریباً تمام قانونهای این فصل را توضیح میدهد.
The main branch has advanced, while your private local feature/search branch has two commits. You want those changes on today’s main. One option is to merge the histories; another is to replay the changes on the new base. This chapter asks: “What commits appear if we apply these changes on top of today’s main?”
شاخهٔ اصلی جلو رفت؛ تغییرهای تو چه میشوند؟Main moved forward. What happens to your work?
فرض کن از commit B شاخه گرفتی و دو commit محلی ساختی. همزمان، همکارت main را تا C جلو برده است. هنوز هیچکدام از commitهای تو را push نکردهای و همکارت هم روی آنها حساب نکرده. میخواهی کار تو از نسخهٔ تازهٔ main شروع شود، نه از نسخهٔ قدیمیتر.
Suppose you branched from commit B and made two local commits. Meanwhile, a teammate advanced main to C. You have not pushed your commits, and nobody else depends on them. You want your work to start from the newer main rather than the older snapshot.
در فصل ۰۶، merge را دیدیم: دو مسیر را نگه میدارد و نتیجه را به هم وصل میکند. این بار قبل از فرمان، یک گراف کوچک میسازیم و شناسههای commitها را ثبت میکنیم. بعد میپرسیم آیا rebase فقط اشارهگر شاخه را جابهجا میکند یا واقعاً چیز دیگری میسازد.
Chapter 06 showed merge preserving two lines and joining their results. This time we will create a small graph and record the commit IDs before running anything. Then we will ask whether rebase merely moves a branch pointer or actually creates something new.
rebase commitها را جابهجا نمیکندRebase does not move the old commits
در فصل ۰۲ دیدیم یک commit به tree و commit والدش اشاره میکند و فراداده هم دارد. پس اگر همان تغییرها این بار بعد از C ثبت شوند، والد فرق میکند؛ در نتیجه commit جدید، شناسه جدیدی هم دارد. commitهای قدیمی D و E پاک یا منتقل نمیشوند؛ Git تغییرهایشان را یکییکی روی پایهٔ تازه بازپخش میکند و commitهای جایگزین D′ و E′ میسازد.
Chapter 02 showed that a commit points to a tree and its parent commit, and also carries metadata. If the same changes are recorded after C, the parent differs, so the resulting commits have new IDs. The old D and E are not moved or erased; Git replays their changes one by one on the new base and creates replacement commits D′ and E′.
شناسه به محتوای commit بستگی دارد؛ از جمله tree، والد و دادههای commit. وقتی والد از B به C عوض میشود، حتی اگر فایل نهایی دقیقاً همان باشد، commit شیء دیگری داریم. بنابراین در شکل معمول rebase، D′ و E′ شناسههایی متفاوت از D و E میگیرند. این همان نکتهٔ فصل ۰۲ است، نه ترفند ظاهریِ log.
A commit ID depends on the commit’s content, including its tree, parent, and commit data. Changing the parent from B to C gives us a different commit object, even if the final files are identical. In the usual rebase case, D′ and E′ therefore have IDs different from D and E. This is Chapter 02’s object model, not a cosmetic log trick.
آزمایش اول: تغییرها را روی main تازه بنشانFirst experiment: replay onto the newer main
برای اینکه نتیجه را بیخطر ببینیم، مخزن تازهای میسازیم. هر commit فایل جداگانهای میسازد تا این آزمایش تعارض نداشته باشد. مخزن باید بدون تغییر باشد؛ اگر دستورهای ساخت پوشه در shell تو فرق دارند، همان کار را با فایلساز خودت انجام بده.
To inspect the result safely, create a fresh repository. Each commit adds a separate file, so this experiment will not conflict. Keep the repository clean; if your shell uses different directory-creation commands, create the same files with your editor.
mkdir rebase-lab && cd rebase-lab git init -b main git config user.name 'Nika Example' git config user.email 'nika@example.test' printf 'base\n' > README.txt git add README.txt && git commit -m 'Add base' git switch -c feature/search printf 'search\n' > search.txt git add search.txt && git commit -m 'Add search' printf 'filters\n' > filters.txt git add filters.txt && git commit -m 'Add filters' git switch main printf 'operations\n' > operations.txt git add operations.txt && git commit -m 'Add operations note' git switch feature/search git log --oneline --graph --decorate --all git rev-parse feature/search git rev-parse feature/search~1 git rebase main git log --oneline --graph --decorate --all git rev-parse feature/search git rev-parse feature/search~1 git status --short
قبل از rebase، خروجی log باید یک انشعاب از commit پایه نشان دهد: main روی یادداشت operations است و قابلیت دو commit خودش را دارد. دو شناسه را هم نگه دار؛ یکی برای E و دیگری برای D. بعد از git rebase main، همان نام قابلیت به نوک تازهٔ زنجیره اشاره میکند و log خطی میشود.
Before rebase, the log should show a split from the base: main ends at the operations note, while feature has its two commits. Keep the two IDs—one for E and one for D. After git rebase main, the feature name points to the new chain’s tip and the visible graph is linear.
* <new-E> (HEAD -> feature/search) Add filters
* <new-D> Add search
* <C> (main) Add operations note
* <B> Add baseاین خروجیِ نماینده است؛ شناسهها و شکل کوتاهشان در هر مخزن فرق میکند. حالا git rev-parse feature/search و feature/search~1 را با شناسههای قبلی مقایسه کن: معمولاً هر دو تغییر کردهاند. برای دیدن محتوای نهایی، git show --stat یا فایلها را بررسی کن. بدون تغییر بودن git status فقط میگوید تغییر ثبتنشدهای باقی نمانده؛ درست بودن رفتار برنامه را ثابت نمیکند.
This is representative output; IDs and abbreviated forms vary by repository. Compare the new results of git rev-parse feature/search and feature/search~1 with the saved IDs: normally both changed. Inspect the final files or use git show --stat. A clean git status only proves there are no uncommitted changes; it does not prove the application behaves correctly.
| قبل از rebaseBefore rebase | بعد از rebaseAfter rebase | نتیجهMeaning |
|---|---|---|
| قابلیت به E قدیمی میرسد.feature resolves to old E. | قابلیت به E′ میرسد.feature resolves to E′. | اشارهگر شاخه در پایان بازپخش به نوک جدید میرود.The branch ref points to the new tip after replay. |
| D و E والدهای قدیمی دارند.D and E have their original parents. | D′ و E′ والدهای تازه دارند.D′ and E′ have new parents. | معمولاً شناسه هر دو عوض میشود.Both IDs normally change. |
| تاریخچه دو مسیر دارد.History has two diverging lines. | نمای قابلیت خطی میشود.The feature line becomes linear. | این شکل، commit merge ندارد.This shape has no merge commit. |
اگر بازپخش به تعارض بخوردWhen replay reaches a conflict
rebase قرار نیست اختلاف معنایی را حدس بزند. تصور کن commit قدیمی میخواهد مقدار timeout را از ۱۰ به ۳۰ برساند، اما main همان مقدار را ۶۰ کرده. Git تلاش میکند patch قدیمی را روی متن تازه اجرا کند و برای انتخاب نهایی از تو کمک میخواهد.
Rebase cannot guess semantic intent. Suppose an old commit changes timeout from 10 to 30, while main changed it to 60. Git tries to apply the old patch to the new text and asks you to decide the final result.
در مخزن آزمایشی جداگانه، فایل را در شاخهٔ قابلیت به ۳۰ و در main به ۶۰ تغییر بده و از هر طرف commit بساز. وقتی روی قابلیت هستی، git rebase main را اجرا کن. برای اینکه تعارض واقعی و قابلفهم باشد، هر دو تغییر باید همان خط را از یک پایه تغییر داده باشند؛ این تمرین را روی مخزن واقعی یا کاریِ نیمهتمام اجرا نکن.
In a separate disposable repository, change the file to 30 on feature and to 60 on main, committing each side. While on feature, run git rebase main. For a clear conflict, both edits must change the same line from the same base. Do not run this exercise in a real or unfinished working repository.
mkdir rebase-conflict-lab && cd rebase-conflict-lab git init -b main git config user.name 'Nika Example' git config user.email 'nika@example.test' printf 'timeout=10\n' > settings.txt git add settings.txt && git commit -m 'Set base timeout' git switch -c feature/timeout printf 'timeout=30\n' > settings.txt git add settings.txt && git commit -m 'Set feature timeout' git switch main printf 'timeout=60\n' > settings.txt git add settings.txt && git commit -m 'Set main timeout' git switch feature/timeout git rebase main
CONFLICT (content): Merge conflict in settings.txt
error: could not apply <old-D>... Set timeout to 30
git status
git rebase --show-current-patch
git show HEAD:settings.txt
git show REBASE_HEAD:settings.txtاول git status را بخوان تا نام فایلهای حلنشده و راهنمای ادامه را ببینی. git rebase --show-current-patch patch همان commitی را نشان میدهد که Git نتوانسته بازپخش کند. در این rebase ساده، HEAD نسخهٔ پایهٔ فعلیِ بازپخششده است؛ REBASE_HEAD commit قدیمیای را نشان میدهد که Git در حال بازپخش کردنش بود. این شواهد کمک میکنند، اما هیچکدام نمیگویند مقدار درست محصول ۳۰ است یا ۶۰؛ تصمیم را از نیاز برنامه بگیر.
Start with git status to see unresolved paths and the suggested next step. git rebase --show-current-patch displays the patch Git could not replay. In this simple rebase, HEAD is the current rebased base, while REBASE_HEAD identifies the old commit being replayed. These are evidence, not a product decision: they cannot tell you whether 30 or 60 is correct.
با backend پیشفرض merge، در بازپخش commitهای شاخهٔ کاری، ours نسخهٔ زنجیرهای است که تا اینجا روی upstream تازه ساخته شده؛ theirs patch شاخهٔ کاریای است که همان لحظه بازپخش میشود. این جهت میتواند خلاف حدس «ours یعنی شاخهٔ من» باشد. برای یادگیری، وضعیت و خود فایل/patch را ببین و متن نهایی را آگاهانه بنویس؛ صرفاً --ours یا --theirs را انتخاب نکن.
With the default merge backend, during replay, ours is the rebased series built so far on the new upstream; theirs is the work-branch patch currently being replayed. This may be opposite to the guess that “ours means my branch.” Inspect status and the actual file/patch, then write the intended result; do not blindly choose --ours or --theirs.
فایل را با مقدار درست اصلاح کن، نشانگرهای تعارض را بردار، سپس فقط همان فایل حلشده را ناحیه آمادهسازی کن. حالا Git میداند این نسخه را برای ادامهٔ بازپخش انتخاب کردهای:
Edit the file to the intended value, remove conflict markers, then stage the resolved path. Git can now continue replaying the series:
git diff -- settings.txt git add settings.txt git status git rebase --continue
ممکن است commit بعدی هم تعارض داشته باشد؛ هر patch جداگانه بازپخش میشود و هر توقف را دوباره باید بررسی کرد. اگر تصمیم گرفتی کل rebase آزمایشی را کنار بگذاری، git rebase --abort را اجرا کن. اگر --skip بزنی، Git از commit فعلی میگذرد و patch همان commit عمداً وارد تاریخچهٔ تازه نمیشود؛ این گزینه برای «ردشدن از خطا» نیست. فقط وقتی مطمئنی آن تغییر لازم نیست یا از قبل در پایه هست، skip کن.
A later commit may conflict too: each patch is replayed separately, so inspect each pause. To abandon the entire test rebase, run git rebase --abort. If you run --skip, Git skips the current commit and its patch is intentionally absent from the new history. This is not a way to skip an error; use it only when you know the change is unnecessary or already present in the base.
| فرمانCommand | اثرEffect | چه وقت؟When? |
|---|---|---|
git rebase --continue | بعد از حل و ناحیه آمادهسازی کردن، بازپخش را ادامه میدهد.Continues after you resolve and stage the paths. | وقتی نتیجهٔ patch را بررسی کردهای.After reviewing the resolved patch. |
git rebase --abort | عملیات جاری را متوقف میکند و شاخه را به وضعیت آغاز rebase برمیگرداند.Stops the operation and restores the branch to its pre-rebase state. | وقتی میخواهی از آزمایش خارج شوی؛ پیششرط بدون تغییر را جدی بگیر.When abandoning the attempt; keep the clean-tree precondition in mind. |
git rebase --skip | patch جاری را از این بازپخش حذف میکند.Omits the current patch from this replay. | فقط پس از اثبات غیرضروری/تکراریبودن همان تغییر.Only after proving this particular change is unnecessary or redundant. |
وقتی چند commit محلی را برای بازبینی مرتب میکنیTidying a local series for review
چند روز روی یک قابلیت کار کردهای: اول یک commit با پیام WIP، بعد یک رفع ایراد کوچک، بعد یک اصلاح پیام مستندات. حالا قبل از اینکه تاریخچه را با تیم به اشتراک بگذاری، میخواهی پیامها روشن باشند و اصلاح کوچک کنار commit اصلیاش بیاید. git rebase -i فهرست commitهای محلی را برایت باز میکند تا ترتیب و دستور هر کدام را انتخاب کنی.
You have worked on a feature for a few days: a WIP commit, a small fix, then a documentation correction. Before sharing the history, you want clear messages and the small fix folded into its original commit. git rebase -i opens the local commit list so you can choose each action and its order.
این فرمان چهار commit بعد از پایه را فهرست میکند. قبل از اجرا، پوشه کاری را بدون تغییر کن و یک اشارهگر پشتیبان بساز؛ rebase تعاملی تاریخچه را عوض میکند. فهرست todo از قدیمی به جدید است: سطر بالاتر زودتر بازپخش میشود. اگر ترتیب را تغییر بدهی، معنای patchهای بعدی هم ممکن است تغییر کند و تعارض تازهای بسازد.
This command lists the four commits after the base. Before running it, clean the working tree and create a backup ref; interactive rebase rewrites history. The todo list runs oldest to newest: the top row is replayed first. Reordering can change how later patches apply and may create new conflicts.
git switch feature/search git status --short git branch backup/feature-search-before-rebase git rebase -i HEAD~4 pick a1b2c3d Add search parser fixup b2c3d4e Handle empty input reword c3d4e5f WIP: add filter option pick d4e5f6a Add parser tests
این فقط نمونهٔ شکل فهرست است؛ Git شناسه و پیام واقعی مخزن تو را میگذارد. fixup تغییر commit را در قبلی ادغام و پیام خودش را دور میاندازد. اگر میخواهی پیامها را ترکیب یا ویرایش کنی، squash همان تغییرها را جمع میکند اما ویرایش پیام نهایی را پیشنهاد میدهد. reword فقط پیام را عوض میکند؛ drop commit را از سری حذف میکند؛ و edit وسط کار مکث میکند تا بتوانی commit را تغییر بدهی یا تقسیم کنی.
This illustrates the todo shape; Git supplies the real IDs and messages. fixup folds a commit into the previous one and discards its message. squash also combines the changes but lets you edit the resulting message. reword changes only the message; drop removes a commit from the series; and edit pauses so you can amend or split it.
| دستور todoTodo action | معنای عملیPractical effect | حواست به چه باشد؟Watch for |
|---|---|---|
pick | commit را با patch و پیام فعلی بازپخش کن.Replay the commit with its current patch and message. | پیشفرض مناسب برای commitهای آماده.The default for commits already in good shape. |
reword | patch را نگه دار، پیام را ویرایش کن.Keep the patch and edit the message. | commit تازه ساخته میشود.A new commit is created. |
squash / fixup | تغییر را با commit قبلی یکی کن.Combine the change with the previous commit. | fixup پیام را حذف میکند؛ squash اجازهٔ ترکیب پیام میدهد.fixup discards its message; squash lets you combine messages. |
drop | این patch را عمداً از سری تازه بیرون بگذار.Intentionally omit this patch from the new series. | محتوای لازم را ناخواسته حذف نکن.Do not accidentally remove needed behavior. |
edit / reorder | مکث برای تغییر commit، یا تغییر ترتیب بازپخش.Pause to amend a commit, or change replay order. | commitهای بعدی هم بهخاطر والد تازه شناسه جدید میگیرند.Descendants get new IDs because their parents change. |
وقتی یک commit قدیمی را reword، fixup یا جابهجا میکنی، commitهای بعد از آن هم روی والد جدید ساخته میشوند. بنابراین شناسه همهٔ فرزندان آن نقطه عوض میشود، حتی اگر patchشان دستنخورده مانده باشد. پس بعد از پایان، فقط به پیامهای log نگاه نکن: تستها را اجرا کن، diff را بخوان و نتیجهٔ برنامه را بررسی کن.
When an earlier commit is reworded, fixed up, or moved, later commits are rebuilt on new parents. Their IDs change even if their patches did not. Afterward, do more than read the log: run tests, inspect the diff, and verify application behavior.
merge و rebase: دو پاسخ به دو نیازMerge and rebase: two answers to different needs
در فصل ۰۶، merge راه حفظ دو مسیر واقعی بود: Git نتیجه را میساخت و معمولاً commitی با دو والد اضافه میکرد. rebase همان تغییرها را با والدهای تازه بازپخش میکند و یک زنجیرهٔ خطی میسازد. یکی «درست» و دیگری «غلط» نیست؛ مهم است بدانی تاریخچه قرار است چه چیزی را برای خوانندهٔ بعدی نگه دارد.
Chapter 06 used merge to preserve two real lines: Git combines the result and usually adds a two-parent commit. Rebase replays the changes with new parents and creates a linear series. Neither is inherently right or wrong; choose based on what the history should communicate to the next reader.
| سؤالQuestion | merge | rebase |
|---|---|---|
| گراف چه میگوید؟What does the graph show? | دو مسیر را نگه میدارد و نتیجه را وصل میکند؛ گاهی fast-forward است.Keeps both lines and joins their result; sometimes it fast-forwards. | تغییرها را روی پایهٔ تازه خطی بازپخش میکند.Replays changes linearly on the new base. |
| شناسههای قبلی؟Existing IDs? | commitهای موجود را بازنویسی نمیکند.Does not rewrite existing commits. | commitهای بازپخششده و فرزندانشان شناسه تازه میگیرند.Replayed commits and their descendants get new IDs. |
| چه چیزی برای همکاری مهم است؟What matters for collaboration? | مرز ادغام و هر دو مسیر در تاریخچه پیداست.The integration point and both lines remain visible. | تاریخچه مرتبتر میشود، اما اشارهگرهای دیگران ممکن است به commitهای قدیمی بمانند.History can look cleaner, but others’ refs may still point to old commits. |
انتخاب به تیم، نوع شاخه و چیزی که میخواهید تاریخچه ثبت کند بستگی دارد. برای شاخهٔ کوتاه و خصوصی، مرتبکردن محلی معمولاً کمهزینه است. برای شاخهای که چند نفر روی آن کار میکنند، اول دربارهٔ بازنویسی هماهنگ شو؛ شاید حفظ تاریخچه با merge مناسبتر باشد.
The choice depends on the team, branch, and what the history should record. Tidying a short private branch is usually low-cost. If several people use the branch, coordinate before rewriting; preserving it with merge may be a better fit.
وقتی rebase آنطور که انتظار داشتی پیش نمیرودWhen rebase does not go as expected
اگر rebase گیجکننده شد، دنبال «دستور نجات جادویی» نگرد. اول ببین Git در حال بازپخش کدام commit است، روی چه پایهای و فایل الان چه شکلی شده. بعد تصمیم بگیر ادامه بدهی، رد کنی یا کل عملیات را لغو کنی.
If the operation pauses, do not delete history or reset blindly. First identify the current branch and patch. During a rebase, each stop message is evidence about the current step.
روی شاخهٔ اشتباه rebase زدی
You rebased the wrong branch
قبل از فرمان git branch --show-current، git status و git log --graph --oneline --decorate --all را بخوان. rebase روی شاخهٔ جاری اثر میگذارد. اگر عملیات هنوز جاری است، بهجای ساختن فرمانهای جبرانی، شواهد را بخوان و در مخزن آزمایشی --abort را تمرین کن.
Before the command, inspect git branch --show-current, git status, and git log --graph --oneline --decorate --all. Rebase acts on the current branch. If it is in progress, inspect first; practice --abort in a disposable repository rather than stacking compensating commands.
پایهٔ اشتباه را انتخاب کردی
You chose the wrong base
نام پایه را از حدس نزن. با گراف ببین شاخه از کجا جدا شده و قرار است روی کدام commit بنشیند. اگر فقط تمرین است و هنوز بازپخش تمام نشده، git rebase --abort را بزن؛ بعد با نام پایهٔ درست و در حالی که شاخه هدف checkout است شروع کن.
Do not guess the base name. Use the graph to find where the branch diverged and which commit should become its base. In a disposable exercise, if replay is still in progress, abort; then restart on the intended checked-out branch and base.
تعارض پشت تعارض میآید
Conflicts keep recurring
ممکن است هر commit جداگانه همان ناحیه را تغییر دهد. patch جاری را با git rebase --show-current-patch ببین و هر مرحله را مستقل حل و تست کن. اگر نمیدانی نتیجهٔ مطلوب چیست، ادامه نده؛ در آزمایش میتوانی abort کنی.
Several commits may touch the same area separately. Inspect each patch with git rebase --show-current-patch, resolve and test each step, and do not continue if intent is unclear. You can abort the experiment.
برای ردشدن از تعارض، skip زدی
You skipped a commit just to get past a conflict
با --skip، تغییر همان commit در زنجیرهٔ تازه ثبت نمیشود. اگر هنوز معلوم نیست آن تغییر قبلاً وارد شده یا لازم است، abort کن و patch و پایه را بررسی کن. موفقشدن فرمان ثابت نمیکند محتوا کامل مانده.
--skip omits that commit’s change from the new line. If you have not established that the change is redundant or unnecessary, abort and inspect the patch and base. A successful command does not prove the content is complete.
فهرست interactive را برعکس خواندی
You read the interactive list backwards
todo از قدیمی به جدید خوانده و اجرا میشود. قبل از ذخیره، پیامها و ترتیب را با git log --reverse مقایسه کن. جابهجایی یک patch قبل از وابستگیاش میتواند تعارض یا نتیجهٔ نادرست بسازد.
The todo list is read and executed oldest to newest. Before saving, compare its order with git log --reverse. Moving a patch ahead of a dependency can create conflicts or incorrect results.
بعد از rebase میخواهی بیخبر force کنی
You plan to force-push without checking with the team
اول بررسی کن چه کسی شاخه را گرفته و مخزن راه دور از آخرین مشاهدهٔ تو جلو رفته یا نه. --force-with-lease از --force کور محافظت بیشتری میدهد، اما جای توافق و بررسی log را نمیگیرد. فصل بعد push را با مخزن راه دور واقعی تمرین میکنیم.
First find out who fetched the branch and whether the remote advanced since you last saw it. --force-with-lease is safer than blind force, but it does not replace agreement or inspecting the log. The next chapter practices pushing to a real remote.
تمرینها: از پیشبینی تا تصمیم تیمیExercises: from prediction to team decisions
تمرینهای این بخش بیشتر از شکل دستور دربارهٔ پیامدند. قبل از اجرا پیشبینی کن کدام commitها شناسهٔ تازه میگیرند، گراف چه شکلی میشود و اگر این تاریخ قبلاً با تیم مشترک شده باشد چه کسی ممکن است آسیب ببیند.
Write a short answer before opening each solution. The goal is not to memorize rebase options; it is to use the graph, Git’s messages, and your teammates’ state to decide whether it is safe to continue.
گراف A—B—C main و B—D—E feature است. بعد از checkout کردن قابلیت و اجرای git rebase main چه شکلی انتظار داری؟
The graph is A—B—C main and B—D—E feature. After checking out feature and running git rebase main, what shape do you expect?
راهنماییHint
اولین والد قابلیت بعد از پایهٔ تازه کدام commit میشود؟
What becomes the first parent after the new base?
پاسخ و دلیلReasoning
Git تغییرهای D و E را بهترتیب روی C بازپخش میکند: A—B—C—D′—E′. قابلیت به E′ اشاره میکند و main همان C میماند. D و E قدیمی به عقب یا جلو منتقل نمیشوند.
Git replays D and E, in order, on C: A—B—C—D′—E′. feature points to E′ and main stays at C. Old D and E are not moved.
محتوای فایلهای E و E′ دقیقاً برابر است، ولی والد آنها فرق دارد. آیا شناسه commit هم باید برابر باشد؟
The files in E and E′ are identical, but their parent differs. Must the commit IDs match?
راهنماییHint
commit فقط عکس فوری فایل نیست.
A commit is more than a file snapshot.
بیا بازش کنیمAnswer from Chapter 02’s model
نه. والد یکی از دادههای commit است؛ تغییر والد یعنی شیء دیگری و در حالت معمول شناسه تازه. یکسانبودن tree نهایی بهتنهایی شناسه commit را یکسان نمیکند.
No. The parent is part of the commit data. A different parent means a different object and normally a new ID. An identical final tree alone does not make commit IDs equal.
بعد از rebase، git status بدون تغییر است و log خطی دیده میشود. این دو شاهد چه چیزی را ثابت میکنند و چه چیزی را نه؟
After rebase, status is clean and the log looks linear. What do these two observations prove, and what do they not prove?
راهنماییHint
وضعیت ثبتنشده با صحت برنامه یکی نیست.
No uncommitted changes is not the same as correct behavior.
راهحل و توضیحAnalysis
بدون تغییر بودن یعنی پوشه کاری و ناحیه آمادهسازی تغییر گزارششده ندارند؛ شکل log میگوید اشارهگرها اکنون چه رابطهٔ والدها دارند. اینها ثابت نمیکنند patchی از دست نرفته، تستها میگذرند یا رفتار محصول درست است. diff و تست را هم بررسی کن.
A clean status means the working tree and index have no reported changes; the log shows current ancestry. Neither proves no patch was lost, tests pass, or product behavior is correct. Inspect the diff and run tests.
روی feature/search هستی. شاخهٔ مورد نظر برای پایه main است. کدام فرمان این rebase را شروع میکند؟
You are on feature/search, and main is the intended base. Which command starts this rebase?
راهنماییHint
فرمان روی شاخهٔ جاری کار میکند و نام پایه را میگیرد.
The command acts on the current branch and takes the base name.
چرا این جواب درست استCommand and preflight
git rebase main. پیش از آن git branch --show-current و git status را ببین تا مطمئن شوی شاخهٔ جاری همان قابلیت و کار ثبتنشدهای در خطر نیست.
git rebase main. Beforehand, inspect git branch --show-current and git status to confirm you are on feature and have no unprotected work.
Git هنگام بازپخش کردن patch قدیمی متوقف شده. فایل نهایی باید مقدار ۳۰ داشته باشد، چون برنامه اکنون فقط با آن سازگار است. چه مراحلی انجام میدهی؟
Git paused while replaying an old patch. The correct final value is 30 because that is what the application currently supports. What do you do?
راهنماییHint
اول تعارض و patch جاری را بشناس، بعد نسخهٔ نهایی را ناحیه آمادهسازی کن.
Inspect the conflict and current patch, then stage the intended result.
بررسی جوابCareful steps
git status و git rebase --show-current-patch را بخوان، فایل را به مقدار ۳۰ و بدون نشانگر اصلاح کن، آن را بازبینی و با git add FILE ناحیه آمادهسازی کن، سپس git rebase --continue. بعد تست مرتبط را اجرا کن؛ انتخاب مقدار، تصمیم نیاز برنامه است نه Git.
Read git status and git rebase --show-current-patch, edit the file to 30 without markers, review and stage it with git add FILE, then run git rebase --continue. Run the relevant test afterward; the value is a product decision, not Git’s.
در rebase آزمایشی، تعارض را نمیفهمی و هنوز معلوم نیست چه نتیجهای درست است. کدام مسیر امنتر است؟
In a disposable rebase, you do not understand the conflict and cannot tell which result is correct. What is safer?
راهنماییHint
لازم نیست هر عملیات شروعشده را به زور تمام کنی.
You do not have to force every started operation to completion.
پاسخ پیشنهادیExplanation
از git rebase --abort برای خروج از عملیات و بازگشت شاخه به وضعیت شروع rebase استفاده کن، بهخصوص اگر مخزن قبل از آن بدون تغییر بوده. بعد patch و خواسته را روشن کن و آزمایش را از پایهٔ جدا تکرار کن.
Use git rebase --abort to stop and restore the branch to its pre-rebase state, especially if the repository was clean beforehand. Clarify the patch and intent, then repeat in a separate clean experiment.
از این نقطه دیگر فقط اجرای rebase مهم نیست؛ باید بتوانی توضیح بدهی چه چیزی دوباره ساخته شد و چرا شناسهها عوض شدند. گراف قبل و بعد را کنار هم نگه دار.
From this point, merely completing a rebase is not enough. Explain what was recreated and why the IDs changed. Keep the before and after graphs side by side.
یک commit تعارض داده و فقط میخواهی پیام خطا ناپدید شود. همکارت پیشنهاد git rebase --skip داده. چرا این دلیل کافی نیست؟
A commit conflicted and you only want the error message to disappear. A teammate suggests git rebase --skip. Why is that insufficient?
راهنماییHint
skip دقیقاً با patch جاری چه میکند؟
What exactly happens to the current patch?
پاسخ و دلیلAnswer
skip patch همین commit را از زنجیرهٔ جدید حذف میکند. فقط وقتی آن تغییر غیرضروری یا از قبل در پایه است و با شاهد تأییدش کردهای، آن را انتخاب کن. در غیر این صورت تعارض را حل و ادامه بده یا rebase را abort کن.
Skip omits this commit’s patch from the new line. Use it only after proving the change is unnecessary or already in the base. Otherwise resolve and continue, or abort the rebase.
در rebase با merge backend پیشفرض، ours همیشه یعنی commit شاخهٔ قابلیت است؟
With the default merge backend during rebase, does ours always mean the feature commit?
راهنماییHint
Git اول چه زنجیرهای را ساخته است؟
Which series has Git built so far?
بیا بازش کنیمPrecise explanation
نه. ours نسخهٔ زنجیرهٔ بازپخششده از upstream است؛ theirs تغییر commit کاریای است که همین لحظه بازپخش میشود. چون جهت نامگذاری برخلاف حدس رایج است، از روی این واژهها فایل را انتخاب نکن؛ وضعیت و patch را بخوان.
No. Ours is the rebased series built from upstream; theirs is the work-branch commit being replayed. Because these labels can defy intuition, do not choose a file based on the words—inspect status and the patch.
در فهرست interactive، commit مستندات بالای commit کدی آمده. Git کدام را اول بازپخش میکند؟
In an interactive todo list, a documentation commit appears above a code commit. Which is replayed first?
راهنماییHint
فهرست را از بالا به پایین بخوان.
Read the list from top to bottom.
راهحل و توضیحAnswer
سطر مستندات اول بازپخش میشود. اگر به commit کد وابسته است، جابهجایی شاید تعارض یا محتوای نامعتبر بسازد؛ ترتیب را بر اساس وابستگی تغییرها انتخاب کن، نه مرتببودن ظاهری پیامها.
The documentation commit is replayed first. If it depends on the code commit, reordering may cause conflicts or invalid content. Order by dependency, not by how tidy the messages look.
میخواهی یک commit کوچک اصلاحی را با commit قبلی یکی کنی و پیام اصلاح کوچک هم لازم نیست بماند. در todo کدام گزینه مناسبتر است؟
You want to combine a small fix commit with the previous commit and do not need the small commit’s message. Which todo action fits?
راهنماییHint
یکی پیام commit ادغامی را نگه میدارد و دیگری امکان ترکیب پیامها را میدهد.
One discards the folded message; the other lets you combine messages.
چرا این جواب درست استChoice
fixup پیام commit اصلاحی را کنار میگذارد و patch آن را در قبلی میگنجاند. اگر پیامش توضیح مفیدی دارد و میخواهی در پیام نهایی بماند، squash را انتخاب و پیام نهایی را ویرایش کن.
fixup discards the fix commit’s message and folds its patch into the previous commit. If the message carries useful context that should remain, use squash and edit the combined message.
commit دوم از چهار commit را reword کردی. آیا انتظار داری فقط شناسه همان commit عوض شود؟
You reworded the second of four commits. Should only that commit’s ID change?
راهنماییHint
والد commitهای بعدی کدام شیء است؟
What object is the parent of later commits?
بررسی جوابExplanation
commit rewordشده شناسه جدید دارد؛ فرزندانش هم والد تازه خواهند داشت و معمولاً شناسهشان عوض میشود. commit قبلی قبل از آن ممکن است unchanged بماند. یک تغییر میانی میتواند همهٔ descendants را بازنویسی کند.
The reworded commit gets a new ID; its descendants now have new parents and normally new IDs. Earlier commits may remain unchanged. A mid-series edit can rewrite every descendant.
در todo، یک commit را drop میکنی. بعد از اتمام، چه چیزی باید حتماً بررسی شود؟
You mark a commit as drop. What must you inspect afterward?
راهنماییHint
آیا patch آن commit در commit دیگری هم تکرار نشده؟
Is its patch duplicated elsewhere?
پاسخ پیشنهادیAnalysis
بررسی کن رفتار مورد نیاز هنوز هست، diff شاخهٔ نهایی با نسخهٔ پشتیبان را بخوان و تست مرتبط را اجرا کن. drop patch را از این سری حذف میکند، اما ممکن است اثر همان تغییر به commitهای بعدی وابسته باشد؛ صرفاً سبزشدن rebase اثبات صحت نیست.
Verify the required behavior remains, inspect the final diff against the backup, and run relevant tests. Drop omits that patch, but later commits may depend on it; a successful rebase is not proof of correctness.
تمرینهای آخر وارد تاریخچهٔ مشترک میشوند. اینجا زیبایی گراف تنها معیار نیست. اگر همتیمی به شناسههای قدیمی وابسته است، بازنویسی هزینهٔ واقعی دارد.
The final exercises move into shared history. A tidy graph is not the only concern. If teammates depend on the old IDs, rewriting has a real coordination cost.
تو و دو همکار از commitهای یک شاخه استفاده میکنید. میخواهی همان شاخه را rebase کنی تا log خطی شود. قبل از بازنویسی چه تصمیمی لازم است؟
You and two teammates use commits on one branch. You want to rebase it for a linear log. What must be decided before rewriting?
راهنماییHint
اشارهگرهای همکاران خودبهخود عوض نمیشوند.
Teammates’ refs do not change automatically.
پاسخ و دلیلTeam decision
با همه هماهنگ کن که کدام تاریخچه مرجع است، چه کسی تغییرهای تازه را دارد و هر نفر چگونه شاخه خود را با خط جدید هماهنگ میکند. شاید merge مناسبتر باشد. فقط بعد از توافق، برنامهٔ push بازنویسیشده را با بررسی دقیق مخزن راه دور انجام بده.
Agree on the authoritative line, identify who has newer work, and plan how everyone will realign their branches. Merge may be preferable. Only after agreement should you plan a carefully checked update to the remote.
بعد از توافق تیمی قرار است شاخه بازنویسیشده را push کنی. چرا --force-with-lease معمولاً از --force کور بهتر است، و چه چیزی را حل نمیکند؟
After team agreement, you plan to push the rewritten branch. Why is --force-with-lease generally safer than blind --force, and what does it not solve?
راهنماییHint
به جلو رفتن اشارهگر دوردست از زمانی که تو آن را دیدی فکر کن.
Consider whether the remote ref advanced since you last saw it.
بیا بازش کنیمAnswer with limits
--force-with-lease اگر مخزن راه دور از مقدار مورد انتظار جلو رفته باشد معمولاً بهروزرسانی را رد میکند؛ force کور این حفاظ را ندارد و ممکن است commitهای جدید دیگران را کنار بزند. lease هماهنگی تیمی، فهمیدن اشارهگر درست یا بررسی تغییرها را جایگزین نمیکند.
--force-with-lease normally rejects an update if the remote has moved from the expected value; blind force lacks that guard and can displace teammates’ commits. A lease does not replace coordination, identifying the right ref, or reviewing changes.
بازبین میخواهد در تاریخچه معلوم بماند قابلیت از چه زمانی به main وصل شد و commitهای اصلی همان شناسهها را داشته باشند. کدام رویکرد با این نیاز جورتر است؟
A reviewer wants the history to show when feature joined main and preserve the original commit IDs. Which approach better fits?
راهنماییHint
به جای خطیکردن، اتصال دو مسیر را نگه دار.
Keep the join between the two lines instead of linearizing.
راهحل و توضیحConditional choice
merge با این هدف سازگارتر است: commitهای موجود دستنخورده میمانند و در حالت شاخههای واگرا merge commit اتصال را نشان میدهد. اگر fast-forward سیاست اجازه دهد، باید دربارهٔ ثبت صریح merge هم تصمیم بگیری؛ روش را با سیاست تیم هماهنگ کن.
Merge better fits: existing commits stay intact and, for diverged branches, a merge commit records the join. If fast-forward is possible, decide whether the policy requires an explicit merge record. Follow the team’s convention.
بعد از انتخاب پایه، Git یکی از commitها را در فهرست بازپخش نیاورد چون تغییر معادلش آنجا هست. آیا این بهتنهایی نشانهٔ ازبینرفتن کار است؟
After choosing the base, Git omits a commit from the replay list because an equivalent change is already there. Does that alone mean work was lost?
راهنماییHint
محتوای تغییر را در پایه و diff ببین، نه فقط تعداد commitها.
Inspect the change in the base and the diff, not just the commit count.
چرا این جواب درست استCheck
نه لزوماً؛ rebase میتواند commitهای معادل upstream را از فهرست بازپخش کنار بگذارد. با git show و diff ثابت کن همان تغییر واقعاً در پایه هست و تست را اجرا کن. کاهش تعداد commitها بهتنهایی نه موفقیت را ثابت میکند نه فقدان را.
Not necessarily; rebase can omit commits equivalent to ones already in the upstream. Use git show and a diff to prove the change is present in the base, then test it. A lower commit count alone proves neither success nor loss.
قبل از interactive rebase میسازی git branch backup/before-cleanup. این کار از چه چیزی محافظت میکند و از چه چیزی نه؟
Before interactive rebase, you create git branch backup/before-cleanup. What does that protect, and what does it not protect?
راهنماییHint
شاخه نام یک commit موجود است.
A branch names an existing commit.
بررسی جوابExplanation
این اشارهگر نوک شاخه قدیمی و commitهای قابلدسترسی از آن را نگه میدارد تا مقایسه و بازیابی محلی سادهتر شود. تغییرهای ثبتنشده را ذخیره نمیکند؛ آنها را جداگانه commit یا با روش مناسب حفظ کن. اشارهگر پشتیبان را تا پایان بازبینی حذف نکن.
The ref keeps the old tip and its reachable commits available for comparison and local recovery. It does not save uncommitted changes; preserve those separately. Keep the backup ref until review is complete.
در پایان rebase، log خطی است؛ اما تست timeout شکست میخورد و همکارت میگوید مخزن راه دور را با force بهروز کن. سه کار بعدی تو چیست؟
The rebase ended with a linear log, but the timeout test fails and a teammate says to force-update the remote. What are your next three actions?
راهنماییHint
شکل تاریخچه مهم است، اما محتوا و اشارهگر مشترک هم مهماند.
History shape matters, but so do content and shared refs.
پاسخ پیشنهادیInvestigation plan
۱) force نکن؛ diff را با اشارهگر پشتیبان مقایسه و patchهای timeout را بررسی کن. ۲) تست و فایل نهایی را بر اساس نیاز برنامه اصلاح کن، یا در صورت ابهام در محیط امن abort/بازیابی کن. ۳) وضعیت مخزن راه دور و کسانی که commitهای قبلی را گرفتهاند بررسی و برای بازنویسی هماهنگ کن؛ بعد از توافق، lease را فقط بهعنوان حفاظ اضافه به کار ببر.
1) Do not force-push; compare with the backup and inspect timeout patches. 2) Correct the result against application requirements and rerun tests, or recover safely if intent is unclear. 3) Check the remote and who fetched the old commits, then coordinate the rewrite; after agreement, use a lease only as an additional guard.
پروژهٔ کوچک: از شاخهٔ شلوغ تا سری آمادهٔ بازبینیMini-project: from a messy branch to a reviewable series
یک شاخهٔ محلی شلوغ را میگیری و قبل از بازبینی مرتبش میکنی. شرط مهم این است که محتوای نهایی عوض نشود؛ فقط داستان commitها روشنتر شود. برای همین اول یک نام نجات میسازیم تا هر لحظه بتوانیم «قبل» و «بعد» را کنار هم ببینیم.
Nika built a private search feature, but its history contains temporary notes and small corrections. She asks you to prepare it for review without silently changing the final content. Do this in a disposable repository; the goal is to tidy history, not publish or push it.
پروژه باید چهار commit محلی داشته باشد: پیادهسازی parser، اصلاح edge case، پیام موقت برای فیلترها، و تستها. قبل از ویرایش todo، ابتدا نام شاخه و پاکبودن مخزن را بررسی کن. سپس یک اشارهگر پشتیبان بساز و شناسه نوک شاخه و tree فعلی را یادداشت کن:
Create four local commits: parser implementation, an edge-case fix, a temporary filter message, and tests. Before editing the todo, verify the branch name and clean state. Create a backup ref and record the current tip and tree:
git status --short
git branch --show-current
git branch backup/search-before-review
git rev-parse HEAD
git rev-parse HEAD^{tree}
git log --oneline --reverse main..HEADحالا git rebase -i main را باز کن. ترتیب commitها را با وابستگی واقعیشان بررسی کن؛ پیام موقت را با reword به پیامی روشن تبدیل کن و اصلاح edge case را با fixup یا squash کنار parser بگذار. اگر از squash استفاده کردی، پیام نهایی را طوری بنویس که هر دو تغییر را توضیح دهد. هر تغییری در ترتیب را با تستها توجیه کن؛ برای «زیباترشدن گراف» dependency را نشکن.
Now open git rebase -i main. Check the real dependency order; use reword to replace the temporary message and fixup or squash to fold the edge-case correction into the parser commit. With squash, write a message that describes both changes. Justify any reordering with tests; do not break dependencies merely to make the graph look prettier.
پس از پایان، این شواهد را جمع کن:
When it finishes, collect this evidence:
git status --short
git log --oneline --graph --decorate --all
git rev-parse HEAD
git rev-parse HEAD^{tree}
git diff --exit-code backup/search-before-review HEAD
git diff --stat backup/search-before-review HEAD
git show --stat --oneline HEADtree نوک شاخه جدید را با مقدار ثبتشده مقایسه کن. اگر هدف فقط مرتبکردن commitها بوده، tree نهایی باید همان باشد؛ git diff --exit-code backup/search-before-review HEAD در صورت برابری treeها خروجی خالی و exit وضعیت صفر میدهد. بعد تعداد و پیام commitها را در گراف بخوان و تست برنامه را اجرا کن. اگر محتوا فرق دارد، تفاوت را ببین و علتش را بفهم؛ برابری در یک آزمایش هم بهتنهایی صحت معنایی برنامه را ثابت نمیکند.
Compare the new tip’s tree with the saved value. If the goal was only to reorganize commits, the final tree should be identical; git diff --exit-code backup/search-before-review HEAD is silent and exits zero when the trees match. Then review the graph and commit messages, and run the application tests. If content differs, inspect and explain it; identical trees in one experiment still do not prove semantic correctness.
- گراف قبل و بعد با نام شاخهها و شناسههای کوتاه.
- Before-and-after graphs with branch names and abbreviated IDs.
- نام اشارهگر پشتیبان و نتیجهٔ مقایسهٔ tree/diff.
- The backup ref name and tree/diff comparison result.
- فهرست commitهای نهایی، تست اجراشده و دلیل هر reword/fixup/reorder.
- The final commit list, test run, and reason for every reword, fixup, or reorder.
اشارهگر پشتیبان را تا وقتی بازبینی تمام نشده نگه دار. این اشارهگر تغییر ثبتنشدهای را پوشش نمیدهد و خودبهخود نسخهٔ منتشرشده نمیسازد. اگر چیزی از بین رفته یا tree عوض شده، اول علت را بررسی کن؛ پروژه به push یا حذف اشارهگر پشتیبان نیاز ندارد.
Keep the backup ref until review is complete. It does not protect uncommitted changes or publish a copy elsewhere. If something is missing or the tree changed, investigate first; this project requires neither pushing nor deleting the backup ref.
این بار همهچیز محلی بودEverything so far was local
تا اینجا همهٔ تغییرها روی یک ماشین بودند. حالا یک مخزن دیگر وارد داستان میشود و اسمهایی مثل origin/main ظاهر میشوند. فصل بعد کاری میکند که fetch، pull و push از حالت جادو بیرون بیایند.
In this chapter, main and feature were refs in the same repository. We have not contacted a second repository or pushed anything. Chapter 08 adds a remote and distinguishes names such as origin/main from local main; then we can connect local rewriting to real collaboration and pushing.
مرور کوتاهQuick reference
git rebase mainتغییرهای شاخهٔ جاری را یکییکی روی main بازپخش میکند.git rebase mainreplays the current branch’s changes one by one on main.- rebase commitهای قدیمی را جابهجا نمیکند؛ commitهای تازه با والد تازه میسازد.
- Rebase does not move old commits; it creates new commits with new parents.
- در تعارض، patch و نیاز برنامه را بخوان؛
--continueادامه میدهد،--abortعملیات را کنار میگذارد و--skippatch جاری را حذف میکند. - For conflicts, inspect the patch and application requirements;
--continueproceeds,--abortabandons the operation, and--skipomits the current patch. - interactive rebase برای مرتبکردن سری محلی است؛ دستکاری یک commit میتواند شناسه تمام فرزندانش را عوض کند.
- Interactive rebase can tidy a local series; changing one commit can change IDs for all its descendants.
- قبل از بازنویسی تاریخچه مشترک، از اشارهگرهای دیگران و هماهنگی تیم مطمئن شو. هیچ شعار کوتاهی جای بررسی گراف و نیاز تیم را نمیگیرد.
- Before rewriting shared history, account for others’ refs and coordinate. No slogan replaces inspecting the graph and the team’s needs.
- قبل و بعد rebase را روی گراف و با شناسههای واقعی توضیح بدهی.
- Explain a rebase before and after using the graph and actual IDs.
- تعارض بازپخش را با بررسی patch حل کنی، یا آگاهانه ادامه ندهی.
- Resolve a replay conflict by inspecting the patch, or deliberately stop.
- در interactive todo، action و ترتیب مناسب انتخاب کنی و نتیجه را با tree و تست ثابت کنی.
- Choose suitable interactive actions and order, then verify with the tree and tests.
- قبل از بازنویسی شاخهٔ مشترک با همتیمیها هماهنگ شوی و تفاوت lease با force کور را بدانی.
- Coordinate before rewriting a shared branch and explain the difference between a lease and blind force.