Вы не можете делать это, кроме случаев, когда вы вынуждены это делать.
Этот ответ выше не имеет смысла, пока вы не поймете, что Git на самом деле не имеет отношения к ответвлениям . Git - это всего лишь коммитов .
Каждый коммит имеет свой собственный большой уродливый хеш-идентификатор, такой как 08da6496b61341ec45eac36afcc8f94242763468
. Этот идентификатор означает этот коммит, и никакой другой коммит. Попробуйте:
git rev-parse HEAD
в вашем собственном Git-репозитории, чтобы увидеть хеш-идентификатор вашего текущего коммита. Этот хеш-идентификатор теперь означает , что фиксирует, навсегда, и никогда больше не будет коммитов. 1
Но хеш-идентификаторы коммитов большие и безобразныепомнить их. Нам (людям) нужно устройство, которое запомнит их для нас. Возможно, мы можем использовать наши компьютеры! Они хорошо запоминают такие длинные строки. Так что мы делаем, чтобы Git запомнил эти хэш-идентификаторы для нас.
Есть несколько разных мест, где Git будет запоминать хэш-идентификаторы для нас. Например, тег имен содержат один хэш-идентификатор фиксации. 2 Но на самом деле ответвление имен, таких как master
, и простоудерживайте один хэш-идентификатор коммита.
К счастью, каждый коммит также содержит хэш-идентификаторы! Вот как Git формирует то, что мы склонны считать ответвлениями , хотя мы (люди) также склонны думать о названиях наших веток, а иногда и о некоторых других наших именах в наших репозиториях Git, как о "ветвях"" также. Подробнее об этом см. Что именно мы подразумеваем под «ветвью»?
Каждый коммит хранит полный и полный снимок всех наших файлов. А коммит не хранит изменения! Он хранит целые файлы без изменений. 3 Так что если commit A
(где A
обозначает некоторый хэш-идентификатор) хранит содержимое вашего файла foo/bar/file.txt
, commit A
имеет полный иполная копия файла - не различия, просто копия. Если какой-то другой коммит B
хранит содержимое вашего файла foo/bar/file.txt
, коммит B
также имеет полную и полную копию.
Эти полные и полные копии автоматически передаются если они точно совпадают. Каждый сделанный коммит полностью и полностью доступен только для чтения, замораживается навсегда. 4 Сюда также входит сохраненное содержимое файла. Поэтому, если commit A
сохраняет такое же содержимое - даже только для одного файла - как commit B
, базовое хранилище файлов для этого файла является общим. Но вы не можете изменить эту копию файла. Если вы измените файл, вы должны сделать новый коммит C
, который получит свой новый уникальный идентификатор хэша. Этот новый коммит C
может иметь файл foo/bar/file.txt
, но, поскольку мы сказали, что вы изменили содержимое этого файла (на что-то, что не совпадает ни с одним другим существующим коммитом), содержимое этого файла является новым для коммита C
. Он может быть передан снова для последующих коммитов D
, E
и F
, если foo/bar/file.txt
продолжает иметь тот же контент.
1 Техническиидентификатор хеша - это просто криптографическая контрольная сумма всего содержимого объекта фиксации. Теоретически, некоторый другой объект может иметь одинаковую контрольную сумму. Однако, если у вас уже есть объект с хешем H , ваш Git не позволит любому новому объекту с таким же хешем войти в ваш репозиторий. Поэтому, если идентификатор объекта «взят», это означает, что с этого момента этот объект.
См. Также Как недавно обнаруженное столкновение SHA-1 влияет на Git?
2 A облегченный тег напрямую содержит хэш-идентификатор, а аннотированный тег удерживает его через аннотированный теговый объект , который также содержит дополнительные данные. Этот объект тега получает свой собственный уникальный хэш-идентификатор, а тег name затем содержит идентификатор объекта тега. Git будет использовать имя, чтобы найти объект тега, а затем прочитать объект тега, чтобы найти сохраненный хэш-идентификатор.
Теги могут иметь в качестве цели-тега любой из четырех внутренних типов объектов Git. Если аннотированный тег указывает на другой аннотированный тег, Git будет следовать за тегом до тех пор, пока он не достигнет какого-либо другого типа объекта, поэтому, в конечном счете, каждый тег указывает на некоторый объект без аннотированного тега, но, возможно, после многих косвенных указаний. Если конечным объектом является объект дерева или блоба, тег все равно не указывает на коммит.
3 На уровне коммита Git хранит одно дерево объект. Затем объект дерева ссылается на объекты blob и другие объекты tree . Объекты дерева содержат компоненты имен файлов и ссылки на хэш-идентификаторы BLOB-объектов, а объекты BLOB-объектов содержат содержимое файлов.
Эти объекты изначально хранятся как свободные объекты , которые всегда целы инеповрежденными. Позднее Git сжимает объекты в пакетных файлов . При сохранении в файле пакета объект может быть дельта-сжатым по отношению к другим упакованным объектам. Таким образом, в какой-то момент «файлы» - действительно объекты - сохраняются с помощью дельта-кодирования, что похоже на хранение различий. Но это происходит позже и не видно с уровня, на котором Git работает с файлами. Обратите внимание, что сами объекты дерева и фиксации, или, по крайней мере, могут быть дельта-сжатыми!
4 Это необходимо, так же как и естественное, , поскольку способ, которым Git хранит объекты. Каждый объект имеет свое содержимое, что бы это ни было, а затем хэш-идентификатор, который вычисляется с помощью того же процесса проверки контрольной суммы, упомянутого в сноске 1. Если вы удалите объект из базы данных, измените хотя бы один бит и запишите его обратно до базы данных, новый объект имеет новую и другую контрольную сумму. Если новые данные уже в базе данных, новый объект, в конце концов, не новый: Git просто повторно использует существующий объект. В противном случае Git записывает этот объект в базу данных, и теперь этот хэш-идентификатор взят.
Существуют процедуры для освобождения («сбора мусора») неиспользуемых объектов, если и когда они возникают (что на самом деле относительно часто в Git). - многие команды создают временные объекты, а затем выбрасывают их). Вам не нужно ничего делать, чтобы это произошло: сам Git запускает git gc --auto
для вас, когда это уместно. Но у хеш-идентификаторов достаточно битов, чтобы случайное столкновение никогда не происходило на практике;возможны только преднамеренные, вынужденные столкновения. Снова, смотрите Как недавно обнаруженное столкновение SHA-1 влияет на Git?
Но все вышеизложенное касается коммитов! Как насчет branch ?
Мы только что сказали, что если коммиты A
и B
имеют одинаковое содержимое файла для foo/bar/file.txt
, они на самом деле share theосновная копия файла. В терминологии Git они разделяют объект blob . Но в commit C
появилась новая и другая версия foo/bar/file.txt
, поэтому в commit C
появился новый и другой объект BLOB-объекта. Фиксирует D
, E
и F
, затем повторно использует этот контент для их foo/bar/file.txt
файлов, потому что вы сделали коммиты D
, E
и F
безизменение файла.
Теперь, интересная вещь о коммитах состоит в том, что помимо хранения файлов - снимка всех ваших файлов - каждый коммит также хранит некоторые метаданные . В метаданных вы найдете свое собственное имя и адрес электронной почты, которые хранятся как «автор» и «коммиттер». Вы найдете две метки даты и времени, указывающие, когда вы сделали коммит. 5 Git хранит ваше сообщение журнала: тема и основной текст для git log
, чтобы напомнить себе и другим почему вы сделали этот коммит. И, возможно, самое главное, вы найдете хеш-идентификатор родительского коммита или иногда некоторого числа родительских коммитов. Обычно есть только один родитель.
Remember, этот родительский элемент является необработанным хеш-идентификатором коммита - большой уродливой строкой букв и цифр. Это Git запоминает хеш-идентификатор для нас, так что нам не нужно.
Всякий раз, когда в Git что-то держит большой уродливый хеш-идентификатор, мы говорим, что эта вещь указывает на соответствующий объект. Таким образом, если коммит содержит хэш-идентификатор своего предшественника (родителя), коммит указывает на своего родителя. Это означает, что commit B
указывает назад для фиксации A
, а commit C
указывает назад для фиксации B
и т. Д.:
A <-B <-C ... <-F
Эти стрелки фиксации, направленные назад, встроенные вкаждый коммит формирует цепочку, которая связывает коммиты вместе. Поскольку ничто из любого коммита не может изменить , мы можем нарисовать цепочку следующим образом:
A--B--C--D--E--F
Но откуда приходят имена ветвей? Ответ: прямо здесь. последний коммит в этой цепочке - это коммит F
(каким бы ни был его настоящий, большой и некрасивый хеш-идентификатор - мы просто называем его F
здесь). Чтобы запомнить этот идентификатор хэша, Git дает нам имя ветки. Допустим, это ветка master
. name master
будет содержать фактический хэш-идентификатор commit F
:
A--B--C--D--E--F <-- master
Теперь давайте создадим новую ветку dev
. Какой хэш-идентификатор содержит dev
? Что ж, давайте тоже подождем F
! Итак, теперь у нас есть:
A--B--C--D--E--F <-- master, dev
Мы выбираем одну из этих двух ветвей для включения:
git checkout dev
, а затем мы делаем некоторую работу и делаем новый коммит, который получаетсвой новый, уникальный, большой и некрасивый хэш-идентификатор. Мы назовем этот хэш-идентификатор G
. Коммит G
будет указывать обратно на существующий коммит F
, так как этот коммит мы извлекли для make G
, и мы получим:
A--B--C--D--E--F
\
G
Где сделатьназвания веток указывают? Ну, master
все еще указывает на фиксацию F
- но потому что мы просто сделали новый коммит G
, Git автоматически обновляет ветвь имя до точкик новому коммиту G
, дающему:
A--B--C--D--E--F <-- master
\
G <-- dev (HEAD)
Мы рисуем слово (HEAD)
рядом с именем ветви, которое мы использовали с git checkout
, чтобы мы - и Git - знали, какое имя ветви обновляется по мере того, как мы делаем новые коммиты.
Если мы делаем больше новых коммитов на dev
, они указывают на существующие коммиты на dev
. Существующий коммит G
указывает на коммит F
. На какой ветке зафиксирован коммит F
? Ответ Git необычен: commit F
включен в обе ветви одновременно .
Но что если мы git checkout master
, то есть:
A--B--C--D--E--F <-- master (HEAD)
\
G <-- dev
и затем сделать новый коммит H
? Ну, это работает так же, как в прошлый раз. родительский нового коммита H
- это коммит, который мы извлекли мгновением ранее, это коммит F
, и наше имя перемещается, чтобы указать на новый коммит:
H <-- master (HEAD)
/
A--B--C--D--E--F
\
G <-- dev
Коммиты A
- F
находятся на обеих ветвях. Фиксация H
только на master
, а фиксация G
только на dev
.
5 Причина двух отметок времени заключается в том, что вы можете copy commit, при этом (предположительно) что-то меняется в нем. Новый коммит получает новую строку committer : вы сделали новый коммит прямо сейчас. Но он сохраняет свою старую строку author : тот, кто написал исходный коммит, всякий раз, когда они его записывают, также включается в коммит new . Таким образом, у каждого коммита есть автор: человек, который написал первоначальный коммит, с именем, адресом электронной почты и отметкой времени. У каждого коммита тоже есть коммиттер : человек, который сделал этот коммит, с именем, адресом электронной почты и отметкой времени. Если два совпадения, это оригинальная копия исходного коммита.
Теперь мы можем видеть, почему на ваш оригинальный вопрос в Git в основном отвечают «нет»
Мы начинаем сфайл с именем config
, который существует в моментальные снимки G
и H
в это время:
H <-- master (HEAD)
/
A--B--C--D--E--F
\
G <-- dev
стот же контент. Таким образом, этот файл является общим для - сохраняется только один раз - и оба H
и G
используют один общий объект blob для его хранения. Но затем вы изменяете копию файла рабочего дерева, git add
и git commit
, чтобы сделать новый коммит I
:
H--I <-- master (HEAD)
/
A--B--C--D--E--F
\
G <-- dev
У коммита I
есть новая, другая копия config
: новый шарик. Вы хотите, чтобы ветвь с dev
по автоматически получала новый коммит:
H--I <-- master (HEAD)
/
A--B--C--D--E--F
\
G--J <-- dev
, где коммит J
совпадает с коммитом G
за исключением того, что файл config
был обновлен.
Вы можете сделать это вручную , выполнив git checkout dev
, а затем сделав новый коммит J
на dev
, который имеет,в своем снимке та же обновленная копия config
. Но Git не может сделать это автоматически.