nielsbasjes / codeowners

A library to use and verify the CODEOWNERS and .gitignore files
Apache License 2.0
5 stars 4 forks source link
codeowners codeowners-validator maven-enforcer-plugin

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 the actual project files. (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.

CodeOwners Enforcer rule

Configuration parameters

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.6.0</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> -->
          </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"));

Then you can for each file in the project check 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

If you want to build this yourself you must ensure you have both JDK 8 and JDK 17 and/or JDK 21 installed. 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 8.

This means that you must also ensure you have a ~/.m2/toolchains.xml containing at least this entry (where the path on your system is likely to be different, this path is on my Ubuntu 20.04 LTS system)

<?xml version="1.0" encoding="UTF8"?>
<toolchains>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>8</version>
    </provides>
    <configuration>
      <jdkHome>/usr/lib/jvm/java-8-openjdk-amd64</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

License

CodeOwners Tools
Copyright (C) 2023-2024 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.