git создает ветку при слиянии - PullRequest
0 голосов
/ 27 мая 2018

Давайте предположим, что у меня есть 2 пользователя, работающие над одной и той же веткой (мастер).

(я знаю, что это не очень хорошая практика, но это пример)

Есть что делать первымПользователь:

git clone <repository_url>
cd myproject
touch file1.cpp
git add file1.cpp
git commit -m "file 1 creation"

Второй пользователь:

git clone <repository_url>
cd myproject
touch file2.cpp
git add file2.cpp
git commit -m "file 2 creation"

Первый пользователь:

git push origin master

Второй пользователь:

git pull

В это времямерзавец делает слияние.Я работаю в разных средах (Linux, Mac, Windows).

Иногда git автоматически создает ветку.В этом случае я вижу master | MERGING в своем префиксе приглашения bash (до $), а иногда ничего не вижу.

Итак, мой вопрос: создает ли git ветвь, когда я выполняю операцию слияния?

Спасибо

Ответы [ 5 ]

0 голосов
/ 27 мая 2018

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

Я рекомендую всем, кто плохо знаком с Git избегать git pull.Вместо этого запустите git fetch, а затем, если вы хотите слияния, запустите git merge.Если вы предпочитаете ориентированный на перебазирование рабочий процесс, запустите git fetch, затем запустите git rebase.Это даст вам лучшее представление о том, что происходит внизу.Это также позволит избежать многих ошибок, которые люди почти всегда совершают, когда впервые начинают использовать Git. Противоположностью push является fetch, не pull.

Вы упоминаете, что:

Иногда ... я вижу master|MERGING в своем префиксе приглашения bash

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

Фактически, то, что вы имеете в приглашении bash, представляет собой набор умных взаимодействий.между вашей оболочкой bash и вашими командами Git, где ваш bash проверяет Git каждый раз, когда собирается напечатать приглашение.Если Git сообщает, что вы вообще находитесь в репозитории, оболочка включает в строку приглашение current-branch-name репозитория.Если вы находитесь в середине одного из этих конфликтующих слияний, вы также добавляете |MERGING.

Но это возвращает нас к вопросу: Что именно мы подразумеваем под "ветвью"? Нажмите на этот вопрос, чтобы узнать больше.Слово branch может относиться либо к имени ветви , например master, либо к подграфу в DAG (что я люблю называть DAGlet; см. Этот другой вопрос).

Команда git merge не создает новое имя , поэтому в этом смысле она никогда не создает ветвь.

С другой стороны, команда git merge может связать два подграфа в DAG - создать новый DAGlet.В этом смысле он создает новую ветвь.Ну, это так иногда , и было бы точнее сказать, что связывает некоторые существующие DAGlets.Вот тут-то и возникает эта неизбежная сложность.

Давайте на минутку изучим коммиты

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

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

... <-F <-G <-H

, где H - самое последнее, и оно указывает на (содержит идентификатор)G, что указывает на F и т. Д. В обратном направлении по истории.

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

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

...--F--G--H--I   <-- master

Теперь I указывает на H, который (все еще) указывает на G и т. Д.

Вовлечено более одного хранилища Git

Вы начинаете с этого:

Давайте предположим, что у меня есть 2 пользователя, работающие над одной ветвью (master).

Независимо от того, является ли это хорошей практикой или нет, здесь есть смысл, что имя master,означает только одно.Но это просто неправда, потому что у вас есть репозиторий Git, а у второго пользователя есть репозиторий Git, а в месте, куда вы git push обращаетесь, есть репозиторий Git. Каждый получает автомобиль upvote мем хранилище! И у каждого из вас есть свой собственный master.

Что вы все разделяете , однако, есть некоторый набор commits .Вы все начали с клонирования некоторого репозитория, у которого был некоторый набор коммитов, и вы получили их все.Вы, возможно, добавили еще немного с тех пор.Таким образом, для каждого хеш-идентификатора коммита ваш репозиторий Git либо имеет этот коммит, указанный его идентификатором, либо нет.Если ваш репозиторий Git не не имеет этого коммита, вот где git fetch и git push входят.

Что git fetch и git push делают для подключения два репозитория Git .На этом этапе тот, кто выполняет отправку - ваш Git, если вы тот, кто делает git push, или другой Git-репозиторий, если вы делаете git fetch - упаковывает все коммиты они есть то, что у вас нет, или наоборот.Отправитель доставляет этот пакет коммитов (и файлы, которые идут с ним) получателю.

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

Когда вы запускаете git fetch origin, 1 , вы получаете новые коммиты от их master, поэтому имя ваш Git помнит их master это origin/master.


1 Здесь origin - это имя удаленного .Большинство репозиториев имеют ровно один пульт с именем origin: когда вы запускаете git clone <url> для клонирования репозитория, процесс клонирования устанавливает этот пульт, имя которого по умолчанию равно origin, для запоминания URL.


Извлечение, затем слияние

Предположим, что вы оба начали с цепочки коммитов, заканчивающейся на H, и вы добавили I--J, и они - кто бы то ни был они здесь - добавлено K--L:

...--F--G--H--I--J   <-- master
            \
             K--L   <-- origin/master

Теперь ваша работа в объединяет вашу работу - что бы вы ни делали в коммитах I-J - с их работа, что бы они ни делали в K-L.

Самый простой способ объединения в Git - git merge.Этот конкретный вид слияния, который я люблю называть истинным слиянием , работает, буквально комбинируя вашу работу и их работу.Для этого нужно начинать с того момента, когда вы двое разветвляетесь. Обратите внимание, что это отделение не имеет ничего общего с самим именем master.Это потому, что вы сделали коммиты, а они сделали коммиты.

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

Git теперь запускается git diff дважды. 2 Вы можете сделать это самостоятельно:

git diff --find-renames <hash-of-H> <hash-of-J> > /tmp/what-we-did
git diff --find-renames <hash-of-H> <hash-of-L> > /tmp/what-they-did

Теперь вы можете сравнить, что вы сделали с тем, что они сделали.Git делает то же самое, чтобы объединить ваши изменения с их изменениями.

Есливещи, которые вы изменили, находятся «достаточно далеко» от вещей, которые они изменились, или находятся в разных файлах, Git успешно объединит их.Точнее, Git будет думать , что он успешно их объединил.(Вам, человеку, который умнее Git, определиться с реальным успехом здесь.) Но если вы измените одинаковые исходные строки , которые они изменили, Git не сможет объединить дваразные изменения.Git выбросит свои метафорические руки в воздух, объявит конфликт слияния , остановится и заставит вас навести порядок.

Это когда высм. статус слияния. Git остановился в середине конфликтующего слияния.График коммита по-прежнему выглядит как на картинке выше, с двумя "ветвями" (в смысле DAGlet), отделяющимися от общего коммита;они еще не собрались вместе.Теперь ваша задача - изменить беспорядок в нечто разумное, запустить git add для результата и использовать git commit (или в достаточно новых версиях Git git merge --continue - но это просто запускает git commit) для завершения слияния.

Если Git думает, что он может выполнить слияние самостоятельно, то git merge продолжит работу и также запустит git commit самостоятельно.Git не остановится в середине конфликта;он просто перейдет к части git commit.

Этот коммит, который завершает слияние, независимо от того, делает ли Git все это сам или останавливается и заставляет вас очистить и сделать это, связать два графа DAGlets:

...--F--G--H--I--J--M   <-- master
            \      /
             K----L   <-- origin/master

Новый коммит слияния M имеет двух родителей, вместо обычного. Это вторая часть того, что делает git merge: он создает коммит слияния , который использует слово merge в качестве прилагательного.Коммит слияния связывает вместе эти два DAGlets. Слияние не создало фрагменты графа.Объединение просто связало их вместе.

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

(Теперь вы можете использовать git push, чтобы делиться любыми вашими коммитами, которых у вас нет.)


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

Я также опускаю много деталей здесь окак работает процесс для объединения .Это имеет значение, главным образом, когда вам нужно очистить беспорядок неудачного слияния: слияние происходит в вашем index (также называемом вашей промежуточной областью ), а также в Дерево работы (где вы делаете свою работу).Разделение между коммитами, индексом и рабочим деревом имеет большее значение, когда вы начинаете делать более сложные вещи в Git.


Когда слияние не является слиянием

Невсе операции git merge объединяются!Предположим, вы не не выполнили никакой работы с тех пор, как вы запустили git clone, так что вы, скажем:

...--F   <-- master, origin/master

Теперь вы запускаете git fetch и получаете новые коммиты G и H:

...--F   <-- master
      \
       G--H   <-- origin/master

Обратите внимание, что G указывает на F, где вы сейчас находитесь.Если вы теперь запускаете git merge origin/master (или просто git merge), чтобы вырваться вперед, Git заметит, что здесь нет фактического расхождения .Вместо того, чтобы сочетать ваше отсутствие работы с их работой, Git может просто перемотать вперед имя master, чтобы оно указывало на фиксацию H, и проверить фиксацию H, давая вам:

...--F--G--H   <-- master, origin/master

Когда git merge делает это, он говорит: «ускоренная перемотка вперед»: нет различий, объединения работ и нового коммита слияния.Этот процесс очень прост для Git по сравнению с настоящим слиянием: в конце концов, это по сути тот же объем работы, что и git checkout.

Вы можете перебазировать вместо слияния

Я не буду вдаваться в подробности, но вместо объединения вашей работы с чужой работой вы можете перебазировать свою работу на чужую работу,Предположим, что мы начинаем с той же диаграммы объединения работ и потребностей:

...--F--G--H--I--J   <-- master
            \
             K--L   <-- origin/master

Вместо слияния мы можем использовать Git copy commits I и J to новые, несколько отличные коммиты, которые мы можем назвать I' и J', чтобы напомнить нам, что они очень похожи на I и J, но не совпадают.(У них будут новые уникальные хэш-идентификаторы, отличные от ваших оригинальных I и J.) Мы просто расположим их так:

...--F--G--H--I--J   <-- master
            \
             K--L   <-- origin/master
                 \
                  I'-J'  <-- ???

Теперь, когда у нас есть эти копии, мы имеемнаш Git "очистит ярлык" master от J и вместо него укажите J':

...--F--G--H--I--J   <-- ???
            \
             K--L   <-- origin/master
                 \
                  I'-J'  <-- master

Если вы решите отказаться от оригиналов, так какони вам больше не нужны, мы можем полностью удалить метки с вопросительными знаками и исправить один изломов на графике:

...--F--G--H--K--L   <-- origin/master
                  \
                   I'-J'  <-- master

Теперь кажется, что вы написали свои два коммита после получения коммита L вместо записи их на основе коммита H.Исходный код, который переносится с двумя новыми коммитами I' и J', основан на том, что в L, а не на том, что в H.

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

Делать это зависит от вас.Если вы решите сделать это, вы сделаете это вместо слияния.Помните, что rebase работает путем копирования некоторого набора коммитов;будьте осторожны, копируйте только те коммиты, которые вы еще не опубликовали (с git push), или убедитесь, что все, у кого были оригиналы, переключаются с оригиналов на новые улучшенные копии.

Один финалпримечание к git push

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

Но - вот асимметрия - ваш Git просит, чтобы их Git установил их master.Это не их bob/master, это их master.Как правило, их Git откажется разрешить эту операцию, если только это не ускоренная перемотка вперед.

Это ускоренная перемотка - то же самое, что мы видели при ускоренной перемотке вперед, но недействительно-слияния git merge.Это означает, что мы только добавляем коммиты к некоторому имени ветки: новые коммиты в конечном итоге указывают на коммиты, которые они уже имели.Это то, что вы делаете, когда запускаете git merge или git rebase: вы комбинируете свою работу с их последней, так что ваша работа добавляет к их, а не стирает некоторые из них.

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

(Вы можете выполнять толчки без ускоренной перемотки, используя git push --force или эквивалент. Это приводит к уничтожению некоторого коммита в Git, который получает принудительный толчок. Если вы хотите убить их, это нормально - но другие пользователи Git могут fetch отредактировать эти коммиты, так что будьте осторожны:они могут вернуться!)

0 голосов
/ 27 мая 2018

Да, git создает так называемый " merge commit".

Если вы хотите избежать этого, вы должны использовать git pull --rebase каждый раз, когда вы тянете.Таким образом, git будет выполнять маржирование "пока потянет" и пропустит создание "коммит маржа" .

Но вы должны помнить, что опция rebase заставляет вас обрабатывать потенциальные конфликты.

0 голосов
/ 27 мая 2018

В описанных вами шагах git pull объединяет удаленную ветвь (origin/master) с локальной master ветвью.И, как подсказывает ответ Тима, он создает коммит слияния в вашей локальной ветви master.

Чтобы ответить на ваш вопрос: обе ветви создаются во время операции git clone.git fetch обновляет состояние origin/master в локальном хранилище (до текущего состояния на сервере).

0 голосов
/ 27 мая 2018

Это не создаст новую ветку.Второй пользователь (например, любой пользователь) будет иметь локальную копию мастера.Запустив git pull, вы получаете следующие значения: fetched и merged.Слияние не удастся, если будут конфликтующие изменения.

Лучший способ обработки - для второго пользователя:

git pull --rebase 

Это приведет к удаленным изменениям и их применению поверх его локальной фиксации, а затем их можно будет перенести на удаленныйМастер.

0 голосов
/ 27 мая 2018

Выполнение git pull аналогично выполнению git fetch, за которым следует git merge.В конце слияния второго пользователя Git создаст новое слияние commit , но само по себе не создаст новую ветку.

master|MERGING, который вы видите наконсоль просто означает, что Git находится в середине операции слияния.

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