aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/difftree/difftree.go
blob: ff1ceaf153acd9e5eec5742ee25963f4fe7de6b3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package difftree

import (
	"bytes"
	"os"

	"srcd.works/go-git.v4/plumbing/object"
	"srcd.works/go-git.v4/utils/merkletrie"
	"srcd.works/go-git.v4/utils/merkletrie/noder"
)

// DiffTree compares the content and mode of the blobs found via two
// tree objects.
func DiffTree(a, b *object.Tree) ([]*Change, error) {
	from := newTreeNoder(a)
	to := newTreeNoder(b)

	merkletrieChanges, err := merkletrie.DiffTree(from, to, hashEqual)
	if err != nil {
		return nil, err
	}

	return newChanges(merkletrieChanges)
}

// check if the hash of the contents is different, if not, check if
// the permissions are different (but taking into account deprecated
// file modes).  On a treenoder, the hash of the contents is codified
// in the first 20 bytes of the data returned by Hash() and the last
// 4 bytes is the mode.
func hashEqual(a, b noder.Hasher) bool {
	hashA, hashB := a.Hash(), b.Hash()
	contentsA, contentsB := hashA[:20], hashB[:20]

	sameContents := bytes.Equal(contentsA, contentsB)
	if !sameContents {
		return false
	}

	modeA, modeB := hashA[20:], hashB[20:]

	return equivalentMode(modeA, modeB)
}

func equivalentMode(a, b []byte) bool {
	if isFilish(a) && isFilish(b) {
		return true
	}
	return bytes.Equal(a, b)
}

var (
	file           = modeToBytes(object.FileMode)
	fileDeprecated = modeToBytes(object.FileModeDeprecated)
	// remove this by fixing plumbing.Object mode ASAP
	fileGoGit = modeToBytes(os.FileMode(0644))
)

func isFilish(b []byte) bool {
	return bytes.Equal(b, file) ||
		bytes.Equal(b, fileDeprecated) ||
		bytes.Equal(b, fileGoGit)
}