git-reattribute 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- git_reattribute-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- git_reattribute-0.1.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
- git_reattribute-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
- git_reattribute-0.1.0/.github/dependabot.yml +11 -0
- git_reattribute-0.1.0/.github/pull_request_template.md +8 -0
- git_reattribute-0.1.0/.github/workflows/ci.yml +38 -0
- git_reattribute-0.1.0/.github/workflows/publish.yml +41 -0
- git_reattribute-0.1.0/.gitignore +7 -0
- git_reattribute-0.1.0/CHANGELOG.md +19 -0
- git_reattribute-0.1.0/CONTRIBUTING.md +49 -0
- git_reattribute-0.1.0/LICENSE +21 -0
- git_reattribute-0.1.0/PKG-INFO +315 -0
- git_reattribute-0.1.0/README.md +287 -0
- git_reattribute-0.1.0/SECURITY.md +39 -0
- git_reattribute-0.1.0/pyproject.toml +45 -0
- git_reattribute-0.1.0/src/git_reattribute/__init__.py +1 -0
- git_reattribute-0.1.0/src/git_reattribute/__main__.py +4 -0
- git_reattribute-0.1.0/src/git_reattribute/branches.py +55 -0
- git_reattribute-0.1.0/src/git_reattribute/cli.py +314 -0
- git_reattribute-0.1.0/src/git_reattribute/contributors.py +81 -0
- git_reattribute-0.1.0/src/git_reattribute/errors.py +61 -0
- git_reattribute-0.1.0/src/git_reattribute/gitwrapper.py +47 -0
- git_reattribute-0.1.0/src/git_reattribute/identities.py +32 -0
- git_reattribute-0.1.0/src/git_reattribute/models.py +73 -0
- git_reattribute-0.1.0/src/git_reattribute/push.py +30 -0
- git_reattribute-0.1.0/src/git_reattribute/repository.py +38 -0
- git_reattribute-0.1.0/src/git_reattribute/rewrite.py +155 -0
- git_reattribute-0.1.0/src/git_reattribute/verify.py +52 -0
- git_reattribute-0.1.0/tests/conftest.py +108 -0
- git_reattribute-0.1.0/tests/test_branches.py +17 -0
- git_reattribute-0.1.0/tests/test_contributors.py +44 -0
- git_reattribute-0.1.0/tests/test_identities.py +32 -0
- git_reattribute-0.1.0/tests/test_push.py +31 -0
- git_reattribute-0.1.0/tests/test_repository.py +49 -0
- git_reattribute-0.1.0/tests/test_rewrite.py +129 -0
- git_reattribute-0.1.0/tests/test_verify.py +35 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something didn't work as expected
|
|
4
|
+
title: ""
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**What happened**
|
|
9
|
+
|
|
10
|
+
A clear description of the problem.
|
|
11
|
+
|
|
12
|
+
**Command run**
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
git-reattribute ...
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Expected behavior**
|
|
19
|
+
|
|
20
|
+
**Environment**
|
|
21
|
+
|
|
22
|
+
- git-reattribute version: (`git-reattribute --version`)
|
|
23
|
+
- Git version: (`git --version`)
|
|
24
|
+
- OS:
|
|
25
|
+
- Python version: (`python --version`)
|
|
26
|
+
|
|
27
|
+
**Repository context (if relevant)**
|
|
28
|
+
|
|
29
|
+
- Approximate branch/history size:
|
|
30
|
+
- Any shallow clone, submodules, signed commits, or Git LFS involved?
|
|
31
|
+
|
|
32
|
+
**Additional context**
|
|
33
|
+
|
|
34
|
+
Logs, `--verbose` output, or anything else that helps reproduce the issue.
|
|
35
|
+
Please redact any sensitive commit content, emails, or tokens before pasting.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blank_issues_enabled: true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
16
|
+
runs-on: ${{ matrix.os }}
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Configure git identity for tests
|
|
25
|
+
run: |
|
|
26
|
+
git config --global user.name "CI"
|
|
27
|
+
git config --global user.email "ci@example.com"
|
|
28
|
+
|
|
29
|
+
- name: Install package with dev dependencies
|
|
30
|
+
run: python -m pip install -e ".[dev]"
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: pytest -q
|
|
34
|
+
|
|
35
|
+
- name: Verify console script
|
|
36
|
+
run: |
|
|
37
|
+
git-reattribute --version
|
|
38
|
+
git-reattribute --help
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
|
|
17
|
+
- name: Install build tooling
|
|
18
|
+
run: python -m pip install build
|
|
19
|
+
|
|
20
|
+
- name: Build sdist and wheel
|
|
21
|
+
run: python -m build
|
|
22
|
+
|
|
23
|
+
- uses: actions/upload-artifact@v4
|
|
24
|
+
with:
|
|
25
|
+
name: dist
|
|
26
|
+
path: dist/
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment: pypi
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write # required for PyPI trusted publishing (OIDC)
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
|
|
40
|
+
- name: Publish to PyPI
|
|
41
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - Unreleased
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Initial MVP: interactive and non-interactive contributor-identity
|
|
10
|
+
replacement using `git-filter-repo`.
|
|
11
|
+
- Branch discovery and selection.
|
|
12
|
+
- Contributor discovery (author/committer, raw identity fields, no mailmap).
|
|
13
|
+
- Backup ref creation before every rewrite.
|
|
14
|
+
- Author/committer/both replacement scope via `--identity-type`.
|
|
15
|
+
- Co-authored-by trailer stripping for the replaced identity.
|
|
16
|
+
- Post-rewrite verification.
|
|
17
|
+
- `--force-with-lease` push support.
|
|
18
|
+
- Dry-run mode.
|
|
19
|
+
- Signed-commit and shallow-repository warnings.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributing to Git Reattribute
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/drk1rd/git-reattribute.git
|
|
7
|
+
cd git-reattribute
|
|
8
|
+
python -m venv .venv
|
|
9
|
+
source .venv/bin/activate # .venv\Scripts\activate on Windows
|
|
10
|
+
pip install -e ".[dev]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running tests
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pytest
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Tests build temporary Git repositories per test case (see `tests/conftest.py`)
|
|
20
|
+
— nothing is ever run against a real or shared repository. If you're adding
|
|
21
|
+
a test that exercises `RewriteEngine`, prefer extending the existing
|
|
22
|
+
`basic_repo` fixture's history over hand-rolling a new one, unless your test
|
|
23
|
+
needs a distinct topology (e.g. `mailmap_repo`, `bare_remote`).
|
|
24
|
+
|
|
25
|
+
## Making changes
|
|
26
|
+
|
|
27
|
+
- Keep runtime dependencies minimal — this is meant to stay a small, focused
|
|
28
|
+
CLI. If you're adding a dependency, explain why an existing one (or the
|
|
29
|
+
standard library) doesn't cover it.
|
|
30
|
+
- History-rewrite logic lives in `rewrite.py` and goes through
|
|
31
|
+
`git-filter-repo`; don't hand-roll Git object rewriting.
|
|
32
|
+
- Any change to `rewrite.py`, `push.py`, or `cli.py`'s confirmation/warning
|
|
33
|
+
flow should be manually verified against a scratch repository in addition
|
|
34
|
+
to the test suite, since these paths are destructive by design.
|
|
35
|
+
- Update `README.md` and `CHANGELOG.md` alongside behavior changes — the
|
|
36
|
+
README is expected to match what's actually implemented.
|
|
37
|
+
|
|
38
|
+
## Submitting a pull request
|
|
39
|
+
|
|
40
|
+
1. Fork the repo and create a branch from `main`.
|
|
41
|
+
2. Make your change, with tests.
|
|
42
|
+
3. Run `pytest` locally.
|
|
43
|
+
4. Open a PR describing what changed and why (the PR template will prompt
|
|
44
|
+
you for this).
|
|
45
|
+
|
|
46
|
+
## Reporting bugs / requesting features
|
|
47
|
+
|
|
48
|
+
Use the GitHub issue templates. For anything security-related, see
|
|
49
|
+
`SECURITY.md` instead of opening a public issue.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 git-reattribute contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: git-reattribute
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactively replace one Git contributor identity with another, safely.
|
|
5
|
+
Project-URL: Homepage, https://github.com/drk1rd/git-reattribute
|
|
6
|
+
Project-URL: Issues, https://github.com/drk1rd/git-reattribute/issues
|
|
7
|
+
Author: git-reattribute contributors
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: attribution,cli,git,history,rewrite
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: git-filter-repo>=2.38
|
|
22
|
+
Requires-Dist: questionary>=2.0
|
|
23
|
+
Requires-Dist: typer>=0.12
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Git Reattribute
|
|
30
|
+
|
|
31
|
+
[](https://github.com/drk1rd/git-reattribute/actions/workflows/ci.yml)
|
|
32
|
+
[](https://pypi.org/project/git-reattribute/)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
35
|
+
A small, cross-platform CLI for replacing one Git identity with another
|
|
36
|
+
across a repository's history, safely — without requiring you to hand-write
|
|
37
|
+
a `git-filter-repo` invocation.
|
|
38
|
+
|
|
39
|
+
> **⚠️ This tool rewrites Git history.** Rewriting history changes commit
|
|
40
|
+
> SHAs, invalidates existing signatures on rewritten commits, and requires a
|
|
41
|
+
> force push to update any already-published branch. A force push can
|
|
42
|
+
> disrupt collaborators and invalidate their existing clones. Only use this
|
|
43
|
+
> on repositories and history you are authorized to modify. This tool does
|
|
44
|
+
> not modify GitHub (or any other host's) account data or contributor
|
|
45
|
+
> database directly — hosts may independently recompute contributor
|
|
46
|
+
> attribution from the resulting commit metadata.
|
|
47
|
+
|
|
48
|
+
## Why?
|
|
49
|
+
|
|
50
|
+
A Git "identity" isn't always a person — it can be a placeholder, a bot
|
|
51
|
+
account, a shared machine account, or an AI agent that paired on the work.
|
|
52
|
+
Correcting a mis-attributed one usually means hand-writing a
|
|
53
|
+
`git-filter-repo` invocation. Git Reattribute turns that into one guided
|
|
54
|
+
command.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- Interactive branch, contributor, and replacement-identity selection.
|
|
59
|
+
- Non-interactive / scriptable usage via flags.
|
|
60
|
+
- Replaces author, committer, or both (default: both).
|
|
61
|
+
- Removes matching `Co-authored-by:` trailers for the replaced identity.
|
|
62
|
+
- Dry-run mode — preview the impact with no changes made.
|
|
63
|
+
- Creates a backup ref before every rewrite.
|
|
64
|
+
- Mandatory post-rewrite verification.
|
|
65
|
+
- `--force-with-lease` push only, never `--force`, never silent.
|
|
66
|
+
- Warns about signed commits and shallow repositories before rewriting.
|
|
67
|
+
|
|
68
|
+
## Requirements
|
|
69
|
+
|
|
70
|
+
- Python 3.10+
|
|
71
|
+
- Git 2.x
|
|
72
|
+
- `git-filter-repo` — installed automatically as a dependency when you
|
|
73
|
+
`pip install git-reattribute`; you don't need to install it separately.
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m pip install git-reattribute
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd your-repo
|
|
85
|
+
git-reattribute --version
|
|
86
|
+
git-reattribute --help
|
|
87
|
+
git-reattribute
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Example
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
$ git-reattribute
|
|
94
|
+
|
|
95
|
+
Git Reattribute
|
|
96
|
+
|
|
97
|
+
Repository: /home/alice/projects/example
|
|
98
|
+
Remote: origin
|
|
99
|
+
Branch: main
|
|
100
|
+
|
|
101
|
+
Contributors on main:
|
|
102
|
+
|
|
103
|
+
1. Claude <claude@example.com> 47 commits
|
|
104
|
+
2. Alice <alice@example.com> 18 commits
|
|
105
|
+
3. Bob <bob@example.com> 6 commits
|
|
106
|
+
|
|
107
|
+
Select contributor to replace: Claude <claude@example.com> 47 commits
|
|
108
|
+
|
|
109
|
+
Replace with:
|
|
110
|
+
Current Git identity
|
|
111
|
+
Alice <alice@example.com>
|
|
112
|
+
Bob <bob@example.com>
|
|
113
|
+
Enter a custom identity
|
|
114
|
+
|
|
115
|
+
Select replacement: Alice <alice@example.com>
|
|
116
|
+
|
|
117
|
+
Summary
|
|
118
|
+
-------
|
|
119
|
+
Branch: main
|
|
120
|
+
From: Claude <claude@example.com>
|
|
121
|
+
To: Alice <alice@example.com>
|
|
122
|
+
Author commits affected: 47
|
|
123
|
+
Committer commits affected: 47
|
|
124
|
+
Co-authored-by trailers to remove: 3
|
|
125
|
+
|
|
126
|
+
WARNING: This operation rewrites Git history.
|
|
127
|
+
|
|
128
|
+
Type REWRITE to continue: REWRITE
|
|
129
|
+
|
|
130
|
+
History rewrite completed.
|
|
131
|
+
|
|
132
|
+
Recovery reference:
|
|
133
|
+
refs/backup/git-reattribute/2026-08-22T10-30-00
|
|
134
|
+
|
|
135
|
+
To restore the original branch:
|
|
136
|
+
git reset --hard refs/backup/git-reattribute/2026-08-22T10-30-00
|
|
137
|
+
|
|
138
|
+
Keep this reference until you have verified the rewritten history.
|
|
139
|
+
|
|
140
|
+
Verification
|
|
141
|
+
|
|
142
|
+
Old identity commits before: 94
|
|
143
|
+
Remaining old identity commits: 0
|
|
144
|
+
New identity commits: 94
|
|
145
|
+
Remaining Co-authored-by trailers: 0
|
|
146
|
+
|
|
147
|
+
Verification: PASS
|
|
148
|
+
|
|
149
|
+
Push rewritten branch to origin with --force-with-lease? [y/N]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Interactive Usage
|
|
153
|
+
|
|
154
|
+
Running `git-reattribute` with no flags walks you through: select a branch,
|
|
155
|
+
see its contributors, pick who to replace, pick the replacement, preview the
|
|
156
|
+
change, then type `REWRITE` to confirm.
|
|
157
|
+
|
|
158
|
+
## Non-Interactive Usage
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
git-reattribute \
|
|
162
|
+
--branch main \
|
|
163
|
+
--from-email claude@example.com \
|
|
164
|
+
--to-name Alice \
|
|
165
|
+
--to-email alice@example.com \
|
|
166
|
+
--identity-type both \
|
|
167
|
+
--yes --push
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`--yes` does not imply `--push` — pass both explicitly when you want a
|
|
171
|
+
scripted rewrite-and-push.
|
|
172
|
+
|
|
173
|
+
## Dry Run
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git-reattribute --branch main --from-email claude@example.com --to-current-user --dry-run
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Shows affected author/committer commit counts and any `Co-authored-by:`
|
|
180
|
+
trailers that would be removed. No history or remote is touched.
|
|
181
|
+
|
|
182
|
+
## Branches
|
|
183
|
+
|
|
184
|
+
Local branches are listed and selectable. Remote-tracking branches are not
|
|
185
|
+
rewritten directly — check out a local branch first. The rewrite scope is
|
|
186
|
+
the **entire reachable history of the selected branch** — every commit an
|
|
187
|
+
ancestor of that branch's tip, not just commits made while that branch was
|
|
188
|
+
checked out.
|
|
189
|
+
|
|
190
|
+
## Authors vs Committers
|
|
191
|
+
|
|
192
|
+
Every commit has an author and a committer, and they can differ. Use
|
|
193
|
+
`--identity-type author`, `--identity-type committer`, or the default
|
|
194
|
+
`--identity-type both` to control which field(s) get rewritten. The
|
|
195
|
+
interactive UI shows both roles whenever they differ for a given contributor.
|
|
196
|
+
|
|
197
|
+
## Co-authored-by Trailers
|
|
198
|
+
|
|
199
|
+
When an identity (for example an AI agent that paired with the human author)
|
|
200
|
+
appears as a `Co-authored-by:` trailer in a commit message, that trailer is
|
|
201
|
+
**removed** (not replaced) for the identity being replaced, by default. This
|
|
202
|
+
matters because rewriting only the author field of a commit like:
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
Author: Claude <claude@example.com>
|
|
206
|
+
|
|
207
|
+
Co-authored-by: Claude <claude@example.com>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
would otherwise leave the old identity behind in the message body even
|
|
211
|
+
after the commit object's author is fixed.
|
|
212
|
+
|
|
213
|
+
This runs alongside the author/committer rewrite for the same source
|
|
214
|
+
identity — there is no separate replacement target for trailers, and the
|
|
215
|
+
trailer is deleted, not rewritten to a new co-author. Disable it with
|
|
216
|
+
`--no-strip-coauthor-trailers`.
|
|
217
|
+
|
|
218
|
+
**Matching rule:** a trailer line matches when it starts with
|
|
219
|
+
`Co-authored-by:` (case-insensitive) followed by the source identity's name
|
|
220
|
+
and `<email>`, with any amount of whitespace tolerated around the colon,
|
|
221
|
+
name, and angle brackets — but the name and email themselves must match the
|
|
222
|
+
source identity's recorded name/email (case-insensitively), not a partial or
|
|
223
|
+
fuzzy match. Only matching trailer lines are removed; other trailers (e.g.
|
|
224
|
+
`Signed-off-by:`) and body text that merely mentions the same name are left
|
|
225
|
+
untouched.
|
|
226
|
+
|
|
227
|
+
## History Rewriting Warning
|
|
228
|
+
|
|
229
|
+
This tool rewrites Git history using [`git-filter-repo`](https://github.com/newren/git-filter-repo).
|
|
230
|
+
Two different `--force` flags matter here, and they are not the same thing:
|
|
231
|
+
|
|
232
|
+
- **`git-filter-repo --force`** (used internally, always) only permits the
|
|
233
|
+
*local* rewrite to run on a non-fresh-clone repository. `git-filter-repo`
|
|
234
|
+
normally refuses to touch anything but a fresh clone as a generic safety
|
|
235
|
+
net; this tool creates its own backup ref (see **Recovery** below) *before*
|
|
236
|
+
that rewrite ever runs, so that backup is the safety net instead, and the
|
|
237
|
+
`--force` flag just lets the local rewrite proceed.
|
|
238
|
+
- **`git push --force`** is never used by this tool, anywhere. Publishing a
|
|
239
|
+
rewritten branch always uses `git push --force-with-lease` (see
|
|
240
|
+
**Push Behavior**), and only after you explicitly confirm it.
|
|
241
|
+
|
|
242
|
+
Contributor discovery uses Git's raw `%an`/`%ae`/`%cn`/`%ce` fields, not
|
|
243
|
+
mailmap-resolved fields — a repository's `.mailmap` is intentionally ignored
|
|
244
|
+
so what you see and rewrite always matches the actual commit-object bytes.
|
|
245
|
+
|
|
246
|
+
Signed commits lose their signatures when rewritten — a warning is shown
|
|
247
|
+
before you confirm. Merge commits are rewritten natively by `git-filter-repo`
|
|
248
|
+
as part of a full-branch rewrite; this requires no special handling in v1
|
|
249
|
+
since there is no commit-range filtering yet.
|
|
250
|
+
|
|
251
|
+
## Push Behavior
|
|
252
|
+
|
|
253
|
+
Pushing always uses `git push --force-with-lease`, never `git push --force`.
|
|
254
|
+
Nothing is pushed unless you pass `--push` (scripted) or confirm the
|
|
255
|
+
interactive prompt. If the remote branch changed since the rewrite began,
|
|
256
|
+
the push is rejected and nothing is retried automatically.
|
|
257
|
+
|
|
258
|
+
## Recovery
|
|
259
|
+
|
|
260
|
+
Before every rewrite — and before `git-filter-repo` is ever invoked — a
|
|
261
|
+
backup ref is created pointing at the original branch tip:
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
refs/backup/git-reattribute/<timestamp>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The rewrite is scoped (via `--refs <branch>`) to only the selected branch,
|
|
268
|
+
so the backup ref itself is never touched or rewritten by the same
|
|
269
|
+
operation. It is never deleted automatically. After a rewrite, the tool
|
|
270
|
+
prints the exact recovery command:
|
|
271
|
+
|
|
272
|
+
```text
|
|
273
|
+
Recovery reference:
|
|
274
|
+
refs/backup/git-reattribute/2026-08-22T10-30-00
|
|
275
|
+
|
|
276
|
+
To restore the original branch:
|
|
277
|
+
git reset --hard refs/backup/git-reattribute/2026-08-22T10-30-00
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Keep the backup ref until you've verified the rewritten history (and pushed,
|
|
281
|
+
if applicable) — then it's safe to delete with
|
|
282
|
+
`git update-ref -d refs/backup/git-reattribute/<timestamp>`.
|
|
283
|
+
|
|
284
|
+
## Limitations
|
|
285
|
+
|
|
286
|
+
- Shallow clones may show an incomplete contributor list and rewrite scope;
|
|
287
|
+
run `git fetch --unshallow` first.
|
|
288
|
+
- Submodules, Git LFS objects, and annotated tag signatures are not
|
|
289
|
+
specially handled in v1.
|
|
290
|
+
- Branch-protection or remote-policy push rejections are surfaced as-is;
|
|
291
|
+
this tool never attempts to bypass them.
|
|
292
|
+
- One source identity → one target identity per run.
|
|
293
|
+
|
|
294
|
+
## Development
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
python -m pip install -e ".[dev]"
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Testing
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
pytest
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Tests build temporary Git repositories per-test; nothing is ever run against
|
|
307
|
+
a real/shared repository.
|
|
308
|
+
|
|
309
|
+
## Release Process
|
|
310
|
+
|
|
311
|
+
See `CHANGELOG.md`. Versioning follows SemVer (`MAJOR.MINOR.PATCH`).
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
MIT — see `LICENSE`.
|