Skip to content

A library to use and verify the CODEOWNERS (both the Github and Gitlab variants) and .gitignore files

License

Notifications You must be signed in to change notification settings

cvuik/codeowners

 
 

Repository files navigation

Github actions Build status Coverage Status License Maven Central: codeowners-reader Maven Central: gitignore-reader Maven Central: codeowners-enforcer-rules GitHub stars If this project has business value for you then don't hesitate to support me with a small donation. If this project has business value for you then don't hesitate to support me with a small donation.

CodeOwners

In several systems (like GitHub and Gitlab) you can have a CODEOWNERS file which is used to ensure all changes are approved by the right people.

Reality: The syntax of these files can be tricky, and it is quite easy to write a config that has the effect that not all files are covered.

What is this

  1. A Java library to read a CODEOWNERS file.
  2. A Java library to read a/all .gitignore file(s) in directory.
  3. An extra rule for the Maven Enforcer plugin to check the CODEOWNERS against
    1. the actual project files. (See Usage below)
    2. the actual users, groups and roles in Gitlab. (BETA) (See Usage below)

The intended goal is to make the build fail if the codeowners file does not cover all files and directories in the project.

  • The libraries for the CODEOWNERS and gitignore files are usable in Java 8 and newer.
  • The Maven Enforcer rule needs Java 11 or newer.

CodeOwners Enforcer rule

Configuration parameters

  • baseDir
    • In case you run the plugin from a child module.
  • codeOwnersFile
    • The name of the codeowners file when you are not using a common standard.
  • allExisingFilesMustHaveCodeOwner
    • Check that all existing files have at least one mandatory codeowner.
    • Gitlab also supports "Optional" code owners but that is useless to check for.
  • allNewlyCreatedFilesMustHaveCodeOwner
    • Check that if a new file is created in any of the directories in the project that it would automatically have a mandatory code owner.
    • Note when a specific filename exception is used in the gitignore rules then this check is not perfect.
  • allFilesMustHaveCodeOwner
    • Do both allExisingFilesMustHaveCodeOwner and allNewlyCreatedFilesMustHaveCodeOwner
  • verbose
    • Make the rule output much more details than you would normally like to see.
  • showApprovers
    • Make the rule output show the approvers for all non-ignored files in the entire project. The intended usage is that with this you can debug the CODEOWNERS result and manually see for every file in your project if you are happy with the resulting approvers.

Checking against the users present on a Gitlab Instance (BETA)

If you have a Gitlab instance with your project then you can get additional checks done. This plugin can also verify if all mentioned users, groups and roles are actually allowed to approve a change. This is important because when Gitlab determines that all the mentioned approvers either do not exist or do not have the appropriate approval level then everyone can merge any change on a file.

This is currently a BETA feature.

A known limitation is that checking against the email address in a CODEOWNERS file is only possible IF the user has made that specific email address their public address.

Usage

There are 3 parameters needed to activate this feature:

  • The Gitlab Project/Personal Access Token (only read_api and GUEST role are needed)
  • The Gitlab server url
  • The Gitlab project id of this project

This check will fail on existing users/groups that are not part of the project, on illegal roles (i.e. @@something) and if it is certain a rule has 0 approvers. If a user is configured by email address and this cannot be found it is NOT certain this user does or does not have access. This is because of security limitations in the Gitlab API which only allow a regular user to find other users by their public email address. See https://docs.gitlab.com/api/users/#as-a-regular-user

Output

By default a table is output with the warnings and errors. You can set this to get output on all approvers.

<gitlab>
    <showAllApprovers>true</showAllApprovers>
</gitlab>

Gitlab Project/Personal Access Token

To allow this plugin to use the Gitlab API an access token is needed that is created with only read_api and the GUEST role.

You should never commit ANY token to a repository so this plugin simply cannot directly read any token. It is ONLY possible to read it from an environment variable.

So the token can only be retrieved from an environment variable. In this example it is expected that the environment variable CHECK_USERS_TOKEN contains the value of you token.

<gitlab>
    <accessToken>
        <environmentVariableName>CHECK_USERS_TOKEN</environmentVariableName>
    </accessToken>
</gitlab>

TIP: When running in Gitlab CI only the access token is needed via an environment variable. If you configure it this way (which is recommended) then starting with version 1.11.1 it will automatically skip this check if you are NOT running within a GitlabCI environment.

When to fail

For some projects it is not wanted to fail if there is a problem with the CODEOWNERS in relation to the Gitlab accounts and permissions. Normally this rule will fail if an Error level situation is found.

You can change when to fail in relation to the Gitlab users and groups:

  • NEVER: Never fail.
  • ERROR: Only fail if there is an Error. (this is the default setting)
  • WARNING: Fail if there is at least a single Warning or Error.
<gitlab>
    <failLevel>NEVER</failLevel>
</gitlab>

Gitlab server url

The Gitlab server url can be configured directly or retrieved from an environment variable.

  • If not configured it is assumed it can be found in the environment variable CI_SERVER_URL which is the default place where Gitlab CI puts it.
  • Directly configured:
    <gitlab>
        <serverUrl>
            <url>https://gitlab.example.nl</url>
        </serverUrl>
    </gitlab>
  • Retrieved from a configured environment variable. In this example it is expected that the environment variable MY_ENVIRONMENT_VARIABLE contains something like https://gitlab.example.nl.
    <gitlab>
        <serverUrl>
            <environmentVariableName>MY_ENVIRONMENT_VARIABLE</environmentVariableName>
        </serverUrl>
    </gitlab>

Gitlab project id

The Gitlab project id can be configured directly or retrieved from an environment variable.

  • If not configured it is assumed it can be found in the environment variable CI_PROJECT_ID which is the default place where Gitlab CI puts it.
  • Directly configured:
    <gitlab>
        <projectId>
            <id>group/project</id>
        </projectId>
    </gitlab>
  • Retrieved from a configured environment variable. In this example it is expected that the environment variable MY_ENVIRONMENT_VARIABLE contains something like group/project.
    <gitlab>
        <projectId>
            <environmentVariableName>MY_ENVIRONMENT_VARIABLE</environmentVariableName>
        </projectId>
    </gitlab>

Example

In one of my projects it looks like this:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <version>3.4.1</version>
  <dependencies>
    <dependency>
      <groupId>nl.basjes.maven.enforcer.codeowners</groupId>
      <artifactId>codeowners-enforcer-rules</artifactId>
      <version>1.11.1</version>
    </dependency>
  </dependencies>
  <executions>
    <execution>
      <id>Ensure the CODEOWNERS is correct</id>
      <phase>verify</phase>
      <goals>
        <goal>enforce</goal>
      </goals>
      <inherited>false</inherited>
      <configuration>
        <rules>
          <codeOwners>
            <baseDir>${maven.multiModuleProjectDirectory}</baseDir>
            <codeOwnersFile>${maven.multiModuleProjectDirectory}/CODEOWNERS</codeOwnersFile>
            <allFilesMustHaveCodeOwner>true</allFilesMustHaveCodeOwner>
            <!-- <verbose>true</verbose> -->
            <!-- <showApprovers>true</showApprovers> -->
            <gitlab>
                <accessToken>
                    <environmentVariableName>CHECK_USERS_TOKEN</environmentVariableName>
                </accessToken>
            </gitlab>
          </codeOwners>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

GitIgnore library

Basic use

Simply create an instance of the GitIgnoreFileSet and pass the root directory of the project as a parameter

GitIgnoreFileSet ignoreFileSet = new GitIgnoreFileSet(new File("/home/niels/workspace/project"));

You can then check for each file in the project if it is ignored or not

if (ignoreFileSet.ignoreFile("/home/niels/workspace/project/something/something/README.md")) {
    ...
}

Full path or just or a project relative filename

The directory name with which you initialize the GitIgnoreFileSet is considered to be the directory name of the project root. By default, if you ask for a file if it is ignored or not, this library assumes you are specifying all files within the SAME base directory structure.

So loading the gitignore files from workspace/project then a file in the root of the project must (by default) be specified as workspace/project/pom.xml and a file deeper in the project as for example workspace/project/src/main/java/nl/basjes/Something.java

Something like this:

GitIgnoreFileSet ignoreFileSet = new GitIgnoreFileSet(new File("workspace/project"));

if (ignoreFileSet.ignoreFile("workspace/project/pom.xml")) {
    ...
}

So comming from the default situation the ignoreFileSet.assumeQueriesIncludeProjectBaseDir() does nothing.

You can optionally specify that the files you request are relative to the project root.

So loading the gitignore files from workspace/project then a file in the root of the project must be specified as /pom.xml and a file deeper in the project as for example /src/main/java/nl/basjes/Something.java

Something like this:

GitIgnoreFileSet ignoreFileSet = new GitIgnoreFileSet(new File("workspace/project"));

if (ignoreFileSet.ignoreFile("/pom.xml", true)) {
    ...
}

Or you can set it as the default assumption.

GitIgnoreFileSet ignoreFileSet = new GitIgnoreFileSet(new File("workspace/project"));
ignoreFileSet.assumeQueriesAreProjectRelative();

if (ignoreFileSet.ignoreFile("/pom.xml")) {
    ...
}

GitIgnore edge case

This tutorial page documents this edge case that this library also follows.

I see this as unexpected behaviour yet this is really what git does !

Pattern

logs/
!logs/important.log

Matches

logs/debug.log
logs/important.log

Explanation

Wait a minute! Shouldn't logs/important.log be negated in the example on the left
Nope! Due to a performance-related quirk in Git, you can not negate a file that is ignored due to a pattern matching a directory

Building

The maven build must be run under Java 17 or newer (because of plugins) and will use toolchains to actually build the software using JDK 21.

License

CodeOwners Tools
Copyright (C) 2023-2025 Niels Basjes

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an AS IS BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

About

A library to use and verify the CODEOWNERS (both the Github and Gitlab variants) and .gitignore files

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 89.9%
  • Shell 6.8%
  • Groovy 2.3%
  • ANTLR 1.0%