0
# Index and Staging
1
2
Git index (staging area) operations for preparing commits. The index serves as a staging area between the working directory and repository, allowing fine-grained control over what changes are included in commits and providing conflict resolution capabilities.
3
4
## Capabilities
5
6
### Index Management
7
8
The Index class provides access to the Git staging area with methods for adding, removing, and querying staged content.
9
10
```python { .api }
11
class Index:
12
def read(self, force: bool = True):
13
"""Read index from disk"""
14
15
def write(self):
16
"""Write index to disk"""
17
18
def write_tree(self, repo: Repository = None) -> Oid:
19
"""
20
Write index as tree object.
21
22
Parameters:
23
- repo: Repository (uses index's repo if None)
24
25
Returns:
26
Tree object ID
27
"""
28
29
def read_tree(self, tree: Tree):
30
"""Read tree into index"""
31
32
def clear(self):
33
"""Remove all entries from index"""
34
35
def __len__(self) -> int:
36
"""Number of entries in index"""
37
38
def __getitem__(self, key: str | int) -> IndexEntry:
39
"""Get entry by path or index"""
40
41
def __contains__(self, path: str) -> bool:
42
"""Check if path is in index"""
43
44
def __iter__(self):
45
"""Iterate over index entries"""
46
47
# File Operations
48
def add(self, path: str):
49
"""Add file to index"""
50
51
def add_all(self, pathspecs: list[str] = None, flags: int = 0):
52
"""
53
Add all matching files to index.
54
55
Parameters:
56
- pathspecs: Path patterns (None = all files)
57
- flags: Add flags
58
"""
59
60
def remove(self, path: str, stage: int = 0):
61
"""Remove file from index"""
62
63
def remove_all(self, pathspecs: list[str], flags: int = 0):
64
"""Remove all matching files from index"""
65
66
def update_all(self, pathspecs: list[str] = None, flags: int = 0):
67
"""Update all matching files in index"""
68
69
# Entry Management
70
def get_entry_stage(self, path: str, stage: int) -> IndexEntry | None:
71
"""Get entry at specific stage"""
72
73
def add_frombuffer(self, path: str, buffer: bytes, mode: int):
74
"""Add file from buffer"""
75
76
# Conflict Resolution
77
@property
78
def conflicts(self) -> ConflictCollection:
79
"""Access merge conflicts"""
80
81
def conflict_cleanup(self):
82
"""Remove conflict markers from index"""
83
84
# Status
85
def diff_to_workdir(self, **kwargs) -> Diff:
86
"""Compare index to working directory"""
87
88
def diff_to_tree(self, tree: Tree, **kwargs) -> Diff:
89
"""Compare index to tree"""
90
91
# Index Add Flags
92
GIT_INDEX_ADD_DEFAULT: int # Default behavior
93
GIT_INDEX_ADD_FORCE: int # Add ignored files
94
GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH: int # Disable pathspec matching
95
GIT_INDEX_ADD_CHECK_PATHSPEC: int # Check pathspec validity
96
```
97
98
### Index Entries
99
100
IndexEntry represents a single file in the Git index with metadata.
101
102
```python { .api }
103
class IndexEntry:
104
@property
105
def path(self) -> str:
106
"""File path relative to repository root"""
107
108
@property
109
def oid(self) -> Oid:
110
"""Object ID of file content"""
111
112
@property
113
def mode(self) -> int:
114
"""File mode (permissions)"""
115
116
@property
117
def flags(self) -> int:
118
"""Entry flags"""
119
120
@property
121
def flags_extended(self) -> int:
122
"""Extended entry flags"""
123
124
@property
125
def stage(self) -> int:
126
"""Merge stage (0=normal, 1=base, 2=ours, 3=theirs)"""
127
128
@property
129
def ctime(self) -> int:
130
"""Creation time"""
131
132
@property
133
def mtime(self) -> int:
134
"""Modification time"""
135
136
@property
137
def dev(self) -> int:
138
"""Device ID"""
139
140
@property
141
def ino(self) -> int:
142
"""Inode number"""
143
144
@property
145
def uid(self) -> int:
146
"""User ID"""
147
148
@property
149
def gid(self) -> int:
150
"""Group ID"""
151
152
@property
153
def file_size(self) -> int:
154
"""File size in bytes"""
155
```
156
157
### Conflict Resolution
158
159
Handle merge conflicts in the index with three-way merge information.
160
161
```python { .api }
162
class ConflictCollection:
163
def __getitem__(self, path: str) -> tuple[IndexEntry, IndexEntry, IndexEntry]:
164
"""Get conflict entries (ancestor, ours, theirs)"""
165
166
def __contains__(self, path: str) -> bool:
167
"""Check if path has conflicts"""
168
169
def __iter__(self):
170
"""Iterate over conflicted paths"""
171
172
def __len__(self) -> int:
173
"""Number of conflicted files"""
174
175
def get(self, path: str) -> tuple[IndexEntry, IndexEntry, IndexEntry] | None:
176
"""Get conflict entries with None fallback"""
177
178
class MergeFileResult:
179
@property
180
def automergeable(self) -> bool:
181
"""True if merge was automatic"""
182
183
@property
184
def path(self) -> str:
185
"""Merged file path"""
186
187
@property
188
def mode(self) -> int:
189
"""Merged file mode"""
190
191
@property
192
def contents(self) -> bytes:
193
"""Merged file contents"""
194
```
195
196
### Status Constants
197
198
File status flags used throughout pygit2 for working directory and index state.
199
200
```python { .api }
201
# Index Status Flags
202
GIT_STATUS_CURRENT: int # No changes
203
GIT_STATUS_INDEX_NEW: int # New file in index
204
GIT_STATUS_INDEX_MODIFIED: int # Modified file in index
205
GIT_STATUS_INDEX_DELETED: int # Deleted file in index
206
GIT_STATUS_INDEX_RENAMED: int # Renamed file in index
207
GIT_STATUS_INDEX_TYPECHANGE: int # Type change in index
208
209
# Working Tree Status Flags
210
GIT_STATUS_WT_NEW: int # New file in workdir
211
GIT_STATUS_WT_MODIFIED: int # Modified file in workdir
212
GIT_STATUS_WT_DELETED: int # Deleted file in workdir
213
GIT_STATUS_WT_TYPECHANGE: int # Type change in workdir
214
GIT_STATUS_WT_RENAMED: int # Renamed file in workdir
215
GIT_STATUS_WT_UNREADABLE: int # Unreadable file
216
217
# Combined Status Flags
218
GIT_STATUS_IGNORED: int # Ignored file
219
GIT_STATUS_CONFLICTED: int # Conflicted file
220
```
221
222
### Usage Examples
223
224
#### Basic Index Operations
225
226
```python
227
import pygit2
228
229
repo = pygit2.Repository('/path/to/repo')
230
index = repo.index
231
232
# Check index status
233
print(f"Index has {len(index)} entries")
234
235
# Add single file
236
index.add('README.md')
237
index.write()
238
239
# Add all files
240
index.add_all()
241
index.write()
242
243
# Remove file
244
index.remove('unwanted.txt')
245
index.write()
246
247
# Stage specific files
248
pathspecs = ['*.py', 'docs/*.md']
249
index.add_all(pathspecs)
250
index.write()
251
```
252
253
#### Working with Index Entries
254
255
```python
256
# Examine index contents
257
for entry in index:
258
print(f"{entry.path}: {entry.oid} (mode: {oct(entry.mode)})")
259
if entry.stage > 0:
260
print(f" Merge stage: {entry.stage}")
261
262
# Get specific entry
263
if 'main.py' in index:
264
entry = index['main.py']
265
print(f"main.py: {entry.oid}")
266
print(f"Size: {entry.file_size} bytes")
267
print(f"Modified: {entry.mtime}")
268
269
# Check for conflicts
270
if index.conflicts:
271
print(f"Index has {len(index.conflicts)} conflicted files")
272
```
273
274
#### Creating Commits from Index
275
276
```python
277
# Create tree from index
278
tree_oid = index.write_tree()
279
280
# Create commit
281
signature = pygit2.Signature('Author', 'author@example.com')
282
commit_oid = repo.create_commit(
283
'HEAD',
284
signature,
285
signature,
286
'Commit message',
287
tree_oid,
288
[repo.head.target] # Parent commits
289
)
290
291
print(f"Created commit: {commit_oid}")
292
```
293
294
#### Conflict Resolution
295
296
```python
297
# Check for merge conflicts
298
if repo.index.conflicts:
299
print("Resolving conflicts:")
300
301
for path in repo.index.conflicts:
302
ancestor, ours, theirs = repo.index.conflicts[path]
303
304
print(f"\nConflict in {path}:")
305
if ancestor:
306
print(f" Ancestor: {ancestor.oid}")
307
if ours:
308
print(f" Ours: {ours.oid}")
309
if theirs:
310
print(f" Theirs: {theirs.oid}")
311
312
# Get file contents for manual merge
313
if ours and theirs:
314
our_content = repo[ours.oid].data
315
their_content = repo[theirs.oid].data
316
317
# Perform manual merge (simplified)
318
merged_content = merge_files(our_content, their_content)
319
320
# Stage resolved content
321
repo.index.add_frombuffer(
322
path,
323
merged_content,
324
pygit2.GIT_FILEMODE_BLOB
325
)
326
327
# Write resolved index
328
repo.index.write()
329
330
# Clean up conflict markers
331
repo.index.conflict_cleanup()
332
```
333
334
#### Index Diffs
335
336
```python
337
# Compare index to working directory
338
diff = repo.index.diff_to_workdir()
339
print(f"Working directory has {len(diff.deltas)} changes:")
340
341
for delta in diff.deltas:
342
status_map = {
343
pygit2.GIT_DELTA_ADDED: "Added",
344
pygit2.GIT_DELTA_DELETED: "Deleted",
345
pygit2.GIT_DELTA_MODIFIED: "Modified",
346
pygit2.GIT_DELTA_RENAMED: "Renamed"
347
}
348
status = status_map.get(delta.status, "Unknown")
349
print(f" {status}: {delta.new_file.path}")
350
351
# Compare index to HEAD
352
head_tree = repo[repo.head.target].tree
353
diff = repo.index.diff_to_tree(head_tree)
354
print(f"Index has {len(diff.deltas)} staged changes")
355
```
356
357
#### Advanced Index Operations
358
359
```python
360
# Add files from buffer
361
file_content = b"print('Hello, World!')"
362
index.add_frombuffer('hello.py', file_content, pygit2.GIT_FILEMODE_BLOB)
363
364
# Update only modified files (don't add new files)
365
index.update_all()
366
367
# Force add ignored files
368
index.add_all(flags=pygit2.GIT_INDEX_ADD_FORCE)
369
370
# Read specific tree into index
371
tree = repo.revparse_single('v1.0.0').tree
372
index.read_tree(tree)
373
index.write()
374
375
# Clear index completely
376
index.clear()
377
index.write()
378
```