14 KiB
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
- How It Works
- File Format Changes (v3.0)
- User Guide
- Developer Guide
- Testing
- Migration from v2.0
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
createdandlast_modifiedtimestamps (ISO 8601 UTC) - Automatic "Latest Wins" strategy available
- Manual conflict resolution through visual dialog
4. Soft Delete Support
- Deleted items marked with
deletedflag anddeleted_attimestamp - 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
{
"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
{
"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
{
"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
-
Open your current album in pyPhotoAlbum
-
Click "Merge Projects" in the File tab of the ribbon
-
Select the other album file (.ppz) to merge
-
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
- If they're the same album (same project_id):
-
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
-
Click "Apply Merge" to complete the merge
-
Save the merged album when ready
Best Practices
-
Save before merging - The system will prompt you, but it's good practice
-
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
-
Communicate with collaborators - Agree on who edits which pages to minimize conflicts
-
Review the merge - Check the merged result before saving
-
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
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
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:
- Inherit from BaseLayoutElement
class MyCustomElement(BaseLayoutElement):
def __init__(self, **kwargs):
super().__init__(**kwargs) # Initializes UUID and timestamps
# Your custom fields here
- Call
_deserialize_base_fields()first in deserialize
def deserialize(self, data: Dict[str, Any]):
self._deserialize_base_fields(data) # Load UUID/timestamps
# Load your custom fields
- Include base fields in serialize
def serialize(self) -> Dict[str, Any]:
data = {
"type": "mycustom",
# Your custom fields
}
data.update(self._serialize_base_fields()) # Add UUID/timestamps
return data
- Call
mark_modified()when changed
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):
# 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:
# 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:
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:
- Generate a unique
project_id - Generate
uuidfor all pages and elements - Set
createdandlast_modifiedto current time - Add
deletedanddeleted_atfields (all set to False/None) - Update
data_versionto "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:
- Don't save after opening in v3.0
- Close without saving
- 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:
- Three-way merge - Use base version for better conflict resolution
- Merge history tracking - Log all merges performed
- Partial merge - Merge only specific pages
- Cloud collaboration - Real-time collaborative editing
- Merge preview - Show full diff before applying
- Asset conflict handling - Better handling of duplicate assets
- 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.