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, вам больше не нужно это делать, поскольку у него есть опция сжатия при объединении ветвей.
Решение - 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
Решение для людей, предпочитающих кликать:
Установите sourcetree (бесплатно)
Проверьте, как выглядят ваши коммиты. Скорее всего у вас есть нечто подобное
Щелкните правой кнопкой мыши родительский коммит. В нашем случае это основная ветка.
Вы можете сжать коммит с предыдущим, нажав кнопку. В нашем случае нужно щелкнуть 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
В предыдущих ответах я не видел никакой информации о том, как бороться с « грязными ветвями » и « самоконфликтами ». Например, у меня часто возникают коммиты в моей функциональной ветке (назовем ее ), которые вызывают конфликты друг с другом. Я обнаружил, что это одна из самых неприятных проблем, с которыми приходится иметь дело.
Как раздавить грязные ветки? Используйте временную ветку!
Я нашел решение Феликса Ризеберга лучшим. Это моя немного более короткая транскрипция его совета:
- Создать локальную ветку офф.
-
git checkout master && git pull && git checkout -b tmp
-
- Объединить все изменения в (без каких-либо коммитов, только промежуточные изменения файла).
-
git merge --squash $feature
-
- Вручную решить все оставшиеся «настоящие конфликты»
- (Это единственный шаг, который скрипт не может сделать за вас)
- Совершить. теперь + 1 коммит (содержащий все изменения).
-
git commit ...
-
- Оформить заказ и
git reset --hard tmp
(исходное содержимое исчезло, и теперь оно в основномtmp
, но переименовали)-
git checkout $feature && git reset --hard tmp
-
- Игнорировать и переопределить
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, используя графический интерфейс через командную строку:
- Перейдите в окно Контроль версий (Alt + 9/Command + 9) - вкладка "Журнал".
- Выберите точку в дереве, из которой вы создали свою ветку
- Щелкните правой кнопкой мыши по нему -> Сбросить текущую ветку сюда -> Выбрать софт (!!!) (это важно, чтобы не потерять ваши изменения)
- Нажмите кнопку "Сброс" в нижней части диалогового окна.
Вот и все. Вы отменили все свои изменения. Теперь, если вы сделаете новый коммит, он будет раздавлен
Вы можете сделать это с помощью подкоманд, т.е.
$ git rebase -i HEAD~$(git rev-list --count HEAD ^master)
Это сначала подсчитает коммиты, так как вы отклонились от мастера, а затем вернетесь к этой точной длине.
Предполагая, что вы находитесь в функциональной ветке:
- Найдите первый коммит в функциональной ветке. Вы можете просмотреть его напрямую в ветке, если вы используете gitlab или github, и скопировать оттуда хэш, или вы можете использовать следующую команду:
git log <source_branch>..<feature_branch> --pretty=format:%h
- Выполните следующие команды:
git reset --soft <base_commit_hash>
git commit --amend --no-edit
Теперь на данном этапе у вас есть 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, но не совсем то же самое.