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

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

git merge origin/branchB

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

git checkout branchB
git pull
git checkout branchA

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

367
задан charles 09 июля '10 в 23:32
источник поделиться

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, не проверяя сначала, если это приведет к смене без перемотки вперед. Это связано с тем, что для устранения любых потенциальных конфликтов необходима рабочая копия.

Однако в случае быстрых слияний, это возможно, потому что такие слияния никогда не могут привести к конфликтам, по определению. Чтобы сделать это, не проверяя сначала ветвь, вы можете использовать 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 обновляется, даже если он не приводит к быстрому обновлению.

См. также

548
ответ дан user456814 18 июля '13 в 15:06
источник поделиться

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

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

git branch -f branch-b branch-a

Будет обновлен branch-b, чтобы указать на заголовок branch-a.

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

72
ответ дан Amber 11 нояб. '10 в 20:10
источник поделиться

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

Я использую 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 "$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 "$1" "$2";;
    * ) _usage
esac

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

26
ответ дан Jefromi 11 нояб. '10 в 20:40
источник поделиться

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

19
ответ дан Jefromi 10 июля '10 в 3:59
источник поделиться

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

git fetch remote
git branch -f localbranch remote/remotebranch

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

10
ответ дан kkoehne 08 июня '11 в 13:08
источник поделиться

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

6
ответ дан wnoise 11 нояб. '10 в 21:00
источник поделиться

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

git fetch origin branchB:branchB

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

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

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

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

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

git fetch . remotes/origin/master:master

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

5
ответ дан Bennett McElwee 17 нояб. '15 в 3:07
источник поделиться

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

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

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

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

3
ответ дан lkraider 29 мая '14 в 20:15
источник поделиться

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

[core]
    logallrefupdates=true

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

git reflog show mybranch

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

3
ответ дан Casebash 20 дек. '11 в 3:22
источник поделиться

Я написал функцию оболочки для аналогичного случая использования, с которым я ежедневно сталкиваюсь в проектах. Это, в основном, ярлык для обновления локальных веток с общей веткой, например, до разработки 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

2
ответ дан rkd 13 февр. '16 в 3:03
источник поделиться

Другие вопросы по меткам