summaryrefslogtreecommitdiff
path: root/git.py
diff options
context:
space:
mode:
authorAlberto Bertogli <albertito@blitiri.com.ar>2020-05-24 16:01:04 +0200
committerAlberto Bertogli <albertito@blitiri.com.ar>2020-05-24 17:04:04 +0200
commitad950208bf19d49e8f825007ba267ca3c43b8390 (patch)
tree92ccad075ac07e9fdc6a1b2fd26b28001fd62a36 /git.py
parent1183d6f817046a9f2b82a8d61b56046f046afb3f (diff)
downloadgit-arr-fork-ad950208bf19d49e8f825007ba267ca3c43b8390.zip
Auto-format the code with black
This patch applies auto-formatting of the source code using black (https://github.com/psf/black). This makes the code style more uniform and simplifies editing. Note I also tried yapf, and IMO produced nicer output and handled some corner cases much better, but unfortunately it doesn't yet support type annotations, which will be introduced in later commits. So in the future we might switch to yapf instead.
Diffstat (limited to 'git.py')
-rw-r--r--git.py258
1 files changed, 146 insertions, 112 deletions
diff --git a/git.py b/git.py
index 09ccd37..3b2b5b7 100644
--- a/git.py
+++ b/git.py
@@ -19,37 +19,45 @@ from html import escape
# Path to the git binary.
GIT_BIN = "git"
-def run_git(repo_path, params, stdin = None, silent_stderr = False, raw = False):
+
+def run_git(repo_path, params, stdin=None, silent_stderr=False, raw=False):
"""Invokes git with the given parameters.
This function invokes git with the given parameters, and returns a
file-like object with the output (from a pipe).
"""
- params = [GIT_BIN, '--git-dir=%s' % repo_path] + list(params)
+ params = [GIT_BIN, "--git-dir=%s" % repo_path] + list(params)
stderr = None
if silent_stderr:
stderr = subprocess.PIPE
if not stdin:
- p = subprocess.Popen(params,
- stdin = None, stdout = subprocess.PIPE, stderr = stderr)
+ p = subprocess.Popen(
+ params, stdin=None, stdout=subprocess.PIPE, stderr=stderr
+ )
else:
- p = subprocess.Popen(params,
- stdin = subprocess.PIPE, stdout = subprocess.PIPE,
- stderr = stderr)
+ p = subprocess.Popen(
+ params,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=stderr,
+ )
+
p.stdin.write(stdin)
p.stdin.close()
if raw:
return p.stdout
- return io.TextIOWrapper(p.stdout, encoding = 'utf8',
- errors = 'backslashreplace')
+ return io.TextIOWrapper(
+ p.stdout, encoding="utf8", errors="backslashreplace"
+ )
-class GitCommand (object):
+class GitCommand(object):
"""Convenient way of invoking git."""
+
def __init__(self, path, cmd, *args, **kwargs):
self._override = True
self._path = path
@@ -63,10 +71,10 @@ class GitCommand (object):
self.__setattr__(k, v)
def __setattr__(self, k, v):
- if k == '_override' or self._override:
+ if k == "_override" or self._override:
self.__dict__[k] = v
return
- k = k.replace('_', '-')
+ k = k.replace("_", "-")
self._kwargs[k] = v
def arg(self, a):
@@ -92,19 +100,20 @@ class GitCommand (object):
params = [self._cmd]
for k, v in list(self._kwargs.items()):
- dash = '--' if len(k) > 1 else '-'
+ dash = "--" if len(k) > 1 else "-"
if v is None:
- params.append('%s%s' % (dash, k))
+ params.append("%s%s" % (dash, k))
else:
- params.append('%s%s=%s' % (dash, k, str(v)))
+ params.append("%s%s=%s" % (dash, k, str(v)))
params.extend(self._args)
- return run_git(self._path, params, self._stdin_buf, raw = self._raw)
+ return run_git(self._path, params, self._stdin_buf, raw=self._raw)
-class SimpleNamespace (object):
+class SimpleNamespace(object):
"""An entirely flexible object, which provides a convenient namespace."""
+
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
@@ -120,14 +129,15 @@ class smstr:
readable.
.html -> an HTML-embeddable representation.
"""
+
def __init__(self, raw):
if not isinstance(raw, (str, bytes)):
raise TypeError(
- "The raw string must be instance of 'str', not %s" %
- type(raw))
+ "The raw string must be instance of 'str', not %s" % type(raw)
+ )
self.raw = raw
if isinstance(raw, bytes):
- self.unicode = raw.decode('utf8', errors = 'backslashreplace')
+ self.unicode = raw.decode("utf8", errors="backslashreplace")
else:
self.unicode = raw
self.url = urllib.request.pathname2url(raw)
@@ -147,7 +157,7 @@ class smstr:
def split(self, sep):
"""Like str.split()."""
- return [ smstr(s) for s in self.raw.split(sep) ]
+ return [smstr(s) for s in self.raw.split(sep)]
def __add__(self, other):
if isinstance(other, smstr):
@@ -156,9 +166,9 @@ class smstr:
def _to_html(self):
"""Returns an html representation of the unicode string."""
- html = ''
+ html = ""
for c in escape(self.unicode):
- if c in '\t\r\n\r\f\a\b\v\0':
+ if c in "\t\r\n\r\f\a\b\v\0":
esc_c = c.encode("unicode-escape").decode("utf8")
html += '<span class="ctrlchr">%s</span>' % esc_c
else:
@@ -186,7 +196,7 @@ def unquote(s):
s = s.encode("latin1").decode("unicode-escape")
# Convert to utf8.
- s = s.encode("latin1").decode("utf8", errors='backslashreplace')
+ s = s.encode("latin1").decode("utf8", errors="backslashreplace")
return s
@@ -194,7 +204,7 @@ def unquote(s):
class Repo:
"""A git repository."""
- def __init__(self, path, name = None, info = None):
+ def __init__(self, path, name=None, info=None):
self.path = path
self.name = name
self.info = info or SimpleNamespace()
@@ -203,9 +213,9 @@ class Repo:
"""Returns a GitCommand() on our path."""
return GitCommand(self.path, cmd)
- def for_each_ref(self, pattern = None, sort = None, count = None):
+ def for_each_ref(self, pattern=None, sort=None, count=None):
"""Returns a list of references."""
- cmd = self.cmd('for-each-ref')
+ cmd = self.cmd("for-each-ref")
if sort:
cmd.sort = sort
if count:
@@ -217,61 +227,61 @@ class Repo:
obj_id, obj_type, ref = l.split()
yield obj_id, obj_type, ref
- def branches(self, sort = '-authordate'):
+ def branches(self, sort="-authordate"):
"""Get the (name, obj_id) of the branches."""
- refs = self.for_each_ref(pattern = 'refs/heads/', sort = sort)
+ refs = self.for_each_ref(pattern="refs/heads/", sort=sort)
for obj_id, _, ref in refs:
- yield ref[len('refs/heads/'):], obj_id
+ yield ref[len("refs/heads/") :], obj_id
def branch_names(self):
"""Get the names of the branches."""
- return ( name for name, _ in self.branches() )
+ return (name for name, _ in self.branches())
- def tags(self, sort = '-taggerdate'):
+ def tags(self, sort="-taggerdate"):
"""Get the (name, obj_id) of the tags."""
- refs = self.for_each_ref(pattern = 'refs/tags/', sort = sort)
+ refs = self.for_each_ref(pattern="refs/tags/", sort=sort)
for obj_id, _, ref in refs:
- yield ref[len('refs/tags/'):], obj_id
+ yield ref[len("refs/tags/") :], obj_id
def tag_names(self):
"""Get the names of the tags."""
- return ( name for name, _ in self.tags() )
+ return (name for name, _ in self.tags())
- def commit_ids(self, ref, limit = None):
+ def commit_ids(self, ref, limit=None):
"""Generate commit ids."""
- cmd = self.cmd('rev-list')
+ cmd = self.cmd("rev-list")
if limit:
cmd.max_count = limit
cmd.arg(ref)
- cmd.arg('--')
+ cmd.arg("--")
for l in cmd.run():
- yield l.rstrip('\n')
+ yield l.rstrip("\n")
def commit(self, commit_id):
"""Return a single commit."""
- cs = list(self.commits(commit_id, limit = 1))
+ cs = list(self.commits(commit_id, limit=1))
if len(cs) != 1:
return None
return cs[0]
- def commits(self, ref, limit = None, offset = 0):
+ def commits(self, ref, limit=None, offset=0):
"""Generate commit objects for the ref."""
- cmd = self.cmd('rev-list')
+ cmd = self.cmd("rev-list")
if limit:
cmd.max_count = limit + offset
cmd.header = None
cmd.arg(ref)
- cmd.arg('--')
+ cmd.arg("--")
- info_buffer = ''
+ info_buffer = ""
count = 0
for l in cmd.run():
- if '\0' in l:
- pre, post = l.split('\0', 1)
+ if "\0" in l:
+ pre, post = l.split("\0", 1)
info_buffer += pre
count += 1
@@ -290,11 +300,11 @@ class Repo:
def diff(self, ref):
"""Return a Diff object for the ref."""
- cmd = self.cmd('diff-tree')
+ cmd = self.cmd("diff-tree")
cmd.patch = None
cmd.numstat = None
cmd.find_renames = None
- if (self.info.root_diff):
+ if self.info.root_diff:
cmd.root = None
# Note we intentionally do not use -z, as the filename is just for
# reference, and it is safer to let git do the escaping.
@@ -305,13 +315,13 @@ class Repo:
def refs(self):
"""Return a dict of obj_id -> ref."""
- cmd = self.cmd('show-ref')
+ cmd = self.cmd("show-ref")
cmd.dereference = None
r = defaultdict(list)
for l in cmd.run():
l = l.strip()
- obj_id, ref = l.split(' ', 1)
+ obj_id, ref = l.split(" ", 1)
r[obj_id].append(ref)
return r
@@ -322,9 +332,9 @@ class Repo:
def blob(self, path, ref):
"""Returns a Blob instance for the given path."""
- cmd = self.cmd('cat-file')
+ cmd = self.cmd("cat-file")
cmd.raw(True)
- cmd.batch = '%(objectsize)'
+ cmd.batch = "%(objectsize)"
# Format: <ref>:<path>
# Construct it in binary since the path might not be utf8.
@@ -332,29 +342,39 @@ class Repo:
out = cmd.run()
head = out.readline()
- if not head or head.strip().endswith(b'missing'):
+ if not head or head.strip().endswith(b"missing"):
return None
- return Blob(out.read()[:int(head)])
+ return Blob(out.read()[: int(head)])
def last_commit_timestamp(self):
"""Return the timestamp of the last commit."""
- refs = self.for_each_ref(pattern = 'refs/heads/',
- sort = '-committerdate', count = 1)
+ refs = self.for_each_ref(
+ pattern="refs/heads/", sort="-committerdate", count=1
+ )
for obj_id, _, _ in refs:
commit = self.commit(obj_id)
return commit.committer_epoch
return -1
-class Commit (object):
+class Commit(object):
"""A git commit."""
- def __init__(self, repo,
- commit_id, parents, tree,
- author, author_epoch, author_tz,
- committer, committer_epoch, committer_tz,
- message):
+ def __init__(
+ self,
+ repo,
+ commit_id,
+ parents,
+ tree,
+ author,
+ author_epoch,
+ author_tz,
+ committer,
+ committer_epoch,
+ committer_tz,
+ message,
+ ):
self._repo = repo
self.id = commit_id
self.parents = parents
@@ -367,28 +387,30 @@ class Commit (object):
self.committer_tz = committer_tz
self.message = message
- self.author_name, self.author_email = \
- email.utils.parseaddr(self.author)
+ self.author_name, self.author_email = email.utils.parseaddr(
+ self.author
+ )
- self.committer_name, self.committer_email = \
- email.utils.parseaddr(self.committer)
+ self.committer_name, self.committer_email = email.utils.parseaddr(
+ self.committer
+ )
- self.subject, self.body = self.message.split('\n', 1)
+ self.subject, self.body = self.message.split("\n", 1)
self.author_date = Date(self.author_epoch, self.author_tz)
self.committer_date = Date(self.committer_epoch, self.committer_tz)
-
# Only get this lazily when we need it; most of the time it's not
# required by the caller.
self._diff = None
def __repr__(self):
- return '<C %s p:%s a:%s s:%r>' % (
- self.id[:7],
- ','.join(p[:7] for p in self.parents),
- self.author_email,
- self.subject[:20])
+ return "<C %s p:%s a:%s s:%r>" % (
+ self.id[:7],
+ ",".join(p[:7] for p in self.parents),
+ self.author_email,
+ self.subject[:20],
+ )
@property
def diff(self):
@@ -400,57 +422,68 @@ class Commit (object):
@staticmethod
def from_str(repo, buf):
"""Parses git rev-list output, returns a commit object."""
- if '\n\n' in buf:
+ if "\n\n" in buf:
# Header, commit message
- header, raw_message = buf.split('\n\n', 1)
+ header, raw_message = buf.split("\n\n", 1)
else:
# Header only, no commit message
- header, raw_message = buf.rstrip(), ' '
+ header, raw_message = buf.rstrip(), " "
- header_lines = header.split('\n')
+ header_lines = header.split("\n")
commit_id = header_lines.pop(0)
header_dict = defaultdict(list)
for line in header_lines:
- k, v = line.split(' ', 1)
+ k, v = line.split(" ", 1)
header_dict[k].append(v)
- tree = header_dict['tree'][0]
- parents = set(header_dict['parent'])
- author, author_epoch, author_tz = \
- header_dict['author'][0].rsplit(' ', 2)
- committer, committer_epoch, committer_tz = \
- header_dict['committer'][0].rsplit(' ', 2)
+ tree = header_dict["tree"][0]
+ parents = set(header_dict["parent"])
+
+ authorhdr = header_dict["author"][0]
+ author, author_epoch, author_tz = authorhdr.rsplit(" ", 2)
+
+ committerhdr = header_dict["committer"][0]
+ committer, committer_epoch, committer_tz = committerhdr.rsplit(" ", 2)
# Remove the first four spaces from the message's lines.
- message = ''
- for line in raw_message.split('\n'):
- message += line[4:] + '\n'
-
- return Commit(repo,
- commit_id = commit_id, tree = tree, parents = parents,
- author = author,
- author_epoch = author_epoch, author_tz = author_tz,
- committer = committer,
- committer_epoch = committer_epoch, committer_tz = committer_tz,
- message = message)
+ message = ""
+ for line in raw_message.split("\n"):
+ message += line[4:] + "\n"
+
+ return Commit(
+ repo,
+ commit_id=commit_id,
+ tree=tree,
+ parents=parents,
+ author=author,
+ author_epoch=author_epoch,
+ author_tz=author_tz,
+ committer=committer,
+ committer_epoch=committer_epoch,
+ committer_tz=committer_tz,
+ message=message,
+ )
+
class Date:
"""Handy representation for a datetime from git."""
+
def __init__(self, epoch, tz):
self.epoch = int(epoch)
self.tz = tz
self.utc = datetime.datetime.utcfromtimestamp(self.epoch)
self.tz_sec_offset_min = int(tz[1:3]) * 60 + int(tz[4:])
- if tz[0] == '-':
+ if tz[0] == "-":
self.tz_sec_offset_min = -self.tz_sec_offset_min
self.local = self.utc + datetime.timedelta(
- minutes = self.tz_sec_offset_min)
+ minutes=self.tz_sec_offset_min
+ )
- self.str = self.utc.strftime('%a, %d %b %Y %H:%M:%S +0000 ')
- self.str += '(%s %s)' % (self.local.strftime('%H:%M'), self.tz)
+ self.str = self.utc.strftime("%a, %d %b %Y %H:%M:%S +0000 ")
+ self.str += "(%s %s)" % (self.local.strftime("%H:%M"), self.tz)
def __str__(self):
return self.str
@@ -458,6 +491,7 @@ class Date:
class Diff:
"""A diff between two trees."""
+
def __init__(self, ref, changes, body):
"""Constructor.
@@ -477,23 +511,23 @@ class Diff:
ref_id = next(lines)
except StopIteration:
# No diff; this can happen in merges without conflicts.
- return Diff(None, [], '')
+ return Diff(None, [], "")
# First, --numstat information.
changes = []
l = next(lines)
- while l != '\n':
- l = l.rstrip('\n')
- added, deleted, fname = l.split('\t', 2)
- added = added.replace('-', '0')
- deleted = deleted.replace('-', '0')
+ while l != "\n":
+ l = l.rstrip("\n")
+ added, deleted, fname = l.split("\t", 2)
+ added = added.replace("-", "0")
+ deleted = deleted.replace("-", "0")
fname = smstr(unquote(fname))
changes.append((int(added), int(deleted), fname))
l = next(lines)
# And now the diff body. We just store as-is, we don't really care for
# the contents.
- body = ''.join(lines)
+ body = "".join(lines)
return Diff(ref_id, changes, body)
@@ -505,9 +539,9 @@ class Tree:
self.repo = repo
self.ref = ref
- def ls(self, path, recursive = False):
+ def ls(self, path, recursive=False):
"""Generates (type, name, size) for each file in path."""
- cmd = self.repo.cmd('ls-tree')
+ cmd = self.repo.cmd("ls-tree")
cmd.long = None
if recursive:
cmd.r = None
@@ -521,17 +555,17 @@ class Tree:
for l in cmd.run():
_mode, otype, _oid, size, name = l.split(None, 4)
- if size == '-':
+ if size == "-":
size = None
else:
size = int(size)
# Remove the quoting (if any); will always give us a str.
- name = unquote(name.strip('\n'))
+ name = unquote(name.strip("\n"))
# Strip the leading path, the caller knows it and it's often
# easier to work with this way.
- name = name[len(path):]
+ name = name[len(path) :]
# We use a smart string for the name, as it's often tricky to
# manipulate otherwise.
@@ -548,5 +582,5 @@ class Blob:
@property
def utf8_content(self):
if not self._utf8_content:
- self._utf8_content = self.raw_content.decode('utf8', 'replace')
+ self._utf8_content = self.raw_content.decode("utf8", "replace")
return self._utf8_content