TL; DR
Как отметил Тим Бигелайзен, автор и коммиттер коммита Git является частью самого коммита, и существующие коммиты с неверным адресом не изменятся.Однако вы можете остановить , используя их, и это то, что git merge --squash
позволяет.Следовательно, ответ будет: да, это может решить проблему и нет, это не решит проблему , в зависимости от того, как вы на это смотрите и что делаете во время и после момента, когда вы делаете коммит git merge --squash
.
По сути, после сквош-слияния вы просто удаляете ветку, на которой вы сделали эти 30 коммитов.Возможно, вам придется удалить его из двух мест: ваш Git на вашем компьютере и Git на GitHub (который, по-видимому, в некотором роде ваш, но в некотором роде - в конце концов, именно GitHub контролируют его).
Long
Вы упомянули:
, используя github.com из их пользовательского интерфейса
Когда вы работаете с Git из командыстрока (в Windows, Linux, MacOS и т. д.), вы обычно начинаете с клонирования репозитория откуда-то: например,
$ git clone https://github.com/path/to/repo.git
$ cd repo
(хотя я предпочитаю ssh://git@github.com/...
URL).В настоящее время существует два Git-репозитория: один на GitHub, а другой локально на вашей собственной машине.
Squashing, fetch, push и множественные Git-репозитории в целом
В общем, здесь есть две вещи, которые нужно знать: как работает git merge --squash
, и как эти два разных Gits общаются и делятся коммитами.В частности, в этом есть две части, независимо от того, как вы это делаете: сначала вы сделаете один новый коммит, затем вы будете использовать git push
или git fetch
до перевод новый коммит (и, возможно, старый коммит или нет) между двумя отдельными Gits.
Любые коммиты, которые вы делаете на на вашем компьютере, используя ваш Git, используйте настройкивы сделали на своем компьютере - но если вы используете веб-интерфейс на GitHub и нажимаете там различные кнопки, вы используете их машины для коммитов в их репозитории Git, которые будут использовать настройки , которые они сохранили для вас.Следовательно, если вы перейдете к кнопке с боковой выпадающей стрелкой, которая позволяет выбирать между слияние , rebase-and-merge и squash-and-merge выберите любой из этих трех и щелкните по нему, их Git делает новый коммит в их Git-репозитории, и все настройки, которые вы установили в своем собственном Git на своем компьютере, не имеют значения.
Вы можете сделать git merge --squash
на своем собственном компьютере, а затем использовать git push
, чтобы вы сделали коммиты локально и только затем отправили их на другой Git.Независимо от того, когда и когда делать это, зависит только от вас.
Некоторые сведения о создании любого нового коммита
Вы начинаете с выбора некоторого имени ветки и некоторого коммита, который должен быть включен.либо явно запустив:
git checkout somebranch
, либо неявным образом из-за того, что вы сделали это в какой-то момент раньше, так что в любом случае, в этом конкретном репозитории, вы сейчас находитесь на ветке somebranch
, который по определению имеет последний, или tip , коммит:
...--o--o--o <-- somebranch (HEAD)
Каждый коммит имеет какой-то большой уродливый хеш-идентификатор, который означает этот конкретный коммит: любой GitХранилище может сразу сказать, есть ли у него эта фиксация, и если у нее есть эта фиксация по этому хэш-идентификатору, у него есть эта точная фиксация .Все Gits по всему миру согласны: коммит с этим содержимым - с моментальным снимком, с этим именем пользователя и адресом электронной почты, с этими отметками даты, с этим лог-сообщением и т. Д. - будет иметь , идентификатор хеша, и ни у какого другого коммита никогда не будет этого идентификатора хеша.
Имя somebranch
содержит внутри себя идентификатор хеша этого коммита, и вы можете сделать:
git rev-parse somebranch
доувидеть этот хэш-идентификатор.Вы также увидите этот хэш-идентификатор или его сокращенную версию в выходных данных git log
и git log --oneline
.Этот хэш-идентификатор, хранящийся в этом имени ветви, - это то, как Git знает, что ваша текущая ветвь somebranch
заканчивается этим конкретным коммитом.
Внутри этого коммита находится хэш-идентификатор его непосредственного предшественника или parent коммит.Вот как Git может перейти от коммита tip к коммиту на шаг раньше.Внутри родителя находится хеш-идентификатор деда текущего коммита, поэтому от родителя Git может перейти к дедушке.У бабушки и дедушки есть еще один хэш-идентификатор, поэтому Git может ходить к прабабушке и так далее.Эта цепочка хеш-идентификаторов позволяет Git находить все коммиты, начиная с конца и работая в обратном направлении.
Следовательно, если у нас есть цепочка коммитов, оканчивающаяся, скажем, на коммит с хешем H
:
... <-grandparent <-parent <-H <-- branch-name
и мы собираемся сделать новый коммит, способ, которым Git на самом деле делает новый коммит состоит в том, чтобы упаковать моментальный снимок коммита с указанием имени автора и сообщения журналаи так далее, установите parent hash в H
и запишите его.Поскольку содержимое этого коммита уникально - родительский хеш H
вместе с меткой времени, добавляемой Git, которая сообщает, какая именно секунда была при создании нового коммита, убедитесь, что он будет уникальным - новый коммит получаетновый уникальный идентификатор хеша.Давайте назовем это I
:
... <-parent <-H <-I
Теперь все, что Git должен сделать, это записать хэш-идентификатор нового коммита I
в имя ветви, так что коммит I
является подсказкой.
Другими словами, Git начинается в конце каждой ветви и работает в обратном направлении.Так что этот вид цепочки позволяет Git находить все коммиты, которые содержатся в этой ветке .Некоторые коммиты могут содержаться только в какой-то другой ветви:
B--C <-- master
/
...--A
\
D--...--G--H--I <-- somebranch (HEAD)
Здесь есть несколько коммитов, скажем, где-то около 30 коммитов, которые только на somebranch
, с двумя коммитами, которые только на master
и многими коммитами на обе ветви.(Если вы посчитаете D-E-F-G-H-I-J
, есть место только для 7 коммитов; ну, давайте просто притворимся, что где-то между D
и H
:-)) есть куча новых букв.Коммиты с D
по H
имеют неправильное имя автора, а коммиты I
и J
имеют правильное имя.Наш план сейчас состоит в том, чтобы прекратить использование всех D
до H
полностью.
Использование git merge --squash
Давайте посмотрим, как именно git merge --squash
действительно работает,По сути, он делает то же самое, что и обычное слияние, кроме как в самом конце.Регулярное слияние начинается с определения, какой коммит является лучшим shared коммитом, т. Е. Лучшим коммитом, который находится на обеих ветвях.Этот лучший общий коммит представляет собой базу слияния из двух ветвей.
Мы извлекаем одну ветку, а затем называем другую в нашей команде git merge --squash
:
$ git checkout master # note: this attaches HEAD to master
$ git merge --squash somebranch
Git проверяет цепочки коммитов, которые составляют историю, которая находится в этом хранилище, чтобы найти лучший общий коммит.В нашем случае, когда история master
ведет к фиксации A
, а история somebranch
также ведет к фиксации A
, коммит A
является наилучшим общим коммитом.(Все коммиты до A
также доступны: они не так хороши, как A
.)
Далее Git сравнивает снимок в базе слияния - в коммитеA
- для этого в нашей текущей ветке tip commit C
:
git diff --find-renames <hash-of-A> <hash-of-C> # what we changed in master
Он делает то же самое, чтобы выяснить, что «они» (на самом деле, мы) изменили в другой ветке:
git diff --find-renames <hash-of-A> <hash-of-I> # what they changed
Затем Git делает все возможное, чтобы объединить эти два набора изменений, применяя объединенные изменения к снимку с коммита A
.Если все идет хорошо, эта комбинация является снимком для использования.Если все пойдет не так, Git прекратит конфликт слияния и получит помощь от пользователя, чтобы исправить и завершить его.
(Подробнееточно, ваш Git сделает это.Сетевой Git на GitHub просто скажет извините, я не могу этого сделать и даже не позволю вам вообще начать слияние - они проверяют перед тем, как сделать кнопку слияния нажатой.Он немного отличается от обычного Git, поскольку не позволяет вам взаимодействовать с ним для разрешения конфликтов.У людей GitHub есть настольный клиент, но я никогда не использовал его;Я не знаю, является ли это оболочкой из командной строки Git или чем-то более причудливым.)
Здесь мы будем предполагать, что либо все идет хорошо, либо вы исправили все конфликты и запустите git merge --continue
илиgit commit
для завершения слияния.Git теперь делает коммит, у которого вместо обычного одного родителя двух родителей:
B--C--------------J <-- master (HEAD)
/ /
...--A /
\ /
D--...--G--H--I <-- somebranch
Новый коммит переходит на ветку master
, потому что HEAD
прикреплен к master
(из-за нашего git checkout master
выше).Новый коммит J
указывает на коммит C
, коммит, который был вершиной master
;но он также указывает на фиксацию I
, фиксацию которой все еще составляет верхушка somebranch
.Коммиты C
и I
теперь оба на master
, из-за этого дополнительного второго родителя, который ведет к фиксации I
.
Когда вы используете git merge --squash
, ключевое отличие состоит в том, чтоНовый коммит J
не имеет второго родителя:
B--C---------------J <-- master (HEAD)
/
...--A
\
D--...--G--H--I <-- somebranch
Все остальное точно так же: снимок для J
, автора и коммиттера (устанавливается любым Gitфактически делает commit J
), отметку времени (аналогично) и родительский хэш-идентификатор commit C
.Но нет второго родителя для нового коммита J
.
Если вы хотите, чтобы коммиты D
через I
исчезли, вы просто удалите ветвь somebranch
. имя somebranch
- это то, как ваш Git находит эти коммиты: он начинает с хеш-идентификатора I
из имени somebranch
, затем использует I
для поиска H
, H
, чтобы найти G
, и так далее, все вниз по линии, пока не достигнете A
.Имя somebranch
позволит вам достичь A
, но и имя master
.Таким образом, удаление name somebranch
делает коммиты D
через I
исчезающими из поля зрения.В конце концов - обычно через 30 дней или около того - ваш Git будет собирать мусор этих коммитов.
Но, опять же, вы упомянули "использование [the] github.com ... UI".Если вы собираетесь это сделать, вы должны сначала отправить коммиты с D
через I
на GitHub, или, возможно, вы уже отправили их.
Передача коммитов с помощью git fetch
и git push
Вы можете в любое время подключить один Git к другому Git и либо получить (fetch
), либо отправить (push
) коммиты с другого Git или на него.Затем из Git вашего собственного компьютера вы получаете коммиты GitHub-Git с git fetch
и отправляете коммиты на , которые Git с git push
.
Реальный протокол передачи имеет многонебольших тонкостей, но все начинается довольно просто: ваш Git и их Git ведут беседу, в которой один рассказывает другому о хеш-идентификаторах коммитов, которые он может отправить, а другой говорит: У меня есть этоодин уже или о, я возьму этот .Итак, предположим, что у вас есть:
...--A
\
D--...--G--H--I <-- somebranch (HEAD)
и у них есть:
B--C <-- master (HEAD)
/
...--A
(различные HEAD
с являютсяВ конце концов, это два разных Гита.Если вы подключите ваш Git к их git push origin somebranch
, ваш Git будет перечислять ваши хэш-идентификаторы коммитов I
, H
и т. Д. Обратно в A
.Когда ваш Git достигнет хеш-идентификатора для A
, они скажут: хорошо, хватит, у меня он есть! Затем ваш Git соберет коммиты с D
через H
и отправит их через.
В конце ваш Git добавляет окончательный запрос: Пожалуйста, укажите название вашей ветви somebranch
, чтобы запомнить хеш-код I
. Их Git не имеет a somebranch
пока, так что они в порядке с этим и делают это, и ваш мерзавец и их мерзавец сделаны, и вы отключаетесь.
Вы можете либо до, либо после этого также запустить git fetch origin
. Вот ваш Git вызывает их Git, но на этот раз их Git является отправителем. Они отправят вам совершить хэш C
, сказав: У моего master
есть этот хеш, у вас есть коммит C
? Если вы этого не сделаете, они также предложат B
, а затем A
, который у вас есть.
Если вы запускаете git fetch origin master
, ваш Git будет принимать только те коммиты, которые они имеют на своих master
, которых у вас нет вообще, но с git fetch origin
ваш Git будет принимать любые коммиты, которых у вас нет из любых имен их ветвей: они перечислят их все вместе с хэш-идентификаторами коммитов, а ваш Git может и будет жадным и попросит их всех.
В любом случае, в конце этого процесса нет отдельного вежливого запроса: ваш Git просто говорит: ОК, теперь я знаю, что их master
идентифицирует коммит C
, поэтому я обновлю my origin/master
, чтобы он идентифицировал commit C
. Итак, вы получите это в своем хранилище:
B--C <-- origin/master
/
...--A <-- master
\
D--...--G--H--I <-- somebranch (HEAD)
Ваш собственный master
теперь на два коммита позади их master
, так что теперь вы можете запустить:
$ git checkout master
, который перемещает HEAD
в master
:
B--C <-- origin/master
/
...--A <-- master (HEAD)
\
D--...--G--H--I <-- somebranch
и затем запустите:
git merge origin/master
(или git merge --ff-only origin/master
). Это замечает, что общей базой слияния между вашим master
и вашим origin/master
является коммит A
- вершина master
- поэтому он выполняет то, что Git называет операция быстрой пересылки , а не настоящее слияние: он просто проверяет фиксацию C
напрямую и перемещает туда master
:
B--C <-- master (HEAD), origin/master
/
...--A
\
D--...--G--H--I <-- somebranch
Обратите внимание, что git fetch
обновляет ваши origin/*
имена в соответствии с их Git, в то время как git push
заканчивается вежливыми запросами в форме: Пожалуйста, установите ваши фактические имена веток для этих конкретных коммитов . Это означает, что вы всегда можете запустить git fetch
: это никак не повлияет на ваши ветви. Но иногда вы не можете достичь простого git push
, поскольку они иногда отказываются менять свои ветви. (Я не буду вдаваться в подробности здесь, так как это уже очень долго, но для этого git push --force
: он изменяет вежливый запрос на команду. Им не нужно подчиняться команде, но они вероятно, будет.)
О git pull
Команда git pull
- нечто странное в Git. Он запускает git fetch
, затем запускает вторую команду Git. Вторая команда обычно git merge
. Вы можете сказать ему использовать git rebase
вместо этого, либо на разовой основе, либо вообще. Я предпочитаю избегать git pull
и запускать эти две команды отдельно, потому что это позволяет мне посмотреть, что git fetch
извлек , прежде чем я выберу любую команду для запуска. Если вы разрешите git pull
запустить вторую команду, вы должны выбрать , какую вторую команду использовать до , вы знаете, что на самом деле git fetch
выбирает.
Но многие люди используют git pull
, поэтому вы должны знать, что он делает. В этом случае это может быть ловушка, как мы увидим.
Допустим, вы сквош-слияние на GitHub
Чтобы сделать сквош-слияние на GitHub, вы должны сначала отправить им все свои somebranch
коммиты:
$ git push origin somebranch
У них теперь есть:
B--C <-- master (HEAD)
/
...--A
\
D--...--G--H--I <-- somebranch
в их Git. Теперь вы делаете пул-запрос с помощью веб-интерфейса GitHub и переходите по кликающим кнопкам, выбираете squash-and-merge и нажимаете его:
B--C----------J <-- master (HEAD), origin/master
/
...--A
\
D--...--G--H--I <-- somebranch
Теперь вам нужно удалить somebranch
, как на GitHub, так и в вашем собственном репозитории Git . Если вы сделаете это, у вас все будет хорошо: 30 (или сколько угодно) коммитов, которые были и на данный момент остаются, только на somebranch
(в обоих Гитах) больше не видны. (Примечание: GitHub стремится немедленно удалить свои копии, в то время как ваш Git ожидает более 30 дней.)
Но если вы или кто-нибудь по этому вопросу, случайно слите somebranch
с master
, вы теперь получите новый коммит слияния:
B--C----------J
/ \
...--A K
\ /
D--...--G--H--I
Обратите внимание, что любой может совершить эту ошибку в любое время после создания J
и до того, как D
-through- I
исчезнет / станет невидимым.Так как git pull
запускает git fetch
, а затем git merge
, делает эту ошибку легко, используя git pull
.
В частности, если вы самостоятельно somebranch
и выполняетеgit pull origin master
, вы получите именно эту ошибку.К счастью, это легко исправить.Теперь у вас есть это:
B--C----------J <-- master
/ \
...--A K <-- somebranch (HEAD)
\ /
D--...--G--H--I
Вам по-прежнему нужно удалить ответвление somebranch
, и теперь коммиты D
через K
невидимы и со временем исчезнут:
$ git checkout master
$ git branch -D somebranch
* * checkout
потому что вы не можете удалить ветку, на которой находитесь;-D
(как бы верхний регистр) - это способ принудительного удаления, хотя git branch
обычно жаловался бы на то, что вы потеряете коммиты D
- через- K
(что, конечно,идея).