Git: кумулятивный diff с ограничением фиксации

git log имеет некоторые очень полезные параметры ограничения фиксации, такие как --no-merges и --first-parent. Я хотел бы иметь возможность использовать эти параметры при создании кумулятивного патча diff/stat/numstat для диапазона коммитов.

С помощью этих команд:

git log --oneline --first-parent --no-merges --patch   29665b0..0b76a27
git log --oneline --first-parent --no-merges --stat    29665b0..0b76a27
git log --oneline --first-parent --no-merges --numstat 29665b0..0b76a27

diff не является кумулятивным (изменения перечислены отдельно для каждой фиксации).

С помощью этих команд:

git diff --patch   29665b0..0b76a27
git diff --stat    29665b0..0b76a27
git diff --numstat 29665b0..0b76a27

diff является кумулятивным, но, к сожалению, git diff не поддерживает параметры ограничения фиксации.

Так что мне бы хотелось, это кумулятивная функциональность разграничения git diff в сочетании с ограничивающей лимит-функциональностью git log.

Одна из моих идей заключалась в том, чтобы использовать git log для генерации списка хэшей фиксации, а затем каким-то образом передать этот список в git diff, чтобы создать кумулятивный разброс указанных коммитов. Что-то вроде этого (очевидно, этот метод хэшей трубопроводов git diff фактически не работает):

git log --pretty=format:%h --first-parent --no-merges 29665b0..0b76a27 | git diff

где --pretty=format:%h выводит хэши совпадающих коммитов.


Update

Благодаря @torek и @twalberg я теперь лучше понимаю операцию git diff. Синтаксис диапазона 29665b0..0b76a27 действительно вводит в заблуждение, и теперь я понимаю, что он фактически не выполняет кумулятивный разброс по диапазону коммитов. Просматривая docs, я нашел это:

"diff" - это сравнение двух конечных точек, а не диапазонов, а обозначения диапазонов (<commit>..<commit> и <commit>...<commit>) не означают диапазон, определенный в разделе "УКАЗАНИЕ ДИАПАЗОНОВ" в разделе gitrevisions (7).

Учитывая это, я перефразирую свой вопрос. С помощью этих команд:

git log --oneline --first-parent --no-merges --patch   29665b0..0b76a27
git log --oneline --first-parent --no-merges --stat    29665b0..0b76a27
git log --oneline --first-parent --no-merges --numstat 29665b0..0b76a27

изменения перечислены отдельно для каждого сопоставления. Как я могу объединить эти индивидуальные изменения, чтобы создать кумулятивный патч /stat/numstat?

Ответы на связанный возможный дублирующий вопрос полезны, предлагая решение: создать временную ветвь, вишни выбрать соответствующие коммиты, а затем сгенерировать diff.

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

Ответ 1

Здесь есть по крайней мере одно основное недоразумение. В частности, git diff на самом деле не является кумулятивным: вместо этого он просто попарно.

В частности, эти две команды выполняют одно и то же:

git diff rev1 rev2
git diff rev1..rev2

То есть, в git diff, в действительности не существует такой области, как диапазон.


С этой точки зрения, давайте заглянем за кулисы в git log. Что git log делает с диапазоном действительно 1 чтобы передать диапазон до git rev-list, который создает список каждого оборота в диапазоне, применяя модификаторы по пути:

git rev-list 29665b0..0b76a27

выплескивает каждый rev достижимый от 0b76a27, который также недоступен из 29665b0. Добавление --first-parent, --max-parents=1 (aka --no-merges) и т.д. Отфильтровывает некоторые из оборотов, которые будут перечислены здесь.

Конечный результат возвращается к git log, который затем смотрит на каждую ревизию в порядке git rev-list, выплевывает их - это также контролируется через --date-order и --topo-order и так далее; см. документацию для git rev-list - и показывает каждую запись в журнале, возможно, вместе с diff, созданной git diff-tree (который для одного родителя совершает, сравнивает фиксацию с ее родителем).

То, что вы можете сделать, вызывает непосредственно git rev-list непосредственно, а затем очищает верхнюю и нижнюю ревизии от своего вывода. (В этом конкретном случае вы, вероятно, тоже хотите --topo-order, чтобы последний rev был самым ранним, по графику, независимо от дат.) Например, в script:

#! /bin/sh
tempfile=$(mktemp -t mydiff)
trap "rm -f $tempfile" 1 2 3 15
git rev-list 29665b0..0b76a27 --first-parent --no-merges --topo-order > $tempfile
# remember that the first rev listed is the last rev in the range
last=$(head -1 $tempfile)
first=$(tail -1 $tempfile)
rm -f $tempfile # done with it, don't leave it around while showing diff
git diff $first $last

С помощью git rev-parse вы можете значительно улучшить параметры синтаксического анализа и разделить их на параметры опций vs rev-list options, но не так, как вам нужно. Главное, чтобы улучшить выше, - это избавиться от жестко закодированного диапазона версий.


1 Некоторые команды git действительно действительно передают аргументы в сторону git rev-list, так как это только сценарии оболочки, которые используют команды git rev-list и другие git для обработки этого. Другие построены вместе, так что git log и git rev-list на самом деле являются одним двоичным, а одна часть передает задание в другую часть, но без вызова новой программы.

В любом случае, обратите внимание, что git log master просто передает master на git rev-list, что приводит к тому, что список всех revs доступен из метки ветки master. Если вы добавите --no-walk, git rev-list выдает только один оборот, так что git log показывает только одну ревизию.

Ответ 2

# Create a temporary branch to mark the start of the cherry-picked commits
git branch tmpstart

# Create and checkout a temporary branch for the cherry-picked commits
git checkout -b tmpend

# Use git log to filter the range of commits with the desired
# commit-limiting options, and then cherry-pick each matching commit
git log \
    --first-parent \      # Commit-limiting
    --no-merges \         # Commit-limiting
    --reverse \           # Reverse the order (ascending chronological order)
    --pretty=format:%h \  # Output the abbreviated hash of each matching commit
    29665b0..0b76a27 \    # Range of commits
  | xargs -n 1 git cherry-pick

# Generate the patch/stat/numstat of the cherry-picked commits
git diff --patch   tmpstart tmpend
git diff --stat    tmpstart tmpend
git diff --numstat tmpstart tmpend