aboutsummaryrefslogtreecommitdiffstats
path: root/utils/merkletrie/change.go
blob: 450feb4bac82e819cdc948968de60f6064203fde (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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package merkletrie

import (
	"errors"
	"fmt"
	"io"

	"github.com/go-git/go-git/v5/utils/merkletrie/noder"
)

var (
	ErrEmptyFileName = errors.New("empty filename in tree entry")
)

// Action values represent the kind of things a Change can represent:
// insertion, deletions or modifications of files.
type Action int

// The set of possible actions in a change.
const (
	_ Action = iota
	Insert
	Delete
	Modify
)

// String returns the action as a human readable text.
func (a Action) String() string {
	switch a {
	case Insert:
		return "Insert"
	case Delete:
		return "Delete"
	case Modify:
		return "Modify"
	default:
		panic(fmt.Sprintf("unsupported action: %d", a))
	}
}

// A Change value represent how a noder has change between to merkletries.
type Change struct {
	// The noder before the change or nil if it was inserted.
	From noder.Path
	// The noder after the change or nil if it was deleted.
	To noder.Path
}

// Action is convenience method that returns what Action c represents.
func (c *Change) Action() (Action, error) {
	if c.From == nil && c.To == nil {
		return Action(0), fmt.Errorf("malformed change: nil from and to")
	}
	if c.From == nil {
		return Insert, nil
	}
	if c.To == nil {
		return Delete, nil
	}

	return Modify, nil
}

// NewInsert returns a new Change representing the insertion of n.
func NewInsert(n noder.Path) Change { return Change{To: n} }

// NewDelete returns a new Change representing the deletion of n.
func NewDelete(n noder.Path) Change { return Change{From: n} }

// NewModify returns a new Change representing that a has been modified and
// it is now b.
func NewModify(a, b noder.Path) Change {
	return Change{
		From: a,
		To:   b,
	}
}

// String returns a single change in human readable form, using the
// format: '<' + action + space + path + '>'.  The contents of the file
// before or after the change are not included in this format.
//
// Example: inserting a file at the path a/b/c.txt will return "<Insert
// a/b/c.txt>".
func (c Change) String() string {
	action, err := c.Action()
	if err != nil {
		panic(err)
	}

	var path string
	if action == Delete {
		path = c.From.String()
	} else {
		path = c.To.String()
	}

	return fmt.Sprintf("<%s %s>", action, path)
}

// Changes is a list of changes between to merkletries.
type Changes []Change

// NewChanges returns an empty list of changes.
func NewChanges() Changes {
	return Changes{}
}

// Add adds the change c to the list of changes.
func (l *Changes) Add(c Change) {
	*l = append(*l, c)
}

// AddRecursiveInsert adds the required changes to insert all the
// file-like noders found in root, recursively.
func (l *Changes) AddRecursiveInsert(root noder.Path) error {
	return l.addRecursive(root, NewInsert)
}

// AddRecursiveDelete adds the required changes to delete all the
// file-like noders found in root, recursively.
func (l *Changes) AddRecursiveDelete(root noder.Path) error {
	return l.addRecursive(root, NewDelete)
}

type noderToChangeFn func(noder.Path) Change // NewInsert or NewDelete

func (l *Changes) addRecursive(root noder.Path, ctor noderToChangeFn) error {
	if root.String() == "" {
		return ErrEmptyFileName
	}

	if !root.IsDir() {
		l.Add(ctor(root))
		return nil
	}

	i, err := NewIterFromPath(root)
	if err != nil {
		return err
	}

	var current noder.Path
	for {
		if current, err = i.Step(); err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		if current.IsDir() {
			continue
		}
		l.Add(ctor(current))
	}

	return nil
}