Browse Source

Add --first-parent

Signed-off-by: Vadim Markovtsev <vadim@sourced.tech>
Vadim Markovtsev 6 years ago
parent
commit
141fb04211
4 changed files with 59 additions and 16 deletions
  1. 6 3
      cmd/hercules/root.go
  2. 1 1
      doc.go
  3. 32 10
      internal/core/pipeline.go
  4. 20 2
      internal/core/pipeline_test.go

+ 6 - 3
cmd/hercules/root.go

@@ -151,6 +151,7 @@ targets can be added using the --plugin system.`,
 	Args: cobra.RangeArgs(1, 2),
 	Run: func(cmd *cobra.Command, args []string) {
 		flags := cmd.Flags()
+		firstParent, _ := flags.GetBool("first-parent")
 		commitsFile, _ := flags.GetString("commits")
 		protobuf, _ := flags.GetBool("pb")
 		profile, _ := flags.GetBool("profile")
@@ -199,7 +200,7 @@ targets can be added using the --plugin system.`,
 		var err error
 		if commitsFile == "" {
 			fmt.Fprint(os.Stderr, "git log...\r")
-			commits, err = pipeline.Commits()
+			commits, err = pipeline.Commits(firstParent)
 		} else {
 			commits, err = hercules.LoadCommitsFromFile(commitsFile, repository)
 		}
@@ -389,10 +390,12 @@ func init() {
 	rootCmd.MarkFlagFilename("plugin")
 	rootFlags := rootCmd.Flags()
 	rootFlags.String("commits", "", "Path to the text file with the "+
-		"commit history to follow instead of the default rev-list "+
-		"--first-parent. The format is the list of hashes, each hash on a "+
+		"commit history to follow instead of the default `git log`. " +
+		"The format is the list of hashes, each hash on a "+
 		"separate line. The first hash is the root.")
 	rootCmd.MarkFlagFilename("commits")
+	rootFlags.Bool("first-parent", false, "Follow only the first parent in the commit history - " +
+		"\"git log --first-parent\".")
 	rootFlags.Bool("pb", false, "The output format will be Protocol Buffers instead of YAML.")
 	rootFlags.Bool("quiet", !terminal.IsTerminal(int(os.Stdin.Fd())),
 		"Do not print status updates to stderr.")

+ 1 - 1
doc.go

@@ -29,7 +29,7 @@ Then add the required analysis:
 This call will add all the needed intermediate pipeline items. Then link and execute the analysis tree:
 
   pipeline.Initialize(nil)
-  result, err := pipeline.Run(pipeline.Commits())
+  result, err := pipeline.Run(pipeline.Commits(false))
 
 Finally extract the result:
 

+ 32 - 10
internal/core/pipeline.go

@@ -364,15 +364,18 @@ func (pipeline *Pipeline) Len() int {
 }
 
 // Commits returns the list of commits from the history similar to `git log` over the HEAD.
-func (pipeline *Pipeline) Commits() ([]*object.Commit, error) {
-	cit, err := pipeline.repository.Log(&git.LogOptions{From: plumbing.ZeroHash})
+// `firstParent` specifies whether to leave only the first parent after each merge
+// (`git log --first-parent`) - effectively decreasing the accuracy but increasing performance.
+func (pipeline *Pipeline) Commits(firstParent bool) ([]*object.Commit, error) {
+	var result []*object.Commit
+	repository := pipeline.repository
+	head, err := repository.Head()
 	if err != nil {
 		if err == plumbing.ErrReferenceNotFound {
-			refs, errr := pipeline.repository.References()
+			refs, errr := repository.References()
 			if errr != nil {
 				return nil, errors.Wrap(errr, "unable to list the references")
 			}
-			var head *plumbing.Reference
 			refs.ForEach(func(ref *plumbing.Reference) error {
 				if strings.HasPrefix(ref.Name().String(), "refs/heads/HEAD/") {
 					head = ref
@@ -380,16 +383,35 @@ func (pipeline *Pipeline) Commits() ([]*object.Commit, error) {
 				}
 				return nil
 			})
-			if head != nil {
-				cit, err = pipeline.repository.Log(&git.LogOptions{From: head.Hash()})
-			}
 		}
-		if err != nil {
+		if head == nil && err != nil {
 			return nil, errors.Wrap(err, "unable to collect the commit history")
 		}
 	}
+
+	if firstParent {
+		commit, err := repository.CommitObject(head.Hash())
+		if err != nil {
+			panic(err)
+		}
+		// the first parent matches the head
+		for ; err != io.EOF; commit, err = commit.Parents().Next() {
+			if err != nil {
+				panic(err)
+			}
+			result = append(result, commit)
+		}
+		// reverse the order
+		for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
+			result[i], result[j] = result[j], result[i]
+		}
+		return result, nil
+	}
+	cit, err := repository.Log(&git.LogOptions{From: head.Hash()})
+	if err != nil {
+		return nil, errors.Wrap(err, "unable to collect the commit history")
+	}
 	defer cit.Close()
-	var result []*object.Commit
 	cit.ForEach(func(commit *object.Commit) error {
 		result = append(result, commit)
 		return nil
@@ -545,7 +567,7 @@ func (pipeline *Pipeline) Initialize(facts map[string]interface{}) {
 	}
 	if _, exists := facts[ConfigPipelineCommits]; !exists {
 		var err error
-		facts[ConfigPipelineCommits], err = pipeline.Commits()
+		facts[ConfigPipelineCommits], err = pipeline.Commits(false)
 		if err != nil {
 			log.Panicf("failed to list the commits: %v", err)
 		}

+ 20 - 2
internal/core/pipeline_test.go

@@ -285,9 +285,9 @@ func TestPipelineOnProgress(t *testing.T) {
 	assert.Equal(t, 4, progressOk)
 }
 
-func TestPipelineCommits(t *testing.T) {
+func TestPipelineCommitsFull(t *testing.T) {
 	pipeline := NewPipeline(test.Repository)
-	commits, err := pipeline.Commits()
+	commits, err := pipeline.Commits(false)
 	assert.Nil(t, err)
 	assert.True(t, len(commits) >= 100)
 	hashMap := map[plumbing.Hash]bool{}
@@ -297,6 +297,24 @@ func TestPipelineCommits(t *testing.T) {
 	assert.Equal(t, len(commits), len(hashMap))
 	assert.Contains(t, hashMap, plumbing.NewHash(
 		"cce947b98a050c6d356bc6ba95030254914027b1"))
+	assert.Contains(t, hashMap, plumbing.NewHash(
+		"a3ee37f91f0d705ec9c41ae88426f0ae44b2fbc3"))
+}
+
+func TestPipelineCommitsFirstParent(t *testing.T) {
+	pipeline := NewPipeline(test.Repository)
+	commits, err := pipeline.Commits(true)
+	assert.Nil(t, err)
+	assert.True(t, len(commits) >= 100)
+	hashMap := map[plumbing.Hash]bool{}
+	for _, c := range commits {
+		hashMap[c.Hash] = true
+	}
+	assert.Equal(t, len(commits), len(hashMap))
+	assert.Contains(t, hashMap, plumbing.NewHash(
+		"cce947b98a050c6d356bc6ba95030254914027b1"))
+	assert.NotContains(t, hashMap, plumbing.NewHash(
+		"a3ee37f91f0d705ec9c41ae88426f0ae44b2fbc3"))
 }
 
 func TestLoadCommitsFromFile(t *testing.T) {