Как я могу перенести один git коммит программным способом? - PullRequest
16 голосов
/ 23 мая 2010

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

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

Я хотел бы иметь возможность писать команды в сценарии оболочки, которые могут выполнять следующие действия:

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

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

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

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

Ответы [ 3 ]

10 голосов
/ 29 мая 2010

Альтернативные подходы

Вы упоминаете, что работаете над каким-то зеркалом для Git и Darcs. Вместо перетаскивания рабочего дерева по истории, вы можете вместо этого взглянуть на команды git fast-import и git fast-export для посмотрите, предлагают ли они лучший способ управления данными, которые вам нужно извлечь / предоставить.

Как определить, может ли ветвь быстро перейти к вышестоящей ветке

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

Поиск восходящего потока для ветви

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

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

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

Проверка возможности быстрой перемотки вперед

Ветвь при коммите А может быть быстро перенесена на коммит Б, если и только если А является предком Б.

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 мог выполнять быструю перемотку вперед до в восходящем направлении (он только подсчитывает, насколько далеко HEAD находится за восходящим потоком, а не как далеко вверх находится за HEAD).

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

В общем, история с быстрой перемоткой может быть не линейной. В приведенной ниже истории DAG master может быстро перейти к upstream , но и A, и B являются «одним коммитом вперед» от master на пути к * * перед тысячей пятьдесят-одина * 1 052 *.

---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 в качестве неназванной ветви. Пока ваше рабочее дерево чистое и вы не возражаете перемещать ветку (или отдельную ГОЛОВКУ), git reset --hard ничего не будет выбрасывать. Если вы все еще волнуетесь, вам следует серьезно заняться использованием git fast-export , когда вам вообще не нужно прикасаться к рабочему дереву.

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

Когда вы продвигаетесь к точке, которая находится чуть ниже слияния, группа обеспечения доступности баз данных будет выглядеть следующим образом (топология такая же, как и раньше, перемещается только метка master ): * * 1077

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

На данный момент, если вы«Двигаться вперед на один коммит», вы перейдете к слиянию. Это также «принесет» (сделает доступным из master ) все коммиты от B до коммита слияния. Если вы предполагаете, что «продвижение вперед на один коммит» добавит только один коммит в историю DAG, тогда этот шаг нарушит это предположение.

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

9 голосов
/ 29 мая 2010

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

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

# 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, если сохраните результат первого вызова в файл.

7 голосов
/ 23 мая 2010

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

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