TL; DR
Проблема в этом случае была из-за неправильного понимания опции --all
для git push
, что означает pu sh все ветви, которые у меня есть (против pu sh все проверенные филиалы ). Во-первых, вы не хотели, чтобы локальное имя develop
было первым, и ( эта часть важна ) не возражали отказаться от коммитов, которые были доступны только из имени develop
, поэтому удалили имя develop
решил вашу непосредственную проблему:
git branch -D develop # this is a force-delete, skipping safety checks
Здесь есть несколько полезных предостережений, заметок и исторических вещей, о которых стоит упомянуть.
Long
Текст вопроса выше показывает что у вас есть довольно современный Git с git worktree
в качестве доступной команды. В частности, этот список git branch
:
$ git branch
develop
* master
+ foo
+ bar
указывает на наличие четырех (локальных) имен филиалов: develop
, master
, foo
и bar
. 1 Звездочка *
указывает, что ветвь master
является именем текущей ветки в главном рабочем дереве. Два знака плюс +
указывают, что ветви foo
и bar
каждая отмечены по имени в каждом из двух добавленных рабочих деревьев. Пробел перед develop
показывает, что хотя локальная ветвь develop
существует, она нигде не проверяется.
Версии Git, предшествующие Git 2.5, отсутствуют git worktree add
и могут иметь максимум одна ветка выписана в любое время. Однако может существовать любое количество (локальных) названий ветвей.
1 foo
и bar
явно заменяют, так как git branch
по умолчанию сортирует по алфавиту, и эти в неправильном порядке.
Примечание: все филиалы являются локальными
В то время как Git имеет пультов как origin
и remote- имена отслеживания как origin/master
, все локальные имена ветвей являются локальными, и фактически слово «local» перед «ответвлением name» является избыточным.
Способ думать об этом можно так:
Git является распределенной системой контроля версий (DVCS), в отличие от централизованной VCS как Subversion. В централизованной VCS (CVCS) где-то есть главный сервер. Он содержит репозиторий . У вас нет хранилища на вашем компьютере. Вы просто извлекаете кучу файлов из CVCS. Вы работаете с этими файлами, затем помещаете эти файлы обратно в (центральную) VCS.
С DVCS вы фактически получаете часть или весь репозиторий: дубликат или клон . По умолчанию с Git, и способ думать об этом, это то, что вы получаете полный клон: копию каждой версии, которую также имеет другая копия каждой версии.
В любой распределенной системе дистрибутивы могут либо постоянно общаться друг с другом, либо синхронизироваться при определенных c выделенных встречах. Git (как и многие другие DVCS) выбирает последнее, потому что оно должно: сети и компьютеры, через которые мы совместно используем репозитории Git, не всегда работают или могут не предоставлять все разрешения всем или что-либо еще. Поэтому нам необходимы выделенные повторные синхронизации.
События синхронизации для Git происходят с использованием git fetch
и git push
, которые не совсем симметричны c. Подробнее об этом мы узнаем чуть позже.
Будучи хранилищем, Git хранит множество версий файлов. Различные VCS используют разные методы для этого. В случае Git единицей хранения здесь является commit . То есть Git не хранит файлы , точно. Git сохраняет коммитов , а затем каждый коммит содержит некоторый набор файлов. Единицей упаковки является коммит: у вас либо весь коммит, либо его нет.
Это означает, что когда вы соединяете два Gits друг с другом, чтобы получить их к share , уровень, на котором они делятся вещами - это commit . Для этого каждый коммит имеет свое уникальное имя. Имя коммита - это его sh ID. Когда один Git имеет фиксацию для другого Git, отправитель предлагает фиксацию получателю по имени - свой идентификатор sh. Приемник смотрит в свою базу данных: У меня есть этот ha sh ID? Если так, у меня есть этот коммит: скажите отправителю, нет, спасибо, у меня уже есть. Если нет, то я не отвечаю: скажите отправителю, пожалуйста, отправьте это.
Обратите внимание, что эти идентификаторы ha sh универсальны. То есть каждый Git использует один и тот же га sh ID для одного и того же коммита. Вот почему можно проверить по номеру ha sh ID.
В Git каждый коммит перечисляет свой непосредственный предыдущий или родительский коммит (ы) по их га sh идентификаторы. Они позволяют Git формировать цепочки коммитов, обращенные назад:
... <-F <-G <-H ...
, где некоторые га sh ID H
дают Git путь к sh, которые фиксируются из его базы данных , Затем сам коммит содержит идентификатор ha sh более раннего коммита G
, который позволяет Git fi sh зафиксировать коммит из его базы данных, который находит идентификатор ha sh еще более раннего коммита F
и т. д.
В Git, names - не просто имена ветвей, хотя мы просто рассмотрим здесь имена ветвей - просто держите ha sh ID один из этих коммитов. Этого достаточно, поскольку, пока имя ветви содержит идентификатор ha sh для last commit ветви, Git может найти все предыдущие, используя цепочки:
...--F--G--H <-- master
\
I--J <-- develop
Здесь H
обозначает последний коммит в master
, тогда как J
обозначает последний коммит в develop
.
Каждый Git репозиторий имеет свой собственный набор имен , потому что каждый Git репозиторий может добавить в ветку! Когда вы git checkout
ветвь по имени, вы запрашиваете у Git:
- взять все файлы из , которые фиксируются (в хранилище глубокой заморозки), и поместить их в рабочую область;
- позволяет работать с файлами; и
- позволяют вам сделать новый коммит из обновленных файлов
, и когда вы сделаете это, вы получите новый коммит, добавленный к филиал. Новый коммит автоматически указывает на то, что было последним коммитом, например go:
...--F--G--H--K <-- master
\
I--J <-- develop
, если вы только что добавили новый коммит для master
.
Следовательно, имена ветвей вашего Git полностью отделены от их Git имен ветвей, независимо от кто они могут быть. Ваши имена ветвей помнят ваши последние коммиты. Их помнят их. Если они одинаковы, это нормально. Если нет, то вот где выборка и / или pu sh имеют тенденцию появляться на рисунке.
Таким образом, все имена ветвей всегда локальны. Они находят последние коммиты, из которых Git находит более ранние коммиты.
Когда вы получаете кучу коммитов от другого Git - давайте назовем этот другой Git origin
, как мы обычно делаем, - этот other Git имел свои имена ветвей. Вот как они нашли коммиты, которые они дали вашему Git. Ваш Git теперь умно запоминает имена своих ветвей, используя ваши имена для удаленного отслеживания . Их master
становится вашим origin/master
. Их develop
становится вашим origin/develop
.
Это означает, что вам не нужны любые имена ветвей, чтобы запомнить эти коммиты. Ваши имена для удаленного отслеживания достаточны:
...--F--G--H--K <-- origin/master
\
I--J <-- origin/develop
Но вы не можете сделать новые фиксации с использованием имени для удаленного отслеживания. Вы можете делать новые коммиты только для имени ветви. 2 Так что теперь вам нужно создать несколько названий веток, если вы хотите сделать новые коммиты.
2 Технически, вы можете делать новые коммиты, когда не в ветке, в режиме Git detached HEAD . Но это не делает их по имени удаленного отслеживания, это просто делает их в режиме отсоединенного HEAD. Как только вы переключаетесь обратно на имя ветки, коммиты, сделанные вами таким образом, становятся трудными для поиска, и в итоге Git просто удаляет их как неиспользованные. Таким образом, вы обычно не хотите работать таким образом вообще. Режим Detaded HEAD в основном предназначен для внутреннего использования Git.
Рабочее дерево может быть только в одной ветви
Git коммиты навсегда заморожены: только для чтения за все время. Буквально невозможно изменить какую-либо часть любого коммита, даже для самого Git. 3 Они также находятся в специальной, сжатой - иногда сильно сжатой - Git -только форме. Таким образом, чтобы выполнить любую реальную работу, Git должен извлечь файлы из коммита и поместить их в обычную повседневную форму.
Обычная форма, где ваши файлы - просто файлы, а не особые странные Git - только то, что вы найдете в своем рабочем дереве, или - с момента появления git worktree add
- любое рабочее дерево, включая добавленные. Чтобы заполнить рабочее дерево, вы выбираете коммит, обычно выбирая имя ветви, и переключаетесь на этот коммит:
git switch <name>
или:
git checkout <name>
Git удаляет, если необходимо, все старые файлы, связанные с предыдущей проверкой, и переключается на файлы, связанные с last в указанной ветви. Теперь вы находитесь «на» этой ветви, в которой git status
скажет on branch master
или что-то еще, а git branch
пометит название этой ветви звездочкой *
или знаком плюс +
в зависимости от ситуации.
Акт переключения на имя ветви может привести к созданию этого имени ветви. Git просто пытается быть умным и полезным. Если вы скажете:
git checkout develop
, а у вас нет a develop
, но у вас do есть имя для удаленного отслеживания origin/develop
, ваш Git скажет себе: ага, у меня этого нет, но у меня есть кое-что, что может стать , и теперь создать ваше собственное имя ветви develop
, указывающее на тот же коммит, что и ваш origin/develop
:
...--F--G--H--K <-- master, origin/master
\
I--J <-- develop (HEAD), origin/develop
Здесь HEAD
указывает, что, по крайней мере в этом рабочем дереве, это имя ветви, которым вы сейчас являетесь использование.
Когда вы впервые клонируете репозиторий, Git автоматически делает git checkout master
, или git checkout
(или git switch
) другого имени, которое вы выбираете каким-либо образом. Это автоматически создает имя первого ветвления в новом клоне, и поэтому у меня есть master
, указывающий на коммит K
выше.
По различным важным внутренним причинам каждое рабочее дерево должно находиться на другое имя ветви или использование отдельного HEAD (либо все в порядке, так что вы можете иметь столько добавленных рабочих деревьев на отдельных заголовках, сколько захотите). Все рабочие деревья совместно используют набор доступных имен ветвей, и создание нового рабочего дерева может создать новое имя таким же образом, переключаясь на ветку в вашем основном рабочем дереве, созданном develop
здесь.
Добавленные рабочие деревья имеют общие имена веток для всего репозитория , поскольку все они используют один и тот же репозиторий. Стандартный репозиторий поставляется с:
- Большой базой данных коммитов и другими Git объектами, именуемыми ha sh ID.
- Меньшей базой данных имен - именами ветвей, имена для удаленного отслеживания и т. д., которые дают идентификатор ha sh one commit. Для имени ветви это последний коммит в ветви. Для имени удаленного слежения это память вашего Git другого ветвления Git, когда в последний раз Git говорил с этим Git (см. Ниже).
- Одна пара индекса и рабочего дерева.
Используя git worktree add
, вы добавляете больше пар индекса и рабочего дерева в хранилище. Это позволяет вам иметь одновременно более одного коммита и / или ветки. Мы не упомянули индекс (и не будем go подробно останавливаться здесь), но он действительно связан с рабочим деревом. Так же как и понятие HEAD
: каждое добавленное рабочее дерево имеет свое собственное HEAD
.
Каждый раз, когда вы cd
входите или выходите из какого-либо одного рабочего дерева, вы изменяете пару индекс-и-рабочее дерево и переключаетесь на другое HEAD
. Существуют и другие специальные ссылочные имена , такие как ORIG_HEAD
и MERGE_HEAD
, которые также относятся к дереву для каждой работы. Но имена branch являются общими.
3 Реальная причина этого состояния только для чтения связана с этими идентификаторами ha sh, хотя здесь мы не будем раскрывать эти детали.
Передача коммитов и получение / установка имен с помощью fetch и pu sh
Как я уже упоминал выше, git fetch
и git push
позволит вам подключить ваш Git к другому Git. Это позволяет получать от них коммиты (fetch
) или отправлять коммиты (push
). Детали могут быть очень сложными, но с точки зрения высокого уровня, это действительно довольно просто.
Эти два не симметричны c!
Когда вы git fetch
, ваш Git звонит другой Git. Другой Git перечисляет имена его ветвей (и другие имена), и идентификаторы ha sh для каждого из last фиксируют в этих ветвях (и любые другие сохраненные идентификаторы ha sh). Ваш Git затем проверяет, для каждого pair:
- Хочу ли я обновить свою копию (
origin/master
et c)? - Если да, у меня есть этот коммит?
Другой Git предложит чаевые коммитов всех веток по своим sh идентификаторам, и ваш Git либо скажет да, пожалуйста или нет, спасибо, уже есть один в зависимости от обстоятельств. Если ваш Git запрашивает коммит, его Git обязан сказать ... и его / ее родитель (ы) являются / являются этими другими га sh идентификаторами , и ваш Git может попросить для них и их родителей и т. д.
Как только ваш Git запросит коммиты, они упаковывают их - counting
и compressing
и т. д. - и отправляют их. Ваш Git теперь имеет коммиты и с обновленными именами веток и может обновлять ваши имена для удаленного отслеживания.
Действие по умолчанию для git fetch origin
- получить все его филиалов и обновите всех ваших имен для удаленного отслеживания. Так как они не предназначены для , которые вы должны обновлять, можно просто обновить их все сейчас. Вы не можете «включить» имя удаленного слежения: попытка git checkout origin/master
просто переводит вас в режим Git detached HEAD . Поэтому, если origin/master
переместит , чтобы указать на какой-либо другой коммит, это не повлияет на ваш извлечение.
Операция извлечения - односторонняя передача: от них к вам. Вы не не даете им никаких коммитов, хотя вы говорите им, что вы не хотите определенных коммитов, подразумевая, что они у вас уже есть. Затем, получив все их имена веток, ваш Git обновит все ваши имена для удаленного отслеживания. Опция --all
не требуется: --all
is automati c.
Операция pu sh настолько близка, насколько вы добираетесь до противоположности, но она не совсем противоположна. Ваш Git вызывает их Git. Обычно вам не нужен список из их названий ветвей. 4 Вы просто начинаете предлагать им коммиты. Например, если вы запустите:
git push origin develop
, ваш Git предложит коммит последний в ваш develop
, по ha sh Я БЫ. Они проверяют, чтобы увидеть: есть ли у меня этот коммит? Если нет, они просят вашего Git отправить его, а ваш Git предлагает своего родителя, и они проверяют это и так далее. В конце концов вы и они находите какой-то коммит, который у них уже есть, и вам не нужно отправлять этот коммит или что-либо, что предшествует ему: у них уже есть всех из них.
Предположим, вы запустите:
git push origin develop foo bar
Предполагая, что develop
, foo
и bar
все перечисляют разные идентификаторы ha sh, ваш Git теперь будет предлагать свои три коммитов изначально , У вас будет Git добавление родительских коммитов, пока они не достигнут коммитов, которые у них уже есть.
В любом случае, ваш Git теперь упаковывает эти коммиты (counting
, compressing
, et c) и отправляет посылку. Тогда - вот где pu sh так сильно отличается от fetch - ваш Git отправляет несколько вежливых запросов:
- Пожалуйста, если все в порядке, установите вашу ветку имя
develop
- _____ (га sh ID). - Пожалуйста, если все в порядке, установите ваше имя филиала
foo
в _____ (ha sh ID). - Пожалуйста, если все в порядке, установите ваше имя ветви
bar
на _____ (ha sh ID).
Поэтому важно, какой имена , которые вы используете на своей стороне, потому что вы попросите их установить одинаковые имена на их стороне.
Pu sh и Таким образом, выборки не симметричны c двумя разными способами:
- Очевидным является направление передачи: выборка означает получить от них материал и pu sh означает отправлять им сообщения .
- Менее очевидным является то, что fetch "безопасен": он обновляет ваши имена для удаленного слежения . Pu sh не так безопасен: он просит их напрямую обновить имена branch . Если они добавили коммиты с момента последнего разговора Git с их Git, у них может быть новый последний коммит. Для них не будет нормальным менять свое имя, потому что, если они изменят, какой коммит - последний , возможно, некоторые коммиты «упадут» в конце.
Чтобы понять, почему это так, рассмотрим эту ситуацию. Вы начинаете с клона fre sh и получаете:
...--G--H <-- master, origin/master
Вы добавляете новый коммит I
к вашему master
, но пока вы это делаете, они добавляют новый коммит J
к их master
. Если вы запустите git fetch
сейчас, вы возьмете их J
и настроите свой собственный origin/master
, чтобы запомнить его, и теперь у вас будет:
I <-- master
/
...--G--H
\
J <-- origin/master
Вы, безусловно, можете отправить им коммит I
. Но затем вы говорите им: Теперь укажите master
на I
, хорошо? Если они это сделают, они не смогут найти совершить J
больше. Он «упадет с конца», потому что I
указывает на H
, а не на J
; ничто больше не будет указывать на J
.
Они ответят на ваш вежливый запрос: Нет, все не в порядке! Отклонено: не ускоренная перемотка вперед.
4 Если вы запросили пу sh в стиле * 1518, ваш Git делает нужен список имен их ветвей.
Исторические сведения
В Git версиях до Git 2.0, git push
по умолчанию используется нажатие соответствия филиалов. То есть он будет перечислять ваши ветви, перечислять их и видеть, какие имена совпадают. Затем он вежливо попросит их Git установить все этих имен на основе всех ваших имен (конечно, после отправки любых необходимых коммитов).
С Git 2.0, git push
по умолчанию используется режим с именем simple
. Это означает, что по умолчанию используется только ветка current , и даже тогда требуется, чтобы ваши Git и их Git использовали то же имя , а также некоторые другие требования чтобы избежать обычных Git -newb ie ловушек.
Чтобы вы могли легко отправлять все имена ваших филиалов, git push
имеет опцию --all
. Это означает все ветви (не все ссылки - которые будут включать теги - но конкретно только ветви). Неважно, сколько проверено. В Git до версии 2.5 только один может быть извлечен в любом случае.
Между тем, git fetch
всегда по умолчанию извлекал все их имена ветвей на ваш собственный имена для удаленного слежения. 5 Так что нет необходимости в опции --all
, чтобы обозначать все ветви . Но git fetch
имеет опцию --all
: это означает все пульты . Это имеет значение, только если у вас более одного пульта. Вы можете добавить дополнительные пульты, используя git remote add
, но после первоначального клонирования большинство людей имеют только один пульт и никогда больше не добавляют, поэтому в большинстве случаев git fetch --all
не делает ничего полезного.
5 Технически это регулируется настройкой remote.<em>name</em>.fetch
, где name
- имя пульта. Но по умолчанию это «все имена ветвей». Был ранний период, задолго до Git 1,7, когда детали для этого тоже менялись, но они довольно быстро стабилизировались, и никто, кажется, не использует Git, предшествующий 1,7.
Заключение
Если вы хотите использовать опцию --all
, вам нужно тщательно контролировать набор ветвей, который у вас есть. Вам нужно только имя ветки, если вы планируете использовать его для совершения новых фиксаций с течением времени, но помните, что имена ваших веток - это основной способ, которым ваши Git находят ваши коммиты , Если вы намереваетесь удалить один, убедитесь, что все в порядке, чтобы удалить его сейчас: вы отправили свои коммиты в какой-то другой клон того же репозитория Git, если хотите сохранить их.