NAME

git-grouper - Categorize git repositories into one/more groups and perform actions on them

VERSION

This document describes version 0.008 of git-grouper (from Perl distribution Git-Grouper), released on 2026-02-07.

SYNOPSIS

Listing groups

List all defined groups:

% git-grouper ls-all-groups      ; # just list the names
% git-grouper ls-all-groups -l   ; # list detailed records

List which group(s) repositories belong to:

% git-grouper ls-repo-groups ; # list current working repo's group(s)
% git-grouper ls-repo-groups REPO1 REPO2 ...
% git-grouper ls-repo-groups REPO1 REPO2 ...

Listing repositories

List repositories that belong to specified group(s):

% git-grouper filter-repo-has-group GROUP REPO1 REPO2 ...
% git-grouper filter-repo-has-group --ignore-nondirs *
% git-grouper filter-repo-has-group 'GROUP1&GROUP2' REPO1 REPO2 ... ; # repos must belong to all of specified groups
% git-grouper filter-repo-has-group 'GROUP1|GROUP2' REPO1 REPO2 ... ; # repos must belong to any of specified groups

List repositories that do not belong to specified group(s):

% git-grouper filter-repo-lacks-group GROUP REPO1 REPO2 ...
% git-grouper filter-repo-lacks-group --ignore-nondirs *
% git-grouper filter-repo-lacks-group 'GROUP1&GROUP2' REPO1 REPO2 ... ; # repos must not belong to any of specified groups
% git-grouper filter-repo-lacks-group 'GROUP1|GROUP2' REPO1 REPO2 ... ; # repos must not belong to just one of specified groups

List repositories that do not belong to any group:

% git-grouper filter-repo-orphan REPO1 REPO2 ...
% git-grouper filter-repo-orphan --ignore-nondirs *

List repositories that belong to a group or more:

% git-grouper filter-repo-not-orphan REPO1 REPO2 ...
% git-grouper filter-repo-not-orphan --ignore-nondirs *

List repositories that belong to multiple (instead of a single) group:

% git-grouper filter-repo-multiple-group REPO1 REPO2 ...
% git-grouper filter-repo-multiple-group --ignore-nondirs *

List repositories that belong to just one (instead of multiple) group:

% git-grouper filter-repo-single-group REPO1 REPO2 ...
% git-grouper filter-repo-single-group --ignore-nondirs *

Listing repositories

List all defined repositories:

% git-grouper ls-all-remotes      ; # just list the names
% git-grouper ls-all-remotes -l   ; # list detailed records

List repo's remotes according to group's configuration:

% git-grouper ls-repo-remotes ; # remotes of current working repo
% git-grouper ls-repo-remotes REPO1 REPO2 ...
% git-grouper ls-repo-remotes --ignore-nondirs *

Configuring repositories based on group's attributes

% git-grouper configure-repo ; # configure current working repo
% git-grouper configure-repo REPO1 REPO2 ...
% git-grouper configure-repo --ignore-nondirs *

DESCRIPTION

git-grouper is a tool to categorize your git repository into one of predefined groups. Later, you can: 1) set your repository's remotes, username, email, etc based on the group's attributes; 2) perform actions on a group of repositories.

Why group git repositories? To perform actions on a set of git repositories in one go. Also, another reason is to set remotes or username/email based on a group's characteristics instead of individually. One case where this can be useful is when restoring repositories from backup via cloning. You might want to clone from local backup instead of from origin, but when cloning git will set origin of your restored repository to your backup. With git-grouper, you can restore the origin (as well as other remotes) from a configuration.

To use git-grouper, you first create a configuration file in IOD format, e.g. at ~/.config/git-grouper.conf. In it, you define your groups, remotes, nad other things. Details on the configuration can be read in the "CONFIGURATION FILE" section. After that, you can use run this command with one of the available subcommands (see "SYNOPSIS").

SUBCOMMANDS

configure-repo

Configure repo based on group's attributes.

filter-repo-has-group

Only list repos that belong to specified group(s).

filter-repo-lacks-group

Only list repos that do not belong to specified group(s).

filter-repo-multiple-group

Only list repos that belong to at least two groups.

filter-repo-not-orphan

Only list repos that belong to at least one group.

filter-repo-orphan

Only list repos that do not belong to any group(s).

filter-repo-single-group

Only list repos that belong to just a single group.

groups

Shortcut for ls-repo-groups (you can use just "g").

ls-all-groups

List all defined groups.

ls-all-remotes

List all defined remotes.

ls-repo-groups

List the group(s) of specified repos.

ls-repo-remotes

List remotes of specified repos based on group configuration.

OPTIONS

* marks required options.

Common options

--debug

Shortcut for --log-level=debug.

--format=s

Choose output format, e.g. json, text.

Default value:

undef

Output can be displayed in multiple formats, and a suitable default format is chosen depending on the application and/or whether output destination is interactive terminal (i.e. whether output is piped). This option specifically chooses an output format.

--help, -h, -?

Display help message and exit.

--json

Set output format to json.

--log-level=s

Set log level.

By default, these log levels are available (in order of increasing level of importance, from least important to most): trace, debug, info, warn/warning, error, fatal. By default, the level is usually set to warn, which means that log statements with level info and less important levels will not be shown. To increase verbosity, choose info, debug, or trace.

For more details on log level and logging, as well as how new logging levels can be defined or existing ones modified, see Log::ger.

--naked-res

When outputing as JSON, strip result envelope.

Default value:

0

By default, when outputing as JSON, the full enveloped result is returned, e.g.:

[200,"OK",[1,2,3],{"func.extra"=>4}]

The reason is so you can get the status (1st element), status message (2nd element) as well as result metadata/extra result (4th element) instead of just the result (3rd element). However, sometimes you want just the result, e.g. when you want to pipe the result for more post-processing. In this case you can use --naked-res so you just get:

[1,2,3]
--page-result

Filter output through a pager.

This option will pipe the output to a specified pager program. If pager program is not specified, a suitable default e.g. less is chosen.

--quiet

Shortcut for --log-level=error.

--subcommands

List available subcommands.

--trace

Shortcut for --log-level=trace.

--verbose

Shortcut for --log-level=info.

--version, -v

Display program's version and exit.

--view-result

View output using a viewer.

This option will first save the output to a temporary file, then open a viewer program to view the temporary file. If a viewer program is not chosen, a suitable default, e.g. the browser, is chosen.

Options for subcommand configure-repo

--clean-remotes

Delete all remotes not specified by the group configuration.

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

Options for subcommand filter-repo-has-group

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--group-spec=s

(No description)

Can also be specified as the 1st command-line argument.

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 2nd command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 2nd command-line argument and onwards.

Can be specified multiple times.

Options for subcommand filter-repo-lacks-group

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--group-spec=s

(No description)

Can also be specified as the 1st command-line argument.

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 2nd command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 2nd command-line argument and onwards.

Can be specified multiple times.

Options for subcommand filter-repo-multiple-group

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

Options for subcommand filter-repo-not-orphan

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

Options for subcommand filter-repo-orphan

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

Options for subcommand filter-repo-single-group

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

Options for subcommand groups

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--groups-array=s

How to return groups.

Default value:

"never"

Valid values:

["never","auto","always"]

When set to 'never', will always return a string where multiple groups are written as comma-separated list ('', 'group1', 'group1,group2'). When set to 'auto', will return either an empty string when there is no group, or string for a single group, or array for multiple groups. When set to 'always', will always return an array for the groups result.

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

--result-array=s

How to return result.

Default value:

"auto"

Valid values:

["auto","always"]

If set to 'auto', then when there is only one repo, will not return an array but the groups directly. When set to 'always', will always return an array of records.

Options for subcommand ls-all-groups

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--detail, -l

(No description)

Options for subcommand ls-all-remotes

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--detail, -l

(No description)

Options for subcommand ls-repo-groups

--config-file=s

(No description)

--config-json=s

See --config.

--config=s

(No description)

--groups-array=s

How to return groups.

Default value:

"never"

Valid values:

["never","auto","always"]

When set to 'never', will always return a string where multiple groups are written as comma-separated list ('', 'group1', 'group1,group2'). When set to 'auto', will return either an empty string when there is no group, or string for a single group, or array for multiple groups. When set to 'always', will always return an array for the groups result.

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

--result-array=s

How to return result.

Default value:

"auto"

Valid values:

["auto","always"]

If set to 'auto', then when there is only one repo, will not return an array but the groups directly. When set to 'always', will always return an array of records.

Options for subcommand ls-repo-remotes

--ignore-nondirs

(No description)

--repo-json=s

See --repo.

Can also be specified as the 1st command-line argument and onwards.

--repo=s@

(No description)

Can also be specified as the 1st command-line argument and onwards.

Can be specified multiple times.

--result-array=s

Valid values:

["auto","always"]

COMPLETION

This script has shell tab completion capability with support for several shells.

bash

To activate bash completion for this script, put:

complete -C git-grouper git-grouper

in your bash startup (e.g. ~/.bashrc). Your next shell session will then recognize tab completion for the command. Or, you can also directly execute the line above in your shell to activate immediately.

It is recommended, however, that you install modules using cpanm-shcompgen which can activate shell completion for scripts immediately.

tcsh

To activate tcsh completion for this script, put:

complete git-grouper 'p/*/`git-grouper`/'

in your tcsh startup (e.g. ~/.tcshrc). Your next shell session will then recognize tab completion for the command. Or, you can also directly execute the line above in your shell to activate immediately.

It is also recommended to install shcompgen (see above).

other shells

For fish and zsh, install shcompgen as described above.

CONFIGURATION FILE

Configuration file is in IOD format, which is based on INI that is familiar to most. Groups and remotes are written as IOD sections, much like in git configuration.

A sample configuration file

[remote "github"]
url_template = git@github.com:[% github_username %]/[% repo_name %].git

[remote "github_company1"]
url_template = git@github.com+company1:company1/[% repo_name %].git

[remote "privbak"]
url_template = ssh://u1@hostname:/path/to/[% repo_name %].git

[group "company1"]
repo_name_pattern = /^company1-/
remotes = ["github_company1", "privbak"]
username = foo
email = foo@company1.com

[group "priv_perl"]
repo_name_pattern = /^perl-/
has_tags = ["priv"]
remotes = ["github", "privbak"]
username = perlancar
email = perlancar@gmail.com

[group "perl"]
repo_name_pattern = /^perl-/
github_username = perlancar
remotes = ["github", "privbak"]
username = perlancar
email = perlancar@gmail.com

[group "other"]
repo_name_pattern = /^./
remotes = ["privbak"]
username = foo
email = foo@example.com

Group definition

[group GROUPNAME]
; filter keys
repo_pattern_name = /REGEX/
has_all_tags = [TAG1, TAG2, ...]
has_any_tags = [TAG1, TAG2, ...]
lacks_all_tags = [TAG1, TAG2, ...]
lacks_any_tags = [TAG1, TAG2, ...]

; attribute keys
remotes = [REMOTE1, REMOTE2, ...]
user_name = SOME NAME
email = EMAIL@ADDRESS

Group name must be an identifier, which means it must start with a letter or underscore, and contains only alphanumeric characters.

A group definition contains filter keys (keys which specify which repositories can belong to this group) and attribute keys (keys that set what attributes repositories in this group should have). To belong to the group, all filters must be satisfied.

Note that instead of satisfying all filters, you can also make a repository belong to a group by having an empty file named .group-GROUPNAME in the top-level directory.

  • repo_pattern_name

    A regex pattern. Repositories that have names that match this pattern will satisfy this filter.

  • has_all_tags

    An array of strings (tags). Repositories must have all the specified tags to satisfy this filter. Tags are identifiers. Repository can be tagged by having an empty file that is named .tag-TAGNAME in the top-level directory.

  • has_any_tags

    An array of strings (tags). Repositories just need to have one of the specified tags to satisfy this filter.

  • lacks_all_tags

    An array of strings (tags). Repositories must lack all the specified tags to satisfy this filter.

  • lacks_any_tags

    An array of strings (tags). Repositories just need to lack any the specified tags to satisfy this filter.

HOMEPAGE

Please visit the project's homepage at https://metacpan.org/release/Git-Grouper.

SOURCE

Source repository is at https://github.com/perlancar/perl-Git-Grouper.

AUTHOR

perlancar <perlancar@cpan.org>

CONTRIBUTING

To contribute, you can send patches by email/via RT, or send pull requests on GitHub.

Most of the time, you don't need to build the distribution yourself. You can simply modify the code, then test via:

% prove -l

If you want to build the distribution (e.g. to try to install it locally on your system), you can install Dist::Zilla, Dist::Zilla::PluginBundle::Author::PERLANCAR, Pod::Weaver::PluginBundle::Author::PERLANCAR, and sometimes one or two other Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond that are considered a bug and can be reported to me.

COPYRIGHT AND LICENSE

This software is copyright (c) 2026, 2025 by perlancar <perlancar@cpan.org>.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

BUGS

Please report any bugs or feature requests on the bugtracker website https://rt.cpan.org/Public/Dist/Display.html?Name=Git-Grouper

When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.