Отказ от ответственности: этот вопрос возник из-за незнания основных концепций 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 diff
вы обнаружите: это не считается; То, что он принимает диапазон ревизий, является исторической случайностью.
@jthill Я отредактировал вопрос, пытаясь быть более конкретным, но чем больше я обо всем этом думаю, тем больше запутываюсь.
@j6t, не могли бы вы рассказать подробнее? Я думал, что единственной целью git diff
является сравнение одного коммита и его неявного родителя (или особого случая для корневых коммитов) или принятие диапазонов ревизий для сравнения их «начал» и «концов».
git diff
нужно знать только две конечные точки, между которыми возникает разница. Его вообще не заботят коммиты между конечными точками. По этой причине следует сказать git diff master feature
. То, что вы можете написать это как git diff master..feature
, сбивает с толку и устарело, в частности, это совсем не то же самое, что означает диапазон ревизий master..feature
в случае, когда коммит в master
не является предком feature
.
@j6t да, это имеет смысл. Я так привык использовать синтаксис object1..object2
в git diff
, что подумал, что ..
имеет разные интерпретации для разных команд (например, «начинается» и «заканчивается» для diff
, но «все в диапазоне» для log
). Спасибо за разработку!
Не добавляйте к вопросу ответы («решения»). Вместо этого напишите ответ. Вы можете самостоятельно ответить на свой вопрос.
@ j6t да, но боюсь, ни один из них не стоит того, чтобы быть ответом. Меня беспокоит то, как я проверяю имена ссылок: в редактировании 3 он на самом деле не проверяет синтаксис ссылок, но также проверяет, существует ли ссылка, поскольку могут быть refs/heads/--symbolic-full-name
и refs/--symbolic-full-name
, поэтому требуется больше git
, что, вероятно, связано с недостатком знаний. сантехнических команд, особенностей фиксации и диапазонов оборотов, как вы упомянули для git-diff
. Кроме того, я не могу сосредоточиться на проверке диапазонов оборотов, которую я задал в вопросе, поскольку сейчас мне кажется, что это неразрешимо, поэтому мой ответ будет неполным.
@j6t Кажется, я наконец нашел то, что мне нужно. Все решения и «сломанные» команды git, которые я описал в вопросе, являются всего лишь результатом неправильных предположений, которые я разработал фактически без всякой причины.
Оказывается, мне не нужны никакие из этих проверок: ни проверка синтаксиса фиксации, ни проверка синтаксиса диапазона ревизий. Оба они просто бесполезны в написании сценариев, поскольку я могу передать все «странные» имена коммитов, такие как --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
Я потратил слишком много времени на ошибочные исследования и предположения, но я рад, что оказалось, что им так легко пользоваться, и я надеюсь, что он покроет все мои потребности и не преподнесет сюрпризов.
«что я считаю неверным»… почему? Пожалуйста, будьте конкретны и конкретны, покажите какую-нибудь команду, которая не работает, когда указан
$(git rev-parse master..feature)
, а не простоmaster..feature
.