Объединять, обновлять и вытягивать ветки Git без использования проверок

Я работаю над проектом с двумя ветвями, A и B. Обычно я работаю над ветвью A и объединяю вещи из ветки B. Для слияния я обычно делал:

git merge origin/branchB

Тем не менее, я также хотел бы сохранить локальную копию ветки B, так как иногда я могу проверить ветку без первого слияния с моей ветвью A. Для этого я бы сделал:

git checkout branchB
git pull
git checkout branchA

Есть ли способ сделать это в одной команде и без необходимости переключать ветку назад и вперед? Должен ли я использовать git update-ref для этого? Как?

Ответ 1

Короткий ответ

Пока вы выполняете слияние с быстрой перемоткой вперед, вы можете просто использовать

git fetch <remote> <sourceBranch>:<destinationBranch>

Примеры:

# Merge local branch foo into local branch master,
# without having to checkout master first.
# Here `.` means to use the local repository as the "remote":
git fetch . foo:master

# Merge remote branch origin/foo into local branch foo,
# without having to checkout foo first:
git fetch origin foo:foo

В то время как Янтарный ответ будет также работать в случаях быстрой перемотки вперед, используя git fetch таким образом, вместо этого немного безопаснее, чем просто принудительное перемещение ссылки на ветку, поскольку git fetch автоматически предотвратит случайные небыстрые форварды до тех пор, пока вы не используете + в refspec.

Длительный ответ

Вы не можете объединить ветвь B в ветвь A, не проверяя сначала, если это приведет к смене без перемотки вперед. Это связано с тем, что для устранения любых потенциальных конфликтов необходима рабочая копия.

Однако в случае быстрых слияний, это возможно, потому что такие слияния никогда не могут привести к конфликтам, по определению. Чтобы сделать это, не проверяя сначала ветвь, вы можете использовать git fetch с помощью refspec.

Здесь приведен пример обновления master (запрет изменений без перемотки вперед), если вы выбрали другую ветвь feature:

git fetch upstream master:master

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

[alias]
    sync = !sh -c 'git checkout --quiet HEAD; git fetch upstream master:master; git checkout --quiet -'

Что делает этот псевдоним:

  • git checkout HEAD: это превращает вашу рабочую копию в состояние отдельной головы. Это полезно, если вы хотите обновить master, пока у вас есть его извлечение. Я думаю, что это было необходимо сделать, потому что иначе ссылка на master не будет двигаться, но я не помню, действительно ли это прямо сейчас.

  • git fetch upstream master:master: это быстро перенаправляет ваш локальный master на то же место, что и upstream/master.

  • git checkout - проверяет вашу ранее выделенную ветку (то, что делает - в этом случае).

Синтаксис git fetch для (не) быстрого перехода вперед

Если вы хотите, чтобы команда fetch вышла из строя, если обновление не является быстрым, вы просто используете refspec формы

git fetch <remote> <remoteBranch>:<localBranch>

Если вы хотите разрешить обновления без пересылки, добавьте + в начало refspec:

git fetch <remote> +<remoteBranch>:<localBranch>

Обратите внимание, что вы можете передать локальное репо в качестве параметра "remote" с помощью .:

git fetch . <sourceBranch>:<destinationBranch>

Документация

Из git fetch документации, которая объясняет этот синтаксис (внимание мое):

<refspec>

Формат параметра <refspec> - необязательный плюс +, за которым следует исходный код ref <src>, за которым следует двоеточие :, за которым следует пункт назначения ref <dst>.

Удаляется ссылка ref, соответствующая <src>, и если <dst> не является пустой строкой, локальный ref, соответствующий ей, пересылается с помощью <src>. Если используется дополнительный плюс +, локальный ref обновляется, даже если он не приводит к быстрому обновлению.

См. также

Ответ 2

Нет, нет. Извлечение целевой ветки необходимо для разрешения конфликтов, среди прочего (если Git не может автоматически объединить их).

Однако, если слияние происходит быстро, вам не нужно проверять целевую ветвь, потому что вам на самом деле ничего не нужно объединять - все, что вам нужно сделать, это обновить ветку, чтобы она указала на новый руководитель исх. Вы можете сделать это с помощью git branch -f:

git branch -f branch-b branch-a

Обновит branch-b, чтобы он указывал на голову branch-a.

Опция -f означает --force, что означает, что вы должны быть осторожны при ее использовании.

Не используйте его, если вы не уверены, что слияние будет ускоренным.

Ответ 3

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

Я использую script, который я использую для этого: выполнение быстрых слияний, не касаясь дерева работы (если вы не сливаетесь с HEAD). Он немного длинный, потому что он хотя бы немного надежный - он проверяет, чтобы слияние было быстрым, затем выполняет его, не проверяя ветвь, но получая те же результаты, что и у вас, - вы видите diff --stat сводка изменений, а запись в рефлоге - это как ускоренное слияние вперед, а не "reset", которое вы получите, если используете branch -f. Если вы назовете его git-merge-ff и поместите его в каталог bin, вы можете вызвать его как команду git: git merge-ff.

#!/bin/bash

_usage() {
    echo "Usage: git merge-ff <branch> <committish-to-merge>" 1>&2
    exit 1
}

_merge_ff() {
    branch="$1"
    commit="$2"

    branch_orig_hash="$(git show-ref -s --verify refs/heads/$branch 2> /dev/null)"
    if [ $? -ne 0 ]; then
        echo "Error: unknown branch $branch" 1>&2
        _usage
    fi

    commit_orig_hash="$(git rev-parse --verify $commit 2> /dev/null)"
    if [ $? -ne 0 ]; then
        echo "Error: unknown revision $commit" 1>&2
        _usage
    fi

    if [ "$(git symbolic-ref HEAD)" = "refs/heads/$branch" ]; then
        git merge $quiet --ff-only "$commit"
    else
        if [ "$(git merge-base $branch_orig_hash $commit_orig_hash)" != "$branch_orig_hash" ]; then
            echo "Error: merging $commit into $branch would not be a fast-forward" 1>&2
            exit 1
        fi
        echo "Updating ${branch_orig_hash:0:7}..${commit_orig_hash:0:7}"
        if git update-ref -m "merge $commit: Fast forward" "refs/heads/$branch" "$commit_orig_hash" "$branch_orig_hash"; then
            if [ -z $quiet ]; then
                echo "Fast forward"
                git diff --stat "[email protected]{1}" "$branch"
            fi
        else
            echo "Error: fast forward using update-ref failed" 1>&2
        fi
    fi
}

while getopts "q" opt; do
    case $opt in
        q ) quiet="-q";;
        * ) ;;
    esac
done
shift $((OPTIND-1))

case $# in
    2 ) _merge_ff "$1" "$2";;
    * ) _usage
esac

P.S. Если кто-нибудь видит какие-либо проблемы с этим script, прокомментируйте! Это была работа с записью и забыть, но я был бы рад ее улучшить.

Ответ 4

Вы можете сделать это только в том случае, если слияние является быстрой перемоткой вперед. Если это не так, то git необходимо, чтобы файлы были извлечены, чтобы они могли объединить их!

Сделать это только для быстрой перемотки вперед:

git fetch <branch that would be pulled for branchB>
git update-ref -m "merge <commit>: Fast forward" refs/heads/<branch> <commit>

где <commit> - это выбранная фиксация, которую вы хотите перемотать вперед. Это в основном похоже на использование git branch -f для перемещения ветки, за исключением того, что она также записывает его в reflog, как если бы вы действительно выполняли слияние.

Пожалуйста, пожалуйста, пожалуйста, не делайте этого для чего-то, что не является быстрой перемоткой, или просто переустановите свою ветку на другую фиксацию. (Чтобы проверить, видит ли git merge-base <branch> <commit> ветвь SHA1.)

Ответ 5

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

git fetch remote
git branch -f localbranch remote/remotebranch

Это отбрасывает локальную устаревшую ветвь и воссоздает одно с тем же именем, поэтому используйте с осторожностью...

Ответ 6

В вашем случае вы можете использовать

git fetch origin branchB:branchB

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

В этой форме выборки есть еще несколько полезных опций:

git fetch <remote> <sourceBranch>:<destinationBranch>

Обратите внимание, что <remote> может быть локальным репозиторием, а <sourceBranch> может быть ветвью отслеживания. Таким образом, вы можете обновить локальную ветвь, даже если она не проверена, без доступа к сети.

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

git fetch . remotes/origin/master:master

чтобы безопасно обновить мой локальный мастер, даже если у меня в настоящее время имеется какая-либо другая ветка. Не требуется доступ к сети.

Ответ 7

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

Ответ 8

Для многих случаев (например, для слияния) вы можете просто использовать удаленную ветку без обновления локальной ветки отслеживания. Добавление сообщения в рефлоге звучит как overkill и перестанет быть быстрым. Чтобы упростить восстановление, добавьте следующее в git config

[core]
    logallrefupdates=true

Затем введите

git reflog show mybranch

чтобы увидеть недавнюю историю вашего ветки

Ответ 9

Введите git -forward-merge:

Без необходимости назначения отправления, git-forward-merge <source> <destination> объединяет источник в ветку назначения.

https://github.com/schuyler1d/git-forward-merge

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

Ответ 10

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

Проводя это, даже если вы не хотите использовать checkout, если другие не возражают против этого ограничения.

glmh ( "git pull and merge here" ) автоматически будет checkout branchB, pull последним, повторно checkout branchA и merge branchB.

Не учитывается необходимость сохранения локальной копии branchA, но ее можно легко изменить, добавив шаг перед проверкой branchB. Что-то вроде...

git branch ${branchA}-no-branchB ${branchA}

Для простых переходов с быстрым переходом это пропускается в сообщение сообщения о фиксации.

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

Чтобы настроить, добавьте в .bashrc или .zshrc и т.д.:

glmh() {
    branchB=$1
    [ $# -eq 0 ] && { branchB="develop" }
    branchA="$(git branch | grep '*' | sed 's/* //g')"
    git checkout ${branchB} && git pull
    git checkout ${branchA} && git merge ${branchB} 
}

Использование:

# No argument given, will assume "develop"
> glmh

# Pass an argument to pull and merge a specific branch
> glmh your-other-branch

Примечание. Это недостаточно эффективно для передачи аргументов за пределами имени ветки в git merge

Ответ 11

Еще один способ эффективно сделать это:

git fetch
git branch -d branchB
git branch -t branchB origin/branchB

Поскольку это строчная буква -d, она удалит ее, только если данные где-то еще будут существовать. Это похоже на ответ @kkoehne, за исключением того, что это не заставляет. Из-за -t он снова настроит пульт.

У меня была немного другая потребность, чем в OP, который заключался в создании новой ветки функций из develop (или master) после объединения запроса на извлечение. Это может быть выполнено в одной строке без силы, но это не обновляет локальную ветвь develop. Это просто вопрос проверки новой ветки и ее основание origin/develop:

git checkout -b new-feature origin/develop

Ответ 12

git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]

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

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

Worktree создаст новый рабочий каталог для вашего кода, в котором вы можете одновременно проверять другую ветку вместо того, чтобы поменять местами ветки.

Если вы хотите удалить его, вы можете очистить с помощью

git worktree remove [-f] <worktree>

Ответ 13

просто вытащить мастера, не проверяя мастер, которым я пользуюсь

git fetch origin master:master