Skip to content

Adding a git subcommand

Chris McGee edited this page Jul 20, 2018 · 3 revisions

dgit is partitioned into the "cmd" and "git" subpackages, which handle the flag parsing and git interaction, respectively. The main.go mostly delegates to the cmd function with the same name as the subcommand, which parses the options into a struct from the git package, then calls the git package with the same name as the subcommand to perform the work.

The git subpackage should, generally, be a library and not directly write to stdout or stderr (except for debugging), while the cmd package should convert the high level structures returned by the subpackage to the expected format for stdout (or propagate any error to the caller.)

In order to add a new subcommand, you usually need to take the following steps (these are rough guidelines and may vary from command to command):

  1. Add a case to the main.go for the subcommand name and provide the arg usage (not the flags part) of the command
  2. Add a function with the same name to the cmd with a prototype similar to func Subcommand(c *git.Client, args []string) error { }
  3. Add a function to the git package with a prototype similar to func Subcommand(c *git.Client, opts SubcommandOptions) (something, error) { ... }
  4. Create a struct type git.SubcommandOptions. This should roughly match the git command line, except options with aliases should only have 1 canonical option (the cmd flag parsing should take care of the aliases), and things that say they read from a file or stdin should usually use an io.Reader instead to make testing easier.
  5. Parse the args using a new flag.Flagset into the struct created in step 4 and call git.Subcommand.
  6. Add some test cases for git.Subcommand. (Table driven tests are often easiest)
  7. Implement git.Subcommand and update status.txt with the state of it and git version that you used as a reference.
  8. Add an entry for your new command in the help's list of available subcommands in main.go, under case "help"

Steps 2-5 (the command parsing) is often the longest part and hardest part, so if you're not comfortable enough with git to implement a subcommand, even a PR that just parses the command line with a stub function that returns fmt.Errorf("Not implemented") is often helpful.

Note that many subcommands can't be parsed with the Go flag package, but a first approximation using it is often helpful. (Ones where you need to distinguish from a flag being not passed vs explicitly passed the default value, ones that are order sensitive, or ones that can be passed multiple times are the most common reasons for the flag package not being sufficient.) There are useful flag vars in the flags.go file in the cmd package that provide implementations that you can use with flags.Var() for aliases, multiple flag instances and not "not implemented" shunts to help using the flag package to parse unusual flags needed for the git command line.

Clone this wiki locally