Skip to content

Commit 75d4493

Browse files
author
Dhia Ayachi
authored
Merge branch 'main' into master
2 parents fa2c917 + 3eb750c commit 75d4493

File tree

3 files changed

+157
-31
lines changed

3 files changed

+157
-31
lines changed

cmd/changelog-build/main.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ func main() {
7474
sort.Slice(in, changelog.SortNotes(in))
7575
return in
7676
},
77+
"sortByDate": func(in []changelog.Note) []changelog.Note {
78+
sort.Slice(in, func(i, j int) bool {
79+
return in[i].Date.Before(in[j].Date)
80+
})
81+
return in
82+
},
7783
"combineTypes": func(in ...[]changelog.Note) []changelog.Note {
7884
count := 0
7985
for _, i := range in {
@@ -108,11 +114,12 @@ func main() {
108114
}
109115
var notes []changelog.Note
110116
notesByType := map[string][]changelog.Note{}
111-
for _, entry := range entries {
117+
for i := 0; i < entries.Len(); i++ {
118+
entry := entries.Get(i)
112119
if strings.HasSuffix(entry.Issue, ".txt") {
113120
entry.Issue = strings.TrimSuffix(entry.Issue, ".txt")
114121
}
115-
notes = append(notes, changelog.NotesFromEntry(entry)...)
122+
notes = append(notes, changelog.NotesFromEntry(*entry)...)
116123
}
117124
for _, note := range notes {
118125
notesByType[note.Type] = append(notesByType[note.Type], note)

entry.go

Lines changed: 143 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package changelog
22

33
import (
4+
"fmt"
45
"io/ioutil"
56
"path/filepath"
67
"sort"
8+
"sync"
9+
"time"
10+
11+
"golang.org/x/sync/errgroup"
712

813
"github.com/go-git/go-billy/v5/memfs"
914
"github.com/go-git/go-git/v5"
@@ -24,9 +29,92 @@ var TypeValues = []string{"enhancement",
2429
type Entry struct {
2530
Issue string
2631
Body string
32+
Date time.Time
33+
Hash string
34+
}
35+
36+
// EntryList provides thread-safe operations on a list of Entry values
37+
type EntryList struct {
38+
mu sync.RWMutex
39+
es []*Entry
40+
}
41+
42+
// NewEntryList returns an EntryList with capacity c
43+
func NewEntryList(c int) *EntryList {
44+
return &EntryList{
45+
es: make([]*Entry, 0, c),
46+
}
47+
}
48+
49+
// Append appends entries to the EntryList
50+
func (el *EntryList) Append(entries ...*Entry) {
51+
el.mu.Lock()
52+
defer el.mu.Unlock()
53+
el.es = append(el.es, entries...)
54+
}
55+
56+
// Get returns the Entry at index i
57+
func (el *EntryList) Get(i int) *Entry {
58+
el.mu.RLock()
59+
defer el.mu.RUnlock()
60+
if i >= len(el.es) || i < 0 {
61+
return nil
62+
}
63+
return el.es[i]
64+
}
65+
66+
// Set sets the Entry at index i. The list will be resized if i is larger than
67+
// the current list capacity.
68+
func (el *EntryList) Set(i int, e *Entry) {
69+
if i < 0 {
70+
panic("invalid slice index")
71+
}
72+
el.mu.Lock()
73+
defer el.mu.Unlock()
74+
75+
if i > (cap(el.es) - 1) {
76+
// resize the slice
77+
newEntries := make([]*Entry, i)
78+
copy(newEntries, el.es)
79+
el.es = newEntries
80+
}
81+
el.es[i] = e
82+
}
83+
84+
// Len returns the number of items in the EntryList
85+
func (el *EntryList) Len() int {
86+
el.mu.RLock()
87+
defer el.mu.RUnlock()
88+
return len(el.es)
89+
}
90+
91+
// SortByIssue does an in-place sort of the entries by their issue number.
92+
func (el *EntryList) SortByIssue() {
93+
el.mu.Lock()
94+
defer el.mu.Unlock()
95+
sort.Slice(el.es, func(i, j int) bool {
96+
return el.es[i].Issue < el.es[j].Issue
97+
})
2798
}
2899

29-
func Diff(repo, ref1, ref2, dir string) ([]Entry, error) {
100+
type changelog struct {
101+
content []byte
102+
hash string
103+
date time.Time
104+
}
105+
106+
// Diff returns the slice of Entry values that represent the difference of
107+
// entries in the dir directory within repo from ref1 revision to ref2 revision.
108+
// ref1 and ref2 should be valid git refs as strings and dir should be a valid
109+
// directory path in the repository.
110+
//
111+
// The function calculates the diff by first checking out ref2 and collecting
112+
// the set of all entries in dir. It then checks out ref1 and subtracts the
113+
// entries found in dir. The resulting set of entries is then filtered to
114+
// exclude any entries that came before the commit date of ref1.
115+
//
116+
// Along the way, if any git or filesystem interactions fail, an error is returned.
117+
func Diff(repo, ref1, ref2, dir string) (*EntryList, error) {
30118
r, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
31119
URL: repo,
32120
})
@@ -35,42 +123,34 @@ func Diff(repo, ref1, ref2, dir string) ([]Entry, error) {
35123
}
36124
rev2, err := r.ResolveRevision(plumbing.Revision(ref2))
37125
if err != nil {
38-
return nil, err
126+
return nil, fmt.Errorf("could not resolve revision %s: %w", ref2, err)
39127
}
40128
var rev1 *plumbing.Hash
41129
if ref1 != "-" {
42130
rev1, err = r.ResolveRevision(plumbing.Revision(ref1))
43131
if err != nil {
44-
return nil, err
132+
return nil, fmt.Errorf("could not resolve revision %s: %w", ref1, err)
45133
}
46134
}
47135
wt, err := r.Worktree()
48136
if err != nil {
49137
return nil, err
50138
}
51-
err = wt.Checkout(&git.CheckoutOptions{
139+
if err := wt.Checkout(&git.CheckoutOptions{
52140
Hash: *rev2,
53141
Force: true,
54-
})
55-
if err != nil {
56-
return nil, err
142+
}); err != nil {
143+
return nil, fmt.Errorf("could not checkout repository at %s: %w", ref2, err)
57144
}
58145
entriesAfterFI, err := wt.Filesystem.ReadDir(dir)
59146
if err != nil {
60-
return nil, err
147+
return nil, fmt.Errorf("could not read repository directory %s: %w", dir, err)
61148
}
62-
entriesAfter := make(map[string][]byte, len(entriesAfterFI))
149+
// a set of all entries at rev2 (this release); the set of entries at ref1
150+
// will then be subtracted from it to arrive at a set of 'candidate' entries.
151+
entryCandidates := make(map[string]bool, len(entriesAfterFI))
63152
for _, i := range entriesAfterFI {
64-
f, err := wt.Filesystem.Open(filepath.Join(dir, i.Name()))
65-
if err != nil {
66-
return nil, err
67-
}
68-
contents, err := ioutil.ReadAll(f)
69-
f.Close()
70-
if err != nil {
71-
return nil, err
72-
}
73-
entriesAfter[i.Name()] = contents
153+
entryCandidates[i.Name()] = true
74154
}
75155
if rev1 != nil {
76156
err = wt.Checkout(&git.CheckoutOptions{
@@ -82,22 +162,56 @@ func Diff(repo, ref1, ref2, dir string) ([]Entry, error) {
82162
}
83163
entriesBeforeFI, err := wt.Filesystem.ReadDir(dir)
84164
if err != nil {
85-
return nil, err
165+
return nil, fmt.Errorf("could not read repository directory %s: %w", dir, err)
86166
}
87167
for _, i := range entriesBeforeFI {
88-
delete(entriesAfter, i.Name())
168+
delete(entryCandidates, i.Name())
169+
}
170+
// checkout rev2 so that we can read files later
171+
if err := wt.Checkout(&git.CheckoutOptions{
172+
Hash: *rev2,
173+
Force: true,
174+
}); err != nil {
175+
return nil, fmt.Errorf("could not checkout repository at %s: %w", ref2, err)
89176
}
90177
}
91-
entries := make([]Entry, 0, len(entriesAfter))
92-
for name, contents := range entriesAfter {
93-
entries = append(entries, Entry{
94-
Issue: name,
95-
Body: string(contents),
178+
179+
entries := NewEntryList(len(entryCandidates))
180+
errg := new(errgroup.Group)
181+
for name := range entryCandidates {
182+
name := name // https://golang.org/doc/faq#closures_and_goroutines
183+
errg.Go(func() error {
184+
fp := filepath.Join(dir, name)
185+
f, err := wt.Filesystem.Open(fp)
186+
if err != nil {
187+
return fmt.Errorf("error opening file at %s: %w", name, err)
188+
}
189+
contents, err := ioutil.ReadAll(f)
190+
f.Close()
191+
if err != nil {
192+
return fmt.Errorf("error reading file at %s: %w", name, err)
193+
}
194+
log, err := r.Log(&git.LogOptions{FileName: &fp})
195+
if err != nil {
196+
return fmt.Errorf("error fetching git log for %s: %w", name, err)
197+
}
198+
lastChange, err := log.Next()
199+
if err != nil {
200+
return fmt.Errorf("error fetching next git log: %w", err)
201+
}
202+
entries.Append(&Entry{
203+
Issue: name,
204+
Body: string(contents),
205+
Date: lastChange.Author.When,
206+
Hash: lastChange.Hash.String(),
207+
})
208+
return nil
96209
})
97210
}
98-
sort.Slice(entries, func(i, j int) bool {
99-
return entries[i].Issue < entries[j].Issue
100-
})
211+
if err := errg.Wait(); err != nil {
212+
return nil, err
213+
}
214+
entries.SortByIssue()
101215
return entries, nil
102216
}
103217

note.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import (
44
"regexp"
55
"sort"
66
"strings"
7+
"time"
78
)
89

910
type Note struct {
1011
Type string
1112
Body string
1213
Issue string
14+
Hash string
15+
Date time.Time
1316
}
1417

1518
var textInBodyREs = []*regexp.Regexp{
@@ -53,6 +56,8 @@ func NotesFromEntry(entry Entry) []Note {
5356
Type: typ,
5457
Body: note,
5558
Issue: entry.Issue,
59+
Hash: entry.Hash,
60+
Date: entry.Date,
5661
})
5762
}
5863
}

0 commit comments

Comments
 (0)