Skip to content

Commit 91e941d

Browse files
yasunogithubclaude
andcommitted
fix: handle edge cases in incremental tree builder
- Handle file/dir type conflicts in applyChangesToTree (dir→file and file→dir) - Skip empty trees from delete-only changes in createTreeFromChanges - Normalize dirPathRel with filepath.ToSlash for Windows compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 6dc290b19fdd
1 parent be1a780 commit 91e941d

1 file changed

Lines changed: 38 additions & 1 deletion

File tree

cmd/entire/cli/checkpoint/tree_incremental.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ func applyChangesToTree(repo *git.Repository, baseTreeHash plumbing.Hash, change
9999
Hash: newSubHash,
100100
})
101101
processedDirs[entry.Name] = true
102+
} else if fileChange, ok := changes.files[entry.Name]; ok {
103+
// Type conflict: base has dir, changes has file → replace dir with file
104+
if !fileChange.delete {
105+
newEntries = append(newEntries, object.TreeEntry{
106+
Name: entry.Name,
107+
Mode: fileChange.mode,
108+
Hash: fileChange.hash,
109+
})
110+
}
111+
processedFiles[entry.Name] = true
102112
} else {
103113
// No changes in this directory, keep as-is
104114
newEntries = append(newEntries, entry)
@@ -115,6 +125,20 @@ func applyChangesToTree(repo *git.Repository, baseTreeHash plumbing.Hash, change
115125
}
116126
// If delete: don't add to newEntries
117127
processedFiles[entry.Name] = true
128+
} else if dirChanges, ok := changes.dirs[entry.Name]; ok {
129+
// Type conflict: base has file, changes has dir → replace file with dir
130+
newSubHash, subErr := createTreeFromChanges(repo, dirChanges)
131+
if subErr != nil {
132+
return plumbing.ZeroHash, subErr
133+
}
134+
if newSubHash != plumbing.ZeroHash {
135+
newEntries = append(newEntries, object.TreeEntry{
136+
Name: entry.Name,
137+
Mode: filemode.Dir,
138+
Hash: newSubHash,
139+
})
140+
}
141+
processedDirs[entry.Name] = true
118142
} else {
119143
// No change, keep as-is
120144
newEntries = append(newEntries, entry)
@@ -140,6 +164,10 @@ func applyChangesToTree(repo *git.Repository, baseTreeHash plumbing.Hash, change
140164
if subErr != nil {
141165
return plumbing.ZeroHash, subErr
142166
}
167+
// Skip empty trees (e.g., all changes were deletions for non-existent paths)
168+
if newSubHash == plumbing.ZeroHash {
169+
continue
170+
}
143171
newEntries = append(newEntries, object.TreeEntry{
144172
Name: name,
145173
Mode: filemode.Dir,
@@ -172,13 +200,22 @@ func createTreeFromChanges(repo *git.Repository, changes *changeTree) (plumbing.
172200
if err != nil {
173201
return plumbing.ZeroHash, err
174202
}
203+
// Skip empty subtrees (all files were deletions)
204+
if subHash == plumbing.ZeroHash {
205+
continue
206+
}
175207
entries = append(entries, object.TreeEntry{
176208
Name: name,
177209
Mode: filemode.Dir,
178210
Hash: subHash,
179211
})
180212
}
181213

214+
// If all entries were deletions, don't create an empty tree
215+
if len(entries) == 0 {
216+
return plumbing.ZeroHash, nil
217+
}
218+
182219
sortTreeEntries(entries)
183220
return storeTree(repo, entries)
184221
}
@@ -219,7 +256,7 @@ func addDirectoryToChangeTree(repo *git.Repository, dirPathAbs, dirPathRel strin
219256
}
220257

221258
// Use forward slashes for git tree paths
222-
treePath := dirPathRel + "/" + filepath.ToSlash(relWithinDir)
259+
treePath := filepath.ToSlash(dirPathRel) + "/" + filepath.ToSlash(relWithinDir)
223260
ct.addFile(treePath, blobHash, mode)
224261
return nil
225262
})

0 commit comments

Comments
 (0)