Adding a New Command
Lots of the subcommands are written as builtins, which means they are
implemented in C and compiled into the main git executable. Implementing the
very simple psuh command as a built-in will demonstrate the structure of the
codebase, the internal API, and the process of working together as a contributor
with the reviewers and maintainer to integrate this change into the system.
Built-in subcommands are typically implemented in a function named "cmd_"
followed by the name of the subcommand, in a source file named after the
subcommand and contained within builtin/. So it makes sense to implement your
command in builtin/psuh.c. Create that file, and within it, write the entry
point for your command in a function matching the style and signature:
int cmd_psuh(int argc, const char **argv, const char *prefix)
We’ll also need to add the declaration of psuh; open up builtin.h, find the
declaration for cmd_pull, and add a new line for psuh immediately before it,
in order to keep the declarations alphabetically sorted:
int cmd_psuh(int argc, const char **argv, const char *prefix);
Be sure to #include "builtin.h" in your psuh.c. You’ll also need to
#include "gettext.h" to use functions related to printing output text.
Go ahead and add some throwaway printf to the cmd_psuh function. This is a
decent starting point as we can now add build rules and register the command.
|
Note
|
Your throwaway text, as well as much of the text you will be adding over
the course of this tutorial, is user-facing. That means it needs to be
localizable. Take a look at po/README under "Marking strings for translation".
Throughout the tutorial, we will mark strings for translation as necessary; you
should also do so when writing your user-facing commands in the future. |
int cmd_psuh(int argc, const char **argv, const char *prefix)
{
printf(_("Pony saying hello goes here.\n"));
return 0;
}
Let’s try to build it. Open Makefile, find where builtin/pull.o is added
to BUILTIN_OBJS, and add builtin/psuh.o in the same way next to it in
alphabetical order. Once you’ve done so, move to the top-level directory and
build simply with make. Also add the DEVELOPER=1 variable to turn on
some additional warnings:
$ echo DEVELOPER=1 >config.mak
$ make
|
Note
|
When you are developing the Git project, it’s preferred that you use the
DEVELOPER flag; if there’s some reason it doesn’t work for you, you can turn
it off, but it’s a good idea to mention the problem to the mailing list. |
Great, now your new command builds happily on its own. But nobody invokes it.
Let’s change that.
The list of commands lives in git.c. We can register a new command by adding
a cmd_struct to the commands[] array. struct cmd_struct takes a string
with the command name, a function pointer to the command implementation, and a
setup option flag. For now, let’s keep mimicking push. Find the line where
cmd_push is registered, copy it, and modify it for cmd_psuh, placing the new
line in alphabetical order (immediately before cmd_pull).
The options are documented in builtin.h under "Adding a new built-in." Since
we hope to print some data about the user’s current workspace context later,
we need a Git directory, so choose RUN_SETUP as your only option.
Go ahead and build again. You should see a clean build, so let’s kick the tires
and see if it works. There’s a binary you can use to test with in the
bin-wrappers directory.
$ ./bin-wrappers/git psuh
Check it out! You’ve got a command! Nice work! Let’s commit this.
git status reveals modified Makefile, builtin.h, and git.c as well as
untracked builtin/psuh.c and git-psuh. First, let’s take care of the binary,
which should be ignored. Open .gitignore in your editor, find /git-pull, and
add an entry for your new command in alphabetical order:
...
/git-prune-packed
/git-psuh
/git-pull
/git-push
/git-quiltimport
/git-range-diff
...
Checking git status again should show that git-psuh has been removed from
the untracked list and .gitignore has been added to the modified list. Now we
can stage and commit:
$ git add Makefile builtin.h builtin/psuh.c git.c .gitignore
$ git commit -s
You will be presented with your editor in order to write a commit message. Start
the commit with a 50-column or less subject line, including the name of the
component you’re working on, followed by a blank line (always required) and then
the body of your commit message, which should provide the bulk of the context.
Remember to be explicit and provide the "Why" of your change, especially if it
couldn’t easily be understood from your diff. When editing your commit message,
don’t remove the Signed-off-by trailer which was added by -s above.
psuh: add a built-in by popular demand
Internal metrics indicate this is a command many users expect to be
present. So here's an implementation to help drive customer
satisfaction and engagement: a pony which doubtfully greets the user,
or, a Pony Saying "Um, Hello" (PSUH).
This commit message is intentionally formatted to 72 columns per line,
starts with a single line as "commit message subject" that is written as
if to command the codebase to do something (add this, teach a command
that). The body of the message is designed to add information about the
commit that is not readily deduced from reading the associated diff,
such as answering the question "why?".
Signed-off-by: A U Thor <author@example.com>
Go ahead and inspect your new commit with git show. "psuh:" indicates you
have modified mainly the psuh command. The subject line gives readers an idea
of what you’ve changed. The sign-off line (-s) indicates that you agree to
the Developer’s Certificate of Origin 1.1 (see the
Documentation/SubmittingPatches [[dco]] header).
For the remainder of the tutorial, the subject line only will be listed for the
sake of brevity. However, fully-fleshed example commit messages are available
on the reference implementation linked at the top of this document.
Implementation
It’s probably useful to do at least something besides printing out a string.
Let’s start by having a look at everything we get.
Modify your cmd_psuh implementation to dump the args you’re passed, keeping
existing printf() calls in place:
int i;
...
printf(Q_("Your args (there is %d):\n",
"Your args (there are %d):\n",
argc),
argc);
for (i = 0; i < argc; i++)
printf("%d: %s\n", i, argv[i]);
printf(_("Your current working directory:\n<top-level>%s%s\n"),
prefix ? "/" : "", prefix ? prefix : "");
Build and try it. As you may expect, there’s pretty much just whatever we give
on the command line, including the name of our command. (If prefix is empty
for you, try cd Documentation/ && ../bin-wrappers/git psuh). That’s not so
helpful. So what other context can we get?
Add a line to #include "config.h". Then, add the following bits to the
function body:
const char *cfg_name;
...
git_config(git_default_config, NULL);
if (git_config_get_string_tmp("user.name", &cfg_name) > 0)
printf(_("No name is found in config\n"));
else
printf(_("Your name: %s\n"), cfg_name);
git_config() will grab the configuration from config files known to Git and
apply standard precedence rules. git_config_get_string_tmp() will look up
a specific key ("user.name") and give you the value. There are a number of
single-key lookup functions like this one; you can see them all (and more info
about how to use git_config()) in Documentation/technical/api-config.txt.
You should see that the name printed matches the one you see when you run:
$ git config --get user.name
Great! Now we know how to check for values in the Git config. Let’s commit this
too, so we don’t lose our progress.
$ git add builtin/psuh.c
$ git commit -sm "psuh: show parameters & config opts"
|
Note
|
Again, the above is for sake of brevity in this tutorial. In a real change
you should not use -m but instead use the editor to write a meaningful
message. |
Still, it’d be nice to know what the user’s working context is like. Let’s see
if we can print the name of the user’s current branch. We can mimic the
git status implementation; the printer is located in wt-status.c and we can
see that the branch is held in a struct wt_status.
wt_status_print() gets invoked by cmd_status() in builtin/commit.c.
Looking at that implementation we see the status config being populated like so:
status_init_config(&s, git_status_config);
But as we drill down, we can find that status_init_config() wraps a call
to git_config(). Let’s modify the code we wrote in the previous commit.
Be sure to include the header to allow you to use struct wt_status:
Then modify your cmd_psuh implementation to declare your struct wt_status,
prepare it, and print its contents:
struct wt_status status;
...
wt_status_prepare(the_repository, &status);
git_config(git_default_config, &status);
...
printf(_("Your current branch: %s\n"), status.branch);
Run it again. Check it out - here’s the (verbose) name of your current branch!
Let’s commit this as well.
$ git add builtin/psuh.c
$ git commit -sm "psuh: print the current branch"
Now let’s see if we can get some info about a specific commit.
Luckily, there are some helpers for us here. commit.h has a function called
lookup_commit_reference_by_name to which we can simply provide a hardcoded
string; pretty.h has an extremely handy pp_commit_easy() call which doesn’t
require a full format object to be passed.
Add the following includes:
#include "commit.h"
#include "pretty.h"
Then, add the following lines within your implementation of cmd_psuh() near
the declarations and the logic, respectively.
struct commit *c = NULL;
struct strbuf commitline = STRBUF_INIT;
...
c = lookup_commit_reference_by_name("origin/master");
if (c != NULL) {
pp_commit_easy(CMIT_FMT_ONELINE, c, &commitline);
printf(_("Current commit: %s\n"), commitline.buf);
}
The struct strbuf provides some safety belts to your basic char*, one of
which is a length member to prevent buffer overruns. It needs to be initialized
nicely with STRBUF_INIT. Keep it in mind when you need to pass around char*.
lookup_commit_reference_by_name resolves the name you pass it, so you can play
with the value there and see what kind of things you can come up with.
pp_commit_easy is a convenience wrapper in pretty.h that takes a single
format enum shorthand, rather than an entire format struct. It then
pretty-prints the commit according to that shorthand. These are similar to the
formats available with --pretty=FOO in many Git commands.
Build it and run, and if you’re using the same name in the example, you should
see the subject line of the most recent commit in origin/master that you know
about. Neat! Let’s commit that as well.
$ git add builtin/psuh.c
$ git commit -sm "psuh: display the top of origin/master"
Adding Documentation
Awesome! You’ve got a fantastic new command that you’re ready to share with the
community. But hang on just a minute - this isn’t very user-friendly. Run the
following:
$ ./bin-wrappers/git help psuh
Your new command is undocumented! Let’s fix that.
Take a look at Documentation/git-*.txt. These are the manpages for the
subcommands that Git knows about. You can open these up and take a look to get
acquainted with the format, but then go ahead and make a new file
Documentation/git-psuh.txt. Like with most of the documentation in the Git
project, help pages are written with AsciiDoc (see CodingGuidelines, "Writing
Documentation" section). Use the following template to fill out your own
manpage:
git-psuh(1)
===========
NAME
----
git-psuh - Delight users' typo with a shy horse
SYNOPSIS
--------
[verse]
'git-psuh [<arg>...]'
DESCRIPTION
-----------
...
OPTIONS[[OPTIONS]]
------------------
...
OUTPUT
------
...
GIT
---
Part of the linkgit:git[1] suite
The most important pieces of this to note are the file header, underlined by =,
the NAME section, and the SYNOPSIS, which would normally contain the grammar if
your command took arguments. Try to use well-established manpage headers so your
documentation is consistent with other Git and UNIX manpages; this makes life
easier for your user, who can skip to the section they know contains the
information they need.
|
Note
|
Before trying to build the docs, make sure you have the package asciidoc
installed. |
Now that you’ve written your manpage, you’ll need to build it explicitly. We
convert your AsciiDoc to troff which is man-readable like so:
$ make all doc
$ man Documentation/git-psuh.1
$ make -C Documentation/ git-psuh.1
$ man Documentation/git-psuh.1
While this isn’t as satisfying as running through git help, you can at least
check that your help page looks right.
You can also check that the documentation coverage is good (that is, the project
sees that your command has been implemented as well as documented) by running
make check-docs from the top-level.
Go ahead and commit your new documentation change.
Adding Usage Text
Try and run ./bin-wrappers/git psuh -h. Your command should crash at the end.
That’s because -h is a special case which your command should handle by
printing usage.
Take a look at Documentation/technical/api-parse-options.txt. This is a handy
tool for pulling out options you need to be able to handle, and it takes a
usage string.
In order to use it, we’ll need to prepare a NULL-terminated array of usage
strings and a builtin_psuh_options array.
Add a line to #include "parse-options.h".
At global scope, add your array of usage strings:
static const char * const psuh_usage[] = {
N_("git psuh [<arg>...]"),
NULL,
};
Then, within your cmd_psuh() implementation, we can declare and populate our
option struct. Ours is pretty boring but you can add more to it if you want to
explore parse_options() in more detail:
struct option options[] = {
OPT_END()
};
Finally, before you print your args and prefix, add the call to
parse-options():
argc = parse_options(argc, argv, prefix, options, psuh_usage, 0);
This call will modify your argv parameter. It will strip the options you
specified in options from argv and the locations pointed to from options
entries will be updated. Be sure to replace your argc with the result from
parse_options(), or you will be confused if you try to parse argv later.
It’s worth noting the special argument --. As you may be aware, many Unix
commands use -- to indicate "end of named parameters" - all parameters after
the -- are interpreted merely as positional arguments. (This can be handy if
you want to pass as a parameter something which would usually be interpreted as
a flag.) parse_options() will terminate parsing when it reaches -- and give
you the rest of the options afterwards, untouched.
Now that you have a usage hint, you can teach Git how to show it in the general
command list shown by git help git or git help -a, which is generated from
command-list.txt. Find the line for git-pull so you can add your git-psuh
line above it in alphabetical order. Now, we can add some attributes about the
command which impacts where it shows up in the aforementioned help commands. The
top of command-list.txt shares some information about what each attribute
means; in those help pages, the commands are sorted according to these
attributes. git psuh is user-facing, or porcelain - so we will mark it as
"mainporcelain". For "mainporcelain" commands, the comments at the top of
command-list.txt indicate we can also optionally add an attribute from another
list; since git psuh shows some information about the user’s workspace but
doesn’t modify anything, let’s mark it as "info". Make sure to keep your
attributes in the same style as the rest of command-list.txt using spaces to
align and delineate them:
git-prune-packed plumbingmanipulators
git-psuh mainporcelain info
git-pull mainporcelain remote
git-push mainporcelain remote
Build again. Now, when you run with -h, you should see your usage printed and
your command terminated before anything else interesting happens. Great!
Go ahead and commit this one, too.