Как изменить указанный коммит?

Обычно я представляю список коммитов для проверки. Если у меня есть следующие коммиты:

  1. HEAD
  2. Commit3
  3. Commit2
  4. Commit1

... Я знаю, что могу модифицировать head commit с помощью git commit --amend. Но как я могу изменить Commit1, учитывая, что это не HEAD commit?

Ответ 1

Вы можете использовать git rebase. Например, если вы хотите изменить коммит bbc643cd, запустите

$ git rebase --interactive 'bbc643cd^'

Обратите внимание на каретку ^ в конце команды, потому что на самом деле вам нужно вернуть обратно коммит перед тем, который вы хотите изменить.

В редакторе по умолчанию измените pick на edit в строке с упоминанием "bbc643cd".

Сохраните файл и выйдите: git будет интерпретировать и автоматически выполнять команды в файле. Вы окажетесь в предыдущей ситуации, в которой вы только что создали коммит bbc643cd.

На этом этапе bbc643cd является вашим последним коммитом, и вы можете легко изменить его: внести изменения, а затем зафиксировать их командой:

$ git commit --all --amend --no-edit

После этого введите:

$ git rebase --continue

чтобы вернуться к предыдущему коммиту HEAD.

ПРЕДУПРЕЖДЕНИЕ: обратите внимание, что это изменит SHA-1 этого коммита , а также всех дочерних элементов - другими словами, это переписывает историю с этого момента. Вы можете разорвать репо, выполнив это, если нажмете с помощью команды git push --force

Ответ 2

Используйте потрясающую интерактивную rebase:

git rebase -i @~9   # Show the last 9 commits in a text editor

Найдите требуемую фиксацию, измените pick на e (edit) и сохраните и закройте файл. Git перемотает на эту фиксацию, что позволит вам:

  • используйте git commit --amend для внесения изменений или
  • используйте git reset @~, чтобы отменить последнюю фиксацию, но не изменения в файлах (т.е. дойдя до точки, в которой вы были, когда вы редактировали файлы, но еще не зафиксировали ее).

Последний полезен для выполнения более сложных вещей, таких как разделение на несколько коммитов.

Затем запустите git rebase --continue, а Git воспроизведет последующие изменения поверх измененного коммита. Вас могут попросить исправить некоторые конфликты слияния.

Примечание: @ является сокращением для HEAD, а ~ является фиксацией до указанного фиксации.

Подробнее о переписывании истории в документах Git.


Не бойтесь переустанавливать

ProTip ™: не бойтесь экспериментировать с "опасными" командами, которые переписывают историю * - Git не удаляет ваши фиксации в течение 90 дней по умолчанию; вы можете найти их в reflog:

$ git reset @~3   # go back 3 commits
$ git reflog
c4f708b [email protected]{0}: reset: moving to @~3
2c52489 [email protected]{1}: commit: more changes
4a5246d [email protected]{2}: commit: make important changes
e8571e4 [email protected]{3}: commit: make some changes
... earlier commits ...
$ git reset 2c52489
... and you're back where you started

* Остерегайтесь опций, таких как --hard и --force, хотя - они могут отбрасывать данные.
* Также не переписывайте историю в каких-либо ветких, с которыми вы сотрудничаете.



Во многих системах git rebase -i откроет Vim по умолчанию. Vim работает не так, как большинство современных текстовых редакторов, поэтому посмотрите как переустановить с помощью Vim. Если вы предпочитаете использовать другой редактор, измените его на git config --global core.editor your-favorite-text-editor.

Ответ 3

Интерактивный rebase с --autosquash - это то, что я часто использую, когда мне нужно исправлять предыдущие, фиксирует глубже в истории. Это существенно ускоряет процесс, который иллюстрирует ответ ZelluX, и особенно удобен, когда у вас есть несколько коммитов, которые нужно редактировать.

Из документации:

--autosquash

Когда сообщение журнала фиксации начинается с "squash!..." (или "fixup!..." ), и есть коммит, чей заголовок начинается с того же..., автоматически изменяет список todo reba -i так что фиксация, помеченная для раздачи, появляется сразу после модификации фиксации

Предположим, что у вас есть история, которая выглядит так:

$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1

и у вас есть изменения, которые вы хотите внести в Commit2, затем выполните свои изменения, используя

$ git commit -m "fixup! Commit2"

альтернативно вы можете использовать commit-sha вместо сообщения фиксации, поэтому "fixup! e8adec4 или даже просто префикс сообщения фиксации.

Затем инициируйте интерактивную перезагрузку для фиксации до

$ git rebase e8adec4^ -i --autosquash

ваш редактор откроется с правильно заказанными коммитами

pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3

все, что вам нужно сделать, это сохранить и выйти

Ответ 4

Run:

$ git rebase --interactive commit_hash^

каждый ^ указывает, сколько коммандов вы хотите отредактировать, если это только один (хеш фиксации, который вы указали), то вы просто добавляете один ^.

Используя Vim, вы изменяете слова pick на reword для коммитов, которые вы хотите изменить, сохранить и выйти (:wq). Затем git предложит вам каждую фиксацию, которую вы пометили как переименование, чтобы вы могли изменить сообщение фиксации.

Каждое сообщение фиксации, которое вы должны сохранить и выйти (:wq), перейти к следующему сообщению фиксации

Если вы хотите выйти без применения изменений, нажмите :q!

EDIT: для перехода в vim используйте j для перехода вверх, k, чтобы пойти вниз, h, чтобы идти влево, и l, чтобы перейти вправо (все в режиме NORMAL нажмите ESC, чтобы перейти в режим NORMAL). Чтобы отредактировать текст, нажмите i, чтобы войти в режим INSERT, где вы вставляете текст. Нажмите ESC, чтобы вернуться в режим NORMAL:)

UPDATE. Здесь отличная ссылка из списка github Как отменить (почти) что-нибудь с git

Ответ 5

Если по какой-то причине вам не нравятся интерактивные редакторы, вы можете использовать git rebase --onto.

Предположим, вы хотите изменить Commit1. Сначала ветвь от Commit1:

git checkout -b amending [commit before Commit1]

Во-вторых, возьмите Commit1 с помощью cherry-pick:

git cherry-pick Commit1

Теперь измените свои изменения, создав Commit1':

git add ...
git commit --amend -m "new message for Commit1"

И, наконец, после того, как вы спрятали любые другие изменения, пересаживайте остальную часть своих коммитов до master поверх вашего new commit:

git rebase --onto amending Commit1 master

Читайте: "rebase, на ветку amending, все совершает транзакции между Commit1 (не включительно) и master (включительно)". То есть Commit2 и Commit3 полностью разрезают старый Commit1. Вы могли бы просто выбрать вишню, но это проще.

Не забудьте очистить ветки!

git branch -d amending

Ответ 6

Полностью неинтерактивная команда (1)

Я просто подумал, что у меня есть псевдоним, который я использую для этого. Он основан на неинтерактивной интерактивной перестановке. Чтобы добавить его в свой git, запустите эту команду (пояснение ниже):

git config --global alias.amend-to '!f() { SHA='git rev-parse "$1"'; git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"; }; f'

Самым большим преимуществом этой команды является то, что она не-vim.


(1), учитывая, что во время rebase конфликтов нет, конечно

использование

git amend-to <REV> # e.g.
git amend-to HEAD~1
git amend-to aaaa1111

Имя amend-to кажется подходящим ИМХО. Сравните поток с --amend:

git add . && git commit --amend --no-edit
# vs
git add . && git amend-to <REV>

объяснение

  • git config --global alias.<NAME> '!<COMMAND>' - создает глобальный псевдоним git с именем <NAME> который будет выполнять команду non-git <COMMAND>
  • f() { <BODY> }; f f() { <BODY> }; f - "анонимная" функция bash.
  • SHA='git rev-parse "$1"'; - преобразует аргумент в git-ревизию и присваивает результат переменной SHA
  • git commit --fixup "$SHA" - фиксация-фиксация для SHA. См. git-commit docs
  • GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
    • git rebase --interactive "$SHA^" часть была покрыта другими ответами.
    • --autosquash используется совместно с git commit --fixup, см. Документы git-rebase для получения дополнительной информации
    • GIT_SEQUENCE_EDITOR=true - это то, что делает все это не интерактивным. Этот взлом я узнал из этого сообщения в блоге.

Ответ 7

Основываясь на документации

Изменение сообщения старых или нескольких сообщений фиксации

git rebase -i HEAD~3 

Приведенное выше отображает список последних 3 коммитов в текущей ветке, замените 3 на что-то еще, если вы хотите больше. Список будет выглядеть примерно так:

pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

Заменить выбор с помощью повторного использования перед каждым сообщением фиксации, которое вы хотите изменить. Скажем, вы изменили вторую фиксацию в списке, ваш файл будет выглядеть следующим образом:

pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

Сохраните и закройте файл списка фиксации, появится новый редактор для изменения вашего сообщения фиксации, изменения сообщения фиксации и сохранения.

Завершить принудительное принудительное изменение.

git push --force

Ответ 8

Пришел к этому подходу (и это, вероятно, точно так же, как с использованием интерактивной rebase), но для меня это довольно просто.

Примечание. Я представляю этот подход для иллюстрации того, что вы можете сделать, а не повседневной альтернативы. Поскольку он имеет много шагов (и, возможно, некоторые оговорки.)

Предположим, вы хотите изменить commit 0, и вы в настоящее время находитесь на feature-branch

some-commit---0---1---2---(feature-branch)HEAD

Ознакомьтесь с этой фиксацией и создайте quick-branch. Вы также можете клонировать свою ветку функций как точку восстановления (перед запуском).

?(git checkout -b feature-branch-backup)
git checkout 0
git checkout -b quick-branch

Теперь у вас будет что-то вроде этого:

0(quick-branch)HEAD---1---2---(feature-branch)

Изменения в сцене, запишите все остальное.

git add ./example.txt
git stash

Заменить изменения и возврат обратно на feature-branch

git commit --amend
git checkout feature-branch

Теперь у вас будет что-то вроде этого:

some-commit---0---1---2---(feature-branch)HEAD
           \
             ---0'(quick-branch)

Rebase feature-branch на quick-branch (разрешить любые конфликты на этом пути). Примените stash и удалите quick-branch.

git rebase quick-branch
git stash pop
git branch -D quick-branch

И вы получите:

some-commit---0'---1'---2'---HEAD(feature-branch)

Git не будет дублировать (хотя я не могу сказать, в какой степени) 0 совершает при перезагрузке.

Примечание: все хеши фиксации изменены, начиная с фиксации, которую мы изначально планировали изменить.

Ответ 9

Автоматическое редактирование интерактивной ребазы, за которым следует фиксация готовности, готовая к выполнению

Я обнаружил, что фиксирую прошлую фиксацию достаточно часто, чтобы написать для нее сценарий.

Здесь рабочий процесс:

  1. git commit-edit <commit-hash>
    

    Это выведет вас на фиксацию, которую вы хотите отредактировать.

  2. Исправьте и выполните фиксацию, как вы хотели, в первую очередь.

    (Вы можете использовать git stash save чтобы сохранить файлы, которые вы не совершаете)

  3. Повторите фиксацию с помощью --amend, например:

    git commit --amend
    
  4. Заполните rebase:

    git rebase --continue
    

Чтобы выше работали, поместите приведенный ниже сценарий в исполняемый файл с именем git-commit-edit где git-commit-edit нибудь в вашем $PATH:

#!/bin/bash

set -euo pipefail

script_name=${0##*/}

warn () { printf '%s: %s\n' "$script_name" "$*" >&2; }
die () { warn "[email protected]"; exit 1; }

[[ $# -ge 2 ]] && die "Expected single commit to edit. Defaults to HEAD~"

# Default to editing the parent of the most recent commit
# The most recent commit can be edited with 'git commit --amend'
commit=$(git rev-parse --short "${1:-HEAD~}")
message=$(git log -1 --format='%h %s' "$commit")

if [[ $OSTYPE =~ ^darwin ]]; then
  sed_inplace=(sed -Ei "")
else
  sed_inplace=(sed -Ei)
fi

export GIT_SEQUENCE_EDITOR="${sed_inplace[*]} "' "s/^pick ('"$commit"' .*)/edit \\1/"'
git rebase --quiet --interactive --autostash --autosquash "$commit"~
git reset --quiet @~ "$(git rev-parse --show-toplevel)"  # Reset the cache of the toplevel directory to the previous commit
git commit --quiet --amend --no-edit --allow-empty  #  Commit an empty commit so that that cache diffs are un-reversed

echo
echo "Editing commit: $message" >&2
echo

Ответ 10

Чтобы получить неинтерактивную команду, поместите скрипт с этим контентом в свой PATH:

#!/bin/sh
#
# git-fixup
# Use staged changes to modify a specified commit
set -e
cmt=$(git rev-parse $1)
git commit --fixup="$cmt"
GIT_EDITOR=true git rebase -i --autosquash "$cmt~1"

Используйте его, разместив свои изменения (с git add), а затем запустите git fixup <commit-to-modify>. Конечно, он будет интерактивным, если вы столкнетесь с конфликтами.

Ответ 11

Я решил это,

1) путем создания новой фиксации с изменениями, которые я хочу.

r8gs4r commit 0

2) Я знаю, какой фиксации мне нужно объединить с ним. который является фиксацией 3.

поэтому, git rebase -i HEAD~4 # 4 представляет недавний 4 фиксации (здесь commit 3 находится на 4-м месте)

3) в интерактивной ребазе недавняя фиксация будет расположена внизу. он будет выглядеть одинаково,

pick q6ade6 commit 3
pick vr43de commit 2
pick ac123d commit 1
pick r8gs4r commit 0

4) здесь нам нужно переставить фиксацию, если вы хотите объединиться с конкретным. это должно быть так,

parent
|_child

pick q6ade6 commit 3
f r8gs4r commit 0
pick vr43de commit 2
pick ac123d commit 1

после перестановки вам нужно заменить p pick на f (fixup будет сливаться без сообщения фиксации) или s (слияние squash с сообщением фиксации может измениться во время выполнения)

а затем сохраните свое дерево.

теперь слияние выполняется с существующей фиксацией.

Примечание. Это не предпочтительный метод, если вы не поддерживаете его самостоятельно. если у вас большой размер команды, его не приемлемый метод для перезаписи дерева git закончится конфликтами, которые, как вы знаете, будут другими. если вы хотите сохранить свое дерево чистым с меньшим количеством коммитов, можете попробовать это, и если его небольшая команда в противном случае не будет предпочтительнее.....

Ответ 12

git stash + rebase автоматизация

Когда мне нужно много раз модифицировать старый коммит для обзоров Геррита, я делаю:

git-amend-old() (
  # Stash, apply to past commit, and rebase the current branch on to of the result.
  current_branch="$(git rev-parse --abbrev-ref HEAD)"
  apply_to="$1"
  git stash
  git checkout "$apply_to"
  git stash apply
  git add -u
  git commit --amend --no-edit
  new_sha="$(git log --format="%H" -n 1)"
  git checkout "$current_branch"
  git rebase --onto "$new_sha" "$apply_to"
)

GitHub вверх по течению.

Использование:

  • изменить файл
  • git-amend-old $old_sha

Мне нравится это над --autosquash поскольку оно не раздавливает другие несвязанные исправления.

Ответ 13

Для меня это было для удаления некоторых учетных данных из репо. Я попытался переустановить и столкнулся с тонкой, казалось бы, несвязанными конфликтами на пути, когда пытался переустановить, - продолжайте. Не утруждайте себя попыткой перезагрузить себя, используйте инструмент под названием BFG (brew install bfg) на mac.

Ответ 14

Если вы еще не выдвинули коммиты, вы можете вернуться к предыдущему коммиту, используя git reset HEAD^[1,2,3,4...]

Например

git commit <file1> -m "Updated files 1 and 2"
git commit <file3> -m "Updated file 3"

Ой, забыл добавить file2 в первый коммит...

git reset HEAD^1 // because I only need to go back 1 commit

git add <file2>

Это добавит file2 к первому коммиту.

Ответ 15

Ну, это решение может показаться очень глупым, но может спасти вас в определенных условиях.

Мой друг только что натолкнулся на случайную фиксацию очень больших файлов (четыре автоматически сгенерированных файла размером от 3 до 5 ГБ каждый), а затем сделал еще несколько коммитов кода, прежде чем понял, что git push не работает !

Файлы были перечислены в .gitignore но после переименования папки контейнера они были открыты и зафиксированы! И теперь было еще несколько коммитов кода поверх этого, но push работал вечно (пытаясь загрузить ГБ данных!) И в конечном итоге потерпел неудачу из-за ограничений размера файла Github.

Проблема с интерактивным перебазированием или чем-то подобным заключалась в том, что им приходилось ковыряться в этих огромных файлах, и им потребовалось бы целую вечность, чтобы что-то сделать. Тем не менее, потратив почти час в CLI, мы не были уверены, действительно ли файлы (и дельты) удалены из истории или просто не включены в текущие коммиты. Толчок тоже не работал, и мой друг действительно застрял.

Итак, решение, которое я придумал, было:

  1. Переименуйте текущую папку git в ~/Project-old.
  2. Снова клонируйте папку git из github (в ~/Project).
  3. Оформить заказ на ту же ветку.
  4. Вручную cp -r файлы из ~/Project-old folder в ~/Project.
  5. Убедитесь в том, что массивные файлы, которые не должны были быть проверены в это mv - е изд, а также включены в .gitignore правильно.
  6. Также убедитесь, что вы не перезаписываете папку .git в недавно клонированном ~/Project старой. Это где журналы проблемной истории живет!
  7. Теперь просмотрите изменения. Это должен быть союз всех последних коммитов, исключая проблемные файлы.
  8. Наконец, внесите изменения, и это хорошо, когда вас push.

Самая большая проблема этого решения заключается в том, что оно имеет дело с ручным копированием некоторых файлов, а также объединяет все последние коммиты в один (очевидно, с новым хешем коммитов.) B

Большим преимуществом является то, что он очень понятен на каждом этапе, отлично работает с огромными файлами (а также с чувствительными) и не оставляет следов в истории!