У меня две ветки, одна - master, другая - dev. Есть файл с именем config, который я хочу использовать в разных ветках. То есть, если я изменю его в master, а затем переключусь на dev, изменения сохранятся, и я смогу увидеть измененный файл в dev. Кроме того, я бы хотел, чтобы его можно было отправить в удаленные репозитории, поэтому я не могу добавить его в файл .gitignore.

Как я могу это сделать ?

git
0
coin cheung 25 Окт 2019 в 13:25

1 ответ

Лучший ответ

Вы не можете этого сделать, за исключением случаев, когда вас заставляют это делать.

Этот ответ не имеет смысла, пока вы не поймете, что Git на самом деле не имеет отношения к веткам . Git - это все о коммитах .

У каждого коммита есть свой собственный большой уродливый хэш-идентификатор, например 08da6496b61341ec45eac36afcc8f94242763468. Этот идентификатор означает эту фиксацию, а не другую фиксацию. Пытаться:

git rev-parse HEAD

В вашем собственном репозитории Git, чтобы увидеть хэш-идентификатор вашего текущего коммита. Этот хэш-идентификатор теперь означает эту фиксацию, навсегда, и никогда не будет никакой другой фиксации. 1

Но хеш-идентификаторы коммитов большие и уродливые, и простой человек их не запомнит. Нам (людям) нужно устройство, которое запомнит их за нас. Возможно, мы сможем использовать наши компьютеры! Они хорошо запоминают такие длинные струны. Итак, мы заставляем Git запоминать эти хеш-идентификаторы для нас.

Есть несколько разных мест, где Git запомнит нам хеш-идентификаторы. Например, имена тегов содержат один хэш-идентификатор фиксации. 2 Но на самом деле имена веток , такие как master, также просто храните один хэш-идентификатор фиксации.

К счастью, каждая фиксация также содержит хеш-идентификаторы! Вот как Git формирует то, что мы склонны считать ветвями , хотя мы (люди) также склонны думать об именах наших веток, а иногда и о некоторых других именах в наших репозиториях Git, как " ветки ». Подробнее об этом см. Что именно мы подразумеваем под «ветвью»?

Каждая фиксация хранит полный и полный снимок всех наших файлов. Коммит не сохраняет изменения! Он хранит целые файлы без изменений. 3 Поэтому, если фиксация A (где A означает некоторый идентификатор хэша) сохраняет содержимое вашего файла foo/bar/file.txt, выполните фиксацию A имеет полную и полную копию файла - не отличий, а только копию. Если какой-либо другой коммит B хранит содержимое вашего файла foo/bar/file.txt, коммит B также имеет полную копию.

Эти полные и полные копии автоматически передаются , если они точно совпадают. Каждая сделанная фиксация полностью и полностью доступна только для чтения, заморожена на все время. 4 Это также включает в себя сохраненное содержимое файла. Таким образом, если фиксация A хранит то же содержимое - даже только для одного файла - как фиксация B, базовое файловое хранилище для этого файла является общим. Но вы не можете изменить эту копию файла. Если вы все же измените файл, вы должны сделать новую фиксацию C, которая получит свой собственный новый и уникальный идентификатор хэша. Эта новая фиксация C может иметь файл foo/bar/file.txt, но, поскольку мы сказали, что вы изменили содержимое этого файла (на то, что не соответствует никаким другим существующим фиксациям), содержимое этого файла является новым для фиксации {{X5 }}. Его можно снова использовать для последующих коммитов D, E и F до тех пор, пока foo/bar/file.txt сохраняет то же содержание.


1 Технически хэш-ID - это просто криптографическая контрольная сумма всего содержимого объекта фиксации. Теоретически у разных объектов может быть одинаковая контрольная сумма. Однако, если у вас уже есть объект с хешем H , ваш Git не позволит любому новому, но другому объекту с таким же хешем войти в ваш репозиторий. Таким образом, если идентификатор объекта «взят», это означает этот объект с этого момента.

См. Также Как недавно обнаруженная коллизия SHA-1 влияет на Git?

2 облегченный тег содержит идентификатор хэша напрямую, в то время как аннотированный тег хранит его через аннотированный объект тега , который также содержит дополнительные данные. Этот объект тега получает свой собственный уникальный идентификатор хеша, а тег name затем содержит идентификатор объекта тега. Git будет использовать имя, чтобы найти объект тега, а затем прочитать объект тега, чтобы найти сохраненный идентификатор хэша.

Теги могут иметь в качестве цели тега любой из четырех внутренних типов объектов Git. Если аннотированный тег указывает на другой аннотированный тег, Git будет следовать за тегом до тех пор, пока не достигнет некоторого другого типа объекта, поэтому в конечном итоге каждый тег указывает на какой-то объект без аннотированного тега, но, возможно, после многих косвенных обращений. Если конечным объектом является дерево или объект blob, тег в конце концов не указывает на фиксацию.

3 На уровне фиксации Git хранит один объект tree . Затем объект дерева ссылается на объекты blob и другие объекты tree . Объекты дерева содержат компоненты имени файла и ссылки на идентификаторы хэша больших двоичных объектов, а объекты больших двоичных объектов содержат содержимое файлов.

Эти объекты изначально хранятся как свободные объекты , которые всегда целы и неповреждены. Позже Git сжимает объекты в файлы упаковки . При сохранении в файле пакета объект может быть дельта-сжатым против других упакованных предметов. Итак, в какой-то момент «файлы» - на самом деле объекты - действительно сохраняются с помощью дельта-кодирования, которое аналогично хранению различий. Но это происходит позже и не видно на уровне, на котором Git работает с файлами. Обратите внимание, что сами объекты дерева и фиксации тоже подвергаются дельта-сжатию или, по крайней мере, могут быть сжаты!

4 Это необходимо и естественно, из-за способа хранения объектов в Git. Каждый объект имеет свое содержимое, каким бы оно ни было, а затем хэширует идентификатор хэша, который вычисляется с помощью того же процесса контрольной суммы, упомянутого в сноске 1. Если вы извлекаете объект из базы данных, измените хотя бы один бит и запишите его обратно < em> в базу данных, новый объект имеет новую и другую контрольную сумму. Если новые данные уже в базе данных, новый объект в конце концов не новый: Git просто повторно использует существующий объект. В противном случае Git записывает этот объект в базу данных, и теперь используется этот хэш-идентификатор.

Существуют процедуры для освобождения («сборки мусора») неиспользуемых объектов, если и когда они возникают (что на самом деле довольно часто в Git - многие команды создают временные объекты, а затем выбрасывают их). Вам не нужно ничего делать, чтобы это произошло: Git сам запускает для вас git gc --auto, когда это необходимо. Но в хэш-идентификаторах достаточно битов, поэтому на практике случайного столкновения не произойдет; могут произойти только преднамеренные, вынужденные столкновения. Опять же, см. Как недавно обнаруженная коллизия SHA-1 влияет на Git?


Но все вышесказанное касается коммитов! А как насчет веток ?

Мы только что сказали, что если коммиты A и B имеют одинаковое содержимое файла для foo/bar/file.txt, они фактически совместно используют лежащую в основе копию файла. В терминологии Git они разделяют объект blob . Но коммит C представил новую и другую версию foo/bar/file.txt, поэтому коммит C имеет новый и другой объект blob. Фиксирует D, E и F, а затем повторно использует этот контент для своих foo/bar/file.txt файлов, потому что вы сделали коммиты {{X10} }, E и F без изменения файла.

Что интересно в отношении коммитов, так это то, что помимо хранения файлов - снимка всех ваших файлов - каждый коммит также сохраняет некоторые метаданные . В метаданных вы найдете свое собственное имя и адрес электронной почты, сохраненные как «автор» и «коммиттер». Вы найдете две отметки даты и времени, указывающие, когда вы сделали фиксацию. 5 Git сохраняет ваше сообщение журнала: тема и тело текст для git log, чтобы напомнить себе и другим почему вы сделали это обязательство. И, что, возможно, наиболее важно, вы найдете хэш-идентификатор родительского коммита, а иногда и некоторое количество родительских коммитов. Обычно есть только один родитель.

Помните, что этот родительский элемент - это необработанный хэш-идентификатор фиксации - большая уродливая строка букв и цифр. Это Git запоминает хэш-идентификатор для нас, так что нам не нужно.

Всякий раз, когда что-то в Git содержит большой уродливый хэш-идентификатор, мы говорим, что эта вещь указывает на соответствующий объект. Итак, если коммит содержит хэш-идентификатор своего предшественника (родительского) коммита, comimt указывает на своего родителя. Это означает, что фиксация B указывает на фиксацию A, а фиксация C указывает обратно на фиксацию B, и так далее:

A <-B <-C ... <-F

Эти направленные назад стрелки фиксации, встроенные в каждую фиксацию, образуют цепочку, которая связывает фиксации вместе. Поскольку ничто в любом коммите не может измениться , мы можем нарисовать цепочку следующим образом:

A--B--C--D--E--F

Но при чем тут названия веток? Ответ: прямо здесь. Последний коммит в этой цепочке - это коммит F (каким бы он ни был на самом деле, большим и уродливым хеш-идентификатором - здесь мы просто называем его F). Чтобы запомнить этот идентификатор хэша, Git дает нам имя ветки. Допустим, это ветка master. Затем name master будет содержать фактический идентификатор хэша фиксации 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, поскольку это фиксация, которую мы проверили, чтобы сделать G, и мы получим:

A--B--C--D--E--F
                \
                 G

На что указывают названия веток? Что ж, master по-прежнему указывает на фиксацию F - но поскольку мы только что сделали новую фиксацию G, Git автоматически обновляет ветку < em> name , чтобы указать на новую фиксацию G, давая:

A--B--C--D--E--F   <-- master
                \
                 G   <-- dev (HEAD)

Мы рисуем слово (HEAD) рядом с именем ветки, которое мы использовали с git checkout, чтобы мы - и Git - знали, какое имя ветки обновляется, когда мы делаем новые коммиты.

Если мы сделаем больше новых коммитов на dev, они будут указывать на существующие коммиты на dev. Существующая фиксация G указывает на фиксацию F. На какой ветке выполняется фиксация F? Ответ Git необычен: фиксация 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 Причина появления двух отметок времени в том, что вы можете скопировать фиксацию, при этом (предположительно) что-то в ней изменив. Новая фиксация получает новую строку committer : вы сделали новую фиксацию прямо сейчас. Но он сохраняет свою старую строку author : тот, кто написал исходную фиксацию, всякий раз, когда они ее писали, также попадает в новую фиксацию. Таким образом, у каждой фиксации есть автор : человек, написавший исходную фиксацию, с именем, адресом электронной почты и отметкой времени. У каждого коммита тоже есть коммиттер : человек, который сделал это конкретное коммит, с именем, адресом электронной почты и отметкой времени. Если они совпадают, это исходная копия исходной фиксации.


Теперь мы можем понять, почему на ваш исходный вопрос в 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: новый blob. Вы хотите, чтобы ветка dev автоматически приобрела новую фиксацию:

                 H--I   <-- master (HEAD)
                /
A--B--C--D--E--F
                \
                 G--J   <-- dev

Где фиксация J совпадает с фиксацией G, за исключением того, что файл config был обновлен.

Вы можете сделать это вручную , выполнив git checkout dev и затем сделав новую фиксацию J на dev, в моментальном снимке которого есть такая же обновленная копия { {X3}}. Но Git не может делать это автоматически.

0
torek 25 Окт 2019 в 13:49