Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Git Log Revision Range #36

Open
philnielsen opened this issue Apr 21, 2020 · 5 comments
Open

Git Log Revision Range #36

philnielsen opened this issue Apr 21, 2020 · 5 comments
Labels
enhancement New feature or request help wanted Extra attention is needed no-autoclose Issues/PRs to be ignored by stale bot

Comments

@philnielsen
Copy link

Found this old Issue on the old repo: src-d/go-git#1166.
It would be super useful for something like git log HEAD..origin/master to be added to git.LogOptions as this would make working with Git Logs much easier.

@mcuadros
Copy link
Member

This requires a couple of developments:

  • The first is implemented the ranged revision parser, which I believe is not implemented in the internal/revision package. @antham the original contributor of this part knows more than me about the status of this.

  • And the second implemented the logic of this on the Log, that should be very hard.

PR's are welcome.

@mcuadros mcuadros added enhancement New feature or request help wanted Extra attention is needed labels Apr 23, 2020
@dprotaso
Copy link

dprotaso commented Jan 8, 2021

A workaround I've used is

repo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
	URL: "some-repo",
})

start, err := repo.ResolveRevision("start-sha")
check(err)

end, err := repo.ResolveRevision("end-sha")
check(err)

iter, err := repo.Log(&git.LogOptions{From: *end})
check(err)

err = iter.ForEach(func(c *object.Commit) error {
	fmt.Println(c.Message)

	if c.Hash == *start {
		return storer.ErrStop
	}

	return nil
})

check(err)

@Ajnasz
Copy link

Ajnasz commented Jan 9, 2021

Hey!

Based on @dprotaso code, I came up with this:

An iterator, which expects 2 object.CommitIter (which can come from git.Repository.Log) and it will call the callback on the commits which are in one list but not on the other. If I understood correctly the git documentation that's what the git log a..b does.

... commits reachable from any of the ones given with ^ in front are subtracted from that set. ...

... A special notation ".." can be used as a short-hand for "^ ". ...

What I do I expect two object.CommitIters. I create a map from the hash of the exclude list, then during the iteration I skip those commits which are already exists in the map.

package main

import (
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
)

type excludeCommitIter struct {
	iter        object.CommitIter
	excludeIter object.CommitIter
	excludeList map[plumbing.Hash]bool
}

func newExcludeCommitIter(iter object.CommitIter, excludeIter object.CommitIter) *excludeCommitIter {
	out := excludeCommitIter{iter: iter, excludeIter: excludeIter}
	out.excludeList = make(map[plumbing.Hash]bool)
	return &out
}

func (iter *excludeCommitIter) init() {
	if len(iter.excludeList) == 0 {
		iter.excludeIter.ForEach(func(c *object.Commit) error {
			iter.excludeList[c.Hash] = true
			return nil
		})
	}
}

func (iter *excludeCommitIter) Next() (*object.Commit, error) {
	iter.init()
	obj, err := iter.iter.Next()

	if err != nil {
		return nil, err
	}

	if _, exists := iter.excludeList[obj.Hash]; !exists {
		return obj, nil
	}

	return iter.Next()
}

func (iter *excludeCommitIter) ForEach(cb func(*object.Commit) error) error {
	iter.init()
	return iter.iter.ForEach(func(c *object.Commit) error {
		if _, exists := iter.excludeList[c.Hash]; !exists {
			return cb(c)
		}

		return nil
	})
}

Using this iterator I can get the list of changes between the my two branches.
In the following example I print the subject of each commit which is in sourceRev but not in exculdeRev

func gitLogRevision(sourcePath string, sourceRev plumbing.Revision, excludeRev plumbing.Revision) ([]string, error) {
	r, err := git.PlainOpen(filepath.Join(sourcePath, ".git"))

	if err != nil {
		return nil, err
	}

	source, err := r.ResolveRevision(sourceRev)
	if err != nil {
		return nil, err
	}

	exclude, err := r.ResolveRevision(excludeRev)
	if err != nil {
		return nil, err
	}

	sourceIter, err := r.Log(&git.LogOptions{From: *source})
	if err != nil {
		return nil, err
	}

	excludeIter, err := r.Log(&git.LogOptions{From: *exclude})
	if err != nil {
		return nil, err
	}

	iter := newExcludeCommitIter(sourceIter, excludeIter)

	var firstLines []string

	err = iter.ForEach(func(c *object.Commit) error {
		// git log --no-merges
		if len(c.ParentHashes) > 1 {
			return nil
		}

		// git log --format="%s", aka the first line of the commit message
		firstLines = append(firstLines, strings.Split(c.Message, "\n")[0])

		return nil
	})

	if err != nil {
		return nil, err
	}

	return firstLines, nil
}

It can be further improved, for example we can make it more general, "filterable iterator" and the filter function can be passed in, therefore any kind of filtering could be done.

I'm still not sure that it's right, however on my test env it looks good.

What do you think?

@Ajnasz
Copy link

Ajnasz commented Jan 10, 2021

It can be implemented even more simpler, by using the newFilterCommitIter

	seen := map[plumbing.Hash]struct{}{}

	excludeIter.ForEach(func(c *object.Commit) error {
		seen[c.Hash] = struct{}{}
		return nil
	})

	var isValid object.CommitFilter = func(commit *object.Commit) bool {
		_, ok := seen[commit.Hash]

                // len(commit.ParentHashes) filters out merge commits
		return !ok && len(commit.ParentHashes) < 2
	}

	iter := object.NewFilterCommitIter(sourceCommit, &isValid, nil)

I don't know if is there a better way to see if a commit exists in a branch

Ajnasz added a commit to Ajnasz/go-git that referenced this issue Jan 30, 2021
The implemented a way to get changes between two revisions
Basicall what `git log foo..bar` would do.

Extended `LogOptions`, so it accepts a `Except` Hash which defines what
commits should be excluded from the final result.
Hadling that flag, if exists we create a new iterator which called
commitDifferenceIterator.

That iterator expects a map of `Hash`es, which commits will be excluded
from the final commit list.

Thsi pr is related to issue go-git#36
rodibrin added a commit to marcusholl/jenkins-library that referenced this issue Feb 8, 2021
issue defined: go-git/go-git#36
solution provided by: https://github.com/Ajnasz/go-git/
    commit/ed33969b0b7994b593d1a36fc46f034791cafe6f
@github-actions
Copy link

To help us keep things tidy and focus on the active tasks, we've introduced a stale bot to spot issues/PRs that haven't had any activity in a while.

This particular issue hasn't had any updates or activity in the past 90 days, so it's been labeled as 'stale'. If it remains inactive for the next 30 days, it'll be automatically closed.

We understand everyone's busy, but if this issue is still important to you, please feel free to add a comment or make an update to keep it active.

Thanks for your understanding and cooperation!

@github-actions github-actions bot added the stale Issues/PRs that are marked for closure due to inactivity label Sep 30, 2023
@pjbgf pjbgf added no-autoclose Issues/PRs to be ignored by stale bot and removed stale Issues/PRs that are marked for closure due to inactivity labels Sep 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed no-autoclose Issues/PRs to be ignored by stale bot
Projects
None yet
Development

No branches or pull requests

5 participants