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

Отказ от ответственности: этот вопрос возник из-за незнания основных концепций 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 реализацию.

«что я считаю неверным»… почему? Пожалуйста, будьте конкретны и конкретны, покажите какую-нибудь команду, которая не работает, когда указан $(git rev-parse master..feature), а не просто master..feature.

jthill 11.08.2024 22:41

На всякий случай git diff вы обнаружите: это не считается; То, что он принимает диапазон ревизий, является исторической случайностью.

j6t 12.08.2024 07:33

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

terrorrussia-keeps-killing 12.08.2024 08:19

@j6t, не могли бы вы рассказать подробнее? Я думал, что единственной целью git diff является сравнение одного коммита и его неявного родителя (или особого случая для корневых коммитов) или принятие диапазонов ревизий для сравнения их «начал» и «концов».

terrorrussia-keeps-killing 12.08.2024 08:23
git diff нужно знать только две конечные точки, между которыми возникает разница. Его вообще не заботят коммиты между конечными точками. По этой причине следует сказать git diff master feature. То, что вы можете написать это как git diff master..feature, сбивает с толку и устарело, в частности, это совсем не то же самое, что означает диапазон ревизий master..feature в случае, когда коммит в master не является предком feature.
j6t 12.08.2024 08:47

@j6t да, это имеет смысл. Я так привык использовать синтаксис object1..object2 в git diff, что подумал, что .. имеет разные интерпретации для разных команд (например, «начинается» и «заканчивается» для diff, но «все в диапазоне» для log). Спасибо за разработку!

terrorrussia-keeps-killing 12.08.2024 09:02

Не добавляйте к вопросу ответы («решения»). Вместо этого напишите ответ. Вы можете самостоятельно ответить на свой вопрос.

j6t 12.08.2024 17:06

@ j6t да, но боюсь, ни один из них не стоит того, чтобы быть ответом. Меня беспокоит то, как я проверяю имена ссылок: в редактировании 3 он на самом деле не проверяет синтаксис ссылок, но также проверяет, существует ли ссылка, поскольку могут быть refs/heads/--symbolic-full-name и refs/--symbolic-full-name, поэтому требуется больше git, что, вероятно, связано с недостатком знаний. сантехнических команд, особенностей фиксации и диапазонов оборотов, как вы упомянули для git-diff. Кроме того, я не могу сосредоточиться на проверке диапазонов оборотов, которую я задал в вопросе, поскольку сейчас мне кажется, что это неразрешимо, поэтому мой ответ будет неполным.

terrorrussia-keeps-killing 12.08.2024 18:06

@j6t Кажется, я наконец нашел то, что мне нужно. Все решения и «сломанные» команды git, которые я описал в вопросе, являются всего лишь результатом неправильных предположений, которые я разработал фактически без всякой причины.

terrorrussia-keeps-killing 12.08.2024 20:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
9
104
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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

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

Другие вопросы по теме