Конфигурацията
Да кажем, че това е историята около ангажимента, който искате да премахнете
... o - o - o - o ... ... o
^ ^ ^ ^
| | +- next |
| +- bad +-- master (HEAD)
start
където:
bad
е ангажиментът, който искате да премахнете;
start
е родителят на ангажимента, който искате да премахнете;
next
е следващият комит след bad
; добър е, искате да го запазите и цялата времева линия след него; той ще замени bad
след пребазиране.
Предпоставки
За да можете безопасно да премахнете bad
, важно е никой друг клон, съществуващ по времето, когато е създаден bad
, да не е обединен в основната времева линия след bad
. т.е. като премахнете bad
и връзките му с неговите родителски и дъщерни ангажименти от графиката на историята, вие получавате две несвързани части от времевата линия.
Вероятно е възможно да се премахне bad
дори ако друг съществуващ клон е бил обединен след bad
. Не проверих тази ситуация, но очаквам някои пречки поради ангажимента за сливане.
Идеята
Всеки git
ангажимент се идентифицира чрез хеш, който се изчислява с помощта на свойствата на ангажимента: съдържание, съобщение, автор и дата на комитър и имейл.
Повторното базиране винаги променя датата на комитър. Може също да промени имейла на комитера, съобщението и съдържанието на комита.
За да възстановим оригиналните дати на комитър след пребазиране, трябва да ги запазим заедно с малко информация, която може да идентифицира всеки комит след пребазирането.
Тъй като искате да модифицирате ангажимент, съдържанието на ангажимента се променя по време на повторното базиране. Добавянето или премахването на файлове или ангажименти променя съдържанието на всички бъдещи ангажименти.
Това ни оставя без свойство, което уникално идентифицира ангажиментите и не се променя по време на желаното пребазиране. Можем да опитаме да използваме две или повече свойства, които не се променят по време на повторното базиране.
Имейлите (автор и комитер) не са от почти никаква полза. Ако има един човек, който е работил по проекта, те са еднакви за всички ангажименти и не могат да се използват. Свойствата, които остават (различни са при повечето ангажименти, не се влияят от повторното базиране) са дата на автора и съобщение за ангажимент (първият ред).
Ако двойката (дата на автор, съобщение за ангажимент) предоставя уникални стойности за всички ангажименти, засегнати от повторното базиране, тогава можем да възстановим датите на ангажимент след това без грешки.
Проверете дали може да се направи безопасно
Има лесен начин да проверите дали двойките (дата на автор, съобщение за ангажимент) са уникални за засегнатите комити.
Изпълнете следните две команди:
$ git log --format="%aI %s" start...master | uniq | wc -l
$ git log --oneline start...master | wc -l
Ако показват същия номер, значи имате късмет: двойката (дата на автор, съобщение за ангажиране) може да се използва за уникално идентифициране на ангажиранията. Прочетете.
Ако числата са различни (първата команда винаги ще генерира число, по-малко или равно на това, получено от втората команда), тогава нямате късмет.
Извлечете информацията, необходима за коригиране на датите на ангажиране след повторното базиране
Тази команда
$ git log --format="%H %cI %aI %s" start...master > /tmp/hashlist
извлича хеша на комита, датата на комита (полезния товар), датата на автора и съобщението за комит (ключа) за всички комити, започващи с start
, и ги съхранява във файл.
Архивирайте текущия мастер
Въпреки че е често срещано погрешно схващане, че git
"пренаписва историята", всъщност той просто генерира алтернативна линия на историята и решава, че това е правилната история. Не променя или премахва "пренаписаните" ангажименти; те все още присъстват известно време в неговата база данни и могат да бъдат възстановени в случай, че операцията е неуспешна.
Можем проактивно да архивираме текущия ред на историята, за да го възстановим лесно, ако е необходимо. Всичко, което трябва да направим, е да създадем нов клон, който сочи към master
. По този начин, когато git rebase
премести master
към новата времева линия, старата все още е достъпна с помощта на новия клон.
$ git branch old_master
Командата по-горе създава клон с име old_master
, който поддържа текущата времева линия на фокус, докато не завършим всички промени и не сме доволни от новия световен ред.
Направете повторното базиране
Премахването на ангажимента bad
от хронологията е толкова просто, колкото:
$ git rebase --preserve-merges --onto start bad
Коригирайте датите на ангажименти
Следващата команда "пренаписва" историята и променя датата на комитър, използвайки стойностите, които сме записали преди:
$ git filter-branch --env-filter 'export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)' -f start...master
Как работи:
git
обикаля историята между ангажиментите, обозначени с start
и master
, и за всеки комит изпълнява командата, предоставена като аргумент на --env-filter
, преди да пренапише комита. Той задава променливата на средата GIT_COMMIT
с хеша на пренаписания комит.
Тъй като вече направихме rebase
, който модифицира хешовете на всички ангажименти, не можем да използваме $GIT_COMMIT
директно, за да идентифицираме оригиналната дата на ангажимент на комита (защото $GIT_COMMIT
е комит, генериран от git rebase
и ние не се интересуваме от техните дати на комит).
Командата, която предоставяме на --env-filter
export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)
изпълнява git log -1 --format="%aI %s" $GIT_COMMIT
за генериране на двойката ключове (дата на автора, съобщение за ангажиране), обсъдена по-горе. Неговият изход се предава като аргумент на командата fgrep -m 1 "..." /tmp/hashlist | cut -d" " -f2
, която намира двойката в списъка с предварително запазени хешове (fgrep
) и извлича оригиналната дата на ангажимент от запазения ред (cut
). И накрая, стойността на датата на ангажимент се съхранява в променливата на средата GIT_COMMITTER_DATE
, която се използва от git
за пренаписване на комита.
Проверка
Отново използване на командата git log
$ git log --format="%cI %aI %s" start...master
можете да проверите дали пренаписаната история съвпада с оригиналната история. Ако използвате графичен git
клиент, можете да проверите резултатите по-лесно чрез визуална проверка. Клонът old_master
поддържа старата линия на историята видима в клиента и можете лесно да сравните датите на всеки ангажимент на old_master
клон със съответния от master
клон.
Ако нещо не върви добре или трябва да промените процедурата, можете лесно да започнете отначало, като:
$ git reset --hard old_master
Почисти
Когато сте доволни от резултата, можете да премахнете архивния клон и файла, използван за съхраняване на оригиналните дати на ангажимент:
$ git branch -D old_master
$ rm /tmp/hashlist
Това е всичко!
person
axiac
schedule
13.06.2015
--committer-date-is-author-date
- person Vogel612   schedule 11.06.2015