trueloc 0.3.0
Count lines of code from GitHub pull requests
Requires Python: >=3.12

# trueloc - Lines of Code Counter

[![PyPI](https://img.shields.io/pypi/v/trueloc)](https://pypi.org/project/trueloc/)
[![Python](https://img.shields.io/pypi/pyversions/trueloc)](https://pypi.org/project/trueloc/)
[![License](https://img.shields.io/github/license/basnijholt/trueloc)](LICENSE)
[![Downloads](https://img.shields.io/pypi/dm/trueloc)](https://pypi.org/project/trueloc/)
[![CI](https://img.shields.io/github/actions/workflow/status/basnijholt/trueloc/pytest.yml?label=tests)](https://github.com/basnijholt/trueloc/actions/workflows/pytest.yml)
[![Docs](https://img.shields.io/badge/docs-trueloc.nijho.lt-blue)](https://trueloc.nijho.lt)

<img src="https://raw.githubusercontent.com/basnijholt/trueloc/main/logo.svg" alt="trueloc Logo" align="right" style="width: 150px;" />

A CLI tool to analyze your coding activity via GitHub pull requests and direct commits:

- **Total lines** written since any date
- **Per-PR and per-commit breakdown** showing when each contribution was made
- **File type analysis** revealing which languages you've worked with most

Counts ALL lines touched across every commit in a PR—even for squash merges where GitHub only shows the final diff. For example, a PR where you add 1000 lines, delete them, then add 1 line shows +1001 / -1000 (not just +1).

## Installation

Recommended (installs in isolated environment):
```bash
uv tool install trueloc
```

Or run directly without installing:
```bash
uvx trueloc count USERNAME --since 1m
```

Alternatively, with pip:
```bash
pip install trueloc
```

Requires the GitHub CLI (`gh`) to be installed and authenticated:
```bash
gh auth login
```

## Usage

```bash
# Count lines from PRs and direct commits since a date
trueloc count USERNAME --since 2023-01-01

# Use relative dates
trueloc count USERNAME --since 5d      # 5 days ago
trueloc count USERNAME --since 2w      # 2 weeks ago
trueloc count USERNAME --since 3m      # 3 months ago
trueloc count USERNAME --since 1y      # 1 year ago
trueloc count USERNAME --since "last month"

# Specify a date range
trueloc count USERNAME --since 2024-01-01 --until 2024-06-30

# Count only net diff (not per-commit)
trueloc count USERNAME --since 2023-01-01 --net

# Exclude direct commits (PRs only)
trueloc count USERNAME --since 2023-01-01 --no-direct-commits

# Hide file extension breakdown
trueloc count USERNAME --since 2023-01-01 --no-extensions

# Disable caching (fresh API calls)
trueloc count USERNAME --since 2023-01-01 --no-cache

# Output as JSON for scripting/postprocessing
trueloc count USERNAME --since 2023-01-01 --json

# Clear the cache
trueloc clear-cache
```

## CLI Reference

### Main Commands

```bash markdown-code-runner
echo '```'
trueloc --help
echo '```'
```
<!-- OUTPUT:START -->
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
```
[1m                                                                                [0m
[1m [0m[1;33mUsage: [0m[1mtrueloc [OPTIONS] COMMAND [ARGS]...[0m[1m                                    [0m[1m [0m
[1m                                                                                [0m
 Count lines of code from GitHub pull requests.

[2m╭─[0m[2m Options [0m[2m───────────────────────────────────────────────────────────────────[0m[2m─╮[0m
[2m│[0m [1;36m-[0m[1;36m-install[0m[1;36m-completion[0m            Install completion for the current shell.    [2m│[0m
[2m│[0m [1;36m-[0m[1;36m-show[0m[1;36m-completion[0m               Show completion for the current shell, to    [2m│[0m
[2m│[0m                                 copy it or customize the installation.       [2m│[0m
[2m│[0m [1;36m-[0m[1;36m-help[0m                [1;32m-h[0m        Show this message and exit.                  [2m│[0m
[2m╰──────────────────────────────────────────────────────────────────────────────╯[0m
[2m╭─[0m[2m Commands [0m[2m──────────────────────────────────────────────────────────────────[0m[2m─╮[0m
[2m│[0m [1;36mcount       [0m[1;36m [0m Count lines of code from merged PRs and direct commits.        [2m│[0m
[2m│[0m [1;36mclear-cache [0m[1;36m [0m Clear the disk cache.                                          [2m│[0m
[2m│[0m [1;36mcount-local [0m[1;36m [0m Count lines of code from a local git repository.               [2m│[0m
[2m╰──────────────────────────────────────────────────────────────────────────────╯[0m

```

<!-- OUTPUT:END -->

### Count Command Options

```bash markdown-code-runner
echo '```'
trueloc count --help
echo '```'
```
<!-- OUTPUT:START -->
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
```
[1m                                                                                [0m
[1m [0m[1;33mUsage: [0m[1mtrueloc count [OPTIONS] USERNAME[0m[1m                                       [0m[1m [0m
[1m                                                                                [0m
 Count lines of code from merged PRs and direct commits.

 [2mBy default, counts all lines touched across all commits in each PR,[0m
 [2mplus direct commits to the main branch (not from PRs).[0m

 [2mExample: a single PR where you add 1000 lines, delete them, then add[0m
 [2m1 line = +1001 / [0m[1;2;32m-1000[0m[2m (even though the PR's net diff shows only +1).[0m
 [2mAdditions and deletions are summed separately across all commits.[0m

 [2mUse [0m[1;2;36m-[0m[1;2;36m-net[0m[2m to count only the final diff (net additions/deletions).[0m
 [2mUse [0m[1;2;36m-[0m[1;2;36m-no[0m[1;2;36m-direct-commits[0m[2m to exclude direct commits to main branch.[0m

[2m╭─[0m[2m Arguments [0m[2m─────────────────────────────────────────────────────────────────[0m[2m─╮[0m
[2m│[0m [31m*[0m    username      [1;33mTEXT[0m  GitHub username [2;31m[required][0m                          [2m│[0m
[2m╰──────────────────────────────────────────────────────────────────────────────╯[0m
[2m╭─[0m[2m Options [0m[2m───────────────────────────────────────────────────────────────────[0m[2m─╮[0m
[2m│[0m [31m*[0m  [1;36m-[0m[1;36m-since[0m           [1;32m-s[0m                         [1;33mTEXT[0m  Start date (e.g., 5d,  [2m│[0m
[2m│[0m                                                       2w, 3m, 1y, 'last      [2m│[0m
[2m│[0m                                                       month', 2024-01-01)    [2m│[0m
[2m│[0m                                                       [2;31m[required]            [0m [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-until[0m           [1;32m-u[0m                         [1;33mTEXT[0m  End date (e.g., 1d,    [2m│[0m
[2m│[0m                                                       'yesterday',           [2m│[0m
[2m│[0m                                                       2024-12-31)            [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-no[0m[1;36m-cache[0m                                   [1;33m    [0m  Disable disk cache     [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-extensions[0m          [1;35m-[0m[1;35m-no[0m[1;35m-extensions[0m        [1;33m    [0m  Show breakdown by file [2m│[0m
[2m│[0m                                                       extension              [2m│[0m
[2m│[0m                                                       [2m[default: extensions] [0m [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-per[0m[1;36m-commit[0m          [1;35m-[0m[1;35m-net[0m                  [1;33m    [0m  Count all lines        [2m│[0m
[2m│[0m                                                       touched per commit     [2m│[0m
[2m│[0m                                                       (default) vs net diff  [2m│[0m
[2m│[0m                                                       only                   [2m│[0m
[2m│[0m                                                       [2m[default: per-commit] [0m [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-direct[0m[1;36m-commits[0m      [1;35m-[0m[1;35m-no[0m[1;35m-direct-commits[0m    [1;33m    [0m  Include direct commits [2m│[0m
[2m│[0m                                                       to main branch (not    [2m│[0m
[2m│[0m                                                       from PRs)              [2m│[0m
[2m│[0m                                                       [2m[default:             [0m [2m│[0m
[2m│[0m                                                       [2mdirect-commits]       [0m [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-json[0m                                       [1;33m    [0m  Output results as JSON [2m│[0m
[2m│[0m                                                       for scripting          [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-repo[0m            [1;32m-r[0m                         [1;33mTEXT[0m  Only process a single  [2m│[0m
[2m│[0m                                                       repository (e.g.,      [2m│[0m
[2m│[0m                                                       'owner/repo' or        [2m│[0m
[2m│[0m                                                       'repo')                [2m│[0m
[2m│[0m    [1;36m-[0m[1;36m-help[0m            [1;32m-h[0m                         [1;33m    [0m  Show this message and  [2m│[0m
[2m│[0m                                                       exit.                  [2m│[0m
[2m╰──────────────────────────────────────────────────────────────────────────────╯[0m

```

<!-- OUTPUT:END -->

## Features

- **Per-commit counting** (default): Counts every line touched in every commit
- **Net diff mode**: Alternative mode that only counts final diff (`--net`)
- **Direct commits**: Includes commits pushed directly to main (not via PR)
- **File extension breakdown**: Shows which languages you've worked with
- **JSON output**: Machine-readable output for scripting (`--json`)
- **Disk caching**: Uses diskcache to avoid hammering the GitHub API
- **Rate limit handling**: Automatically waits when rate limited with progress bar
- **Flexible dates**: Supports relative (`5d`, `2w`, `3m`, `1y`) and natural language (`last month`)

## Caching Strategy

- **Immutable data** (cached forever): commit stats, PR commits, PR files
- **Mutable data** (1 day TTL): user repos, merged PR lists

Cache is stored in `~/.cache/trueloc/`.

## Tech Stack

- Python 3.12+
- [Typer](https://typer.tiangolo.com/) - CLI framework
- [httpx](https://www.python-httpx.org/) - HTTP client
- [Rich](https://rich.readthedocs.io/) - Terminal output
- [diskcache](https://grantjenks.com/docs/diskcache/) - Persistent caching
- [dateparser](https://dateparser.readthedocs.io/) - Flexible date parsing
- [Hatch](https://hatch.pypa.io/) - Build system
- [Ruff](https://docs.astral.sh/ruff/) + [mypy](https://mypy-lang.org/) - Linting/typing

## Development

```bash
# Install dev dependencies
uv sync --group dev

# Run tests
pytest

# Run linting
ruff check .
ruff format .
mypy .
```

## License

MIT