Как проверить допустимые имена ветвей Git?

Я разрабатываю крючок git post-receive в Python. Данные предоставляются на stdin линиями, аналогичными

ef4d4037f8568e386629457d4d960915a85da2ae 61a4033ccf9159ae69f951f709d9c987d3c9f580 refs/heads/master

Первый хэш - это старый-ref, второй - ref-ref, а третий - обновляемая ссылка.

Я хочу разбить это на 3 переменные, а также проверить ввод. Как проверить имя ветки?

В настоящее время я использую следующее регулярное выражение

^([0-9a-f]{40}) ([0-9a-f]{40}) refs/heads/([0-9a-zA-Z]+)$

Это не принимает все возможные имена ветвей, как указано man git -check-ref-format. Например, он исключает ветвь по имени build-master, которая действительна.

Бонусные знаки

Я действительно хочу исключить любую ветку, которая начинается с "build-". Можно ли это сделать в том же регулярном выражении?

Испытания

Учитывая большие ответы ниже, я написал несколько тестов, которые можно найти в https://github.com/alexchamberlain/githooks/blob/master/miscellaneous/git-branch-re-test.py.

Статус: все приведенные ниже выражения не скомпилируются. Это может указывать на наличие проблемы с моими script или несовместимыми синтаксисами.

Ответы

Ответ 1

Позволяет анализировать различные правила и создавать из них регулярные выражения:

  • Они могут включать слэш / для иерархической (каталожной) группировки, но никакой компонент, разделенный слэш, не может начинаться с точки . или заканчиваться последовательностью .lock.

    # must not contain /.
    (?!.*/\.)
    # must not end with .lock
    (?<!\.lock)$
    
  • Они должны содержать хотя бы один /. Это обеспечивает наличие категории, например head/, tags/и т.д., Но фактические имена не ограничены. Если используется опция --allow-onelevel, это правило отменяется.

    .+/.+  # may get more precise later
    
  • Они не могут иметь две последовательные точки .. где угодно.

    (?!.*\.\.)
    
  • Они не могут иметь управляющие символы ASCII (т.е. байты, значения которых ниже \040 или \177 DEL), пробел, тильда ~, карет ^ или двоеточие : где угодно.

    [^\000-\037\177 ~^:]+   # pattern for allowed characters
    
  • У них не может быть вопросительный знак ?, звездочка * или открыть скобку [ где угодно. См. Параметр --refspec-pattern ниже для исключения из этого правила.

    [^\000-\037\177 ~^:?*[]+   # new pattern for allowed characters
    
  • Они не могут начинаться или заканчиваться косой чертой / или содержать несколько последовательных косых черт (см. параметр --normalize ниже для исключения из этого правила)

    ^(?!/)
    (?<!/)$
    (?!.*//)
    
  • Они не могут заканчиваться точкой ..

    (?<!\.)$
    
  • Они не могут содержать последовательность @{.

    (?!.*@\{)
    
  • Они не могут быть единственным символом @.

    ([email protected]$)
    
  • Они не могут содержать \.

    (?!.*\\)
    

Соединяя все это вместе, мы приходим к следующему чудовищу:

^(?!.*/\.)(?!.*\.\.)(?!/)(?!.*//)(?!.*@\{)([email protected]$)(?!.*\\)[^\000-\037\177 ~^:?*[]+/[^\000-\037\177 ~^:?*[]+(?<!\.lock)(?<!/)(?<!\.)$

И если вы хотите исключить те, которые начинаются с build-, просто добавьте еще один lookahead:

^(?!build-)(?!.*/\.)(?!.*\.\.)(?!/)(?!.*//)(?!.*@\{)([email protected]$)(?!.*\\)[^\000-\037\177 ~^:?*[]+/[^\000-\037\177 ~^:?*[]+(?<!\.lock)(?<!/)(?<!\.)$

Это можно также оптимизировать, объединив несколько вещей, которые ищут общие шаблоны:

^([email protected]$|build-|/|.*([/.]\.|//|@\{|\\))[^\000-\037\177 ~^:?*[]+/[^\000-\037\177 ~^:?*[]+(?<!\.lock|[/.])$

Ответ 2

git check-ref-format <ref> с subprocess.Popen является возможностью:

import subprocess
process = subprocess.Popen(["git", "check-ref-format", ref])
exit_status = process.wait()

Преимущества:

  • Если алгоритм когда-либо изменится, проверка будет автоматически обновляться.
  • вы уверены, что поняли это правильно, что сложнее с монстром Regex

Недостатки:

  • медленнее, потому что подпроцесс. Но преждевременная оптимизация - корень всего зла.
  • требует Git как двоичную зависимость. Но в случае с крюком он всегда будет там.

pygit2, который использует привязки C к libgit2, было бы еще лучшей возможностью, если там отображается check-ref-format, так как она была бы быстрее, чем Popen, но я ее не нашел.

Ответ 3

Нет необходимости писать чудовища в Perl. Просто используйте /x:

# RegExp rules based on git-check-ref-format
my $valid_ref_name = qr%
   ^
   (?!
      # begins with
      /|                # (from #6)   cannot begin with /
      # contains
      .*(?:
         [/.]\.|        # (from #1,3) cannot contain /. or ..
         //|            # (from #6)   cannot contain multiple consecutive slashes
         @\{|           # (from #8)   cannot contain a sequence @{
         \\             # (from #9)   cannot contain a \
      )
   )
                        # (from #2)   (waiving this rule; too strict)
   [^\040\177 ~^:?*[]+  # (from #4-5) valid character rules

   # ends with
   (?<!\.lock)          # (from #1)   cannot end with .lock
   (?<![/.])            # (from #6-7) cannot end with / or .
   $
%x;

foreach my $branch (qw(
   master
   .master
   build/master
   ref/HEAD/blah
   /HEAD/blah
   HEAD/blah/
   master.lock
   head/@{block}
   master.
   build//master
   build\master
   build\\master
),
   'master blaster',
) {
   print "$branch --> ".($branch =~ $valid_ref_name)."\n";
}

Joey ++ для некоторого кода, хотя я внес некоторые исправления.

Ответ 4

Принимая правила непосредственно со связанной страницы, следующее регулярное выражение должно соответствовать только допустимым именам ветвей в refs/heads, не начиная с "build -":

refs/heads/(?!.)(?!build-)((?!\.\.)([email protected]{)[^\cA-\cZ ~^:?*[\\])+))(?<!\.)(?<!\.lock)

Это начинается с refs/heads, как это делает ваш.

Затем (?!build-) проверяет, что следующие 6 символов не являются build- и (?!.) проверяет, что ветка не начинается с ..

Вся группа (((?!\.\.)([email protected]{)[^\cA-\cZ ~^:?*[\\])+) соответствует имени ветки.

(?!\.\.) проверяет, нет ли экземпляров двух периодов в строке, а ([email protected]{) проверяет, что ветвь не содержит @{.

Затем [^\cA-\cZ ~^:?*[\\] соответствует любому из допустимых символов, исключая управляющие символы \cA-\cZ и все остальные символы, которые специально запрещены.

Наконец, (?<!\.) гарантирует, что имя ветки не заканчивается на период, а (?<!.lock) проверяет, что оно не заканчивается на .\lock.

Это может быть расширено, чтобы аналогично соответствовать действительным именам ветвей в произвольных папках, вы можете использовать

(?!.)((?!\.\.)([email protected]{)[^\cA-\cZ ~^:?*[\\])+))(/(?!.)((?!\.\.)([email protected]{)[^\cA-\cZ ~^:?*[\\])+)))*?/(?!.)(?!build-)((?!\.\.)([email protected]{)[^\cA-\cZ ~^:?*[\\])+))(?<!\.)(?<!\.lock)

Это относится в основном к тем же правилам к каждой части имени ветки, но только проверяет, что последний не начинается с build-

Ответ 5

Для тех, кто подходит к этому вопросу, ищет регулярное выражение PCRE для соответствия допустимому имени ветки Git, это следующее:

^(?!/|.*([/.]\.|//|@\{|\\\\))[^\040\177 ~^:?*\[]+(?<!\.lock|[/.])$

Это измененная версия регулярного выражения, написанная Joey. Однако в этой версии наклонный не требуется (это соответствует branchName, а не refs/heads/branchName).

Обратитесь к его правильному ответу на этот вопрос. Он обеспечивает полное разбиение каждой части регулярного выражения и то, как он относится к каждому требованию, указанному на страницах git-check-ref-format(1).