diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rwxr-xr-x | .mise-tasks/co.sh | 3 | ||||
| -rwxr-xr-x | .mise-tasks/ps.sh | 3 | ||||
| -rw-r--r-- | README.md | 69 | ||||
| -rw-r--r-- | g2h/__init__.py | 0 | ||||
| -rw-r--r-- | g2h/cli.py | 39 | ||||
| -rw-r--r-- | g2h/commit_page.py | 28 | ||||
| -rw-r--r-- | g2h/git.py | 20 | ||||
| -rw-r--r-- | g2h/log_page.py | 45 | ||||
| -rw-r--r-- | g2h/overview_page.py | 16 | ||||
| -rw-r--r-- | g2h/page.py | 19 | ||||
| -rw-r--r-- | g2h/page_factory.py | 22 | ||||
| -rw-r--r-- | g2h/tree_page.py | 51 | ||||
| -rw-r--r-- | mise.toml | 24 | ||||
| -rw-r--r-- | requirements.txt | 3 | ||||
| -rw-r--r-- | templates/_head.md | 6 | ||||
| -rw-r--r-- | templates/commit.md | 9 | ||||
| -rw-r--r-- | templates/log.md | 10 | ||||
| -rw-r--r-- | templates/overview.md | 19 | ||||
| -rw-r--r-- | templates/tree.md | 10 | ||||
| -rw-r--r-- | test_repo/.gitignore | 1 | ||||
| -rw-r--r-- | test_repo/abc/foo | 1 | ||||
| -rw-r--r-- | test_repo/git.tgz | bin | 0 -> 22304 bytes | |||
| -rw-r--r-- | test_repo/sub/dir/2/other.file | 0 | ||||
| -rw-r--r-- | test_repo/sub/dir/test/some.file | 0 | ||||
| -rw-r--r-- | test_repo/sub/file | 0 | ||||
| -rw-r--r-- | tests/__init__.py | 0 | ||||
| -rw-r--r-- | tests/commit_test.py | 36 | ||||
| -rw-r--r-- | tests/log_test.py | 188 | ||||
| -rw-r--r-- | tests/overview_test.py | 9 | ||||
| -rw-r--r-- | tests/page_test.py | 19 | ||||
| -rw-r--r-- | tests/tree_test.py | 98 | ||||
| -rw-r--r-- | workspace.josh | 6 |
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 Binary files differnew file mode 100644 index 0000000..3ea5a1a --- /dev/null +++ b/test_repo/git.tgz 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 @@ |