in tools/go-changelog/entry.go [154:253]
func Diff(repo, ref1, ref2, dir string) (*EntryList, error) {
r, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: repo,
})
if err != nil {
return nil, err
}
rev2, err := r.ResolveRevision(plumbing.Revision(ref2))
if err != nil {
return nil, fmt.Errorf("could not resolve revision %s: %w", ref2, err)
}
var rev1 *plumbing.Hash
if ref1 != "-" {
rev1, err = r.ResolveRevision(plumbing.Revision(ref1))
if err != nil {
return nil, fmt.Errorf("could not resolve revision %s: %w", ref1, err)
}
}
wt, err := r.Worktree()
if err != nil {
return nil, err
}
if err := wt.Checkout(&git.CheckoutOptions{
Hash: *rev2,
Force: true,
}); err != nil {
return nil, fmt.Errorf("could not checkout repository at %s: %w", ref2, err)
}
entriesAfterFI, err := wt.Filesystem.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("could not read repository directory %s: %w", dir, err)
}
// a set of all entries at rev2 (this release); the set of entries at ref1
// will then be subtracted from it to arrive at a set of 'candidate' entries.
entryCandidates := make(map[string]bool, len(entriesAfterFI))
for _, i := range entriesAfterFI {
entryCandidates[i.Name()] = true
}
if rev1 != nil {
err = wt.Checkout(&git.CheckoutOptions{
Hash: *rev1,
Force: true,
})
if err != nil {
return nil, err
}
entriesBeforeFI, err := wt.Filesystem.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("could not read repository directory %s: %w", dir, err)
}
for _, i := range entriesBeforeFI {
delete(entryCandidates, i.Name())
}
// checkout rev2 so that we can read files later
if err := wt.Checkout(&git.CheckoutOptions{
Hash: *rev2,
Force: true,
}); err != nil {
return nil, fmt.Errorf("could not checkout repository at %s: %w", ref2, err)
}
}
entries := NewEntryList(len(entryCandidates))
errg := new(errgroup.Group)
for name := range entryCandidates {
name := name // https://golang.org/doc/faq#closures_and_goroutines
errg.Go(func() error {
fp := filepath.Join(dir, name)
f, err := wt.Filesystem.Open(fp)
if err != nil {
return fmt.Errorf("error opening file at %s: %w", name, err)
}
contents, err := ioutil.ReadAll(f)
f.Close()
if err != nil {
return fmt.Errorf("error reading file at %s: %w", name, err)
}
log, err := r.Log(&git.LogOptions{FileName: &fp})
if err != nil {
return fmt.Errorf("error fetching git log for %s: %w", name, err)
}
lastChange, err := log.Next()
if err != nil {
return fmt.Errorf("error fetching next git log: %w", err)
}
entries.Append(&Entry{
Issue: name,
Body: string(contents),
Date: lastChange.Author.When,
Hash: lastChange.Hash.String(),
})
return nil
})
}
if err := errg.Wait(); err != nil {
return nil, err
}
entries.SortByIssue()
return entries, nil
}