ابزارهای پیشرفته
Advanced Git tools: worktree, submodule, sparse-checkout, hooks, and LFS
این فصل جعبهابزار عجیبوغریب Git نیست. پنج درد مشخص داریم و برای هرکدام یک ابزار تخصصی: worktree، submodule، sparse-checkout، hook و Git LFS. اگر آن درد را نداری، احتمالاً به ابزارش هم نیاز نداری.
You need a hotfix alongside unfinished work, only part of a monorepo, an independently maintained library, an early check for a recurring mistake, and versioned large files. These are different problems, so we will first identify which part of the workflow each tool changes.
اول مسئله را بشناسیمName the problem before the tool
قبل از اینکه اسم ابزارها را حفظ کنی، مسئله را ببین. یکجا میخواهی دو شاخه را همزمان باز داشته باشی، یکجا مخزن دیگری را به نسخهٔ مشخص وصل کنی، یکجا فقط بخشی از monorepo را روی دیسک ببینی. اینها پنج مسئلهٔ متفاوتاند و راهحل مشترک ندارند.
Imagine preparing an urgent fix, inspecting only part of a large repository, keeping an independent library beside an application, preventing a recurring mistake, and versioning large files—all at once. It is tempting to search for “an advanced Git tool,” but these are five different problems.
هر ابزار یک بُعد متفاوت را تغییر میدهد: worktree محلهای کاری همزمان میسازد؛ submodule یک مخزن مستقل را به نسخهای مشخص از مخزن والد وصل میکند؛ sparse-checkout تعیین میکند چه مسیرهایی در پوشه کاری دیده شوند؛ hook یک فرمان محلی را در نقطهای از گردش کار اجرا میکند؛ و Git LFS محتوای فایلهای بزرگ را بیرون از مخزن عادی نگه میدارد و در Git به آن اشاره میکند.
در این فصل هر بار از یک درد واقعی شروع میکنیم، یک آزمایش کوچک انجام میدهیم و بعد مرز ابزار را هم میگوییم. ابزار پیشرفته قرار نیست جای فهم مدل Git را بگیرد؛ فقط وقتی مسئلهاش را درست تشخیص داده باشیم کمک میکند.
Each tool changes a different dimension: worktree creates concurrent working trees; submodule pins an independent repository to a specific commit; sparse-checkout chooses which paths appear in the working tree; a hook runs a local command at a workflow event; Git LFS keeps large-file payloads outside ordinary Git objects while Git stores a pointer.
We will begin each topic with a real frustration, run a small experiment, and then state the tool’s boundary. Advanced tools do not replace understanding Git’s model; they help only after we identify the right problem.
۱. اصلاح فوری، بدون بههمزدن کار نیمهتمام1. Make a hotfix without disturbing unfinished work
روی شاخهٔ feature/search مشغول کاری و فایلهایت هنوز آمادهٔ commit نیستند. ناگهان یک باگ مهم گزارش میشود که باید روی main اصلاح شود. stash کردن ممکن است، اما اگر این جابهجایی چند بار تکرار شود، احتمال اشتباه بالا میرود. آیا لازم است هر بار یک پوشه کاری را جمع کنیم تا شاخه عوض شود؟
git worktree اجازه میدهد یک مخزن، چند پوشه کاری داشته باشد. دادهها و مخزن Git مشترکاند، اما هر محل کاری پوشه و شاخهٔ خودش را دارد. به این ترتیب، کار نیمهتمام در پوشهٔ خودش میماند و اصلاح فوری در پوشهای دیگر انجام میشود.
برای اینکه آزمایش روی مخزن واقعی یا کاری ناتمام اثر نگذارد، یک مخزن disposable بساز:
mkdir worktree-lab
cd worktree-lab
git init -b main
git config user.name "Learner"
git config user.email "learner@example.test"
echo "base" > app.txt
git add app.txt
git commit -m "base"
git worktree add ../hotfix-lab -b hotfix
git worktree list
git -C ../hotfix-lab status
حالا در پوشهٔ اصلی یک فایل را تغییر بده و در پوشهٔ hotfix-lab فایل دیگری را. هر کدام وضعیت جداگانه دارند. شاخهها و اشیای commit اما متعلق به همان مخزن مشترکاند.
یک محدودیت مهم: بهطور معمول Git اجازه نمیدهد یک شاخه را همزمان در دو worktree checkout کنی. اگر دستور میگوید شاخه از قبل در جای دیگری استفاده میشود، اول با git worktree list محلش را پیدا کن؛ با force کردن، مسئله را پنهان نکن. مستند رسمی این رفتار و فرمانهای مدیریت worktree را توضیح میدهد: worktree.
بعد از تمامشدن کار، از ریشهٔ مخزن میتوانی محل اضافه را با git worktree remove ../hotfix-lab حذف کنی. Git اگر آن worktree تغییر ثبتنشده داشته باشد از حذف عادی محافظت میکند. اگر پوشه را بیرون از Git پاک کردهای و رکوردش مانده، ابتدا git worktree prune --dry-run را ببین؛ فقط بعد از بررسی خروجی، prune واقعی را اجرا کن. پوشهای را که هنوز به تغییرهایش نیاز داری حذف نکن.
کِی استفاده نکنیم؟ اگر فقط گاهی شاخه عوض میکنی و stash یا commit موقت مسئله را ساده حل میکند، چند worktree مدیریت اضافهای است. برای اجرای موازی ساختها یا اصلاح فوری کنار یک کار نیمهتمام، ارزشش بیشتر میشود.
You are midway through feature/search, with uncommitted files, when an important bug must be fixed on main. Stashing is possible, but repeated switching increases the chance of mistakes. Must we keep packing away one working tree to change branches?
git worktree lets one repository have multiple working trees. Repository data is shared, but each working location has its own directory and checked-out branch. The unfinished feature stays where it is while the hotfix happens elsewhere.
Run the commands above in a disposable repository. Change different files in the original directory and hotfix-lab; each location has its own status, while commits and repository objects belong to the same repository.
Important limit: Git normally prevents the same branch from being checked out in two worktrees at once. If a command says the branch is already in use, locate it with git worktree list; do not hide the problem with force. The official git-worktree documentation describes this behavior and the management commands.
When finished, remove the additional location from the main repository with git worktree remove ../hotfix-lab. Git protects a worktree with uncommitted changes from ordinary removal. If you deleted its directory outside Git, inspect git worktree prune --dry-run before pruning stale records. Do not prune a worktree that is merely on a temporarily disconnected drive.
When not to use it: if you switch branches occasionally and stash or a temporary commit is simple, extra worktrees add management overhead. They are more useful for parallel builds or a hotfix beside unfinished work.
۲. کتابخانهٔ جداگانه را به نسخهٔ مشخص وصل کنیم2. Pin an independent library to a specific version
تیم یک کتابخانهٔ داخلی دارد که تاریخچه، دسترسی و انتشار خودش را دارد. کپیکردن فایلهایش داخل برنامه، ارتباط با مخزن اصلی را از بین میبرد. در عوض میتوانیم آن مخزن را بهعنوان submodule اضافه کنیم.
submodule «پوشهای که Git والد همهٔ فایلهایش را مثل بقیه دنبال کند» نیست. والد یک gitlink نگه میدارد: اشارهای به commit مشخص از مخزن فرزند. مسیر و نشانی اولیهٔ مخزن فرزند هم در .gitmodules ثبت میشود. تاریخچه و commitهای کتابخانه همچنان در مخزن خودشاند.
To add one, run git submodule add URL vendor/library with a reachable child-repository URL in a disposable parent repository. Inspect the staged .gitmodules and gitlink, then commit them in the parent. The URL here is a placeholder, not a working public repository.
git submodule add URL vendor/library
git status --short
git diff --cached -- .gitmodules
git commit -m "Add library submodule"
فردی که مخزن والد را تازه میگیرد، ممکن است پوشهٔ submodule را خالی ببیند؛ چون clone معمولی لزوماً محتوای فرزند را checkout نمیکند. برای دریافت همهٔ submoduleهای تودرتو:
git clone --recurse-submodules URL
# یا پس از clone معمولی:
git submodule update --init --recursive
git submodule status
در خروجی وضعیت، پیشوندها سرنخاند: - یعنی هنوز initialize نشده، + یعنی commit checkoutشده با commit ثبتشده در والد فرق دارد، و فاصله یعنی با نسخهٔ ثبتشده همخوان است. بهروزرسانی معمول submodule میتواند آن را روی commit مشخص و جداشده HEAD بگذارد؛ این بهخودیخود خطا نیست. مستندات رسمی، رفتار clone، update و وضعیت را شرح میدهند: submodule و gitsubmodules.
اگر میخواهی نسخهٔ کتابخانه را جلو ببری، وارد پوشهٔ فرزند شو، شاخهٔ مناسب را checkout کن، تغییر را در خود مخزن فرزند commit و به مخزن راه دور آن push کن. بعد برگرد به والد و تغییر gitlink را commit کن. اگر commit فرزند را push نکنی، همتیمیها شاید به commit اشارهشده دسترسی نداشته باشند.
کِی استفاده نکنیم؟ اگر وابستگی نسخهٔ بستهبندیشده و مدیریتشدهای دارد، package مدیر معمولاً نصب و انتشار سادهتری میدهد. submodule وقتی معنادار است که واقعاً میخواهی کد و تاریخچهٔ آن مخزن مستقل بماند ولی پروژه روی یک commit مشخص تکیه کند.
Suppose another team owns an internal library with its own history, access rules, and releases. Copying its files into your application loses the link to its source repository. A submodule can instead connect the repositories while keeping their ownership separate.
A submodule is not an ordinary directory whose files the parent tracks normally. The parent stores a gitlink—a reference to a particular commit in the child repository—and records the child path and initial URL in .gitmodules. The child keeps its own commits and branches.
A fresh clone may show an empty submodule directory because the child has not yet been checked out. Use the recursive clone command above, or initialize an existing clone. In git submodule status, - means uninitialized, + means the checked-out commit differs from the parent’s recorded commit, and a leading space indicates a match. Updating commonly checks out the pinned commit in detached-HEAD state; that is not automatically an error. See the official git-submodule and gitsubmodules references.
To advance the library, work inside the child repository: check out the intended branch, commit there, and push to its remote. Then return to the parent and commit the updated gitlink. If the child commit is not pushed, teammates may be unable to retrieve the commit the parent references.
When not to use it: if the dependency is released and managed by a package manager, that is usually simpler. A submodule makes sense when the child’s repository and history should remain independent while the parent pins an exact commit.
۳. مخزن بزرگ است؛ همهٔ فایلها را لازم نداریم3. A large repository does not mean every file is needed
در یک monorepo چندین سرویس وجود دارد، اما تو فقط روی services/catalog کار میکنی. حذف بقیه با دست، فایلها را از دید Git پنهان نمیکند و دوبارهسازی محیط هم شکننده میشود. sparse-checkout مشخص میکند کدام مسیرها در پوشه کاری ظاهر شوند.
در مخزن آزمایشیای که مسیرهای services/catalog، services/billing و docs دارد، از حالت cone استفاده کن:
git sparse-checkout init --cone
git sparse-checkout set services/catalog
git sparse-checkout list
git status --short
git ls-tree --name-only HEAD
حالت cone انتخاب را حول پوشهها نگه میدارد و شکل سادهتر و قابلپیشبینیتری دارد؛ این حالت پیشفرض مستندات فعلی است. مسیرهای سطح بالا نیز ممکن است دیده شوند. فایلهای بیرون الگو در پوشه کاری حاضر نیستند، اما commitها همچنان در مخزناند. قبل از تغییر الگو، فایلهای محلی مهم را بررسی کن: تغییر قوانین sparse میتواند فایلهای نادیدهگرفتهشدهای را که زیر مسیر حذفشوندهاند پاک کند.
نکتهٔ ظریف: sparse-checkout بهتنهایی تضمین نمیکند clone اولیه حجم کمتری از شبکه بگیرد؛ عمدتاً شکل پوشه کاری را کنترل میکند. برای دریافت تدریجی محتوای فایلها، partial clone را جداگانه بررسی میکنیم:
git clone --filter=blob:none --sparse URL
cd repository
git sparse-checkout set services/catalog
--filter=blob:none دریافت blobها را تا زمان نیاز به تعویق میاندازد، اگر سرور از فیلتر پشتیبانی کند؛ Git ممکن است بعداً برای نمایش یا checkout فایل، آن داده را بگیرد. پس sparse انتخاب مسیر است و partial clone انتخاب میزان دریافت شیء؛ یکی را بهجای دیگری فرض نکن. مرجعها: sparse-checkout و clone.
کِی استفاده نکنیم؟ در مخزن کوچک، پیچیدگی الگوها سودی ندارد. برای monorepo بزرگ و کار محدود به چند پوشه مفید است؛ اگر مسئله حجم انتقال اولیه است، باید filtering را هم جداگانه ارزیابی کنی.
A monorepo contains several services, but you work only on services/catalog. Manually deleting the other directories is fragile and does not tell Git to ignore them. Sparse checkout selects which paths appear in your working tree.
In a disposable repository containing services/catalog, services/billing, and docs, try cone mode with the commands above. Cone mode selects directories and is the default in current documentation. Top-level paths may also appear. Excluded files remain in repository history, but changing the sparse patterns can remove ignored files beneath directories that are no longer selected—inspect local files first.
A subtle distinction: sparse checkout alone does not guarantee that the initial clone transfers fewer objects; it primarily shapes the working tree. Partial clone separately defers blob downloads with --filter=blob:none, when the server supports filtering. Git may fetch those objects later when needed. Sparse checkout chooses paths; partial clone filters object transfer. See git-sparse-checkout and git-clone.
When not to use it: pattern management is not worth it for a small repository. It helps in a large monorepo when work is limited to a few directories; if the problem is initial transfer size, evaluate filtering separately.
۴. اشتباه تکراری را زودتر متوقف کنیم4. Catch a recurring mistake earlier
در تیم چند بار رشتهٔ آزمایشی DEBUG_ONLY با commit وارد مخزن شده و بعداً دردسر ساخته است. یک کنترل سبک قبل از commit میتواند همان لپتاپ را خبر کند. Git hook اسکریپتی است که در رویدادی مشخص اجرا میشود؛ مثلاً pre-commit قبل از ساخت commit، یا commit-msg برای بررسی پیام.
در ریشهٔ مخزن آزمایشی، این hook کوچک را بساز. این فقط یک نمونهٔ آموزشی برای محتوای آمادهشده است:
mkdir .githooks
cat > .githooks/pre-commit <<'EOF'
#!/bin/sh
if git diff --cached --unified=0 | grep '^+' | grep -F 'DEBUG_ONLY' >/dev/null; then
echo "Remove DEBUG_ONLY from staged changes." >&2
exit 1
fi
exit 0
EOF
chmod +x .githooks/pre-commit
git config core.hooksPath .githooks
حالا رشته را در یک فایل بگذار، ناحیه آمادهسازی کن و commit بزن. hook باید پیام بدهد و commit انجام نشود؛ تغییر آمادهشده سر جایش میماند تا اصلاحش کنی. خط را پاک کن، دوباره ناحیه آمادهسازی کن و commit را تکرار کن.
برای اجراشدن، hook باید executable باشد و مسیر hooks درست پیکربندی شده باشد. میتوانی مسیر را با git config --get core.hooksPath بررسی کنی. Git hookهای مخزن را بهطور خودکار برای clone جدید فعال نمیکند؛ و درست هم همین است، چون اجرای خودکار اسکریپت دریافتشده میتواند ناامن باشد. همچنین کاربر محلی میتواند بعضی hookها را با گزینهٔ --no-verify دور بزند. پس hook برای بازخورد سریع خوب است، اما قانون مهم باید در CI یا سمت سرور هم بررسی شود. جزئیات hookها و نقاط اجرای آنها در مستند رسمی githooks آمده است.
کِی استفاده نکنیم؟ برای سیاستی که باید همه رعایت کنند، به hook محلی تنها تکیه نکن. از hook برای کنترل سریع و کمهزینه روی دستگاه توسعهدهنده استفاده کن و کنترل نهایی را جای قابلاعتمادتر اجرا کن.
A team has repeatedly committed the temporary marker DEBUG_ONLY. A lightweight local check can catch it before the commit is created. A Git hook is a script invoked at a workflow event, such as pre-commit or commit-msg.
Create the small teaching hook shown above in a disposable repository. Stage a line containing the marker and try to commit: the hook should print a message and return nonzero, so no commit is created while the staged changes remain. Remove the marker, stage the correction, and retry.
The hook must be executable and Git must be configured to use its directory; check git config --get core.hooksPath. Git does not automatically enable repository hooks in a fresh clone, which avoids silently running received code. A local user can also bypass some hooks with --no-verify. Use hooks for fast feedback, but enforce important policy in CI or on the server. See the official githooks documentation.
When not to use it: do not rely on a local hook alone for a rule everyone must follow. Use it for cheap developer feedback and run the authoritative check somewhere trustworthy.
۵. فایل حجیم را کجا نگه داریم؟5. Where should large files live?
در یک بازی، فایلهای PSD و ویدیوی خام چندصد مگابایتاند. هر تغییرشان میتواند اندازهٔ clone و تاریخچه را سنگین کند. Git برای متن و فایلهای معمول عالی است، اما نگهداری نسخههای متعدد از فایلهای دودویی حجیم میتواند برای مخزن و تیم هزینهساز شود.
Git LFS در Git یک فایل اشارهگر کوچک نگه میدارد که شناسه و اندازهٔ محتوای واقعی را توصیف میکند. محتوای اصلی در سرویس LFS ذخیره میشود. هنگام checkout، فیلترهای LFS میتوانند محتوای واقعی را دریافت کنند و هنگام push، Git LFS آن محتوای اصلی را به endpoint مربوط میفرستد. پس Git معمولی بهتنهایی کل انتقال و میزبانی LFS را تضمین نمیکند؛ نصب ابزار و پشتیبانی میزبان و دسترسی شبکه/سهمیه هم لازم است.
روی مخزن disposable و میزبان سازگار، ابتدا Git LFS را نصب و برای کاربر فعال کن، سپس الگو را ثبت کن:
git lfs install
git lfs track "*.psd"
git check-attr filter diff merge -- assets/mock.psd
git add .gitattributes assets/mock.psd
git lfs ls-files
git show :assets/mock.psd
در انتهای آزمایش، نسخهٔ ناحیه آمادهسازیشدهٔ فایل تحت ردیابی معمولاً اشارهگر متنی LFS را نشان میدهد، نه محتوای باینری. .gitattributes باید commit شود تا همتیمیها همان الگو را داشته باشند. فرمانهای track در راهنمای رسمی git-lfs-track آمدهاند؛ معرفی پروژه و سازوکار اشارهگر در مخزن رسمی Git LFS است.
اگر فایل حجیم را قبل از فعالکردن LFS commit کردهای، اجرای git lfs track فایلهای قدیمی تاریخچه را بهتنهایی تبدیل نمیکند. انتقال تاریخی نیازمند عملیات جداگانهای است که شناسهٔ commitها را عوض میکند؛ روی مخزن مشترک بدون هماهنگی انجامش نده. برای این فصل، تاریخچهٔ واقعی را بازنویسی نمیکنیم.
کِی استفاده نکنیم؟ برای فایلهای کوچک یا میزبان بدون پشتیبانی LFS، خود Git یا راهکار بستهبندی معمولی سادهتر است. LFS وقتی مناسب است که فایلهای دودویی بزرگ واقعاً بخشی از نسخهٔ پروژهاند و تیم ظرفیت/سهمیهٔ ذخیرهسازی و انتقالش را دارد.
A game project includes large PSD files and raw video. Each revision can make clones and history heavier. Git is excellent for text and ordinary files, but repeated versions of large binaries can impose substantial storage and transfer costs.
Git LFS stores a small pointer in Git describing the content’s identifier and size, while an LFS service stores the actual payload. LFS filters can fetch the content during checkout and send it to the LFS endpoint during push. Ordinary Git alone does not guarantee LFS hosting or transfer: the client, host support, network access, and quota also matter.
In a disposable repository and on a compatible host, install Git LFS, track a harmless test file, stage it, and inspect it. The staged version of a tracked file should be a small text pointer rather than the binary payload. Commit .gitattributes so teammates share the tracking rule. See the official git-lfs-track guide and Git LFS project.
If a large file was committed before LFS tracking was enabled, git lfs track does not convert old history. Historical migration is a separate operation that changes commit IDs; do not run it on a shared repository without coordination. We will not rewrite real history in this chapter.
When not to use it: ordinary Git or a packaging system is simpler for small files or a host without LFS support. LFS is appropriate when large binaries are part of the versioned project and the team can support the storage, transfer, and quota requirements.
کدام ابزار برای کدام درد؟Which tool fits which problem?
| مسئله | ابزار محتمل | چه چیزی را عوض میکند؟ | مرزش را یادت بماند |
|---|---|---|---|
| دو کار همزمان روی شاخههای جدا | worktree | چند پوشه کاری متصل به مخزن | یک شاخه را همزمان در چند محل checkout نکن |
| وابستگی با تاریخچه و مالکیت جدا | submodule | ثبت یک commit فرزند در والد | clone و بهروزرسانی فرزند مرحلهٔ جدا دارد |
| کار فقط روی بخشی از monorepo | sparse-checkout | مجموعهٔ مسیرهای حاضر در پوشه کاری | بهتنهایی دریافت اولیهٔ شیءها را کم نمیکند |
| بازخورد محلی هنگام عملیات Git | hook | اجرای اسکریپت در یک نقطهٔ مشخص | clone آن را خودکار فعال نمیکند و قابل دورزدن است |
| فایل دودویی حجیم نسخهدار | Git LFS | اشارهگر در Git، محتوای اصلی در سرویس LFS | به نصب، میزبان، شبکه و سهمیه نیاز دارد |
| وابستگی نسخه انتشارشده با نسخهبندی جاافتاده | package مدیر | دریافت فایل تولیدی نسخهدار | شاید به checkout همزمان سورس وابستگی نیاز نداشته باشی |
| مخزن کوچک و گردش کار ساده | هیچکدام | پیچیدگی جدیدی وارد نمیشود | «پیشرفته» بودن دلیل استفاده نیست |
| Problem | Likely tool | What changes? | Remember its boundary |
|---|---|---|---|
| Work on two branches concurrently | worktree | Multiple working trees attached to one repository | Do not check out one branch in multiple locations |
| Dependency with separate ownership and history | submodule | Records a child repository commit in the parent | Child initialization and updates are separate steps |
| Work on only part of a monorepo | sparse-checkout | Paths present in the working tree | Does not by itself reduce the initial object transfer |
| Local feedback during a Git operation | Hook | Runs a script at a specific workflow event | Not automatically enabled on clone; can be bypassed |
| Versioned large binary files | Git LFS | Pointer in Git, payload in LFS storage | Requires client, host support, access, and quota |
| Released dependency with managed versions | Package manager | Fetches a versioned artifact | You may not need the dependency source checked out |
| Small repository and simple workflow | None | Avoids introducing new complexity | “Advanced” is not a reason to use a tool |
اگر هنوز نمیدانی درد دقیق چیست، فعلاً ابزار اضافه نکن. در فصل بعد با یک مخزن آسیبدیده روبهرو میشویم؛ آنجا همین عادت تشخیص، از حفظکردن فرمانها مهمتر است.
The table separates the tools by the problem they solve and the boundary each one keeps. If the problem is still unclear, do not add another tool yet. In the final chapter, we will inspect a damaged repository; the habit of diagnosis will matter more than memorizing commands.
میز عیبیابی: نشانه را به مدرک وصل کنTroubleshooting desk: connect symptoms to evidence
| نشانه | اول چه چیزی را بررسی کنیم؟ | برداشت عجولانهای که باید کنار گذاشت |
|---|---|---|
| شاخه را در worktree جدید نمیسازد | git worktree list؛ شاید شاخه در محل دیگری checkout است | اینکه فوراً باید force کنیم |
| پوشهٔ submodule خالی است | git submodule status و سپس init/update | اینکه محتوا از commit والد حذف شده |
| فایل در ls-tree هست ولی در پوشه نیست | قواعد git sparse-checkout list و وضعیت مسیر | اینکه فایل از تاریخچه پاک شده |
| hook اجرا نشد | core.hooksPath، مجوز executable و نام فایل | اینکه Git hookها را از clone فعال میکند |
| فایل LFS به شکل اشارهگر دیده میشود | نصب Git LFS، دسترسی endpoint و دریافت محتوای اصلی | اینکه اشارهگر یعنی فایل برای همیشه گم شده |
| push موفق بود اما محتوای اصلی در دسترس نیست | پشتیبانی میزبان، دسترسی/سهمیه و خطای فرمان LFS | اینکه Git معمولی محتوای اصلی را حتماً فرستاده |
| فایل از قبل در تاریخچه بود و هنوز سنگین است | بررسی تاریخچه و تصمیم هماهنگ برای مهاجرت | اینکه track کردن الگو، commitهای قدیمی را تغییر داده |
| Symptom | First evidence to inspect | Do not assume |
|---|---|---|
| Cannot add a worktree for a branch | git worktree list; it may already be checked out elsewhere | That force is the right first move |
| Submodule directory is empty | git submodule status, then initialize/update | That the parent commit deleted its files |
| Path exists in the tree but not on disk | git sparse-checkout list and path status | That the path was removed from history |
| Hook did not run | core.hooksPath, executable permission, and filename | That Git enables hooks automatically on clone |
| LFS file appears as a pointer | Git LFS installation, endpoint access, and payload fetch | That the pointer proves the file is permanently lost |
| Push succeeded but payload is unavailable | Host support, access/quota, and LFS command errors | That ordinary Git necessarily sent the payload |
| History clone remains large after tracking | History and a coordinated migration decision | That tracking changed old commits |
در ابزارهای پیشرفته بیشترین اشتباه از این میآید که ابزار درست را برای مسئلهٔ اشتباه انتخاب میکنیم. اگر چیزی کار نکرد، اول برگرد و بپرس مشکل اصلی چه بود: دو پوشهٔ کاری همزمان؟ وابستگی به مخزن دیگر؟ مخزن خیلی بزرگ؟ کنترل قبل از commit؟ یا فایلهای حجیم؟
Use the same safe order practiced in earlier chapters: inspect state, gather evidence, then change something. A command that merely hides the symptom can make the next diagnosis harder.
مینیپروژه: پنج درخواست از یک تیمMini-project: five requests from one team
پنج درخواست کوتاه از یک تیم میگیری و برای هرکدام باید تصمیم بگیری کدام ابزار واقعاً مناسب است. نکته اینجاست که «هیچکدام» هم میتواند جواب درست باشد؛ ابزار پیشرفته فقط به خاطر جذاببودنش وارد معماری نمیشود.
- hotfix در کنار feature: شاخهٔ feature تغییر stagedنشده دارد و باید اصلاح فوری را جدا آماده کنی. دو worktree بساز، فهرستشان را ثبت کن و نشان بده هر کدام روی چه شاخهای است.
- کتابخانهٔ متعلق به تیم دیگر: یک مخزن آزمایشی را submodule کن. در clone تازه، نبودن محتوای checkoutشده را مشاهده و با دستور recursive آن را آماده کن. commit ثبتشدهٔ والد را با وضعیت فرزند تطبیق بده.
- monorepo آزمایشی: سه پوشهٔ سرویس بساز و فقط یکی را در working tree نگه دار. تفاوت فهرست مسیر انتخابی و مسیرهای موجود در tree را با مدرک نشان بده.
- کنترل کیفیت سریع: hook بررسی DEBUG_ONLY را اضافه کن؛ یک بار commit را متوقف کن و بار دوم پس از اصلاح اجازه بده. در یادداشتت توضیح بده چرا تیم باز هم به CI نیاز دارد.
- دارایی حجیم: فقط اگر میزبان و دسترسی Git LFS داری، یک فایل آزمایشی کوچک و بیارزش را track و round-trip آن را بررسی کن. اگر میزبان نداری، بهجای جعل موفقیت، پیشنیازهای لازم را مستند کن و آزمایش را متوقف نگه دار.
چه وقت از هرکدام استفاده نکنیم؟ worktree را برای تعویض شاخهٔ عادی اضافه نکن؛ submodule را جای package منتشرشده نگذار؛ sparse-checkout را برای مخزن کوچک فعال نکن؛ hook محلی را مرجع نهایی سیاست تیم ندان؛ LFS را برای فایل کمحجم یا میزبان ناسازگار انتخاب نکن. گاهی بهترین راه، سادهترین روند کار فعلی است.
پایان کار، فقط پوشههای آزمایشی را بعد از بررسی تغییرهایشان جمع کن. برای worktree از فرمان خود Git کمک بگیر؛ submodule یا فایل آزمایشی را از پروژهٔ واقعی حذف نکن. در گزارش، فرمان و خروجیای را نگه دار که نفر بعدی بتواند انتخابت را بازبینی کند.
Use disposable repositories for these tasks; do not test removal or rewriting on a working project or valuable files. You do not need to combine all five tools in one repository. For each scenario, submit a short note: the problem, chosen tool, evidence supporting the choice, and the solution’s limitation.
- Hotfix beside a feature: the feature branch has unstaged work, and you need a separate urgent fix. Create two worktrees, list them, and show which branch each uses.
- A library owned by another team: add a test repository as a submodule. In a fresh clone, observe the uninitialized child and then initialize it recursively. Compare its checked-out commit with the parent’s recorded commit.
- A sample monorepo: create three service directories and keep only one in the working tree. Show evidence distinguishing selected paths from paths present in the tree.
- Fast local quality check: add the DEBUG_ONLY hook, block one commit, then allow a commit after fixing it. Explain why the team still needs CI.
- A large asset: only if your host and access support Git LFS, track a small disposable file and test a round trip. Otherwise document the prerequisites and stop without pretending the test succeeded.
When not to use each: do not add a worktree for ordinary branch switching; do not replace a published package with a submodule; do not enable sparse checkout for a tiny repository; do not treat a local hook as final team policy; and do not choose LFS for small files or an incompatible host. Sometimes the best choice is the existing simple workflow.
At the end, clean up only disposable locations after checking their changes. Use Git’s worktree command for worktrees, and do not remove a real project’s submodule or files. Keep commands and output in the report so another person can review the decision.
۱۸ تمرین حلشده18 solved exercises
در این تمرینها انتخاب ابزار مهمتر از شکل دستور آن است. قبل از بازکردن پاسخ، یک جمله بنویس: «مسئلهٔ اصلی این تیم چیست؟» اگر همان جمله روشن باشد، معمولاً worktree، submodule، sparse-checkout، hook یا LFS خودش را نشان میدهد.
These exercises begin with recognizing a situation and build toward choosing several tools together. Decide first, then open the short hint and explanation.
۱ کار روی قابلیت نیمهتمام است و اصلاح فوری فوری لازم شده؛ کمدردسرترین مدل چیست؟A feature is unfinished and an urgent hotfix is needed. Which workflow causes the least disruption?
راهنماHint
آیا واقعاً باید همان پوشه یک شاخه را جایگزین شاخهٔ دیگر کند؟
Must the same directory replace one checked-out branch with another?
پاسخ: اگر دو شاخه باید همزمان در دسترس باشند، یک worktree برای اصلاح فوری بساز. فایلهای قابلیت در محل فعلی دستنخورده میمانند. اگر شاخهاش هنوز جای دیگری checkout است، اول فهرست worktreeها را بررسی کن.
Answer: Use a second worktree for the hotfix when both branches must remain available. The feature files stay in place; first check that the target branch is not already checked out elsewhere.
۲ ساخت worktree برای شاخهٔ release رد میشود و Git میگوید شاخه در worktree دیگری استفاده میشود. چه میکنی؟Git refuses a worktree for release because that branch is already in use elsewhere. What do you do?
راهنماHint
قبل از هر حذف یا force، محل checkout فعلی را پیدا کن.
Locate the current checkout before removing anything or forcing the operation.
پاسخ: git worktree list را اجرا و worktree مربوط به release را پیدا میکنم. اگر آن محل هنوز لازم است، شاخهٔ دیگری انتخاب میکنم؛ اگر تمام شده، تغییرهایش را بررسی و سپس با فرمان remove جمعش میکنم. force راه تشخیص نیست.
Answer: Run git worktree list, inspect the existing checkout, and either choose another branch or safely remove the finished worktree after checking its changes. Force is not diagnosis.
۳ پوشهٔ یک worktree را دستی پاک کردهای، ولی هنوز در فهرست دیده میشود. قدم امن بعدی چیست؟You manually deleted a worktree directory, but it still appears in the list. What is the safe next step?
راهنماHint
پیشنمایش پاکسازی را پیش از اجرای واقعی ببین.
Preview the cleanup before running it for real.
پاسخ: از مخزن اصلی git worktree prune --dry-run اجرا میکنم و خروجی را بررسی میکنم؛ فقط رکوردهای stale موردنظر را بعد از اطمینان prune میکنم. اگر دیسک جداگانه موقتاً وصل نیست، prune نکن؛ شاید worktree فقط در دسترس نباشد.
Answer: Preview with git worktree prune --dry-run and prune only confirmed stale records. If a separate drive is merely offline, do not prune it as if it were deleted.
۴ بعد از clone، پوشهٔ کتابخانهٔ submodule خالی است. آیا commit والد فایلها را حذف کرده؟After cloning, the submodule library directory is empty. Did the parent commit delete its files?
راهنماHint
وضعیت submodule را بخوان و تفاوت اشارهگر با محتوای checkoutشده را به یاد بیاور.
Inspect submodule status and distinguish a recorded pointer from checked-out child content.
پاسخ: لزوماً نه. ابتدا git submodule status را میبینم؛ سپس git submodule update --init --recursive را اجرا میکنم. والد commit فرزند را ثبت میکند، اما clone تازه ممکن است هنوز محتوای فرزند را initialize نکرده باشد.
Answer: Not necessarily. Inspect git submodule status, then initialize recursively. The parent pins a child commit, but a fresh clone may not yet have checked out child content.
۵ commit تازهٔ submodule روی رایانهٔ تو هست، اما همتیمی با update نمیتواند آن را بگیرد. کدام مدرک را بررسی میکنی؟A new submodule commit exists on your machine, but a teammate cannot retrieve it during update. What evidence do you check?
راهنماHint
commit فرزند فقط در کدام مخزن وجود دارد؟
In which repository does the child commit exist?
پاسخ: بررسی میکنم commit در مخزن فرزند به مخزن راه دور قابلدسترسی push شده باشد. commit کردن gitlink در والد بهتنهایی محتوای اصلی یا commit فرزند را به مخزن والد منتقل نمیکند؛ بعد از انتشار commit فرزند، اشارهٔ والد را ثبت میکنم.
Answer: Verify that the child commit was pushed to a remote the teammate can access. Committing the parent gitlink does not transfer the child commit into the parent repository.
۶ بعد از submodule update، داخل کتابخانه جداشده HEAD میبینی. آیا باید فوراً آن را اصلاح کنی؟After updating a submodule, its HEAD is detached. Must you fix this immediately?
راهنماHint
هدف والد، نسخهٔ شاخهای است یا commit دقیق؟
Does the parent need a moving branch, or an exact commit?
پاسخ: نه؛ checkout روی commit pinشده رفتار عادی submodule است و ممکن است جداشده باشد. اگر قرار است کتابخانه را ویرایش کنم، در مخزن فرزند یک شاخه بسازم یا checkout کنم، commit و push را آنجا انجام دهم و سپس gitlink والد را بهروزرسانی کنم.
Answer: No. A pinned child commit is commonly checked out detached. For edits, create or switch to a branch in the child repository, publish its commit, then update the parent gitlink.
تا اینجا پنج ابزار را جدا دیدهای. از اینجا تمرینها اسم ابزار را پنهان میکنند و فقط مسئله را میدهند. این همان جایی است که باید جلوی وسوسهٔ «ابزار پیشرفتهتر حتماً بهتر است» را بگیری.
You have seen five tools separately. From here the exercises hide the tool name and give only the problem. This is where you resist the temptation to assume a more advanced tool is automatically better.
۷ git ls-tree مسیری را نشان میدهد که در فایلسیستم نیست. از کجا شروع میکنی؟git ls-tree shows a path that is absent from the filesystem. Where do you start?
راهنماHint
آیا این clone sparse است؟
Is this a sparse checkout?
پاسخ: git sparse-checkout list و git status --short را بررسی میکنم. اگر مسیر از الگوهای sparse بیرون است، نبودنش در پوشه کاری به معنی پاکشدن از commit نیست؛ الگو را آگاهانه اصلاح میکنم.
Answer: Check the sparse patterns and status. A path excluded from the working tree can still exist in the commit tree; adjust the pattern intentionally.
۸ برای انتخاب یک پوشهٔ سرویس، cone مجوز را انتخاب میکنی یا الگوهای non-cone پیچیده؟To select a service directory, would you use cone mode or complex non-cone patterns?
راهنماHint
شکل نیاز واقعاً پوشهمحور است؟
Is the requirement naturally expressed as directory selection?
پاسخ: برای انتخاب پوشه، cone مجوز انتخاب طبیعی و سادهتر است: git sparse-checkout init --cone و سپس set مسیر. سراغ الگوهای پیچیدهتر فقط وقتی میروم که نیاز دقیقاً با انتخاب پوشهها بیانپذیر نباشد و محدودیت نسخهٔ Git را هم بررسی کرده باشم.
Answer: Use cone mode for directory-shaped selection. Reach for more complex patterns only when directories cannot express the requirement and the installed Git behavior has been checked.
۹ sparse-checkout را روی clone معمولی فعال کردی؛ آیا دادهٔ منتقلشده در clone اولیه حتماً کمتر شده؟You enabled sparse checkout after a normal clone. Did the initial clone necessarily transfer less data?
راهنماHint
پوشه کاری با شیء transfer یک مفهوم نیست.
The working tree and object transfer are different concerns.
پاسخ: نه. sparse-checkout عمدتاً مسیرهای حاضر در پوشه کاری را محدود میکند. برای کاهش دریافت blobهای اولیه، partial clone مانند --filter=blob:none را جداگانه بررسی میکنم؛ این راه به پشتیبانی سرور و دریافت احتمالی بعدی نیاز دارد.
Answer: No. Sparse checkout shapes the working tree. Partial clone filters object transfer separately, requires server support, and may fetch blobs later on demand.
۱۰ hook فایلش در مخزن هست اما هنگام commit اجرا نمیشود. سه بررسی اول چیست؟The hook file is in the repository but does not run on commit. What are your first three checks?
راهنماHint
نام، مسیر فعال و مجوز اجرا.
Check the name, active path, and executable permission.
پاسخ: نام دقیق hook، مقدار git config --get core.hooksPath و executable بودن فایل را بررسی میکنم. همچنین مطمئن میشوم Git از همان مخزنی اجرا میشود که تنظیم را در آن انجام دادهام.
Answer: Check the exact hook filename, active core.hooksPath, executable permission, and that Git is running in the repository with that configuration.
۱۱ hook در رایانهٔ تو اجرا میشود ولی همتیمی با clone تازه آن را نمیبیند. چه چیزی جا افتاده؟The hook runs on your machine, but a teammate does not get it in a fresh clone. What is missing?
راهنماHint
commit شدن اسکریپت و فعالسازی hook یک مرحله نیستند.
Committing a script and enabling a hook are separate steps.
پاسخ: Git hookهای داخل پروژه را بهطور خودکار فعال نمیکند. باید راه setup امن و روشن برای تنظیم core.hooksPath داشته باشیم یا ابزار تیم این کار را انجام دهد؛ کنترل ضروری را هم در CI بگذاریم، چون hook محلی قابل دورزدن است.
Answer: Git does not automatically enable repository hooks on clone. Provide an explicit safe setup path and enforce required checks in CI because local hooks can be bypassed.
۱۲ hook با کد خروج غیرصفر commit را متوقف کرده است. آیا تغییرهای آمادهشده از بین رفتهاند؟A hook returned nonzero and stopped the commit. Were the staged changes lost?
راهنماHint
ردشدن عملیات با حذف ناحیه آمادهسازی یکی نیست.
A rejected operation is not the same as deleting the index.
پاسخ: معمولاً نه؛ commit ساخته نشده و تغییرهای آمادهشده برای اصلاح باقی میمانند. git status را میخوانم، علت را رفع میکنم، فایل را دوباره ناحیه آمادهسازی میکنم و commit را تکرار میکنم.
Answer: Usually not. The commit was blocked while staged changes remain available. Inspect status, fix the cause, stage the correction, and retry.
تمرینهای آخر عمداً مرز ابزارها را میسنجند. sparse-checkout جای partial clone را نمیگیرد، submodule یک پوشهٔ معمولی نیست و LFS تاریخچهٔ قدیمی را با یک track ساده پاک نمیکند.
The final exercises deliberately test tool boundaries. Sparse-checkout is not a substitute for partial clone, a submodule is not an ordinary folder, and simply tracking with LFS does not erase old large-file history.
۱۳ checkout یک فایل را به شکل اشارهگر متنی نشان میدهد. از کجا بفهمیم فایل خراب است یا محتوای اصلی دریافت نشده؟A checkout shows a file as a text pointer. How can you tell whether it is corrupt or its payload was not fetched?
راهنماHint
اشارهگر در LFS نمایندهٔ محتواست؛ مسیر انتقال را بررسی کن.
An LFS pointer represents content; inspect the transfer path.
پاسخ: ابتدا git lfs ls-files و نصب Git LFS را بررسی میکنم، سپس دسترسی endpoint، خطای smudge/fetch و وضعیت شبکه را میسنجم. اشارهگر بهتنهایی مدرک گمشدن فایل نیست؛ اگر endpoint در دسترس نیست، آن را گزارش میکنم و وانمود نمیکنم محتوای اصلی موجود است.
Answer: Check LFS installation and tracked files, then endpoint access and fetch/smudge errors. A pointer alone does not prove loss; report unavailable storage rather than claiming the payload exists.
۱۴ Git push موفق شده ولی همتیمی محتوای اصلی فایل حجیم را نمیگیرد. چه تفاوتی را باید در نظر گرفت؟Git push succeeded, but a teammate cannot get the large-file payload. What distinction matters?
راهنماHint
موفقیت انتقال Git و سرویس LFS را یکی نگیر.
Do not conflate Git transfer success with LFS service success.
پاسخ: push عادی commit و اشارهگر را منتقل میکند؛ محتوای اصلی ممکن است از endpoint جداگانهٔ LFS بیاید. پشتیبانی میزبان، دسترسی، سهمیه و خروجی Git LFS را بررسی میکنم. تا رفع آن، تحویل فایل را موفق اعلام نمیکنم.
Answer: A normal Git push transfers commits and pointers; the payload uses the LFS endpoint. Check host support, access, quota, and LFS output before declaring delivery successful.
۱۵ امروز git lfs track "*.psd" را اجرا کردی، اما clone تاریخچه هنوز حجیم است. چرا؟You ran git lfs track "*.psd" today, but cloning history is still large. Why?
راهنماHint
الگوی آینده تاریخچهٔ گذشته را بازنویسی نمیکند.
A rule for future files does not rewrite past history.
پاسخ: track کردن، قواعد attribute را برای فایلهای بعدی ثبت میکند؛ commitهای قدیمی را مهاجرت نمیدهد. تبدیل تاریخچه ابزار جداگانه و بازنویسی شناسهها میخواهد و باید با همهٔ مصرفکنندگان مخزن هماهنگ شود. در مخزن مشترک بیاجازه اجرا نمیکنم.
Answer: Tracking changes future attributes, not old commits. Historical migration rewrites object IDs and needs coordination with every consumer; do not run it casually on a shared repository.
۱۶ قانون شرکت میگوید هیچ commitی نباید تستهای لازم را دور بزند. hook محلی کافی است؟Company policy says no commit may bypass required tests. Is a local hook enough?
راهنماHint
چه کسی میتواند hook محلی را حذف یا دور بزند؟
Who can remove or bypass a local hook?
پاسخ: کافی نیست. hook برای بازخورد زودهنگام است و میتواند غیرفعال یا دور زده شود. تست الزامی را در CI یا سیاست سمت سرور اعمال میکنم و hook را فقط کمک محلی میدانم.
Answer: No. Local hooks improve feedback but can be disabled or bypassed. Enforce mandatory checks in CI or server-side policy.
۱۷ یک کتابخانه نسخهٔ نسخه انتشار دارد و از registry نصب میشود؛ آیا submodule انتخاب اول است؟A library has releases and is installed from a registry. Is a submodule the first choice?
راهنماHint
آیا واقعاً به checkout سورس در commit دقیق نیاز داریم؟
Do you actually need the source checked out at an exact commit?
پاسخ: معمولاً package مدیر سادهتر است؛ نسخهٔ نسخه انتشار را مانند وابستگیهای دیگر میگیرد. submodule زمانی ارزش دارد که اتصال مستقیم به commit مخزن فرزند یا روند کار توسعهٔ همزمان سورس لازم باشد.
Answer: Usually prefer the package manager for a released dependency. A submodule is justified when the parent needs a precise child commit or coordinated source development.
۱۸ در monorepo، همزمانی اصلاح فوری لازم است، checkout فقط یک سرویس میخواهی و فایلهای ویدیویی حجیم هم داری. ابزارها را چگونه از هم تفکیک میکنی؟In a monorepo, you need a concurrent hotfix, only one service checked out, and large video files. How do you assign tools to these needs?
راهنماHint
هر ابزار فقط یک بُعد از مسئله را پاسخ میدهد.
Each tool addresses only one dimension of the problem.
پاسخ: برای اصلاح فوری موازی worktree؛ برای محدودکردن مسیرهای حاضر در پوشه کاری، sparse-checkout؛ و فقط اگر میزبان و سهمیه پشتیبانی میکند، برای فایلهای بزرگ Git LFS. اگر کاهش دریافت اولیه هم لازم است، partial clone را جدا میسنجم. هیچکدام جای دیگری را نمیگیرد و لازم نیست همه را فعال کنیم.
Answer: Use a worktree for parallel hotfix work, sparse checkout for the selected working-tree paths, and LFS for large files only with supported storage and quota. Evaluate partial clone separately for initial transfer; none substitutes for another.
حالا نوبت اتاق نجات استNext: the repository rescue room
دیگر تقریباً همهٔ ابزارهای مسیر را دیدهای. فصل بعد ابزار تازهای اضافه نمیکند؛ چند خرابکاری واقعی را کنار هم میگذارد و ازت میخواهد قبل از هر حرکت، بفهمی Git هنوز چه چیزی در اختیار دارد.
در فصل پایانی، قرار نیست دستور تازهای را طوطیوار اجرا کنیم. یک مخزن بههمریخته را بررسی میکنیم، مدرک جمع میکنیم و تصمیم میگیریم کدام کار را میشود امن انجام داد و کجا باید توقف کرد. ابزار خوب از تشخیص درست شروع میشود.
This chapter introduced tools that change working locations, repository boundaries, visible files, local checks, or large-payload storage. None is magic: a worktree does not protect uncommitted changes; a submodule cannot make an unpublished child commit available; sparse checkout does not erase paths from history; a local hook is not universal policy; and LFS cannot transfer a file without a service and access.
The final chapter will not ask you to blindly repeat a new command. We will inspect a troubled repository, collect evidence, and decide what can safely be done—and where we should stop. Good tooling starts with good diagnosis.
برگهٔ همراهQuick reference
| ابزار | برای چه؟ | مدرک شروع | احتیاط |
|---|---|---|---|
worktree | working treeهای موازی | git worktree list | پیش از حذف، تغییرها را بررسی کن |
submodule | اتصال به commit مخزن مستقل | git submodule status | فرزند باید قابلدسترسی و initialize باشد |
sparse-checkout | انتخاب مسیرهای پوشه کاری | git sparse-checkout list | با partial clone یکی نیست |
| hook | بازخورد محلی هنگام عملیات | core.hooksPath و مجوز اجرا | کنترل ضروری را در CI هم داشته باش |
| Git LFS | مدیریت فایلهای حجیم | git lfs ls-files | endpoint، دسترسی و سهمیه لازم است |
| Tool | Use | First evidence | Caution |
|---|---|---|---|
worktree | Concurrent working trees | git worktree list | Inspect changes before removal |
submodule | Pin a commit from an independent repository | git submodule status | Child must be accessible and initialized |
sparse-checkout | Select working-tree paths | git sparse-checkout list | Not the same as partial clone |
| Hook | Local feedback during an operation | core.hooksPath and executable bit | Enforce required checks in CI too |
| Git LFS | Manage large files | git lfs ls-files | Endpoint, access, and quota are required |
قاعدهٔ این فصل ساده است: اول درد و مدرک، بعد ابزار؛ و اگر سادهترین راه جواب میدهد، همان را نگه دار.
The rule is simple: identify the problem and evidence before choosing a tool. If the simplest workflow works, keep it.