2
0

merge_driver.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. #!/usr/bin/python3
  2. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  3. """Simple example demonstrating merge driver usage in dulwich.
  4. This example:
  5. 1. Creates a test repository with .gitattributes
  6. 2. Implements a JSON merge driver
  7. 3. Creates two branches with conflicting JSON changes
  8. 4. Merges one commit into another using the custom driver
  9. """
  10. import json
  11. import os
  12. from dulwich import porcelain
  13. from dulwich.merge_drivers import get_merge_driver_registry
  14. from dulwich.repo import Repo
  15. class JSONMergeDriver:
  16. """Simple merge driver for JSON files."""
  17. def merge(
  18. self,
  19. ancestor: bytes,
  20. ours: bytes,
  21. theirs: bytes,
  22. path: str | None = None,
  23. marker_size: int = 7,
  24. ) -> tuple[bytes, bool]:
  25. """Merge JSON files by combining objects."""
  26. try:
  27. # Parse JSON content
  28. ancestor_data = json.loads(ancestor.decode()) if ancestor.strip() else {}
  29. ours_data = json.loads(ours.decode()) if ours.strip() else {}
  30. theirs_data = json.loads(theirs.decode()) if theirs.strip() else {}
  31. # Simple merge: combine all fields
  32. merged: dict = {}
  33. merged.update(ancestor_data)
  34. merged.update(ours_data)
  35. merged.update(theirs_data)
  36. # Convert back to JSON with nice formatting
  37. result = json.dumps(merged, indent=2).encode()
  38. return result, True
  39. except (json.JSONDecodeError, UnicodeDecodeError):
  40. # Fall back to simple concatenation on parse error
  41. result = ours + b"\n<<<<<<< MERGE CONFLICT >>>>>>>\n" + theirs
  42. return result, False
  43. # Create temporary directory for test repo
  44. # Initialize repository
  45. repo = Repo.init("merge-driver", mkdir=True)
  46. # Create .gitattributes file
  47. gitattributes_path = os.path.join(repo.path, ".gitattributes")
  48. with open(gitattributes_path, "w") as f:
  49. f.write("*.json merge=jsondriver\n")
  50. # Create initial JSON file
  51. config_path = os.path.join(repo.path, "config.json")
  52. initial_config = {"name": "test-project", "version": "1.0.0"}
  53. with open(config_path, "w") as f:
  54. json.dump(initial_config, f, indent=2)
  55. # Add and commit initial files
  56. repo.get_worktree().stage([".gitattributes", "config.json"])
  57. initial_commit = repo.get_worktree().commit(
  58. b"Initial commit", committer=b"Test <test@example.com>"
  59. )
  60. # Register our custom merge driver globally
  61. registry = get_merge_driver_registry()
  62. registry.register_driver("jsondriver", JSONMergeDriver())
  63. # Create and switch to feature branch
  64. porcelain.branch_create(repo, "feature")
  65. repo.refs[b"HEAD"] = repo.refs[b"refs/heads/feature"]
  66. # Make changes on feature branch
  67. feature_config = {
  68. "name": "test-project",
  69. "version": "1.0.0",
  70. "author": "Alice",
  71. "features": ["logging", "database"],
  72. }
  73. with open(config_path, "w") as f:
  74. json.dump(feature_config, f, indent=2)
  75. repo.get_worktree().stage(["config.json"])
  76. feature_commit = repo.get_worktree().commit(
  77. b"Add author and features", committer=b"Alice <alice@example.com>"
  78. )
  79. # Switch back to master
  80. repo.refs[b"HEAD"] = repo.refs[b"refs/heads/master"]
  81. # Make different changes on master
  82. master_config = {
  83. "name": "test-project",
  84. "version": "1.1.0",
  85. "description": "A test project for merge drivers",
  86. "license": "Apache-2.0",
  87. }
  88. with open(config_path, "w") as f:
  89. json.dump(master_config, f, indent=2)
  90. repo.get_worktree().stage(["config.json"])
  91. master_commit = repo.get_worktree().commit(
  92. b"Add description and license", committer=b"Bob <bob@example.com>"
  93. )
  94. # Perform the merge using porcelain.merge
  95. # The merge should use our custom JSON driver for config.json
  96. merge_result = porcelain.merge(repo, "feature")
  97. # Show the merged content
  98. with open(config_path) as f:
  99. merged_content = f.read()
  100. print("\nMerged config.json content:")
  101. print(merged_content)