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