func Diff()

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
}