Есть несколько вещей, происходящих более или менее одновременно, что может быть довольно запутанным. Чтобы сохранить все как есть, начните с этого понятия: Git не относится к файлам. Git составляет около коммитов . Коммиты содержат файлов, поэтому в результате Git будет хранить файлы;но Git заботится о коммитах с их большими уродливыми хеш-идентификаторами.
Вы видели хеш-идентификаторы коммитов в выводе git log
, вероятно. Например, они выглядят как bc12974a897308fd3254cf0cc90319078fe45eea
- это коммит в Git-репозитории для самого Git. Каждый коммит получает свой уникальный хэш-идентификатор. Никакой другой коммит нигде и никогда не может использовать этот хэш-идентификатор. Git взял это для себя и теперь , что коммит. Все ваши коммиты будут иметь разные хеш-идентификаторы. 1 Каждый Git везде автоматически соглашается с каждым другим Git везде, какой хеш-идентификатор для каждого коммита после того, как он сделан, и без хеш-идентификатора коммитакогда-либо меняется, так что bc12974a8973...
всегда и навсегда является тем конкретным коммитом в Git-репозитории для Git.
Branch names , например fb1
или fb3
, 2 входят в картину, потому что простые люди не могут вспомнить или ввести большие уродливые хэш-идентификаторы. Я должен был вырезать и вставить этот, чтобы сделать это правильно. Эти имена позволяют вам - и Git - найти хеш-идентификаторы.
Вы можете создавать и уничтожать ветки в любое время. Чтобы создать ветку, вы выбираете некоторую фиксацию - какой-то хеш-идентификатор, который вы можете найти по другому имени или запустив git log
и вырезая и вставляя необработанный хеш-идентификатор - и говорите Git: Создайте новое имя,и в этом имени сохраните этот здесь необработанный хэш-идентификатор:
git branch gitty bc12974a897308fd3254cf0cc90319078fe45eea
назначает вышеуказанный хэш-идентификатор имени gitty
. (Ваш Git гарантирует, что этот хэш-идентификатор действительно существует в вашем хранилище и является хеш-идентификатором коммита. 3 ) Если вы используете другое имя:
git branch gitty master
Git first turnимя master
в необработанный хэш-идентификатор, затем помещает необработанный хеш-идентификатор в новое имя gitty
.
Чтобы узнать, какой хеш-идентификатор представляет любое имя в данный момент, используйте git rev-parse
:
$ git rev-parse master
bc12974a897308fd3254cf0cc90319078fe45eea
Вот так я и получил большой уродливый идентификатор хеша, указанный выше.
1 Технически, ему разрешено повторно использовать хэш-идентификаторы, пока два Gits с повторно используемыми идентификаторами никогда не встречаются друг с другом. Так что, если вы никогда не попытаетесь объединить Git-репозиторий для Git с вашим собственным Git-репозиторием, что бы вы ни делали, ваш Git может повторно использовать этот хэш-идентификатор. Вероятность фактического хеш-коллизии крайне мала, за исключением случаев, когда кто-то намеренно форсирует коллизию. См. Также Как недавно обнаруженное столкновение SHA-1 влияет на Git?
2 Я поместил их в нижний регистр. В общем случае неразумно смешивать имена в верхнем и нижнем регистре для имен веток и файлов, если вы хотите, чтобы ваш репозиторий работал правильно на большинстве систем MacOS и Windows.
3 Git использует хэш-идентификаторыбольше, чем просто коммиты. Например, они также определяют конкретное содержимое определенных версий файлов. Следовательно, когда вы делаете тысячи коммитов, которые в основном повторно используют большинство файлов, новые коммиты просто возвращаются к уже существующим, уже зафиксированным файлам.
Нарисуйте картинку
Несмотря на то, что эти хэш-идентификаторы действительно конкретны, они не нужны людям: мы просто не можем держать их все прямо. Опять же, это , почему мы используем ветку names . Но внутри Git Git использует хэш-идентификаторы. Это будет иметь больше смысла в вашей собственной голове, если вы рисуете картинки.
ПомнитеТо, что каждый коммит содержит снимок - набор файлов, как бы они ни выглядели в тот момент, когда вы сделали этот коммит, - но он также содержит некоторую дополнительную информацию, которую Git называет метаданные ,Эти метаданные включают имя и адрес электронной почты лица, сделавшего коммит, а также отметку даты и времени для коммита. Он включает в себя сообщение журнала: щелкните ссылку на копию GitHub этого коммита Git, и вы увидите, что строка его темы - Fourth batch
(а остальное - просто строка подписи - не самое лучшее сообщение о коммите, но оно может быть хуже 4 ). Возможно, наиболее важной частью метаданных коммита является то, что каждый коммит содержит необработанные хэш-идентификаторы своих родительских коммитов.
Когда выполняется одна вещьхеш-идентификатор другого коммита, мы говорим, что первое указывает на второй коммит. Так что, если имя fb1
содержит хеш-идентификатор коммита, имя ветви fb1
указывает на коммит. Этот коммит сам содержит хэш-идентификатор родительского коммита. Родительский коммит содержит хеш-идентификатор своего родителя и т. Д .:
... <--o <--o <--o <--fb1
, где каждый раунд o
представляет коммит.
Все эти стрелки идуттолько один путь - назад - так по определению, имя ветки указывает на последний коммит в цепочке коммитов. У самих коммитов есть внутренние стрелки назад, которые связывают их вместе.
Когда вы делаете ветку и начинаете коммитить, Git заставляет новые коммиты указывать назад на более старые коммиты. Затем он перемещает имя ветви вперед , чтобы оно указывало на новый коммит. Давайте нарисуем это, используя одиночные заглавные буквы для замены хеш-идентификаторов коммитов (поскольку хеш-идентификаторы слишком большие и некрасивые):
...-F--G--H <-- master, fb1 (HEAD)
Я опишу, что слово (HEAD)
здесь делает простомомент. Здесь для удобства я нарисовал все внутренние стрелки просто как соединительные линии. Мы можем просто держать в наших умах, что они указывают назад;Большую часть времени нам не о чем беспокоиться.
Мы только что создали fb1
из master
, поэтому оба имени указывают на один и тот же коммит , помеченныйH
(для хэш-идентификатора) здесь. Обратите внимание, что все коммиты находятся в обеих ветках: это странная вещь, которую делает Git, чего не делают другие системы контроля версий. Коммиты находятся в нескольких ветвях одновременно.
Теперь давайте сделаем новый коммит, который мы назовем I
. Мы выполняем обычный шаг беспорядок с некоторыми файлами, запускаем на них git add
, запускаем git commit
и предоставляем Git сообщение журнала. Git упаковывает новый снимок (всех файлов, а не только тех, которые мы изменили) и устанавливает родителя нового коммита равным H
, так что новый коммит I
указывает на H
:
...-F--G--H <-- master, fb1 (HEAD)
\
I
Теперь Git обновляет имя ветки, чтобы оно указывало на I
. Какое имя ветви обновляет Git? Вот тут и приходит HEAD
. HEAD
сообщает Git , какую ветку мы проверили . У нас fb1
проверено, и, следовательно, было commit H
. Теперь, когда Git создал новый коммит I
, Git обновляет имя , к которому прикреплен HEAD
, так что мы получаем:
...-F--G--H <-- master
\
I <-- fb1 (HEAD)
Теперь мы создали новыйсовершить на ветке fb1
. Коммиты до H
все еще находятся на обеих ветвях. Коммит I
, новый, имеет только на fb1
.
Если мы сделаем больше коммитов, они также будут только на fb1
:
...-F--G--H <-- master
\
I--J <-- fb1 (HEAD)
4 В комиксе xkcd новые коммиты направлены вниз. В выводе git log --graph --oneline
более новые коммиты идут вверх. В способе рисования фрагментов графа коммитов в StackOverflow новые коммиты направлены вправо. Рисунки графиков можно ориентировать любым способом, который помогает вам их просматривать, поэтому выбирайте любой способ, который вам подходит.
Ветви - это здорово, давайте сделаем больше!
Сейчасчто у нас есть fb1
с парой коммитов, давайте вернемся к master
и создадим новую ветку fb2
. Мы можем сделать это любым количеством способов, включая:
git checkout master
git branch fb2
git checkout fb2
или более просто:
git checkout -b fb2 master
, который делает все это за один шаг. В результате наш график вообще не изменяется, но мы добавляем новое имя , fb2
и добавляем туда HEAD
:
...-F--G--H <-- master, fb2 (HEAD)
\
I--J <-- fb1
Теперь у нас зафиксирован коммит H
с новым именем fb2
. Если мы запустим git log
, мы увидим коммит H
, затем коммит G
, затем коммит F
и т. Д. git log
начинается там, где мы находимся, затем следует внутренним стрелкам назад.
Обратите внимание, что коммиты I
и J
все еще находятся в нашем хранилище, и мы все еще можем их найти: имя fb1
находит коммит J
, а из коммита J
мы можем отступить черезвнутренняя стрелка, указывающая назад, чтобы найти I
. Обычный git log
не будет отображать эти два коммита: нам нужно запустить git log fb1
, чтобы увидеть их, чтобы git log
начинался с коммита J
, обозначенного именем fb1
, а не с HEAD
и, следовательно, найти H
.
Теперь, когда мы находимся в этой новой позиции, давайте сделаем два новых коммита. Они получат свои собственные уникальные, большие уродливые хэш-идентификаторы, но мы просто назовем их K
и L
. Давайте нарисуем их - и для удобства давайте перевернем fb1
в верхний ряд:
I--J <-- fb1
/
...-F--G--H <-- master
\
K--L <-- fb2 (HEAD)
Теперь у нас есть три ветки в нашем собственном репозитории Git, найденные по трем именам fb1
, fb2
,и master
. Эти три ветви находят три коммита, но каждый из этих трех коммитов находит больше коммитов: Git начинает с того коммита, который мы ему сообщаем, затем идет назад, следуя внутренним стрелкам, направленным назад.
Потому что коммит H
является родителем I
и K
, мы находим коммит H
три раза. Мы находим его родителя, G
, тоже три раза и так далее для всех коммитов, стоящих за ним. Все эти коммиты теперь находятся на всех трех ветвях. Но коммиты I-J
имеют значение только на ветви fb1
, а коммиты K-L
имеют только на ветви fb2
.
Удаление имен
Предположим, мы должны были удалить полностью master
имя, используя git branch -d master
:
I--J <-- fb1
/
...-F--G--H
\
K--L <-- fb2 (HEAD)
Теперь у нас нет ветви с именем master
. Но у нас все еще есть все его коммитов . Их по-прежнему легко найти: мы можем начать с fb1
(коммит J
) или fb2
(коммит L
) и вернуться к H
, и есть H
. Таким образом, все эти коммиты все еще там, все еще в двух ветвях. Они были на трех ветвях раньше;теперь они на двоих.
Мы можем вернуть master
, используя git branch master <hash-of-H>
(вырезать и вставить идентификатор хеша из git log
), а затем сказать Git удалить имя fb1
. В любом случае, сейчас мы вообще не можем удалить fb2
, потому что мы стоим на нем (HEAD
прикреплен к fb2
). Мы должны сказать Git принудительно удалить fb1
, потому что посмотрите, что произойдет, если мы сделали:
I--J ???
/
...-F--G--H <-- master
\
K--L <-- fb2 (HEAD)
Как только имя fb1
исчезнет, как мы будемнайти коммит J
? Мы зависим от поиска коммита J
, чтобы найти коммит I
тоже. Мы все еще можем найти L
и K
и H
и т. Д., Но если мы удалим name fb1
, мы потеряем идентификатор хеша для последнего коммита в этой ветви.
Поскольку Git - это всего лишь , фиксирует , Git не позволит нам удалить fb1
, не форсируя его, и, конечно, мы не должны. Однако, если fb1
все еще указывает на H
- так что мы можем найти коммит H
через fb2
- Git будет , давайте удалим fb1
, не форсируя его.
Использование другого Git: git fetch
Все вышеперечисленное описывает, как мы используем наш Git-репозиторий локально. Но в вашем случае у вас есть другой репозиторий Git, хранящийся на GitHub. Ваш Git называет этот другой Git origin
- это, в основном, короткое имя для URL, который вы используете, чтобы ваш Git говорил с Git на GitHub.
Вы можете запустить:
git fetch origin
впусть ваш Git вызывает их Git. Ваш Git использует URL-адрес, сохраненный в имени origin
- Git называет эту вещь remote , и это просто короткое имя для URL-адреса и некоторых других полезных данных - чтобы позвонить своему Git и вашему Git иих Git имеют разговор. Их Git перечисляет все их имена веток и их необработанные хэш-идентификаторы коммитов. Ваш Git проверяет, есть ли у вас эти коммиты или нет. Если у вас нет коммитов - помните, хеш-идентификаторы коммитов универсальны для каждого Git, так что ваш Git может просто проверить по ID - ваш Git затем попросит их Gitупаковать эти коммиты. Кроме того, ваш Git получит все файлы этих коммитов, их родительские коммиты и их файлы и так далее. Ваш Git и его Git общаются друг с другом, чтобы выяснить, какие коммиты у вас уже есть, и, следовательно, не нужны, чтобы они отправляли вам только то, что вам нужно. какими бы ни были их хеш-идентификаторы - теперь они хранятся в Git. Но ваш Git должен иметь какое-то имя для последнего нового коммита в любой обновленной ветке. Именно здесь приходят ваши имена для удаленного отслеживания .
Скажем, например, что они добавили коммит к своему мастеру. Мы назовем этот коммит M
:
I--J <-- fb1
/
...-F--G--H <-- master
\
\--M <-- ???
\
K--L <-- fb2 (HEAD)
Какое имя будет использовать ваш Git, чтобы запомнить их master
? Ответ здесь заключается в том, что ваш Git создает ваше собственное имя origin/master
в отдельном пространстве имен 5 , так что мы получаем:
I--J <-- fb1
/
...-F--G--H <-- master
\
\--M <-- origin/master
\
K--L <-- fb2 (HEAD)
Фактически, вы уже имел и origin/master
, указывая на H
ранее. Мы просто не нарисовали его!
Теперь, если вы создали fb1
в Git над origin
и сделали git fetch
, вы также получите origin/fb1
. Это будет указывать на то, какой коммит fb1
на Git Gub указывает на.
Если кто-то еще, кто также может манипулировать GitHub Git, перемещает или удаляет свои fb1
, у вашего Git все еще есть свой fb1
. Это никогда не исчезнет само по себе: это ваша ветвь, и вы управляете ею. Ваш origin/fb1
может двигаться, и если вы установите свой Git на prune имена для удаленного слежения, ваш origin/fb1
может полностью исчезнуть. Но ваш fb1
, ваша ветвь, находится под вашим контролем. Вы выбираете, как переместить или удалить его.
5 Полное имя ветви в вашем хранилище начинается с refs/heads/
. Полное имя имя для удаленного отслеживания origin/master
равно refs/remotes/origin/master
. Это не начинается с refs/heads/
, поэтому вы можете создать свое собственное имя локальной ветви, refs/heads/origin/master
, которое отличается от вашего имени для удаленного отслеживания.
Не делайте этого! Он не сломается Git , но может сломаться вы . :-) Если вы случайно сделали это, переименуйте вашу локальную ветку, чтобы ее имя больше не начиналось с origin/
, чтобы сбить с толку.
Как насчет git push
?
Команда git push
в основном противоположна git fetch
: ваш Git вызывает их Git, а затем вместо получения новых коммитов от них вы даете новые коммиты до их.
Это не совсем противоположно. Когда вы получаете новые коммиты от , ваш Git автоматически создает или обновляет ваши имена для удаленного отслеживания, так что у вас есть имена origin/*
, по которым можно найти их новые коммиты. Но когда вы git push
фиксируете для их, вы просите них установить их ветвь имён.
Итакнапример, если вы git push
зафиксировали I
и J
, вы почти наверняка захотите попросить их установить их имя ветви fb1
в соответствии с вашим.
Давайтескажем, у нас есть коммит M
, и мы уже обновили наш собственный master
, чтобы включить его. У них есть fb1
, указывающие на коммит H
, и у них еще нет fb2
, так что в нашем репозитории мы имеем этот график:
I--J <-- fb1
/
...-F--G--H--M <-- master, origin/master, origin/fb1
\
K--L <-- fb2 (HEAD)
Теперь мы можем запустить:
git push origin fb1
Это отправит коммиты I
и J
им: у них их нет, но они нужны для перемещенияих fb1
, чтобы указать на J
. Тогда наш Git спросит их Git: Пожалуйста, если все в порядке, установите ваш fb1
сейчас так, чтобы он указывал на J
.
В этом случае это будет быть хорошо. Они установят свои fb1
, чтобы указать J
. Ваш Git подтвердит это, обновив свой собственный origin/fb1
, и теперь у вас есть:
I--J <-- fb1, origin/fb1
/
...-F--G--H--M <-- master, origin/master
\
K--L <-- fb2 (HEAD)
в вашем хранилище. У них просто есть master
, идентифицирующие M
и fb1
, идентифицирующие J
;у них еще нет fb2
.
А как насчет git pull
?
Команда git pull
- это просто сокращение для:
- run
git fetch
; - затем выполните вторую команду Git, обычно
git merge
, но вы можете выбрать git rebase
.
Я советую новичкам делать Git, чтобы избегать git pull
, потому что он скрывает этот двухэтапный процесс и оставляет их в безвыходном положении, когда по какой-то причине не удается выполнить команду second . команда со сложными режимами сбоев.
Rebase в некотором смысле хуже: в действительности, это повторяющаяся серия команд git cherry-pick
(за которыми следует одно перемещение метки ветвления), и каждый является формой слияния. Так что, если слияние может потерпеть неудачу, то и каждый вишнёк. Если существует пять коммитов для выбора вишни, чтобы выполнить перебазирование, есть пять шагов, которые могут потерпеть неудачу!
Большинство слияний действительно успешны сами по себе, и значительное количество перебазировок работает без проблем. Но если один действительно терпит неудачу, важно знать, что вы делали, чтобы вы знали, где искать помощь.
Можно использовать git pull
- вместо этого удобствоиз git fetch
, за которым следует вторая команда, но просто помните, что это именно то, что есть, чтобы при сбое второй команды вы знали , какую вторую команду вы использовали и что делать дальше. Даже зная все это, я в основном сам не использую git pull
, потому что мне часто нравится запускать git log
между git fetch
и второй командой, чтобы посмотреть, что git fetch
выбрал. Иногда это меняет , использую ли я git merge
или git rebase
!
Заключение
Учитывая все вышесказанное, давайте посмотрим на вопрос темы:
Что происходит с локальным подтвержденным кодом, если ветка git удалена на сервере?
Ничего. С местными коммитами вообще ничего не происходит. Ваши местные филиалы ваши , причем как вы хотите. Вы выбираете, что будет дальше.