Make git logs easier for use in scenarios when communicating the progress of a project to non-experts
Project description
Lumpy Log - Prettified Git Logs
Make git logs easier for use in scenarios when communicating the progress of a project to non-experts.
Features
- Generates readable markdown reports from Git commit history
- Processes test output (TAP format) and creates test documentation
- Multi-folder organization with unified index
📚 See Also:
- Obsidian Integration Guide - Detailed guide for using with Obsidian
- Development Guide - For contributors and developers
- Publishing Guide - How to publish to PyPI
- Quick Start - Quick reference
Installation
From PyPI (when published)
pip install lumpy-log
For Development
# Clone the repository
git clone https://github.com/UTCSheffield/lumpy_log.git
cd lumpy_log
# Install in editable mode
pip install -e .
Usage
As a CLI Command
After installation, you can use the lumpy-log command:
Generate Git Commit Logs
# Process current directory repository
lumpy-log log
# Process a specific repository
lumpy-log log -i /path/to/repo
# Process with options
lumpy-log log -i /path/to/repo -o devlog --verbose --force
# Backwards compatible (defaults to log command)
lumpy-log -i /path/to/repo
Process Test Results
Lumpy Log can process test output in TAP (Test Anything Protocol) format and create markdown documentation alongside your commit logs.
Install pytest-tap plugin:
pip install pytest-tap
Bash/Linux/macOS:
# Pipe test output directly
pytest --tap | lumpy-log test
# Or save to file first
pytest --tap > test_output.txt
lumpy-log test --input test_output.txt
Windows cmd.exe or PowerShell:
REM Pipe test output directly
py -m pytest --tap | lumpy-log test
REM Or save to file first
py -m pytest --tap > test_output.txt
lumpy-log test --input test_output.txt
REM Include raw output for debugging
py -m pytest --tap | lumpy-log test --raw-output
Test results are saved to output/tests/ with timestamp filenames (e.g., 20260118_1430.md), and the index is automatically updated to include both commits and test results.
Rebuild Index
If you manually modify or reorganize commit/test files, you can regenerate the index:
# Rebuild with default order (oldest first - development log style)
lumpy-log rebuild
# Rebuild with changelog order (newest first)
lumpy-log rebuild --changelog
As a Python Module
You can also run it as a module:
python -m lumpy_log -i /path/to/repo -o output
Command-line Options
Log Command (Git Commits)
-i, --repo: Path to the local Git repository (default: current directory)-o, --outputfolder: Output folder for generated files (default: devlog)-f, --fromcommit: Start from this commit-t, --tocommit: End at this commit-a, --allbranches: Include all branches-v, --verbose: Verbose output-b, --branch: Specific branch to process--force: Force overwrite existing files-d, --dryrun: Dry run - don't write files-n, --no-obsidian-index: Don't generate index.md
Test Command (Test Results)
-o, --outputfolder: Output folder for test results (default: devlog)--input: Input file with test output (if not specified, reads from stdin)-v, --verbose: Verbose output--raw-output: Include raw test output in the report
Rebuild Command (Regenerate Index)
Rebuilds the unified index.md from existing commits and test results without re-processing git history or re-running tests.
# Rebuild index with default order (oldest first)
lumpy-log rebuild
# Rebuild with changelog order (newest first)
lumpy-log rebuild --changelog
# Rebuild from custom output folder
lumpy-log rebuild -o /path/to/output
-o, --outputfolder: Output folder containing commits/ and tests/ (default: devlog)-v, --verbose: Verbose output--changelog: Use changelog order (newest first) instead of default (oldest first)
Output Structure
Lumpy Log organizes output into subdirectories:
devlog/
├── index.md # Unified index with commits and test results
├── commits/ # Git commit markdown files
│ ├── 20260118_1430_abc1234.md
│ └── 20260118_1500_def5678.md
└── tests/ # Test result markdown files
├── 20260118_1430.md
└── 20260118_1500.md
Ignoring Files (.lumpyignore)
Lumpy Log respects a repository-level .lumpyignore file using the same syntax as .gitignore (git wildmatch patterns). By default, it ignores Markdown files (*.md) so documentation changes don't flood the logs. Add additional patterns to .lumpyignore at your repo root to skip files or folders.
Example .lumpyignore:
# Ignore Markdown (default)
*.md
# Ignore generated docs and build artifacts
docs/
dist/
*.tmp
Running Tests
To run the tests, use the following command:
pytest
Example Output
Commit : Refactor verbose logging conditions in ChangeLump methods for clarity
By "Mr Eggleton" on 2026-01-18
"changelump.py" was Modified
# Abstracts out lineIsComment so we can print the results
def _lineIsComment(self, i):
line = self.lines[i]
if(self.verbose):
print(self.lang.name, "self.lang.comment_structure",self.lang.comment_structure)
comment_structure = self.lang.comment_structure
begin = comment_structure.get("begin")
end = comment_structure.get("end")
single = comment_structure.get("single")
# Multiline comments: treat lines with both begin and end as comment,
# and any line inside unmatched begin/end pairs as comment.
if begin:
try:
beginmatches = re.findall(begin, line)
endmatches = re.findall(end, line)
# If both markers appear on the same line, it's a comment line.
if len(beginmatches) and len(endmatches):
return True
# If this line is inside an open multiline comment, it's a comment.
if self._in_multiline_comment(i, begin, end):
return True
except Exception as Err:
print(type(Err), Err)
print(self.lang.comment_family, comment_structure)
# Single-line comments
if single:
try:
if re.search(single, line.strip()):
return True
except Exception as Err:
print("Single", type(Err), Err)
print(self.lang.comment_family, comment_structure["single"])
return False
@property
def code(self):
start = self.start
if(self.commentStart is not None):
start = self.commentStart
#code = ""self.source+"\n"+
code = ("\n".join(self.lines[start: self.end+1]))
if self.verbose:
print("code", code)
return code
def extendOverComments(self):
if self.verbose:
print("extendOverComments", "self.start", self.start)
j = self.start
while(j > 0 and self.lineIsComment(j-1)):
j -= 1
self.commentStart = j
def lineIsComment(self, i):
blineIsComment = self._lineIsComment(i)
if self.verbose:
print("lineIsComment", blineIsComment, self.lines[i])
return blineIsComment
def inLump(self,i):
inLump = (self.start <= i and i <= self.end)
if self.verbose:
print("inLump", "self.start", self.start,"i", i, "inLump",inLump)
return inLump
"""Return True if line i is inside an unmatched multiline comment block."""
try:
# Check if begin and end delimiters are the same (symmetric like """)
# Strip common regex anchors to compare the actual delimiter strings
begin_stripped = begin_re.strip('^$\\s')
end_stripped = end_re.strip('^$\\s')
symmetric = (begin_stripped == end_stripped)
in_comment = False
for idx in range(0, i + 1):
s = self.lines[idx]
if symmetric:
# For symmetric delimiters (like """ in Python), each occurrence
# toggles the comment state: first one opens, second one closes, etc.
# Example: """comment""" means we enter on first """, exit on second
matches = re.findall(begin_re, s)
for _ in matches:
in_comment = not in_comment # Flip True->False or False->True
else:
# For asymmetric delimiters, track depth
begins = len(re.findall(begin_re, s))
ends = len(re.findall(end_re, s))
# Process begins first, then ends
if not in_comment and begins > 0:
in_comment = True
if in_comment and ends > 0:
in_comment = False
return in_comment
except Exception as Err:
if self.verbose:
print("_in_multiline_comment error", type(Err), Err)
return False
Test Results : 2026-01-20 13:30:12
Format: tap
- Tests Run: 113
- Passed: 113 ✅
- Failed: 0
- Skipped: 0
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lumpy_log-0.1.3.tar.gz.
File metadata
- Download URL: lumpy_log-0.1.3.tar.gz
- Upload date:
- Size: 33.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
067f13d3c59cc48b9c12f0ee73484969cf6c81ddd8cd208216495d9b8cf5e9d7
|
|
| MD5 |
7c52df8ffc4213a60fab63923de2d2ab
|
|
| BLAKE2b-256 |
3167e19948b25ed529f5333e64994392319bb766ab72b86021962a50349680df
|
File details
Details for the file lumpy_log-0.1.3-py3-none-any.whl.
File metadata
- Download URL: lumpy_log-0.1.3-py3-none-any.whl
- Upload date:
- Size: 27.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45248094d9a83dadcaccdfcef6dd06811a595bacaf114832abd77e51def76e66
|
|
| MD5 |
ff01dd6d3c780b287e6581b77eb31387
|
|
| BLAKE2b-256 |
37f610bad5d12432828cda8b3604b77e988741df3c28911e044760269da38d8d
|