summaryrefslogtreecommitdiffstats
path: root/g2h
diff options
context:
space:
mode:
Diffstat (limited to 'g2h')
-rw-r--r--g2h/__init__.py0
-rw-r--r--g2h/cli.py39
-rw-r--r--g2h/commit_page.py28
-rw-r--r--g2h/git.py20
-rw-r--r--g2h/log_page.py45
-rw-r--r--g2h/overview_page.py16
-rw-r--r--g2h/page.py19
-rw-r--r--g2h/page_factory.py22
-rw-r--r--g2h/tree_page.py51
9 files changed, 240 insertions, 0 deletions
diff --git a/g2h/__init__.py b/g2h/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/g2h/__init__.py
diff --git a/g2h/cli.py b/g2h/cli.py
new file mode 100644
index 0000000..52a2be7
--- /dev/null
+++ b/g2h/cli.py
@@ -0,0 +1,39 @@
+import argparse
+import os
+
+from .git import Git
+from .page_factory import PageFactory
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ prog='g2h',
+ description='Convert a git repository into markdown files Hugo can understand.')
+ # TODO: Defaults for these two values should be $PWD.
+ parser.add_argument('repo_path', help='Path to the root of git repository')
+ parser.add_argument('out_dir', help='Path to where the generated files should be stored')
+ parser.add_argument('--name', help='Name of the project. Default: directory name of repo')
+ parser.add_argument('--content-subdir', help='Subdir(s) where the page will be served from. Default: None', default='')
+
+ return parser.parse_args()
+
+def main():
+ args = parse_args()
+ git = Git(args.repo_path)
+
+ branches = sorted(git.get_branches(), key=lambda b: b.commit.committed_date, reverse=True)
+ commits = git.get_commits()
+ # TODO: Fix this for repo_path=(.|~)
+ project_name = args.name if args.name is not None else os.path.basename(args.repo_path)
+ tags = sorted(git.get_tags(), key=lambda t: t.commit.committed_date, reverse=True)
+
+ factory = PageFactory(args.out_dir, project_name, args.content_subdir)
+ factory.new_overview(branches, tags).render()
+ factory.new_log(commits, branches, tags).render()
+ for commit in commits:
+ factory.new_commit(commit).render()
+ tree = factory.new_tree(commit, '', git.get_repo())
+ for tree_page in tree.build_pages(factory):
+ tree_page.render()
+
+if __name__ == '__main__':
+ main()
diff --git a/g2h/commit_page.py b/g2h/commit_page.py
new file mode 100644
index 0000000..7123c82
--- /dev/null
+++ b/g2h/commit_page.py
@@ -0,0 +1,28 @@
+import os
+
+from .page import Page
+
+def redact_email(email):
+ if email is None or len(email) == 0:
+ return ''
+
+ if '@' not in email:
+ return email[0] + '...' + email[-1]
+
+ local, domain = email.split('@')
+ redacted_local = local[0] + '...' + local[-1] if len(local) > 0 else ''
+ redacted_domain = domain.replace('.', '...')
+
+ return redacted_local + '@' + redacted_domain
+
+class CommitPage(Page):
+ def __init__(self, out_dir, name, commit, content_subdir):
+ super(CommitPage, self).__init__('commit.md', out_dir, os.path.join(commit.hexsha, 'index.md'), name, content_subdir)
+ self.commit = commit
+
+ def context(self):
+ return {
+ 'commit': self.commit,
+ 'title': self.name + ': ' + self.commit.hexsha,
+ 'redacted_email': redact_email(self.commit.author.email)
+ }
diff --git a/g2h/git.py b/g2h/git.py
new file mode 100644
index 0000000..1e835e7
--- /dev/null
+++ b/g2h/git.py
@@ -0,0 +1,20 @@
+import git
+
+class Git:
+ def __init__(self, repo_path):
+ try:
+ self.repo = git.Repo(repo_path)
+ except git.InvalidGitRepositoryError:
+ raise ValueError(f"Invalid git repository at {repo_path}")
+
+ def get_branches(self):
+ return self.repo.branches
+
+ def get_commits(self):
+ return list(self.repo.iter_commits())
+
+ def get_repo(self):
+ return self.repo
+
+ def get_tags(self):
+ return self.repo.tags
diff --git a/g2h/log_page.py b/g2h/log_page.py
new file mode 100644
index 0000000..4b8ee10
--- /dev/null
+++ b/g2h/log_page.py
@@ -0,0 +1,45 @@
+import os
+
+from .page import Page
+
+def ref_names_for_commit(refs, commit_sha):
+ ref_names = []
+ if refs is None:
+ return ref_names
+ for ref in refs:
+ if ref.commit.hexsha == commit_sha:
+ ref_names.append(ref.name)
+
+ return ref_names
+
+class LogPage(Page):
+ def __init__(self, out_dir, name, commits, branches, tags, content_subdir):
+ super(LogPage, self).__init__('log.md', out_dir, 'l.md', name, content_subdir)
+ self.commits = commits
+ self.branches = branches
+ self.tags = tags
+
+ def _commits_with_refs(self):
+ commits_context = []
+ for commit in self.commits:
+ refs = ref_names_for_commit(self.branches, commit.hexsha) + ref_names_for_commit(self.tags, commit.hexsha)
+ commit_context = {
+ 'committed_datetime': commit.committed_datetime,
+ 'hexsha': commit.hexsha,
+ 'refs': refs,
+ 'summary': commit.summary
+ }
+ commits_context.append(commit_context)
+ return commits_context
+
+ def context(self):
+ commits_context = []
+ if self.commits is not None:
+ commits_context = self._commits_with_refs()
+
+ return {
+ 'commits': commits_context,
+ 'content_subdir': self.content_subdir,
+ 'title': self.name + ': Log'
+ }
+
diff --git a/g2h/overview_page.py b/g2h/overview_page.py
new file mode 100644
index 0000000..76de8cc
--- /dev/null
+++ b/g2h/overview_page.py
@@ -0,0 +1,16 @@
+from .page import Page
+
+class OverviewPage(Page):
+ def __init__(self, out_dir, name, branches, tags, content_subdir):
+ super(OverviewPage, self).__init__('overview.md', out_dir, '_index.md', name, content_subdir)
+ self.branches = branches
+ self.tags = tags
+
+ def context(self):
+ return {
+ 'branches': self.branches,
+ 'content_subdir': self.content_subdir,
+ 'tags': self.tags,
+ 'title': self.name
+ }
+
diff --git a/g2h/page.py b/g2h/page.py
new file mode 100644
index 0000000..a708f2b
--- /dev/null
+++ b/g2h/page.py
@@ -0,0 +1,19 @@
+from jinja2 import Environment, FileSystemLoader
+import os
+
+class Page:
+ def __init__(self, template_file, out_dir, out_file, name, content_subdir):
+ self.out_file_path = os.path.join(out_dir, out_file)
+ self.name = name
+ self.content_subdir = content_subdir
+ template_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'templates'))
+ self.template = Environment(loader=FileSystemLoader(template_dir)).get_template(template_file)
+
+ def context(self):
+ raise NotImplementedError("Must override context")
+
+ def render(self):
+ content = self.template.render(self.context())
+ os.makedirs(os.path.dirname(self.out_file_path), exist_ok=True)
+ with open(self.out_file_path, 'w', encoding='utf-8') as f:
+ f.write(content)
diff --git a/g2h/page_factory.py b/g2h/page_factory.py
new file mode 100644
index 0000000..c4b89ed
--- /dev/null
+++ b/g2h/page_factory.py
@@ -0,0 +1,22 @@
+from .commit_page import CommitPage
+from .overview_page import OverviewPage
+from .log_page import LogPage
+from .tree_page import TreePage
+
+class PageFactory:
+ def __init__(self, out_dir, name, content_subdir):
+ self.out_dir = out_dir
+ self.name = name
+ self.content_subdir = content_subdir
+
+ def new_commit(self, commit):
+ return CommitPage(self.out_dir, self.name, commit, self.content_subdir)
+
+ def new_overview(self, branches, tags):
+ return OverviewPage(self.out_dir, self.name, branches, tags, self.content_subdir)
+
+ def new_log(self, commits, branches, tags):
+ return LogPage(self.out_dir, self.name, commits, branches, tags, self.content_subdir)
+
+ def new_tree(self, commit, tree_segment, repo):
+ return TreePage(self.out_dir, self.name, commit, tree_segment, repo, self.content_subdir)
diff --git a/g2h/tree_page.py b/g2h/tree_page.py
new file mode 100644
index 0000000..22e563a
--- /dev/null
+++ b/g2h/tree_page.py
@@ -0,0 +1,51 @@
+import os
+
+from .page import Page
+
+class TreePage(Page):
+ def __init__(self, out_dir, name, commit, sub_tree, repo, content_subdir):
+ super(TreePage, self).__init__('tree.md', out_dir, os.path.join(commit.hexsha, 't', sub_tree, 'index.md'), name, content_subdir)
+ self.tree = commit.tree.join(sub_tree) if sub_tree else commit.tree
+ self.repo = repo
+ self.commit = commit
+ self.sub_tree = sub_tree
+
+ def _generate_entries(self, nodes):
+ entries = []
+ for node in nodes:
+ latest_commit = next(self.repo.iter_commits(self.commit, max_count=1, paths=node.path))
+ entry = {
+ 'name': node.name + str('/' if node.type == 'tree' else ''),
+ 'modified': latest_commit.committed_datetime
+ }
+ entries.append(entry)
+ return entries
+
+ def _get_entries(self):
+ files = self._generate_entries(self.tree.blobs)
+ dirs = self._generate_entries(self.tree.trees)
+ files.sort(key=lambda x: x['name'])
+ dirs.sort(key=lambda x: x['name'])
+ return dirs + files
+
+ def context(self):
+ return {
+ 'entries': self._get_entries(),
+ 'title': self.name + ': Tree'
+ }
+
+ def build_pages(self, factory):
+ visited = set()
+ remaining = [self.sub_tree]
+ tree_pages = []
+ while remaining:
+ current = remaining.pop()
+ if not current in visited:
+ visited.add(current)
+ tree = self.commit.tree.join(current) if current else self.commit.tree
+ tree_pages.append(factory.new_tree(self.commit, tree.path, self.repo))
+ for sub_tree in tree.trees:
+ remaining.append(sub_tree.path)
+
+ return tree_pages
+