Практическое использование git reset --soft?

136

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

Я понимаю, что могу использовать программный сброс, чтобы редактировать коммит, не изменяя индекс или рабочий каталог, как я сделал бы с этим git commit --amend.

Эти две команды действительно одинаковы ( reset --softпротив commit --amend)? Есть ли основания использовать одно или другое на практике? И что еще более важно, есть ли другие варианты использования reset --softпомимо внесения изменений в коммит?

AJJ
источник

Ответы:

110

git resetэто все о переезде HEAD, и вообще ветка исх .
Вопрос: как насчет рабочего дерева и индекса?
При применении с --soft, движется HEAD, чаще обновляя ветви реф, и толькоHEAD .
Это отличается от commit --amendкак:

  • это не создает новый коммит.
  • на самом деле он может переместить HEAD в любой коммит (как commit --amendтолько в том случае, если не переместить HEAD, при этом позволяя повторить текущий коммит)

Просто нашел этот пример объединения:

  • классическое слияние
  • объединение поддеревьев

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

Томас "wereHamster" Карнекки объясняет в своей статье "Слияние поддеревьев осьминога" :

  • Стратегия слияния поддеревьев может использоваться, если вы хотите объединить один проект с подкаталогом другого проекта и впоследствии поддерживать подпроект в актуальном состоянии. Это альтернатива подмодулям git.
  • Стратегия слияния осьминога может быть использована для слияния трех или более веток. Обычная стратегия может объединять только две ветви, и если вы попытаетесь объединить больше, git автоматически вернется к стратегии осьминога.

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

У меня есть суперпроект, давайте назовем его projectA, и подпроект projectB, который я объединил в подкаталог projectA.

(это часть слияния поддерева)

Я также поддерживаю несколько местных коммитов.
ProjectAрегулярно обновляется, обновляется projectBкаждые пару дней или недель и обычно зависит от конкретной версии projectA.

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

# Merge projectA with the default strategy:
git merge projectA/master

# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master

Здесь автор использовал reset --hard, а затем, read-treeчтобы восстановить то, что первые два слияния сделали с рабочим деревом и индексом, но вот где reset --softможно помочь:
Как мне повторить те два слияния , которые сработали, то есть мое рабочее дерево и индекс хорошо, но без записи этих двух коммитов?

# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}

Теперь мы можем возобновить решение Томаса:

# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD

# And finally do the commit:
git commit

Итак, каждый раз:

  • вы довольны тем, что вы в итоге (с точки зрения рабочего дерева и индекса)
  • Вы не удовлетворены всеми коммитами, которые потребовали вас, чтобы попасть туда:

git reset --soft это ответ.

VonC
источник
8
Примечание для себя: простой пример раздавливания с помощью git reset --soft: stackoverflow.com/questions/6869705/…
VonC
3
Это также полезно, если вы совершили неправильную ветку. Все изменения возвращаются в область подготовки и перемещаются вместе с вами, пока вы оформляете нужную ветку.
Smoebody
44

Вариант использования - объединение ряда локальных коммитов

«Упс. Эти три коммита могут быть только одним».

Таким образом, отмените последние 3 (или что-то еще) коммитов (не затрагивая ни индекс, ни рабочий каталог). Затем зафиксируйте все изменения как одно.

Например

> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate

> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.

> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate

> * e1e8042 Start here.

Теперь все ваши изменения сохранены и готовы к принятию как единое целое.

Короткие ответы на ваши вопросы

Эти две команды действительно одинаковы ( reset --softпротив commit --amend)?

  • Нет.

Есть ли причина использовать тот или иной на практике?

  • commit --amend чтобы добавить файлы / rm из самого последнего коммита или изменить его сообщение.
  • reset --soft <commit> объединить несколько последовательных коммитов в новый.

И что еще более важно, есть ли другие варианты использования reset --softпомимо внесения изменений в коммит?

  • Смотрите другие ответы :)
Шон Луттин
источник
2
См. Другие ответы на вопрос «Есть ли другие варианты использования, reset --softкроме внесения поправок в коммит - Нет»
Энтони Хэтчкинс
В ответ на последний комментарий - частично согласен, но я бы сказал, что изменение одного коммита слишком специфично. Например, вы можете захотеть, как только функция будет написана, раздавить все и вырезать новые коммиты, которые будут более полезны для рецензентов. Я бы сказал, что мягкие сбросы (в том числе простые git reset) хороши, когда вы (а) хотите переписать историю, (б) не заботитесь о старых коммитах (чтобы вы могли избежать суетливости интерактивного ребазирования) и (в) нужно изменить более одной фиксации (в противном случае commit --amendэто проще).
johncip
18

Я использую его для исправления не только последнего коммита.

Допустим, я сделал ошибку при фиксации A, а затем совершил фиксацию B. Теперь я могу исправить только B. Итак git reset --soft HEAD^^, я исправляю и повторно фиксирую A, а затем повторно фиксирую B.

Конечно, для больших коммитов это не очень удобно… но все равно не стоит делать большие коммиты ;-)

Саймон
источник
3
git commit --fixup HEAD^^ git rebase --autosquash HEAD~Xтоже хорошо работает.
Niklas
3
git rebase --interactive HEAD^^где вы выбираете редактировать как коммиты A, так и B. Таким образом сохраняются сообщения коммитов A и B, при необходимости вы все равно можете изменить их.
Paul Pladijs
2
Как мне повторно зафиксировать B после сброса на A?
northben
1
Другой способ , который, вероятно , меньше работы: проверить журнал GIT, получить коммит хэша B, а затем git reset A, сделать и добавить изменения, git commit --amend, git cherry-pick <B-commit-hash>.
naught101
15

Другое возможное использование - это альтернатива хранению (что некоторым не нравится, см., Например, https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-considered-harmful/ ).

Например, если я работаю над веткой и мне нужно срочно что-то исправить на мастере, я могу просто сделать:

git commit -am "In progress."

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

git reset --soft HEAD~1

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

deltacrux
источник
3
Обновление (теперь, когда я лучше понимаю git): --softна самом деле здесь не нужно, если вы действительно не заботитесь о немедленной постановке изменений. Я сейчас просто использую git reset HEAD~при этом. Если у меня есть какие-то изменения, когда мне нужно переключить ветки, и я хочу сохранить это таким образом, то я делаю git commit -m "staged changes", затем git commit -am "unstaged changes", а затем , затем , затем git reset HEAD~, git reset --soft HEAD~чтобы полностью восстановить рабочее состояние. Хотя, честно говоря, сейчас я делаю обе эти вещи намного меньше, чем знаю git-worktree:)
deltacrux
7

Вы можете использовать, git reset --softчтобы изменить версию, которую вы хотите использовать в качестве родительской для изменений, которые вы имеете в своем индексе и рабочем дереве. Случаи, когда это полезно, редки. Иногда вы можете решить, что изменения, внесенные в ваше рабочее дерево, должны принадлежать другой ветви. Или вы можете использовать это как простой способ свернуть несколько коммитов в один (аналогично сжатию / сворачиванию).

Посмотрите этот ответ VonC для практического примера: Squash первых двух коммитов в Git?

Йоханнес Рудольф
источник
Это хороший вопрос, которого я раньше не задавал. Но я не уверен, что вижу, как внести изменения в другую ветку с помощью программного сброса. Итак, у меня есть checkout Branch, я использую, git reset --soft anotherBranchа затем фиксирую там? Но вы на самом деле не меняете ветку ckeckout, так что вы сделаете фиксацию в Branch или в другомBranch ?
AJJ
При этом важно не использовать git checkout, потому что это изменит ваше дерево. Думайте о git reset --soft как о способе простого управления ревизией, на которую указывает HEAD.
Йоханнес Рудольф
7

Один из возможных вариантов использования - это когда вы хотите продолжить работу на другом компьютере. Это будет работать так:

  1. Оформить заказ на новую ветку с именем, похожим на тайник,

    git checkout -b <branchname>_stash
    
  2. Поднимите ветку тайника вверх,

    git push -u origin <branchname>_stash
    
  3. Переключитесь на другую машину.

  4. Вытащите как свой тайник, так и существующие ветки,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. Теперь вы должны быть в существующей ветке. Слить изменения из ветки тайника,

    git merge <branchname>_stash
    
  6. Мягкий сброс существующей ветки до 1 перед слиянием,

    git reset --soft HEAD^
    
  7. Удалите ветку тайника,

    git branch -d <branchname>_stash
    
  8. Также удалите ветку тайника из источника,

    git push origin :<branchname>_stash
    
  9. Продолжайте работать со своими изменениями, как если бы вы их обычно прятали.

Я думаю, что в будущем GitHub и компания. должен предлагать эту функцию «удаленного тайника» за меньшее количество шагов.

llaughlin
источник
2
Я хочу отметить, что первый тайник и всплывающее окно на вашем первом компьютере совершенно не нужны, вы можете просто создать новую ветку прямо из грязной рабочей копии, зафиксировать, а затем отправить изменения на удаленный компьютер.
7

Одно из практических применений - если вы уже сделали привязку к своему локальному репо (например, git commit -m), вы можете отменить эту последнюю фиксацию, выполнив git reset --soft HEAD ~ 1

Также для вашего знания, если вы уже подготовили свои изменения (например, с помощью git add.), Вы можете отменить постановку, выполнив git reset --mixed HEAD, или я обычно просто использовалgit reset

наконец, git reset --hard стирает все, включая ваши локальные изменения. Знак ~ after head сообщает вам, сколько коммитов нужно перейти сверху.

j2emanue
источник
1
git reset --soft HEAD ~1дает мне, fatal: Cannot do soft reset with paths.я думаю, нам нужно удалить пробел после HEAD, чтобы это былоgit reset --soft HEAD~1
Эндрю Лор
6

Отличная причина использовать ' git reset --soft <sha1>' - переместитьHEAD это использовать чистый репо.

Если вы попытаетесь использовать --mixedили--hard , вы получите сообщение об ошибке, поскольку вы пытаетесь изменить и работать с деревом и / или индексом, которые не существуют.

Примечание: вам нужно будет сделать это прямо из чистого репо.

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

Hazok
источник
1
Как упоминалось в вашем предыдущем ответе ( stackoverflow.com/questions/4624881/… ), это верно, когда у вас есть прямой доступ к голому репо (о котором вы упоминаете здесь). +1 хотя.
VonC
@VonC Да, абсолютно верно и спасибо за добавление примечания! Я все время забываю добавить это, поскольку предполагаю, что сбросы выполняются непосредственно из репо. Кроме того, я предполагаю, что ветка, которую человек хочет сбросить в голом репо, является его активной ветвью. Если ветка не является их активной ветвью, тогда необходимо обновить в соответствии с вашим ответом ( stackoverflow.com/questions/3301956/… ) о том, как обновить активную ветку для голых репозиториев. Я также обновлю ответ, указав активную информацию о ветке. Еще раз спасибо!!!
Hazok
2

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

Так git reset --soft HEAD~1гораздо полезнее, чемcommit --amend в этом сценарии. Я могу отменить фиксацию, вернуть все изменения в промежуточную область и возобновить настройку поэтапных битов с помощью SourceTree.

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

Роман Старков
источник
1

Хотя мне очень нравятся ответы в этой ветке, я, git reset --softтем не менее , использую несколько другой, но очень практичный сценарий.

Я использую IDE для разработки, в которой есть хороший инструмент сравнения для отображения изменений (поэтапных и неустановленных) после моей последней фиксации. Теперь большинство моих задач включают несколько коммитов. Например, предположим, что я делаю 5 коммитов для выполнения определенной задачи. Я использую инструмент diff в среде IDE во время каждой инкрементальной фиксации с 1 по 5, чтобы посмотреть мои изменения из последней фиксации. Я считаю, что это очень полезный способ просмотреть свои изменения перед фиксацией.

Но в конце моей задачи, когда я хочу увидеть все свои изменения вместе (до 1-го коммита), чтобы выполнить самопроверку кода перед выполнением запроса на перенос, я бы увидел только изменения из моей предыдущей фиксации (после фиксации 4), а не изменения из всех коммитов моей текущей задачи.

Поэтому я git reset --soft HEAD~4возвращаюсь на 4 коммита назад. Это позволяет мне видеть все изменения вместе. Когда я уверен в своих изменениях, я могу уверенно вносить изменения git reset HEAD@{1}и отправлять их на удаленный доступ.

Шикарный кодер
источник
1
... тогда git add --patchи git commitповторно создать коммит серию вы бы построить , если бы вы знали , что вы делали все вместе. Ваши первые коммиты похожи на заметки на вашем столе или первый черновик заметки, они нужны для организации вашего мышления, а не для публикации.
jthill 05
Эй, я не совсем понял то, что вы предлагаете.
Swanky Coder
1
Просто вместо того, git reset @{1}чтобы восстанавливать свои первые черновики серии, вы можете вместо этого создать серию для публикации из git add -pи git commit.
jthill 06
Да исправить. Еще один способ (тот, который я обычно использую) - перебазировать upstream / master и сжать коммиты в один.
Swanky Coder
1

Другой вариант использования - когда вы хотите заменить другую ветку своей в запросе на перенос, например, допустим, что у вас есть программное обеспечение с функциями A, B, C в разработке.

Вы разрабатываете следующую версию и:

  • Удалена функция B

  • Добавлена ​​функция D

В процессе разработки только что добавленные исправления для функции B.

Вы можете объединить разработку в следующую, но иногда это может быть беспорядочно, но вы также можете использовать git reset --soft origin/developи создать фиксацию с вашими изменениями, и ветка может быть объединена без конфликтов и сохранить ваши изменения.

Оказывается, git reset --softэто удобная команда. Лично я часто использую его для сквоша коммитов, которые не имеют «завершенной работы», такой как «WIP», поэтому, когда я открываю пул реквест, все мои коммиты понятны.

cnexans
источник