This is the third milestone where we implement sub-commands to delegate git commands. Specifically, the sub-commands take the format of

gita <sub-command> [repo-name(s)]

If no repo name is provided, the sub-command applies to all repos. My mostly used sub-commands are gita fetch and gita pull.

These sub-commands don’t take additional arguments, which limits their usefulness. We will implement two remedies. One is sub-command customization, which works for fixed arguments. The other is the super sub-command

gita super [repo-names(s)] <any-git-command-with-or-without-arguments>

where one can really execute any command.

The other posts in this series are

v0.2.1: delegate fetch, pull, push

The delegating sub-commands (say gita pull) have similar structure as the bookkeeping sub-commands such as gita add or gita rm. But obviously we don’t want to add one function for each sub-command. One trick is to pass the git command to the sub-parser and have a function git_cmd to process all the commands. For example, suppose the sub-parser for the pull command is called p_pull. Then we can call

p_pull.set_defaults(func=git_cmd, cmd='git pull')

which defines the cmd to be executed. And the git_cmd function may look like

def git_cmd(args):
    """
    Delegate git command
    """
    repos = utils.get_repos()
    if args.repo:  # with user specified repo(s)
        repos = {k: repos[k] for k in args.repo}
    for path in repos.values():
        utils.exec_git(path, args.cmd)

If these snippets look unfamiliar to you, go back to milestone 1.

v0.2.2: improve the output from multiple repos

When running delegated git commands for multiple repos, it could be hard to identify which message belongs to which repo. The goal of this session is to improve the output. Specifically,

  • every line of message begins with the repo name
  • outputs from different repos are separated by a blank line

For example,

$ gita pull
> repo-abc: ars tarstarstarst
> repo-abc: arstarstarstarst
> repo-abc: 1 file changed, 6 insertions(+), 6 deletions(-)
>
> another-repo: tasra arstarst astars assss aoo
> another-repo:    8298023..d33b002  master     -> origin/master
>
> repo3: Already up to date.

v0.2.3: use yaml file for sub-commands

The approach in session v0.2.1 has its caveat. To add multiple delegating sub-commands, we have a lot of tedious work. Fortunately such repetitive effort is easy to automate. We can build a sub-parser generation process and put the git command information in text format.

I will use yaml files for the text representation, which is not supported by the Python standard library. Alternatively, you can use json files, which is supported. But they are more verbose.

To install the YAML parser, run

pip3 install pyyaml

Remember to add it in requirements.txt too.

With the yaml file, we can load all the commands and create sub-parsers within a loop. I call this file cmds.yml and its content looks like

pull:
  help: pull remote updates
push:
  help: push the local updates

Here the entry names are the delegated git commands. I also included some help messages.

When your code works, there is one more deployment issue. By default, only source code are packaged into the installation files. To include cmds.yml for distribution, we need to have a MANIFEST.IN in the project root folder with the following content

include gita/cmds.yml

It simply tells the packager where the files are.

In the setup.py, we also need the following line.

include_package_data=True,

v0.2.4: enhance yaml file

Another enhancement for our YAML representation is to include git commands with arguments. For example, git remote is not as useful as git remote -v, and we can define gita remote [repo-name(s)] to have the latter behavior. To implement it, we can use an extra field for the full command, say, cmd, as seen below

remote:
  cmd: remote -v
  help: show remote settings

Then in the Python code, we can execute this cmd instead of the entry/sub-command name.

We can also use this YAML file to define other shortcuts. For example

br:
  cmd: branch -vv
  help: show local branches
stat:
  cmd: diff --stat
  help: show edit statistics

This is similar to git alias.

v0.2.5: add sub-command customization

So far we have made the generation of delegating sub-commands really cheap. One low-hanging fruit is to allow users to define their own sub-commands in another yaml file, say inside ~/.config/gita/. All we need is to have the sub-parser generation code process this file.

v0.2.6: add super sub-command

This super sub-command will allow us to execute any git commands with any arguments. It works even with any git aliases on user’s computer. For example,

gita super co prev-release

will checkout the prev-release branch for all repos, given the git alias co for checkout exists.

The implementation is not too different from the other sub-commands. You may want to checkout argparse.REMAINDER for the nargs.

v0.3: clean up and tag

This completes milestone 3. At this point, you can optionally tag the code base using

git tag v0.3