fastgit 0.1.2__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastgit
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Use git from python, fast
5
5
  Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
6
6
  License: Apache-2.0
@@ -4,6 +4,6 @@
4
4
 
5
5
  It is designed for interactive use and automation alike: errors print tersely by default (like git itself) or raise on request, exit codes that mean "no" rather than "failed" are returned normally, and passing `sync=False` gives an async client for servers and concurrent code."""
6
6
 
7
- __version__ = "0.1.2"
7
+ __version__ = "0.1.3"
8
8
 
9
9
  from .core import *
@@ -20,8 +20,12 @@ d = { 'settings': { 'branch': 'main',
20
20
  'fastgit.core.Git.top': ('core.html#git.top', 'fastgit/core.py'),
21
21
  'fastgit.core.GitRes': ('core.html#gitres', 'fastgit/core.py'),
22
22
  'fastgit.core.GitRes.__new__': ('core.html#gitres.__new__', 'fastgit/core.py'),
23
+ 'fastgit.core._arunlocal': ('core.html#_arunlocal', 'fastgit/core.py'),
24
+ 'fastgit.core._depr_uname': ('core.html#_depr_uname', 'fastgit/core.py'),
23
25
  'fastgit.core._gitcmd': ('core.html#_gitcmd', 'fastgit/core.py'),
24
26
  'fastgit.core._gitres': ('core.html#_gitres', 'fastgit/core.py'),
27
+ 'fastgit.core._longopts': ('core.html#_longopts', 'fastgit/core.py'),
28
+ 'fastgit.core._runlocal': ('core.html#_runlocal', 'fastgit/core.py'),
25
29
  'fastgit.core.acallgit': ('core.html#acallgit', 'fastgit/core.py'),
26
30
  'fastgit.core.callgit': ('core.html#callgit', 'fastgit/core.py'),
27
31
  'fastgit.core.get_top': ('core.html#get_top', 'fastgit/core.py')}}}
@@ -12,6 +12,7 @@ from fastcore.utils import *
12
12
  from fastcore.aio import then
13
13
  import subprocess,asyncio
14
14
  from subprocess import CalledProcessError
15
+ from functools import cache
15
16
 
16
17
  # %% ../nbs/00_core.ipynb #880c6a46
17
18
  _ok1 = {'merge-tree','merge-base','grep','check-ignore','diff','diff-index','diff-files','diff-tree','show-ref','config','rev-parse'}
@@ -24,53 +25,75 @@ class GitRes(str):
24
25
  return self
25
26
 
26
27
  # %% ../nbs/00_core.ipynb #e4c0a290
27
- def _gitcmd(path, *args, uname=None, pre=None):
28
- assert not (uname and pre), "Pass `uname` or `pre`, not both"
29
- if uname:
30
- warn("`uname` is deprecated; pass e.g `pre=['/usr/bin/sudo','-u',uname]` instead", DeprecationWarning, stacklevel=3)
31
- pre = ['/usr/bin/sudo', '-u', uname]
28
+ def _gitcmd(path, *args, pre=None):
32
29
  cmd = ['git', '-C', str(Path(path).expanduser().resolve())] + list(args)
33
30
  return [*pre, *cmd] if pre else cmd
34
31
 
35
- def _gitres(cmd, args, out, err, returncode, ok_exit):
32
+ def _depr_uname(uname, pre):
33
+ assert not (uname and pre), "Pass `uname` or `pre`, not both"
34
+ if not uname: return pre
35
+ warn("`uname` is deprecated; pass e.g `pre=['/usr/bin/sudo','-u',uname]` instead", DeprecationWarning, stacklevel=4)
36
+ return ['/usr/bin/sudo', '-u', uname]
37
+
38
+ def _gitres(args, out, err, returncode, ok_exit):
36
39
  if ok_exit is None: ok_exit = 1 if args and args[0] in _ok1 else 0
37
- if returncode not in (0, *listify(ok_exit)): raise CalledProcessError(returncode, cmd, out, err)
40
+ if returncode not in (0, *listify(ok_exit)): raise CalledProcessError(returncode, ['git', *args], out, err)
38
41
  return GitRes((out + err).strip(), returncode)
39
42
 
40
- def callgit(path, *args, uname=None, pre=None, ok_exit=None):
41
- "Run git in `path`, returning stripped stdout+stderr as a `GitRes`; `ok_exit` adds accepted exit codes (default: 1 for `_ok1` commands)"
42
- cmd = _gitcmd(path, *args, uname=uname, pre=pre)
43
- r = subprocess.run(cmd, capture_output=True, text=True)
44
- return _gitres(cmd, args, r.stdout, r.stderr, r.returncode, ok_exit)
43
+ def _runlocal(path, args, pre=None):
44
+ "Default runner: compose the local argv and run it in a subprocess"
45
+ r = subprocess.run(_gitcmd(path, *args, pre=pre), capture_output=True, text=True)
46
+ return r.stdout, r.stderr, r.returncode
47
+
48
+ def callgit(
49
+ path, # Directory to run git in
50
+ *args, # git subcommand and arguments
51
+ uname=None, # Deprecated; use `pre`
52
+ pre=None, # argv prefix, e.g. a sudo wrapper
53
+ ok_exit=None, # Extra accepted exit codes (default: 1 for `_ok1` commands)
54
+ runner=None, # Replaces the local subprocess: `(path, args, pre) -> (stdout, stderr, returncode)`
55
+ ):
56
+ "Run git in `path`, returning stripped stdout+stderr as a `GitRes`"
57
+ out,err,rc = (runner or _runlocal)(path, list(args), pre=_depr_uname(uname, pre))
58
+ return _gitres(args, out, err, rc, ok_exit)
45
59
 
46
60
  # %% ../nbs/00_core.ipynb #4c0df6c6
47
- def get_top(folder):
48
- try: return callgit(folder, 'rev-parse', '--show-toplevel')
61
+ def get_top(folder, runner=None):
62
+ try: return callgit(folder, 'rev-parse', '--show-toplevel', runner=runner)
49
63
  except CalledProcessError: return None
50
64
 
65
+ # %% ../nbs/00_core.ipynb #2c57b813
66
+ @cache
67
+ def _longopts(cmd):
68
+ "Long options `git <cmd>` lists for completion; empty if unavailable"
69
+ try: r = subprocess.run(['git', cmd, '--git-completion-helper'], capture_output=True, text=True)
70
+ except OSError: return set()
71
+ return {o.rstrip('=') for o in r.stdout.split()} if r.returncode==0 else set()
72
+
51
73
  # %% ../nbs/00_core.ipynb #97f78839
52
74
  class Git:
53
75
  "Run git commands in dir `d`; `sync=False` makes every command return an awaitable"
54
- def __init__(self, d, pre=None, raise_exc=False, sync=True): self.d,self.pre,self.raise_exc,self._sync = Path(d).expanduser(),pre,raise_exc,sync
76
+ def __init__(self, d, pre=None, raise_exc=False, sync=True, runner=None): self.d,self.pre,self.raise_exc,self._sync,self.runner = Path(d).expanduser(),pre,raise_exc,sync,runner
55
77
 
56
78
  def __call__(self, cmd, *args, mute_errors=False, raise_exc=None, ok_exit=None, **kwargs):
57
79
  paths = [str(p) for p in listify(kwargs.pop('__', None) or [])]
58
80
  args = listify(args)
59
81
  kwargs = {k:v for k,v in kwargs.items() if v is not False}
60
- args += concat((f'-{k}',str(v)) for k,v in kwargs.items() if len(k)==1 and v is not True)
61
- args += [f'-{k}' for k,v in kwargs.items() if len(k)==1 and v is True]
62
- args += [f'--{k.replace("_","-")}={v}' for k,v in kwargs.items() if len(k)>1 and v is not True]
63
- args += [f'--{k.replace("_","-")}' for k,v in kwargs.items() if len(k)>1 and v is True]
82
+ def _short(k): return len(k)==1 or (len(k)<=3 and k.isalnum() and (o:=_longopts(cmd)) and f'--{k}' not in o)
83
+ args += concat((f'-{k}',str(v)) for k,v in kwargs.items() if _short(k) and v is not True)
84
+ args += [f'-{k}' for k,v in kwargs.items() if _short(k) and v is True]
85
+ args += [f'--{k.replace("_","-")}={v}' for k,v in kwargs.items() if not _short(k) and v is not True]
86
+ args += [f'--{k.replace("_","-")}' for k,v in kwargs.items() if not _short(k) and v is True]
64
87
  if paths: args += ['--'] + paths
65
88
  f = self._run if self._sync else self._arun
66
89
  return f(cmd, args, mute_errors, ifnone(raise_exc, self.raise_exc), ok_exit)
67
90
 
68
91
  def _run(self, cmd, args, mute_errors, raise_exc, ok_exit):
69
- try: return callgit(self.d, cmd, *args, pre=self.pre, ok_exit=ok_exit)
92
+ try: return callgit(self.d, cmd, *args, pre=self.pre, ok_exit=ok_exit, runner=self.runner)
70
93
  except CalledProcessError as e: self._err(e, cmd, args, mute_errors, raise_exc)
71
94
 
72
95
  async def _arun(self, cmd, args, mute_errors, raise_exc, ok_exit):
73
- try: return await acallgit(self.d, cmd, *args, pre=self.pre, ok_exit=ok_exit)
96
+ try: return await acallgit(self.d, cmd, *args, pre=self.pre, ok_exit=ok_exit, runner=self.runner)
74
97
  except CalledProcessError as e: self._err(e, cmd, args, mute_errors, raise_exc)
75
98
 
76
99
  def _err(self, e, cmd, args, mute_errors, raise_exc):
@@ -101,9 +124,21 @@ def commits(self:Git): return then(self.log('--oneline', mute_errors=True, raise
101
124
  def current_branch(self:Git): return self.branch('--show-current')
102
125
 
103
126
  # %% ../nbs/00_core.ipynb #b69ecf1c
104
- async def acallgit(path, *args, uname=None, pre=None, ok_exit=None):
105
- "Async `callgit`: identical contract, run in an `asyncio` subprocess"
106
- cmd = _gitcmd(path, *args, uname=uname, pre=pre)
127
+ async def _arunlocal(path, args, pre=None):
128
+ "Async `_runlocal`"
129
+ cmd = _gitcmd(path, *args, pre=pre)
107
130
  p = await asyncio.create_subprocess_exec(*cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
108
131
  out,err = await p.communicate()
109
- return _gitres(cmd, args, out.decode(), err.decode(), p.returncode, ok_exit)
132
+ return out.decode(), err.decode(), p.returncode
133
+
134
+ async def acallgit(
135
+ path, # Directory to run git in
136
+ *args, # git subcommand and arguments
137
+ uname=None, # Deprecated; use `pre`
138
+ pre=None, # argv prefix, e.g. a sudo wrapper
139
+ ok_exit=None, # Extra accepted exit codes (default: 1 for `_ok1` commands)
140
+ runner=None, # Async runner replacing the local subprocess: `async (path, args, pre) -> (stdout, stderr, returncode)`
141
+ ):
142
+ "Async `callgit`: identical contract, run in an `asyncio` subprocess"
143
+ out,err,rc = await (runner or _arunlocal)(path, list(args), pre=_depr_uname(uname, pre))
144
+ return _gitres(args, out, err, rc, ok_exit)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastgit
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Use git from python, fast
5
5
  Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
6
6
  License: Apache-2.0
File without changes
File without changes
File without changes
File without changes
File without changes