Как отправить исправленный коммит в удаленный репозиторий Git?

663

Когда я немного поработал с исходным кодом, я сделал свою обычную фиксацию, а затем отправил в удаленный репозиторий. Но потом я заметил, что забыл организовать импорт в исходном коде. Поэтому я делаю команду исправления, чтобы заменить предыдущий коммит:

> git commit --amend

К сожалению, фиксация не может быть перенесена обратно в хранилище. Это отклонено как это:

> git push origin
To //my.remote.repo.com/stuff.git/
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to '//my.remote.repo.com/stuff.git/'

Что мне делать? (Я могу получить доступ к удаленному хранилищу.)

Spoike
источник
Что если мой --amend должен был только изменить сообщение коммита? Можно ли как-нибудь отредактировать последнее сообщение коммита, если оно уже было удалено? Я сделал это на Github и получил то же сообщение о не быстрой перемотке вперед. Затем я применил решение ниже, но слияние только что добавило больше сообщений о
7
@faB: я думаю, что это часто задаваемые вопросы. Сообщение коммита хешируется вместе с коммитом, поэтому изменение его изменяет revid (хэш). Если это не ясно: нет, вы не можете. IIRC может хранить внеполосную информацию в заметках (так что вы можете аннотировать существующие коммиты, не изменяя их). Чтобы пометить конкретные коммиты, используйте теги
sehe
1
Вы скоро (git1.8.5, Q4 2013) сможете сделать git push -forceболее осторожно .
VonC
3
Вот ковбойский стиль. Не изучайте дальше или не ищите способы отменить предыдущую git-поправку. Просто добавьте некоторый код-заполнитель, я имею в виду, Добавьте некоторый комментарий, Очистите немного кода или просто добавьте несколько тире-тире-тире .... Теперь сделайте реальный коммит и отправьте его на удаленное устройство. Выполнено !
Нехем
@ user58777 Если ваш -amend был предназначен только для изменения сообщения о коммите, и с тех пор вы не делали никаких дополнительных локальных коммитов, вы можете сбросить локальную ветвь на удаленный коммит, который вы нажали до внесения изменений в сообщение о коммите.
Скотт Ахтен

Ответы:

504

Я на самом деле однажды нажал --forceи .gitхранилище и был отчитан Линусом BIG TIME . В целом это создаст много проблем для других людей. Простой ответ: «Не делай этого».

Я вижу, что другие дали рецепт для этого в любом случае, поэтому я не буду повторять их здесь. Но вот совет для выхода из ситуации после того, как вы вытолкнули исправленный коммит с помощью --force (или + master).

  1. Используйте, git reflogчтобы найти старый коммит, в который вы внесли изменения (позвоните ему old, и мы назовем новый коммит, который вы создали путем внесения изменений new).
  2. Создать слияние между oldи new, записывая дерево new, как git checkout new && git merge -s ours old.
  3. Объедините это с вашим мастером с git merge master
  4. Обновите ваш мастер с результатом с git push . HEAD:master
  5. Выдвиньте результат.

Тогда люди , которые были неудачными достаточно , чтобы основывают свою работу на коммит вы стерты изменения и заставляя толчок будет увидеть в результате слияния будет видеть , что вы предпочитаете newболее old. Их последующие слияния не будут видеть конфликты между вами oldи их newрезультатом, поэтому им не придется страдать.

Жоао Пиментел Феррейра
источник
17
Я очень хорошо знаю, что происходит, когда вы заставляете толкать исправленный коммит (уничтожая историю). К счастью, я был единственным разработчиком проекта с удаленным репо на сетевом диске, так что это было не так уж сложно. Я никогда не думал о слиянии коммитов с поправками, поэтому буду голосовать за это.
Спойк
62
В нашей компании мы довольно регулярно продвигаем усилия ... в специализированных отраслях, разработанных частными лицами.
Ондра Жижка
2
Выговор Линуса был вызван тем, что вы стерли историю с помощью параметра силы, а не потому, что вы не должны этого делать. Решение GabrielleV работает отлично, потому что оно не меняет историю.
user411279
2
Пожалуйста, поскольку автор (gitster) этого ответа, похоже, больше не существует, может кто-нибудь помочь прояснить пункт № 1: найти старый коммит. Если у вас нет резервной копии, где бы вы ее нашли? Поправка и силовой толчок не уничтожили бы его? Может быть, он имеет в виду, чтобы получить его от друга / соавтора, у которого все еще есть это в дереве?
Доктор Беко
2
Доктор Бреко, вы можете использовать, git reflogчтобы найти его
Саймон Зикс
269

Вы видите функцию безопасности Git. Git отказывается обновлять удаленную ветку вашей веткой, потому что основная фиксация вашей ветви не является прямым потомком текущей главной ветки ветки, в которую вы отправляете ветку.

Если бы это было не так, то два человека, отправившие в один и тот же репозиторий примерно в одно и то же время, не знали бы, что одновременно поступает новый коммит, и тот, кто нажал последний, потеряет работу предыдущего толкателя без какого-либо из них. они понимают это.

Если вы знаете, что вы единственный, кто нажимает, и вы хотите отправить исправленный коммит или выдать коммит, который свернет ветку, вы можете «заставить» Git обновить удаленную ветку, используя -f переключателя.

git push -f origin master

Даже это может не сработать, так как Git позволяет удаленным репозиториям отклонять небыстрые запросы на дальнем конце с помощью переменной конфигурации receive.denynonfastforwards. В этом случае причина отказа будет выглядеть следующим образом (обратите внимание на часть «удаленный отказ»):

 ! [remote rejected] master -> master (non-fast forward)

Чтобы обойти это, вам нужно либо изменить конфигурацию удаленного репозитория, либо в качестве грязного хака вы можете удалить и воссоздать ветку таким образом:

git push origin :master
git push origin master

Как правило, последний параметр, который git pushиспользует формат <local_ref>:<remote_ref>, где local_refэто имя ветви в локальном хранилище и remote_refимя ветви в удаленном хранилище. Эта пара команд использует два сокращения. :masterимеет нулевой local_ref, что означает перемещение нулевой ветви на удаленную сторону master, т.е. удаление удаленной ветви. Имя ветви, не :имеющей значения, отправляет локальную ветвь с заданным именем в удаленную ветвь с тем же именем. masterв этой ситуации мало master:master.

CB Bailey
источник
2
это не работает с GitHub, он дал мне следующее сообщение: [удаленный отклонен] master (удаление текущей ветки запрещено)
vedang
Я не хотел насильно толкать (что, как я знал, решит проблему), но теперь, думаю, у меня нет выбора.
Веданг
1
Это единственное решение, которое сработало для моего репо, размещенного на ассембле.
Джастин
1
удаление удаленной главной ветки освободит место в удаленном репо?
Mr_and_Mrs_D
1
@Mr_and_Mrs_D: Не сразу, но по истечении одного git gcраза истекли повторные журналы, старые объекты будут удалены. Никто, кто клонирует репозиторий, не получит никаких объектов, которые больше недоступны, как только ветвь будет обновлена.
CB Bailey
211

Быстрый разглагольствование: тот факт, что никто не разместил здесь простой ответ, демонстрирует отчаянную враждебность пользователей, проявляемую Git CLI.

В любом случае, «очевидный» способ сделать это, предполагая, что вы не пытались форсировать толчок, - это тянуть первым. Это тянет за собой изменения, которые вы внесли (и так больше не вносили), чтобы вы снова их получили.

Как только вы разрешили конфликты, вы можете нажать еще раз.

Так:

git pull

Если вы получаете ошибки в pull, возможно, что-то не так в вашей конфигурации локального репозитория (у меня был неправильный ref в разделе ветки .git / config).

И после

git push

Возможно, вы получите дополнительный коммит с темой, рассказывающей о «тривиальном слиянии».

GabrieleV
источник
2
Да, я писал об этом, см stackoverflow.com/questions/253055/... ;)
Spoike
10
Это действительно не работает, как я ожидал. Создает два новых коммита. Тот, который является копией старого, но с исправленными изменениями. И одно слияние совершить с пустым diff. Все еще оставляя старый коммит без изменений, выявляя, возможно, конфиденциальные данные, которые я пытался исправить. Я верю git push -fили git resetединственный путь сюда.
thnee
40
Технически отвечая на проблему, она не решает проблему. Как вы сказали, это создаст дополнительный коммит, но главная причина, по которой люди вносят изменения в коммит, - это избегать создания нового. Так что, если бы плакат следовал вашим инструкциям, он не получил бы желаемого результата. Не менее важно было бы не вносить изменения в коммит.
Дэн Джонс
102

Краткий ответ: не выдвигайте исправленные коммиты в публичное репо.

Длинный ответ: несколько команд Git, вроде git commit --amendиgit rebase , на самом деле переписывают граф истории. Это нормально до тех пор, пока вы не опубликовали свои изменения, но как только вы это сделаете, вам действительно не стоит копаться в истории, потому что, если кто-то уже получил ваши изменения, тогда, когда они попытаются повторить попытку, это может закончиться неудачей. , Вместо внесения изменений в коммит, вы должны просто сделать новый коммит с изменениями.

Однако, если вы действительно хотите добавить исправленный коммит, вы можете сделать это следующим образом:

$ git push origin +master:master

Ведущий +знак заставит произойти толчок, даже если он не приведет к фиксации «ускоренной перемотки вперед». (Ускоренная фиксация происходит, когда изменения, которые вы отправляете, являются прямым потомком изменений, уже находящихся в публичном репо.)

mipadi
источник
5
Чем это отличается (лучше или хуже) от git push -f? Спасибо!
Бентфорд
11
@bentford: Это в основном то же самое, что и git push -f.
Mipadi
54

Вот очень простой и понятный способ отправить ваши изменения после того, как вы уже сделали commit --amend:

git reset --soft HEAD^
git stash
git push -f origin master
git stash pop
git commit -a
git push origin master

Что делает следующее:

  • Сбросить заголовок ветки до родительского коммита.
  • Спрятать этот последний коммит.
  • Принудительное нажатие на пульт. Удаленный теперь не имеет последнего коммита.
  • Поп твой тайник.
  • Совершайте чисто.
  • Нажмите на пульт.

Не забудьте изменить «origin» и «master», если применяете это к другой ветке или удаленной.

Faiza
источник
3
2 замечания: - убедитесь, что изменили название ветви, если вы работаете над другой - мне пришлось использовать git addперед моей фиксацией, чтобы включить изменения.
SylvainB
1
В Windows CMD, первая команда должна быть экранированы: git reset --soft "HEAD^". В остальном работает нормально.
Мистер мистер
2
"очень простой и чистый способ .." цит. Эта процедура включает в себя принудительный толчок. В свете всех критических замечаний в Ответах выше я не уверен, является ли эта процедура действительно чистой.
Na13-c
24

Я решил это, отбросив свой локальный исправленный коммит и добавив новые изменения сверху:

# Rewind to commit before conflicting
git reset --soft HEAD~1

# Pull the remote version
git pull

# Add the new commit on top
git add ...
git commit
git push
Бара
источник
2
Это самая простая версия!
mknaf
Добавление другого коммита 'change' лучше, чем возиться с переписыванием истории. Я согласен с @mknaf
sdkks
8

У меня такая же проблема.

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

Как Git-новичок, я думал, что это полный FUBAR .

Решение: В некотором роде @bara предложил + создал локальную резервную ветку

# Rewind to commit just before the pushed-and-amended one.
# Replace <hash> with the needed hash.
# --soft means: leave all the changes there, so nothing is lost.
git reset --soft <hash>

# Create new branch, just for a backup, still having all changes in it.
# The branch was feature/1234, new one - feature/1234-gone-bad
git checkout -b feature/1234-gone-bad

# Commit all the changes (all the mess) not to lose it & not to carry around
git commit -a -m "feature/1234 backup"

# Switch back to the original branch
git checkout feature/1234

# Pull the from remote (named 'origin'), thus 'repairing' our main problem
git pull origin/feature/1234

# Now you have a clean-and-non-diverged branch and a backup of the local changes.
# Check the needed files from the backup branch
git checkout feature/1234-gone-bad -- the/path/to/file.php

Возможно, это не быстрое и чистое решение, и я потерял свою историю (1 коммит вместо 5), но это спасло работу за день.

davisca
источник
6

Если вы не отправили код в удаленную ветку (GitHub / Bitbucket), вы можете изменить сообщение коммита в командной строке, как показано ниже.

 git commit --amend -m "Your new message"

Если вы работаете над определенной веткой, сделайте это:

git commit --amend -m "BRANCH-NAME: new message"

Если вы уже отправили код с неправильным сообщением, вам следует быть осторожным при изменении сообщения. то есть после того, как вы измените сообщение фиксации и попытаетесь нажать его снова, у вас возникнут проблемы. Чтобы сделать это гладко, выполните следующие действия.

Пожалуйста, прочитайте весь ответ, прежде чем делать это

git commit --amend -m "BRANCH-NAME : your new message"

git push -f origin BRANCH-NAME                # Not a best practice. Read below why?

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

 git commit --amend -m "BRANCH-NAME : your new message"
 git pull origin BRANCH-NAME
 git push -f origin BRANCH-NAME

Это лучший метод при изменении сообщения фиксации, если оно уже было отправлено.

Прабхакар Ундурти
источник
1
Если вы успешно отозвали коммит в своем последнем примере, зачем вам принудительно нажимать? Разве стандартного толчка недостаточно? Спасибо
Томас
Вопрос, заданный Томасом, на самом деле очень актуален. Мне не нужно было толкать толчок после тяги.
Na13-c
Пожалуйста, не называйте это «лучшей практикой», потому что есть способ обойти --force, см. Принятый ответ
Фарид
5

Если вы знаете, что никто не снял ваш неизмененный коммит, используйте --force-with-leaseопцию git push.

В TortoiseGit вы можете сделать то же самое в опциях «Push ...» «Force: May Discard» и проверяя «известные изменения».

Принудительно (может отменить известные изменения) позволяет удаленному репозиторию принимать более безопасную передачу без ускоренной перемотки вперед. Это может привести к потере коммитов в удаленном репозитории; используйте это с осторожностью. Это может предотвратить потерю неизвестных изменений от других людей на пульте. Он проверяет, указывает ли ветвь сервера на тот же коммит, что и на ветку удаленного отслеживания (известные изменения). Если да, будет выполнен принудительный толчок. В противном случае он будет отклонен. Поскольку git не имеет тегов удаленного отслеживания, теги нельзя перезаписать с помощью этой опции.

ShawnFeatherly
источник
4

Вы получаете эту ошибку, потому что пульт Git уже имеет эти файлы коммита. Вы должны принудительно нажать на ветку, чтобы это работало:

git push -f origin branch_name

Также убедитесь, что вы извлекаете код из удаленного узла, поскольку кто-то из вашей команды мог отправить его в ту же ветку.

git pull origin branch_name

Это один из случаев, когда мы вынуждены принудительно выдавать коммит на удаленный доступ.

Правин Дхаван
источник
Почему этот ответ не учитывает основные комментарии, высказанные в предыдущих ответах?
Na13-c
2

Вот очень простой и понятный способ отправить ваши изменения после того, как вы уже сделали git add "your files"и git commit --amend:

git push origin master -f

или:

git push origin master --force
craken
источник
Я слышал, что это плохо, и я уверен, что это так. Я уверен, что есть (хорошая) причина сбоя git по умолчанию (и требует --force).
Рольф
1

Мне пришлось решить эту проблему с удалением из репозитория и справиться с возникшими конфликтами слияния, зафиксировать, а затем подтолкнуть. Но я чувствую, что есть лучший способ.

Spoike
источник
На самом деле, нет. Возможно, проблема в том, что вы не обновили свою локальную копию из удаленного хранилища. Git не будет настаивать на этом, потому что вам, возможно, придется иметь дело со слияниями вручную. В моем другом ответе у меня есть команда (и объяснение), которая вызовет толчок - но будьте осторожны, это может удалить изменения в пульте.
Мипади
1

Я просто продолжал делать то, что сказал мне Гит. Так:

  • Не могу нажать из-за исправленного коммита.
  • Я делаю тягу, как предложено.
  • Слияние не удается. поэтому я исправляю это вручную.
  • Создайте новый коммит (помеченный как «слияние») и нажмите его.
  • Кажется, работает!

Примечание: исправленный коммит был последним.

Рольф
источник
1
Я бы понизил голос, если бы у меня было больше очков репутации, поэтому я просто вежливо спросил бы, кто из вас страдает? Тот, кто исправил? Тот, кто тянул и работал на ветке с исправленным коммитом? До изменения или после него? Я просто очистил каждую мою модификацию, потому что я вас неправильно понял ... К счастью, там было немного ...
Бартис Арон
1

Следующее сработало для меня при смене автора и коммиттера коммита.

git push -f origin master

Git был достаточно умен, чтобы понять, что это были коммиты идентичных дельт, которые различались только в разделе метаинформации.

И местный, и удаленный руководители указали на коммиты, о которых идет речь.

MadPhysicist
источник
0

Вот, как я исправил редактирование в предыдущем коммите:

  1. Сохранить свою работу до сих пор.
  2. Сохраните ваши изменения на время, если они сделаны: git stash теперь ваша рабочая копия чиста в состоянии вашего последнего коммита.
  3. Сделайте правки и исправления.
  4. Зафиксируйте изменения в режиме «Изменить» :git commit --all --amend
  5. Ваш редактор подойдет с запросом сообщения журнала (по умолчанию старое сообщение журнала). Сохраните и выйдите из редактора, когда вы довольны им.

    Новые изменения добавляются в старый коммит. Смотрите сами git logиgit diff HEAD^

  6. Повторно примените ваши скрытые изменения, если они сделаны: git stash apply

Харшал Вани
источник