Code Owners syntax and error handling

Tier: Premium, Ultimate Offering: GitLab.com, Self-managed, GitLab Dedicated

This page describes the syntax and error handling used in Code Owners files, and provides an example file.

Code Owners syntax

Comments

Lines beginning with # are ignored:

# This is a comment

Sections

Sections are groups of entries. A section begins with a section heading in square brackets, followed by the entries.

[Section name]
/path/of/protected/file.rb @username
/path/of/protected/dir/ @group

Section headings

Section headings must always have a name. They can also be made optional, or require a number of approvals. A list of default owners can be added to the section heading line.

# Required section
[Section name]

# Optional section
^[Section name]

# Section requiring 5 approvals
[Section name][5]

# Section with @username as default owner
[Section name] @username

# Section with @group and @subgroup as default owners and requiring 2 approvals
[Section name][2] @group @subgroup

Section names

Sections names are defined between square brackets. Section names are not case-sensitive. Sections with duplicate names are combined.

[Section name]

Required sections

Required sections do not include ^ before the section name.

[Required section]

Optional sections

Optional sections include a ^ before the section name.

^[Optional section]

Sections requiring multiple approvals

Sections requiring multiple approvals include the number of approvals in square brackets after the section name.

[Section requiring 5 approvals][5]
note
Optional sections ignore the number of approvals required.

Sections with default owners

You can define a default owner for the entries in a section by appending the owners to the section heading.

# Section with @username as default owner
[Section name] @username

# Section with @group and @subgroup as default owners and requiring 2 approvals
[Section name][2] @group @subgroup

Code Owner entries

Each Code Owner entry includes a path followed by one or more owners.

README.md @username1
note
If an entry is duplicated in a section, the last entry is used from each section.

Relative paths

If a path does not start with a /, the path is treated as if it starts with a globstar. README.md is treated the same way as /**/README.md:

# This will match /README.md, /internal/README.md, /app/lib/README.md
README.md @username

# This will match /internal/README.md, /docs/internal/README.md, /docs/api/internal/README.md
internal/README.md

Absolute paths

If a path starts with a / it matches the root of the repository.

# Matches only the file named `README.md` in the root of the repository.
/README.md

# Matches only the file named `README.md` inside the `/docs` directory.
/docs/README.md

Directory paths

If a path ends with /, the path matches any file in the directory.

# This is the same as `/docs/**/*`
/docs/

Wildcard paths

Wildcards can be used to match one of more characters of a path.

# Any markdown files in the docs directory
/docs/*.md @username

# /docs/index file of any filetype
# For example: /docs/index.md, /docs/index.html, /docs/index.xml
/docs/index.* @username

# Any file in the docs directory with 'spec' in the name.
# For example: /docs/qa_specs.rb, /docs/spec_helpers.rb, /docs/runtime.spec
/docs/*spec* @username

# README.md files one level deep within the docs directory
# For example: /docs/api/README.md
/docs/*/README.md @username

Globstar paths

Globstars (**) can be used to match zero or more directories and subdirectories.

# This will match /docs/index.md, /docs/api/index.md, /docs/api/graphql/index.md
/docs/**/index.md

Entry owners

Entries must be followed by one or more owner. These can be groups, subgroups, and users. Order of owners is not important.

/path/to/entry.rb @group
/path/to/entry.rb @group/subgroup
/path/to/entry.rb @user
/path/to/entry.rb @group @group/subgroup @user

Groups as entry owners

Groups and subgroups can be owners of an entry. Each entry can be owned by one or more owners. For more details see the Add a group as a Code Owner.

/path/to/entry.rb @group
/path/to/entry.rb @group/subgroup
/path/to/entry.rb @group @group/subgroup

Users as entry owners

Users can be owners of an entry. Each entry can be owned by one or more owners.

/path/to/entry.rb @username1
/path/to/entry.rb @username1 @username2

Error handling in Code Owners

History

Entries with spaces

Paths containing whitespace must be escaped with backslashes: path\ with\ spaces/*.md. Without the backslashes, the path after the first whitespace is parsed as an owner. GitLab the parses folder with spaces/*.md @group into path: "folder", owners: " with spaces/*.md @group".

Unparsable sections

If a section heading cannot be parsed, the section is:

  1. Parsed as an entry.
  2. Added to the previous section.
  3. If no previous section exists, the section is added to the default section.

For example, this file is missing a square closing bracket:

* @group

[Section name
docs/ @docs_group

GitLab recognizes the heading [Section name as an entry. The default section includes 3 rules:

  • Default section
    • * owned by @group
    • [Section owned by name
    • docs/ owned by @docs_group

This file contains an unescaped space between the words Section and name. GitLab recognizes the intended heading as an entry:

[Docs]
docs/**/* @group

[Section name]{2} @group
docs/ @docs_group

The [Docs] section then includes 3 rules:

  • docs/**/* owned by @group
  • [Section owned by name]{2} @group
  • docs/ owned by @docs_group

Malformed owners

Each entry must contain 1 or more owners to be valid, malformed owners are ignored. For example /path/* @group user_without_at_symbol @user_with_at_symbol is owned by @group and @user_with_at_symbol.

Inaccessible or incorrect owners

Inaccessible or incorrect owners are ignored. For example, if @group, @username, and example@gitlab.com are accessible on the project and we create an entry:

* @group @grou @username @i_left @i_dont_exist example@gitlab.com invalid@gitlab.com

GitLab ignores @grou, @i_left, @i_dont_exist, and invalid@gitlab.com.

For more information on who is accessible, see Add a group as a Code Owner.

Zero owners

If an entry includes no owners, or zero accessible owners exist, the entry is invalid. Because this rule can never be satisfied, GitLab auto-approves it in merge requests.

note
When a protected branch has Require code owner approval enabled, rules with zero owners are still honored.

Less than 1 required approval

When defining the number of approvals for a section, the minimum number of approvals is 1. Setting the number of approvals to 0 results in GitLab requiring one approval.

Example CODEOWNERS file

# This is an example of a CODEOWNERS file.
# Lines that start with `#` are ignored.

# app/ @commented-rule

# Specify a default Code Owner by using a wildcard:
* @default-codeowner

# Specify multiple Code Owners by using a tab or space:
* @multiple @code @owners

# Rules defined later in the file take precedence over the rules
# defined before.
# For example, for all files with a filename ending in `.rb`:
*.rb @ruby-owner

# Files with a `#` can still be accessed by escaping the pound sign:
\#file_with_pound.rb @owner-file-with-pound

# Specify multiple Code Owners separated by spaces or tabs.
# In the following case the CODEOWNERS file from the root of the repo
# has 3 Code Owners (@multiple @code @owners):
CODEOWNERS @multiple @code @owners

# You can use both usernames or email addresses to match
# users. Everything else is ignored. For example, this code
# specifies the `@legal` and a user with email `janedoe@gitlab.com` as the
# owner for the LICENSE file:
LICENSE @legal this_does_not_match janedoe@gitlab.com

# Use group names to match groups, and nested groups to specify
# them as owners for a file:
README @group @group/with-nested/subgroup

# End a path in a `/` to specify the Code Owners for every file
# nested in that directory, on any level:
/docs/ @all-docs

# End a path in `/*` to specify Code Owners for every file in
# a directory, but not nested deeper. This code matches
# `docs/index.md` but not `docs/projects/index.md`:
/docs/* @root-docs

# Include `/**` to specify Code Owners for all subdirectories
# in a directory. This rule matches `docs/projects/index.md` or
# `docs/development/index.md`
/docs/**/*.md @root-docs

# This code makes matches a `lib` directory nested anywhere in the repository:
lib/ @lib-owner

# This code match only a `config` directory in the root of the repository:
/config/ @config-owner

# If the path contains spaces, escape them like this:
path\ with\ spaces/ @space-owner

# Code Owners section:
[Documentation]
ee/docs    @docs
docs       @docs

# Use of default owners for a section. In this case, all files (*) are owned by
the dev team except the README.md and data-models which are owned by other teams.
[Development] @dev-team
*
README.md @docs-team
data-models/ @data-science-team

# This section is combined with the previously defined [Documentation] section:
[DOCUMENTATION]
README.md  @docs