как объединить вторичную ветку с пустой веткой master? - PullRequest
0 голосов
/ 16 ноября 2018

Я использую git для принятия изменений в моем проекте.

Для этой цели: -

  1. Я создал новый пустой репозиторий в github
  2. Я создалвторичная ветвь с именем second, кроме основной ветки
  3. Я зафиксировал и перенес свои изменения в эту вторую ветку
  4. Теперь все мои проекты и их содержимое находятся во второй ветке, а моя основная ветвь пуста

Теперь у меня есть два сомнения: -

a) могу ли я перенести любой код в пустую главную ветвь напрямую, не вступая в процесс слияния двух ветвей?

b) какобъединить вторую ветку с пустой главной веткой

Ответы [ 2 ]

0 голосов
/ 16 ноября 2018

Мы должны начать с этой странной идеи: в Git нет такой вещи, как пустая ветка .

Пустые ветви не существуют в Git

В Git имя ветви, такое как master, идентифицирует коммит - точно один коммит, всегда. Этот единственный коммит является коммитом tip ветви. Если у этого коммита есть один или несколько родительских коммитов, и почти у каждого коммита есть хотя бы один родительский, родительские элементы также содержатся внутри ветви, как и есть родительский (ие) родительский (ие) родительский (ие) и так далее.

Обратите внимание, что сами обязуются , которые формируют эту обращенную назад структуру, начиная с конца и работая в обратном направлении:

A  <-B  <-C

Здесь, коммит A - это самый первый коммит из когда-либо сделанных. У него есть no parent: это исключение из общего правила, согласно которому коммиты имеют родителей. A не имеет родителя, потому что не может иметь его; это был первый коммит в истории. Но commit B содержит идентификатор хеша commit A, как родительский элемент B. Мы говорим, что B указывает на A. Аналогично, коммит C содержит хэш-идентификатор B, поэтому C указывает на B.

Имя ветви master обычно указывает на фиксацию C в качестве единственного идентифицированного коммита:

A--B--C   <--master

Git использует имя master для поиска C, использует C для поиска B и B для поиска A.

Если мы создадим новую ветку сейчас, например, используя git branch develop или git checkout -b develop, это создаст новое имя , которое указывает на такой же коммит C:

A--B--C   <-- master, develop

Мы сообщаем Git, какую ветку мы хотим включить, используя git checkout. Это присоединяет имя HEAD к одному из двух имен ветвей. Например, если мы git checkout develop сейчас, HEAD присоединен к develop:

A--B--C   <-- master, develop (HEAD)

Если мы сейчас сделаем новый коммит сейчас - назовем его D, хотя Git придумает для него какой-то большой некрасивый хеш-идентификатор - способ, которым Git достигает этого, заключается в создании нового коммита D с коммитом C в качестве Родитель D, так что D указывает на C:

A--B--C
       \
        D

и в качестве последнего шага создания этого коммита Git обновляет любую ветвь name HEAD, к которой присоединено, так что это имя теперь указывает на новый коммит D:

A--B--C   <-- master
       \
        D   <-- develop

Обратите внимание, что никогда не было develop пустой веткой. Первоначально develop и master оба указали на фиксацию C; оба содержали все три коммита. Теперь develop содержит четыре коммита, а master по-прежнему содержит те же три коммита, что и раньше. Коммиты A-B-C находятся на в обе ветви одновременно .

Итак, что ты сделал?

  1. Я создал новый пустой репозиторий в github

Обратите внимание, что в GitHub есть два способа создания хранилища. Один из способов создает для вас начальный коммит и master указывает на этот начальный коммит. Начальная фиксация содержит файл README и, необязательно, файл .gitignore и / или LICENSE. GitHub создаст этот начальный коммит, если вы установите флажок:

Инициализировать этот репозиторий с помощью README

Если вы не поставили галочку, GitHub действительно создает пустой репозиторий. Коммитов нет, поэтому нет веток в этом пустом хранилище!

  1. Я создал вторичную ветвь с именем second, кроме основной ветки

Поскольку ветвь может быть создана только при наличии коммита, на который она может указывать, вы буквально не можете сделать это. Сообщение об ошибке здесь немного странно, но оно показывает, что это невозможно сделать:

$ mkdir er; cd er
$ git init
Initialized empty Git repository in .../er/.git/
$ git branch second
fatal: Not a valid object name: 'master'.

Почему Гит жаловался на имя master? Ответ немного удивителен: ветка master не существует, но наша HEAD все равно прикреплена к ней. То есть HEAD присоединен к несуществующей ветви.

Это не очень разумное положение вещей, но Git нужно, чтобы начать. первый коммит странный: у него не будет родителя.Git должен сохранить имя ветки в где-нибудь создать , поэтому он сохраняет его, записав его в HEAD.Как только мы действительно сделаем новый коммит, , что создаст имя ветви.

Git сообщает об этом состоянии несколькими различными способами, называя это либо нерожденная ветвь , либо сиротская ветвь , в зависимости от того, какой бит Git выполняет вызов.Вот что говорит git status в этой конкретной версии Git:

$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

Точно так же git log теперь немного умнее, чем в старые добрые времена:

$ git log
fatal: your current branch 'master' does not have any commits yet

(раньше это приводило к более загадочной ошибке, как это делает git branch).

Мы можем, однако, попросить git checkout создать новое имя ветви, и еслимы делаем это, наше состояние меняется:

$ git checkout -b second
Switched to a new branch 'second'
$ git branch
$ git status
On branch second
...
$ git log
fatal: your current branch 'second' does not have any commits yet

Вывод git branch пуст, правильно показывая, что у нас все еще вообще нет ветвей.Вывод git status показывает нам, что мы находимся в этой нерожденной ветви, а git log говорит нам, что здесь нет коммитов.

Если мы сделаем коммит сейчас, имя ветви second будет прыгатьв существовании, указывая на новый коммит, который будет единственным коммитом:

$ echo example > README
$ git add README
$ git commit -m 'initial commit'
[second (root-commit) d4d9655] initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 README
$ git log --all --decorate --oneline --graph
* d4d9655 (HEAD -> second) initial commit
$ git branch
* second

Там у нас это есть: теперь существует одна ветвь;имя second идентифицирует самый первый коммит, который в данном конкретном случае имеет d4d9655 в качестве своего (сокращенного) хеш-идентификатора.

Я зафиксировал и перенес свои изменения в эту вторую ветку

Если вы смогли создать ветку на шаге 2 с помощью git branch, это означает, что у вас уже есть masterи он не пустой, и коммит, который вы сделали на шаге 3 в ветви second, заставил имя second указывать на второй коммит в репозитории, чьим родителем является first коммит в хранилище, на которое указывает имя master.Если это так, запустите:

git log --all --decorate --oneline --graph

покажет оба коммита, а также два названия ветвей в качестве украшений.

Если нет - если у вас действительно не было ветки master вообще -тогда у вас все еще нет ветки master;у вас есть только один коммит в одной ветви с именем second.

Теперь все мои проекты и их содержимое находятся во второй ветви, а моя основная ветвь пуста

Опять же, это буквально невозможно.Используйте git log --all --decorate --oneline --graph для просмотра всех ваших коммитов и того, куда идут названия веток.Опция --graph ничего не делает пока , но если у вас есть коммиты слияния, это очень полезно.

Разбор ваших вопросов

Теперь яесть два сомнения: -

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

а) могу ли я вставить любой код в пустую основную ветку напрямую, не вдаваясь в процесс объединения двух ветвей?

Существует нескольковажные различия, чтобы сделать и здесь.Во-первых, когда вы используете git push, вы нажимаете коммит .Git это все о коммитах.Каждый коммит, как мы уже видели выше, имеет некоторый родительский коммит (ы), за исключением первоначального ("корневого") коммитаКоммиты также сохраняют данные о коммите: например, ваше имя, адрес электронной почты, отметку времени и сообщение журнала.И вместе с этим коммитом метаданные - родитель (ы), автор / коммиттер и сообщение журнала - каждый коммит сохраняет снимок набора файлов.

HeБолее того, в некотором смысле репозиторий Git содержит файлы, но на более высоком уровне он на самом деле не так уж много файлов: репозиторий представляет собой набор commitits . Фиксация перетаскивает файлы вместе с ними как побочный эффект. Этот побочный эффект, конечно, делает Git полезным, но важно помнить, что сам Git не столько беспокоится о файлах , сколько о коммитах .

Вы можете создать новый коммит в любое время. Когда вы это сделаете, Git будет:

  1. получить сообщение от вас;
  2. упаковать (заморозить навсегда) содержимое индекса (который вы обновляете с помощью git add);
  3. сохранить свое имя и адрес электронной почты и текущее время в качестве автора и коммиттера;
  4. записать новый коммит с текущим коммитом в качестве родителя нового коммита, чтобы новый коммит указывал на то, что было текущим коммитом в то время, когда вы запустили git commit, и снимок, сделанный на шаге 2;
  5. получить хеш-код для нового коммита, завершив шаг 4;
  6. записать этот хэш-идентификатор в имя текущей ветви, записанное HEAD.

Мы еще не говорили об индексе, на который я ссылаюсь в шаге 2 здесь; мы вернемся к этому через некоторое время. Этот последний шаг, однако, то, что заставляет название ветви меняться. Имя изменяется при переходе, указывая на новый коммит, который теперь является последним коммитом на ветке. Все более ранние коммиты также в ветке, достигаемые путем обратной работы с новым коммитом tip.

б) как объединить вторую ветку с пустой главной веткой

Опять же, ветви никогда не бывают пустыми .

Чтобы объединить одну ветку с другой, мы используем git merge, но перед тем, как сделать это, нам обычно нужно сначала запустить git checkout, чтобы выбрать ветку. Важно точно понимать, что делает git checkout. Это влияет на разницу между коммитами, индексом и рабочим деревом.

Фиксирует, индекс и рабочее дерево

В Git коммиты постоянны - ну, в основном; в некоторых ситуациях вы можете полностью отказаться от коммитов, что на самом деле очень полезно - и полностью доступно только для чтения. Ни один коммит, сделанный однажды, не может быть изменен, даже один бит. Коммиты имеют несколько целей, но главная из них - дать вам возможность вернуть каждый файл таким, каким он был в момент совершения коммита.

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

$ git rev-parse second
d4d9655d070430e91022c1ad843267f9d05f60d1

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

Файлы, сохраненные с фиксацией, хранятся в специальном, замороженном (только для чтения), сжатом формате Git-only. Они также никогда не могут быть изменены (и фактически Git хранит их под идентификаторами хэшей, как и коммиты). Это свойство, никогда не меняющееся, означает, что Git может повторно использовать файлы в new коммитах, если новый коммит имеет такое же содержимое файла, что и предыдущий коммит. Это одна из причин того, что, хотя каждый коммит хранит полную копию каждого файла, репозитории Git обычно не занимают много места на диске: старые файлы многократно используются.

Конечно, система, которая никогда не позволяет изменять любые файлы, не очень полезна: она хороша для извлечения архива, но не годится для разработки. Таким образом, Git извлекает замороженные файлы, сжатые только для Git, в обычные файлы для чтения / записи в обычном компьютерном формате. Эти файлы находятся в вашем рабочем дереве . Но Git не использует сами эти файлы: копии рабочего дерева существуют для , который вы можете использовать, а не для Git.

Что делает Git - это необычно; большинство систем управления версиями этого не делают - это помещает что-то между замороженными зафиксированными файлами Git и простыми в использовании файлами рабочего дерева. Это «что-то» - индекс Гита . Извлечение коммита, используя git checkout, заполняет этот индекс, размораживая замороженный файл в индексе. Копия в индексе все еще находится в специальном сжатом формате Git-only (с идентификатором хэша), но теперь может быть перезаписано .

Следовательно, то, что git checkout делает, это:

  • заполнить индекс из коммита: разморозить, но пока не распаковывать файлы; тогда (одновременно)
  • заполнить рабочее дерево из индекса: распаковать файлы; и
  • наконец, присоедините имя HEAD к названию филиала, которое вы выбрали для проверки.

В результате после успешного git checkout somebranch обычно действуют три вещи: 1

  1. Индекс содержит все файлы из коммита tip somebranch.
  2. Рабочее дерево содержит все файлы из коммита tip somebranch.
  3. HEAD прилагается к имени somebranch.

Это означает, что вы теперь готовы изменить файлы рабочего дерева и - это сложная часть - скопировать их обратно в индекс . Команда git add берет файл рабочего дерева и копирует его в индекс, сжимая (но не замораживая) файл в специальную форму Git-only.

Если вы создаете совершенно новый файл в рабочем дереве и используете git add, это копирует файл в индекс. На этом этапе файл является совершенно новым в индексе. Если вы измените какой-либо существующий файл в рабочем дереве и используете git add, это скопирует файл в индекс, перезаписав тот, который был в индексе ранее. Вы также можете использовать git rm - для удаления файлов из индекса, но учтите, что это также приведет к удалению копии рабочего дерева.

Когда вы запускаете git commit, Git просто замораживает то, что находится в индексе в это время. Вот почему вы должны придерживаться индекса git add. Поэтому индекс можно суммировать как , что войдет в следующий коммит, если и когда вы сделаете еще один коммит.


1 Существует несколько исключений из этого правила: эти три вещи точно сохраняются, когда вы git checkout из чистого состояния. Более подробную информацию см. В Извлечение другой ветви, если в текущей ветви есть незафиксированные изменения . Однако, если проверка прошла успешно, HEAD будет присоединен к целевой ветви.


Краткое описание слияния

Давайте теперь посмотрим, что произойдет, если вы запустите:

git checkout somebranch
git merge otherbranch

Первый шаг, как мы видели, присоединяет наш HEAD к имени somebranch, получая коммит наконечника somebranch в нашем индексе и рабочем дереве. Предполагая, что все прошло хорошо, наш индекс и рабочее дерево точно совпадают с этим коммитом наконечника на этом этапе.

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

          H--I   <-- somebranch (HEAD)
         /
...--F--G
         \
          J--K--L   <-- otherbranch

ЧтоGit делает для этого случая поиск лучшего общего коммита - коммита, который находится на обеих ветвях, "близких к подсказкам". В этом случае, если мы начнем с вершины somebranch, то есть совершаем коммит I, и работаем в обратном направлении, мы достигнем коммита G, а если мы начнем с L и будем работать в обратном направлении, мы также достигнем совершить G. Это лучший общий коммит, чем коммит F, поэтому коммит G является базой слияния этой пары коммитов.

Или график может выглядеть так:

...--F--G--H--I   <-- somebranch (HEAD)
               \
                J--K--L   <-- otherbranch

Git по-прежнему найдет лучший общий общий коммит, начиная с I и работая в обратном направлении, а также начиная с L и работая в обратном направлении. Этот лучший коммит это сам коммит I. Это включает то, что Git называет fast-forward , что на самом деле не является слиянием.

График может выглядеть следующим образом:

...--F--G--H--I   <-- otherbranch
               \
                J--K--L   <-- somebranch (HEAD)

В этом случае общий коммит отстает от вершины somebranch. Нечего сливаться, и git merge скажет так и ничего не сделает.

Есть больше возможностей, включая очень запутанные графики, но давайте остановимся здесь. (Для вычисления баз слияний в сложных топологиях см. Другие ответы или прочитайте бит правильной теории графов , включая эту статью Бендера и др. .)

Чтобы выполнить ускоренную перемотку вперед, Git, по сути, просто перемещает имя ветви из текущей позиции в новый совет и запускает git checkout в новом коммите, давая:

...--F--G--H--I   [old "somebranch"]
               \
                J--K--L   <-- somebranch (HEAD), otherbranch

Для реального слияния Git использует базу слияния.

Реальные слияния

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

          H--I   <-- somebranch (HEAD)
         /
...--F--G
         \
          J--K--L   <-- otherbranch

Git начинается с перечисления всех файлов из коммитов G, I и L. Git сравнивает набор файлов в G с набором файлов в I, чтобы узнать, что мы изменили:

git diff --find-renames <hash-of-G> <hash-of-I>   # what we changed

, а затем сравнивает набор файлов в G с набором файлов в L, чтобы выяснить, что они изменили:

git diff --find-renames <hash-of-G> <hash-of-L>   # what they changed

Git теперь имеет два набора изменений, которые он может попытаться объединить. Для каждого файла, который не изменился ни у кого из нас, объединение легко: используйте файл из G, или из I, или из L (не важно, какие: все три копии одинаковы). Для каждого файла, который изменил только один из нас, используйте эту копию из этого коммита, I или L. Для файлов, которые оба из нас изменили, возьмите базовую копию слияния из G и добавьте оба изменения.

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

После объединения всех изменений во всех файлах, если не было конфликтов, Git, как обычно, сделает новый коммит из индекса. Этот новый коммит, однако, будет иметь двух родителей вместо обычного одного родителя. Затем Git обновит текущее имя ветки как обычно, поэтому мы получим:

          H--I------M   <-- somebranch (HEAD)
         /         /
...--F--G         /
         \       /
          J--K--L   <-- otherbranch

, где M - это коммит слияния с двумя родителями.

Если были какие-то конфликты, которые Git не мог разрешить самостоятельно, Git останавливается с сообщением о конфликте слияния. Тогда ваша задача - записать правильный результат слияния в рабочее дерево и использовать git add, чтобы скопировать его в индекс, удалив три копии Git, оставшиеся в индексе. (До этого момента вы можете извлечь любой или все три из трех входов - базовую версию слияния, левую или локальную версию или версию --ours, а также правую или удаленную или другую версию или версию --theirs - из Индекс тоже.) Как только вы разрешите все конфликты слияния и git add отредактируете все полученные файлы, вы запускаете git commit (или git merge --continue, который просто запускает git commit для вас), чтобы зафиксировать результат слияния, производя тот же график, как если бы Git сделал коммит автоматически.

0 голосов
/ 16 ноября 2018

Предполагая, что вы создали локальную ветку second на своем компьютере и зафиксировали там новые изменения, вы можете локально объединить их в master с помощью

git checkout master
git merge second

Теперь ваше местное отделение master содержит коммиты от second. Вы можете отправить это в удаленный репозиторий (github), предполагая, что вы клонировали локальный репозиторий с помощью git clone с помощью

git push

Если это не работает, потому что вам нужно установить удаленный репозиторий с помощью

git remote set-url origin https://github.com/USERNAME/REPOSITORY.git

Чтобы положить коммиты, сделанные вами в second в master, вам определенно необходимо merge.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...