Как я могу перемотать вперед один git commit программно?

Я периодически получаю сообщение от git, которое выглядит так:

Your branch is behind the tracked remote branch 'local-master/master' 
by 3 commits, and can be fast-forwarded.

Я хотел бы уметь писать команды в shell-скрипт, который может делать следующее:

  1. Как я могу сказать, Может ли моя текущая ветвь быть быстро перенаправлена из удаленной ветви, которую она отслеживает?

  2. Как я могу сказать, сколько коммитов "позади" моей ветви?

  3. Как я могу перемотать вперед просто один commit, so что, например, моя локальная ветвь будет идти от " позади 3 коммитов "к"позади 2 коммитов"?

(для тех, кто заинтересован, я пытаюсь собрать качественное зеркало git/darcs.)

3 ответов


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

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

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

как узнать, может ли ветка перемотать вперед к своему восходящему потоку Бранч

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

Поиск вверх по течению для ветки

Git 1.7.0 имеет удобный способ запроса, какая ветвь отслеживает ветвь (ее ветвь "вверх по течению"). The @{upstream} синтаксис спецификации объекта может использоваться в качестве спецификатора ветви. Как голое имя, это относится к восходящей ветви для ветви, которая в настоящее время извлечена. В качестве суффикса он может использоваться для поиска восходящей ветви для ветвей, которые в настоящее время не проверяются.

для Gits ранее 1.7.0 вам придется самостоятельно анализировать параметры конфигурации ветки (branch.name.remote и branch.name.merge). Кроме того, если у вас есть стандартное соглашение об именах, вы можете просто использовать его для определения имени восходящей ветви.

в этом ответе я напишу upstream для ссылки на фиксацию в конце ветви, которая находится выше текущей ветви.

проверка возможности перемотки вперед

ветвь при фиксации A может быть быстро перенаправлена на фиксацию B, если и только если A является предком B.

gyim показывает один из способов проверить это условие (перечислите все коммиты, доступные из B и проверьте A в списке). Возможно, более простой способ проверить это условие-проверить, что A является базой слияния A и Б.

can_ff() {
    a="$(git rev-parse "")" &&
    test "$(git merge-base "$a" "")" = "$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

это не требует, чтобы голова могла перемотать вперед на вверх по течению (он только подсчитывает, как далеко голова позади вверх по течению, а не как далеко вверх по течению позади головы).

двигаться вперед на один коммит

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

---o---o                      master
       |\
       | A--o--o--o--o--o--o  upstream
        \                 /
         B---o---o---o---o

вы можете следовать одной стороне, как если бы это была линейная история, но только до непосредственного предка фиксации слияния.

пересмотр ходьба команды имеют --first-parent опция, которая позволяет легко следовать только коммиты, которые приводят к первому родительскому слияния коммитов. Объедините это с сброс git и вы можете эффективно перетащите ветку "вперед, по одной фиксации за раз".

git reset --hard "$(git rev-list --first-parent --topo-order --reverse ^HEAD upstream | head -1)"

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

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

когда вы переместились в точку, близкую к слиянию, DAG будет выглядеть так (топология такая же, как и раньше, это только мастер метка, которая переместилась):

---o---o--A--o--o--o--o--o    master
       |                  \
       |                   o  upstream
        \                 /
         B---o---o---o---o

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

вы, вероятно, хотите, чтобы тщательно рассмотреть то, что вы действительно хотите делать в этом случае. Можно просто перетащить дополнительные коммиты, как это, или должен быть какой-то механизм для "возврата" к родителю B и продвижения вперед по этой ветви перед обработкой коммита слияния?


Это, вероятно, не самый элегантный, но он работает:

$ 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.