Я [вижу], что люди используют возврат, и я не могу понять почему, поскольку это кажется более сложным и более опасным. Почему я должен использовать возврат?
Должен ли это сильное слово.(Не настолько сильный, как должен или должен , но, по крайней мере, достаточно сильный. :-))
... Сброс до первого правильного коммитаи затем принудительно нажмите его еще раз, чтобы удалить ошибочный коммит в удаленной ветви
Всякий раз, когда вам нужно использовать git push --force
или эквивалент, вы перемещаете имя ветви так, как другие люди могли бы не ожидайте.( Может слабее, чем должен: в частичном порядке, я бы сказал может <может <может <должен <должен / должен </em>.) В частности, имена веток Gitестественно двигаться в прогрессии, в которой коммиты добавляются к ветвям, из-за того, как ветки растут при совершении коммитов.
Рассмотрим:
$ git checkout <somebranch>
... work ...
$ git add <files> # or --all or . or whatever
$ git commit
git checkout
step имеет эффект присоединения HEAD
к ветви и копирования tip commit этой ветви , на который указывает имя ветви, в индекс и рабочее дерево вашего Git, чтобы вы могли работать с ним:
... <-F <-G <-H <--branch (HEAD)
Ветвь с именем branch
- ссылка, полное имя которой refs/heads/<em>branch</em>
- хранит необработанный хэш-идентификатор некоторого коммита H
.Коммит H
сам хранит необработанный хэш-идентификатор своего родительского коммита G
, в котором хранится необработанный хэш-идентификатор некоторого коммита F
и т. Д.Итак, мы говорим, что имя указывает на подсказку коммита H
, которая указывает на более ранний коммит G
и т. Д.
Шаг git add
обновляет ваш индекс /staging-area, чтобы он был готов к коммиту, а шаг git commit
создает новый коммит.Новый коммит хранит в качестве родительского хеш-идентификатора хеш-код текущего извлеченного коммита H
.(Разумеется, в качестве снимка хранится замороженный снимок, сделанный из текущей области индекса / промежуточной области.) Затем, в качестве последнего шага, git commit
записывает идентификатор хэша нового коммита в имя ветви, к которой прикреплен HEAD
:
... <-F <-G <-H <-I <--branch (HEAD)
Так растут ветви, по одному коммиту за раз, когда люди делают коммиты.Когда вы объединяете серию коммитов в массовом порядке, либо в виде реального слияния, либо в виде ускоренной операции «не реально слить вообще», ветвь также получает новые коммиты, возможно, сразу много, а может инелинейным образом, но важно то, что новые коммиты всегда ведут к существующим коммитам:
...--F--G--H--I---M <-- master (HEAD)
\ /
J--K--L <-- develop
Добавление коммитов слияния M
к master
оставляет коммиты H
и I
достижимо из master
, потому что мы можем следовать внутренним стрелкам фиксации в обратном направлении, отображаемым здесь в виде линий, потому что стрелки слишком трудно рисовать в тексте сейчас -используя стрелку верхнего ряда из M
.(Стрелка влево и вниз от M
до L
позволяет нам ездить также с M
, скажем, K
или J
. Думай как (а) Git есть хорошая аналогия с транзитной системой в Портленде, хотя любая столичная железнодорожная система похожа.)
Но предположим, что мы делаем это вместо этого:
...--F--G--H--X <-- master (HEAD)
\
J--K <-- develop
и затем понимаем, что, к сожалению, мыозначает поместить коммит X
в develop
.Мы используем любые средства, подходящие для копирования X
для нового коммита, такие как cherry-pick или git rebase --onto
(оба выполняют одну и ту же работу).Затем мы используем git checkout master; git reset --hard master~1
, чтобы оттолкнуть X
с пути, чтобы он больше не включался master
:
X
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
(Здесь L
- копия X
, поместите гдемы хотели этого.) Такого рода движение имен веток оставляет коммит X
висящим без какого-либо способа найти его - по крайней мере, никак не в нашем репозитории.Но если мы уже используем git push
для отправки коммита X
куда-то еще, у некоторого другого Git есть имя для него.Фактически, мы тоже:
X <-- origin/master
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
Наш origin/master
, который является способом поминания нашего Git master
на origin
, все еще помнит, что коммит X
существует.Это означает, что Git origin
помнит X
как на их master
.
, которыефактически, это , почему мы должны использовать git push --force origin master
: чтобы сказать Git в origin
, что он должен отбросить свой коммит X
. Если мы сделаем это раньше, чем кто-либо еще - любой, у кого есть доступ к этому Git - также копирует X
в их репозиторий Git, мы в порядке: никто не видел X
, так что никто не может пострадать, если мы удалим X
.
Проблемы начинают накапливаться, если кто-то еще сделал захватить копию другого Git. Теперь есть третий репозиторий Git, в котором все еще есть коммит X
, возможно, в их master
. Возможно, они построили новые коммиты поверх (их копия) X
, которые они хотят сохранить:
...--F--G--H--X--Y--Z <-- master (HEAD)
Теперь мы скажем им: О, забудь X
, забери его и из своего хранилища. Это требует, чтобы они сделали свои git rebase --onto
или аналогичные, чтобы скопировать их Y
и Z
для новых коммитов, которые больше не возвращаются к X
.
Короче говоря, удаляя X
из нашего Git и из origin
Git, мы обременяем всех, кто разделяет эти репозитории Git: они тоже все должны удалить свои X
и справиться с любыми последствиями.
Существуют проекты, в которых все согласны с тем, что это может произойти - либо в любое время с любой веткой, либо в любое время с некоторым конкретным подмножеством (ами) ветвей. В этих проектах сброс ветвей и принудительное нажатие - это нормально. Есть проекты, в которых нет других пользователей или где вы можете принудительно нажать, прежде чем кто-либо сможет обнаружить ошибку; в этих случаях сброс и принудительное нажатие также хороши. Проблемы возникают, когда вы начинаете много работать для людей, которые не готовы это делать. В этом случае создание нового коммита, который просто un-делает работу в X
дает им возможность включить эту новую работу таким образом, что они являются готов принять.