Initial commit

This commit is contained in:
Olivier 2018-04-14 22:12:21 +00:00
commit 503575bd9f
3 changed files with 285 additions and 0 deletions

110
README.md Normal file
View File

@ -0,0 +1,110 @@
# git-me
`git me` is a very simple identity management script for the popular distributed version control system `git`.
It is written in Python (3.6+) which is easy to install on Linux distributions, and often already installed.
## Motivations
When you have multiple online identities, it can get a bit confusing with `git` as you either have to remember to
specify your identity for every repo or use a global configuration and end up leaking the wrong identity
to the wrong repository.
With `git-me` fully installed, you will not be able to use identity-leaking commands such as `git commit`
unless you have an identity specified **in that repository's local `.git/config` file**.
Of course, `git-me` provides a quick way to configure that identity from a list of presets that you provide,
either in a TSV file or by using `git me add`.
#### Side-note: git and SSH for multiple accounts
(Side-note:) In order to use multiple GitLab/GitHub accounts with different SSH keys, you can use a `ssh-config` like the
following:
```
Host acc1.gitlab
HostName gitlab.com
RSAAuthentication Yes
IdentityFile ~/.ssh/acc1_rsa
Host o42.gitlab
HostName gitlab.com
RSAAuthentication Yes
IdentityFile ~/.ssh/oliichi42_rsa
Host gitlab.com
HostName specify.a.gitlab.account
```
## Usage
`git me` is designed to be simple and intuitive. See some of the commands here:
### Managing identities
Identities will be stored in `~/.config/git-me` by default, but the `GIT_ME_STORE` environment variable can
be used to change that. The identity store file is a TSV (tab-seperated value) file.
#### To add an identity:
`git me add <slug> "<name>" <e-mail address>`
Note: a `slug` is an easy-to-type specifier that you choose; it must be unique.
Example:
`git me add personal "Joseph Watson" j.watson@example.com`
#### To list your identities:
`git me ls` will list your identities in a (currently skewed) table.
Example:
```
joe@jwpc:~$ git me ls
git-me identity store: /home/joe/.config/git-me
# Slug Name E-mail Address
personal Joseph Watson j.watson@example.com
```
#### To remove an identity:
`git me rm <slug>` will delete the identity with that slug.
Example:
`git me rm personal`
### Using an identity
`git me use <slug>` will configure the current git repo to use the identity with that slug.
Example:
```
joe@jwpc:~/gitrepo$ git config -l --local
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
joe@jwpc:~/gitrepo$ git me use personal
git-me identity store: /home/joe/.config/git-me
joe@jwpc:~/gitrepo$ git config -l --local
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
user.name=Joseph Watson
user.email=j.watson@example.com
```
### Checking that your identity is being used
You can use the command `git me assert` to assert that you have an identity set for the local repository.
It will warn you and fail if you do not.
The real power of this command comes from using the included `git-checked.py` wrapper as it will
automatically assert that you have a set identity BEFORE you commit.
You can use it by adding e.g. a bash alias.
```bash
alias git='python3.6 /path/to/git-checked.py'
```

10
git-checked.py Normal file
View File

@ -0,0 +1,10 @@
import sys, subprocess, os
def eprint(*all):
print(*all, file=sys.stderr)
if len(sys.argv) >= 2 and sys.argv[1] in ['commit', 'ci']:
if os.system('git me assert') > 1:
eprint("git-checked: Cancelling git action; fix git me issues first!")
exit(1)
subprocess.run(['git'] + sys.argv[1:], stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin, env=os.environ)

165
git-me.py Executable file
View File

@ -0,0 +1,165 @@
#!/usr/bin/env python3.6
import os, argparse, sys, csv, subprocess
def eprint(*all):
print(*all, file=sys.stderr)
STORAGE_LOC = os.environ['GIT_ME_STORE'] if 'GIT_ME_STORE' in os.environ else os.environ['HOME'] + '/.config/git-me'
eprint("git-me identity store: " + STORAGE_LOC)
STCOL_LEN = 3
STCOL_SLUG = 0
STCOL_NAME = 1
STCOL_EMAIL = 2
def add(args):
slug = args.slug.lower()
name = args.name
email = args.email
try:
with open(STORAGE_LOC, 'r') as fstore:
tsvin = csv.reader(fstore, delimiter='\t')
for row in tsvin:
if len(row) < STCOL_LEN:
continue
if row[STCOL_SLUG].lower() == slug:
eprint("An identity with that slug already exists!")
eprint(f" Slug: {row[STCOL_SLUG]}")
eprint(f" Name: {row[STCOL_NAME]}")
eprint(f" E-mail: {row[STCOL_EMAIL]}")
return False
except FileNotFoundError:
pass
# now add it
with open(STORAGE_LOC, 'a+') as fstore:
fstore.write(f"\n{slug}\t{name}\t{email}\n")
return True
def list_idents(args):
print("# Slug\t\tName\t\tE-mail Address")
with open(STORAGE_LOC, 'r') as fstore:
tsvin = csv.reader(fstore, delimiter='\t')
for row in tsvin:
if len(row) < STCOL_LEN:
continue
print(f"{row[STCOL_SLUG]}\t\t{row[STCOL_NAME]}\t\t{row[STCOL_EMAIL]}")
return True
def assert_hook(args):
result_name = subprocess.run(['git', 'config', '--local', '--get', 'user.name'], stdout=subprocess.PIPE)
so_name = result_name.stdout.decode().strip()
result_email = subprocess.run(['git', 'config', '--local', '--get', 'user.email'], stdout=subprocess.PIPE)
so_email = result_email.stdout.decode().strip()
if result_email.returncode + result_name.returncode > 0:
if result_name.returncode > 0:
eprint("git-me: No user.name specified!")
else:
eprint(f"git-me: user.name OK = {so_name}")
if result_email.returncode > 0:
eprint("git-me: No user.email specified!")
else:
eprint(f"git-me: user.email OK = {so_email}")
eprint("git-me: Either specify a config manually or use `git me use <id>`.")
return False
else:
eprint(f"git-me: OK. (user.name = {so_name}, user.email = {so_email})")
return True
def remove(args):
slug = args.idslug
os.rename(STORAGE_LOC, STORAGE_LOC + "~tmp")
with open(STORAGE_LOC + "~tmp", 'r') as tsvin, open(STORAGE_LOC, 'w') as tsvout:
tsvin = csv.reader(tsvin, delimiter='\t')
tsvout = csv.writer(tsvout, delimiter='\t')
for row in tsvin:
if len(row) < STCOL_LEN:
continue
if row[STCOL_SLUG].lower() == slug:
# delete; ignore
pass
else:
tsvout.writerow(row)
os.unlink(STORAGE_LOC + "~tmp")
return True
def switch_identity(args):
slug = args.idslug
with open(STORAGE_LOC, 'r') as fstore:
tsvin = csv.reader(fstore, delimiter='\t')
for row in tsvin:
if len(row) < STCOL_LEN:
continue
if row[STCOL_SLUG].lower() == slug:
proc_name = subprocess.run(["git", "config", "--local", "user.name", row[STCOL_NAME]])
if proc_name.returncode != 0:
eprint("git-me: Failed to set user.name! (non-zero exit code. Are you in a git repo?)")
return False
else:
proc_email = subprocess.run(["git", "config", "--local", "user.email", row[STCOL_EMAIL]])
if proc_name.returncode != 0:
eprint("git-me: Failed to set user.email! (non-zero exit code.)")
return False
return True
else:
eprint(f"git-me: Unable to find identity with {slug}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Manages repository identities. Environment variable GIT_ME_STORE can be used to override location of identity store.',
)
subparser = parser.add_subparsers(help='sub-command help', dest='cmd')
subparser.required = True
parser_assert = subparser.add_parser('assert', aliases=['check', 'hook', 'ck'])
parser_assert.set_defaults(func=assert_hook)
parser_add = subparser.add_parser('add', aliases=['mk'])
parser_add.add_argument('slug', type=str, help='A short, easy-to-type code to represent this id')
parser_add.add_argument('name', type=str, help='Your display name')
parser_add.add_argument('email', type=str, help='Your e-mail address')
parser_add.set_defaults(func=add)
parser_list = subparser.add_parser('list', aliases=['ls'])
parser_list.set_defaults(func=list_idents)
parser_remove = subparser.add_parser('remove', aliases=['rm'])
parser_remove.add_argument('idslug', type=str, help='The slug of the identity to remove.')
parser_remove.set_defaults(func=remove)
parser_switch = subparser.add_parser('use', aliases=['switch', 'choose', 'enable'])
parser_switch.add_argument('idslug', type=str, help='The slug of the identity to enable.')
parser_switch.set_defaults(func=switch_identity)
args = parser.parse_args(sys.argv[1:])
if 'func' in args:
if args.func(args):
exit(0)
else:
exit(1)
else:
print("git-me: Please specify a command!", file=sys.stderr)
exit(2)