Нажатие из функциональной ветви подтолкнуло другую ветку? - PullRequest
0 голосов
/ 27 августа 2018

Из нашей ветки develop я создал новую ветку с git checkout -b jsm/logging. Сделал мои изменения, передал и отправил в origin с git push -u origin HEAD. Сделал пиар, слил и удалил ветку remote . Сделал еще один твик и исправил мой последний коммит с git commit --amend --no-edit -a. Затем я проверил свой статус и принудительно нажал git push -f. К моему удивлению, неправильная ветвь была (силой) нажата! Взгляните на мой консольный журнал (обратите внимание, что я присвоил псевдоним от g до git, st - это псевдоним status, а co - псевдоним checkout).

Примечание: я также заметил, что когда я пытаюсь нажать, например, develop, Git часто жалуется, что master не синхронизирован (нужно сначала потянуть) - но почему он что-то делает с мастер когда я не на той ветке? Кажется, связано, не знаю, в чем проблема.

журнал консоли (имя ветки перед "$"):

josh:~/Projects/my-project jsm/logging $ git commit --amend --no-edit  -a
[jsm/logging 4cdb3dc] add logging
 Date: Mon Aug 27 15:18:41 2018 -0400
 1 file changed, 12 insertions(+), 6 deletions(-)

josh:~/Projects/my-project jsm/logging $ git st
## jsm/logging...origin/jsm/logging [ahead 1, behind 1]

josh:~/Projects/my-project jsm/logging $ git push -f 
Counting objects: 1, done.
Writing objects: 100% (1/1), 685 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:my-org/my-project
 + 5a649bc...8d320d2 develop -> develop (forced update)
                     ^^^^^^^ why is it pushing a different branch than I'm on?!

josh:~/Projects/my-project jsm/logging $ g co develop
Switched to branch 'develop'
Your branch is up-to-date with 'origin/develop'.

josh:~/Projects/my-project develop $ g co jsm/logging
Switched to branch 'jsm/logging'
Your branch and 'origin/jsm/logging' have diverged,
and have 1 and 1 different commit each, respectively.
  (use "git pull" to merge the remote branch into yours)

josh:~/Projects/my-project jsm/logging $ git st
## jsm/logging...origin/jsm/logging [ahead 1, behind 1]

josh:~/Projects/my-project jsm/logging $ git push -fu origin jsm/logging
Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (11/11), 1.04 KiB | 0 bytes/s, done.
Total 11 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), completed with 5 local objects.
To git@github.com:my-org/my-project
 * [new branch]      jsm/logging -> jsm/logging
Branch jsm/logging set up to track remote branch jsm/logging from origin.

git config

alias.st=status -sb
alias.co=checkout
alias.cob=checkout -b
pull.rebase=true
push.default=matching
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=git@github.com:my-org/my-project
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.develop.remote=origin
branch.develop.merge=refs/heads/develop
branch.jsm/logging.remote=origin
branch.jsm/logging.merge=refs/heads/jsm/logging

1 Ответ

0 голосов
/ 28 августа 2018

Комментарий Кнуги содержит правильную причину: вы установили push.default на matching. Возможно, вы ожидали использовать current или даже upstream. Это TL; DR прямо там.

По умолчанию push.default в современном Git - simple, что большинство людей считает более безопасным - matching - это то, что было по умолчанию до 2.0, и у многих людей было много головных болей. Но чтобы понять , почему это так, давайте посмотрим, что делает git push на высоком уровне. Это также то, что делает git fetch, так что стоит охватить оба в некоторой степени. Однако я опущу специальные правила, относящиеся к извлечению.

Длинное описание (необязательно)

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

Затем ваш Git вызывает другой Git по этому URL (remote.origin.url). Этот другой Git имеет своих собственных веток, тегов и других ссылок. У вашего Git есть свой Git список всех своих веток, тегов и других ссылок. Вы можете увидеть то, что видит ваш Git, запустив git ls-remote origin, который выполняет эти два шага, распечатывает результат и останавливается.

Для git push следующий шаг зависит от нескольких вещей:

  • Вы указали refspecs в командной строке? (Если это так, Git использует перечисленные вами refspecs.) Refspecs идет после удаленного имени: например, git push origin <refspec1> <refspec2> .... Если вы запустили git push без дополнительных аргументов, вы не указали никаких refspecs.

  • Если вы не указали refspecs, есть ли специальное значение по умолчанию для этого пульта? (Если это так, это значение по умолчанию. Обратите внимание, что это refspec , а не строковый литерал, такой как push.default!)

  • В противном случае push.default является значением по умолчанию. Он имеет пять настроек, которые мы рассмотрим ниже.

Для git fetch есть похожий шаблон, но Git почти всегда заводится с использованием настройки remote.<em>remote</em>.fetch, например, remote.origin.fetch, потому что всегда есть такая настройка, и вы, как пользователь, будете стремиться просто запустить git fetch origin или даже просто git fetch.

Это оставляет еще один очевидный вопрос: что за хрень в любом случае является refspec?

Refspecs

Вторая простейшая форма refspec выглядит как master:master или jsm/logging:jsm/logging - или для git fetch, как master:origin/master. То есть есть имя с левой стороны, символ двоеточия : и имя с правой стороны.

Имя слева - источник , а имя справа - пункт назначения . Каждое имя является ссылкой или ref name, что означает, что вы можете записать полное имя наподобие refs/heads/master. Если вы не произнесете имя по имени, Git обычно будет правильно угадывать, что master - это имя ветви, а v1.2 - это имя тега (просматривая имеющиеся у вас ветви и имена тегов), но если он угадает неправильно, или вы хотите быть действительно уверенным, вы можете указать полное имя.

Но я сказал, что это вторая - самая простая форма. Самое простое - полностью опустить двоеточие и пункт назначения: master или v1.2 или jsm/logging. Здесь выборка и передача различаются в том, как они к ним относятся: они оба по-прежнему источник для операции, но для git push, пункт назначения является копией источника. Для git fetch назначение не сохранять - до отбрасывать - имя. Поскольку мы смотрим на git push, мы можем пропустить специальную выборку и сосредоточиться на том, как git push любит использовать одинаковое имя с обеих сторон.

Стоит отметить: вы можете добавить ведущий + в refspec. Это устанавливает флаг силы только для этого refspec . Давайте рассмотрим простой пример ниже.

Выборки и push в основном о коммитах

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

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

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

Переместив правильный набор коммитов в правильный Git, ваши два Gits теперь должны взаимодействовать на одном последнем шаге: , задавая несколько имен . Если вы используете git push, ваш Git попросит, чтобы его Git установил их имена: вы просите их обновить, например, master, либо обновить или создать jsm/logging. Если вы используете git fetch, ваш Git устанавливает origin/master на основании, например, их master, и этот конкретный трюк переименования их master в origin/master происходит через refspecs настроен в remote.origin.fetch.

С помощью refspecs Git отправляет или выбирает только те вещи, которые вы указали

Итак, если вы делаете имя некоторого набора refspecs в командной строке, ваш Git будет извлекать или выдавать коммиты, основываясь на именах источников, которые вы перечислили. Получающий Git будет помнить коммиты, извлеченные или выдвинутые путем установки некоторых имен - в вашем Git, если fetch ing, в их имена, если push ing - на основе имен получателей, которых вы перечислили.

Обратите внимание, что git push может отправлять свои окончательные операции по установке имени в виде вежливых запросов - * пожалуйста, установите master на a123456.... - или в качестве довольно убедительных команд: установите master на a123456... или прямо в постель без ужина! Их Git может по-прежнему отказывать в командах, но обычно по умолчанию проверяют вежливые запросы, чтобы увидеть, добавляют ли они просто новые коммиты, и подчиняются принудительным командам.

Без refspecs, git push отступает, возможно, вплоть до push.default

Если вы просто запустите git push origin или git push - с или без флага force - ваш Git использует некоторые настройки по умолчанию. Если у вас нет конкретного refspec для пульта, ваш Git использует push.default. Вот где его пять настроек:

  • nothing: это приводит к сбою git push, заставляя вас перечислить некоторые refspec (s). (Я сам попробовал это некоторое время, но мне было слишком больно.)

  • current: это говорит вашему Git использовать вашу текущую ветку . Возможно, это было то, что вы ожидали. Это эквивалентно git push <em>remote</em> refs/heads/<em>branch</em>:refs/heads/<em>branch</em>.

  • upstream (он же tracking): это говорит вашему Git использовать вашу текущую ветвь в качестве источника, но использовать его восходящее имя в качестве места назначения. То есть, если ваша текущая ветвь имеет значение B, но восходящий поток B равен origin/not-B, это эквивалентно git push origin B:not-B. 1

  • simple: аналогично upstream, но требует, чтобы вышестоящее имя совпадало с именем текущей ветви. То есть, если master имеет origin/master в качестве восходящего потока, и вы находитесь на master, git push подталкивает к origin/master, как вы ожидаете, но если B подталкивает к origin/not-B, и вы находитесь на B, git push просто выходит из строя.

  • matching: ваш Git просматривает список имен веток в Git (все имена git ls-remote, начинающиеся с refs/heads/). Для каждого имени ветви, которое у них есть, ваш Git выдвигает вашу ветку с тем же именем.

Обратите внимание, что если вы используете флаг --force, это относится ко всем выдвинутым ветвям. Если ваш режим matching, он применяется к соответствующим веткам. Вот почему ваш вывод гласил:

+ 5a649bc...8d320d2 develop -> develop (forced update)

Ваш мерзавец обнаружил, что и у вас, и у них refs/heads/develop. Их было 5a649bc, ваше было 8d320d2, а 5a649bc не предок 8d320d2. Вежливый запрос - * пожалуйста, установите develop на 8d320d2 - можно было бы отклонить, но с действующим флагом силы ваш Git отправил команду, и их Git повиновался. Это потеряло некоторые коммиты из их develop, поэтому они сказали «принудительное обновление», и ваш Git напечатал это и три точки (обычный не принудительный push показывает только две точки).

Если у вас все еще есть коммит 5a649bc в вашем собственном репозитории, вы можете легко восстановиться после этого. Если нет, это сложнее. Чтобы восстановить, если у вас есть, рассмотрите запуск:

git push origin +5a649bc:refs/heads/develop

При этом используется refspec, в котором установлен + (флаг принудительной установки), source - это хэш необработанного коммита 5a649bc, а destination - имя ветви develop. Обратите внимание, что мудро (и, возможно, даже необходимо) здесь прописать refs/heads/develop, поскольку "name" источника - это необработанный хэш-идентификатор, поэтому ваш Git не знает, что это должна быть ветвь.


1 Это может или не может вызвать призрак Шекспира. (Или это король Гамлет?)

...