0
# Diff and Merge
1
2
Comprehensive diff generation and merge algorithms for comparing trees, detecting changes, and resolving conflicts with rename detection.
3
4
## Capabilities
5
6
### Tree Comparison Functions
7
8
Functions for comparing Git trees and detecting changes between different states.
9
10
```python { .api }
11
def tree_changes(object_store, old_tree: bytes, new_tree: bytes,
12
want_unchanged: bool = False, rename_detector=None,
13
change_type_same: bool = False) -> Iterator[TreeChange]:
14
"""
15
Find changes between two trees.
16
17
Args:
18
object_store: Object store for loading trees
19
old_tree: SHA of old tree (None for empty)
20
new_tree: SHA of new tree (None for empty)
21
want_unchanged: Include unchanged files
22
rename_detector: RenameDetector instance
23
change_type_same: Include type changes as same
24
25
Yields:
26
TreeChange objects describing changes
27
"""
28
29
def tree_changes_for_merge(object_store, tree1: bytes, tree2: bytes,
30
base_tree: bytes) -> Iterator[TreeChange]:
31
"""
32
Find changes for three-way merge.
33
34
Args:
35
object_store: Object store for loading trees
36
tree1: SHA of first tree
37
tree2: SHA of second tree
38
base_tree: SHA of common base tree
39
40
Yields:
41
TreeChange objects for merge conflicts
42
"""
43
44
def walk_trees(object_store, tree1_id: bytes, tree2_id: bytes,
45
prune_identical: bool = True) -> Iterator[Tuple]:
46
"""
47
Walk two trees simultaneously.
48
49
Args:
50
object_store: Object store for loading trees
51
tree1_id: SHA of first tree
52
tree2_id: SHA of second tree
53
prune_identical: Skip identical subtrees
54
55
Yields:
56
Tuples of (path, mode1, sha1, mode2, sha2)
57
"""
58
```
59
60
### Diff Generation Functions
61
62
Functions for generating unified diff output from tree and working directory changes.
63
64
```python { .api }
65
def diff_tree_to_tree(object_store, old_tree: bytes, new_tree: bytes,
66
outf=None) -> bytes:
67
"""
68
Generate unified diff between two trees.
69
70
Args:
71
object_store: Object store for loading objects
72
old_tree: SHA of old tree
73
new_tree: SHA of new tree
74
outf: Optional output file
75
76
Returns:
77
Diff as bytes if outf is None
78
"""
79
80
def diff_index_to_tree(object_store, index_path: str, tree: bytes,
81
outf=None) -> bytes:
82
"""
83
Generate diff between index and tree.
84
85
Args:
86
object_store: Object store for loading objects
87
index_path: Path to index file
88
tree: SHA of tree to compare against
89
outf: Optional output file
90
91
Returns:
92
Diff as bytes if outf is None
93
"""
94
95
def diff_working_tree_to_tree(object_store, tree: bytes, root_path: str = ".",
96
outf=None) -> bytes:
97
"""
98
Generate diff between working tree and Git tree.
99
100
Args:
101
object_store: Object store for loading objects
102
tree: SHA of tree to compare against
103
root_path: Root path of working tree
104
outf: Optional output file
105
106
Returns:
107
Diff as bytes if outf is None
108
"""
109
110
def diff_working_tree_to_index(object_store, index_path: str,
111
root_path: str = ".", outf=None) -> bytes:
112
"""
113
Generate diff between working tree and index.
114
115
Args:
116
object_store: Object store for loading objects
117
index_path: Path to index file
118
root_path: Root path of working tree
119
outf: Optional output file
120
121
Returns:
122
Diff as bytes if outf is None
123
"""
124
```
125
126
### Rename Detection
127
128
Classes and functions for detecting file renames and copies in diffs.
129
130
```python { .api }
131
class RenameDetector:
132
"""Detect renames and copies in tree changes."""
133
134
def __init__(self, object_store, rename_threshold: int = 60,
135
copy_threshold: int = 60, max_files: int = None,
136
big_file_threshold: int = None): ...
137
138
def changes_with_renames(self, changes: List[TreeChange]) -> List[TreeChange]:
139
"""
140
Process changes to detect renames and copies.
141
142
Args:
143
changes: List of TreeChange objects
144
145
Returns:
146
List of TreeChange objects with renames detected
147
"""
148
149
def find_renames(self, added_files: List[TreeChange],
150
deleted_files: List[TreeChange]) -> List[Tuple[TreeChange, TreeChange]]:
151
"""
152
Find renames between added and deleted files.
153
154
Args:
155
added_files: List of added files
156
deleted_files: List of deleted files
157
158
Returns:
159
List of (deleted, added) pairs that are renames
160
"""
161
```
162
163
### Change Representation
164
165
Classes representing different types of changes between trees.
166
167
```python { .api }
168
class TreeChange:
169
"""Represents a change between two trees."""
170
171
type: str # 'add', 'delete', 'modify', 'rename', 'copy', 'unchanged'
172
old: TreeEntry # (name, mode, sha) or None
173
new: TreeEntry # (name, mode, sha) or None
174
175
def __init__(self, type: str, old: TreeEntry = None, new: TreeEntry = None): ...
176
177
@property
178
def is_rename(self) -> bool:
179
"""True if this change represents a rename."""
180
181
@property
182
def is_copy(self) -> bool:
183
"""True if this change represents a copy."""
184
185
@property
186
def is_modify(self) -> bool:
187
"""True if this change represents a modification."""
188
```
189
190
### Colorized Output
191
192
Classes for generating colorized diff output.
193
194
```python { .api }
195
class ColorizedDiffStream:
196
"""Stream wrapper that adds ANSI color codes to diff output."""
197
198
def __init__(self, f, color_config=None): ...
199
200
def write(self, data: bytes) -> None:
201
"""Write colorized diff data to underlying stream."""
202
```
203
204
## Usage Examples
205
206
### Basic Tree Comparison
207
208
```python
209
from dulwich.repo import Repo
210
from dulwich.diff_tree import tree_changes
211
212
# Open repository
213
repo = Repo('.')
214
215
# Get two commits to compare
216
commit1 = repo[b'HEAD~1']
217
commit2 = repo[b'HEAD']
218
219
# Find changes between the commits
220
changes = tree_changes(repo.object_store, commit1.tree, commit2.tree)
221
222
for change in changes:
223
if change.type == 'add':
224
print(f"Added: {change.new.path}")
225
elif change.type == 'delete':
226
print(f"Deleted: {change.old.path}")
227
elif change.type == 'modify':
228
print(f"Modified: {change.new.path}")
229
```
230
231
### Generating Unified Diffs
232
233
```python
234
from dulwich.repo import Repo
235
from dulwich.diff import diff_tree_to_tree
236
import sys
237
238
repo = Repo('.')
239
240
# Compare two commits
241
commit1 = repo[b'HEAD~1']
242
commit2 = repo[b'HEAD']
243
244
# Generate unified diff
245
diff_output = diff_tree_to_tree(repo.object_store, commit1.tree, commit2.tree)
246
247
# Write to stdout
248
sys.stdout.buffer.write(diff_output)
249
```
250
251
### Rename Detection
252
253
```python
254
from dulwich.repo import Repo
255
from dulwich.diff_tree import tree_changes, RenameDetector
256
257
repo = Repo('.')
258
259
# Create rename detector
260
rename_detector = RenameDetector(repo.object_store, rename_threshold=50)
261
262
# Get changes between commits
263
commit1 = repo[b'HEAD~1']
264
commit2 = repo[b'HEAD']
265
266
changes = list(tree_changes(repo.object_store, commit1.tree, commit2.tree))
267
268
# Detect renames
269
changes_with_renames = rename_detector.changes_with_renames(changes)
270
271
for change in changes_with_renames:
272
if change.is_rename:
273
print(f"Renamed: {change.old.path} -> {change.new.path}")
274
elif change.is_copy:
275
print(f"Copied: {change.old.path} -> {change.new.path}")
276
```
277
278
### Working Tree Diffs
279
280
```python
281
from dulwich.repo import Repo
282
from dulwich.diff import diff_working_tree_to_tree
283
284
repo = Repo('.')
285
286
# Compare working tree to HEAD
287
head_tree = repo[repo.head()].tree
288
diff_output = diff_working_tree_to_tree(repo.object_store, head_tree)
289
290
print(diff_output.decode('utf-8'))
291
```
292
293
### Colorized Diff Output
294
295
```python
296
from dulwich.repo import Repo
297
from dulwich.diff import diff_tree_to_tree, ColorizedDiffStream
298
import sys
299
300
repo = Repo('.')
301
302
# Create colorized output stream
303
colorized_stream = ColorizedDiffStream(sys.stdout.buffer)
304
305
# Generate colorized diff
306
commit1 = repo[b'HEAD~1']
307
commit2 = repo[b'HEAD']
308
309
diff_tree_to_tree(repo.object_store, commit1.tree, commit2.tree,
310
outf=colorized_stream)
311
```