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

629

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

git merge origin/branchB

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

git checkout branchB
git pull
git checkout branchA

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

Чарльз
источник
1
Ответ Якуба на первый связанный вопрос объясняет, почему это вообще невозможно. Другое (апостериорное) объяснение состоит в том, что вы не можете слиться в голом репо, поэтому очевидно, что это требует рабочего дерева.
Каскабель
3
@Eric: Распространенные причины в том, что для больших репозиториев отнимает много времени, и они обновляют временные метки, даже если вы возвращаетесь к той же версии, поэтому make думает, что все нужно перестроить.
Каскабель
Второй вопрос, который я связал, - это необычный случай - слияния, которые могут быть ускоренными, но которые ОП хотел объединить, используя --no-ffопцию, которая в любом случае вызывает фиксацию слияния. Если вас это заинтересует, мой ответ показывает, как вы могли бы это сделать - не такой надежный, как мой опубликованный здесь ответ, но сильные стороны этих двух вопросов, безусловно, можно объединить.
Каскабель

Ответы:

973

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

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

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>

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

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

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

git fetch . <sourceBranch>:<destinationBranch>

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

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

<refspec>

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

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

Смотрите также

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

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

Сообщество
источник
3
git checkout --quiet HEADпо git checkout --quiet --detachсостоянию на Git 1.7.5.
Рафа
6
Я обнаружил, что должен был сделать: git fetch . origin/foo:fooобновить мой локальный foo до моего локального происхождения / foo
weston
3
Есть ли причина, по которой части «git checkout HEAD --quiet» и «git checkout --quiet -» включены в длинный ответ, но не в короткий ответ? Я полагаю, это потому, что сценарий может быть запущен, когда вы проверили мастер, даже если вы могли просто сделать git pull?
Шон
8
почему здесь 'get' 'команда' merge '... это просто не имеет смысла; Если «pull» равно «fetch», за которым следует «merge», должен быть более логичный эквивалент «merge --ff-only», который бы локально обновлял «branch» из «origin / branch», учитывая, что «fetch» ​​имеет уже был запущен.
Эд Рэндалл
1
Спасибо за git checkout -трюк! Так же просто, как cd -.
Конь
84

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

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

git branch -f branch-b branch-a

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

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

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

янтарный
источник
46
Просто будьте очень осторожны, чтобы не делать этого, если вы не уверены, что слияние будет ускоренным! Не хочу, чтобы потом понять, что вы потеряли коммиты.
Каскабель
@FuadSaud Не совсем. git resetработает только в проверенной ветке.
Янтарь
9
Тот же результат (ускоренная перемотка вперед) достигается git fetch upstream branch-b:branch-b(взято из этого ответа ).
Оливер
6
Чтобы расширить комментарий @ Oliver, вы также можете сделать git fetch <remote> B:A, где B и A - это совершенно разные ветви, но B можно быстро переместить вперед в A. Вы также можете передать свой локальный репозиторий как «удаленный», используя .в качестве удаленного псевдонима: git fetch . B:A,
8
Вопрос ОП ясно дает понять, что слияние действительно будет ускоренным. Независимо от branch -fтого, как вы указываете , может быть опасно. Так что не используйте это! Используйте fetch origin branchB:branchB, что не удастся безопасно, если слияние не вперед.
Беннет МакЭлви
30

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

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

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

Cascabel
источник
Вы можете быть заинтересованы в stackoverflow.com/a/5148202/717355 для сравнения
Филип Окли
@PhilipOakley Если посмотреть с первого взгляда, то, что в ядре делает то же самое, что и я. Но мой не жестко запрограммирован на одну пару веток, он имеет гораздо большую обработку ошибок, он имитирует вывод git-merge и делает то, что вы имеете в виду, если вы вызываете его, пока вы на самом деле находитесь на ветке.
Каскабель
У меня есть твое имя в моем каталоге \ bin ;-) Я просто забыл об этом и осмотрелся, увидел этот сценарий и он вызвал мой отзыв! Моя копия имеет эту ссылку и # or git branch -f localbranch remote/remotebranchнапоминает мне об источнике и опциях. Оставил свой комментарий по другой ссылке +1.
Филип Окли
3
+1, также может по умолчанию использовать в "$branch@{u}"качестве коммитта для слияния, чтобы получить ветку восходящего потока (из kernel.org/pub/software/scm/git/docs/gitrevisions.html )
orip
Спасибо! Есть ли шанс, что вы можете привести простой пример - использовать в ответ?
мр
20

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

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

Cascabel
источник
4
Есть ли способ заставить его потерпеть неудачу, если он не может перемотать вперед?
Ган
2
@gman вы можете использовать git merge-base --is-ancestor <A> <B>. «Б» - это то, что нужно объединить в «А». В качестве примера можно привести A = master и B = развернуть, убедившись, что разработка быстро переходит в master. Примечание: он существует с 0, если он не ff-способен, существует с 1, если он есть.
Eddiemoya
1
Не задокументировано в git-scm, но оно есть на kernal.org. kernel.org/pub/software/scm/git/docs/git-merge-base.html
eddiemoya
Сделал краткую суть, чтобы закончить то, о чем я говорю (на самом деле это не проверялось, но должно было помочь вам в этом). gist.github.com/eddiemoya/ad4285b2d8a6bdabf432 ---- как примечание, у меня есть большая часть этого под рукой, у меня есть скрипт, который проверяет ff и, если нет, позволяет сначала перебазировать нужную ветку - затем ff сливается - все без проверки ничего.
eddiemoya
12

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

git fetch origin branchB:branchB

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

Эта форма извлечения также имеет несколько полезных опций:

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

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

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

git fetch . remotes/origin/master:master

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

Беннет МакЭлви
источник
11

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

git fetch remote
git branch -f localbranch remote/remotebranch

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

kkoehne
источник
Я только что увидел, что в первоначальном ответе уже упоминается ветвь -f ... Тем не менее, я не вижу преимущества в том, чтобы слияние в reflog для сценария использования было описано ранее.
kkoehne
7

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

wnoise
источник
4

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

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

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

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

lkraider
источник
2
Я думаю, что это лучше, чем git fetch <remote> <source>: <destination>, потому что перемотка вперед - это операция слияния, а не извлечения, и ее легче написать. Плохая вещь, хотя, это не в мерзавце по умолчанию.
Бинар
4

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

[core]
    logallrefupdates=true

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

git reflog show mybranch

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

Casebash
источник
Я думаю, что раздел должен быть [core]нет [user]? (и он по умолчанию
включен
3

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

Другой способ эффективно сделать это:

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
Бенджамин Аткин
источник
1

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

git fetch origin master:master

Содхи Сааб
источник
1

Абсолютно возможно выполнить любое слияние, даже без слияния без перемотки вперед git checkout. worktreeОтвет на @grego хороший намек. Чтобы расширить это:

cd local_repo
git worktree add _master_wt master
cd _master_wt
git pull origin master:master
git merge --no-ff -m "merging workbranch" my_work_branch
cd ..
git worktree remove _master_wt

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

zanerock
источник
1

Если вы хотите сохранить то же дерево, что и одна ветвь, которую хотите объединить (т. Е. Не настоящее «слияние»), вы можете сделать это следующим образом.

# Check if you can fast-forward
if git merge-base --is-ancestor a b; then
    git update-ref refs/heads/a refs/heads/b
    exit
fi

# Else, create a "merge" commit
commit="$(git commit-tree -p a -p b -m "merge b into a" "$(git show -s --pretty=format:%T b)")"
# And update the branch to point to that commit
git update-ref refs/heads/a "$commit"
phkb
источник
1
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>
Grego
источник