کدنامهمرجع‌های مهندسی نرم‌افزار، به فارسی
Git · پروژهٔ نهایی ۱Git · Final project 1

پروژهٔ ۱ — تاریخچهٔ تمیز

Project 1 — Build a clean, readable history

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

Three commits named wip, fix, and more are not the finished story. Reshape this local repository so each commit explains one coherent change—without losing correct content or working files.

۵مرحلهٔ تحویلmilestones
۳نمودارdiagrams
محلیبدون مخزن راه دورno remote

مأموریت: تمیزکاری بدون پاک‌کردن داستانMission: clean the story, not erase the work

مخزن کار می‌کند، اما تاریخچه‌اش حرف نمی‌زند: پیام‌های ضعیف، تغییرهای نامرتبط و عیب‌یابی کنار هم افتاده‌اند. مأموریتت پاک‌کردن رد گذشته نیست؛ یک نسخهٔ امن از نوک فعلی نگه دار و بعد تاریخچهٔ خصوصی را به مجموعه‌ای از commitهای قابل‌فهم تبدیل کن.

A teammate hands you a tiny export utility. It works, but its git log does not tell you which commit introduced the feature, which merely continued it, or which actually fixed a bug. A typo, a debug line, and generated files are mixed in too.

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

Your job is to produce a history a reviewer can understand from top to bottom. The goal is not merely fewer commits, nor one giant commit that hides everything. Choose meaningful boundaries and use tests and comparisons to prove that rewriting organized the story without changing the program’s intended result.

Messy local history before cleanupA good base commit is followed by wip, more, and fix. The commits mix feature work, typo, test continuation, bug fix, and debug output. base · goodplain-text exportwipCSV + README typomoreCSV testfixempty case + debug Outside graph: staged README typo ≠ unstaged clarification · dist/ generated · debug.trace
نمودار ۱ — این همان تاریخچهٔ اولیه است؛ فایل آماده‌شده و فایل کاری هم ممکن است دو نسخهٔ متفاوت از README داشته باشند. خط‌ها رو به جلو، از commit پایه به commitهای بعدی‌اند.Diagram 1 — This is the starting history; the staged and working versions of README may differ. Arrows point forward from the base commit.
مرز پروژهProject boundary

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

This project uses a local repository with no remote. History that has not been shared can be rewritten; doing the same to shared history can disrupt other people’s work. If you adapt the project to another repository, make a separate copy first. Better still, use only the disposable repository created for this brief.

مخزن شروع را بسازBuild the starter repository

برنامه عمداً کوچک است تا مسئله Git بماند، نه یادگیری یک framework. یک خروجی متنی و CSV می‌سازد. کد اولیه را اجرا کن؛ بعد تغییرهای مرحله‌ای زیر را در مخزن تازه اعمال کن و برای هر مرحله همان پیام commit ضعیف را ثبت کن. از پوشهٔ پروژهٔ واقعی‌ات استفاده نکن.

The application is intentionally tiny so the challenge stays about Git rather than learning a framework. It exports plain text and CSV. Run the baseline, then apply the staged changes below in a fresh repository, recording the deliberately poor commit messages at each step. Do not use a real project directory.

۱. فایل‌های پایه1. Baseline files

این فایل‌ها و آزمون اولیه را بساز. commit اول خوب است و باید به‌عنوان مرز قابل اعتماد، بدون تغییر محتوایی باقی بماند.

Create these files and the baseline test. The first commit is good; preserve it as a trustworthy boundary without changing its content.

src/export.js · test/export.test.js · README.md
// src/export.js
function plain(rows) {
  return rows.map((row) => `${row.name}: ${row.count}`).join("\n");
}

module.exports = { plain };

// test/export.test.js
const assert = require("node:assert/strict");
const { plain } = require("../src/export");

assert.equal(plain([{ name: "tea", count: 2 }]), "tea: 2");
console.log("baseline tests passed");

// README.md
# Parcel export

Tiny text and CSV export utility.

Run checks with `node test/export.test.js`.

قبل از commit، اجرا کن: خروجی باید baseline tests passed باشد. حالا git init، شاخهٔ محلی main و commit پایهٔ feat: add plain-text export بساز. شناسه‌اش را یادداشت کن؛ این commit بعداً نباید عوض شود.

Run the test first; it should print baseline tests passed. Initialize Git, use a local main branch, and make the base commit feat: add plain-text export. Record its ID; this commit must remain unchanged.

۲. سه commit بد، با محتوای مشخص2. Three poor commits with specific content

در commit با پیام wip، قابلیت CSV را به src/export.js اضافه کن و همان موقع در README، receive را اشتباه به recieve بنویس. commit بعدی با پیام more فقط آزمون CSV معمولی را اضافه کند: این ادامهٔ همان قابلیت است و بعداً نامزد ادغام با commit قبلی خواهد بود. commit سوم با پیام fix مورد آرایهٔ خالی را در CSV درست کند؛ اما برای بررسی موقت، یک console.log("[debug] csv rows", rows.length) هم در همان تابع CSV بگذارد. آزمون هر سه رفتار اصلی را بنویس: متن ساده، CSV معمولی، CSV خالی.

In the wip commit, add CSV support to src/export.js and misspell receive as recieve in the README. The next commit, more, should add only the ordinary CSV test: it continues the same feature and is a candidate to combine with the previous commit. The third, fix, should correct CSV output for an empty array—but also add a temporary console.log("[debug] csv rows", rows.length) inside that same CSV function. Test the plain-text case, ordinary CSV, and empty CSV.

محتوای آزمایش را دقیق نگه دارKeep the fixture exact

خروجی CSV معمولی را دقیقاً name,count\ntea,2 و خروجی آرایهٔ خالی را رشتهٔ خالی قرار بده. فایل‌های dist/export-preview.json و debug.trace را بساز، اما commit نکن. این دو فایل تولیدی باید در پایان ignore شوند؛ ignoreکردن فایل، فایل ردیابی‌شده را از تاریخچه بیرون نمی‌آورد.

Make ordinary CSV output exactly name,count\ntea,2 and empty input an empty string. Create dist/export-preview.json and debug.trace, but do not commit them. They should be ignored by the end; adding an ignore rule does not remove a file already tracked in history.

برای اینکه تفاوت ناحیه‌ آماده‌سازی و پوشه‌ کاری را واقعاً ببینی، در README غلط املایی را اصلاح کن و فقط همان اصلاح را ناحیه‌ آماده‌سازی کن؛ سپس یک جملهٔ توضیحی دربارهٔ خروجی CSV به README اضافه کن، ولی آن را ناحیه‌ آماده‌سازی نکن. باید برای README هم تغییر آماده‌شده و هم تغییر آماده‌نشده داشته باشی. در این مرحله، هرگز با git add . این تفاوت را از بین نبر.

To make the index/working-tree distinction observable, fix the README typo and stage only that correction. Then add one sentence explaining CSV output but leave it unstaged. README should now have both staged and unstaged changes. Do not erase this distinction with git add ..

برای ساخت این تاریخچه، تغییر CSV را در wip بگذار، آزمون CSV معمولی را در more، و guard ورودی خالی به‌علاوهٔ خط عیب‌یابی را در fix. بعد از هر مرحله، فقط فایل‌های همان مرحله را ناحیه‌ آماده‌سازی کن، diff آماده‌شده را بازبینی کن و commit بزن. برای جزئیات فایل نهایی و آزمون‌ها، بخش مرجع انتهای صفحه را ببین—اما اول خودت پیاده‌سازی را امتحان کن.

Build the history by putting CSV support in wip, the ordinary CSV test in more, and the empty-input guard plus debug line in fix. At each step, stage only that milestone’s files, inspect the staged diff, and commit. The reference section at the end gives the exact final files and tests—but try the implementation yourself first.

ثبت وضعیت آغازRecord the starting state

قبل از شروع بازنویسی، آزمون‌ها را اجرا و وضعیت، گراف، تفاوت staged/unstaged و شناسهٔ HEAD را بیرون از یادداشت‌های قابل بازنویسی ثبت کن. چون قرار است تاریخچه عوض شود، یک شاخه نجات روی نوک فعلی بساز و بررسی کن که واقعاً همان commit را نگه داشته است.

Before rewriting, run the tests and record status, graph, staged/unstaged differences, and HEAD outside the history you are about to rewrite. Create a safety branch at the current tip and verify that it points to the expected commit.

ثبت شواهد، بعد نجات نوک فعلی / capture, then preserve
git status --short --branch
git log --oneline --graph --decorate
git diff --cached -- README.md
git diff -- README.md
git rev-parse HEAD
git branch safety/pre-cleanup HEAD
git show-ref --verify refs/heads/safety/pre-cleanup

در خروجی وضعیت انتظار می‌رود README هم در ستون آماده‌شده و هم در ستون آماده‌نشده دیده شود و فایل‌های تولیدشده ردیابی‌نشده باشند. شاخهٔ نجات فقط commitها را نگه می‌دارد؛ تغییرهای ناحیه‌ آماده‌سازی و پوشه‌ کاری را ذخیره نمی‌کند. آن‌ها را جداگانه نگه دار و قبل از rebase مطمئن شو تمام محتوای موردنظر یا commit شده یا آگاهانه در همان تغییرهای محلی محفوظ مانده است.

Status should show README in both the staged and unstaged columns, with generated files untracked. The safety branch preserves commits only; it does not save index or working-tree changes. Preserve those separately and, before rebasing, ensure intended content is either committed or deliberately kept in local changes.

داستان مقصد را پیش از دستورها انتخاب کنChoose the destination story before typing commands

قبل از بازکردن rebase، برای هر تغییر جواب بده: آیا این یک قابلیت مستقل است؟ ادامهٔ همان قابلیت است؟ اصلاح bug است؟ متن مستندات است؟ قاعدهٔ نادیده‌گرفتن فایل تولیدیهاست؟ این پرسش‌ها مرز commit را می‌سازند. ابزار فقط راه اجرای تصمیم توست.

Before opening a rebase, classify each change: an independent feature, continuation of that feature, bug fix, documentation, or generated-file policy? Those answers define commit boundaries. The tool only executes your decisions.

A readable target historyThe good baseline remains. The feature and its test become one CSV feature commit, followed by an isolated empty-input fix, documentation, and ignore policy. base · unchangedplain-text exportfeat: add CSVimplementation + testfix: empty CSVno debug outputdocs: clarifyREADMEchore: ignoregenerated files One defensible arrangement, not the only valid answer: keep changes atomic and explain each boundary.
نمودار ۲ — یک تاریخچهٔ معقول: قابلیت و آزمون ادامه‌دهنده‌اش کنار هم، bug fix جدا، و مستندات و ignore rule در commitهای قابل توضیح. ترتیب دو commit آخر می‌تواند با استدلال تغییر کند.Diagram 2 — One defensible history: the feature and its continuation test together, an isolated bug fix, and explainable documentation and ignore commits. The final two commits may be ordered differently if justified.
نشانهٔ فعلیCurrent evidenceتصمیم توYour decisionدلیل قابل دفاعDefensible reason
wip + moreیک قابلیت کامل با آزمونOne complete feature with its testcommit دوم به‌تنهایی داستان مستقلی ندارد.The second commit has no useful standalone story.
fix + خط debugاصلاح bug را نگه دار؛ عیب‌یابی را حذف کن.Keep the fix; remove debugging output.چاپ موقت رفتار عملیاتی مطلوب نیست.Temporary output is not intended behavior.
README: staged و unstagedهر دو نسخه را ببین و آگاهانه انتخاب کن.Inspect both snapshots and choose deliberately.ناحیه‌ آماده‌سازی و پوشه‌ کاری دو عکس فوری جدا هستند.The index and working tree are separate snapshots.
dist/ و *.traceقانون ignore بساز؛ فایل تولیدی را commit نکن.Add ignore rules; do not commit generated output.خروجی محلی، بخشی از کد منبع نیست.Local output is not source code.

پنج ایستگاه تحویلFive delivery milestones

ایستگاه ۱ — خط مبنا و نسخهٔ امنMilestone 1 — baseline and safety copy

پروژه را مثل پنج ایستگاه ببین، نه فهرست دستورها. در هر ایستگاه باید چیزی را ثابت کنی: وضعیت اولیه ثبت شده، مرز commitها روشن است، بازنویسی امن بوده و محتوای نهایی عوض نشده است.

Run the baseline, then all three tests. Record initial status and graph. The safety/pre-cleanup branch must retain the original messy tip. If local staged or unstaged changes remain, understand where their content is; a safety branch is not a backup of local files.

ایستگاه ۲ — مرزها را با staging دقیق بسازMilestone 2 — create boundaries with deliberate staging

برای تغییرهای README، خروجی git diff --cached و git diff را جدا بخوان. اولی چیزی را نشان می‌دهد که commit بعدی خواهد دید؛ دومی تغییرهای خارج از ناحیه‌ آماده‌سازی را. از git add -p برای انتخاب hunk استفاده کن و بعد از هر انتخاب، git diff --cached را بخوان. در پایان باید بتوانی دقیق بگویی هر commit چه عکس فوریی را ثبت می‌کند.

Read git diff --cached and git diff separately for README. The former shows what the next commit will contain; the latter shows changes outside the index. Use git add -p to choose hunks and inspect the staged diff after every choice. You should be able to state exactly which snapshot each commit records.

ایستگاه ۳ — تاریخچهٔ خصوصی را بازنویسی کنMilestone 3 — rewrite private history

بعد از آنکه تغییرهای باقیمانده را در commitهای معنی‌دار و پوشه‌ کاری تمیز آماده کردی، interactive rebase را از commit پایه شروع کن. برای wip پیام بهتر انتخاب کن؛ more را در قابلیت CSV ادغام کن؛ روی fix توقف کن تا اصلاح خالی‌بودن را از خط عیب‌یابی جدا کنی؛ commitهای مستندات و ignore را نگه دار. فقط چون rebase گزینه‌ای دارد از reorder یا fixup استفاده نکن؛ باید نتیجه معنای بهتری داشته باشد.

Once remaining changes are in meaningful commits and the working tree is ready, start an interactive rebase from the base commit. Give wip a useful message; combine more into the CSV feature; stop at fix to separate the empty-input fix from debug output; keep the documentation and ignore commits. Do not use reorder or fixup merely because the option exists—the result must tell a better story.

طرح مفهومی todo؛ شناسه‌ها را از log خودت بردار / conceptual todo
reword <wip-oid> wip
squash <more-oid> more
edit <fix-oid> fix
pick <docs-oid> docs
pick <ignore-oid> ignore

این فقط طرح است: rebase فایل todo را با شناسه و پیام واقعی باز می‌کند؛ ترتیب ممکن است بر اساس commitهایی که خودت ساخته‌ای فرق کند. آن را با تاریخچه‌ات تطبیق بده. در توقف edit، diff را بررسی کن، خط عیب‌یابی را از فایل کاری حذف کن و آزمون را دوباره اجرا کن. اگر تعارض پیش آمد، فایل را اصلاح کن، آزمون بگیر و فقط سپس git rebase --continue. اگر مطمئن نیستی، git rebase --abort به وضعیت قبل از همین rebase برمی‌گردد؛ شاخه نجات نیز همچنان مرجع نوک قبلی است.

This is only a plan: Git opens the todo list with your actual commit IDs and messages, and the order may differ depending on how you created the commits. Adapt it to your history. At the edit stop, inspect the diff, remove the debug line from the working file, and rerun tests. If a conflict occurs, resolve it, test, and only then run git rebase --continue. If unsure, git rebase --abort returns to the state before this rebase; the safety branch remains a reference to the old tip.

ایستگاه ۴ — مقصد را با شواهد ثابت کنMilestone 4 — prove the destination

آزمون‌ها را اجرا کن؛ log گرافی و آمار commitها را ببین؛ فایل‌های نهایی را با نوک شاخه نجات مقایسه کن. تفاوت بین این دو tree باید دقیقاً شامل تصمیم‌های پروژه باشد: قابلیت و آزمون حفظ شده، bug fix حفظ شده، عیب‌یابی حذف شده، typo اصلاح شده، جملهٔ README حفظ شده و ignore rule اضافه شده. هیچ فایل فایل تولیدی نباید وارد tree نهایی شود.

Run tests; inspect the graph and commit statistics; compare the final files with the safety-branch tip. The tree difference should match the project decisions exactly: feature and tests retained, bug fix retained, debug removed, typo corrected, README sentence retained, and ignore rules added. No generated artifact should enter the final tree.

ایستگاه ۵ — نتیجه را تحویل بدهMilestone 5 — hand off the result

یک یادداشت کوتاه تحویل بنویس: commit پایه کدام است؟ هر commit نهایی چه مفهومی دارد؟ کدام تغییر را squash کردی و چرا؟ فایل‌های تولیدی چطور بررسی شدند؟ چه چیزی هنوز ثابت نشده؟ بعد از جمع‌آوری شواهد، فقط فایل‌های آزمایشی ساخته‌شده در همین پروژه را پاک کن. برای پاک‌سازی از فرمان‌های کلی و بی‌مرز استفاده نکن.

Write a short handoff: which commit is the base? What does each final commit mean? What did you squash and why? How were generated files checked? What remains unproven? After collecting evidence, remove only disposable files created by this project. Avoid broad cleanup commands.

شرط قبولی: نتیجه و داستان هر دو درست باشندAcceptance: both the result and its story must be right

قبولی فقط «گراف قشنگ» نیست. باید هم رفتار نهایی درست باشد، هم هیچ تغییر مطلوبی گم نشده باشد، هم هر commit دلیل مشخصی برای وجودش داشته باشد.

Run these checks on the final branch and keep your own outputs in the report; commit IDs and branch names will differ.

مدرک نهایی / final evidence
node test/export.test.js
git status --short --branch
git log --oneline --graph --decorate
git show --stat --oneline HEAD
git diff --check safety/pre-cleanup..HEAD
git diff --name-status safety/pre-cleanup..HEAD
git check-ignore -v dist/export-preview.json debug.trace
git status --short --ignored

قبولی یعنی آزمون موفق است؛ پوشه‌ کاری ردیابی‌شده تمیز است؛ log از پیام‌های توضیحی و مرزهای قابل فهم تشکیل شده؛ diff بین نوک قبلی و نهایی فقط تغییرهای خواسته‌شده را دارد؛ check-ignore هر دو فایل تولیدی را به rule درست وصل می‌کند؛ و commit پایه هنوز ancestor تاریخچهٔ جدید است. git status --ignored ممکن است فایل‌های تولیدی را با !! نشان دهد—این طبیعی است و با ردیابی‌نشده بودن یکی نیست.

Pass means tests succeed; the tracked working tree is clean; the log has explanatory messages and understandable boundaries; the old-to-new diff contains only intended changes; check-ignore maps both artifacts to the correct rules; and the base commit remains an ancestor of the rewritten history. git status --ignored may show generated files with !!—that is expected and differs from untracked.

مدرکEvidenceچه چیزی را ثابت می‌کندWhat it provesچه چیزی را ثابت نمی‌کندWhat it does not prove
node test/export.test.jsرفتارهای آزموده‌شده در این لحظه درست‌اند.The tested behaviors pass now.همهٔ ورودی‌های ممکن درست‌اند.Every possible input is correct.
git log --graphشکل و پیام commitهای قابل دسترس از اشاره‌گرها.Shape and messages of commits reachable from refs.کیفیت محتوا یا کامل‌بودن پوشه‌ کاری.Content quality or working-tree completeness.
git diff safety..HEADاختلاف tree دو نوک commit.Tree difference between the two tips.تغییرهای ردیابی‌نشده یا فایل‌های خارج از Git.Untracked files or files outside Git.
git check-ignore -vقاعده‌ای که فایل را ignore کرده.Which rule ignores each file.درست‌بودن همهٔ ruleها برای کل پروژه.That every project ignore rule is correct.

سه گیر واقعی، سه بررسی متفاوتThree realistic snags, three different checks

فایل آماده‌شده با چیزی که روی صفحه می‌بینی فرق داردThe staged file differs from what you see

این سه گیر برای اذیت‌کردن تو نیستند؛ می‌خواهند مجبور شوی به جای «درستش کردم» توضیح بدهی چه چیزی خراب بود و از کجا فهمیدی. هر بار قبل از تغییر، وضعیت را ثبت کن و بعد از اصلاح همان بررسی را تکرار کن.

This is not a bug: the index and working tree hold separate changes. Compare the cached and uncached diffs, then choose which hunk belongs in the commit.

بعد از squash پیام commit ناخواناستThe squashed commit message is unreadable

پیش‌فرض squash ممکن است هر دو پیام ضعیف را کنار هم بیاورد. در ادیتور، متن نهایی را به یک پیام کامل مثل feat: add CSV export تبدیل کن؛ پیام‌های قبلی را بی‌دلیل حفظ نکن.

A squash may combine both poor messages by default. Edit the final message to one complete description such as feat: add CSV export; do not preserve the old messages without a reason.

فایل تولیدی با وجود .gitignore هنوز دیده می‌شودAn artifact still appears despite .gitignore

ممکن است قبلاً ردیابی‌شده شده باشد؛ ignore rule فقط روی فایل‌های ردیابی‌نشده اثر دارد. git ls-files dist/export-preview.json را بررسی کن. اگر ردیابی‌شده است، حذف از ناحیه‌ آماده‌سازی باید آگاهانه و در commit سیاست انجام شود—نه با پاک‌کردن داده‌ای که لازم است.

It may already be tracked; ignore rules affect untracked files only. Check with git ls-files dist/export-preview.json. If tracked, remove it from the index deliberately in the policy commit—not by deleting data you need.

rebase پیام خطا می‌دهد یا تعارض می‌سازدRebase reports an error or conflict

اول وضعیت و مرحلهٔ rebase را بخوان؛ حدس نزن. اگر commit هنوز منتشر نشده و تصمیم به ادامه داری، تعارض را حل و آزمون را اجرا کن. اگر مسیر انتخاب‌شده غلط بود، git rebase --abort امن‌تر از دست‌کاری کورکورانهٔ اشاره‌گرهاست. شاخهٔ نجات را تا پایان بازبینی نگه دار.

Read status and the rebase state first; do not guess. If this is unpublished history and you intend to continue, resolve the conflict and test. If the chosen path is wrong, git rebase --abort is safer than blindly moving refs. Keep the safety branch until review is complete.

نردبان راهنماییProgressive hint ladder

راهنمایی ۱ — قبل از rebase چه چیزی را باید حفظ کنم؟Hint 1 — What should I preserve before rebasing?

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

Record the current tip and create a safety branch. Inspect staged and unstaged differences separately, and account for untracked files. Rebase moves history; it does not create a complete backup for you.

راهنمایی ۲ — چه چیزی واقعاً باید squash شود؟Hint 2 — What actually deserves a squash?

wip قابلیت را شروع می‌کند و more آزمون همان قابلیت را می‌آورد؛ در تاریخچهٔ نهایی، یک قابلیت ناقص و بعد یک آزمون صرفاً تکمیلی معمولاً دو داستان خوب نیستند. fix مورد خالی‌بودن اما علت و نتیجهٔ مستقلی دارد.

wip starts the feature and more adds its test; an incomplete feature followed by a test-only continuation usually is not two useful final stories. The empty-input fix has its own cause and outcome.

راهنمایی ۳ — در توقف edit چطور bug را از عیب‌یابی جدا کنم؟Hint 3 — How do I separate the fix from debugging at an edit stop?

از مفهومی که فصل ۳ تمرین کردی استفاده کن: تغییرهای فایل را ببین، hunkهای لازم را ناحیه‌ آماده‌سازی کن، بعد git diff --cached را وارسی کن. اگر دو تغییر در یک hunk ادغام شده‌اند، فایل را دستی به حالت مطلوب و قابل آزمون برسان؛ staging را حدس نزن.

Reuse the Chapter 3 model: inspect the file changes, stage the needed hunks, and verify git diff --cached. If both changes are in one hunk, edit the file into the intended testable state; do not guess what staging selected.

راهنمایی ۴ — چطور ثابت کنم نتیجه عوض نشده؟Hint 4 — How can I show the result did not change?

آزمون‌ها فقط بخشی از مدرک‌اند. tree نهایی را با tree نوک شاخه نجات مقایسه کن و هر مسیر تغییرکرده را توضیح بده. در این پروژه عمداً انتظار داریم خروجی برنامه و آزمون‌ها حفظ شوند؛ README و ignore سیاست هم اصلاح شوند؛ خط عیب‌یابی و فایل تولیدیها وارد نتیجهٔ ردیابی‌شده نشوند.

Tests are only part of the evidence. Compare the final tree with the safety tip and explain every changed path. Here, program behavior and tests should remain; README and ignore policy should be corrected; debug output and generated artifacts must not enter the tracked result.

راه‌حل مرجع — بعد از تلاش خودت بازش کنReference solution — open after your own attempt

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

This is not the only acceptable result. If you chose a different order, use the tree, tests, and clear messages to explain why your boundaries are better. The section below shows one complete, reproducible path.

نمایش مسیر مرجعShow the reference path

رفتار مرجع برنامهReference application behavior

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

The final code preserves the baseline function, adds a stable CSV header, and returns an empty string for an empty array. This version has no debug output. If you chose another structure, prove these same three behaviors with tests.

src/export.js و test/export.test.js
// src/export.js
function plain(rows) {
  return rows.map((row) => `${row.name}: ${row.count}`).join("\n");
}

function csv(rows) {
  if (rows.length === 0) return "";
  const body = rows.map((row) => `${row.name},${row.count}`).join("\n");
  return `name,count\n${body}`;
}

module.exports = { plain, csv };

// test/export.test.js
const assert = require("node:assert/strict");
const { plain, csv } = require("../src/export");

assert.equal(plain([{ name: "tea", count: 2 }]), "tea: 2");
assert.equal(csv([{ name: "tea", count: 2 }]), "name,count\ntea,2");
assert.equal(csv([]), "");
console.log("all export tests passed");

برای این ابزار آموزشی، escaping مقادیر CSV خارج از scope است؛ مقدارهای آزمون ساده و بدون comma یا newline هستند. این محدودیت را در README یا گزارش ذکر کن، نه اینکه ادعا کنی serializer عمومی ساخته‌ای.

CSV escaping is intentionally out of scope for this teaching utility; test values contain no commas or newlines. State that limitation in the README or handoff instead of claiming this is a general-purpose serializer.

.gitignore و README نهایی
# .gitignore
node_modules/
dist/
*.trace

# README.md
# Parcel export

Tiny text and CSV export utility.

CSV output includes a header row when at least one record is present.

Run checks with `node test/export.test.js`.

نقشهٔ todoTodo plan

commitهای README و ignore را قبل از rebase به شکل دو commit مستقل و آزموده‌شده بساز: docs: explain CSV output و chore: ignore generated files. commit اول README هم typo را اصلاح می‌کند و هم توضیح کوتاه را نگه می‌دارد. فایل .gitignore شامل node_modules/، dist/ و *.trace است. سبعد از commit پایه rebase کن و سه commit ضعیف را طبق طرح به reword، squash و edit تبدیل کن.

Before rebasing, make two separate tested commits for README and ignore policy: docs: explain CSV output and chore: ignore generated files. The README commit corrects the typo and retains the short explanation. .gitignore contains node_modules/, dist/, and *.trace. Then rebase from the base commit and mark the three poor commits as reword, squash, and edit.

بازسازی مرز fix در edit / reconstruct the fix boundary
# At the edit stop, inspect first
git status --short
git show --stat --oneline HEAD
git diff HEAD^ HEAD -- src/export.js test/export.test.js

# Rebuild this unpublished commit from its parent
git reset --mixed HEAD^
git diff -- src/export.js
git diff -- test/export.test.js

# Edit src/export.js: keep the empty-input behavior, remove console.log.
# Stage the corrected source and the empty-input test deliberately.
git add -p src/export.js
git add -p test/export.test.js
git diff --cached
node test/export.test.js
git commit -m "fix: handle empty CSV input"

# Confirm no debug line remains before continuing
git grep -n '\[debug\]' HEAD -- src test
git status --short
git rebase --continue

اگر فرمان git grep موردی پیدا نکند، exit وضعیت غیرصفر می‌دهد؛ اینجا نتیجهٔ مطلوب است، چون رشتهٔ عیب‌یابی در commit نهایی نیست. اگر هنوز در پوشه‌ کاری خط عیب‌یابی مانده، قبل از ادامه حذفش کن. git reset --mixed HEAD^ فقط ناحیه‌ آماده‌سازی را با والد هماهنگ می‌کند و فایل‌های پوشه‌ کاری را نگه می‌دارد؛ با این حال فقط در توقف edit روی همین مخزن آزمایشی اجرا کن.

If git grep finds no match, it exits nonzero; that is the expected result because the debug string is absent from the final commit. If it remains in the working tree, remove it before continuing. git reset --mixed HEAD^ resets the index to the parent while keeping working files; run it only at this edit stop in the disposable repository.

شکل نهایی و نسخهٔ نجاتFinal shape and safety reference

Safety branch preserves the original tip while main is rewrittenBoth branches share the unchanged good base. The safety branch points to the old messy tip; main points to the new clean history. base · unchangedshared ancestor safety/pre-cleanupold commits remain reachable main · rewrittenclean, tested story Compare before cleanuptests · tree diff · statusremove safety ref only later
نمودار ۳ — شاخه نجات، نوک قدیمی را قابل دسترس نگه می‌دارد؛ commit پایه در هر دو مسیر مشترک است. تا زمانی که مقایسه و بازبینی تمام نشده، شاخه نجات را حذف نکن.Diagram 3 — The safety branch keeps the old tip reachable; both histories share the same base. Do not remove the safety branch until comparison and review are complete.

بعد از rebase، آزمون و پذیرش را دوباره اجرا کن. برای اثبات ثابت‌ماندن پایه، شناسه‌ای که قبل از کار ثبت کردی را با git merge-base --is-ancestor <base-oid> HEAD بررسی کن؛ کد خروج صفر یعنی پایه ancestor است. برای قیاس محتوا، git diff safety/pre-cleanup..HEAD را بخوان: این tree نوک شاخه قدیمی را با tree نهایی مقایسه می‌کند، نه تک‌تک تاریخچه‌ها را. تا تحویل نهایی، شاخه نجات را نگه دار.

After rebasing, rerun tests and acceptance checks. To prove the base remains, use the recorded ID with git merge-base --is-ancestor <base-oid> HEAD; exit code zero means it is an ancestor. For content comparison, inspect git diff safety/pre-cleanup..HEAD: it compares the old and final tip trees, not every historical step. Keep the safety branch through handoff.

بازبینی نهاییFinal review

  • آیا قابلیت CSV همراه با آزمونش یک commit کامل و قابل فهم است؟Is CSV support together with its test one understandable, complete commit?
  • آیا bug fix مستقل مانده و عیب‌یابی از منبع و test حذف شده؟Is the bug fix independent, with debug output removed from source and tests?
  • آیا README در tree نهایی هم typo اصلاح‌شده و هم توضیح موردنظر را دارد؟Does the final README contain both the typo correction and intended explanation?
  • آیا فایل تولیدیها ignore شده‌اند، نه اینکه ناخواسته ردیابی‌شده باشند؟Are artifacts ignored rather than accidentally tracked?
  • آیا می‌توانی علت هر مرز commit را با شواهد توضیح بدهی؟Can you justify every commit boundary with evidence?

حالا نوبت کار تیمی استNext, work as a team

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

Your history is now clean and readable on one machine, and the old tip remains safe through review. But this is not collaboration yet: the commits have not been published or seen by anyone else. In the next project, we will carry this story through a remote, where two people work on separate branches, review changes, and decide how to integrate them.

مراجع کوتاهShort references

اگر رفتار فرمانی با نسخهٔ Git تو فرق داشت، متن راهنمای همان نسخه را ملاک بگیر. این پروژه بر staging تعاملی، وضعیت ناحیه‌ آماده‌سازی و پوشه‌ کاری، و فرمان‌های interactive rebase تکیه دارد.

If command behavior differs in your Git version, consult that version’s manual. This project relies on interactive staging, index/working-tree status, and interactive rebase commands.