Git Editor is a sophisticated command-line tool written in Rust for comprehensive Git commit history manipulation. It provides surgical precision for rewriting timestamps, author information, and commit messages while maintaining repository integrity.
# Download the latest release for your platform
wget https://github.com/rohansen856/git-editor/releases/latest/download/git-editor-linux-x86_64.tar.gz
tar -xzf git-editor-linux-x86_64.tar.gz
sudo mv git-editor-linux-x86_64 /usr/local/bin/git-editor
# Verify installation
git-editor --help
# Prerequisites: Rust 1.87+ and a C compiler (libgit2 is built from source)
rustup update stable
# Clone and build
git clone https://github.com/rohansen856/git-editor.git
cd git-editor
# Build release binary
cargo build --release
# Install globally
sudo cp target/release/git-editor /usr/local/bin/
# Or use cargo install
cargo install --path .
# Build Docker image
docker build -t git-editor .
# Run with repository mounted (-it is required: every prompt needs a TTY).
# The image runs as root; on Linux with rootful Docker, pass your uid so that
# libgit2's repository-ownership check passes and no root-owned files are written:
docker run -it --user "$(id -u):$(id -g)" -e HOME=/tmp \
-v /path/to/repo:/workspace git-editor --repo-path /workspace -s
Git Editor operates in distinct modes, each optimized for specific use cases:
| Mode | Primary Flag | Description | Use Case | Required Args |
|---|---|---|---|---|
| Full Rewrite | Default | Complete repository history rewriting | Bulk timestamp and author updates | name, email, begin, end |
| Show History | -s, --show-history |
Display commit history without modifications | Repository analysis and planning | repo-path only |
| Pick Specific | -p, --pick-specific-commits |
Interactive individual commit selection | Targeted commit modifications | repo-path only |
| Range Edit | -x, --range |
Select and edit commit ranges | Batch editing of consecutive commits | repo-path only |
| Simulation | --simulate |
Preview changes without applying | Safe testing and validation | Modifier for full rewrite, -p and -x |
| Documentation | --docs |
Open comprehensive documentation | Help and reference | None |
Precedence Order (highest to lowest):
--docs - Always takes precedence--simulate - Dry-run preview (full rewrite path)--range (-x) - Range-based editing--pick-specific-commits (-p) - Interactive single-commit selection--show-history (-s) - Read-only history display| Argument | Short | Type | Description | Example | Required |
|---|---|---|---|---|---|
--repo-path |
-r |
String | Local path or Git URL to repository | -r /path/to/repo-r https://github.com/user/repo.git |
โ (defaults to ./) |
--email |
- | New author email (full rewrite, -p --commit, -x --select) |
--email user@example.com |
Full rewrite (prompted if missing) | |
--name |
-n |
String | New author name | -n "John Doe" |
Full rewrite (prompted if missing) |
--begin |
-b |
DateTime | First new date: YYYY-MM-DD HH:MM:SS (UTC), optionally with an offset such as +05:30, or RFC 3339 |
-b "2023-01-01 09:00:00 +05:30" |
Full rewrite unless --keep-dates |
--end |
-e |
DateTime | Last new date (same formats) | -e "2023-12-31 17:00:00" |
Full rewrite unless --keep-dates |
--skip-range-check |
- | Flag | Allow ranges shorter than 3h per commit: random gaps of at least 5 minutes, or even spacing when that does not fit (never duplicate seconds) | --skip-range-check |
โ |
--keep-dates |
- | Flag | Full rewrite: keep every commit's original date and time zone | --keep-dates |
โ |
--committer |
- | Enum | match-author (default): committer follows the edited author fields; keep: committer unchanged |
--committer keep |
โ |
--commit |
- | String | With -p: commit number (1 = newest, as listed by -s) or hash prefix, no menu |
-p --commit 3 |
โ |
--set-date / --set-message |
- | String | With -p --commit: new author date / new message |
--set-message "Fix typo" |
โ |
--select |
- | Range | With -x: N-M, N or *, no table; combine with --name, --email, --begin/--end |
-x --select 2-5 |
โ |
--yes |
-y |
Flag | Confirm automatically (scripts and agents) | --yes |
โ |
--json |
- | Flag | Print one JSON document on stdout (history, preview, result or error); progress goes to stderr | -s --json |
โ |
--clone-dir |
- | Path | When --repo-path is a URL, keep the clone in this directory (required to rewrite a URL) |
--clone-dir ./repo-copy |
โ |
--docs-out |
- | Path | With --docs: write the HTML to this file |
--docs --docs-out docs.html |
โ |
| Flag | Short | Description | Conflicts With | Additional Options |
|---|---|---|---|---|
--show-history |
-s |
Display repository commit history | Other mode flags (-s, -p, -x, --docs): rejected |
None |
--pick-specific-commits |
-p |
Interactive commit selection | Other mode flags (-s, -p, -x, --docs): rejected |
None |
--range |
-x |
Range-based commit editing | Other mode flags (-s, -p, -x, --docs): rejected |
--message, --author, --time |
--simulate |
- | Dry-run mode (preview only) | -s, --docs |
--show-diff |
--docs |
- | Open documentation in browser | Every other mode and --simulate: rejected |
None |
These flags work exclusively with --range mode:
| Flag | Description | Effect |
|---|---|---|
--message |
Edit only commit messages | Author name/email and timestamp fields are not editable; author, committer and dates stay untouched |
--author |
Edit only author information | Message and timestamp fields are not editable |
--time |
Edit only timestamps | Message and author fields are not editable |
--range, all fields (message, author, timestamp) will be editable.
| Flag | Description | Output | Requires |
|---|---|---|---|
--show-diff |
Show detailed change preview | Before/after comparison for each commit | Any preview (dry run or before confirming) |
Git Editor distributes timestamps with random weighted gaps while preserving commit order:
--begin, the newest gets exactly --end, and dates strictly increase in between--begin/--end)--skip-range-check: for ranges shorter than 3h ร (commits โ 1): gaps of at least 5 minutes, or even spacing (at least one second apart) if 5 minutes do not fit+05:30) the generated dates are written in that zone--keep-dates (or pressing Enter on both prompted date defaults) keeps every commit's own date and offset and changes only the identity# Git Editor performs these validations:
1. The path exists and is (inside) a Git repository; bare repositories work
2. Email / name / dates are valid; names and emails may not contain < > or newlines
3. Date range large enough for commit count (unless --skip-range-check)
4. HEAD is a branch with commits (detached or unborn HEAD is refused)
5. The branch did not move since the preview (otherwise nothing is written)
# Internal rewriting workflow:
1. Build a plan: edits keyed by commit id (previews render the same plan)
2. Walk HEAD history oldest-first
3. Reuse every commit that has no edit and unchanged parents (same id, signature kept)
4. Rebuild the others from their raw object: new parents, edited author fields,
committer per --committer; encoding, mergetag, other headers, raw message
bytes and time-zone offsets are kept; signatures are dropped (they would not verify)
5. Rewrite matching Signed-off-by / Co-authored-by / Authored-by lines in the trailer block
6. Save the old tip as refs/git-editor/backup/<branch>
7. Move refs/heads/<branch> with a compare-and-swap; report other refs still on the old history
# Undo:
git reset --keep refs/git-editor/backup/<branch>
-p --commit and -x --select replace the menu and table; --yes confirms. Without a terminal, prompts read plain lines from stdin and fail (instead of hanging) when stdin is closed.--json prints one document on stdout: the history, the preview, the rewrite result (old/new ids, backup ref, stale refs) or the error.-s --json โ --simulate --json โ --yes --json. See AGENTS.md in the repository.y or yes.q or Ctrl+C to cancel without saving. Long tables scroll; validation errors are shown below the table..; blank lines and indentation are kept.+05:30.https://github.com/user/repo.gitgit@github.com:user/repo.git (via ssh-agent)http://example.com/repo.gitgit://example.com/repo.git-s, --simulate) use a temporary clone removed on exit--clone-dir <DIR>; push from that directory afterwards| Repository Size | Commit Count | Processing Time | Memory Usage |
|---|---|---|---|
| Small (<10MB) | <1,000 | <10 seconds | <50MB |
| Medium (10-100MB) | 1,000-10,000 | 10-60 seconds | 50-200MB |
| Large (100MB-1GB) | 10,000-100,000 | 1-10 minutes | 200MB-1GB |
| Enterprise (>1GB) | >100,000 | >10 minutes | >1GB |
git-editor --docs
Opens this comprehensive documentation in your default browser.
# Show history of current directory
git-editor -s
# Analyze specific repository
git-editor --repo-path /path/to/repo --show-history
# Analyze remote repository
git-editor -r https://github.com/user/repo.git -s
Displays comprehensive commit history without making any changes.
git-editor \
--name "John Doe" \
--email john@example.com \
--begin "2023-01-01 09:00:00" \
--end "2023-12-31 17:00:00"
Rewrites all commits reachable from HEAD with new author info and timestamps distributed between the specified dates, then moves the current branch to the new tip.
# Clone into a directory you keep, rewrite it, then push from it
git-editor \
--repo-path https://github.com/user/repo.git \
--clone-dir ./repo-rewritten \
--name "Corporate Identity" \
--email corporate@company.com \
--begin "2023-06-01 08:00:00" \
--end "2023-06-30 18:00:00"
git -C ./repo-rewritten push --force-with-lease
Without --clone-dir, a URL is cloned into a temporary directory that is removed on exit; that is fine for -s and --simulate, and write modes refuse to run so a rewrite is never silently lost.
# Interactive commit picker
git-editor --pick-specific-commits
# With specific repository
git-editor -r /path/to/repo --pick-specific-commits
Provides an interactive interface to select and edit individual commits.
# Edit all fields in a range
git-editor --range
# Edit only commit messages
git-editor --range --message
# Edit only author information
git-editor --range --author
# Edit only timestamps
git-editor --range --time
# Multiple field editing
git-editor --range --message --author
Select a range of commits (e.g., commits 5-11) and edit specific fields.
# Basic simulation
git-editor --simulate \
--name "Test User" \
--email test@example.com \
--begin "2023-01-01 09:00:00" \
--end "2023-01-31 17:00:00"
# Detailed diff preview
git-editor --simulate --show-diff \
--name "John Doe" \
--email john@example.com \
--begin "2023-06-01 08:00:00" \
--end "2023-06-30 18:00:00"
Previews all changes with detailed diffs without applying them.
# Preview a pick-mode edit (menu or flags)
git-editor --simulate -p --commit 2 --set-message "Reworded"
# Preview a range edit made in the table, or from flags
git-editor --simulate --range --message
git-editor --simulate --show-diff -x --select 1-3 --name "Jane"
With -p and -x, simulation runs the normal selection and editing, shows exactly what would be written, and stops before writing.
# Anonymize commit history
git-editor \
--name "Anonymous Contributor" \
--email anonymous@privacy.local \
--begin "2023-01-01 00:00:00" \
--end "2023-12-31 23:59:59"
# Corporate identity standardization
git-editor \
--name "Development Team" \
--email dev-team@company.com \
--begin "2023-04-01 09:00:00" \
--end "2023-04-30 17:00:00"
Standardize or anonymize commit authorship for privacy or corporate compliance.
# Compress 6 months of work into 1 month
git-editor \
--name "Rapid Developer" \
--email rapid@dev.com \
--begin "2023-11-01 08:00:00" \
--end "2023-11-30 20:00:00"
# Expand 1 week into 3 months
git-editor \
--name "Consistent Contributor" \
--email consistent@dev.com \
--begin "2023-09-01 09:00:00" \
--end "2023-11-30 17:00:00"
Adjust project timelines for demos, portfolio presentation, or analysis.
Timestamps are generated with random weighted gaps inside --begin/--end, keeping commit order. Default rule: at least 3 hours between commits. Use --skip-range-check for tighter packing (5-minute minimum gap).
The interactive commit picker provides:
Range editing supports:
start-end (e.g. 5-11) or * for all commits--message, --author, and/or --timeSimulation Mode provides:
--show-diff# Supported URL formats:
https://github.com/user/repo.git
https://github.com/user/repo
git@github.com:user/repo.git
https://gitlab.com/user/repo.git
https://bitbucket.org/user/repo.git
# URL normalization and validation
# Automatic .git suffix handling
# Repository name extraction for display (the temporary directory itself has a random name)
git-editor/
โโโ src/
โ โโโ main.rs # Entry point and mode dispatch
โ โโโ lib.rs # Library re-exports (args, rewrite, utils)
โ โโโ args.rs # CLI argument parsing (clap)
โ โโโ docs.rs # Documentation generation
โ โโโ rewrite/
โ โ โโโ mod.rs # Rewrite module exports
โ โ โโโ rewrite_all.rs # Full history rewriting
โ โ โโโ rewrite_specific.rs # Interactive commit selection
โ โ โโโ rewrite_range.rs # Range-based editing (crossterm TUI)
โ โโโ utils/
โ โโโ mod.rs # Utility module exports
โ โโโ types.rs # Common types and aliases
โ โโโ validator.rs # Input validation
โ โโโ datetime.rs # Timestamp generation
โ โโโ commit_history.rs# Git operations
โ โโโ prompt.rs # User interaction
โ โโโ git_clone.rs # Repository cloning
โ โโโ git_config.rs # Git configuration
โ โโโ message_trailers.rs # Signed-off-by / Co-authored-by rewrites
โ โโโ simulation.rs # Dry-run functionality
โ โโโ help.rs # Custom help text (unused; clap --help is primary)
โโโ docs/
โ โโโ template.html # Documentation template
โโโ tests/
โ โโโ integration_tests.rs # Integration test suite
โโโ Cargo.toml # Dependencies and metadata
โโโ Dockerfile # Container configuration
โโโ Makefile # Build automation
| Crate | Version | Purpose | Features Used |
|---|---|---|---|
git2 |
0.20+ | Git repository operations | Repository access, commit manipulation |
clap |
4.5+ | Command-line argument parsing | Derive API, help generation |
chrono |
0.4+ | Date/time handling | DateTime parsing (naive UTC; no time-zone handling) |
colored |
3.0+ | Terminal output coloring | ANSI color codes, cross-platform |
tempfile |
3.20+ | Temporary directory management | Auto-cleanup, secure temp files |
open |
5.0+ | Browser launching | Cross-platform file opening |
url |
2.5+ | URL parsing and validation | Git URL parsing |
regex |
1.11+ | Pattern matching | Email validation, date parsing |
| Test Type | Count | Coverage | Purpose |
|---|---|---|---|
| Unit Tests | 100 | Core logic | Parsing, validation, dates, trailers, table editor, JSON |
| Engine Tests | 12 | Rewrite engine | Commit reuse, preserved headers/offsets, committer modes, merges, backup refs, git fsck |
| End-to-End Tests | 11 | CLI binary | Non-interactive runs with --yes/--json against throwaway repositories |
| Integration Tests | 21 | Library flows | History, timestamps, validation, docs, argument parsing |
| Doc Tests | 0 | Documentation | Code example validation |
# Development build (fast compilation)
cargo build
# Release build (optimized)
cargo build --release
# Run with arguments
cargo run -- --help
cargo run -- --repo-path . -s
# Watch mode for development
cargo watch -x "run -- --docs"
# Run all tests (unit, engine, end-to-end, integration)
cargo test
# Run unit tests only
cargo test --lib
# Run the rewrite engine and end-to-end suites
cargo test --test engine
cargo test --test e2e
# Run integration tests only
cargo test --test integration_tests
# Run specific test with output
cargo test --test integration_tests test_show_history_mode_integration -- --nocapture
# Run tests without browser opening (for development)
GIT_EDITOR_NO_BROWSER=1 cargo test --all
# Format code
cargo fmt
# Check formatting without changes
cargo fmt --check
# Run linter
cargo clippy
# Run linter with strict warnings (CI requirement)
cargo clippy --all-targets --all-features -- -D warnings
# Security audit
cargo audit
# Check for outdated dependencies
cargo outdated
# Build release binary
make build
# Run all tests
make test
# Check code formatting
make fmt
# Run clippy linter
make lint
# Run with default parameters
make run
# Run with custom environment variables
make run-custom
# Clean build artifacts
make clean
# Build Docker image
make docker-build
# Run in Docker container
make docker-run
# Install binary to /usr/local/bin
make install
# Show all available commands
make help
cargo buildcargo runcargo test to ensure no regressionscargo fmt and cargo clippymake test--docsError: Invalid repository path or URL: /invalid/path
Error: Repository path does not contain a valid Git repository: /path
Solutions:
ls -la /path/to/repols -la /path/to/repo/.gitls -ld /path/to/repoError: Invalid start date format (expected YYYY-MM-DD HH:MM:SS): 2023-1-1
Solutions:
YYYY-MM-DD HH:MM:SS2023-01-01 09:00:00Error: Invalid email format: user@domain
Solutions:
user@domain.comletters, digits, . _ % + - before @, a domain with a dot), not full RFC 5322Error: Failed to clone repository '<url>': authentication failed: no usable credentials from ssh-agent or git credential helpers
Solutions:
ssh-add -l)git config --global credential.helper ...)git clone first; credentials embedded in URLs are masked in output# For large repositories, consider:
# 1. Use simulation mode first
git-editor --simulate --show-diff [other-args]
# 2. Process in smaller ranges
git-editor --range --message # Edit specific fields only
# 3. Monitor system resources
htop # Watch memory and CPU usage
# 4. Ensure sufficient disk space
df -h # Check available space
Memory optimization tips:
Answer: History rewriting is permanent. However, you can:
git reset --keep refs/git-editor/backup/<branch>Answer: Yes, Git Editor can clone and process remote repositories:
--clone-dirgit push --force-with-leaseAnswer: Use with extreme caution:
If you encounter issues:
git-editor --docs// Walk HEAD and optionally print history
pub fn get_commit_history(args: &Args, print: bool) -> Result<Vec<CommitInfo>>
// Generate random-gap timestamps for full rewrite (mutates args if cloning)
pub fn generate_timestamps(args: &mut Args) -> Result<Vec<NaiveDateTime>>
// Full repository history rewrite (timestamps from generate_timestamps)
pub fn rewrite_all_commits(args: &Args, timestamps: Vec<NaiveDateTime>) -> Result<()>
// Interactive single-commit selection and editing
pub fn rewrite_specific_commits(args: &Args) -> Result<()>
// Range-based commit editing (crossterm TUI)
pub fn rewrite_range_commits(args: &Args) -> Result<()>
pub struct CommitInfo {
pub oid: git2::Oid, // Full commit object id
pub short_hash: String, // Abbreviated hash
pub timestamp: NaiveDateTime, // Commit timestamp
pub author_name: String, // Author display name
pub author_email: String, // Author email address
pub message: String, // Commit message
pub parent_count: usize, // Number of parent commits
}
pub struct EditOptions {
pub author_name: Option<String>,
pub author_email: Option<String>,
pub timestamp: Option<NaiveDateTime>,
pub message: Option<String>,
}
| Variable | Purpose | Default | Example |
|---|---|---|---|
GIT_EDITOR_NO_BROWSER |
Disable browser opening for docs | Not set | GIT_EDITOR_NO_BROWSER=1 |
NO_BROWSER |
Alternative browser disable flag | Not set | NO_BROWSER=1 |
| Code | Meaning | Description |
|---|---|---|
0 |
Success | Operation completed (including "nothing to change") |
1 |
Error | Any failure, printed as Error: โฆ on stderr (or as a JSON error with --json) |
2 |
Usage error | Invalid or conflicting arguments |
130 |
Cancelled | Declined confirmation, Esc or Ctrl+C |