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

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

git merge origin/branchB

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

git checkout branchB
git pull
git checkout branchA

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

10 ответов


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

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

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, не проверив сначала 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 -'

этот псевдоним делает следующее:

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

  2. git fetch upstream master:master: это быстро вперед ваш местный master как upstream/master.

  3. 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>

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

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

См. Также

  1. git checkout и объединить, не касаясь рабочего дерева

  2. слияние без изменения рабочей каталог


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

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

git branch -f branch-b branch-a

обновить branch-b указать на голову branch-a.

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


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

у меня есть сценарий, который я использую именно для этого: выполнение быстрой перемотки вперед сливается, не касаясь дерева работы (если вы не сливаетесь в HEAD). Он немного длинный, потому что он, по крайней мере, немного прочный - это проверяет, что слияние будет перемоткой вперед, а затем выполняет его без проверки ветви, но дает те же результаты, что и если бы вы-вы видите diff --stat сводка изменений, и запись в рефлоге точно такая же, как быстрое слияние, вместо "сброса", который вы получаете, если используете 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=""
    commit=""

    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 "$branch@{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 "" "";;
    * ) _usage
esac

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


вы можете сделать это только в том случае, если слияние является перемоткой вперед. Если это не так, то 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 ветви.)


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

git fetch remote
git branch -f localbranch remote/remotebranch

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


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

git fetch origin branchB:branchB

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

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

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

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

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

git fetch . remotes/origin/master:master

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


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


Enter git-forward-merge:

без выезда, пункт назначения, git-forward-merge <source> <destination> сливается в ветку по назначению.

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

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


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

[core]
    logallrefupdates=true

введите

git reflog show mybranch

посмотреть недавнюю историю для вашего филиала


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

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

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

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

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

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

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

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

glmh() {
    branchB=
    [ $# -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

Примечание:не достаточно прочный, чтобы передать args за пределами имени ветви git merge