| GIT-FILTER-REPO(1) | Git Manual | GIT-FILTER-REPO(1) |
نام (NAME)
git-filter-repo - بازنویسی همهکاره تاریخچه گیت
خلاصه دستور (SYNOPSIS)
git filter-repo --analyze
git filter-repo [<path_filtering_options>] [<content_filtering_options>]
[<ref_renaming_options>] [<commit_message_filtering_options>]
[<name_or_email_filtering_options>] [<parent_rewriting_options>]
[<generic_callback_options>] [<miscellaneous_options>]
توضیحات (DESCRIPTION)
بازنویسی سریع کل تاریخچه مخزن با استفاده از فیلترهای مشخصشده توسط کاربر. این یک عملیات مخرب است که نباید ساده انگاشته شود؛ این دستور کامیتها، درختها، تگها و blobهای جدید متناظر با اشیای اصلی مخزن (اما فیلترشده از آنها) را مینویسد، سپس تاریخچه اصلی را حذف کرده و فقط تاریخچه جدید را باقی میگذارد. برای جزئیات بیشتر درباره پیامدهای استفاده از این ابزار، به بخش “DISCUSSION” مراجعه کنید. انواع مختلفی از بازنویسی تاریخچه امکانپذیر است؛ نمونهها شامل (اما نه محدود به) موارد زیر هستند:
علاوه بر این، چندین مورد به طور خودکار مدیریت میشوند (بسیاری از این موارد را میتوان لغو کرد، اما همگی به طور پیشفرض فعال هستند):
و امکانات اضافی دیگری از طریق یک گزینه پیکربندی در دسترس هستند:
همچنین، شایان ذکر است که یک سازوکار ایمنی مهم وجود دارد:
برای کسانی که میدانند موارد ناخواسته و حجیمی در تاریخچهشان وجود دارد و برای یافتن آنها کمک میخواهند، این دستور همچنین
همچنین ببینید بخشهای “VERSATILITY”، “DISCUSSION”، “EXAMPLES” و “INTERNALS” را.
گزینهها (OPTIONS)
گزینههای تحلیل (Analysis Options)
--analyze
فیلتر کردن بر اساس مسیرها (همچنین --filename-callback را ببینید)
این گزینهها مسیرهای انتخابی را مشخص میکنند. توجه داشته باشید که همانند خود git، تغییر نامها دنبال نمیشوند بنابراین ممکن است لازم باشد چندین مسیر را مشخص کنید، به عنوان مثال --path olddir/ --path newdir/
--invert-paths
--path-match <dir_or_file>, --path <dir_or_file>
--path-glob <glob>
--path-regex <regex>
--use-base-name
تغییر نام بر اساس مسیرها (همچنین --filename-callback را ببینید)
نکته: اگر فیلتر کردن مسیر را با تغییر نام مسیر ترکیب کنید، توجه داشته باشید که دستور تغییر نام، مسیرها را انتخاب نمیکند، بلکه تنها چگونگی تغییر نام مسیرهایی را که با فیلترها انتخاب شدهاند مشخص میکند.
--path-rename <old_name:new_name>, --path-rename-match <old_name:new_name>
میانبرهای مسیر (Path shortcuts)
--paths-from-file <filename>
--subdirectory-filter <directory>
--to-subdirectory-filter <directory>
فیلترهای ویرایش محتوا (همچنین --blob-callback را ببینید)
--replace-text <expressions_file>
--strip-blobs-bigger-than <size>
--strip-blobs-with-ids <blob_id_filename>
تغییر نام ارجاعات (همچنین --refname-callback را ببینید)
--tag-rename <old:new>
فیلتر کردن پیامهای کامیت (همچنین --message-callback را ببینید)
--replace-message <expressions_file>
--preserve-commit-hashes
--preserve-commit-encoding
فیلتر کردن نامها و ایمیلها (همچنین --name-callback و --email-callback را ببینید)
--mailmap <filename>
--use-mailmap
بازنویسی والدین (Parent rewriting)
--replace-refs {delete-no-add, delete-and-add, update-no-add, update-or-add, update-and-add, old-default}
--prune-empty {always, auto, never}
--prune-degenerate {always, auto, never}
--no-ff
قطعهکدهای عمومی بازخوانی (Generic callback code snippets)
--filename-callback <function_body>
--message-callback <function_body>
--name-callback <function_body>
--email-callback <function_body>
--refname-callback <function_body>
--file-info-callback <function_body>
--blob-callback <function_body>
--commit-callback <function_body>
--tag-callback <function_body>
--reset-callback <function_body>
حذف دادههای حساس (Sensitive Data Removal)
--sensitive-data-removal, --sdr
توجه داشته باشید که اگر هرگونه تغییر صرفاً محلی (یعنی تغییرات pushنشده) در مخزن خود بر روی هر شاخه یا ارجاعی دارید، این مرحله دریافت ممکن است آنها را دور بریزد. کار کردن در یک کلون تازه از این مشکل جلوگیری میکند؛ همچنین اگر نمیخواهید با یک کلون تازه کار کنید و تغییرات مهم صرفاً محلی دارید، گزینه --no-fetch را ببینید.
--no-fetch
مکان فیلترسازی از/به (Location to filter from/to)
نکته
مشخص کردن مکانهای مبدأ یا مقصد جایگزین، متضمن --partial است. با این حال، بر خلاف کاربردهای عادی --partial، این کار خطر ترکیب تاریخچه قدیم و جدید را به همراه ندارد زیرا تاریخچههای قدیم و جدید در مخازن متفاوتی قرار دارند.
--source <source>
--target <target>
گزینههای متفرقه (Miscellaneous options)
--help, -h
--force, -f
--partial
--refs <refs+>
--dry-run
--debug
--stdin
--quiet
خروجی (OUTPUT)
هر بار که filter-repo اجرا میشود، فایلهایی در دایرکتوری .git/filter-repo/ ایجاد میشوند. این فایلها با هر بار اجرا بهروزرسانی یا رونویسی (overwrite) میشوند.
نگاشت کامیت (Commit map)
فایل $GIT_DIR/filter-repo/commit-map حاوی نگاشتی از نحوه تغییر یافتن (یا عدم تغییر) تمامی کامیتها است.
نگاشت ارجاعات (Reference map)
فایل $GIT_DIR/filter-repo/ref-map حاوی نگاشتی از ارجاعات محلی تغییریافته (یا تغییرنیافته) است.
ارجاعات تغییریافته (Changed References)
فایل $GIT_DIR/filter-repo/changed-refs حاوی فهرستی از refهایی است که تغییر یافتهاند.
اولین کامیتهای تغییریافته (First Changed Commits)
فایل $GIT_DIR/filter-repo/first-changed-commits حاوی فهرستی از اولین کامیت(هایی) است که توسط عملیات فیلترسازی تغییر یافتهاند. اینها کامیتهایی هستند که بازنویسی شدهاند و هیچ والدینی نداشتند که آنها نیز بازنویسی شده باشند.
بنابراین، برای مثال اگر پیش از اجرای git-filter-repo کامیتهای A1-B1-C1-D1-E1 را داشتید، و پس از آن کامیتهای A1-B2-C2-D2-E2 را داشتید، فایل First Changed Commits فقط شامل یک خط خواهد بود، که هش B2 است.
در بیشتر موارد، تنها یک کامیت فهرست خواهد شد، اما اگر چندین کامیت ریشه یا یک تاریخچه غیرخطی داشته باشید که در آن کامیتهای روی آن تاریخچههای واگرا اولین موارد اصلاحشده باشند، ممکن است چندین کامیت اولیه تغییریافته وجود داشته باشد و هر یک در خطوط جداگانه فهرست خواهند شد.
قبلاً اجرا شده (Already Ran)
فایل $GIT_DIR/filter-repo/already_ran فایلی است که ثبت میکند git-filter-repo اجرا شده است. هنگامی که این فایل وجود داشته باشد، اجراهای بعدی به عنوان ادامهای از عملیات فیلترسازی قبلی در نظر گرفته میشوند.
بهطور ملموس، این به این معناست: * بررسی "کلون تازه" (Fresh Clone) دور زده میشود
این کار به این دلیل انجام میشود که اجراهای قبلی باعث میشوند مخزن دیگر شبیه به یک کلون تازه به نظر نرسد، و بنابراین در بررسی کلون تازه رد شود، اما انجام فیلترسازی از طریق چندین بار فراخوانی git-filter-repo یک کاربرد موردنظر و پشتیبانیشده است. شما قبلاً در اجرای اولیه خود، بررسی "کلون تازه" را گذرانده یا دور زدهاید.
به عبارت دیگر، اگر اولین فراخوانی filter-repo کامیت A را به کامیت B بازنویسی کرده باشد، و دومین فراخوانی filter-repo کامیت B را به کامیت C بازنویسی کند، دومین اجرا به جای ورودی "B C"، یک ورودی "A C" برای کامیت تغییریافته خواهد داشت.
با جزئیات بیشتر، اگر مخزن در ابتدا دارای کامیتهای زیر بود: A1-B1-C1-D1-E1 و اولین فراخوانی filter-repo این را تغییر داد به A1-B1-C2-D2-E2 آنگاه اولین اجرا "C1" را به عنوان اولین کامیت تغییریافته گزارش میکرد. اگر دومین اجرای filter-repo این را بیشتر تغییر دهد به A1-B1-C2-D3-E3 آنگاه "C1" را به عنوان اولین کامیت تغییریافته گزارش خواهد داد، نه "D2"، زیرا با کامیتهای اصلی مقایسه میکند نه با کامیتهای میانی.
با این حال، اگر فایل already_ran وجود داشته باشد اما هنگام فراخوانی git-filter-repo بیش از ۱ روز از عمر آن گذشته باشد، از کاربر پرسیده میشود که آیا اجرای جدید باید ادامهای از اجرای قبلی در نظر گرفته شود یا خیر. اگر آنها پاسخ مثبت ندهند، سه مورد فوق اعمال نخواهند شد. این اعلان به این دلیل وجود دارد که کاربران ممکن است یک بازنویسی تاریخچه در یک مخزن انجام دهند، آن را فراموش کنند و دایرکتوری $GIT_DIR/filter-repo را رها نمایند، و سپس چند ماه یا چند سال بعد نیاز به انجام بازنویسی دیگری داشته باشند. اگر کامیتهای ناشی از بازنویسی قبلی عمومی شده و به اشتراک گذاشته شده باشند، اجرای بعدی filter-repo نباید ادامهای از اجرای فیلترسازی قبلی در نظر گرفته شود.
اشیاء LFS اصلی (Original LFS Objects)
هنگام اجرا با فلگ --sensitive-data-removal، و در صورتی که LFS توسط مخزن در حال استفاده باشد، فایل $GIT_DIR/filter-repo/original_lfs_objects شامل فهرستی از اشیاء LFS است که قبل از بازنویسی توسط مخزن به آنها ارجاع داده شده است، به ترتیب مرتبشده.
اشیاء LFS یتیم (Orphaned LFS Objects)
هنگام اجرا با فلگ --sensitive-data-removal، و در صورتی که LFS توسط مخزن در حال استفاده باشد، فایل $GIT_DIR/filter-repo/orphaned_lfs_objects شامل فهرستی از اشیاء LFS است که قبلاً توسط مخزن مورد ارجاع بودند اما پس از اجرای git-filter-repo دیگر ارجاعی به آنها وجود ندارد. اشیاء به ترتیب مرتبشده ظاهر میشوند.
بررسی ایمنی کلون تازه و --FORCE (FRESH CLONE SAFETY CHECK AND --FORCE)
از آنجا که filter-repo بازنویسی بازگشتناپذیر تاریخچه را انجام میدهد، جلوگیری از ایجاد تغییرات در مخزنی که کاربر نسخه پشتیبان مناسبی از آن ندارد بسیار مهم است. سازوکار دفاعی اولیه، صرفاً آموزش کاربران و اتکا به آنهاست که به خوبی از دادههای خود مراقبت کنند؛ بنابراین هشدارهای متعددی در مستندات درباره چگونگی بازنویسی تاریخچه توسط filter repo وجود دارد.
با این حال، به عنوان خدمتی به کاربران، میخواهیم یک بررسی ایمنی اضافی فراتر از مستندات ارائه دهیم. راه مناسبی برای بررسی اینکه آیا کاربر نسخه پشتیبان خوبی دارد یا نه وجود ندارد، اما میتوانیم یک پرسش مرتبط مطرح کنیم که یک تقریب ناقص اما کاملاً منطقی است: "آیا این مخزن یک کلون تازه است؟" متأسفانه، این نیز سؤالی است که نمیتوانیم پاسخی بینقص برای آن دریافت کنیم؛ git هیچ راهی برای پاسخ به این پرسش فراهم نمیکند. با این حال، حدود ده مورد وجود دارد که متوجه شدم همواره در کلونهای کاملاً جدید صادق به نظر میرسند (با این فرض که آنها یا کلونهایی از مخازن ریموت هستند یا با فلگ --no-local ساخته شدهاند)، و من همه آنها را بررسی میکنم.
این بررسیها میتوانند هم مثبت کاذب و هم منفی کاذب داشته باشند. ممکن است فردی یک نسخه پشتیبان کاملاً مطمئن از مخزن خود داشته باشد بدون اینکه واقعاً یک کلون تازه باشد — اما راهی برای filter-repo وجود ندارد که از این موضوع باخبر شود. برعکس، ممکن است فردی به تمام مواردی که filter-repo در بررسیهای ایمنی خود چک میکند نگاه کند و صرفاً مخزن بدون پشتیبان خود را طوری دستکاری نماید که آن شرایط را برآورده سازد (اگرچه این کار نیازمند تلاش قابلتوجهی است، و از نظر آماری بسیار بعید است که مخزنی که یک کلون تازه نیست بهطور تصادفی با تمامی این معیارها مطابقت داشته باشد). در عمل، بررسیهای ایمنی مورد استفاده filter-repo در جلوگیری از اجرای تصادفی filter-repo توسط افراد روی مخزنی که نباید آن را اجرا کنند، بسیار عالی عمل میکنند. این بررسی حتی یکبار جلوی خود من را گرفت، زمانی که واقعاً قصد اجرای filter-repo را داشتم اما در دایرکتوری متفاوتی نسبت به آنچه تصور میکردم بودم.
بهطور خلاصه، استفاده از ‘--force` برای نادیده گرفتن بررسیهای ایمنی کاملاً مجاز است تا زمانی که با بازنویسی برگشتناپذیر محتوای مخزن فعلی توسط filter-repo مشکلی نداشته باشید. این ایده بسیار بدی است که همیشه به مشخص کردن --force عادت کنید؛ اگر چنین کنید، روزی یکی از دستورات خود را مانند من در دایرکتوری اشتباهی اجرا خواهید کرد، و دیگر بررسی ایمنی نخواهید داشت تا به داد شما برسد. همچنین، قطعاً توصیه --force در فرومها، سایتهای پرسش و پاسخ، یا در ایمیلها به سایر کاربران بدون توضیح دقیق این موضوع که --force به معنای به خطر انداختن دادههای مخازن شماست، بههیچوجه جایز نیست. من بهویژه از افرادی ناراحت میشوم که این فلگ را زمانی پیشنهاد میکنند که مشخصاً نیازی به آن نیست؛ آنها بیدلیل دادههای دیگران را به خطر میاندازند.
انعطافپذیری و کاربردها (VERSATILITY)
ابزار filter-repo دارای سلسلهمراتبی از قابلیتها در طیفی از فلگهای آسان و کاربردی که انواع از پیش تعریفشده فیلترسازی را انجام میدهند، تا گزینههایی که انعطافپذیری زیادی در کنترل نحوه انجام فیلترسازی فراهم میکنند است. این طیف شامل موارد زیر است:
برای اطلاعات بیشتر درباره کالبکها، به بخش موسوم به “CALLBACKS” مراجعه کنید. برای مثالهایی در مورد نوشتن برنامههای پایتون که filter-repo را به عنوان یک ماژول ایمپورت میکنند تا ابزارهای جدید بازنویسی تاریخچه ایجاد نمایند، به دایرکتوری contrib/filter-repo-demos/ نگاه کنید. آن دایرکتوری در میان مثالهای دیگر، شامل پیادهسازی مجدد git-filter-branch است که سریعتر از git-filter-branch کار میکند، و پیادهسازی مجدد BFG Repo Cleaner با چندین رفع باگ و قابلیتهای جدید است.
بحث و بررسی (DISCUSSION)
استفاده از filter-repo نسبتاً ساده است، اما بازنویسی تاریخچه بخشی از یک بحث بزرگتر از نظر همکاری تیمی است. هنگامی که تاریخچه را بازنویسی میکنید، تاریخچههای قدیمی و جدید دیگر با یکدیگر سازگار نیستند؛ اگر این تاریخچه را به جایی پوش کنید تا دیگران ببینند، به نظر میرسد که گویی شما تمامی شاخهها و تگها را ریبیس (rebase) کردهاید. علاوه بر این بخش، اطمینان حاصل کنید که پیش از ادامه، با بخش "RECOVERING FROM UPSTREAM REBASE" در git-rebase(1) (و بهویژه "The hard case") آشنایی دارید.
مراحل استفاده از git-filter-repo به عنوان بخشی از تصویر بزرگتر انجام بازنویسی تاریخچه تقریباً به شرح زیر است:
چرا origin من حذف شده است؟ (Why is my origin removed?)
هنگامی که تاریخچه را بازنویسی میکنید، تمامی شناسههای کامیت (با شروع از اولین کامیتی که تغییرات در آن رخ داده است) تغییر مییابند. حتی اگر فکر میکنید یک کامیت میانی را تغییر ندادهاید، این واقعیت که هر یک از اجداد آن را تغییر دادهاید نیز تغییری محسوب میشود که اثرگذار است و باعث میشود شناسه آن کامیت نیز تغییر یابد. متأسفانه برای خود شما یا فرد دیگری بسیار آسان است که بهطور تصادفی تاریخچه قدیمی و نامطلوبی را که سعی در بازنویسی آن داشتید با تاریخچه جدید ادغام کنید، که نه تنها باعث بازگشت تاریخچه قدیمی میشود، بلکه "دو نسخه" از هر کامیت (هم کامیت اصلی و هم جایگزین پاکسازیشده) ایجاد میکند، و در نتیجه تعداد کامیتهای موجود در مخزن شما را دو برابر میسازد. بهطور خلاصه، در نهایت با آشفتگی حتی بزرگتری نسبت به آنچه شروع کرده بودید مواجه میشوید.
این اتفاق مکرراً برای افرادی که از git filter-branch یا BFG repo cleaner استفاده میکنند میافتد، و ممکن است برای کسانی که از git filter-repo استفاده میکنند نیز رخ دهد اگر اصرار داشته باشند که تغییرات را به مخزن اصلی بازگردانده و پوش کنند. نمونه راههایی که میتوانید از طریق آنها به چنین تاریخچه حتی ناخوشایندتری برسید عبارتند از:
حذف ریموت origin و پیشنهاد دادن اینکه افراد به یک مخزن جدید پوش کنند (و اطمینان از اینکه به دیگران بگویند مخزن جدید را کلون کنند) معمولاً یک نیروی بازدارنده خوب برای جلوگیری از این مشکلات است. اما، اگر افراد علیرغم این هشدارها واقعاً بخواهند به مخزن اصلی پوش کنند، انجام آن بسیار ساده است؛ کافی است دستور زیر را اجرا کنید:
و سپس میتوانید پوش کنید (مثلاً git push --force --branches --tags --prune). از آنجا که حذف آدرس origin روشی بسیار ارزان برای پیشگیری بالقوه از آشفتگیهای بزرگ است، و دور زدن آن برای کسانی که واقعاً میخواهند روی تاریخچه اصلی بازنویسی کنند بسیار آسان است، حذف آدرس origin یک اقدام ایمنی فوقالعاده است که من به کار میگیرم.
یک هشدار نهایی اگر واقعاً میخواهید تغییرات را به مخزن اصلی بازگردانید و پوش کنید: بخش بعدی در مورد حذف دادههای حساس را ببینید. آن مراحل مواردی هستند که هنگام پوش کردن به مخزن اصلی لازم خواهند بود؛ آنها به قدری دشوار و پردردسر هستند که فرض میکنم فقط زمانی ارزش انجام دارند که دادههای حساس در میان باشد، اما شما میتوانید تصمیم بگیرید که برای سایر انواع بازنویسی نیز از آنها پیروی کنید.
حذف دادههای حساس (Sensitive Data Removals)
حذف دادههای حساس نوع خاصی از بازنویسی تاریخچه است. در حالی که ترکیب تاریخچه پاکسازیشده با تاریخچه پاکسازینشده همیشه بسیار مشکلساز است، برای حذف دادههای حساس همچنین بد است که به دیگران اجازه داده شود به هر نحوی به مشاهده/کلون/دریافت تاریخچه پاکسازینشده ادامه دهند؛ کاربران اغلب باید تلاش کنند تا تاریخچه قدیمی را نیز بهطور کامل محو سازند.
توجه داشته باشید که اگر دادههای حساس مورد بحث یک توکن/رمز عبور/اعتبارنامه/راز (token/password/credential/secret) است (که اغلب چنین است)، بسیار مهم است که ابتدا آن اعتبارنامه را ابطال (revoke) و تعویض (rotate) کنید. هنگامی که اعتبارنامه ابطال یا تعویض شود، دیگر نمیتوان از آن برای دسترسی استفاده کرد. ابطال/تعویض ممکن است مشکل شما را بدون توسل به اقدام سنگین بازنویسی و پاکسازی تاریخچه حل کند.
برای بازنویسی تاریخچه جهت حذف دادههای حساس، سه مرحله سطحبالا وجود دارد:
هر یک از این موارد در زیر با جزئیات بیشتر بررسی خواهد شد.
یک نکته مهم که باید به آن توجه داشت این است که باید به دیگرانی که روی همان مخزن کار میکنند دستور داده شود تا زمانی که شما پاکسازی را انجام میدهید دست از کار بکشند؛ اگر آنها در حین پاکسازی شما به توسعه ادامه دهند، احتمالاً مجبور خواهید شد یا تغییرات آنها را دور بریزید یا پاکسازی خود را از نو شروع کنید.
مخزن را
بهصورت
محلی با
استفاده از
git-filter-repo
بازنویسی
کنید
گام اول این است که نسخهای از مخزن خود را بهصورت محلی با استفاده از git-filter-repo بازنویسی کنید. دستورات دقیق برای اجرا بسته به اینکه دادههای حساس در کجای مخزن شما یافت میشوند متفاوت خواهد بود، اما چند نکته کلی:
پس از بازنویسی تاریخچه بهصورت محلی، حتماً آن را بررسی کنید تا مطمئن شوید دادههای حساس حذف شدهاند. برخی از دستوراتی که ممکن است برای بررسی مفید باشند عبارتند از:
git log --all --name-status -- ${PROBLEMATIC_FILE1} ${PROBLEMATIC_FILE2}
یا
git log -S"${PROBLEMATIC_STRING}" --all -p --
اگر هر یک از این دستورات دادههای حساس بیشتری را نشان داد، پیش از ادامه، دستورات اضافی git-filter-repo را برای پاکسازی دادههای لازم اجرا کنید.
اطمینان
حاصل کنید
که سایر
نسخهها
پاکسازی
شدهاند:
سرور اصلی
پاکسازی مخزنی که از آن کلون کردهاید مستلزم force push کردن تاریخچه بازنویسیشده شما روی تاریخچه اصلی است. شما باید تمامی refها را بهصورت اجباری پوش کنید، نه فقط شاخه فعلی خود را. میتوانید برای انجام این کار از دستور زیر استفاده کنید (قبل از اجرای آن، فهرست موارد بلافاصله بعد از این دستور را بخوانید):
git push --force --mirror origin
چندین نکته درباره این دستور:
همچنین، اگر هر یک از اشیاء LFS در اثر بازنویسی شما یتیم (orphaned) شدند، آن اشیاء احتمالاً حاوی دادههای حساس هستند و باید از سرور LFS حذف/پاکسازی شوند. باید از نگهدارنده سرور LFS که استفاده میکنید درباره نحوه حذف/پاکسازی آنها روی سرور سؤال کنید.
اطمینان
حاصل کنید
که سایر
نسخهها
پاکسازی
شدهاند:
کلونهای
همکاران
پس از اینکه سرور را پاکسازی کردید، سادهترین راه برای پاکسازی سایر کلونها این است که از همه بخواهید کلونهای موجود خود را حذف کرده و دوباره کلون بگیرند.
اگر این یک گزینه نیست، باید با احتیاط پیش بروید زیرا یک دستور ساده git pull && git push از هر کلون دیگری، مجدداً مخزن اصلی را آلوده خواهد کرد و آشفتگی را برای پاکسازی حتی دشوارتر میسازد. برای جلوگیری از این موضوع، قبل از پوش کردن از هر کلون دیگری، باید از آنها بخواهید نسخه خود را طبق توضیحات زیر پاکسازی کنند.
با این حال، ابتدا اجازه دهید یادآوری کنم که شما نباید از سایر توسعهدهندگان بخواهید کلون خود را با اجرای همان دستورات git-filter-repo که خودتان اجرا کردید پاکسازی کنند. در حالی که ممکن است این کار گاهی تصادفاً جواب دهد، اما بهطور کلی قابل اعتماد نیست. اجرای همان دستورات git-filter-repo، حتی در صورت یکسان بودن، میتواند منجر به دریافت هشهای جدیدی برای کامیتها شود که با هشهای جدید شما تفاوت دارد، و در نهایت با آشفتگیای شامل دو یا چند نسخه از هر کامیت مواجه خواهید شد.
در عوض، توسعهدهندگانی که کلونهای دیگری از مخزن دارند، در صورتی که تمایلی به دور انداختن نسخه خود و کلون مجدد ندارند، باید مراحل زیر را برای پاکسازی نسخه خود دنبال کنند:
پس از تکمیل این مراحل، همچنین باید بررسی کنید که کلون دیگر حاوی هیچ داده حساسی نباشد (از قلم انداختن چیزی واقعاً آسان است، که شما را در معرض خطر آلوده کردن مجدد سایر مخازن با دادههای حساس قرار میدهد). میتوانید این کار را با اجرای دستور زیر انجام دهید:
git cat-file -t ${HASH_OF_FIRST_CHANGED_COMMIT}
جایی که ${HASH_OF_FIRST_CHANGED_COMMIT} توسط git-filter-repo در انتهای اجرای آن چاپ شده بود (اگر بیش از یک "اولین کامیت تغییریافته" وجود داشت، این دستور را چندین بار، با هش هر کامیت اجرا کنید). اگر این دستور یک خطای مهلک (fatal error) برگرداند، کامیت بهدرستی از این مخزن حذف شده است. اگر با "commit" پاسخ دهد، آن شیء همچنان وجود دارد و لازم است تگها را مجدداً حذف کنید، تمام شاخهها/refهای لازم را دوباره ریبیس کنید، و مجدداً reflogها را منقضی کرده و gc را دوباره انجام دهید. اگر کنجکاو هستید که کدام شاخهها یا refها شاخههای مشکلسازی بودند که ${HASH_OF_FIRST_CHANGED_COMMIT} را نگه داشته بودند، با فرض اینکه کارهای انقضای reflog و gc فوق را انجام داده باشید، دستور زیر باید به شما در یافتن شاخهها/refهای مشکلساز کمک کند:
git for-each-ref --contains ${HASH_OF_FIRST_CHANGED_COMMIT}
همچنین به یاد داشته باشید، دستور cat-file باید برای هر ${HASH_OF_FIRST_CHANGED_COMMIT} درگیر در صورتی که بیش از یکی داشته باشید، با یک خطای مهلک برگردد.
پس از انجام همه این موارد، اگر هر شیء LFS در اثر بازنویسی یتیم شده باشد (که باز هم اگر هنگام اجرای git-filter-repo از گزینه --sensitive-data-removal استفاده کنید به شما گفته خواهد شد)، باید آن اشیاء LFS را نیز حذف کنید. آنها را در چند دایرکتوری در زیر مسیر .git/lfs/objects/ پیدا کرده و حذف نمایید.
جلوگیری
از تکرار و
اجتناب از
نشت
دادههای
حساس در
آینده
اقدامات متعددی وجود دارد که میتوانید برای کمک به جلوگیری از تکرار مشکلات انجام دهید. ممکن است همه آنها برای مورد شما قابل اعمال نباشند، اما هرچه موارد بیشتری اعمال شوند، احتمال جلوگیری از مشکلات بیشتر است.
برای مقابله با نشت دادههای حساس موجود:
اقداماتی برای کمک به جلوگیری از سایر نشتهای دادههای حساس در آینده:
مثالها (EXAMPLES)
فیلتر کردن بر اساس مسیر (Path based filtering)
برای نگه داشتن تنها فایل README.md به همراه دایرکتوریهای guides و tools/releases/:
git filter-repo --path README.md --path guides/ --path tools/releases
نام دایرکتوریها را میتوان با یا بدون اسلش پایانی مشخص کرد، و تمام نامهای فایل نسبت به بالاترین سطح ریشه (toplevel) مخزن سنجیده میشوند. برای نگه داشتن تمامی فایلها به جز این مسیرها، کافی است --invert-paths را اضافه کنید:
git filter-repo --path README.md --path guides/ --path tools/releases --invert-paths
اگر میخواهید هم فیلتر گنجاندن (inclusion) و هم فیلتر استثنا کردن (exclusion) داشته باشید، کافی است filter-repo را چندین بار اجرا کنید. به عنوان مثال، برای نگهداشتن زیردایرکتوری src/main اما حذف فایلهای درون src/main با نام data، اجرا کنید:
git filter-repo --path src/main/ git filter-repo --path-glob 'src/*/data' --invert-paths
توجه داشته باشید که ستاره (*) در چندین دایرکتوری تطابق پیدا میکند، بنابراین دستور دوم مواردی مانند src/main/org/whatever/data را حذف خواهد کرد. همچنین دستور دوم به خودی خود مواردی مانند src/not-main/foo/data را نیز حذف میکند، اما از آنجا که src/not-main/ توسط دستور اول حذف شده است، این مسئله مشکلی ایجاد نمیکند. علاوه بر این، قرار دادن ستاره درون نقلقول گاهی برای جلوگیری از بسط سراسری (glob expansion) توسط پوسته حائز اهمیت است.
شما همچنین میتوانید مسیرها را با عبارات باقاعده (regular expression) انتخاب کنید (ببینید https://docs.python.org/3/library/re.html#regular-expression-syntax). به عنوان مثال، برای اینکه فقط فایلهایی از مخزن گنجانده شوند که نام آنها به فرمت YYYY-MM-DD.txt بوده و حداقل در دو زیردایرکتوری عمیقتر قرار دارند:
git filter-repo --path-regex '^.*/.*/[0-9]{4}-[0-9]{2}-[0-9]{2}.txt$'
اگر میخواهید دو دایرکتوری تغییر نام پیدا کنند (و در صورتی که هر دو به یک مکان تغییر نام داده شوند، احتمالاً ادغام شوند)، از --path-rename استفاده کنید؛ به عنوان مثال، برای تغییر نام هر دوی cmds/ و src/scripts/ به tools/:
git filter-repo --path-rename cmds:tools --path-rename src/scripts/:tools/
مشابه با --path، دایرکتوریها برای --path-rename نیز میتوانند با یا بدون اسلش پایانی مشخص شوند.
اگر با --path-rename تغییر نامی به سمتی انجام دهید که از قبل در حال استفاده بوده است، بدون هیچ هشداری رونویسی (overwrite) خواهد شد. با این حال، اگر سعی کنید چندین فایل را به یک مکان مشترک تغییر نام دهید (مثلاً هر دوی src/scripts/run_release.sh و cmds/run_release.sh با تغییر نامهای بالا موجود بوده و محتوای متفاوتی داشتند)، به شما خطایی داده خواهد شد. اگر با چنین حالتی مواجه شدید، ممکن است بخواهید یک دستور تغییر نام دیگر اضافه کنید تا یکی از مسیرها را به جای دیگری منتقل نماید که تداخلی ایجاد نشود:
git filter-repo --path-rename cmds/run_release.sh:tools/do_release.sh \
--path-rename cmds/:tools/ \
--path-rename src/scripts/:tools/
همچنین --path-rename مسائلی را در مورد ترتیب اعمال مطرح میکند؛ همه آرگومانهای مسیر به ترتیب اعمال میشوند. بنابراین، دستوری مانند
git filter-repo --path-rename sources/:src/main/ --path src/main/
منطقی خواهد بود اما جابجا کردن این دو آرگومان منطقی نیست (چون src/main/ با تغییر نام ایجاد میشود، بنابراین معکوس کردن ترتیب آنها مخزنی خالی به شما تحویل میدهد). همچنین دقت کنید که تغییر نام cmds/run_release.sh در دو مثال قبل، پیش از سایر تغییر نامها انجام شد.
توجه داشته باشید که تغییر نام مسیرها، فیلتر کردن مسیرها را انجام نمیدهد؛ بنابراین دستور زیر
git filter-repo --path src/main/ --path-rename tools/:scripts/
منجر به وجود آمدن دایرکتوریهای tools یا scripts نخواهد شد، زیرا تک فیلترِ موجود تنها src/main/ را انتخاب کرده است. احتمالاً در عوض میخواهید این دستور را اجرا کنید:
git filter-repo --path src/main/ --path tools/ --path-rename tools/:scripts/
اگر ترجیح میدهید فیلتر کردن را صرفاً بر اساس نام پایه (basename) انجام دهید، از فلگ --use-base-name استفاده کنید (البته این گزینه با --path-rename ناسازگار است). برای مثال، جهت گنجاندن فایلهای README.md و Makefile از هر دایرکتوری:
git filter-repo --use-base-name --path README.md --path Makefile
اگر میخواستید تمامی فایلهای .DS_Store را در هر دایرکتوری حذف کنید، میتوانید از این دستور:
git filter-repo --invert-paths --path '.DS_Store' --use-base-name
یا این دستور استفاده کنید:
git filter-repo --invert-paths --path-glob '*/.DS_Store' --path '.DS_Store'
(گزینه --path-glob به تنهایی کافی نیست زیرا ممکن است فایل .DS_Store را در بالاترین سطح ریشه نادیده بگیرد؛ از طرف دیگر، عبارتی مانند --path-glob '*.DS_Store' هرچند آن مشکل را برطرف میکند اما فایلهایی با نام foo.DS_Store یا bar/baz.DS_Store را نیز در بر میگیرد)
در نهایت، گزینه --filename-callback را نیز در بخشی با عنوان “CALLBACKS” ببینید.
فیلتر کردن بر اساس مسیرهای متعدد (Filtering based on many paths)
اگر فهرست بلندی از فایلها، دایرکتوریها، الگوهای سراسری (globs) یا عبارات باقاعده برای فیلتر کردن دارید، میتوانید آنها را در یک فایل قرار داده و از --paths-from-file استفاده کنید؛ برای مثال با فایلی به نام stuff-i-want.txt و محتویات:
# Blank lines and comment lines are ignored.
# Examples similar to --path:
README.md
guides/
tools/releases
# An example that is like --path-glob:
glob:*.py
# An example that is like --path-regex:
regex:^.*/.*/[0-9]{4}-[0-9]{2}-[0-9]{2}.txt$
# An example of renaming a path
tools/==>scripts/
# An example of using a regex to rename a path
regex:(.*)/([^/]*)/([^/]*)\.text$==>\2/\1/\3.txt
سپس میتوانید دستور
git filter-repo --paths-from-file stuff-i-want.txt
را اجرا کنید تا مخزنی داشته باشید که تنها حاوی فایل README.md در بالاترین سطح، دایرکتوریهای guides/ و tools/releases/، تمام فایلهای پایتون و فایلهایی باشد که نام آنها به شکل YYYY-MM-DD.txt در عمق حداقل دو زیردایرکتوری است، و همچنین tools/ را به scripts/ و فایلهایی مانند foo/bar/baz.text را به bar/foo/baz.txt تغییر نام دهد. به پیشوندهای خطی خاص glob: و regex: و رشته خاص ==> برای مشخص کردن تغییر نامها توجه کنید.
گاهی روشی برای تولید آسان فهرست تمام فایلهای مد نظر خود دارید. به عنوان مثال، اگر میدانید که هیچیک از فایلهای ردگیریشده کنونی شامل خط جدید (newline) یا کاراکترهای خاص نیستند (مدخل core.quotePath را از git config --help ببینید) به گونهای که git ls-files همه فایلها را عیناً خط به خط چاپ میکند، و میدانستید که فقط میخواهید فایلهای ردگیریشده فعلی را نگه دارید (و در نتیجه هر فایلی را که فقط در شاخههای دیگر یا در کامیتهای قدیمیتر وجود دارد از کل تاریخچه کامیتها حذف کنید)، در این صورت میتوانید از جفت دستوراتی مانند این استفاده کنید:
git ls-files >../paths-i-want.txt git filter-repo --paths-from-file ../paths-i-want.txt
به طور مشابه، میتوانید از --paths-from-file برای حذف تعداد زیادی فایل استفاده کنید. برای مثال، میتوانید git filter-repo --analyze را برای دریافت گزارشها اجرا کنید، یکی از آنها مانند .git/filter-repo/analysis/path-deleted-sizes.txt را بررسی کرده و نام تمامی فایلها را در فایلی مانند /tmp/files-i-dont-want-anymore.txt کپی کنید و سپس دستور
git filter-repo --invert-paths --paths-from-file /tmp/files-i-dont-want-anymore.txt
را اجرا کنید تا همه آنها را حذف نماید.
میانبرهای مبتنی بر دایرکتوری (Directory based shortcuts)
فرض کنید ساختار دایرکتوری شما به این صورت باشد:
module/ foo.c bar.c otherDir/ blah.config stuff.txt zebra.jpg
اگر فقط دایرکتوری module/ را میخواستید و تمایل داشتید که این دایرکتوری به ریشه جدید تبدیل شود، به طوری که ساختار دایرکتوری جدید شما به این شکل درآید:
foo.c bar.c
در این صورت میتوانید اجرا کنید:
git filter-repo --subdirectory-filter module/
اگر تمامی فایلها از مخزن اصلی را میخواستید، اما قصد داشتید همه چیز را به زیردایرکتوری به نام my-module/ منتقل کنید، به طوری که ساختار جدید دایرکتوری شما به این صورت باشد:
my-module/
module/
foo.c
bar.c
otherDir/
blah.config
stuff.txt
zebra.jpg
در این صورت در عوض دستور زیر را اجرا میکردید:
git filter-repo --to-subdirectory-filter my-module/
فیلتر کردن بر اساس محتوا (Content based filtering)
اگر میخواهید تمام فایلهای بزرگتر از یک اندازه معین را فیلتر (حذف) کنید، میتوانید از --strip-blobs-bigger-than به همراه مقدار اندازه (پسوندهای K، M و G شناخته میشوند) استفاده کنید، برای مثال:
git filter-repo --strip-blobs-bigger-than 10M
اگر میخواهید تمامی فایلهای دارای شناسه شیء گیت (هشها) مشخص را حذف کنید، هشها را در یک فایل فهرست کرده و اجرا کنید:
git filter-repo --strip-blobs-with-ids FILE_WITH_GIT_BLOB_IDS
اگر میخواهید محتویات فایلها را تغییر دهید، میتوانید این کار را بر اساس فهرستی از عبارات در یک فایل، یک عبارت در هر خط، انجام دهید. به عنوان مثال، با فایلی به نام expressions.txt حاوی:
p455w0rd
foo==>bar
glob:*666*==>
regex:\bdriver\b==>pilot
literal:MM/DD/YYYY==>YYYY-MM-DD
regex:([0-9]{2})/([0-9]{2})/([0-9]{4})==>\3-\1-\2
سپس اجرای
git filter-repo --replace-text expressions.txt
فایلها را بررسی کرده و p455w0rd را با ***REMOVED***، foo را با bar، هر خط حاوی 666 را با یک خط خالی، کلمه driver را با pilot (اما نه اگر حروفی قبل یا بعد از آن باشد؛ مثلاً drivers بدون تغییر باقی میماند)، متن دقیق MM/DD/YYYY را با YYYY-MM-DD و رشتههای تاریخ با قالب MM/DD/YYYY را با قالب YYYY-MM-DD جایگزین خواهد کرد. در فایل عبارات، چند نکته قابل توجه وجود دارد:
همچنین گزینه --blob-callback را در بخشی با عنوان “CALLBACKS” ببینید.
بهروزرسانی پیامهای کامیت/تگ (Updating commit/tag messages)
اگر میخواهید پیامهای کامیت یا تگ را ویرایش کنید، میتوانید این کار را با همان ساختار نحوی --replace-text که در بالا توضیح داده شد انجام دهید. برای مثال با فایلی به نام expressions.txt حاوی:
foo==>bar
سپس اجرای
git filter-repo --replace-message expressions.txt
عبارت foo را در پیامهای کامیت یا تگ با bar جایگزین خواهد کرد.
همچنین گزینه --message-callback را در بخشی با عنوان “CALLBACKS” ببینید.
فیلتر کردن بر اساس نام مرجع (Refname based filtering)
برای تغییر نام تگها، از --tag-rename استفاده کنید، برای مثال:
git filter-repo --tag-rename foo:bar
این کار هر تگی را که با foo آغاز میشود تغییر نام میدهد تا از این پس با bar شروع شود. هر یک از طرفین دونقطه میتواند خالی باشد، برای مثال:
git filter-repo --tag-rename '':'my-module-'
برای تغییرات عمومیتر نام مراجع، --refname-callback را در بخشی با عنوان “CALLBACKS” ببینید.
فیلتر کردن بر اساس کاربر و ایمیل (User and email based filtering)
برای تغییر نام کاربری و ایمیلهای کامیتها، میتوانید یک فایل mailmap در قالبی که توسط git-shortlog(1) پذیرفته میشود ایجاد کنید. به عنوان مثال، اگر فایلی به نام my-mailmap دارید، میتوانید اجرا کنید:
git filter-repo --mailmap my-mailmap
و اگر محتویات فعلی آن فایل به شرح زیر باشد (در صورتی که فایل mailmap مشخصشده تحت کنترل نسخه باشد، نسخههای پیشین آن در تاریخچه نادیده گرفته میشوند):
Name For User <email@addre.ss> <new@ema.il> <old1@ema.il> New Name And <new@ema.il> <old2@ema.il> New Name And <new@ema.il> Old Name And <old3@ema.il>
در این صورت میتوان نام کاربری و/یا ایمیلها را بر اساس نگاشت مشخصشده بهروزرسانی کرد.
همچنین --name-callback و --email-callback را در بخشی با عنوان “CALLBACKS” ببینید.
بازنویسی والد (Parent rewriting)
برای جایگزین کردن $commit_A با $commit_B (مثلاً برای اینکه تمامی کامیتهایی که $commit_A والد آنها بوده، در عوض $commit_B را به عنوان والد داشته باشند) و بازنویسی تاریخچه برای دائمی کردن آن:
git replace $commit_A $commit_B git filter-repo --proceed
برای ایجاد یک کامیت جدید با همان محتویات $commit_A اما با والد(های) متفاوت، و سپس جایگزین کردن $commit_A با کامیت جدید و بازنویسی تاریخچه جهت دائمی کردن آن:
git replace --graft $commit_A $new_parent_or_parents git filter-repo --proceed
گزینه --proceed برای جلوگیری از رد شدن در بررسی «هیچ آرگومانی مشخص نشده است» (no arguments specified) لازم است. توجه داشته باشید که نسخههای قدیمیتر git-filter-repo نیاز داشتند پس از ایجاد پیوند (graft) گزینه --force ارسال شود تا از فعال شدن بررسی not-a-fresh-clone جلوگیری شود؛ آن بررسی اصلاح شده است تا این استفاده بیش از حد از --force برطرف گردد.
بازنویسیهای جزئی تاریخچه (Partial history rewrites)
برای بازنویسی تاریخچه تنها روی یک شاخه (که ممکن است باعث شود دیگر هیچ تاریخچه مشترکی با سایر شاخهها نداشته باشد)، از --refs استفاده کنید. برای مثال جهت حذف فایلی با نام extraneous.txt از شاخه master:
git filter-repo --invert-paths --path extraneous.txt --refs master
برای بازنویسی صرفاً تعدادی از کامیتهای اخیر:
git filter-repo --invert-paths --path extraneous.txt --refs master~3..master
فراخوانیهای بازگشتی (CALLBACKS)
برای انعطافپذیری بیشتر، filter-repo به شما امکان میدهد توابعی را در خط فرمان مشخص کنید تا تمام تغییرات را فیلتر نمایید. لطفاً توجه داشته باشید که هشدارهایی درباره سازگاری API در ارتباط با این فراخوانیهای بازگشتی وجود دارد که باید پیش از استفاده از آنها آگاه باشید؛ به توضیح "API BACKWARD COMPATIBILITY CAVEAT" در نزدیکی بالای کد منبع git-filter-repo مراجعه کنید.
بیشتر توابع فراخوانی بازگشتی دارای فرمت کلی یکسانی هستند (--file-info-callback یک استثنا است که بعداً به آن اشاره خواهد شد). برای یک آرگومان خط فرمان مانند
--foo-callback 'BODY'
کد زیر کامپایل و فراخوانی خواهد شد:
def foo_callback(foo): BODY
بنابراین، تنها کافی است اطمینان حاصل کنید که BODY شما foo را به درستی تغییر داده و بازمیگرداند. یک نکته مهم برای تمام فراخوانیهای بازگشتی این است که filter-repo در همه جا به جای رشتهها از رشتههای بایتی (bytestrings - به https://docs.python.org/3/library/stdtypes.html#bytes مراجعه کنید) استفاده میکند.
چهار فراخوانی بازگشتی وجود دارد که به شما امکان میدهد مستقیماً روی اشیای خام حاوی دادههایی که نوشتن آنها در قالب git-fast-import(1) آسان است، کار کنید:
--blob-callback --commit-callback --tag-callback --reset-callback
ما بعداً به این موارد بازخواهیم گشت زیرا معمولاً سایر فراخوانیهای بازگشتی راحتتر هستند. فراخوانیهای بازگشتی دیگر روی بخش کوچکی از اشیای خام عمل میکنند یا روی بخشهایی در انواع مختلف اشیای خام کار میکنند (مانند نام نویسنده و نام کامیتکننده و نام برچسبگذار در میان کامیتها و برچسبها، یا نامهای ارجاع (refnames) در کامیتها، برچسبها و ریستها، یا پیامها در کامیتها و برچسبها). فراخوانیهای بازگشتی آسانتر عبارتند از:
--filename-callback --message-callback --name-callback --email-callback --refname-callback --file-info-callback
در هر یک از آنها انتظار میرود که صرفاً مقدار جدیدی را بر اساس مقدار ارسالی بازگردانید. برای نمونه،
git-filter-repo --name-callback 'return name.replace(b"Wiliam", b"William")'
منجر به فراخوانی تابع زیر خواهد شد:
def name_callback(name): return name.replace(b"Wiliam", b"William")
فراخوانی بازگشتی ایمیل نیز بسیار مشابه است:
git-filter-repo --email-callback 'return email.replace(b".cm", b".com")'
فراخوانی بازگشتی نام ارجاع (refname) نیز مشابه است، اما توجه داشته باشید که نام ارجاع ارسالی و بازگردانده شده باید کامل (fully qualified) باشند (مانند b"refs/heads/master" به جای صرفاً b"master" و b"refs/tags/v1.0.7" به جای b"1.0.7"):
git-filter-repo --refname-callback ' # Change e.g. refs/heads/master to refs/heads/prefix-master rdir,rpath = os.path.split(refname) return rdir + b"/prefix-" + rpath'
فراخوانی بازگشتی پیام بسیار شبیه به سه فراخوانی بازگشتی پیشین است، هرچند روی رشتهای بایتی عمل میکند که احتمالاً بیش از یک خط است:
git-filter-repo --message-callback '
if b"Signed-off-by:" not in message:
message += b"\nSigned-off-by: Me My <self@and.eye>"
return re.sub(b"[Ee]-?[Mm][Aa][Ii][Ll]", b"email", message)'
فراخوانی بازگشتی نام فایل (filename) کمی جالبتر است. بازگرداندن None به این معنی است که فایل باید از تمام کامیتها حذف شود، بازگرداندن نام فایل به صورت دستنخورده نشاندهنده نگهداشتن فایل است، و بازگرداندن یک نام متفاوت به این معنی است که فایل باید تغییر نام یابد. یک مثال:
git-filter-repo --filename-callback '
if b"/src/" in filename:
# Remove all files with a directory named "src" in their path
# (except when "src" appears at the toplevel).
return None
elif filename.startswith(b"tools/"):
# Rename tools/ -> scripts/misc/
return b"scripts/misc/" + filename[6:]
else:
# Keep the filename and do not rename it
return filename
'
فراخوانی بازگشتی اطلاعات فایل (file-info callback) پیچیدهتر است. این فراخوانی برای مواردی طراحی شده است که فیلترسازی به هر دو مورد نام فایل و محتویات (و احتمالاً حالت دسترسی یا mode) بستگی دارد. این تابع برای تغییرات فایل غیر از حذفها فراخوانی میشود (زیرا عملیات حذف محتوای فایلی برای کار کردن ندارد). فراخوانی بازگشتی اطلاعات فایل چهار پارامتر دریافت میکند (filename، mode، blob_id و value) و انتظار میرود سه مقدار را بازگرداند (filename، mode، blob_id). نام فایل به شکلی مشابه با فراخوانی بازگشتی نام فایل مدیریت میشود؛ میتوان از آن برای تغییر نام فایل استفاده کرد (یا برای صرفنظر از تغییر، آن را روی None تنظیم نمود). حالت دسترسی (mode) یک رشته بایتی ساده است (b"100644" برای فایلهای عادی غیرقابل اجرا، b"100755" برای فایلها/اسکریپتهای قابل اجرا، b"120000" برای پیوندهای نمادین، و b"160000" برای زیرماژولها). پارامتر blob_id بیشترین کاربرد را در کنار پارامتر value دارد. پارامتر value نمونهای از کلاسی است که شامل توابع زیر است: value.get_contents_by_identifier(blob_id) → contents (bytestring) و value.get_size_by_identifier(blob_id) → size_of_blob (int) و value.insert_file_with_contents(contents) → blob_id و value.is_binary(contents) → bool و value.apply_replace_text(contents) → new_contents (bytestring) و دارای داده عضوی زیر است که میتوانید در آن بنویسید: value.data (dict). این توابع به شما امکان میدهند محتویات فایل یا اندازه آن را به دست آورید، فایل جدیدی را در جریان ایجاد کنید که blob_id آن را میتوانید بازگردانید، بررسی کنید که آیا محتویات مشخصشده باینری است یا خیر (با استفاده از روش ابتکاری دستور grep(1))، و قوانین جایگزینی از --replace-text را اعمال کنید (توجه داشته باشید که --file-info-callback باعث میشود تغییرات حاصل از --replace-text بهطور خودکار اعمال نشوند). برای نمونه میتوانید از این مورد برای اعمال تغییرات از --replace-text فقط روی انواع خاصی از فایلها و تغییر همزمان نام فایلهایی که این تغییرات روی آنها اعمال میشود استفاده کنید:
git-filter-repo --file-info-callback '
if not filename.endswith(b".config"):
# Make no changes to the file; return as-is
return (filename, mode, blob_id)
new_filename = filename[0:-7] + b".cfg"
contents = value.get_contents_by_identifier(blob_id)
new_contents = value.apply_replace_text(contents)
new_blob_id = value.insert_file_with_contents(new_contents)
return (new_filename, mode, new_blob_id)
توجه داشته باشید که اگر تاریخچه شامل چندین بازنگری با فایل یکسان باشد (برای نمونه در چندین شاخه cherry-picked شده باشد یا تعدادی revert وجود داشته باشد)، آنگاه --file-info-callback چندین بار فراخوانی خواهد شد. اگر میخواهید از پردازش چندباره یک فایل یکسان جلوگیری کنید، میتوانید نتایج تبدیل را در دیکشنری value.data ذخیره نمایید. برای نمونه، میتوانیم مثال بالا را تغییر دهیم تا تبدیلها را فقط روی blob_idهایی اعمال کند که قبلاً ندیدهایم:
git-filter-repo --file-info-callback '
if not filename.endswith(b".config"):
# Make no changes to the file; return as-is
return (filename, mode, blob_id)
new_filename = filename[0:-7] + b".cfg"
if blob_id in value.data:
return (new_filename, mode, value.data[blob_id])
contents = value.get_contents_by_identifier(blob_id)
new_contents = value.apply_replace_text(contents)
new_blob_id = value.insert_file_with_contents(new_contents)
value.data[blob_id] = new_blob_id
return (new_filename, mode, new_blob_id)
یک مثال دیگر برای --file-info-callback این است که تمام فایلهای .sh قابل اجرا شوند و یک خط جدید اضافی به انتهای فایلهای .sh افزوده شود:
git-filter-repo --file-info-callback '
if not filename.endswith(b".sh"):
# Make no changes to the file; return as-is
return (filename, mode, blob_id)
# There are only 4 valid modes in git:
# - 100644, for regular non-executable files
# - 100755, for executable files/scripts
# - 120000, for symlinks
# - 160000, for submodules
new_mode = b"100755"
contents = value.get_contents_by_identifier(blob_id)
new_contents = contents + b"\n"
new_blob_id = value.insert_file_with_contents(new_contents)
return (filename, new_mode, new_blob_id)
برخلاف انواع فراخوانیهای بازگشتی پیشین، از فراخوانیهای بازگشتی blob، reset، tag و commit انتظار نمیرود مقداری را بازگردانند، بلکه در عوض انتظار میرود شیء ارسالشده را اصلاح کنند. فیلدهای اصلی برای این اشیاء عبارتند از (مشروط به هشدارهای سازگاری عقبرو API که پیشتر ذکر شد):
نمونهای برای هر کدام:
git filter-repo --blob-callback '
if len(blob.data) > 25:
# Mark this blob for removal from all commits
blob.skip()
else:
blob.data = blob.data.replace(b"Hello", b"Goodbye")
'
git filter-repo --reset-callback 'reset.ref = reset.ref.replace(b"master", b"dev")'
git filter-repo --tag-callback '
if tag.tagger_name == b"Jim Williams":
# Omit this tag
tag.skip()
else:
tag.message = tag.message + b"\n\nTag of %s by %s on %s" % (tag.ref, tag.tagger_email, tag.tagger_date)'
git filter-repo --commit-callback '
# Remove executable files with three 6s in their name (including
# from leading directories).
# Also, undo deletion of sources/foo/bar.txt (change types are
# either b"D" (deletion) or b"M" (add or modify); renames are
# handled by deleting the old file and adding a new one)
commit.file_changes = [
change for change in commit.file_changes
if not (change.mode == b"100755" and
change.filename.count(b"6") == 3) and
not (change.type == b"D" and
change.filename == b"sources/foo/bar.txt")]
# Mark all .sh files as executable; modes in git are always one of
# 100644 (normal file), 100755 (executable), 120000 (symlink), or
# 160000 (submodule)
for change in commit.file_changes:
if change.filename.endswith(b".sh"):
change.mode = b"100755"
'
ساختار درونی (INTERNALS)
احتمالاً نیازی به خواندن این بخش ندارید، مگر اینکه بسیار کنجکاو باشید یا بخواهید بازنویسی بسیار پیچیدهای در تاریخچه انجام دهید.
نحوه کارکرد filter-repo
بهطور کلی، filter-repo با اجرای دستور زیر کار میکند
git fast-export <options> | filter | git fast-import <options>
که در آن filter-repo نه تنها کل خط لوله را اجرا میکند بلکه به عنوان filter در میانه نیز عمل مینماید. با این حال، filter-repo چند کار اضافی دیگر نیز انجام میدهد تا به ابزاری کامل برای فیلترسازی تبدیل شود. ترتیبی که با دقت بیشتری آنچه را filter-repo اجرا میکند نشان میدهد به شرح زیر است:
چند نکته یا استثنا درباره هر یک از موارد فوق:
اطلاعات مربوط به این مراحل زمانی که --debug به filter-repo داده شود چاپ میشود. هنگام انجام بازنویسی تاریخچه با --partial، مراحل ۲، ۳، ۷ و ۸ بدون قید و شرط نادیده گرفته میشوند، مرحله ۵ در صورتی که --replace-refs برابر با update-no-add باشد نادیده گرفته میشود، و تنها بخش nuke-unused-refs از مرحله ۵ در صورتی که --replace-refs مقدار دیگری داشته باشد نادیده گرفته خواهد شد.
محدودیتها
محدودیتهای
به ارث
رسیده
از آنجا که git filter-repo برای انجام بخش زیادی از کارهای سنگین، fast-export و fast-import را فراخوانی میکند، محدودیتهایی را از آن سامانهها به ارث میبرد:
همچنین به دلیل طراحی این سامانهها محدودیتهایی وجود دارد:
محدودیتهای
ذاتی
برخی از انواع فیلترسازی محدودیتهایی دارند که بر هر ابزاری که تلاش کند آنها را انجام دهد اثر میگذارد؛ بیشترین کاری که هر ابزاری میتواند انجام دهد این است که هنگام شناسایی یک مشکل، تلاش کند به کاربر اطلاع دهد:
مشکلات
ویژه filter-repo
نکاتی
درباره
برگشتپذیری
برخی افراد به برگشتپذیری یک بازنویسی علاقهمند هستند؛ به عنوان مثال بازنویسی تاریخچه، احتمالاً افزودن چند کامیت، سپس لغو بازنویسی و بازگرداندن تاریخچه اصلی به همراه چند کامیت جدید «بازنویسینشده». بدیهی است در صورتی که بازنویسی شما شامل دور ریختن اطلاعات باشد (مانند فیلتر کردن و حذف فایلها یا جایگزینی چندین رشته متفاوت با ***REMOVED***)، این امر غیرممکن است، اما ممکن است با برخی بازنویسیها امکانپذیر باشد. ابزار filter-repo احتمالاً به چند دلیل برای این نوع از گردش کار مناسب نخواهد بود:
همچنین ببینید (SEE ALSO)
گیت (GIT)
بخشی از مجموعه git(1)
یادداشتها (NOTES)
- 1.
- مستندات گیتلب (GitLab) درباره کاهش حجم مخزن
- 2.
- بخش "Fully removing the data from GitHub" از مستندات گیتهاب (GitHub)
| 2024-11-27 | Git 2.47.1.2.g787e9d71 |