File: merge_driver.py

package info (click to toggle)
dulwich 1.1.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,676 kB
  • sloc: python: 104,149; makefile: 163; sh: 67
file content (125 lines) | stat: -rw-r--r-- 3,876 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/python3
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

"""Simple example demonstrating merge driver usage in dulwich.

This example:
1. Creates a test repository with .gitattributes
2. Implements a JSON merge driver
3. Creates two branches with conflicting JSON changes
4. Merges one commit into another using the custom driver
"""

import json
import os

from dulwich import porcelain
from dulwich.merge_drivers import get_merge_driver_registry
from dulwich.repo import Repo


class JSONMergeDriver:
    """Simple merge driver for JSON files."""

    def merge(
        self,
        ancestor: bytes,
        ours: bytes,
        theirs: bytes,
        path: str | None = None,
        marker_size: int = 7,
    ) -> tuple[bytes, bool]:
        """Merge JSON files by combining objects."""
        try:
            # Parse JSON content
            ancestor_data = json.loads(ancestor.decode()) if ancestor.strip() else {}
            ours_data = json.loads(ours.decode()) if ours.strip() else {}
            theirs_data = json.loads(theirs.decode()) if theirs.strip() else {}

            # Simple merge: combine all fields
            merged: dict = {}
            merged.update(ancestor_data)
            merged.update(ours_data)
            merged.update(theirs_data)

            # Convert back to JSON with nice formatting
            result = json.dumps(merged, indent=2).encode()
            return result, True

        except (json.JSONDecodeError, UnicodeDecodeError):
            # Fall back to simple concatenation on parse error
            result = ours + b"\n<<<<<<< MERGE CONFLICT >>>>>>>\n" + theirs
            return result, False


# Create temporary directory for test repo
# Initialize repository
repo = Repo.init("merge-driver", mkdir=True)

# Create .gitattributes file
gitattributes_path = os.path.join(repo.path, ".gitattributes")
with open(gitattributes_path, "w") as f:
    f.write("*.json merge=jsondriver\n")

# Create initial JSON file
config_path = os.path.join(repo.path, "config.json")
initial_config = {"name": "test-project", "version": "1.0.0"}
with open(config_path, "w") as f:
    json.dump(initial_config, f, indent=2)

# Add and commit initial files
repo.get_worktree().stage([".gitattributes", "config.json"])
initial_commit = repo.get_worktree().commit(
    b"Initial commit", committer=b"Test <test@example.com>"
)

# Register our custom merge driver globally
registry = get_merge_driver_registry()
registry.register_driver("jsondriver", JSONMergeDriver())

# Create and switch to feature branch
porcelain.branch_create(repo, "feature")
repo.refs[b"HEAD"] = repo.refs[b"refs/heads/feature"]

# Make changes on feature branch
feature_config = {
    "name": "test-project",
    "version": "1.0.0",
    "author": "Alice",
    "features": ["logging", "database"],
}
with open(config_path, "w") as f:
    json.dump(feature_config, f, indent=2)

repo.get_worktree().stage(["config.json"])
feature_commit = repo.get_worktree().commit(
    b"Add author and features", committer=b"Alice <alice@example.com>"
)

# Switch back to master
repo.refs[b"HEAD"] = repo.refs[b"refs/heads/master"]

# Make different changes on master
master_config = {
    "name": "test-project",
    "version": "1.1.0",
    "description": "A test project for merge drivers",
    "license": "Apache-2.0",
}
with open(config_path, "w") as f:
    json.dump(master_config, f, indent=2)

repo.get_worktree().stage(["config.json"])
master_commit = repo.get_worktree().commit(
    b"Add description and license", committer=b"Bob <bob@example.com>"
)

# Perform the merge using porcelain.merge
# The merge should use our custom JSON driver for config.json
merge_result = porcelain.merge(repo, "feature")
# Show the merged content
with open(config_path) as f:
    merged_content = f.read()

print("\nMerged config.json content:")
print(merged_content)