Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .git-hook-commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""Git commit-msg hook.

Reads the activity (MarkAPI-NNNN) from the branch name -- anywhere in the
name -- and:

1. validates the message format: <type>(<optional scope>): <message>
allowed types: feature, fix, chore, unittest
2. injects the activity prefix into the first line: [MarkAPI-NNNN] ...

Automatic Git commits (merge/revert/squash) are skipped.
Uses the standard library only for portability across Linux and macOS.
"""

import re
import subprocess
import sys

ALLOWED_TYPES = ("feature", "fix", "chore", "unittest")

BRANCH_ACTIVITY_RE = re.compile(r"MarkupAPI-(\d+)", re.IGNORECASE)
EXISTING_PREFIX_RE = re.compile(r"^\s*\[MarkupAPI-\d+\]\s*", re.IGNORECASE)
MESSAGE_FORMAT_RE = re.compile(
r"^(?:%s)(?:\([a-z0-9-]+\))?: .+" % "|".join(ALLOWED_TYPES)
)

# Automatic commits generated by Git that should pass without validation.
MarkAPI_COMMIT_PREFIXES = (
"Merge branch ",
"Merge pull request ",
"Merge remote-tracking ",
'Revert "',
)
SQUASH_MERGE_RE = re.compile(r"^Merge .+ into ")


def fail(message):
print(message, file=sys.stderr)
sys.exit(1)


def is_auto_commit(first_line):
if first_line.startswith(MarkAPI_COMMIT_PREFIXES):
return True
return bool(SQUASH_MERGE_RE.match(first_line))


def current_branch():
# symbolic-ref works even on the first commit (branch with no commits),
# where rev-parse --abbrev-ref HEAD fails. On a detached HEAD,
# symbolic-ref returns a non-zero code and we fall back to rev-parse
# (which returns "HEAD").
for cmd in (
["git", "symbolic-ref", "--short", "HEAD"],
["git", "rev-parse", "--abbrev-ref", "HEAD"],
):
try:
result = subprocess.run(cmd, capture_output=True, text=True)
except OSError:
return None
if result.returncode == 0:
branch = result.stdout.strip()
if branch:
return branch
return None


def main():
if len(sys.argv) < 2:
fail("❌ commit-msg hook: message file path not provided.")

msg_path = sys.argv[1]
with open(msg_path, "r", encoding="utf-8") as handle:
lines = handle.read().splitlines()

if not lines:
fail("❌ Empty commit message.")

first_line = lines[0].rstrip("\r")

# Let automatic Git commits pass through.
if is_auto_commit(first_line):
sys.exit(0)

branch = current_branch()
if not branch or branch == "HEAD":
fail(
"❌ Could not determine the current branch (detached HEAD?).\n"
"The branch must contain the activity MarkAPI-NNNN."
)

match = BRANCH_ACTIVITY_RE.search(branch)
if not match:
fail(
"❌ Branch without an activity.\n"
"The branch name must contain MarkAPI-NNNN "
"(e.g. feature/MarkAPI-1234/my-feature).\n"
"Current branch: %s" % branch
)

activity = "MarkAPI-%s" % match.group(1)

# Strip an existing prefix to avoid duplication (amend/rebase/reword).
body = EXISTING_PREFIX_RE.sub("", first_line).strip()

if not MESSAGE_FORMAT_RE.match(body):
fail(
"❌ Invalid commit message.\n"
"Expected format: <type>(<optional scope>): <message>\n"
"Allowed types: %s\n"
"Examples:\n"
" chore: format readme\n"
" fix(auth): fix authentication when token is invalid"
% ", ".join(ALLOWED_TYPES)
)

lines[0] = "[%s] %s" % (activity, body)

with open(msg_path, "w", encoding="utf-8") as handle:
handle.write("\n".join(lines) + "\n")

sys.exit(0)


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ latest_commit: ## Show last commit ref
build_date: ## Show build date
@echo "Build date: " $(SCMS_BUILD_DATE)

configure_git_hooks: ## Configure git hooks
cp -fa .git-hook-commit-msg .git/hooks/commit-msg
ln -sf ../../.git-hook-commit-msg .git/hooks/commit-msg

############################################
## atalhos docker compose desenvolvimento ##
############################################
Expand Down
2 changes: 1 addition & 1 deletion config/menu.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
WAGTAIL_MENU_APPS_ORDER = [
"sps_package_validation",
"markup_doc",
"reference",
"scielo",
"tracker",
"model_ai",
Expand Down
2 changes: 1 addition & 1 deletion core/wagtail_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def group_wagtail_cms_menu_items(request, menu_items):
)
menu_items.append(
SubmenuMenuItem(
_("Wagtail CMS"),
_("Content Manager"),
cms_menu,
icon_name="folder-open-inverse",
name="wagtail_cms",
Expand Down
18 changes: 13 additions & 5 deletions markup_doc/wagtail_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
)
from markup_doc.sync_api import sync_collection_from_api
from markup_doc.tasks import get_labels, task_sync_journals_from_api, update_xml
<<<<<<< HEAD
from xml_manager.wagtail_hooks import (
SPSPackageValidationSnippetViewSet,
XMLDocumentHTMLSnippetViewSet,
XMLDocumentPDFSnippetViewSet,
)
=======
>>>>>>> 92e3c5e1efc5b93532d4cc264f5c408e416a3ff4


@hooks.register("register_admin_urls")
Expand Down Expand Up @@ -101,7 +109,7 @@ def form_valid(self, form):
class UploadDocxViewSet(SnippetViewSet):
model = UploadDocx
add_view_class = ArticleDocxCreateView
menu_label = _("Carregar DOCX")
menu_label = _("DOCX")
menu_icon = "upload"
add_to_admin_menu = False
exclude_from_explorer = False
Expand All @@ -115,7 +123,7 @@ class MarkupXMLViewSet(SnippetViewSet):
model = MarkupXML
add_view_class = ArticleDocxMarkupCreateView
edit_view_class = ArticleDocxEditView
menu_label = _("XML SPS marcado")
menu_label = _("XML SPS")
menu_icon = "code"
add_to_admin_menu = False
exclude_from_explorer = False
Expand All @@ -139,7 +147,7 @@ def form_valid(self, form):
class CollectionModelViewSet(SnippetViewSet):
model = CollectionModel
add_view_class = CollectionModelCreateView
menu_label = _("Coleções SciELO")
menu_label = _("Coleção")
menu_icon = "folder-inverse"
add_to_admin_menu = False
exclude_from_explorer = False
Expand Down Expand Up @@ -203,15 +211,15 @@ class XMLSPSSnippetViewSetGroup(SnippetViewSetGroup):
menu_icon = "code"
items = (
MarkupXMLViewSet,
SPSPackageValidationSnippetViewSet,
XMLDocumentPDFSnippetViewSet,
XMLDocumentHTMLSnippetViewSet,
ReferenceModelViewSet,
)


class ScieloSnippetViewSetGroup(SnippetViewSetGroup):
menu_name = "scielo"
menu_label = _("SciELO")
menu_label = _("Journal Manager")
menu_icon = "folder-open-inverse"
menu_order = get_menu_order("scielo")
items = (
Expand Down
8 changes: 6 additions & 2 deletions reference/wagtail_hooks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Third-party imports
from django.http import HttpResponseRedirect
from django.utils.translation import gettext_lazy as _
from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import CreateView, SnippetViewSet

# Local application imports
Expand Down Expand Up @@ -35,9 +36,12 @@ class ReferenceModelViewSet(SnippetViewSet):
model = Reference
add_view_class = ReferenceCreateView
menu_name = "reference"
menu_label = _("Referências bibliográficas")
menu_label = _("Referências")
menu_icon = "openquote"
menu_order = get_menu_order("reference")
exclude_from_explorer = False
list_per_page = 20
add_to_admin_menu = False
add_to_admin_menu = True


register_snippet(ReferenceModelViewSet)
12 changes: 3 additions & 9 deletions xml_manager/wagtail_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import CreateView, EditView, SnippetViewSet

from config.menu import get_menu_order

from . import urls
from .forms import SPSPackageValidationForm
from .models import (
Expand Down Expand Up @@ -156,7 +154,7 @@ class XMLDocumentPDFSnippetViewSet(SnippetViewSet):
verbose_name_plural = _("XML Document PDFs")
icon = "doc-full"
menu_name = "xml_manager"
menu_label = _("PDFs derivados")
menu_label = _("PDFs")
menu_icon = "doc-full"
add_to_admin_menu = False

Expand All @@ -177,7 +175,7 @@ class XMLDocumentHTMLSnippetViewSet(SnippetViewSet):
verbose_name_plural = _("XML Document HTMLs")
icon = "doc-full"
menu_name = "xml_manager"
menu_label = _("HTMLs derivados")
menu_label = _("HTMLs")
menu_icon = "doc-full-inverse"
add_to_admin_menu = False

Expand All @@ -202,8 +200,7 @@ class SPSPackageValidationSnippetViewSet(SnippetViewSet):
menu_name = "sps_package_validation"
menu_label = _("Validar SPS")
menu_icon = "sps-package-validation"
add_to_admin_menu = True
menu_order = get_menu_order("sps_package_validation")
add_to_admin_menu = False

list_display = (
"__str__",
Expand All @@ -220,9 +217,6 @@ class SPSPackageValidationSnippetViewSet(SnippetViewSet):
search_fields = ("package_document__title",)


register_snippet(SPSPackageValidationSnippetViewSet)


@hooks.register("register_icons")
def register_xml_manager_icons(icons):
return icons + ["wagtailadmin/icons/sps-package-validation.svg"]
Expand Down
Loading