mani, a CLI Tool to Manage Multiple Repositories

TL;DR - Mani is a many-repo tool that helps you manage multiple repositories. It's useful when you are working with microservices, multi-project systems, or just a bunch of personal repositories and want a central place for pulling all repositories and running commands over them.

In mani, you specify repositories and commands in a config file and then run the commands over all or a subset of the projects.

Don't worry, this post isn't going to be about the old age debate of whether you should be using a mono- or a many-repo setup. I think there's a place for both, and the two approaches have their pros and cons.

Anyway, even if you're using a mono-repo in your current workplace, you're probably using a many-repo setup for your projects (work, personal projects, etc.), so you still might find Mani useful (or some of the alternatives).

Mani came about because I needed a CLI tool to manage multiple repositories, both at work and for my projects. The premise is, you have a bunch of repositories and want the following:

  1. a central place for your repositories, containing name, URL, and a small description of the repository
  2. ability to clone all repositories in 1 command
  3. ability to run ad-hoc and custom commands (perhaps git status to see working tree status) on 1, a subset, or all of the repositories
  4. ability to get an overview of 1, a subset, or all of the repositories and commands

Mani also standardizes one of the recurring patterns that I've seen in the workplace: on your first day, you're given access to an organization on Github, and perhaps if you're lucky, there's an outdated bash script which pulls some of the repositories you're supposed to work on. Other times, you're simply given a README document with links to all the projects you're supposed to visit manually and clone.

Usage

Let's install Mani, binaries are available on Github release page, or you can install it via cURL (only Linux & MacOS):

curl -sfL https://raw.githubusercontent.com/alajmo/mani/main/install.sh | sh

There's also additional install methods in manicli.com.

Now, let's say you have a bunch git repositories:


$ tree -L 2

.
├── frontend
│   ├── dashgrid
│   └── pinto
└── template-generator

We'll start by initializing Mani in this directory:


$ mani init

Initialized mani repository in /home/samir/tmp/init
- Created mani.yaml
- Created .gitignore

Following projects were added to mani.yaml

 Project            | Path
--------------------+--------------------
 init               | .
 dashgrid           | frontend/dashgrid
 pinto              | frontend/pinto
 template-generator | template-generator

Let's see the content of the two files generated:


$ cat mani.yaml

projects:
  init:
    path: .

  dashgrid:
    path: frontend/dashgrid
    url: https://github.com/alajmo/dashgrid.git

  pinto:
    path: frontend/pinto
    url: https://github.com/alajmo/pinto.git

  template-generator:
    url: https://github.com/alajmo/template-generator.git

tasks:
  hello:
  desc: Print Hello World
  cmd: echo "Hello World"

$ cat .gitignore

# mani #
template-generator
frontend/pinto
frontend/dashgrid
# mani #

the mani init command created a mani.yaml file that contains our repositories as well as an example task named hello-world and a .gitignore file that contains all the projects. This is because when we initialize this directory as a git repository (which is recommended, as other users can simply clone this repository and then run mani sync to clone all repositories), we want to prevent the directories to be added to our Mani repository.

Mani has a bunch of different sub-commands that helps us view and manage our repositories:


# Open mani.yaml in your preferred editor
$ mani edit

# List projects
$ mani list projects

Project
-------------------
init
dashgrid
pinto
template-generator

# List repositories in a tree-like format
$ mani list projects --tree

┌─ frontend
│  ├─ dashgrid
│  └─ pinto
└─ template-generator

# Describe all tasks
$ mani describe tasks

Name: hello
Description: Print Hello World
Theme: default
Target:
    All: false
    Cwd: false
    Projects:
    Paths:
    Tags:
Spec:
    Output: text
    Parallel: false
    IgnoreError: false
    OmitEmpty: false
Cmd:
    echo "Hello World"

Now, let's adds some tags and descriptions to our projects, and some new tasks:

projects:
  example:
    path: .
    desc: A mani example

  pinto:
    path: frontend/pinto
    url: https://github.com/alajmo/pinto.git
    desc: A vim theme editor
    tags: [frontend, node]

  template-generator:
    url: https://github.com/alajmo/template-generator.git
    desc: A simple bash script used to manage boilerplates
    tags: [cli, bash]
    env:
      branch: master

themes:
  custom:
    table:
      options:
        draw_border: true
        separate_columns: true
        separate_header: true
        separate_rows: true

tasks:
  git-status:
    desc: show working tree status
    cmd: git status

  git-last-commit-msg:
    desc: show last commit
    cmd: git log -1 --pretty=%B

  git-last-commit-date:
    desc: show last commit date
    cmd: |
      git log -1 --format="%cd (%cr)" -n 1 --date=format:"%d  %b %y" \
      | sed 's/ //'

  git-branch:
    desc: show current git branch
    cmd: git rev-parse --abbrev-ref HEAD

  npm-install:
    desc: run npm install in node repos
    target:
      tags: [node]
    cmd: npm install

  git-overview:
    desc: show branch, local and remote diffs, last commit and date
    theme: custom
    commands:
      - task: git-branch
      - task: git-last-commit-msg
      - task: git-last-commit-date

Run git-status task and target projects with the tag bash:


$ mani run git-status --tags bash

TASK [git-status: show working tree status] **************************

template-generator | On branch master
template-generator | Your branch is up to date with 'origin/master'.
template-generator |
template-generator | nothing to commit, working tree clean
Name:         git-status
Description:  Show git status
Shell:        sh -c
Env:
Command:      git status

Run task git-status for repositories under the frontend directory:


TASK [git-status: show working tree status] *************

pinto | On branch main
pinto | Your branch is up to date with 'origin/main'.
pinto |
pinto | nothing to commit, working tree clean

Run another task that has multiple commands and display the output in a table:


$ mani run git-overview  -t bash -d frontend/ -o table

+--------------------+------------+-------------------------------------------+-----------------------------------+
| Project            | Git-Branch | Git-Last-Commit-Msg                       | Git-Last-Commit-Date              |
+--------------------+------------+-------------------------------------------+-----------------------------------+
| pinto              | main       | Update readme                             | 22 Mar 22 (6 weeks ago)           |
|                    |            |                                           |                                   |
+--------------------+------------+-------------------------------------------+-----------------------------------+
| template-generator | master     | Edit command should work without argument | 24 Jan 20 (2 years, 3 months ago) |
|                    |            |                                           |                                   |
+--------------------+------------+-------------------------------------------+-----------------------------------+

Now if we want to execute an ad-hoc command, for instance, count the number of files in all projects, we can use the mani exec sub-command:


$ mani exec --all --output table --parallel 'find . -type f | wc -l'

 Project            | Output
--------------------+--------
 example            | 486
 pinto              | 361
 template-generator | 42

Conclusion

I hope you find this intro to Mani useful, and if you want to learn more, head to github.com/alajmovic/mani.

Alternatives

Mani isn't the first of its kind, check out the following alternatives!

dark/light theme