aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/object/change.go
blob: 729ff5a3c3ce0c0baf8d597e19ee902a576ca294 (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
package object

import (
	"bytes"
	"fmt"
	"strings"

	"gopkg.in/src-d/go-git.v4/utils/merkletrie"
)

// Change values represent a detected change between two git trees.  For
// modifications, From is the original status of the node and To is its
// final status.  For insertions, From is the zero value and for
// deletions To is the zero value.
type Change struct {
	From ChangeEntry
	To   ChangeEntry
}

var empty = ChangeEntry{}

// Action returns the kind of action represented by the change, an
// insertion, a deletion or a modification.
func (c *Change) Action() (merkletrie.Action, error) {
	if c.From == empty && c.To == empty {
		return merkletrie.Action(0),
			fmt.Errorf("malformed change: empty from and to")
	}
	if c.From == empty {
		return merkletrie.Insert, nil
	}
	if c.To == empty {
		return merkletrie.Delete, nil
	}

	return merkletrie.Modify, nil
}

// Files return the files before and after a change.
// For insertions from will be nil. For deletions to will be nil.
func (c *Change) Files() (from, to *File, err error) {
	action, err := c.Action()
	if err != nil {
		return
	}

	if action == merkletrie.Insert || action == merkletrie.Modify {
		to, err = c.To.Tree.TreeEntryFile(&c.To.TreeEntry)
		if !c.To.TreeEntry.Mode.IsFile() {
			return nil, nil, nil
		}

		if err != nil {
			return
		}
	}

	if action == merkletrie.Delete || action == merkletrie.Modify {
		from, err = c.From.Tree.TreeEntryFile(&c.From.TreeEntry)
		if !c.From.TreeEntry.Mode.IsFile() {
			return nil, nil, nil
		}

		if err != nil {
			return
		}
	}

	return
}

func (c *Change) String() string {
	action, err := c.Action()
	if err != nil {
		return fmt.Sprintf("malformed change")
	}

	return fmt.Sprintf("<Action: %s, Path: %s>", action, c.name())
}

// Patch returns a Patch with all the file changes in chunks. This
// representation can be used to create several diff outputs.
func (c *Change) Patch() (*Patch, error) {
	return getPatch("", c)
}

func (c *Change) name() string {
	if c.From != empty {
		return c.From.Name
	}

	return c.To.Name
}

// ChangeEntry values represent a node that has suffered a change.
type ChangeEntry struct {
	// Full path of the node using "/" as separator.
	Name string
	// Parent tree of the node that has changed.
	Tree *Tree
	// The entry of the node.
	TreeEntry TreeEntry
}

// Changes represents a collection of changes between two git trees.
// Implements sort.Interface lexicographically over the path of the
// changed files.
type Changes []*Change

func (c Changes) Len() int {
	return len(c)
}

func (c Changes) Swap(i, j int) {
	c[i], c[j] = c[j], c[i]
}

func (c Changes) Less(i, j int) bool {
	return strings.Compare(c[i].name(), c[j].name()) < 0
}

func (c Changes) String() string {
	var buffer bytes.Buffer
	buffer.WriteString("[")
	comma := ""
	for _, v := range c {
		buffer.WriteString(comma)
		buffer.WriteString(v.String())
		comma = ", "
	}
	buffer.WriteString("]")

	return buffer.String()
}

// Patch returns a Patch with all the changes in chunks. This
// representation can be used to create several diff outputs.
func (c Changes) Patch() (*Patch, error) {
	return getPatch("", c...)
}