summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rwxr-xr-x.mise-tasks/co.sh3
-rwxr-xr-x.mise-tasks/ps.sh3
-rw-r--r--README.md69
-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
-rw-r--r--mise.toml24
-rw-r--r--requirements.txt3
-rw-r--r--templates/_head.md6
-rw-r--r--templates/commit.md9
-rw-r--r--templates/log.md10
-rw-r--r--templates/overview.md19
-rw-r--r--templates/tree.md10
-rw-r--r--test_repo/.gitignore1
-rw-r--r--test_repo/abc/foo1
-rw-r--r--test_repo/git.tgzbin0 -> 22304 bytes
-rw-r--r--test_repo/sub/dir/2/other.file0
-rw-r--r--test_repo/sub/dir/test/some.file0
-rw-r--r--test_repo/sub/file0
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/commit_test.py36
-rw-r--r--tests/log_test.py188
-rw-r--r--tests/overview_test.py9
-rw-r--r--tests/page_test.py19
-rw-r--r--tests/tree_test.py98
-rw-r--r--workspace.josh6
33 files changed, 757 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..974957a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+__pycache__/
+/test_out/
diff --git a/.mise-tasks/co.sh b/.mise-tasks/co.sh
new file mode 100755
index 0000000..67e256a
--- /dev/null
+++ b/.mise-tasks/co.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+#MISE description="Stage and commit the whole working dir"
+git add . && git commit "$@"
diff --git a/.mise-tasks/ps.sh b/.mise-tasks/ps.sh
new file mode 100755
index 0000000..cf142f4
--- /dev/null
+++ b/.mise-tasks/ps.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+#MISE description="Stage and amend the whole working dir"
+git add . && git commit --amend --no-edit
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..582f912
--- /dev/null
+++ b/README.md
@@ -0,0 +1,69 @@
+# g2h - Generate Hugo style markdown pages from your git repository history
+
+This program allows you to generate markdown files to have a static web based interface for navigating git repositores.
+Something like [cgit](https://git.zx2c4.com/cgit/) for static websites, like [stagit](https://codemadness.org/stagit.html), but emitting Markdown for use in [Hugo](https://gohugo.io)[^1].
+
+## Goals
+
+- Generate static pages for projects fitting for the size of a personal homepage.
+- Generate markdown that can easily be integrated with Hugo.
+- Make full tree state and individual files for every commit viewable and also downloadable.
+- Work for [me](https://c.onl).
+
+## Non-goals
+
+- Provide codesearch.
+- Replacement for Git forges.
+- Replace cgit.
+- Work for large projects, as they may be found in a company (like) setting.
+- Implement any work that should be taken care of by Hugo, e.g. combining multiple projects onto a single page, feeds and custom styling.
+
+## Usage
+
+Let's assume your git project is in `~/development/project` and the sources for your static site are in `~/web/page`.
+Then you would run this command:
+```bash
+g2h --repo ~/development/project --out ~/web/page --title "My awesome project" --static-subdir project --content-subdir git/project
+```
+The markdown files for Hugo will be placed in `~/web/page/content/git/project` and static files, like tarballs for each commit and individual file hashes, into `~/web/page/static/project`.
+For a full list of options, their defaults and how they're changing the behavior of the program run `g2h --help`.
+
+## Features
+
+- [x] Overview page.
+ Table of branches and table of tags.
+ Each with name, most recent message summary and last modified date.
+ - [ ] Link branch and tag name to tree view.
+ - [x] Link commit messages to commit page.
+ - [ ] Set `date` frontmatter to latest commit date from repo.
+- [x] Log page.
+ Table with branch/tag, message summary and last modified date columns.
+ - [x] Link commit messages to commit page.
+ - [ ] Tabbed log page.
+- [x] Commit page.
+ Collection of metadata at the beginning of the page.
+ Consisting of commit hash, commit date, author
+ Followed by the commit message and then a list of changed files.
+ - [x] Add redacted email to author.
+ - [ ] Add links to parent(s).
+ - [ ] Add links to child(ren).
+ - [ ] Show diffstat on commit page.
+ - [ ] Add button to browse tree.
+ - [ ] Add button to download tree tarball.
+- [x] Tree view.
+ Show the state of the tree at the current commit.
+ - [ ] Folders should be traversable by clicking.
+ - [ ] Clicking on a file should open that file's page.
+- [ ] Individual file page.
+ File content in a codeblock with file extension based language detection.
+ For binary files a note that it can't be displayed.
+ - [ ] Don't treat pictures as binary files.
+ - [ ] Breadcrumbs for navigating.
+- [ ] Nav bar for navigating the pages mentioned above.
+ - [ ] Link to special readme, if a readme can be found, rendering markdown.
+- [ ] Wrapper script that takes care fo `venv` setup.
+- [ ] Limit to certain branch(es).
+- [ ] Limit commit range.
+- [ ] Parallelize generating output.
+
+[^1]: I'm using Hugo but any static site generator with a similar layout should work.
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
+
diff --git a/mise.toml b/mise.toml
new file mode 100644
index 0000000..4756681
--- /dev/null
+++ b/mise.toml
@@ -0,0 +1,24 @@
+[env]
+_.python.venv = { path = ".venv", create = true, python = "3.13.5" }
+
+[tasks.install]
+description = "Install dependencies"
+alias = "i"
+run = "pip install -r requirements.txt"
+
+[tasks.run]
+description = "Run g2h with the test repo"
+alias = "r"
+run = "rm -rf ./test_out && python3 -m g2h.cli test_repo test_out --name 'Test repo'"
+
+[tasks.test]
+alias = "t"
+run = "pytest"
+
+[tasks.commit]
+run = [
+ 'tar czf test_repo/git.tgz -C test_repo .git',
+ 'rm -rf test_repo/.git',
+ './.mise-tasks/co.sh {{arg(name="commit args", var=true)}}',
+ 'tar xzf test_repo/git.tgz -C test_repo'
+]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..65a012c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+GitPython==3.1.45
+Jinja2==3.1.6
+pytest==8.4.1
diff --git a/templates/_head.md b/templates/_head.md
new file mode 100644
index 0000000..90c29a2
--- /dev/null
+++ b/templates/_head.md
@@ -0,0 +1,6 @@
+---
+title: "{{ title }}"
+layout: git
+---
+
+# {{ title }}
diff --git a/templates/commit.md b/templates/commit.md
new file mode 100644
index 0000000..4ea5594
--- /dev/null
+++ b/templates/commit.md
@@ -0,0 +1,9 @@
+{% include '_head.md' %}
+
+**Commit**: {{ commit.hexsha }}
+**Author**: {{ commit.author }} <{{ redacted_email }}>
+**Date**: {{ commit.committed_datetime.strftime('%Y-%m-%d %H:%M:%S') }}
+
+```
+{{ commit.message }}
+```
diff --git a/templates/log.md b/templates/log.md
new file mode 100644
index 0000000..1949cf2
--- /dev/null
+++ b/templates/log.md
@@ -0,0 +1,10 @@
+{% include '_head.md' %}
+
+{% if commits %}
+| Message | Branches/Tags | Date |
+|---------|---------------|------|
+{%- for commit in commits %}
+| [{{ commit.summary }}](/{{ content_subdir}}/{{ commit.hexsha }}/) | {% if commit.refs %}{{ commit.refs | join(', ') }}{% endif %} | {{ commit.committed_datetime.strftime('%Y-%m-%d %H:%M:%S') }} |{% endfor %}
+{% else %}
+No commits found.
+{% endif %}
diff --git a/templates/overview.md b/templates/overview.md
new file mode 100644
index 0000000..5b93863
--- /dev/null
+++ b/templates/overview.md
@@ -0,0 +1,19 @@
+{% include '_head.md' %}
+
+{% if branches %}
+| Branch | Commit | Date |
+|--------|--------|------|
+{%- for branch in branches %}
+| {{ branch.name }} | [{{ branch.commit.summary }}](/{{ content_subdir }}/{{ branch.commit.hexsha }}) | {{ branch.commit.committed_datetime.strftime('%Y-%m-%d %H:%M:%S') }} |{% endfor %}
+{% else %}
+No branches found.
+{% endif %}
+
+{% if tags %}
+| Tag | Commit | Date |
+|-----|--------|------|
+{%- for tag in tags %}
+| {{ tag.name }} | [{{ tag.commit.summary }}](/{{ content_subdir }}/{{ tag.commit.hexsha }}) | {{ tag.commit.committed_datetime.strftime('%Y-%m-%d %H:%M:%S') }} |{% endfor %}
+{% else %}
+No tags found.
+{% endif %}
diff --git a/templates/tree.md b/templates/tree.md
new file mode 100644
index 0000000..654c9d0
--- /dev/null
+++ b/templates/tree.md
@@ -0,0 +1,10 @@
+{% include '_head.md' %}
+
+{% if entries %}
+| Name | Last modified |
+|------|---------------|
+{%- for entry in entries %}
+| {{ entry.name }} | {% if entry.modified %} {{ entry.modified.strftime('%Y-%m-%d %H:%M:%S') }} {% endif %} |{% endfor %}
+{% else %}
+No files.
+{% endif %}
diff --git a/test_repo/.gitignore b/test_repo/.gitignore
new file mode 100644
index 0000000..bcbb3fd
--- /dev/null
+++ b/test_repo/.gitignore
@@ -0,0 +1 @@
+/git.tgz
diff --git a/test_repo/abc/foo b/test_repo/abc/foo
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/test_repo/abc/foo
@@ -0,0 +1 @@
+bar
diff --git a/test_repo/git.tgz b/test_repo/git.tgz
new file mode 100644
index 0000000..3ea5a1a
--- /dev/null
+++ b/test_repo/git.tgz
Binary files differ
diff --git a/test_repo/sub/dir/2/other.file b/test_repo/sub/dir/2/other.file
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test_repo/sub/dir/2/other.file
diff --git a/test_repo/sub/dir/test/some.file b/test_repo/sub/dir/test/some.file
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test_repo/sub/dir/test/some.file
diff --git a/test_repo/sub/file b/test_repo/sub/file
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test_repo/sub/file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/commit_test.py b/tests/commit_test.py
new file mode 100644
index 0000000..3a9871a
--- /dev/null
+++ b/tests/commit_test.py
@@ -0,0 +1,36 @@
+from g2h.commit_page import CommitPage, redact_email
+from unittest.mock import MagicMock
+
+def test_init():
+ commit = MagicMock()
+ commit.hexsha = '123'
+ page = CommitPage('test/commit', 'commit test', commit, '')
+ assert(page.name == 'commit test')
+ assert(page.out_file_path == 'test/commit/123/index.md')
+ assert(page.template.name == 'commit.md')
+
+def test_context():
+ commit = MagicMock()
+ commit.hexsha = '123'
+ commit.author.email = 'unit@te.st'
+ page = CommitPage('test/commit', 'commit test', commit, '')
+ context = page.context()
+ assert(context['title'] == 'commit test: 123')
+ assert(context['commit'] == commit)
+ assert(context['redacted_email'] == 'u...t@te...st')
+
+def test_redacting_email():
+ assert(redact_email(None) == '')
+ assert(redact_email('') == '')
+ assert(redact_email('a') == 'a...a')
+ assert(redact_email('az') == 'a...z')
+ assert(redact_email('root') == 'r...t')
+ assert(redact_email('root@localhost') == 'r...t@localhost')
+ assert(redact_email('test@example.com') == 't...t@example...com')
+ assert(redact_email('test@sub.example.com') == 't...t@sub...example...com')
+ assert(redact_email('az@example.com') == 'a...z@example...com')
+ assert(redact_email('a@example.com') == 'a...a@example...com')
+ assert(redact_email('@example.com') == '@example...com')
+ assert(redact_email('test@') == 't...t@')
+ assert(redact_email('az@') == 'a...z@')
+ assert(redact_email('@') == '@')
diff --git a/tests/log_test.py b/tests/log_test.py
new file mode 100644
index 0000000..041544e
--- /dev/null
+++ b/tests/log_test.py
@@ -0,0 +1,188 @@
+from g2h.log_page import LogPage
+from unittest.mock import MagicMock
+
+def test_init():
+ page = LogPage('test/log', 'log test', None, None, None, '')
+ assert(page.name == 'log test')
+ assert(page.out_file_path == 'test/log/l.md')
+ assert(page.template.name == 'log.md')
+
+def test_context_no_commits():
+ page = LogPage('test/log', 'log test', None, None, None, '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [])
+
+def test_context_no_branches_and_tags():
+ commit = MagicMock()
+ commit.summary = 'mock commit'
+ commit.hexsha = 1234;
+ commit.committed_datetime = 0
+ page = LogPage('test/log', 'log test', [commit], None, None, '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [{'committed_datetime': 0, 'hexsha': 1234, 'refs': [], 'summary': 'mock commit'}])
+
+def test_context_matching_branch_no_tags():
+ commit = MagicMock()
+ commit.summary = 'mock commit'
+ commit.hexsha = 1234;
+ commit.committed_datetime = 0
+ branch = MagicMock()
+ branch.commit.hexsha = commit.hexsha
+ branch.name = 'mock branch'
+ page = LogPage('test/log', 'log test', [commit], [branch], None, '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [{'committed_datetime': 0, 'hexsha': 1234, 'refs': ['mock branch'], 'summary': 'mock commit'}])
+
+def test_context_unmatching_branch_no_tags():
+ commit = MagicMock()
+ commit.summary = 'mock commit'
+ commit.hexsha = 1234;
+ commit.committed_datetime = 0
+ branch = MagicMock()
+ branch.commit.hexsha = 4321
+ branch.name = 'mock branch'
+ page = LogPage('test/log', 'log test', [commit], [branch], None, '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [{'committed_datetime': 0, 'hexsha': 1234, 'refs': [], 'summary': 'mock commit'}])
+
+def test_context_no_branch_matching_tag():
+ commit = MagicMock()
+ commit.summary = 'mock commit'
+ commit.hexsha = 1234;
+ commit.committed_datetime = 0
+ tag = MagicMock()
+ tag.commit.hexsha = commit.hexsha
+ tag.name = 'mock tag'
+ page = LogPage('test/log', 'log test', [commit], None, [tag], '')
+ context = page.context()
+ assert(context['commits'] == [{'committed_datetime': 0, 'hexsha': 1234, 'refs': ['mock tag'], 'summary': 'mock commit'}])
+
+def test_context_no_branch_no_matching_tag():
+ commit = MagicMock()
+ commit.summary = 'mock commit'
+ commit.hexsha = 1234;
+ commit.committed_datetime = 0
+ tag = MagicMock()
+ tag.commit.hexsha = 4321
+ tag.name = 'mock tag'
+ page = LogPage('test/log', 'log test', [commit], None, [tag], '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [{'committed_datetime': 0, 'hexsha': 1234, 'refs': [], 'summary': 'mock commit'}])
+
+def test_context_no_matching_branch_no_matching_tag():
+ commit = MagicMock()
+ commit.summary = 'mock commit'
+ commit.hexsha = 1234;
+ commit.committed_datetime = 0
+ branch = MagicMock()
+ branch.commit.hexsha = 321
+ branch.name = 'mock branch'
+ tag = MagicMock()
+ tag.commit.hexsha = 4321
+ tag.name = 'mock tag'
+ page = LogPage('test/log', 'log test', [commit], [branch], [tag], '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [{'committed_datetime': 0, 'hexsha': 1234, 'refs': [], 'summary': 'mock commit'}])
+
+def test_context_matching_branch_matching_tag():
+ commit = MagicMock()
+ commit.summary = 'mock commit'
+ commit.hexsha = 1234;
+ commit.committed_datetime = 0
+ branch = MagicMock()
+ branch.commit.hexsha = commit.hexsha
+ branch.name = 'mock branch'
+ tag = MagicMock()
+ tag.commit.hexsha = commit.hexsha
+ tag.name = 'mock tag'
+ page = LogPage('test/log', 'log test', [commit], [branch], [tag], '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [{'committed_datetime': 0, 'hexsha': 1234, 'refs': ['mock branch', 'mock tag'], 'summary': 'mock commit'}])
+
+def test_multiple_commits():
+ commit1 = MagicMock()
+ commit1.summary = 'mock commit 1'
+ commit1.hexsha = 1;
+ commit1.committed_datetime = 0
+ commit2 = MagicMock()
+ commit2.summary = 'mock commit 2'
+ commit2.hexsha = 2;
+ commit2.committed_datetime = 1
+ page = LogPage('test/log', 'log test', [commit1, commit2], None, None, '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [
+ {'committed_datetime': 0, 'hexsha': 1, 'refs': [], 'summary': 'mock commit 1'},
+ {'committed_datetime': 1, 'hexsha': 2, 'refs': [], 'summary': 'mock commit 2'}
+ ])
+
+def test_multiple_commits_branch():
+ commit1 = MagicMock()
+ commit1.summary = 'mock commit 1'
+ commit1.hexsha = 1;
+ commit1.committed_datetime = 0
+ commit2 = MagicMock()
+ commit2.summary = 'mock commit 2'
+ commit2.hexsha = 2;
+ commit2.committed_datetime = 1
+ branch = MagicMock()
+ branch.commit.hexsha = 1
+ branch.name = 'mock branch'
+ page = LogPage('test/log', 'log test', [commit1, commit2], [branch], None, '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [
+ {'committed_datetime': 0, 'hexsha': 1, 'refs': ['mock branch'], 'summary': 'mock commit 1'},
+ {'committed_datetime': 1, 'hexsha': 2, 'refs': [], 'summary': 'mock commit 2'}
+ ])
+
+def test_multiple_commits_tag():
+ commit1 = MagicMock()
+ commit1.summary = 'mock commit 1'
+ commit1.hexsha = 1;
+ commit1.committed_datetime = 0
+ commit2 = MagicMock()
+ commit2.summary = 'mock commit 2'
+ commit2.hexsha = 2;
+ commit2.committed_datetime = 1
+ tag = MagicMock()
+ tag.commit.hexsha = 1
+ tag.name = 'mock tag'
+ page = LogPage('test/log', 'log test', [commit1, commit2], None, [tag], '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [
+ {'committed_datetime': 0, 'hexsha': 1, 'refs': ['mock tag'], 'summary': 'mock commit 1'},
+ {'committed_datetime': 1, 'hexsha': 2, 'refs': [], 'summary': 'mock commit 2'}
+ ])
+
+def test_multiple_commits_branch_and_tag():
+ commit1 = MagicMock()
+ commit1.summary = 'mock commit 1'
+ commit1.hexsha = 1;
+ commit1.committed_datetime = 0
+ commit2 = MagicMock()
+ commit2.summary = 'mock commit 2'
+ commit2.hexsha = 2;
+ commit2.committed_datetime = 1
+ branch = MagicMock()
+ branch.commit.hexsha = 1
+ branch.name = 'mock branch'
+ tag = MagicMock()
+ tag.commit.hexsha = 2
+ tag.name = 'mock tag'
+ page = LogPage('test/log', 'log test', [commit1, commit2], [branch], [tag], '')
+ context = page.context()
+ assert(context['title'] == 'log test: Log')
+ assert(context['commits'] == [
+ {'committed_datetime': 0, 'hexsha': 1, 'refs': ['mock branch'], 'summary': 'mock commit 1'},
+ {'committed_datetime': 1, 'hexsha': 2, 'refs': ['mock tag'], 'summary': 'mock commit 2'}
+ ])
+
diff --git a/tests/overview_test.py b/tests/overview_test.py
new file mode 100644
index 0000000..06eea97
--- /dev/null
+++ b/tests/overview_test.py
@@ -0,0 +1,9 @@