Объединять, обновлять и извлекать ветки Git, не используя извлечения - PullRequest
552 голосов
/ 10 июля 2010

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

git merge origin/branchB

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

git checkout branchB
git pull
git checkout branchA

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

Ответы [ 11 ]

853 голосов
/ 18 июля 2013

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

Пока вы выполняете слияние fast-forward , вы можете просто использовать

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 будетавтоматически предотвращать случайные не-быстрые пересылки, если вы не используете + в ссылке.

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

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

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

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

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>

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

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

См. Также

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

  2. Объединение без изменения рабочего каталога

80 голосов
/ 11 ноября 2010

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

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

git branch -f branch-b branch-a

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

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

29 голосов
/ 11 ноября 2010

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

У меня случайно есть сценарий, который я использую именно для этого: выполнять ускоренные слияния без соприкосновения с рабочим деревом (если только вы не объединяетесь в HEAD). Это немного долго, потому что это, по крайней мере, немного надежно - он проверяет, чтобы слияние было бы быстрым, затем выполняет его, не проверяя ветвь, но производя те же результаты, как если бы вы имели - вы видите diff --stat Сводка изменений, и запись в reflog точно такая же, как слияние с ускоренной перемоткой, вместо "перезагрузки", которую вы получаете, если используете 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. Если кто-то видит какие-либо проблемы с этим сценарием, пожалуйста, прокомментируйте! Это была работа «пиши и забудь», но я был бы рад ее улучшить.

19 голосов
/ 10 июля 2010

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

10 голосов
/ 08 июня 2011

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

git fetch remote
git branch -f localbranch remote/remotebranch

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

9 голосов
/ 17 ноября 2015

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

git fetch origin branchB:branchB

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

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

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

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

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

git fetch . remotes/origin/master:master

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

6 голосов
/ 11 ноября 2010

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

3 голосов
/ 29 мая 2014

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

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

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

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

3 голосов
/ 20 декабря 2011

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

[core]
    logallrefupdates=true

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

git reflog show mybranch

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

2 голосов
/ 13 февраля 2016

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

.
...