merge_driver.py 3.7 KB

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