Chapters ▾ 2nd Edition

7.3 ابزارهای گیت (Git Tools) - ذخیره موقت و پاک‌سازی (Stashing and Cleaning)

ذخیره موقت و پاک‌سازی (Stashing and Cleaning)

اغلب، وقتی روی بخشی از پروژه‌تان کار می‌کنید، وضعیت کار به‌هم‌ریخته می‌شود و می‌خواهید برای مدتی به شاخه‌ای دیگر بروید تا روی موضوع دیگری کار کنید. مشکل اینجاست که نمی‌خواهید کار نیمه‌کاره‌تان را به‌صورت یک کامیت ذخیره کنید فقط برای اینکه بعداً به همین نقطه برگردید. پاسخ این مسئله دستور git stash است.

ذخیره موقت، وضعیت ناپاک دایرکتوری کاری شما — یعنی فایل‌های تغییر یافته پیگیری‌شده و تغییرات آماده‌شده — را گرفته و آن را در یک پشته از تغییرات ناتمام ذخیره می‌کند که می‌توانید در هر زمان (حتی روی شاخه‌ای دیگر) دوباره اعمال کنید.

یادداشت
Migrating to git stash push

از اواخر اکتبر ۲۰۱۷، بحث‌های گسترده‌ای در لیست ایمیل گیت صورت گرفته است که دستور git stash save به نفع جایگزین موجود git stash push منسوخ شده است. دلیل اصلی این تغییر این است که git stash push گزینه ذخیره موقت انتخابی pathspecs را معرفی می‌کند، چیزی که git stash save پشتیبانی نمی‌کند.

دستور git stash save به این زودی‌ها حذف نخواهد شد، پس نگران ناپدید شدن ناگهانی آن نباشید. اما ممکن است بخواهید برای بهره‌مندی از قابلیت‌های جدید به تدریج به جایگزین push مهاجرت کنید.

ذخیره موقت کار خود (Stashing Your Work)

برای نشان دادن نحوه ذخیره موقت، وارد پروژه خود شوید و روی چند فایل کار کنید و شاید یکی از تغییرات را هم آماده (stage) کنید. اگر دستور git status را اجرا کنید، وضعیت ناپاک خود را خواهید دید:

$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

حالا می‌خواهید شاخه را عوض کنید، اما نمی‌خواهید هنوز کاری که انجام داده‌اید را کامیت کنید، پس تغییرات را ذخیره موقت می‌کنید. برای اضافه کردن یک ذخیره موقت جدید به پشته خود، دستور git stash یا git stash push را اجرا کنید:

$ git stash
Saved working directory and index state \
  "WIP on master: 049d078 Create index file"
HEAD is now at 049d078 Create index file
(To restore them type "git stash apply")

اکنون می‌بینید که دایرکتوری کاری شما پاک شده است:

$ git status
# On branch master
nothing to commit, working directory clean

در این مرحله می‌توانید شاخه را تغییر دهید و روی بخش دیگری کار کنید؛ تغییرات شما در پشته ذخیره شده‌اند. برای دیدن ذخیره‌های موقتی که ذخیره کرده‌اید، می‌توانید از دستور git stash list استفاده کنید:

$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log

در اینجا دو ذخیره موقت قبلاً ذخیره شده است، پس شما به سه کار ذخیره شده مختلف دسترسی دارید. می‌توانید ذخیره‌ای که به تازگی ایجاد کرده‌اید را با دستور نشان داده شده در راهنمای دستور اصلی stash یعنی git stash apply دوباره اعمال کنید. اگر بخواهید یکی از ذخیره‌های قدیمی‌تر را اعمال کنید، می‌توانید با نام آن مشخص کنید، مثلاً: git stash apply stash@{2}. اگر ذخیره‌ای مشخص نکنید، گیت جدیدترین ذخیره را فرض کرده و سعی می‌کند آن را اعمال کند:

$ git stash apply
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   index.html
	modified:   lib/simplegit.rb

no changes added to commit (use "git add" and/or "git commit -a")

می‌بینید که گیت فایل‌هایی را که هنگام ذخیره stash تغییر داده بودید دوباره تغییر می‌دهد. در این مثال، وقتی سعی کردید stash را اعمال کنید، دایرکتوری کاری شما پاک بود و روی همان شاخه‌ای بودید که stash را ذخیره کرده بودید. داشتن دایرکتوری کاری پاک و اعمال stash روی همان شاخه برای موفقیت آمیز بودن اعمال stash ضروری نیست. می‌توانید روی یک شاخه stash ذخیره کنید، بعد به شاخه دیگری بروید و تغییرات را دوباره اعمال کنید. همچنین می‌توانید هنگام اعمال stash، فایل‌های تغییر یافته و ناتمام در دایرکتوری کاری داشته باشید — در این حالت اگر چیزی به‌درستی اعمال نشود، گیت به شما تعارض‌های ادغام (merge conflicts) می‌دهد.

تغییرات فایل‌ها دوباره اعمال شدند، اما فایلی که قبلاً آماده کرده بودید دوباره آماده نشده است. برای این کار، باید دستور git stash apply را با گزینه --index اجرا کنید تا به دستور گفته شود تغییرات آماده شده را نیز دوباره اعمال کند. اگر همان ابتدا این دستور را اجرا کرده بودید، به وضعیت اولیه خود باز می‌گشتید:

$ git stash apply --index
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

گزینه apply فقط سعی می‌کند کار ذخیره شده را اعمال کند — همچنان آن را در پشته خود دارید. برای حذف آن، می‌توانید دستور git stash drop را با نام stash برای حذف اجرا کنید:

$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

همچنین می‌توانید از دستور git stash pop استفاده کنید تا stash را اعمال کرده و بلافاصله از پشته حذف کند.

ذخیره موقت خلاقانه (Creative Stashing)

چند نوع ذخیره موقت وجود دارد که ممکن است مفید باشند. گزینه اول که بسیار محبوب است، گزینه --keep-index برای دستور git stash است. این گزینه به گیت می‌گوید که نه تنها تمام محتوای آماده شده را در stash ایجاد شده ذخیره کند، بلکه همزمان آن را در شاخص باقی بگذارد.

$ git status -s
M  index.html
 M lib/simplegit.rb

$ git stash --keep-index
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
M  index.html

یکی دیگر از موارد رایجی که ممکن است بخواهید با stash انجام دهید، ذخیره‌سازی فایل‌های ردیابی‌نشده (untracked) به همراه فایل‌های ردیابی‌شده است. به طور پیش‌فرض، دستور git stash تنها فایل‌های تغییر یافته و مرحله‌بندی شده‌ی ردیابی‌شده را ذخیره می‌کند. اگر گزینه‌ی --include-untracked یا -u را مشخص کنید، گیت فایل‌های ردیابی‌نشده را نیز در stash ایجاد شده وارد می‌کند. با این حال، وارد کردن فایل‌های ردیابی‌نشده در stash، فایل‌هایی که به صورت صریح نادیده گرفته شده‌اند (ignored) را شامل نمی‌شود؛ برای وارد کردن فایل‌های نادیده گرفته شده نیز، از گزینه‌ی --all (یا فقط -a) استفاده کنید.

$ git status -s
M  index.html
 M lib/simplegit.rb
?? new-file.txt

$ git stash -u
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
$

در نهایت، اگر گزینه‌ی --patch را مشخص کنید، گیت همه‌ی تغییرات را ذخیره نمی‌کند، بلکه به صورت تعاملی از شما می‌پرسد کدام تغییرات را می‌خواهید stash کنید و کدام را می‌خواهید در شاخه کاری خود نگه دارید.

$ git stash --patch
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
         return `#{git_cmd} 2>&1`.chomp
       end
     end
+
+    def show(treeish = 'master')
+      command("git show #{treeish}")
+    end

 end
 test
Stash this hunk [y,n,q,a,d,/,e,?]? y

Saved working directory and index state WIP on master: 1b65b17 added the index file

ایجاد شاخه‌ای از یک stash (Creating a Branch from a Stash)

اگر کاری را stash کنید، آن را مدتی رها کنید و سپس در همان شاخه‌ای که کار را stash کرده بودید ادامه دهید، ممکن است هنگام اعمال دوباره‌ی آن کار به مشکل بربخورید. اگر اعمال تغییرات بخواهد فایلی را که شما در این فاصله تغییر داده‌اید اصلاح کند، با یک تداخل ادغام (merge conflict) مواجه خواهید شد و باید آن را حل کنید. اگر می‌خواهید راه ساده‌تری برای آزمایش دوباره‌ی تغییرات stash شده داشته باشید، می‌توانید دستور git stash branch <نام شاخه جدید> را اجرا کنید؛ این دستور شاخه‌ای جدید با نام انتخابی شما ایجاد می‌کند، به تعهد (commit) ای که هنگام stash کردن روی آن بودید می‌رود، تغییرات شما را دوباره اعمال می‌کند و اگر با موفقیت اعمال شد، stash را حذف می‌کند.

$ git stash branch testchanges
M	index.html
M	lib/simplegit.rb
Switched to a new branch 'testchanges'
On branch testchanges
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014)

این یک راه میانبر خوب برای بازیابی آسان کار stash شده و ادامه‌ی کار روی آن در یک شاخه جدید است.

پاک‌سازی شاخه کاری (Working Directory) (Cleaning your Working Directory)

در نهایت، ممکن است نخواهید برخی کارها یا فایل‌ها را stash کنید، بلکه صرفاً بخواهید آن‌ها را حذف کنید؛ برای این منظور دستور git clean طراحی شده است.

دلایل رایجی برای پاک‌سازی شاخه کاری ممکن است حذف فایل‌های اضافه‌ای باشد که در اثر ادغام‌ها یا ابزارهای خارجی ایجاد شده‌اند یا حذف فایل‌های ساخته شده (build artifacts) برای اجرای یک ساخت کاملاً تمیز.

باید در استفاده از این دستور بسیار محتاط باشید، چون هدف آن حذف فایل‌هایی است که در شاخه کاری شما ردیابی نمی‌شوند. اگر نظرتان تغییر کند، معمولاً نمی‌توانید محتوای آن فایل‌ها را بازیابی کنید. گزینه‌ای امن‌تر این است که ابتدا با git stash --all همه چیز را حذف کنید اما در یک stash ذخیره کنید.

اگر فرض کنیم می‌خواهید فایل‌های اضافی را حذف کنید یا شاخه کاری خود را پاکسازی نمایید، می‌توانید این کار را با git clean انجام دهید. برای حذف همه‌ی فایل‌های ردیابی‌نشده در شاخه کاری، می‌توانید دستور git clean -f -d را اجرا کنید که هر فایل و هر زیرشاخه‌ای که در نتیجه خالی شود را حذف می‌کند. گزینه‌ی -f به معنای «اجبار» یا «واقعاً این کار را انجام بده» است و اگر متغیر پیکربندی گیت clean.requireForce به صراحت روی false تنظیم نشده باشد، ضروری است.

اگر می‌خواهید ببینید این دستور چه کاری انجام خواهد داد، می‌توانید آن را با گزینه‌ی --dry-run (یا -n) اجرا کنید، که یعنی «یک اجرای آزمایشی انجام بده و به من بگو چه چیزهایی حذف می‌شدند».

$ git clean -d -n
Would remove test.o
Would remove tmp/

به طور پیش‌فرض، دستور git clean تنها فایل‌های ردیابی‌نشده‌ای را حذف می‌کند که نادیده گرفته نشده‌اند. هر فایلی که با الگوی موجود در .gitignore یا فایل‌های نادیده‌گیری دیگر مطابقت داشته باشد، حذف نخواهد شد. اگر بخواهید آن فایل‌ها را هم حذف کنید، مثلاً برای حذف همه‌ی فایل‌های .o تولیدشده از یک ساخت تا بتوانید ساخت کاملاً تمیزی داشته باشید، می‌توانید گزینه‌ی -x را به دستور clean اضافه کنید.

$ git status -s
 M lib/simplegit.rb
?? build.TMP
?? tmp/

$ git clean -n -d
Would remove build.TMP
Would remove tmp/

$ git clean -n -d -x
Would remove build.TMP
Would remove test.o
Would remove tmp/

اگر نمی‌دانید دستور git clean دقیقاً چه کاری انجام خواهد داد، همیشه ابتدا با گزینه‌ی -n اجرا کنید تا قبل از اجرای واقعی، نتیجه را بررسی کنید، سپس -n را به -f تغییر دهید. روش دیگر برای اطمینان بیشتر، استفاده از گزینه‌ی -i یا حالت «تعامل‌پذیر» (interactive) است.

این حالت دستور clean را به صورت تعاملی اجرا می‌کند.

$ git clean -x -i
Would remove the following items:
  build.TMP  test.o
*** Commands ***
    1: clean                2: filter by pattern    3: select by numbers    4: ask each             5: quit
    6: help
What now>

بدین ترتیب می‌توانید فایل‌ها را یکی‌یکی بررسی کنید یا الگوهایی برای حذف به صورت تعاملی تعیین نمایید.

یادداشت

یک وضعیت خاص وجود دارد که ممکن است لازم باشد در درخواست از گیت برای پاک‌سازی شاخه کاری خود، جدیت بیشتری به خرج دهید. اگر در شاخه کاری‌ای باشید که در آن سایر مخازن گیت را کپی یا کلون کرده‌اید (شاید به عنوان زیرماژول‌ها)، حتی دستور git clean -fd هم از حذف آن دایرکتوری‌ها خودداری می‌کند. در چنین مواردی، باید گزینه‌ی دوم -f را برای تأکید اضافه کنید.

scroll-to-top