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, 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

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 /tmp

If we list files again, we can see that there are two additional files created (mani.yaml and .gitignore):


$ tree -aL 2
.
├── .gitignore
├── mani.yaml
├── frontend
│   ├── dashgrid
│   └── pinto
└── template-generator

Let's see the content of the two files:


$ cat mani.yaml

projects:
  - name: tmp
    path: .

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

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

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

tasks:
  - name: hello-world
    description: Print Hello World
    command: echo "Hello World"

$ cat .gitignore

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

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
┌────────────────────┬──────┬─────────────┐
│ name               │ tags │ description │
├────────────────────┼──────┼─────────────┤
│ tmp                │      │             │
│ dashgrid           │      │             │
│ pinto              │      │             │
│ template-generator │      │             │
└────────────────────┴──────┴─────────────┘

# Describe tasks
$ mani describe tasks
Name:         hello-world
Description:  Print Hello World
Shell:        sh -c
Env:
Command:      echo "Hello World"

# List repositories in a tree-like format
$ mani tree
┌─ frontend
│  ├─ dashgrid
│  └─ pinto
└─ template-generator

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


projects:
  - name: tmp
    path: .

  - name: dashgrid
    path: frontend/dashgrid
    description: A pure javascript high performance grid
    tags: [node]
    url: https://github.com/alajmo/dashgrid.git

  - name: pinto
    path: frontend/pinto
    description: Create, view and edit vim color themes
    tags: [node]
    url: https://github.com/alajmo/pinto.git

  - name: template-generator
    description: Generate boilerplates from CLI
    tags: [bash,cli]
    url: https://github.com/alajmo/template-generator.git

tasks:
  - name: hello-world
    tags: [bash]
    description: Print Hello World
    command: echo "Hello World"

  - name: npm-version
    tags: [node]
    shell: node -e
    description: Print package version
    command: |
      const { version } = require("./package.json").version
      console.log(version)

  - name: git-status
    description: Show git status
    command: git status

  - name: git-daily
    description: show branch, local and remote diffs, last commit, and date
    commands:
      - name: Branch
        command: git rev-parse --abbrev-ref HEAD

      - name: L Diff
        command: git diff --name-only | wc -l

      - name: R Diff
        command: |
          current_branch=$(git rev-parse --abbrev-ref HEAD)
          git diff "$current_branch" "origin/$current_branch" --name-only 2> /dev/null | wc -l

      - name: Last commit
        command: git log -1 --pretty=%B

      - name: Commit date
        command: git log -1 --format="%cd (%cr)" -n 1 --date=format:"%d  %b %y" | sed 's/ //'

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


$ mani run git-status --tags bash
Name:         git-status
Description:  Show git status
Shell:        sh -c
Env:
Command:      git status


template-generator
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

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


$ mani run git-status --dirs frontend/
Name:         git-status
Description:  Show git status
Shell:        sh -c
Env:
Command:      git status


dashgrid
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean


pinto
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

Run the npm-version task, this task will target projects with the node tag by default (since we set it in the task definition), uses Node.js to evaluate the command, and shows the output in a pretty table:


$ mani run npm-version --output table
Name:         npm-version
Description:  Print package version
Shell:        node -e
Env:
Command:      const { version } = require("./package.json")
              console.log(version)


┌──────────┬─────────────┐
│ Project  │ npm-version │
├──────────┼─────────────┤
│ dashgrid │ 0.2.0       │
├──────────┼─────────────┤
│ pinto    │ 0.3.2       │
└──────────┴─────────────┘

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


$ mani run git-daily --describe=false -t bash -d frontend/

┌────────────────────┬────────┬────────┬────────┬──────────────────────────────────┐
│ Project            │ Branch │ L Diff │ R Diff │ Commit date                      │
├────────────────────┼────────┼────────┼────────┼──────────────────────────────────┤
│ dashgrid           │ master │ 0      │ 0      │ 13 dec 20 (8 months ago)         │
├────────────────────┼────────┼────────┼────────┼──────────────────────────────────┤
│ pinto              │ master │ 0      │ 0      │ 09 aug 21 (5 days ago)           │
├────────────────────┼────────┼────────┼────────┼──────────────────────────────────┤
│ template-generator │ master │ 0      │ 0      │ 24 jan 20 (1 year, 7 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 'ls -1 | wc -l' -a -o table
┌────────────────────┬────────┐
│ Project            │ Output │
├────────────────────┼────────┤
│ tmp                │ 3      │
│                    │        │
├────────────────────┼────────┤
│ dashgrid           │ 9      │
│                    │        │
├────────────────────┼────────┤
│ pinto              │ 10     │
│                    │        │
├────────────────────┼────────┤
│ template-generator │ 9      │
│                    │        │
└────────────────────┴────────┘

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