# History Rewriting in Git
This document covers common git history rewriting operations, including the specific use case of removing commits between tags.
## Table of Contents
1. [Removing Commits Between Tags](#removing-commits-between-tags)
2. [Interactive Rebase](#interactive-rebase)
3. [Filter Branch Operations](#filter-branch-operations)
4. [History Cleanup](#history-cleanup)
5. [Safety Considerations](#safety-considerations)
## Removing Commits Between Tags
### Use Case
You need to remove specific commits that exist between two tags while preserving the rest of the history. This is often needed when:
- Removing experimental features that didn't work out
- Cleaning up accidental commits
- Preparing a clean release history
- Removing sensitive data from history
### Manual Terminal Process
```bash
# Step 1: Identify the commits between tags
git log --oneline tag1..tag2
# Step 2: Create a backup branch (safety first!)
git checkout -b backup-before-rewrite
# Step 3: Go back to the branch you want to rewrite
git checkout main
# Step 4: Start interactive rebase from the commit before tag1
git rebase -i tag1^
# Step 5: In the editor, mark commits to remove
# Change 'pick' to 'drop' for commits you want to remove
# Example editor content:
# pick abc1234 Keep this commit
# drop def5678 Remove this commit
# pick ghi9012 Keep this commit
# Step 6: Save and close the editor, let rebase proceed
# Step 7: If conflicts occur, resolve them
git status
# Edit conflicted files
git add <resolved-files>
git rebase --continue
# Step 8: Update tag2 to point to the new commit
# First, find the new commit hash that should have tag2
git log --oneline
# Then force-update the tag
git tag -f tag2 <new-commit-hash>
# Step 9: If you need to update remote
git push --force-with-lease origin main
git push --force origin tag2
```
### Common Issues and Solutions
#### Issue 1: Merge Commits
If there are merge commits between the tags, the rebase becomes more complex:
```bash
# Use --preserve-merges or --rebase-merges
git rebase -i --rebase-merges tag1^
```
#### Issue 2: Tag Dependencies
If other tags or branches depend on the removed commits:
```bash
# List all tags that might be affected
git tag --contains <commit-to-remove>
# Update each affected tag after rebase
git tag -f affected-tag <new-commit>
```
#### Issue 3: Shared History
If the history has been shared with others:
```bash
# Coordinate with team before force pushing
# Use --force-with-lease for safety
git push --force-with-lease origin main
# Notify team members to reset their local branches
# Team members should run:
git fetch origin
git reset --hard origin/main
```
### Proposed MCP Tool
```yaml
tool: rewrite_history_between_tags
parameters:
start_tag: string (required)
end_tag: string (required)
commits_to_remove: array[string] (optional - commit hashes)
preserve_merges: boolean (default: true)
create_backup: boolean (default: true)
force_execute: boolean (default: false)
workflow:
1. Validate tags exist
2. Create backup branch if requested
3. Identify commits between tags
4. Show preview of changes
5. If force_execute, perform rebase
6. Update end_tag to new position
7. Show summary of changes
safety_features:
- Automatic backup creation
- Preview mode by default
- Conflict detection and guidance
- Tag dependency analysis
- Remote status checking
```
## Interactive Rebase
### Common Operations
#### Reordering Commits
```bash
git rebase -i HEAD~5
# In editor, reorder the lines to change commit order
```
#### Squashing Commits
```bash
git rebase -i HEAD~3
# Change 'pick' to 'squash' for commits to combine
```
#### Editing Commit Messages
```bash
git rebase -i HEAD~2
# Change 'pick' to 'reword' for commits to edit
```
#### Splitting Commits
```bash
git rebase -i HEAD~1
# Change 'pick' to 'edit'
# When rebase stops:
git reset HEAD^
git add -p # Add changes partially
git commit -m "First part"
git add .
git commit -m "Second part"
git rebase --continue
```
### Advanced Rebase Options
```bash
# Rebase onto different branch
git rebase --onto new-base old-base branch
# Rebase with autosquash
git commit --fixup <commit-hash>
git rebase -i --autosquash <base>
# Rebase with exec commands
git rebase -i --exec "make test" HEAD~3
```
## Filter Branch Operations
### Removing Files from History
```bash
# Remove a file from all commits
git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
# More efficient with --index-filter
git filter-branch --index-filter 'git rm --cached --ignore-unmatch passwords.txt' HEAD
```
### Changing Author Information
```bash
git filter-branch --env-filter '
if [ "$GIT_COMMITTER_EMAIL" = "wrong@email.com" ]; then
export GIT_COMMITTER_EMAIL="correct@email.com"
export GIT_AUTHOR_EMAIL="correct@email.com"
fi
' HEAD
```
### Moving Subdirectory to New Repository
```bash
git filter-branch --subdirectory-filter src/component HEAD
```
## History Cleanup
### Removing Large Files
```bash
# Find large files in history
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
awk '/^blob/ {print substr($0,6)}' | \
sort --numeric-sort --key=2 | \
tail -10
# Remove with filter-branch
git filter-branch --index-filter 'git rm --cached --ignore-unmatch large-file.bin' HEAD
```
### Cleaning Reflog and Garbage Collection
```bash
# After rewriting history, clean up
git reflog expire --expire=now --all
git gc --prune=now --aggressive
```
### Using BFG Repo-Cleaner (Alternative)
```bash
# Faster alternative to filter-branch
java -jar bfg.jar --delete-files passwords.txt
java -jar bfg.jar --strip-blobs-bigger-than 100M
```
## Safety Considerations
### Before Rewriting History
1. **Create Backups**
```bash
git branch backup-$(date +%Y%m%d-%H%M%S)
```
2. **Check Remote Status**
```bash
git fetch --all
git status
```
3. **Verify No Uncommitted Changes**
```bash
git stash push -m "Before history rewrite"
```
### During History Rewrite
1. **Handle Conflicts Carefully**
- Understand why conflicts occur
- Test after each resolution
- Keep notes of changes made
2. **Verify Results**
```bash
# Compare before and after
git log --graph --oneline backup-branch..HEAD
```
### After History Rewrite
1. **Update All References**
```bash
# Update tags
git tag -l | xargs -I {} git tag -f {} {}
# Update remote branches
git push --force-with-lease --all
git push --force --tags
```
2. **Communicate Changes**
- Notify all team members
- Document what was changed and why
- Provide recovery instructions
### Recovery Options
```bash
# If something goes wrong, recover from backup
git reset --hard backup-branch
# Or use reflog
git reflog
git reset --hard HEAD@{n}
# Or fetch from remote
git fetch origin
git reset --hard origin/main
```
## Best Practices
1. **Avoid Rewriting Public History**
- Only rewrite commits not yet pushed
- Coordinate with team for shared branches
2. **Use Modern Tools**
- Prefer `git rebase` over `filter-branch` when possible
- Consider `git filter-repo` for complex operations
3. **Test First**
- Practice on a copy of the repository
- Verify the operation achieves desired result
4. **Document Changes**
- Keep a log of what was rewritten and why
- Update documentation to reflect new history
5. **Automate When Possible**
- Use scripts for repetitive operations
- Implement safety checks in automation