# 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.