Как проверить, является ли данный аргумент синтаксически допустимым коммитом или синтаксически допустимым диапазоном изменений?

Отказ от ответственности: этот вопрос возник из-за незнания основных концепций Git CLI, поэтому на самом деле это проблема XY.


У меня есть несколько сценариев Bash, которые являются обертками git. Большинство сценариев должны использовать диапазоны фиксации и редакции. Мне трудно понять, как я могу проверить, соответствуют ли данные аргументы этим потребностям синтаксически, иначе сценарии должны отклонять данные аргументы и завершаться с ненулевым кодом выхода, например, чтобы не конфликтовать с параметрами git-rev-list. и не иметь возможности вводить при переключении других команд.

Ближайшие команды, которые я нашел:

  • совершить:

    • git cat-file с опцией --batch-check, которая использует стандартный ввод и выдает выходные данные в формате %(objecttype) %(objectname), которые можно проверить, всегда ли тип объекта commit или tag (используя grep, Bash read что угодно), и выйти из сценария, если это не так, но я не чувствую, что это так оптимальное решение;
    • git rev-parse с установленной опцией --revs-only (и, возможно, --symbolic): это отфильтровывает аргументы с префиксом тире, и это совершенно нормально (за исключением того, что он молча потребляет аргументы, не относящиеся к версии, и я считаю, что такое поведение бесполезно во всех нужных мне случаях), его не волнует если данный аргумент представляет собой только фиксацию или тег, и это нормально, пока мне нужно, чтобы фиксация была передана командам, которые принимают объекты фиксации, и терпят неудачу, если это не так.
  • диапазоны ревизий:

    • пока нет; git rev-parse --revs-only --symbolic master..feature выдает feature, а затем ^master в двух строках, что я считаю неправильным.

Можно ли проверить, является ли данный набор аргументов допустимыми диапазонами фиксации или изменения, и быстро потерпеть неудачу, если какой-либо из них синтаксически недопустим? Если дизайн моих скриптов сломан, я также буду рад это исправить, переработав мои скрипты оболочки.


Редактировать 1.

Как было предложено в первом комментарии, git rev-list $(git rev-parse master..feature) должно работать. Действительно, это тот момент, который я упустил и не знал. На самом деле он расширяется до git rev-list <feature_OBJECTNAME> '^<master_OBJECTNAME>'. Я не знал об этом синтаксисе и всегда считал, что он должен быть разделен .., например <master_OBJECTNAME>..<feature_OBJECTNAME>. Это действительно хорошо, даже если он не дает сбоя (я все еще не уверен, нужно ли мне, чтобы он давал сбой быстро или сбой после того, как аргументы, связанные с оборотом, передаются прямо командам git).

Теперь предположим, что мой скрипт анализирует свои аргументы, переходит к остальным и передает все переменные аргументы в коммит git, используя "$@":

#!/bin/bash
...
# parse the script-related args here
...
set -x # debug it
# note if the subshell $(...) is quoted, this may not work with `unknown revision or path not in the working tree`
git rev-list $(rev-parse "$@")

Пример:

$ ./script master..feature
7b98875c67a78f976b6bad24a7366c23db0f0725
35c2d1f84d8fa3b72396962b181cf797672c689d
3472c8b350cc967470450f0fb6f00c3af26c8378

Действительно здорово, что это работает с rev-parse! Теперь предположим, что я передаю скрипту больше аргументов.

# `rev-list` can process `--skip` but I don't want this option to be injected to `rev-list`
# BUT I'm totally fine with `master..feature~4`
$ ./script --skip 4 master..feature -- path1 path

не удается, потому что git rev-parse пытается разрешить 4 как имя объекта

fatal: ambiguous argument '4': unknown revision or path not in the working tree.

Следующая команда

# I don't seem to be able to detect the `--` separator
# as I would like this to be detected by the underlying git command
$ ./script master..feature -- path1 path

терпит неудачу с

^f0deddee7cd577969f8b5771137b8be6973e4117
path1
fatal: ambiguous argument 'path1': unknown revision or path not in the working tree.

Если я добавлю --revs-only к команде git rev-parse, это будет зависеть от аргументов:

  • ./script --skip master..feature выдает имена объектов, например <feature_OBJECTNAME>\n^<master_OBJECTNAME>, но сохраняет молчание --skip
  • ./script --skip 4 master..feature вообще терпит неудачу
  • ./script --skip 4 master..feature path1 path2 не получается, как указано выше
  • ./script --skip master..feature path1 path2 выдает имена объектов, как будто нет path1 и path2, не выдавая предупреждений для path1 и path2
  • ./script --skip master..feature -- path1 path2 создает имена объектов так, как будто нет path1 и path2, не выдавая предупреждений для path1 и path2 (обратите внимание на разделитель --)

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


Редактировать 2.

Я нашел интересный случай. Если я создаю новую ссылку типа git update-ref refs/heads/--foo master, refs/heads/--foo становится несовместимой с git rev-list из-за неоднозначных параметров команды и имен ссылок:

$ git rev-list --foo
usage: git rev-list [<options>] <commit>... [--] [<path>...]

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

$ git rev-list refs/heads/--foo
<OBJECTNAME_n>
<OBJECTNAME_n-1>
<OBJECTNAME_n-2>
...
<OBJECTNAME_1>

Это требует, чтобы пользователь моих сценариев использовал только ссылочные полные имена для таких случаев и, похоже, нарушает git rev-parse --revs-ohly, если ссылочное короткое имя выглядит так (добавление -- к git rev-parse вообще не приводит к выводу).

Редактировать 2.2.

Имея ссылку refs/heads/--symbolic-full-name, следующая команда:

$ git rev-parse --symbolic-full-name master --symbolic-full-name

создает только refs/heads/master, но игнорирует ветвь --symbolic-full-name, которая, как можно ожидать, будет преобразована в refs/heads/--symbolic-full-name.

Следующее работает так, как ожидалось:

$ git rev-parse --abbrev-ref refs/heads/--symbolic-full-name
--symbolic-full-name

В настоящее время я понятия не имею, как различать допустимые коммиты, допустимые диапазоны версий и параметры команд git.


Редактировать 3.

Кажется, я нашел решение проблемы коммита.

# git-show-ref (it accepts `--` and does not require ref full names) conjucted with git-cat-file seems to work,
# but in a suboptimal way since it requires multiple running for git-show-ref and git-cat-file
#
# this can work for object names, commits, tags, and it can fail as expected for any other non-commit-ish string like a command line option
normalize_commit_ish() {
    declare OBJECT_NAME=
    declare FULL_NAME=
    declare __FULL_NAME=
    declare OBJECT_TYPE=
    declare __=
    for NAME; do
        {
            read -r OBJECT_NAME FULL_NAME || true
            if [[ -n "$FULL_NAME" ]]; then
                read -r __ __FULL_NAME || true
# TODO this check totally ignores the rules described at:
# https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltrefnamegtemegemmasterememheadsmasterememrefsheadsmasterem
                if [[ -n "$__FULL_NAME" ]]; then
                    echo "$0: error: ambiguous refs detected: $FULL_NAME and $__FULL_NAME" >&2
                    return 1
                fi
                printf '%s\n' "$FULL_NAME"
                continue
            fi
        } < <(git show-ref -- "$NAME")
        read -r OBJECT_NAME < <(git rev-parse "$NAME" 2> || true) || true
        if [[ -n "$OBJECT_NAME" ]]; then
            read -r TYPE < <(git cat-file -t -- "$OBJECT_NAME" || true) || true
            case "$TYPE" in
            'commit'|'tag')
                printf '%s\n' "$OBJECT_NAME"
                continue
                ;;
            'tree'|'blob')
                echo "$0: error: $NAME must be commit-ish but was $TYPE" >&2
                return 1
                ;;
            '')
                # nothing resolved, go to the next step (currently the error dead-end)
                ;;
            *)
                echo "$0: error: $NAME is of unknown type $TYPE" >&2
                return 1
                ;;
            esac
        fi
        echo "$0: error: cannot resolve commit-ish for $NAME" >&2
        return 1
    done
}

Пример запуска:

COMMIT_ISH=($(normalize_commit_ish 'tag' '@' 'master~1' 'refs/heads/--symbolic-full-name' 'heads/--symbolic-full-name' '--foo' 'master' '--symbolic-full-name')) && printf '%s\n' ${#COMMIT_ISH[@]} "${COMMIT_ISH[@]}"

выходы:

refs/tags/tag
14cd17e4539f6881abeb7629f249fac12fb197ac
a74945dee0f61aaedf2b43c9a43f55600f118706
refs/heads/--symbolic-full-name
refs/heads/--symbolic-full-name
refs/heads/--foo
refs/heads/master
refs/heads/--symbolic-full-name

Также эта функция может обнаруживать недопустимые имена ссылок и объекты:

normalize_commit_ish '--this-is-other-git-command-option-it-may-clash-with' || true
normalize_commit_ish 'this-ref-does-not-exist' || true

Две приведенные выше команды производят:

fatal: Not a valid object name --this-is-other-git-command-option-it-may-clash-with
./script: error: cannot resolve commit-ish for --this-is-other-git-command-option-it-may-clash-with
fatal: Not a valid object name this-ref-does-not-exist
./script: error: cannot resolve commit-ish for this-ref-does-not-exist

как и ожидалось.

Все еще ищу normalize_revision_ranges реализацию.


4
104
1

Ответ:

Решено

Оказывается, мне не нужны никакие из этих проверок: ни проверка синтаксиса фиксации, ни проверка синтаксиса диапазона ревизий. Оба они просто бесполезны в написании сценариев, поскольку я могу передать все «странные» имена коммитов, такие как --symbolic-full-name (сокращенное имя для refs/heads/--symbolic-full-name в экспериментах выше), прямо в команды git и позволить git выполнить всю работу самостоятельно.

Ключ, с которым я случайно столкнулся, описанный на странице gitcli, — это переключатель командной строки --end-of-options, который заставляет git принимать «странные» имена коммитов, поскольку этот переключатель по сути является разделителем между параметрами команды и коммитом или диапазон ревизий:

$ git update-ref refs/heads/--symbolic-full-name master

# an example command that cannot distinguish the branch name and an invalid option
$ git rev-list --symbolic-full-name
# ... git-rev-list help goes here because of the error ...

# an example command that accepts a single commit-ish I don't need "syntax check" for anymore
$ git rev-list --end-of-options --symbolic-full-name
# ... git-rev-list object names go here ...

# an example command that accepts a revision range I don't need the "syntax check" for anymore too
$ git rev-list --end-of-options --symbolic-full-name~2..--symbolic-full-name
# object name 2
# object name 1

$ git update-ref -d refs/heads/--symbolic-full-name

Я потратил слишком много времени на ошибочные исследования и предположения, но я рад, что оказалось, что им так легко пользоваться, и я надеюсь, что он покроет все мои потребности и не преподнесет сюрпризов.