Объединять, обновлять и извлекать ветки Git, не используя извлечения
Я работаю над проектом, который имеет 2 ветви, A и B. Я обычно работаю в ветви A, и объединяю вещи из ветви B. Для объединения я обычно делаю:
git merge origin/branchB
Тем не менее, я также хотел бы сохранить локальную копию ветви B, так как я могу иногда проверять ветку, не сливаясь сначала с моей веткой A. Для этого я бы сделал:
git checkout branchB
git pull
git checkout branchA
Есть ли способ сделать вышеупомянутое в одной команде, и без необходимости переключать ветви назад и вперед? Должен ли я использовать git update-ref
для этого? Как?
20 ответов
Краткий ответ
Пока вы выполняете ускоренное слияние, вы можете просто использовать
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 без предварительной проверки 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>
Обратите внимание, что вы можете передать свой локальный репо как "удаленный" параметр, используя .
:
git fetch . <sourceBranch>:<destinationBranch>
Документация
От git fetch
Документация, которая объясняет этот синтаксис (выделено мной):
<refspec>
Формат
<refspec>
параметр является необязательным плюсом+
с указанием источника ссылки<src>
с последующим двоеточием:
, затем пункт назначения<dst>
,Ссылка на удаленный объект, который соответствует
<src>
извлекается, и если<dst>
не пустая строка, локальная ссылка, которая соответствует ей, быстро пересылается с помощью<src>
, Если дополнительный плюс+
используется, локальный ref обновляется, даже если это не приводит к ускоренному обновлению.
Смотрите также
Нет, нет Извлечение целевой ветви необходимо для разрешения конфликтов, среди прочего (если Git не может автоматически объединить их).
Однако, если слияние происходит быстро, вам не нужно проверять целевую ветвь, потому что вам на самом деле ничего не нужно объединять - все, что вам нужно сделать, это обновить ветку, чтобы она указала на новый руководитель исх. Вы можете сделать это с git branch -f
:
git branch -f branch-b branch-a
Будет обновлять branch-b
указывать на голову branch-a
,
-f
вариант выступает за --force
, что означает, что вы должны быть осторожны при его использовании. Не используйте его, если вы не уверены, что слияние будет ускоренным.
Как сказала Эмбер, слияния ускоренной перемотки - это единственный случай, когда вы можете это сделать. Любое другое слияние, по-видимому, должно пройти через все трехстороннее слияние, применение исправлений, разрешение конфликтных ситуаций - и это означает, что вокруг должны быть файлы.
У меня есть сценарий, который я использую именно для этого: выполнение слияний в ускоренном режиме без касания рабочего дерева (если только вы не объединяетесь в 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
PS Если кто-нибудь видит какие-либо проблемы с этим сценарием, пожалуйста, прокомментируйте! Это была работа "пиши и забудь", но я был бы рад ее улучшить.
Вы можете сделать это, только если слияние ускорено. Если это не так, то 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.)
В вашем случае вы можете использовать
git fetch origin branchB:branchB
который делает то, что вы хотите (при условии быстрого слияния). Если ветвь не может быть обновлена, потому что она требует слияния без ускоренной перемотки вперед, тогда это безопасно завершается с сообщением.
Эта форма извлечения также имеет несколько полезных опций:
git fetch <remote> <sourceBranch>:<destinationBranch>
Обратите внимание, что <remote>
может быть локальным хранилищем, и <sourceBranch>
может быть отслеживающей веткой. Таким образом, вы можете обновить локальную ветку, даже если она не извлечена, без доступа к сети.
В настоящее время мой доступ к вышестоящему серверу осуществляется через медленный VPN, поэтому я периодически подключаюсь, git fetch
обновить все пульты, а затем отключить. Тогда, если, скажем, удаленный мастер изменился, я могу сделать
git fetch . remotes/origin/master:master
чтобы безопасно привести моего местного мастера в актуальное состояние, даже если у меня в настоящее время есть другая ветвь, проверенная. Доступ к сети не требуется.
Другой, по общему признанию, довольно грубый способ - просто заново создать ветку:
git fetch remote
git branch -f localbranch remote/remotebranch
Это отбрасывает локальную устаревшую ветвь и воссоздает одну с тем же именем, поэтому используйте с осторожностью...
Вы можете клонировать репо и выполнить слияние в новом репо. На той же файловой системе, это будет жесткая ссылка, а не скопировать большую часть данных. Закончите, потянув результаты в исходный репо.
Вопрос простой, и ответ должен быть таким же простым. Все, что просит OP, - это объединить восходящий поток
origin/branchB
в свою текущую ветку без переключения ветвей.
TL;DR:
git fetch
git merge origin/branchB
Полный ответ:
git pull
делает выборку + слияние. Это примерно то же самое, что и две приведенные ниже команды, где
<remote>
обычно
origin
(по умолчанию), а ветвь удаленного отслеживания начинается с
<remote>/
за которым следует имя удаленной ветки:
git fetch [<remote>]
git merge @{u}
В
@{u}
нотация - это настроенная ветвь удаленного отслеживания для текущей ветви. Если
branchB
треки
origin/branchB
тогда
@{u}
из
branchB
это то же самое, что печатать
origin/branchB
(видеть
git rev-parse --help
для получения дополнительной информации).
Поскольку вы уже сливаетесь с
origin/branchB
, все, что отсутствует, это
git fetch
(который может запускаться из любой ветки), чтобы обновить эту ветку удаленного отслеживания.
Однако обратите внимание, что если было какое-либо слияние из пула для включения, вам лучше слить
branchB
в
branchA
после оттягивания
branchB
(и, в конечном итоге, отправьте изменения в orign/branchB), но пока они перематываются вперед, они останутся такими же.
Помните о местных
branchB
не будет обновляться до тех пор, пока вы не переключитесь на него и не выполните фактическое извлечение, однако, пока в эту ветку не добавлены локальные коммиты, он будет просто перемещаться вперед в удаленную ветку.
Введите git-forward-merge:
Без необходимости проверять пункт назначения,
git-forward-merge <source> <destination>
объединяет источник в целевую ветку.
https://github.com/schuyler1d/git-forward-merge
Работает только для автоматических слияний, если есть конфликты, вам нужно использовать обычное слияние.
Для многих пользователей GitFlow наиболее полезными командами являются:
git fetch origin master:master --update-head-ok
git fetch origin dev:dev --update-head-ok
В
--update-head-ok
флаг позволяет использовать ту же команду при включенном
dev
или же
master
ветви.
Удобный псевдоним в
.gitconfig
:
[alias]
f=!git fetch origin master:master --update-head-ok && git fetch origin dev:dev --update-head-ok
Во многих случаях (например, слияние) вы можете просто использовать удаленную ветвь без необходимости обновлять локальную ветвь отслеживания. Добавление сообщения в reflog звучит как перебор и остановит его быстрее. Чтобы облегчить восстановление, добавьте следующее в ваш git config
[core]
logallrefupdates=true
Затем введите
git reflog show mybranch
чтобы увидеть недавнюю историю для вашей отрасли
Я написал функцию оболочки для аналогичного варианта использования, с которым я сталкиваюсь ежедневно в проектах. Это в основном ярлык для поддержания локальных веток в актуальном состоянии с помощью общей ветки, такой как разработка до открытия 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
Просто вытащить мастер, не проверяя мастер, который я использую
git fetch origin master:master
Другой способ эффективно сделать это:
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
git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
Ты можешь попробовать git worktree
чтобы две ветки были открыты бок о бок, это звучит так, как будто это может быть то, что вы хотите, но сильно отличается от некоторых других ответов, которые я видел здесь.
Таким образом, вы можете отслеживать две отдельные ветки в одном репозитории git, поэтому вам нужно выполнить выборку только один раз, чтобы получать обновления в обоих рабочих деревьях (вместо того, чтобы дважды клонировать git и git pull для каждого)
Worktree создаст новый рабочий каталог для вашего кода, в котором вы можете одновременно извлекать другую ветку вместо того, чтобы менять ветки на месте.
Если вы хотите удалить его, вы можете очистить его с помощью
git worktree remove [-f] <worktree>
Если вы хотите сохранить то же дерево, что и одна из ветвей, которые вы хотите объединить (т. Е. Не настоящее "объединение"), вы можете сделать это следующим образом.
# 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"
Совершенно возможно выполнить любое слияние, даже без перемотки вперед, без 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
ветку, не переключая кассу.
На самом деле это очень важная функция в моем повседневном рабочем процессе. Должен быть задокументирован здесь для справки.
Предположим, я работаю в ветке, но у меня есть что-то, что должно быть зафиксировано в , что является веткой, отстающей от текущей.HEAD
. Раньше я делал эти шаги: сначала checkout , затемgit rebase feat
. Но это обычно приводит к тому, что мой работающий сервер разработки обнаруживает изменение и повторно отображает старую версию, что обычно приводит к сбою, если ваше рабочее дерево слишком отличается от текущей ветки. Чтобы избежать значительных изменений рабочего дерева, оставаясь на ветке, я просто делаюgit fetch <repo> <src>:<dst>
, в приведенном выше случае это будет:
git fetch . HEAD:feat2
или
git fetch . feat1:feat2
После этого будет указывать на ту же фиксацию, что иfeat1
. Затем вы можете безопасно оформить заказfeat2
без сбоя вашего сервера разработки.
Я использую следующий псевдоним:
[alias]
sync = !git fetch && git fetch -u . $(git for-each-ref --format='%(push):%(refname)' refs/heads)
См. /questions/38923186/mozhet-li-git-pull-all-obnovit-vse-moi-lokalnyie-filialyi/66342680#66342680 для подробного объяснения.
Вы можете просто git pull origin branchB
в ваш branchA
и git сделает это за вас.