Git: Как раздавить все коммиты на ветке

Я делаю новую ветку от master с:

git checkout -b testbranch

Я делаю 20 коммитов в это.

Теперь я хочу раздавить эти 20 коммитов. Я делаю это с:

git rebase -i HEAD~20

А что если я не знаю, сколько коммитов? Есть ли способ сделать что-то вроде:

git rebase -i all on this branch

25 ответов

Решение

Другой способ уничтожить все ваши коммиты - сбросить индекс на master:

 git checkout yourBranch
 git reset $(git merge-base master yourBranch)
 git add -A
 git commit -m "one commit on yourBranch"

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


РЕДАКТИРОВАТЬ: вам нужно будет использовать git push --force


Karlotcha Hoa добавляет в комментариях:

Для сброса вы можете сделать

git reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD)) 

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

Оформите ветку, для которой вы хотите объединить все коммиты в один коммит. Скажем, это называетсяfeature_branch,

git checkout feature_branch

Шаг 1:

Сделайте мягкий сброс вашего origin/feature_branch с вашим местным master ответвление (в зависимости от ваших потребностей вы также можете выполнить сброс с помощью origin/master). Это сбросит все дополнительные коммиты в вашем feature_branch, но без изменения каких-либо ваших файлов изменяется локально.

git reset --soft master

Шаг 2:

Добавьте все изменения в вашем каталоге git repo в новый коммит, который будет создан. И совершить то же самое с сообщением.

git add -A && git commit -m "commit message goes here"

То, что вы делаете, довольно подвержено ошибкам. Просто делать:

git rebase -i master

который автоматически перебазирует только коммиты вашей ветки на текущий последний мастер.

Еще один простой способ сделать это: перейти на исходную ветку и сделать merge --squash, Эта команда не выполняет "квадратный" коммит. когда вы это сделаете, будут собраны все сообщения о коммитах вашей ветки.

$ git checkout master
$ git merge --squash yourBranch
$ git commit # all commit messages of yourBranch in one, really useful
 > [status 5007e77] Squashed commit of the following: ...

Предполагая, что вы разветвлялись от мастера, вам не нужно вводить yourBranch на шаг сброса все время:

git checkout yourBranch
git reset --soft HEAD~$(git rev-list --count HEAD ^master)
git add -A
git commit -m "one commit on yourBranch"

Пояснение:

  • git rev-list --count HEAD ^master подсчитывает коммиты, так как вы сделали свою ветку объектов от мастера, например. 20.
  • git reset --soft HEAD~20 сделает мягкий сброс последних 20 коммитов. Это оставляет ваши изменения в файлах, но удаляет коммиты.

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

В моем.bash_profile я добавил псевдоним для gisquash сделать это с помощью одной команды:

# squash all commits into one
alias gisquash='git reset --soft HEAD~$(git rev-list --count HEAD ^master)'

После сброса и совершения вы должны сделать git push --force,

Подсказка:

Если вы используете Gitlab >= 11.0, вам больше не нужно это делать, поскольку у него есть опция сжатия при объединении ветвей. Gitlab Squashing опция

Решение - 1

A. Перетащите мастер в свою ветку функций (обязательно обновите свой мастер)

      git pull origin master  

Б. Сделайте мягкий сброс в мастер

      git reset --soft master

C. Зафиксируйте свои изменения

      git commit -m “commit message"

D. Сделайте git push

      git push --force  

Решение - 2

Сжатие фиксации с использованием git rebase

А.

      $git rebase -i HEAD~3  (HEAD~<no. of commits you want to squash>)

B. Вы получите одно интерактивное приглашение, в котором вам нужно выбрать верхнюю фиксацию и вставить сквош или сквош перед теми, которые вы хотите объединить / сквош.

Примечание. Обязательно внесите изменения в режиме вставки и сохраните файл; (wq в редакторе VI)

C. Теперь вы получите еще одно интерактивное приглашение, в котором вам нужно поставить # перед сообщением о коммитах, которое вам не нужно, или добавить свое собственное сообщение. Снова сохраните файл, и ваши коммиты будут успешно перебазированы.

Ваше здоровье!

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

git merge origin/master && git reset --soft origin/master

Предыдущий cmd слияния гарантирует, что никакие недавние изменения из мастера не пойдут вам на голову (перевернутые) при фиксации! После этого просто зафиксируйте изменения и выполните git push -f

Решение для людей, предпочитающих кликать:

  1. Установите sourcetree (бесплатно)

  2. Проверьте, как выглядят ваши коммиты. Скорее всего у вас есть нечто подобное

  3. Щелкните правой кнопкой мыши родительский коммит. В нашем случае это основная ветка.

  1. Вы можете сжать коммит с предыдущим, нажав кнопку. В нашем случае нужно щелкнуть 2 раза. Вы также можете изменить сообщение фиксации

  2. Результаты потрясающие, и мы готовы к работе!

Боковое примечание: если вы нажимали свои частичные коммиты на удаленный, вы должны использовать принудительный толчок после сквоша

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

git reset --soft $(git merge-base master YOUR_BRANCH) && git commit -am "YOUR COMMIT MESSAGE" && git rebase -i master

Это при условии, что мастер является базовой веткой.

Как

Вам нужно получить базу слияния вашей ветки

      git merge-base master your-branch
# 566f8438e0cd0e331ceb49a9cb0920143dfb065c

Затем вы можете переустановить его

      git rebase -i 566f8438e0cd0e331ceb49a9cb0920143dfb065c
# then squash/pick/do commit messages

или просто сделайте мягкий сброс и зафиксируйте все

      git reset --soft 566f8438e0cd0e331ceb49a9cb0920143dfb065c
git add .
git commit -m "The only commit"

Автоматизировать

Если вы делаете это часто, вы можете автоматизировать это, поместив их в свой .bashrc с использованием.

      g-rebase-branch() {
  git branch --show-current | xargs git merge-base master | xargs git rebase -i
}

g-one-commit() {
  local last_commit_message=`git show -s --format=%s`
  git branch --show-current | xargs git merge-base master | xargs git reset --soft
  git add -A
  git commit -m "$last_commit_message"
  git commit --amend
}

а затем сделайте это прямо в терминале.

      g-one-commit

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

Как раздавить грязные ветки? Используйте временную ветку!

Я нашел решение Феликса Ризеберга лучшим. Это моя немного более короткая транскрипция его совета:

  1. Создать локальную ветку офф.
    • git checkout master && git pull && git checkout -b tmp
  2. Объединить все изменения в (без каких-либо коммитов, только промежуточные изменения файла).
    • git merge --squash $feature
  3. Вручную решить все оставшиеся «настоящие конфликты»
    • (Это единственный шаг, который скрипт не может сделать за вас)
  4. Совершить. теперь + 1 коммит (содержащий все изменения).
    • git commit ...
  5. Оформить заказ иgit reset --hard tmp(исходное содержимое исчезло, и теперь оно в основномtmp, но переименовали)
    • git checkout $feature && git reset --hard tmp
  6. Игнорировать и переопределитьorigin/feature(потом очистить)
    • git push -f && git branch -D tmp

Феликс отмечает, что это приведет к максимально чистому слиянию, без каких-либо странных внутренних конфликтов, возникающих из-за беспорядочных/сложных отношений междуmasterиfeature:

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

Чтобы немного уточнить ответ пещерного человека , используйтеgit reset --soft <commit>. Из документации эта команда:

Не затрагивает индексный файл или рабочее дерево вообще (но сбрасывает голову в <commit>, как это делают все режимы). Это оставляет все ваши измененные файлы «Изменения для фиксации», как git statusпоставил бы.

Другими словами, он отменяет все ваши коммиты до <commit>. Но это не меняет рабочий каталог. В итоге вы получите все изменения, не проиндексированные и незафиксированные. Как будто этих промежуточных коммитов никогда не было.

Пример:

      # on master
git checkout -b testbranch
# make many commits
git reset --soft master
git add .
git commit -m 'The only commit.'

В этот момент вы все еще на testbranch, который имеет один коммит. Слияние с мастером, как обычно.

Первая часть ответа пещерного человека ( git rebase -i) в моих руках не было коммитов сквоша.

Если вы используете IDE на основе JetBrains, например IntelliJ Idea и prefare, используя графический интерфейс через командную строку:

  1. Перейдите в окно Контроль версий (Alt + 9/Command + 9) - вкладка "Журнал".
  2. Выберите точку в дереве, из которой вы создали свою ветку
  3. Щелкните правой кнопкой мыши по нему -> Сбросить текущую ветку сюда -> Выбрать софт (!!!) (это важно, чтобы не потерять ваши изменения)
  4. Нажмите кнопку "Сброс" в нижней части диалогового окна.

Вот и все. Вы отменили все свои изменения. Теперь, если вы сделаете новый коммит, он будет раздавлен

Вы можете сделать это с помощью подкоманд, т.е.

$ git rebase -i HEAD~$(git rev-list --count HEAD ^master)

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

Предполагая, что вы находитесь в функциональной ветке:

  1. Найдите первый коммит в функциональной ветке. Вы можете просмотреть его напрямую в ветке, если вы используете gitlab или github, и скопировать оттуда хэш, или вы можете использовать следующую команду:

git log <source_branch>..<feature_branch> --pretty=format:%h

  1. Выполните следующие команды:
      git reset --soft <base_commit_hash>

git commit --amend --no-edit

Теперь на данном этапе у вас есть 1 коммит, который включает в себя изменения, сделанные во всех предыдущих коммитах.

Просмотрите его, и вам нужно принудительно нажать на него. После принудительного нажатия все изменения будут объединены в один коммит, и ваша ветка будет иметь только 1 коммит.

  1. Принудительное нажатие в ветке функций
      git push --force 

Вы можете использовать инструмент, который я создал специально для этой задачи:

https://github.com/sheerun/git-squash

В основном вам нужно позвонить git squash master и вы сделали

      git checkout -b temp
git checkout yourbranch
git fetch
git reset --hard origin/master
git merge --squash temp
git commit -m "new message" 

самый простой способ сделать.

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

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

      git rebase -i master

Если вы хотите объединить 1 все коммиты в один, а ваш редактор Git — Vim2, введите следующую команду Vim:

      :2,$s/pick/fixup/

Затем сохранитесь и выйдите(:wq) Вим.


1 fixupозначает отбрасывание дополнительных сообщений о коммитах сжатых коммитов . Чтобы иметь возможность дальнейшей обработки сообщений о коммитах сжатых коммитов, замените его наsquash.

2 Вы можете переключить свой редактор только для одной команды, установивEDITORпеременная среды:

      EDITOR=vim git rebase -i master

Другое решение - сохранить все журналы фиксации в файл.

git журнал> branch.log

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

git reset -soft

все коммиты будут отменены

Весь этот git reset, жесткий, мягкий и все остальное, упомянутое здесь, вероятно, работает (это не для меня), если вы выполняете шаги правильно и какой-то джинн.
Если вы обычный Джо Смо, попробуйте следующее:
Как использовать git merge --squash?


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


если вы находитесь в ветке, давайте назовем ее "my_new_feature" в режиме разработки, и ваш запрос на перенос содержит 35 коммитов (или сколько угодно), и вы хотите, чтобы это было 1.

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

B. Получите последнюю версию разработки и перейдите в новую ветку, назовите ее "my_new_feature_squashed"

C. магия здесь.
Вы хотите перенести свою работу с "my_new_feature" на "my_new_feature_squashed",
так что просто сделайте (пока в вашей новой ветке мы создали вне разработки):
git merge --squash my_new_feature

Все ваши изменения теперь будут в вашей новой ветке, не стесняйтесь ее протестировать, а затем просто сделайте одну единственную фиксацию, нажмите, новый PR этой ветки - и дождитесь повтора на следующий день.
Разве ты не любишь кодировать?:)

Я знаю, что на этот вопрос уже дан ответ, но я пошел и написал функцию bash вокруг принятого ответа, чтобы вы могли сделать это одной командой. Он начинается с создания резервной ветки на случай, если сквош по какой-то причине не удастся. Затем сквош и коммит.

      # Squashes every commit starting after the given head of the given branch.
# When the squash is done, it will prompt you to commit the squash.
# The head of the given parent branch must be a commit that actually exists
# in the current branch.
#
# This will create a backup of the current branch before it performs the squash.
# The name of the backup is the second argument to this function.
#
# Example: $ git-squash master my-current-branch-backup
git-squash() {
  PARENT_BRANCH=$1
  BACKUP_BRANCH=$2

  CURRENT_BRANCH=$(git branch --show-current)

  git branch $BACKUP_BRANCH
  BACKUP_SUCCESS=$?

  if [ $BACKUP_SUCCESS -eq 0 ]; then
    git reset $(git merge-base $PARENT_BRANCH $CURRENT_BRANCH)
    git add -A
    git commit
    echo "Squashed $CURRENT_BRANCH. Backup of original created at $BACKUP_BRANCH$"
  else
    echo "Could not create backup branch. Aborting squash"
  fi
}

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

(по ветке разработки)

git fetch
git merge origin/master  #so development branch has all current changes from master
git reset origin/master  #will show all changes from development branch to master as unstaged
git gui # do a final review, stage all changes you really want
git commit # all changes in a single commit
git branch -f master #update local master branch
git push origin master #push it

Единственное общее решение, которое я нашел до сих пор.

      git reset $(git reflog show --no-abbrev $(git branch --show-current) | grep "branch: Created from" | awk '{print $1;}')
git add .
git commit -m "Squashed commit"

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

Поскольку нас не волнуют промежуточные коммиты, мы можем использовать простое решение:

      # Merge and resolve conflicts
git merge origin/master

# Soft reset and create a HEAD with the version you want
git reset --soft origin/master
git commit -m "Commit message"
git push origin your-branch -f

... затем удалить историю ...

      # Get the most up-to-date master
git checkout master
git reset --hard

# Create a temporary branch
git checkout -b temp-branch

# Retrieve the diff between master and your-branch and commit with a single commit
git checkout origin/your-branch
git commit -m "Feature commit"

# Force push to the feature branch
git push origin temp-branch:your-branch -f

# Clean up
git checkout your-branch
git branch -D temp-branch

Все это можно поместить в функцию bash с тремя такими параметрами squashall --their master --mine your-branch --msg "Feature commit"

и он будет работать до тех пор, пока у вас есть локальная правильная версия файлов.

Если вас устраивает ответ, связанный с другой веткой, попробуйте git checkout --orphan <new_branch>

Это позволило мне просто зафиксировать ВСЕ файлы из предыдущей ветки как одну фиксацию.

Это что-то вроде git merge squash, но не совсем то же самое.

Другие вопросы по тегам