Как да направя git rebase и да запазя времевия печат на ангажимента?

Искам да направя повторно базиране, за да премахна определен ангажимент от моята история. Знам как да го направя. Ако обаче го направя, клеймото за време на ангажимент се задава на момента, в който завърших повторното базиране. Искам ангажиментите да запазят времевия печат.

Видях последния отговор тук: https://stackoverflow.com/a/19522951/3995351 , но не работа.

Последната важна команда току-що показа нов ред с

>

Затова отварям нов въпрос.


person TheWatcher    schedule 11.06.2015    source източник
comment
не работи, отговор ... моля, свържете се с отговора и може би обяснете малко по-задълбочено..   -  person Vogel612    schedule 11.06.2015
comment
FWIW, датата на автора трябва да се запази по подразбиране. Променя се само датата на комит.   -  person David Deutsch    schedule 11.06.2015
comment
Опитахте ли и други отговори от този въпрос?? дайте ни повече информация   -  person Vogel612    schedule 11.06.2015
comment
@Vogel612 проблемът е, че въпросът беше за датата на автора. Това, което искам да не се променя, е датата на ангажимент. Отговорът, който имам предвид, е единственият, който отговаря на нуждите ми   -  person TheWatcher    schedule 11.06.2015
comment
@DavidDeutsch да и искам датата на ангажимент да не се променя   -  person TheWatcher    schedule 11.06.2015
comment
Вашият проблем (както го описвате) е решен в този отговор на същия въпрос... опитахте ли това в някои sandbox repo?   -  person Vogel612    schedule 11.06.2015
comment
възможен дубликат на git rebase без промяна на времевите клейма на ангажиране   -  person Vogel612    schedule 11.06.2015
comment
@Vogel612 може би дубликат, но използването на датата на автора като дата на ангажиране не е решение за мен, искам старите дати на ангажиране да бъдат запазени   -  person TheWatcher    schedule 11.06.2015
comment
старите дати на ангажимент са датите на автора, като се има предвид, че вече не сте извършили повторно базиране, променяйки времевите клейма на ангажимента, защото не сте използвали --committer-date-is-author-date   -  person Vogel612    schedule 11.06.2015
comment
@Vogel612 Не, имам много неща, избрани от череши в хранилището, така че датите на ангажиране може да са много различни от датите на автора   -  person TheWatcher    schedule 11.06.2015
comment
Вижте тази статия: axiac.ro/blog/2014/11/merging-git -repositories Частта, от която се нуждаете, е обяснена в разделите за архивиране и възстановяване на датите на комитър. Отговорът, който посочвате, беше много полезно начало, но за съжаление командата, обяснена там, е неправилна (непълна?), както вече разбрахте.   -  person axiac    schedule 12.06.2015
comment
@axiac току-що опита това, може би съм много зле.. Не работи за мен :( Можете ли да ми дадете стъпка по стъпка? :D Мисля, че нямам нужда от всички неща, направени там   -  person TheWatcher    schedule 12.06.2015
comment
Сега препрочетох въпроса ви. Всъщност процедурата, обяснена в тази статия, не работи във вашия случай. За мен проработи, защото не промених съдържанието на пребазирани ангажименти. Ситуацията е различна, когато искате да премахнете ангажимент. Проблемът е трудността (или невъзможността) да се намери част от информацията, която уникално идентифицира всеки ангажимент преди и след повторното базиране.   -  person axiac    schedule 12.06.2015
comment
@axiac може би можем по някакъв начин да автоматизираме отговора от Дейвид Дойч. Наистина не бих искал да правя това едно по едно.. :D   -  person TheWatcher    schedule 12.06.2015
comment
Написах отговор, който предоставя целия работен процес, необходим за премахване на ангажимент от хронологията и възстановяване на оригиналните дати на ангажимент. Тя има за цел да направи процедурата възможно най-безопасна. Надявам се, че може да се приложи без проблеми във вашия случай.   -  person axiac    schedule 13.06.2015


Отговори (2)


Конфигурацията

Да кажем, че това е историята около ангажимента, който искате да премахнете

... 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
comment
Благодаря ти много! Изглежда обещаващо :) ще опитам това днес. - person TheWatcher; 14.06.2015
comment
Дава ми грешка при първата команда: git log --format %aI %s c318373c22214f91fb189619e093be298aa6c0e0...lollipop | уникален | wc -l фатален: двусмислен аргумент '%aI %s': неизвестна ревизия или път не е в работното дърво. Използвайте '--', за да отделите пътищата от ревизиите, като това: 'git ‹command› [‹revision›...] -- [‹file›...]' - person TheWatcher; 14.06.2015
comment
Липсваше знак за равенство (=). Правилната команда е git log --format="%aI %s" .... Актуализира отговора. - person axiac; 14.06.2015
comment
Хей, всичко работи, но при последната реална стъпка се казва: (1/219)фатално: невалиден формат на датата: %cНе можах да напиша пренаписан ангажимент - person TheWatcher; 17.06.2015
comment
Тук ли си? Знаете ли какъв може да е проблемът? - person TheWatcher; 20.06.2015
comment
Какво е точното съобщение за грешка и коя команда го генерира? Предишният ви коментар е двусмислен: %cI се използва с git log, но частта не можа да напише пренаписан ангажимент изглежда като грешка, произведена от git filter-branch (и тази команда, която не използва %cI). - person axiac; 20.06.2015
comment
Страхотно, много ти благодаря! Едно малко нещо, на което току-що се натъкнах: Изглежда винаги трябва да използвате sort, преди да използвате uniq. Минимален пример: printf "hello\nworld\nhello\nworld" | uniq връща четири реда, докато printf "hello\nworld\nhello\nworld" | sort | uniq връща два (както се очаква). Или директно използвайте sort -u (или sort --unique), което прави прехвърлянето към uniq остаряло. - person msa; 16.05.2019
comment
Не знам защо, но продължавам да получавам grep: ./commit-list: No such file or directory, моят хеш списък се казва commit-list и се намира в същата директория (първоначално го поставих в родителската директория, но ../commit-list имаше същия проблем). - person Zarepheth; 20.12.2019
comment
В крайна сметка замених относителния път с абсолютния от корена /c/.../commit-list и проработи. :стр - person Zarepheth; 20.12.2019
comment
Току-що опитах това при пребазиране върху друг клон със сливане, което се случи след началния ми комит. Командата git filter-branch нулира всички дати на ангажиране между моята начална точка и сливането до текущата дата и час -- може би защото не съм включил този клон във файла hashlist. - person Zarepheth; 08.01.2020

И така, ето един досаден начин да го направите (в зависимост от това колко ангажименти трябва да пребазирате), но аз го изпробвах и работи. Когато правите интерактивно повторно базиране, маркирайте всеки комит с "e", за да можете да го редактирате. Това ще доведе до пауза на git след всеки комит. При всяка пауза можете да посочите коя дата да използвате и да продължите към следващия комит с:

GIT_COMMITTER_DATE="Wed Feb 16 14:00 2011 +0100" git commit --amend   
git rebase --continue

Това, разбира се, е голяма болка в задната част и трябва да знаете всички дати на ангажименти предварително, но ако не можете да го направите по друг начин, поне трябва да работи.

person David Deutsch    schedule 11.06.2015
comment
Хм, имам около 300 ангажимента... Така че трябва да е много трудно да се направи това ръка за ръка, но може би можем да автоматизираме този набор от команди? - person TheWatcher; 11.06.2015
comment
Уау, 300 определено са твърде много за моя метод, освен ако нямате много време. Единственото друго нещо, което мога да предложа, е да погледнете последния отговор на този въпрос . - person David Deutsch; 11.06.2015
comment
А, съжалявам за това. - person David Deutsch; 12.06.2015
comment
може би бихте могли да ми помогнете да разреша проблема или този отговор, който не успява за мен.. - person TheWatcher; 12.06.2015