0
# References and Branches
1
2
Complete reference management including branches, tags, symbolic references, and packed refs with validation, atomic updates, and comprehensive locking support for safe concurrent operations.
3
4
## Capabilities
5
6
### Reference Container Classes
7
8
Container classes for managing Git references with different storage backends.
9
10
```python { .api }
11
class RefsContainer:
12
"""Abstract container for Git references."""
13
14
def __init__(self, logger=None):
15
"""Initialize refs container with optional logger."""
16
17
def __getitem__(self, name: bytes) -> bytes:
18
"""Get reference value by name, following symbolic refs."""
19
20
def __setitem__(self, name: bytes, value: bytes) -> None:
21
"""Set reference value by name unconditionally."""
22
23
def __delitem__(self, name: bytes) -> None:
24
"""Remove reference by name unconditionally."""
25
26
def __contains__(self, refname: bytes) -> bool:
27
"""Check if reference exists in container."""
28
29
def __iter__(self) -> Iterator[bytes]:
30
"""Iterate over all reference names."""
31
32
def allkeys(self) -> Iterator[bytes]:
33
"""All refs present in this container."""
34
35
def keys(self, base: Optional[bytes] = None) -> Iterator[bytes]:
36
"""Refs present in container, optionally under a base."""
37
38
def subkeys(self, base: bytes) -> Set[bytes]:
39
"""Refs under a base with base prefix stripped."""
40
41
def as_dict(self, base: Optional[bytes] = None) -> Dict[bytes, bytes]:
42
"""Return contents of container as dictionary."""
43
44
def get_symrefs(self) -> Dict[bytes, bytes]:
45
"""Get symbolic references mapping source to target."""
46
47
def get_peeled(self, name: bytes) -> Optional[bytes]:
48
"""Get cached peeled value of a ref if available."""
49
50
def get_packed_refs(self) -> Dict[bytes, bytes]:
51
"""Get contents of packed-refs file."""
52
53
def add_packed_refs(self, new_refs: Dict[bytes, Optional[bytes]]) -> None:
54
"""Add given refs as packed refs (None means remove)."""
55
56
def read_ref(self, refname: bytes) -> Optional[bytes]:
57
"""Read reference without following symbolic refs."""
58
59
def read_loose_ref(self, name: bytes) -> Optional[bytes]:
60
"""Read loose reference and return its contents."""
61
62
def follow(self, name: bytes) -> Tuple[List[bytes], bytes]:
63
"""Follow reference name and return chain of refs and final SHA."""
64
65
def set_if_equals(
66
self,
67
name: bytes,
68
old_ref: Optional[bytes],
69
new_ref: bytes,
70
committer: Optional[bytes] = None,
71
timestamp: Optional[float] = None,
72
timezone: Optional[int] = None,
73
message: Optional[bytes] = None
74
) -> bool:
75
"""Set refname to new_ref only if it currently equals old_ref."""
76
77
def add_if_new(
78
self,
79
name: bytes,
80
ref: bytes,
81
committer: Optional[bytes] = None,
82
timestamp: Optional[float] = None,
83
timezone: Optional[int] = None,
84
message: Optional[bytes] = None
85
) -> bool:
86
"""Add new reference only if it doesn't already exist."""
87
88
def remove_if_equals(
89
self,
90
name: bytes,
91
old_ref: Optional[bytes],
92
committer: Optional[bytes] = None,
93
timestamp: Optional[float] = None,
94
timezone: Optional[int] = None,
95
message: Optional[bytes] = None
96
) -> bool:
97
"""Remove refname only if it currently equals old_ref."""
98
99
def set_symbolic_ref(
100
self,
101
name: bytes,
102
other: bytes,
103
committer: Optional[bytes] = None,
104
timestamp: Optional[float] = None,
105
timezone: Optional[int] = None,
106
message: Optional[bytes] = None
107
) -> None:
108
"""Make a ref point at another ref."""
109
110
def import_refs(
111
self,
112
base: bytes,
113
other: Dict[bytes, bytes],
114
committer: Optional[bytes] = None,
115
timestamp: Optional[float] = None,
116
timezone: Optional[int] = None,
117
message: Optional[bytes] = None,
118
prune: bool = False
119
) -> None:
120
"""Import refs from another container under base."""
121
122
def pack_refs(self, all: bool = False) -> None:
123
"""Pack loose refs into packed-refs file."""
124
125
class DictRefsContainer(RefsContainer):
126
"""Dictionary-based reference container.
127
128
This container does not support symbolic or packed references
129
and is not threadsafe.
130
"""
131
132
def __init__(
133
self,
134
refs: Dict[bytes, bytes],
135
logger=None
136
):
137
"""
138
Initialize dict-based refs container.
139
140
Args:
141
refs: Dictionary mapping ref names to SHA-1 values
142
logger: Optional reflog logger
143
"""
144
145
class InfoRefsContainer(RefsContainer):
146
"""Reference container based on info/refs file.
147
148
Read-only container that parses Git's info/refs format.
149
"""
150
151
def __init__(self, f):
152
"""
153
Initialize info/refs container.
154
155
Args:
156
f: File-like object containing info/refs data
157
"""
158
159
class DiskRefsContainer(RefsContainer):
160
"""Filesystem-based reference container.
161
162
Stores references as files in the Git repository's refs directory
163
and supports packed-refs files for efficient storage.
164
"""
165
166
def __init__(
167
self,
168
path: str,
169
worktree_path: Optional[str] = None,
170
logger=None
171
):
172
"""
173
Initialize filesystem refs container.
174
175
Args:
176
path: Path to Git repository
177
worktree_path: Optional path to worktree (for worktree support)
178
logger: Optional reflog logger
179
"""
180
181
def refpath(self, name: bytes) -> str:
182
"""
183
Get filesystem path for reference name.
184
185
Args:
186
name: Reference name
187
188
Returns:
189
Full filesystem path to reference file
190
"""
191
192
def get_packed_refs(self) -> Dict[bytes, bytes]:
193
"""
194
Get contents of packed-refs file.
195
196
Returns:
197
Dictionary mapping ref names to SHA-1 values
198
"""
199
200
def get_peeled(self, name: bytes) -> Optional[bytes]:
201
"""
202
Get peeled value from packed-refs file.
203
204
Args:
205
name: Reference name
206
207
Returns:
208
Peeled SHA-1 value if available, None otherwise
209
"""
210
211
def _write_packed_refs(self) -> None:
212
"""Write current packed refs to disk."""
213
214
def _read_packed_refs(self) -> Tuple[Dict[bytes, bytes], Dict[bytes, bytes]]:
215
"""Read packed refs from disk.
216
217
Returns:
218
Tuple of (packed_refs_dict, peeled_refs_dict)
219
"""
220
```
221
222
### Reference Validation Functions
223
224
Functions for validating and parsing Git references.
225
226
```python { .api }
227
def check_ref_format(refname: bytes) -> bool:
228
"""
229
Check if reference name follows Git naming rules.
230
231
Args:
232
refname: Reference name to validate
233
234
Returns:
235
True if valid reference name
236
"""
237
238
def parse_symref_value(contents: bytes) -> bytes:
239
"""
240
Parse symbolic reference value from file contents.
241
242
Args:
243
contents: Raw file contents
244
245
Returns:
246
Target reference name
247
"""
248
249
def parse_remote_ref(ref: bytes) -> Tuple[bytes, bytes]:
250
"""
251
Parse remote reference into remote name and branch.
252
253
Args:
254
ref: Remote reference (e.g., b'refs/remotes/origin/main')
255
256
Returns:
257
Tuple of (remote_name, branch_name)
258
"""
259
```
260
261
### Packed References
262
263
Functions for reading and writing packed-refs files for efficient reference storage.
264
265
```python { .api }
266
def read_packed_refs(f) -> Iterator[Tuple[bytes, bytes]]:
267
"""
268
Read packed references from file.
269
270
Args:
271
f: File-like object containing packed-refs data
272
273
Yields:
274
Tuples of (sha1, ref_name)
275
"""
276
277
def read_packed_refs_with_peeled(f) -> Iterator[Tuple[bytes, bytes, Optional[bytes]]]:
278
"""
279
Read packed refs file including peeled refs.
280
281
Assumes the "# pack-refs with: peeled" line was already read.
282
283
Args:
284
f: File-like object to read from, seek'ed to second line
285
286
Yields:
287
Tuples of (sha1, ref_name, peeled_sha1_or_none)
288
"""
289
290
def write_packed_refs(
291
f,
292
packed_refs: Dict[bytes, bytes],
293
peeled_refs: Optional[Dict[bytes, bytes]] = None
294
) -> None:
295
"""
296
Write packed references to file.
297
298
Args:
299
f: File-like object to write to
300
packed_refs: Dictionary of ref_name to SHA-1
301
peeled_refs: Optional dictionary of ref_name to peeled SHA-1
302
"""
303
```
304
305
### Reference Type Checking
306
307
Functions for identifying different types of references.
308
309
```python { .api }
310
def is_local_branch(ref: bytes) -> bool:
311
"""
312
Check if reference is a local branch.
313
314
Args:
315
ref: Reference name
316
317
Returns:
318
True if local branch reference (starts with refs/heads/)
319
"""
320
321
def strip_peeled_refs(refs: Dict[bytes, bytes]) -> Dict[bytes, bytes]:
322
"""
323
Remove peeled reference entries from refs dictionary.
324
325
Args:
326
refs: Dictionary of references
327
328
Returns:
329
Dictionary with peeled refs (ending with ^{}) removed
330
"""
331
332
def split_peeled_refs(refs: Dict[bytes, bytes]) -> Tuple[Dict[bytes, bytes], Dict[bytes, bytes]]:
333
"""
334
Split peeled refs from regular refs.
335
336
Args:
337
refs: Dictionary of references
338
339
Returns:
340
Tuple of (regular_refs, peeled_refs) dictionaries
341
"""
342
343
def serialize_refs(
344
store: ObjectContainer,
345
refs: Dict[bytes, bytes]
346
) -> Iterator[bytes]:
347
"""
348
Serialize references to info/refs format.
349
350
Args:
351
store: Object store for peeling tags
352
refs: Dictionary of references
353
354
Yields:
355
Lines in info/refs format
356
"""
357
358
def read_info_refs(f) -> Dict[bytes, bytes]:
359
"""
360
Read info/refs file format.
361
362
Args:
363
f: File-like object containing info/refs data
364
365
Returns:
366
Dictionary mapping ref names to SHA-1 values
367
"""
368
369
def write_info_refs(
370
refs: Dict[bytes, bytes],
371
store: ObjectContainer
372
) -> Iterator[bytes]:
373
"""
374
Generate info/refs format with peeled values.
375
376
Args:
377
refs: Dictionary of references
378
store: Object store for peeling tags
379
380
Yields:
381
Lines in info/refs format including peeled values
382
"""
383
```
384
385
### Reference Locking
386
387
Context manager for atomic reference updates.
388
389
```python { .api }
390
class locked_ref:
391
"""
392
Lock a ref while making modifications.
393
394
Works as a context manager to ensure atomic reference updates.
395
Locks the reference file to prevent concurrent modifications.
396
"""
397
398
def __init__(self, refs_container: DiskRefsContainer, refname: bytes):
399
"""
400
Initialize locked reference.
401
402
Args:
403
refs_container: DiskRefsContainer to operate on
404
refname: Name of reference to lock
405
"""
406
407
def __enter__(self) -> "locked_ref":
408
"""Enter context and acquire lock."""
409
410
def __exit__(self, exc_type, exc_value, traceback) -> None:
411
"""Exit context and release lock."""
412
413
def get(self) -> Optional[bytes]:
414
"""
415
Get current value of the ref.
416
417
Returns:
418
Current SHA-1 or symbolic ref value, None if not found
419
"""
420
421
def ensure_equals(self, expected_value: Optional[bytes]) -> bool:
422
"""
423
Ensure ref currently equals expected value.
424
425
Args:
426
expected_value: Expected current value
427
428
Returns:
429
True if ref equals expected value
430
"""
431
432
def set(self, new_ref: bytes) -> None:
433
"""
434
Set ref to new value.
435
436
Args:
437
new_ref: New SHA-1 or symbolic ref value
438
"""
439
440
def set_symbolic_ref(self, target: bytes) -> None:
441
"""
442
Make this ref point at another ref.
443
444
Args:
445
target: Name of ref to point at
446
"""
447
448
def delete(self) -> None:
449
"""Delete the ref file while holding the lock."""
450
```
451
452
## Exception Classes
453
454
```python { .api }
455
class SymrefLoop(Exception):
456
"""Exception raised when symbolic reference creates a loop."""
457
458
def __init__(self, ref: bytes, depth: int):
459
"""
460
Initialize symref loop exception.
461
462
Args:
463
ref: Reference that caused the loop
464
depth: Depth at which loop was detected
465
"""
466
467
## Usage Examples
468
469
### Basic Reference Operations
470
471
```python
472
from dulwich.repo import Repo
473
from dulwich.refs import check_ref_format, parse_remote_ref
474
475
# Open repository and access references
476
repo = Repo('.')
477
refs = repo.refs
478
479
# List all references
480
for ref_name in refs.keys():
481
print(f"{ref_name.decode()}: {refs[ref_name].hex()}")
482
483
# Follow symbolic references
484
head_chain, head_sha = refs.follow(b'HEAD')
485
print(f"HEAD chain: {[r.decode() for r in head_chain]}")
486
print(f"HEAD SHA: {head_sha.decode()}")
487
488
# Get current branch safely
489
try:
490
head_target = refs[b'HEAD']
491
if b'refs/heads/' in head_chain[0]:
492
current_branch = head_chain[0][11:] # Remove 'refs/heads/' prefix
493
print(f"Current branch: {current_branch.decode()}")
494
except (KeyError, IndexError):
495
print("HEAD is detached")
496
497
# Create a new branch reference with validation
498
new_ref = b'refs/heads/feature-branch'
499
if check_ref_format(new_ref[5:]): # Remove 'refs/' prefix for validation
500
refs[new_ref] = repo.head()
501
502
# Parse remote references
503
remote_ref = b'refs/remotes/origin/main'
504
remote_name, branch_name = parse_remote_ref(remote_ref)
505
print(f"Remote: {remote_name.decode()}, Branch: {branch_name.decode()}")
506
```
507
508
### Working with Symbolic References
509
510
```python
511
from dulwich.refs import parse_symref_value, locked_ref
512
513
# Check if HEAD is a symbolic reference
514
with open('.git/HEAD', 'rb') as f:
515
head_contents = f.read()
516
if head_contents.startswith(b'ref: '):
517
target_ref = parse_symref_value(head_contents)
518
print(f"HEAD points to: {target_ref.decode()}")
519
520
# Atomically update a reference
521
with locked_ref(repo.refs, b'refs/heads/feature') as ref_lock:
522
current_value = ref_lock.get()
523
if ref_lock.ensure_equals(current_value):
524
ref_lock.set(new_commit_sha)
525
print("Reference updated successfully")
526
```
527
528
### Working with Packed References
529
530
```python
531
from dulwich.refs import read_packed_refs, write_packed_refs
532
533
# Read packed references
534
with open('.git/packed-refs', 'rb') as f:
535
for sha, ref_name in read_packed_refs(f):
536
print(f"{ref_name.decode()}: {sha.decode()}")
537
538
# Write packed references
539
packed_refs = {
540
b'refs/heads/main': b'abc123...',
541
b'refs/tags/v1.0': b'def456...'
542
}
543
544
with open('.git/packed-refs', 'wb') as f:
545
write_packed_refs(f, packed_refs)
546
```
547
548
### Advanced Reference Operations
549
550
```python
551
from dulwich.refs import DiskRefsContainer, is_local_branch, strip_peeled_refs
552
553
# Work with disk-based references
554
refs_container = DiskRefsContainer('/path/to/repo')
555
556
# Filter for local branches only
557
all_refs = refs_container.as_dict()
558
local_branches = {
559
name: sha for name, sha in all_refs.items()
560
if is_local_branch(name)
561
}
562
563
# Remove peeled refs from dictionary
564
clean_refs = strip_peeled_refs(all_refs)
565
566
# Import refs from another repository
567
remote_refs = {'main': b'abc123...', 'develop': b'def456...'}
568
refs_container.import_refs(
569
b'refs/remotes/origin',
570
remote_refs,
571
message=b'fetch from origin'
572
)
573
```