GIT - the stupid content tracker
– Linus Torvalds, Initial revision of “git”, the information manager from hell
After years of code review with stacked diffs1, I’ve been using GitLab merge requests at work.
Merge requests frustrated me until helpful folks pointed me toward GerritLab, a small Python tool for making stacked merge requests in GitLab—exactly what I was looking for.
But to talk to GitLab, GerritLab required a cleartext token in my
~/.gitconfig
. I wanted to stow my token in a password
vault, so I crafted a change for GerritLab that used gitcredentials(7).
Like most git features, git credentials
are obscure,
byzantine, and incredibly useful. It works like this:
import subprocess, json
= """\
INPUT protocol=https
host=example.com
username=thcipriani
"""
= subprocess.run(
git_credentials_fill "git", "credential", "fill"],
[input=INPUT,
=True,
text=subprocess.PIPE,
stdout
)
= {
git_credentials for line in git_credentials_fill.stdout.splitlines()
key: value if '=' in line
for key, value in [line.split('=', 1)]
}
print(json.dumps(git_credentials, indent=4))
Which looks like this when you run it:
$ ./example-git-creds.py
Password for 'https://thcipriani@example.com':
{
"protocol": "https",
"host": "example.com",
"username": "thcipriani",
"password": "hunter2"
}
The magic here is the shell command
git credentials fill
, which:
- Accepts a protocol, username, and host on standard input.
- Delegates to a “git credential helper” (
git-credential-libsecret
in my case). A credential helper is an executable that retrieves passwords from the OS or another program that provides secure storage. - My git credential helper checks for credentials matching
https://thcipriani@example.com
and finds none. - Since my credential helper comes up empty, git prompts me for my password.
- Git sends
<key>=<value>\n
pairs to standard output for each of the keysprotocol
,host
,username
, andpassword
.
To stow the password for later, I can use
git credential approve
.
subprocess.run("git", "credential", "approve"],
[input=git_credentials_fill.stdout,
=True
text )
If I do that, the next time I run the script, git finds the password without prompting:
$ ./example-git-creds.py
{
"protocol": "https",
"host": "example.com",
"username": "thcipriani",
"password": "hunter2"
}
Git credential’s purpose
The problem git credentials solve is this:
- With git over ssh, you use your keys.
- With git over https, you type a password. Over and over and over.
Beleaguered git maintainers solved this dilemma with the credential storage system—git credentials.
With the right configuration, git will stop asking for your password when you push to an https remote.
Instead, git credentials retrieve and send auth info to remotes.
The maze of options
My mind initially refused to learn git credentials due to its twisty little maze of terms that all sound alike:
git credential fill
: how you invoke a user’s configured git credential helpergit credential approve
: how you save git credentials (if this is supported by the user’s git credential helper)git credential.helper
: the git config that points to a script that poops out usernames and passwords. These helper scripts are often namedgit-credential-<something>
.git-credential-cache
: a specific, built-in git credential helper that caches credentials in memory for a while.git-credential-store
: STOP. DON’T TOUCH. This is a specific, built-in git credential helper that stores credentials in cleartext in your home directory. Whomp whomp.git-credential-manager
: a specific and confusingly named git credential helper from Microsoft®. If you’re on Linux or Mac, feel free to ignore it.
But once I mapped the terms, I only needed to pick a git credential helper.
Configuring good credential helpers
The built-in git-credential-store
is a bad credential
helper—it saves your passwords in cleartext in
~/.git-credentials
.2
If you’re on a Mac, you’re in luck3—one command points git credentials to your keychain:
git config --global credential.helper osxkeychain
Third-party developers have contributed helpers for popular password stores:
- 1Password
- pass: the standard Unix password manager
- OAuth
- Git’s documentation contains a list of credential-helpers, too
Meanwhile, Linux and Windows have standard options. Git’s source repo includes helpers for these options in the contrib directory.
On Linux, you can use libsecret. Here’s how I configured it on Debian:
sudo apt install libsecret-1-0 libsecret-1-dev
cd /usr/share/doc/git/contrib/credential/libsecret/
sudo make
sudo mv git-credential-libsecret /usr/local/bin/
git config --global credential.helper libsecret
On Windows, you can use the confusingly named git credential manager. I have no idea how to do this, and I refuse to learn.
Now, if you clone a repo over https, you can push over https without pain4. Plus, now you have a handy password library for shell scripts:
#!/usr/bin/env bash
input="\
protocol=https
host=example.com
user=thcipriani
"
eval "$(echo "$input" | git credential fill)"
echo "The password is: $password"
“stacked diffs” or “stacked pull-requests”—there’s no universal term.↩︎
git-credential-store is not a git credential helper of honor. No highly-esteemed passwords should be stored with it. This message is a warning about danger. The danger is still present, in your time, as it was in ours.↩︎
I think. I only have Linux computers to test this on, sorry
;_;
↩︎Or the config option
pushInsteadOf
, which is what I actually do.↩︎