Я периодически получаю сообщение от мерзавца, которые похожи на это:
Your branch is behind the tracked remote branch 'local-master/master'
by 3 commits, and can be fast-forwarded.
Я хотел бы смочь записать команды в сценарии оболочки, который может сделать следующее:
Как я могу сказать, может ли мое текущее ответвление быть быстро передано от удаленного ответвления, оно отслеживает?
Как я могу сказать, сколько фиксаций "позади" моего ответвления?
Как может я ускоренная перемотка вперед всего одной фиксацией, так, чтобы, например, мое локальное ответвление пошло бы от "позади 3 фиксациями" к "позади 2 фиксациями"?
(Для тех, кому интересно, я пытаюсь соединить качество git/darcs зеркало.)
Удаленная ветвь может быть быстро перенаправлена в локальную ветвь, если текущая фиксация является предком удаленной головы ветки. Другими словами, если «история одной ветки» удаленной ветки содержит текущую фиксацию (потому что, если это так, то наверняка новые фиксации были зафиксированы «на» текущей фиксации)
Так что безопасный способ определить, может ли удаленная ветвь быть быстрой пересылкой:
# Convert reference names to commit IDs
current_commit=$(git rev-parse HEAD)
remote_commit=$(git rev-parse remote_name/remote_branch_name)
# Call git log so that it prints only commit IDs
log=$(git log --topo-order --format='%H' $remote_commit | grep $current_commit)
# Check the existence of the current commit in the log
if [ ! -z "$log" ]
then echo 'Remote branch can be fast-forwarded!'
fi
Обратите внимание, что git log был вызван без параметра --all (который перечислял бы все ветки), поэтому невозможно, чтобы текущая фиксация находилась на «боковой ветви» и все еще печатается на выходе.
Количество коммитов перед текущим фиксацией равно количеству строк в $ log перед $ current_commit.
Если вы хотите перемотать вперед только одну фиксацию, вы берете строку, предшествующую текущей фиксации (например, с grep -B 1), и сбрасываете локальную ветвь на эту фиксацию.
ОБНОВЛЕНИЕ : вы можете использовать git log commit1..commit2
, чтобы определить количество коммитов с перемоткой вперед:
if [ ! -z "$log" ]
then
# print the number of commits ahead of the current commit
ff_commits=$(git log --topo-order --format='%H' \
$current_commit..$remote_commit | wc -l)
echo "Number of fast-forwarding commits: $ff_commits"
# fast-forward only one commit
if [ $ff_commits -gt 1 ]
then
next_commit=$(git log --topo-order --format='%H' \
$current_commit..$remote_commit | tail -1)
git reset --hard $next_commit
fi
fi
Конечно, вы можете сделать это с помощью одного вызова журнала git, если вы сохранить результат первого вызова в файл.
Это, наверное, не самый элегантный вариант, но он работает:
$ git fetch $ git status | sed -n 2p # Your branch is behind 'origin/master' by 23 commits, and can be fast-forwarded. $ git reset origin/master~22 > /dev/null $ git status | sed -n 2p # Your branch is behind 'origin/master' by 22 commits, and can be fast-forwarded.
Вы упомянули, что работаете над чем-то вроде зеркала для Git и Darcs. Вместо того, чтобы тащить рабочее дерево через историю, вы могли бы рассмотреть команды git fast-import и git fast-export и посмотреть, не предлагают ли они лучший способ управления данными, которые вам нужно извлечь/предоставить.
Здесь есть две части. Во-первых, вы должны либо знать, либо определить, какая ветвь является "восходящей" для текущей ветви. Затем, когда вы знаете, как ссылаться на восходящую ветвь, вы проверяете возможность перемотки вперед.
В Git 1.7.0 есть удобный способ запросить, какую ветвь отслеживает ветвь (её "восходящий поток"). Синтаксис спецификации объекта @{upstream}
может быть использован в качестве спецификатора ветви. В качестве простого имени он ссылается на восходящую ветвь для ветви, которая в данный момент проверяется. Как суффикс, он может быть использован для поиска восходящей ветви для ветвей, которые в данный момент не проверены.
Для Gits более ранних версий, чем 1.7.0, вам придётся самостоятельно разбирать параметры конфигурации ветки (branch.name.remote
и branch.name.merge
). В качестве альтернативы, если у вас есть стандартное соглашение об именовании, вы можете просто использовать его для определения имени восходящей ветви.
В этом ответе я буду писать upstream
для обозначения коммита в вершине ветви, которая находится выше по течению от текущей ветви.
Ветвь на коммите A может быть перемотана на коммит B тогда и только тогда, когда A является предком B.
gyim показывает один из способов проверки этого условия (перечислить все коммиты, достижимые из B, и проверить наличие A в списке). Возможно, более простой способ проверки этого условия - проверить, что A является базой слияния A и B.
can_ff() {
a="$(git rev-parse "$1")" &&
test "$(git merge-base "$a" "$2")" = "$a"
}
if can_ff HEAD local-master/master; then
echo can ff to local-master/master
else
echo CAN NOT ff to local-master/master
fi
git rev-list ^HEAD upstream | wc -l
Для этого не требуется, чтобы HEAD мог перематываться вперёд до upstream (считается только то, насколько HEAD отстаёт от upstream, а не то, насколько upstream отстаёт от HEAD).
В общем случае, история с возможностью быстрой перемотки вперед может быть нелинейной. В приведенной ниже DAG истории, master может переместиться вперед на upstream, но и A, и B находятся "на один коммит вперед" от master на пути к upstream.
---o---o master
|\
| A--o--o--o--o--o--o upstream
\ /
B---o---o---o---o
Вы можете следовать в одну сторону, как если бы это была линейная история, но только до непосредственного предка коммита слияния.
Команды перемещения по ревизиям имеют опцию --first-parent
, которая позволяет легко отслеживать только те коммиты, которые ведут к первому предку коммитов слияния. Объедините это с git reset, и вы сможете эффективно перетащить ветку "вперёд, по одному коммиту за раз".
git reset --hard "$(git rev-list --first-parent --topo-order --reverse ^HEAD upstream | head -1)"
В комментарии к другому ответу вы выражаете опасения по поводу git reset. Если вы боитесь испортить какую-то ветку, то вы можете либо использовать временную ветку, либо использовать отделенный HEAD в качестве безымянной ветки. Пока ваше рабочее дерево чистое и вы не возражаете против перемещения ветки (или отсоединённого HEAD), git reset --hard
ничего не испортит. Если вы все еще беспокоитесь, вам стоит серьезно рассмотреть возможность использования git fast-export, где вам вообще не придется трогать рабочее дерево.
Следовать за другим родителем будет сложнее. Вероятно, вам придётся написать свой собственный обходчик истории, чтобы вы могли подсказывать ему, в каком направлении двигаться при каждом слиянии.
Когда вы продвинулись вперёд до точки, находящейся непосредственно перед слиянием, DAG будет выглядеть следующим образом (топология та же, что и раньше, только метка master переместилась):
---o---o--A--o--o--o--o--o master
| \
| o upstream
\ /
B---o---o---o---o
В этот момент, если вы "продвинетесь вперёд на один коммит", вы перейдёте к слиянию. Это также "принесёт" (сделает доступными из master) все коммиты из B до коммита слияния. Если вы предположите, что "перемещение вперёд на один коммит" добавит только один коммит в историческую группу DAG, то этот шаг нарушит это предположение.
Вероятно, вы захотите тщательно обдумать, что вы действительно хотите сделать в этом случае. Можно ли просто перетащить дополнительные коммиты таким образом, или должен быть какой-то механизм для "возврата" к родительской ветви B и продвижения вперед по этой ветви до обработки коммита слияния?
.