Today we are going to talk about git commands and how they are implemented. Don’t worry, I won’t get too technical – if only I have not studied Git sources (I do not speak C well enough for that anyway). It is enough for us to read the manpage of the git
command itself (which I did, along with some experimentation).
Try saying git --exec-path
in the terminal. On my system, the answer is /usr/lib/git-core
. Let’s see what’s there – ls /usr/lib/git-core
says this:
git git-add git-add--interactive git-am git-annotate git-apply git-archimport git-archive git-bisect git-bisect--helper git-blame git-branch git-bundle git-cat-file git-check-attr git-check-ignore git-check-mailmap git-checkout git-checkout-index git-check-ref-format git-cherry git-cherry-pick git-citool git-clean git-clone git-column git-commit git-commit-graph git-commit-tree git-config git-count-objects git-credential git-credential-cache git-credential-cache--daemon git-credential-gnome-keyring git-credential-libsecret git-credential-store git-cvsexportcommit git-cvsimport git-cvsserver git-daemon git-describe git-diff git-diff-files git-diff-index git-difftool git-difftool--helper git-diff-tree git-fast-export git-fast-import git-fetch git-fetch-pack git-filter-branch git-fmt-merge-msg git-for-each-ref git-format-patch git-fsck git-fsck-objects git-gc git-get-tar-commit-id git-grep git-gui git-gui--askpass git-hash-object git-help git-http-backend git-http-fetch git-http-push git-imap-send git-index-pack git-init git-init-db git-instaweb git-interpret-trailers git-legacy-stash git-log git-ls-files git-ls-remote git-ls-tree git-mailinfo git-mailsplit git-merge git-merge-base git-merge-file git-merge-index git-merge-octopus git-merge-one-file git-merge-ours git-merge-recursive git-merge-resolve git-merge-subtree git-mergetool git-mergetool--lib git-merge-tree git-mktag git-mktree git-multi-pack-index git-mv git-mw git-name-rev git-notes git-p4 git-pack-objects git-pack-redundant git-pack-refs git-parse-remote git-patch-id git-prune git-prune-packed git-pull git-push git-quiltimport git-range-diff git-read-tree git-rebase git-rebase--am git-rebase--common git-rebase--preserve-merges git-receive-pack git-reflog git-remote git-remote-ext git-remote-fd git-remote-ftp git-remote-ftps git-remote-http git-remote-https git-remote-mediawiki git-remote-testsvn git-repack git-replace git-request-pull git-rerere git-reset git-revert git-rev-list git-rev-parse git-rm git-send-email git-send-pack git-shell git-sh-i18n git-sh-i18n--envsubst git-shortlog git-show git-show-branch git-show-index git-show-ref git-sh-setup git-stage git-stash git-status git-stripspace git-submodule git-submodule--helper git-subtree git-svn git-symbolic-ref git-tag git-unpack-file git-unpack-objects git-update-index git-update-ref git-update-server-info git-upload-archive git-upload-pack git-var git-verify-commit git-verify-pack git-verify-tag git-web--browse git-whatchanged git-worktree git-write-tree mergetools
Wow. Just wow. This means that all git commands are in fact separate executables, put in this directory. Why is that important?
There are several reasons. One is that it is now clear why – to get help on committing – we need to say man git-commit
– we just want to get the manual page for the git-commit
command, which is invoked under the hood when we say git commit
.
Another one is that we can influence the process during which Git somehow translates git commit
to git-commit
by using Git aliases, but we can also do another thing (pretty useless, but cool):
echo "#!/bin/sh" > ~/bin/git-pwd echo "pwd" >> ~/bin/git-pwd chmod +x ~/bin/git-pwd
We now have a “Git command” git pwd
, which just displays the current directory.
Another thing we can do is this (assuming GNU coreutils):
$ find . -maxdepth 1 -executable -type f -exec file \{\} \; | cut -d: -f2 | cut -c2- | cut -d, -f1 | sort -u ELF 64-bit LSB pie executable Perl script text executable POSIX shell script Python script
As you can see, Git is not written entirely in C – actually, various commands use various languages, and we have shell scripts, Perl and Python code here!
And now for the most important takeaway from this post. We can now better understand the distinction between git options and git command options. For instance, Git has the (very useful in scripts) -C
option (which peforms a cd
before executing the command proper), which is completely different from e.g. the -C
option for git commit
(which reuses a message of another commit).
By the way, you can probably guess now what git -C /tmp pwd
will display, right?
But wait, there’s more! What would happen if we defined an executable script called git-commit
in ~/bin
? Well, nothing. How do I know that? Remember the git --exec-path
command? In case you are wondering how exactly Git finds the script to run, try putting this:
#!/bin/sh echo $PATH
somewhere in your $PATH
as, say, git-show-path
, and say git show-path
in your terminal. See? Nothing magical here, just the shell.
To sum it up: I’m not sure how useful all this is (maybe apart from git -C ...
, which is really handy in scripts), but it’s definitely interesting to know. Let me know if you find some actual use for all this knowledge!