Initial commit
This commit is contained in:
commit
503575bd9f
|
@ -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'
|
||||||
|
```
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue