542 lines
14 KiB
Markdown
542 lines
14 KiB
Markdown
# Project Merge & Conflict Resolution Feature
|
|
|
|
## Overview
|
|
|
|
pyPhotoAlbum v3.0 introduces comprehensive merge conflict resolution support, enabling multiple users to edit the same album and merge their changes intelligently. The system uses UUIDs, timestamps, and a project ID to track changes and resolve conflicts.
|
|
|
|
## Table of Contents
|
|
|
|
- [Key Features](#key-features)
|
|
- [How It Works](#how-it-works)
|
|
- [File Format Changes (v3.0)](#file-format-changes-v30)
|
|
- [User Guide](#user-guide)
|
|
- [Developer Guide](#developer-guide)
|
|
- [Testing](#testing)
|
|
- [Migration from v2.0](#migration-from-v20)
|
|
|
|
---
|
|
|
|
## Key Features
|
|
|
|
### 1. **Project ID-Based Merge Detection**
|
|
- Each project has a unique `project_id` (UUID)
|
|
- **Same project_id** → Merge with conflict resolution
|
|
- **Different project_id** → Concatenate (combine all pages)
|
|
|
|
### 2. **UUID-Based Element Tracking**
|
|
- Every page and element has a stable UUID
|
|
- Elements can be tracked even when page numbers or z-order changes
|
|
- Enables reliable conflict detection across versions
|
|
|
|
### 3. **Timestamp-Based Conflict Resolution**
|
|
- All changes tracked with `created` and `last_modified` timestamps (ISO 8601 UTC)
|
|
- Automatic "Latest Wins" strategy available
|
|
- Manual conflict resolution through visual dialog
|
|
|
|
### 4. **Soft Delete Support**
|
|
- Deleted items marked with `deleted` flag and `deleted_at` timestamp
|
|
- Prevents resurrection conflicts
|
|
- Tombstone pattern ensures deleted items stay deleted
|
|
|
|
### 5. **Visual Merge Dialog**
|
|
- Side-by-side comparison of conflicting changes
|
|
- Page previews and element details
|
|
- Multiple resolution strategies:
|
|
- **Latest Wins**: Most recent change wins (automatic)
|
|
- **Always Use Yours**: Keep all local changes
|
|
- **Always Use Theirs**: Accept all remote changes
|
|
- **Manual**: Choose per-conflict
|
|
|
|
---
|
|
|
|
## How It Works
|
|
|
|
### Merge Workflow
|
|
|
|
```
|
|
1. User clicks "Merge Projects" in File ribbon tab
|
|
↓
|
|
2. Select .ppz file to merge
|
|
↓
|
|
3. System compares project_ids
|
|
├─→ Same ID: Detect conflicts → Show merge dialog
|
|
└─→ Different ID: Ask to concatenate
|
|
↓
|
|
4. User resolves conflicts (if any)
|
|
↓
|
|
5. Merged project becomes current project
|
|
↓
|
|
6. User saves merged project
|
|
```
|
|
|
|
### Conflict Detection
|
|
|
|
The system detects three types of conflicts:
|
|
|
|
#### 1. **Page-Level Conflicts**
|
|
- Page modified in both versions
|
|
- Page deleted in one, modified in other
|
|
- Page properties changed (size, type, etc.)
|
|
|
|
#### 2. **Element-Level Conflicts**
|
|
- Element modified in both versions (position, size, rotation, content)
|
|
- Element deleted in one, modified in other
|
|
- Element properties changed differently
|
|
|
|
#### 3. **Project-Level Conflicts**
|
|
- Settings changed in both (page size, DPI, cover settings, etc.)
|
|
|
|
### Automatic Conflict Resolution
|
|
|
|
**Non-conflicting changes** are automatically merged:
|
|
- Page 1 modified in version A, Page 2 modified in version B → Keep both
|
|
- New pages added at different positions → Merge both sets
|
|
- Different elements modified → Keep all modifications
|
|
|
|
**Conflicting changes** require resolution:
|
|
- Same element modified in both versions
|
|
- Element/page deleted in one but modified in other
|
|
|
|
---
|
|
|
|
## File Format Changes (v3.0)
|
|
|
|
### What's New in v3.0
|
|
|
|
#### Project Level
|
|
```json
|
|
{
|
|
"data_version": "3.0",
|
|
"project_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"created": "2025-01-22T10:30:00.123456+00:00",
|
|
"last_modified": "2025-01-22T14:45:12.789012+00:00",
|
|
...
|
|
}
|
|
```
|
|
|
|
#### Page Level
|
|
```json
|
|
{
|
|
"page_number": 1,
|
|
"uuid": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
"created": "2025-01-22T10:30:00.123456+00:00",
|
|
"last_modified": "2025-01-22T11:15:30.456789+00:00",
|
|
"deleted": false,
|
|
"deleted_at": null,
|
|
...
|
|
}
|
|
```
|
|
|
|
#### Element Level
|
|
```json
|
|
{
|
|
"type": "image",
|
|
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
|
|
"created": "2025-01-22T10:30:00.123456+00:00",
|
|
"last_modified": "2025-01-22T13:20:45.123456+00:00",
|
|
"deleted": false,
|
|
"deleted_at": null,
|
|
"position": [10, 10],
|
|
"size": [100, 100],
|
|
...
|
|
}
|
|
```
|
|
|
|
### Backwards Compatibility
|
|
|
|
- **v3.0 can read v2.0 and v1.0 files** with automatic migration
|
|
- **v2.0/v1.0 cannot read v3.0 files** (breaking change)
|
|
- Migration automatically generates UUIDs and timestamps for old files
|
|
|
|
---
|
|
|
|
## User Guide
|
|
|
|
### How to Merge Two Album Versions
|
|
|
|
1. **Open your current album** in pyPhotoAlbum
|
|
|
|
2. **Click "Merge Projects"** in the File tab of the ribbon
|
|
|
|
3. **Select the other album file** (.ppz) to merge
|
|
|
|
4. **System analyzes the projects:**
|
|
- If they're the same album (same project_id):
|
|
- Shows conflicts requiring resolution
|
|
- Auto-merges non-conflicting changes
|
|
- If they're different albums:
|
|
- Asks if you want to combine all pages
|
|
|
|
5. **Resolve conflicts** (if merging same album):
|
|
- View side-by-side comparison
|
|
- Choose "Use Your Version" or "Use Other Version" for each conflict
|
|
- Or click "Auto-Resolve All" with a strategy:
|
|
- **Latest Wins**: Keeps most recently modified version
|
|
- **Always Use Yours**: Keeps all your changes
|
|
- **Always Use Theirs**: Accepts all their changes
|
|
|
|
6. **Click "Apply Merge"** to complete the merge
|
|
|
|
7. **Save the merged album** when ready
|
|
|
|
### Best Practices
|
|
|
|
1. **Save before merging** - The system will prompt you, but it's good practice
|
|
|
|
2. **Use cloud sync carefully** - If using Dropbox/Google Drive:
|
|
- Each person should have their own working copy
|
|
- Merge explicitly rather than relying on cloud sync conflicts
|
|
|
|
3. **Communicate with collaborators** - Agree on who edits which pages to minimize conflicts
|
|
|
|
4. **Review the merge** - Check the merged result before saving
|
|
|
|
5. **Keep backups** - The autosave system creates checkpoints, but manual backups are recommended
|
|
|
|
### Common Scenarios
|
|
|
|
#### Scenario 1: You and a Friend Edit Different Pages
|
|
- **Result**: Auto-merge ✅
|
|
- No conflicts, both sets of changes preserved
|
|
|
|
#### Scenario 2: You Both Edit the Same Image Position
|
|
- **Result**: Conflict resolution needed ⚠️
|
|
- You choose which position to keep
|
|
|
|
#### Scenario 3: You Delete an Image, They Move It
|
|
- **Result**: Conflict resolution needed ⚠️
|
|
- You choose: keep it deleted or use their moved version
|
|
|
|
#### Scenario 4: Combining Two Different Albums
|
|
- **Result**: Concatenation
|
|
- All pages from both albums combined into one
|
|
|
|
---
|
|
|
|
## Developer Guide
|
|
|
|
### Architecture
|
|
|
|
```
|
|
pyPhotoAlbum/
|
|
├── models.py # BaseLayoutElement with UUID/timestamp support
|
|
├── project.py # Project and Page with UUID/timestamp support
|
|
├── version_manager.py # v3.0 migration logic
|
|
├── project_serializer.py # Save/load with v3.0 support
|
|
├── merge_manager.py # Core merge conflict detection & resolution
|
|
├── merge_dialog.py # Qt UI for visual conflict resolution
|
|
└── mixins/operations/
|
|
└── merge_ops.py # Ribbon integration & workflow
|
|
```
|
|
|
|
### Key Classes
|
|
|
|
#### MergeManager
|
|
```python
|
|
from pyPhotoAlbum.merge_manager import MergeManager, MergeStrategy
|
|
|
|
manager = MergeManager()
|
|
|
|
# Check if projects should be merged or concatenated
|
|
should_merge = manager.should_merge_projects(project_a_data, project_b_data)
|
|
|
|
# Detect conflicts
|
|
conflicts = manager.detect_conflicts(our_data, their_data)
|
|
|
|
# Auto-resolve
|
|
resolutions = manager.auto_resolve_conflicts(MergeStrategy.LATEST_WINS)
|
|
|
|
# Apply merge
|
|
merged_data = manager.apply_resolutions(our_data, their_data, resolutions)
|
|
```
|
|
|
|
#### Data Model Updates
|
|
|
|
```python
|
|
from pyPhotoAlbum.models import ImageData
|
|
from pyPhotoAlbum.project import Page, Project
|
|
|
|
# All elements now have:
|
|
element = ImageData(...)
|
|
element.uuid # Auto-generated UUID
|
|
element.created # ISO 8601 timestamp
|
|
element.last_modified # ISO 8601 timestamp
|
|
element.deleted # Boolean flag
|
|
element.deleted_at # Timestamp when deleted
|
|
|
|
# Mark as modified
|
|
element.mark_modified() # Updates last_modified
|
|
|
|
# Mark as deleted
|
|
element.mark_deleted() # Sets deleted=True, deleted_at=now
|
|
|
|
# Same for pages and projects
|
|
page.mark_modified()
|
|
project.mark_modified()
|
|
```
|
|
|
|
### Adding Merge Support to Custom Elements
|
|
|
|
If you create custom element types, ensure they:
|
|
|
|
1. **Inherit from BaseLayoutElement**
|
|
```python
|
|
class MyCustomElement(BaseLayoutElement):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs) # Initializes UUID and timestamps
|
|
# Your custom fields here
|
|
```
|
|
|
|
2. **Call `_deserialize_base_fields()` first in deserialize**
|
|
```python
|
|
def deserialize(self, data: Dict[str, Any]):
|
|
self._deserialize_base_fields(data) # Load UUID/timestamps
|
|
# Load your custom fields
|
|
```
|
|
|
|
3. **Include base fields in serialize**
|
|
```python
|
|
def serialize(self) -> Dict[str, Any]:
|
|
data = {
|
|
"type": "mycustom",
|
|
# Your custom fields
|
|
}
|
|
data.update(self._serialize_base_fields()) # Add UUID/timestamps
|
|
return data
|
|
```
|
|
|
|
4. **Call `mark_modified()` when changed**
|
|
```python
|
|
def set_my_property(self, value):
|
|
self.my_property = value
|
|
self.mark_modified() # Update timestamp
|
|
```
|
|
|
|
### Migration System
|
|
|
|
To add a new migration (e.g., v3.0 to v4.0):
|
|
|
|
```python
|
|
# In version_manager.py
|
|
|
|
@DataMigration.register_migration("3.0", "4.0")
|
|
def migrate_3_0_to_4_0(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Migrate from version 3.0 to 4.0.
|
|
|
|
Main changes:
|
|
- Add new fields
|
|
- Update structures
|
|
"""
|
|
# Perform migration
|
|
data['new_field'] = default_value
|
|
|
|
# Update version
|
|
data['data_version'] = "4.0"
|
|
|
|
return data
|
|
```
|
|
|
|
### Testing
|
|
|
|
Run the provided test scripts:
|
|
|
|
```bash
|
|
# Test v2.0 → v3.0 migration
|
|
python test_migration.py
|
|
|
|
# Test merge functionality
|
|
python test_merge.py
|
|
```
|
|
|
|
Expected output: All tests should pass with ✅
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Manual Testing Checklist
|
|
|
|
#### Test 1: Basic Migration
|
|
- [ ] Open a v2.0 project
|
|
- [ ] Verify it loads without errors
|
|
- [ ] Check console for "Migration 2.0 → 3.0" message
|
|
- [ ] Save the project
|
|
- [ ] Verify saved version is 3.0
|
|
|
|
#### Test 2: Same Project Merge
|
|
- [ ] Create a project, save it
|
|
- [ ] Open the file twice in different instances
|
|
- [ ] Modify same element in both
|
|
- [ ] Merge them
|
|
- [ ] Verify conflict dialog appears
|
|
- [ ] Resolve conflict
|
|
- [ ] Verify merged result
|
|
|
|
#### Test 3: Different Project Concatenation
|
|
- [ ] Create two different projects
|
|
- [ ] Try to merge them
|
|
- [ ] Verify concatenation option appears
|
|
- [ ] Verify combined project has all pages
|
|
|
|
#### Test 4: Auto-Merge Non-Conflicting
|
|
- [ ] Create project with 2 pages
|
|
- [ ] Version A: Edit page 1
|
|
- [ ] Version B: Edit page 2
|
|
- [ ] Merge
|
|
- [ ] Verify auto-merge without conflicts
|
|
- [ ] Verify both edits preserved
|
|
|
|
### Automated Testing
|
|
|
|
Run the test scripts:
|
|
|
|
```bash
|
|
cd /home/dtourolle/Development/pyPhotoAlbum
|
|
|
|
# Migration test
|
|
./test_migration.py
|
|
|
|
# Merge test
|
|
./test_merge.py
|
|
```
|
|
|
|
---
|
|
|
|
## Migration from v2.0
|
|
|
|
### Automatic Migration
|
|
|
|
When you open a v2.0 project in v3.0, it will automatically:
|
|
|
|
1. Generate a unique `project_id`
|
|
2. Generate `uuid` for all pages and elements
|
|
3. Set `created` and `last_modified` to current time
|
|
4. Add `deleted` and `deleted_at` fields (all set to False/None)
|
|
5. Update `data_version` to "3.0"
|
|
|
|
### Migration Output Example
|
|
|
|
```
|
|
Migration 2.0 → 3.0: Adding UUIDs, timestamps, and project_id
|
|
Generated project_id: 550e8400-e29b-41d4-a716-446655440000
|
|
Migrated 5 pages to v3.0
|
|
Migration completed successfully
|
|
```
|
|
|
|
### After Migration
|
|
|
|
- **Save the project** to persist the migration
|
|
- The migrated file can **only be opened in v3.0+**
|
|
- Keep a backup of v2.0 file if you need v2.0 compatibility
|
|
|
|
### Rollback
|
|
|
|
If you need to rollback to v2.0:
|
|
1. Don't save after opening in v3.0
|
|
2. Close without saving
|
|
3. Open original v2.0 file in v2.0
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Merge Dialog Won't Appear
|
|
|
|
**Problem**: Clicking "Merge Projects" does nothing
|
|
|
|
**Solutions**:
|
|
- Check both projects are v3.0 (or were migrated)
|
|
- Verify projects have the same `project_id`
|
|
- Check console for error messages
|
|
|
|
### Can't Resolve Conflicts
|
|
|
|
**Problem**: "Apply Merge" button is grayed out
|
|
|
|
**Solutions**:
|
|
- Make a resolution choice for each conflict
|
|
- Or click "Auto-Resolve All" first
|
|
|
|
### Changes Not Preserved
|
|
|
|
**Problem**: After merge, some changes are missing
|
|
|
|
**Solutions**:
|
|
- Check which resolution strategy you used
|
|
- "Latest Wins" prefers most recent modifications
|
|
- Review each conflict manually if needed
|
|
|
|
### Project Won't Load
|
|
|
|
**Problem**: "Incompatible file version" error
|
|
|
|
**Solutions**:
|
|
- This is a v2.0 or v1.0 file
|
|
- Migration should happen automatically
|
|
- If not, check version_manager.py for errors
|
|
|
|
---
|
|
|
|
## FAQ
|
|
|
|
### Q: Can I merge more than two projects at once?
|
|
**A:** Not directly. Merge two at a time, then merge the result with a third.
|
|
|
|
### Q: What happens to undo history after merge?
|
|
**A:** Undo history is session-specific and not preserved during merge. Save before merging.
|
|
|
|
### Q: Can I see what changed before merging?
|
|
**A:** The merge dialog shows changed elements with timestamps. Future versions may add detailed diff view.
|
|
|
|
### Q: Is merge atomic?
|
|
**A:** No. If you cancel during conflict resolution, no changes are made. Once you click "Apply Merge", the changes are applied to the current project.
|
|
|
|
### Q: Can I merge projects from different versions?
|
|
**A:** Yes! v2.0 and v1.0 projects are automatically migrated to v3.0 before merging.
|
|
|
|
### Q: What if two people add the same image?
|
|
**A:** If the image has the same filename and is added to different pages, both instances are kept. If added to the same location on the same page, it becomes a conflict.
|
|
|
|
### Q: Can I programmatically merge projects?
|
|
**A:** Yes! See the Developer Guide section for `MergeManager` API usage.
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements for future versions:
|
|
|
|
1. **Three-way merge** - Use base version for better conflict resolution
|
|
2. **Merge history tracking** - Log all merges performed
|
|
3. **Partial merge** - Merge only specific pages
|
|
4. **Cloud collaboration** - Real-time collaborative editing
|
|
5. **Merge preview** - Show full diff before applying
|
|
6. **Asset conflict handling** - Better handling of duplicate assets
|
|
7. **Conflict visualization** - Visual overlay showing changes
|
|
|
|
---
|
|
|
|
## Version History
|
|
|
|
### v3.0 (2025-01-22)
|
|
- ✨ Initial merge conflict resolution feature
|
|
- ✨ UUID and timestamp tracking
|
|
- ✨ Project ID-based merge detection
|
|
- ✨ Visual merge dialog
|
|
- ✨ Automatic migration from v2.0
|
|
- ✨ Soft delete support
|
|
|
|
---
|
|
|
|
## Credits
|
|
|
|
Merge system designed and implemented with the following principles:
|
|
- **UUID stability** - Elements tracked across versions
|
|
- **Timestamp precision** - ISO 8601 UTC for reliable ordering
|
|
- **Backwards compatibility** - Seamless migration from v2.0
|
|
- **User-friendly** - Visual conflict resolution
|
|
- **Developer-friendly** - Clean API, well-documented
|
|
|
|
For questions or issues, please file a bug report in the project repository.
|