ref: 115beec0b2d29cb89ec73809b432a40e22bac07b
dir: /difftrees.go/
package furgit
// TreeDiffEntryKind represents the type of difference between two tree entries.
type TreeDiffEntryKind int
const (
// TreeDiffEntryKindInvalid indicates an invalid difference type.
TreeDiffEntryKindInvalid TreeDiffEntryKind = iota
// TreeDiffEntryKindDeleted indicates that the entry was deleted.
TreeDiffEntryKindDeleted
// TreeDiffEntryKindAdded indicates that the entry was added.
TreeDiffEntryKindAdded
// TreeDiffEntryKindModified indicates that the entry was modified.
TreeDiffEntryKindModified
)
// TreeDiffEntry represents a difference between two tree entries.
type TreeDiffEntry struct {
// Path is the full slash-separated path relative to the root
// of the repository.
Path []byte
// Kind indicates the type of difference.
Kind TreeDiffEntryKind
// Old is the old tree entry (nil iff added).
Old *TreeEntry
// New is the new tree entry (nil iff deleted).
New *TreeEntry
}
// DiffTrees compares two trees rooted at a and b and returns all differences
// as a flat slice of TreeDiffEntry. Differences are discovered recursively.
func (repo *Repository) DiffTrees(a, b *StoredTree) ([]TreeDiffEntry, error) {
var out []TreeDiffEntry
err := repo.diffTreesRecursive(a, b, nil, &out)
return out, err
}
func (repo *Repository) diffTreesRecursive(a, b *StoredTree, prefix []byte, out *[]TreeDiffEntry) error {
if a == nil && b == nil {
return nil
}
if a == nil {
for i := range b.Entries {
entry := &b.Entries[i]
full := joinPath(prefix, entry.Name)
*out = append(*out, TreeDiffEntry{
Path: full,
Kind: TreeDiffEntryKindAdded,
Old: nil,
New: entry,
})
if entry.Mode == FileModeDir {
sub, err := repo.readTree(entry.ID)
if err != nil {
return err
}
if err := repo.diffTreesRecursive(nil, sub, full, out); err != nil {
return err
}
}
}
return nil
}
if b == nil {
for i := range a.Entries {
entry := &a.Entries[i]
full := joinPath(prefix, entry.Name)
*out = append(*out, TreeDiffEntry{
Path: full,
Kind: TreeDiffEntryKindDeleted,
Old: entry,
New: nil,
})
if entry.Mode == FileModeDir {
sub, err := repo.readTree(entry.ID)
if err != nil {
return err
}
if err := repo.diffTreesRecursive(sub, nil, full, out); err != nil {
return err
}
}
}
return nil
}
left := make(map[string]*TreeEntry, len(a.Entries))
for i := range a.Entries {
e := &a.Entries[i]
left[string(e.Name)] = e
}
right := make(map[string]*TreeEntry, len(b.Entries))
for i := range b.Entries {
e := &b.Entries[i]
right[string(e.Name)] = e
}
seen := make(map[string]bool, len(a.Entries)+len(b.Entries))
for n := range left {
seen[n] = true
}
for n := range right {
seen[n] = true
}
for name := range seen {
le := left[name]
re := right[name]
full := joinPath(prefix, []byte(name))
switch {
case le == nil && re != nil:
*out = append(*out, TreeDiffEntry{
Path: full,
Kind: TreeDiffEntryKindAdded,
Old: nil,
New: re,
})
if re.Mode == FileModeDir {
sub, err := repo.readTree(re.ID)
if err != nil {
return err
}
if err := repo.diffTreesRecursive(nil, sub, full, out); err != nil {
return err
}
}
case le != nil && re == nil:
*out = append(*out, TreeDiffEntry{
Path: full,
Kind: TreeDiffEntryKindDeleted,
Old: le,
New: nil,
})
if le.Mode == FileModeDir {
sub, err := repo.readTree(le.ID)
if err != nil {
return err
}
if err := repo.diffTreesRecursive(sub, nil, full, out); err != nil {
return err
}
}
default:
modified := (le.Mode != re.Mode) || (le.ID != re.ID)
if modified {
*out = append(*out, TreeDiffEntry{
Path: full,
Kind: TreeDiffEntryKindModified,
Old: le,
New: re,
})
}
if le.Mode == FileModeDir && re.Mode == FileModeDir && le.ID != re.ID {
ls, err := repo.readTree(le.ID)
if err != nil {
return err
}
rs, err := repo.readTree(re.ID)
if err != nil {
return err
}
if err := repo.diffTreesRecursive(ls, rs, full, out); err != nil {
return err
}
}
}
}
return nil
}
func joinPath(prefix, name []byte) []byte {
if len(prefix) == 0 {
out := make([]byte, len(name))
copy(out, name)
return out
}
out := make([]byte, len(prefix)+1+len(name))
copy(out, prefix)
out[len(prefix)] = '/'
copy(out[len(prefix)+1:], name)
return out
}
func (repo *Repository) readTree(id Hash) (*StoredTree, error) {
obj, err := repo.ReadObject(id)
if err != nil {
return nil, err
}
tree, ok := obj.(*StoredTree)
if !ok {
return nil, ErrInvalidObject
}
return tree, nil
}