Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Default owner for everything in the repository
* @coldsofttech
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Release

# Publishes to PyPI when a version tag such as v0.2.0 is pushed.
# Needs a PYPI_API_TOKEN secret (Settings > Secrets and variables > Actions).
# Uses PyPI trusted publishing (OIDC), so no API token is stored. PyPI needs a
# trusted publisher for this repository: workflow release.yml, environment pypi.
on:
push:
tags: ["v*.*.*"]
Expand Down Expand Up @@ -49,6 +50,7 @@ jobs:
environment: pypi
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v7

Expand All @@ -58,9 +60,7 @@ jobs:
run: uv build

- name: Publish to PyPI
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
run: uv publish
run: uv publish --trusted-publishing always

- name: GitHub release
uses: softprops/action-gh-release@v3
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## [Unreleased]

## [0.1.2] - 2026-10-25

### Changed
- `--use-lfs` now commits LFS rules by default so checkouts and clones get the real files. Use `--no-lfs-commit-rules` for the old per-run behaviour.
- Releases are published to PyPI with trusted publishing instead of an API token.
- The PyPI page now shows keywords, classifiers and project links.

## [0.1.1] - 2026-10-24

### Added
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ nothing staged, then asks for confirmation.
| `--log-file` | Write the log to this file instead of the default folder |
| `--lfs-threshold-mib` | With `--use-lfs`, files larger than this use LFS (default 100, `0` = every file) |
| `--use-lfs` | Store files over 100 MiB with Git LFS for this run (see below) |
| `--lfs-commit-rules` | With `--use-lfs`, also commit LFS rules so clones get the real files |
| `--no-lfs-commit-rules` | With `--use-lfs`, keep LFS rules local to the run (checkouts and clones then show pointer files, not real files) |
| `--report` | Write a JSON summary of the run to this file |
| `--notify` | Show a desktop notification when the run ends |
| `--notify-webhook` | Send a JSON summary to this web address when the run ends |
Expand Down Expand Up @@ -285,7 +285,7 @@ commitrocket --repo . --message "Add assets" # later: pushes the saved commi

**Videos in LFS that clones can download too**
```
commitrocket --repo . --message "Videos" --use-lfs --lfs-commit-rules
commitrocket --repo . --message "Videos" --use-lfs
```

**See what would happen first**
Expand Down Expand Up @@ -432,12 +432,13 @@ used up quickly. Git's own settings files (`.gitattributes`, `.gitignore`, `.lfs
always stay as normal files. The threshold cannot be above 100, since larger files are
rejected by hosts.

To let clones receive the real files, add `--lfs-commit-rules`. CommitRocket then commits
So that checkouts and clones receive the real files, CommitRocket commits the LFS rules by default. It commits
one rule per file type (for example `*.mp4`) to `.gitattributes` as `<message> (LFS rules)`,
pushes it first, and your files follow as LFS pointers. Because rules are per type, every
file of that type is stored in LFS from then on, including small ones and future ones.
Files without an extension get an exact-path rule. `.gitattributes` must have no
uncommitted changes beforehand.
uncommitted changes beforehand. Pass `--no-lfs-commit-rules` to skip this; videos then turn into
text pointer files after a pull, reset or clone.

If a run fails after committing LFS files but before pushing them, the LFS hooks are left
installed so the files can still be uploaded. Run CommitRocket again to finish; avoid a
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ remain, `130` interrupted.
`refs/remotes/<remote>/<branch>..HEAD`, or `HEAD --not --remotes=<remote>` when that
branch was never pushed.
- **`--no-push`**: skips push and verification; rejected together with `--use-lfs`.
- **`--lfs-commit-rules`**: `LfsSession.write_committed_rules` appends one rule per
- **`--lfs-commit-rules`** (default on with `--use-lfs`; `--no-lfs-commit-rules` turns it off): `LfsSession.write_committed_rules` appends one rule per
extension (`committed_rule_lines`; exact path for extension-less or odd names) to
`.gitattributes` after checking it is clean. The runner commits it as
`<message> (LFS rules)` and pushes it before the first batch. Local-only rules in
Expand Down
21 changes: 20 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,32 @@ build-backend = "hatchling.build"

[project]
name = "commitrocket"
version = "0.1.1"
version = "0.1.2"
description = "Batch Git changes by size, commit each batch, and push with live progress."
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.11"
keywords = ["git", "commit", "push", "batch", "large-files", "git-lfs", "cli"]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Version Control :: Git",
"Topic :: Utilities",
]
dependencies = ["rich>=13", "pathspec>=0.12"]

[project.urls]
Homepage = "https://github.com/coldsofttech/CommitRocket"
Issues = "https://github.com/coldsofttech/CommitRocket/issues"

[project.scripts]
commitrocket = "commitrocket.cli:main"

Expand Down
2 changes: 1 addition & 1 deletion src/commitrocket/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""CommitRocket: batch Git changes by size, commit each batch, and push."""

__version__ = "0.1.1"
__version__ = "0.1.2"
16 changes: 11 additions & 5 deletions src/commitrocket/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,18 @@ def build_parser():
)
parser.add_argument(
"--lfs-commit-rules",
dest="lfs_commit_rules",
action="store_true",
help="With --use-lfs, also commit LFS rules to .gitattributes (one per "
"file extension) so clones get real files and future files of the same "
"type use LFS",
default=True,
help="With --use-lfs, commit LFS rules to .gitattributes (one per file "
"extension) so clones and checkouts get real files (default)",
)
parser.add_argument(
"--no-lfs-commit-rules",
dest="lfs_commit_rules",
action="store_false",
help="With --use-lfs, keep the LFS rules local to this run. Checkouts "
"and clones will then contain LFS pointer files instead of the real files",
)
parser.add_argument(
"--dry-run",
Expand Down Expand Up @@ -260,8 +268,6 @@ def validate(parser, args):
"stay local and a later plain 'git push' would publish LFS pointers "
"without their data"
)
if args.lfs_commit_rules and not args.use_lfs:
parser.error("--lfs-commit-rules requires --use-lfs")

for label, value in (("--remote", args.remote), ("--branch", args.branch)):
if value is not None:
Expand Down
2 changes: 1 addition & 1 deletion src/commitrocket/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class RunOptions:
lfs_threshold_mib: int = LFS_THRESHOLD_MIB
lfs_threads: int | None = None
lfs_batch_mib: int | None = None
lfs_commit_rules: bool = False
lfs_commit_rules: bool = True
include: list = field(default_factory=list)
exclude: list = field(default_factory=list)
include_from: list = field(default_factory=list)
Expand Down
3 changes: 2 additions & 1 deletion src/commitrocket/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ def list_pending():
else:
lfs_scope = (
f"{which} will be stored with Git LFS for this run only; "
"no LFS config is committed."
"no LFS config is committed, so checkouts and clones will "
"contain LFS pointer files instead of the real files."
)
if o.dry_run and not is_lfs_available():
lfs_scope += " git-lfs is not installed and would be installed."
Expand Down
1 change: 0 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def test_own_output_files_are_found_only_inside_the_repo(tmp_path):
"argv",
[
["--message", "m", "--no-push", "--use-lfs"],
["--message", "m", "--lfs-commit-rules"],
["--message", "m", "--message-template", "{nope}"],
["--message", "m", "--lfs-threshold-mib", "150"],
["--message", "m", "--retries", "-1"],
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading