This is the mail archive of the gcc@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: GCC Git hooks


On Thu, 9 Jan 2020, Joseph Myers wrote:

> On Mon, 16 Sep 2019, Joel Brobecker wrote:
> 
> > Looking at the configuration file, I believe the git-hooks
> > should have most, if not all, of the features that would be needed for
> > the GCC repository. In particular, there is already a way to relay
> > commits to third-party tools via calling of a script; GDB uses that
> > to interface with their Bugzilla database.
> 
> I am now looking at the hook setup for GCC.  As far as I can see, I'll 
> initially need a GCC-specific fork of the hooks for two reasons:

Concretely, these are the changes I'm currently using to configure the 
hooks in a way I think appropriate for GCC, and it would be useful if the 
hooks could support such configuration in a more generic way in future so 
that we can stop using a GCC-specific patched installation of the hooks.  
The following features are hardcoded that didn't seem to have a way to 
configure them:

* Additional branch namespaces refs/users/<user>/heads and 
refs/vendors/<vendor>/heads, and similar tag namespaces 
refs/users/<user>/tags and refs/vendors/<vendor>/tags.

* Only allowing branch deletion for certain ref patterns (refs/users and 
refs/vendors in this case).

* Controlling when non-fast-forward pushes are allowed for ref patterns 
outside refs/heads.

* Preventing pushes that add branches based on a given commit or introduce 
it to the history of branches not previously based on that commit (to 
prevent getting the history of the git-svn GCC mirror accidentally added 
to the refs fetched by default).  This precise form of check may be fairly 
GCC-specific, so providing a way to call out to a custom script to check 
ref updates would also be a possibility for how to support such checks.

diff --git a/hooks/fast_forward.py b/hooks/fast_forward.py
index 1e90cc5..f958dce 100755
--- a/hooks/fast_forward.py
+++ b/hooks/fast_forward.py
@@ -48,8 +48,11 @@ def check_fast_forward(ref_name, old_rev, new_rev):
     # such an update is allowed.
     ok_branches = git_config('hooks.allow-non-fast-forward')
 
-    for branch in ["refs/heads/" + branch.strip()
-                   for branch in ok_branches + FORCED_UPDATE_OK_BRANCHES]:
+    ok_refs = ["refs/heads/" + branch.strip()
+               for branch in ok_branches + FORCED_UPDATE_OK_BRANCHES]
+    ok_refs.append('refs/users/')
+    ok_refs.append('refs/vendors/')
+    for branch in ok_refs:
         if re.match(branch, ref_name) is not None:
             # This is one of the branches where a non-fast-forward update
             # is allowed.  Allow the update, but print a warning for
diff --git a/hooks/updates/branches/deletion.py b/hooks/updates/branches/deletion.py
index 118945f..edaab64 100644
--- a/hooks/updates/branches/deletion.py
+++ b/hooks/updates/branches/deletion.py
@@ -1,5 +1,6 @@
 """Handling of branch deletion."""
 
+from errors import InvalidUpdate
 from git import commit_oneline
 from updates import AbstractUpdate
 from updates.branches import branch_summary_of_changes_needed
@@ -15,12 +16,22 @@ class BranchDeletion(AbstractUpdate):
     """Update object for branch creation/update."""
     def self_sanity_check(self):
         """See AbstractUpdate.self_sanity_check."""
-        assert self.ref_name.startswith('refs/heads/')
+        assert (self.ref_name.startswith('refs/heads/')
+                or self.ref_name.startswith('refs/users/')
+                or self.ref_name.startswith('refs/vendors/'))
 
     def validate_ref_update(self):
         """See AbstractUpdate.validate_ref_update."""
-        # Deleting a branch is always allowed.
-        pass
+        # Deleting a user or vendor branch is always allowed.
+        if not (self.ref_name.startswith('refs/users')
+                or self.ref_name.startswith('refs/vendors')):
+            raise InvalidUpdate(
+                'Branch deletion is only allowed for user and vendor '
+                'branches. If another branch was created by mistake, '
+                'contact an administrator to delete it on the server '
+                'with git update-ref. If a development branch is dead, '
+                'also contact an administrator to move it under '
+                'refs/dead/heads/ rather than deleting it.')
 
     def get_update_email_contents(self):
         """See AbstractUpdate.get_update_email_contents.
diff --git a/hooks/updates/branches/update.py b/hooks/updates/branches/update.py
index eb51f9a..96cc777 100644
--- a/hooks/updates/branches/update.py
+++ b/hooks/updates/branches/update.py
@@ -2,7 +2,7 @@
 
 from errors import InvalidUpdate
 from fast_forward import check_fast_forward
-from git import is_null_rev, commit_oneline, commit_subject
+from git import git, is_null_rev, commit_oneline, commit_subject
 from updates import AbstractUpdate
 from updates.branches import branch_summary_of_changes_needed
 
@@ -63,6 +63,8 @@ class BranchUpdate(AbstractUpdate):
         # the update unless we have had a chance to verify that these hooks
         # work well with those branches.
         assert (self.ref_name.startswith('refs/heads/')
+                or self.ref_name.startswith('refs/users/')
+                or self.ref_name.startswith('refs/vendors/')
                 # Namespaces used by Gerrit.
                 or self.ref_name.startswith('refs/meta/')
                 or self.ref_name.startswith('refs/publish/')
@@ -80,6 +82,20 @@ class BranchUpdate(AbstractUpdate):
         # irrelevant.
         if not is_null_rev(self.old_rev):
             check_fast_forward(self.ref_name, self.old_rev, self.new_rev)
+            # GCC-specific: do not allow updates introducing ancestry
+            # based on the old git-svn repository, to ensure people
+            # rebase onto the new history rather than merging branches
+            # based on git-svn history into those based on the new history.
+            rev_list = git.rev_list(
+                self.new_rev, '^%s' % self.old_rev)
+        else:
+            rev_list = git.rev_list(
+                self.new_rev)
+        if '3cf0d8938a953ef13e57239613d42686f152b4fe' in rev_list:
+            raise InvalidUpdate(
+                'Refs not based on the git-svn history must not be '
+                'updated to be based on it, and new branches may not be '
+                'based on the old history.')
 
     def get_update_email_contents(self):
         """See AbstractUpdate.get_update_email_contents.
diff --git a/hooks/updates/factory.py b/hooks/updates/factory.py
index b24a8ff..49b349c 100644
--- a/hooks/updates/factory.py
+++ b/hooks/updates/factory.py
@@ -49,6 +49,10 @@ REF_CHANGE_MAP = {
     ('refs/tags/',         UPDATE, 'commit'): LightweightTagUpdate,
 }
 
+# GCC-specific namespaces.  refs/vendors/<vendor>/ and
+# refs/users/<user>/ contain heads/ and tags/.
+GCC_NAMESPACES = ('refs/users/', 'refs/vendors/')
+
 
 def new_update(ref_name, old_rev, new_rev, all_refs, submitter_email):
     """Return the correct object for the given parameters.
@@ -81,6 +85,22 @@ def new_update(ref_name, old_rev, new_rev, all_refs, submitter_email):
             new_cls = REF_CHANGE_MAP[key]
             break
     if new_cls is None:
+        for namespace in GCC_NAMESPACES:
+            if ref_name.startswith(namespace):
+                sub_name = ref_name[len(namespace):]
+                sub_split = sub_name.split('/')
+                if len(sub_split) >= 3:
+                    mod_name = 'refs/' + ('/'.join(sub_split[1:]))
+                    for key in REF_CHANGE_MAP:
+                        (map_ref_prefix, map_change_type, map_object_type) = key
+                        if ((change_type == map_change_type
+                             and object_type == map_object_type
+                             and mod_name.startswith(map_ref_prefix))):
+                            new_cls = REF_CHANGE_MAP[key]
+                            break
+                if new_cls is not None:
+                    break
+    if new_cls is None:
         return None
 
     return new_cls(ref_name, old_rev, new_rev, all_refs,
diff --git a/hooks/updates/tags/atag_update.py b/hooks/updates/tags/atag_update.py
index 4366f30..ed4a9d5 100644
--- a/hooks/updates/tags/atag_update.py
+++ b/hooks/updates/tags/atag_update.py
@@ -32,7 +32,9 @@ class AnnotatedTagUpdate(AbstractUpdate):
     """
     def self_sanity_check(self):
         """See AbstractUpdate.self_sanity_check."""
-        assert self.ref_name.startswith('refs/tags/')
+        assert (self.ref_name.startswith('refs/tags/')
+                or self.ref_name.startswith('refs/users/')
+                or self.ref_name.startswith('refs/vendors/'))
         assert self.new_rev_type == 'tag'
 
     def validate_ref_update(self):
diff --git a/hooks/updates/tags/ltag_deletion.py b/hooks/updates/tags/ltag_deletion.py
index 50ce1a3..90c04d8 100644
--- a/hooks/updates/tags/ltag_deletion.py
+++ b/hooks/updates/tags/ltag_deletion.py
@@ -32,7 +32,9 @@ class LightweightTagDeletion(AbstractUpdate):
         REMARKS
             This method handles both lightweight and annotated tags.
         """
-        assert self.ref_name.startswith('refs/tags/')
+        assert (self.ref_name.startswith('refs/tags/')
+                or self.ref_name.startswith('refs/users/')
+                or self.ref_name.startswith('refs/vendors/'))
         assert self.new_rev_type == 'delete'
 
     def validate_ref_update(self):
diff --git a/hooks/updates/tags/ltag_update.py b/hooks/updates/tags/ltag_update.py
index 5bfa2b2..dca6a9f 100644
--- a/hooks/updates/tags/ltag_update.py
+++ b/hooks/updates/tags/ltag_update.py
@@ -29,7 +29,9 @@ class LightweightTagUpdate(AbstractUpdate):
     """
     def self_sanity_check(self):
         """See AbstractUpdate.self_sanity_check."""
-        assert self.ref_name.startswith('refs/tags/')
+        assert (self.ref_name.startswith('refs/tags/')
+                or self.ref_name.startswith('refs/users/')
+                or self.ref_name.startswith('refs/vendors/'))
         assert self.new_rev_type == 'commit'
 
     def validate_ref_update(self):

-- 
Joseph S. Myers
joseph@codesourcery.com


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]