Skip to content

Commit 8bcee9a

Browse files
committed
pipeline: add Pipeline.Branch to define where to find CommitsHead
Signed-off-by: Robert Lin <[email protected]>
1 parent 13a2081 commit 8bcee9a

File tree

4 files changed

+143
-32
lines changed

4 files changed

+143
-32
lines changed

cmd/hercules/root.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
"github.com/spf13/pflag"
2525
"golang.org/x/crypto/ssh/terminal"
2626
progress "gopkg.in/cheggaaa/pb.v1"
27-
"gopkg.in/src-d/go-billy-siva.v4"
27+
sivafs "gopkg.in/src-d/go-billy-siva.v4"
2828
"gopkg.in/src-d/go-billy.v4/memfs"
2929
"gopkg.in/src-d/go-billy.v4/osfs"
3030
"gopkg.in/src-d/go-git.v4"
@@ -190,6 +190,7 @@ targets can be added using the --plugin system.`,
190190
}
191191
firstParent := getBool("first-parent")
192192
commitsFile := getString("commits")
193+
branch := getString("branch")
193194
head := getBool("head")
194195
protobuf := getBool("pb")
195196
profile := getBool("profile")
@@ -219,6 +220,7 @@ targets can be added using the --plugin system.`,
219220

220221
// core logic
221222
pipeline := hercules.NewPipeline(repository)
223+
pipeline.Branch = branch
222224
pipeline.SetFeaturesFromFlags()
223225
var bar *progress.ProgressBar
224226
if !disableStatus {
@@ -484,28 +486,33 @@ var cmdlineDeployed map[string]*bool
484486
func init() {
485487
loadPlugins()
486488
rootFlags := rootCmd.Flags()
489+
490+
// commits flags
487491
rootFlags.String("commits", "", "Path to the text file with the "+
488492
"commit history to follow instead of the default 'git log'. "+
489493
"The format is the list of hashes, each hash on a "+
490494
"separate line. The first hash is the root.")
491-
err := rootCmd.MarkFlagFilename("commits")
492-
if err != nil {
495+
if err := rootCmd.MarkFlagFilename("commits"); err != nil {
493496
panic(err)
494497
}
495498
hercules.PathifyFlagValue(rootFlags.Lookup("commits"))
499+
rootFlags.String("branch", "", "Specify a branch to analyze.")
496500
rootFlags.Bool("head", false, "Analyze only the latest commit.")
497501
rootFlags.Bool("first-parent", false, "Follow only the first parent in the commit history - "+
498502
"\"git log --first-parent\".")
503+
504+
// output flags
499505
rootFlags.Bool("pb", false, "The output format will be Protocol Buffers instead of YAML.")
500506
rootFlags.Bool("quiet", !terminal.IsTerminal(int(os.Stdin.Fd())),
501507
"Do not print status updates to stderr.")
502508
rootFlags.Bool("profile", false, "Collect the profile to hercules.pprof.")
503509
rootFlags.String("ssh-identity", "", "Path to SSH identity file (e.g., ~/.ssh/id_rsa) to clone from an SSH remote.")
504-
err = rootCmd.MarkFlagFilename("ssh-identity")
505-
if err != nil {
510+
if err := rootCmd.MarkFlagFilename("ssh-identity"); err != nil {
506511
panic(err)
507512
}
508513
hercules.PathifyFlagValue(rootFlags.Lookup("ssh-identity"))
514+
515+
// register all flags
509516
cmdlineFacts, cmdlineDeployed = hercules.Registry.AddFlags(rootFlags)
510517
rootCmd.SetUsageFunc(formatUsage)
511518
rootCmd.AddCommand(versionCmd)

internal/core/pipeline.go

+48-20
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ type Pipeline struct {
272272
// PrintActions indicates whether to print the taken actions during the execution.
273273
PrintActions bool
274274

275+
// Branch used for pipeline.HeadCommit. Leave blank to use HEAD.
276+
Branch string
277+
275278
// Repository points to the analysed Git repository struct from go-git.
276279
repository *git.Repository
277280

@@ -484,34 +487,59 @@ func (pipeline *Pipeline) Commits(firstParent bool) ([]*object.Commit, error) {
484487
// HeadCommit returns the latest commit in the repository (HEAD).
485488
func (pipeline *Pipeline) HeadCommit() ([]*object.Commit, error) {
486489
repository := pipeline.repository
487-
head, err := repository.Head()
488-
if err == plumbing.ErrReferenceNotFound {
489-
refs, errr := repository.References()
490-
if errr != nil {
491-
return nil, errors.Wrap(errr, "unable to list the references")
492-
}
493-
var refnames []string
494-
refByName := map[string]*plumbing.Reference{}
495-
err = refs.ForEach(func(ref *plumbing.Reference) error {
496-
refname := ref.Name().String()
497-
refnames = append(refnames, refname)
498-
refByName[refname] = ref
499-
if strings.HasPrefix(refname, "refs/heads/HEAD/") {
490+
491+
var head *plumbing.Reference
492+
if pipeline.Branch != "" {
493+
pipeline.l.Infof("querying for head of branch %s", pipeline.Branch)
494+
branch := plumbing.NewBranchReferenceName(pipeline.Branch)
495+
iter, err := repository.Branches()
496+
if err != nil {
497+
return nil, errors.Wrap(err, "unable to list branches")
498+
}
499+
if err := iter.ForEach(func(ref *plumbing.Reference) error {
500+
pipeline.l.Info(ref.Name())
501+
if ref.Name() == branch {
500502
head = ref
501503
return storer.ErrStop
502504
}
503505
return nil
504-
})
505-
if head == nil {
506-
sort.Strings(refnames)
507-
headName := refnames[len(refnames)-1]
508-
pipeline.l.Warnf("could not determine the HEAD, falling back to %s", headName)
509-
head = refByName[headName]
506+
}); err != nil {
507+
return nil, errors.Wrap(err, "unable to find branch head")
508+
}
509+
} else {
510+
var err error
511+
head, err = repository.Head()
512+
if err == plumbing.ErrReferenceNotFound {
513+
refs, errr := repository.References()
514+
if errr != nil {
515+
return nil, errors.Wrap(errr, "unable to list the references")
516+
}
517+
var refnames []string
518+
refByName := map[string]*plumbing.Reference{}
519+
err = refs.ForEach(func(ref *plumbing.Reference) error {
520+
refname := ref.Name().String()
521+
refnames = append(refnames, refname)
522+
refByName[refname] = ref
523+
if strings.HasPrefix(refname, "refs/heads/HEAD/") {
524+
head = ref
525+
return storer.ErrStop
526+
}
527+
return nil
528+
})
529+
if head == nil {
530+
sort.Strings(refnames)
531+
headName := refnames[len(refnames)-1]
532+
pipeline.l.Warnf("could not determine the HEAD, falling back to %s", headName)
533+
head = refByName[headName]
534+
}
535+
} else if err != nil {
536+
return nil, errors.Wrap(err, "unable to find the head reference")
510537
}
511538
}
512539
if head == nil {
513-
return nil, errors.Wrap(err, "unable to find the head reference")
540+
return nil, errors.New("unable to find the head reference")
514541
}
542+
515543
commit, err := repository.CommitObject(head.Hash())
516544
if err != nil {
517545
return nil, err

internal/core/pipeline_test.go

+24-7
Original file line numberDiff line numberDiff line change
@@ -442,13 +442,30 @@ func TestPipelineCommitsFirstParent(t *testing.T) {
442442
}
443443

444444
func TestPipelineHeadCommit(t *testing.T) {
445-
pipeline := NewPipeline(test.Repository)
446-
commits, err := pipeline.HeadCommit()
447-
assert.NoError(t, err)
448-
assert.Len(t, commits, 1)
449-
assert.True(t, len(commits[0].ParentHashes) > 0)
450-
head, _ := test.Repository.Head()
451-
assert.Equal(t, head.Hash(), commits[0].Hash)
445+
t.Run("default", func(t *testing.T) {
446+
pipeline := NewPipeline(test.Repository)
447+
commits, err := pipeline.HeadCommit()
448+
assert.NoError(t, err)
449+
assert.Len(t, commits, 1)
450+
assert.True(t, len(commits[0].ParentHashes) > 0)
451+
head, _ := test.Repository.Head()
452+
assert.Equal(t, head.Hash(), commits[0].Hash)
453+
})
454+
t.Run("with branch specified", func(t *testing.T) {
455+
testBranch := "test-branch"
456+
repo, out := test.NewInMemRepository(&test.InMemRepositoryOptions{
457+
CreateBranch: testBranch,
458+
})
459+
assert.False(t, out.CreatedBranchHash.IsZero())
460+
461+
pipeline := NewPipeline(repo)
462+
pipeline.Branch = testBranch
463+
commits, err := pipeline.HeadCommit()
464+
assert.NoError(t, err)
465+
assert.Len(t, commits, 1)
466+
assert.True(t, len(commits[0].ParentHashes) > 0)
467+
assert.Equal(t, out.CreatedBranchHash, commits[0].Hash)
468+
})
452469
}
453470

454471
func TestLoadCommitsFromFile(t *testing.T) {

internal/test/repository.go

+59
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package test
22

33
import (
4+
"fmt"
45
"io"
56
"io/ioutil"
67
"os"
78
"path"
9+
"time"
810

11+
"gopkg.in/src-d/go-billy.v4/memfs"
912
"gopkg.in/src-d/go-git.v4"
1013
"gopkg.in/src-d/go-git.v4/plumbing"
1114
"gopkg.in/src-d/go-git.v4/plumbing/object"
@@ -75,3 +78,59 @@ func init() {
7578
panic(err)
7679
}
7780
}
81+
82+
// InMemRepositoryOptions declares config for NewInMemRepository
83+
type InMemRepositoryOptions struct {
84+
CreateBranch string
85+
}
86+
87+
// InMemRepositoryOutput provides output from options provided in InMemRepositoryOptions
88+
type InMemRepositoryOutput struct {
89+
CreatedBranchHash plumbing.Hash
90+
}
91+
92+
// NewInMemRepository initializes a new in-memory repository
93+
func NewInMemRepository(opts *InMemRepositoryOptions) (*git.Repository, InMemRepositoryOutput) {
94+
var out InMemRepositoryOutput
95+
96+
repo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
97+
URL: "https://github.com/src-d/hercules",
98+
})
99+
if err != nil {
100+
panic(err)
101+
}
102+
103+
if opts != nil && opts.CreateBranch != "" {
104+
t, err := repo.Worktree()
105+
if err != nil {
106+
panic(err)
107+
}
108+
if _, err := t.Add("."); err != nil {
109+
panic(err)
110+
}
111+
if err := t.Checkout(&git.CheckoutOptions{
112+
Branch: plumbing.NewBranchReferenceName(opts.CreateBranch),
113+
Create: true,
114+
}); err != nil {
115+
panic(err)
116+
}
117+
out.CreatedBranchHash, err = t.Commit(
118+
fmt.Sprintf("test commit on %s", opts.CreateBranch),
119+
&git.CommitOptions{
120+
Author: &object.Signature{Name: "bobheadxi", Email: "[email protected]", When: time.Now()},
121+
},
122+
)
123+
if err != nil {
124+
panic(err)
125+
}
126+
127+
// check out master again
128+
if err := t.Checkout(&git.CheckoutOptions{
129+
Branch: plumbing.NewBranchReferenceName("master"),
130+
}); err != nil {
131+
panic(err)
132+
}
133+
}
134+
135+
return repo, out
136+
}

0 commit comments

Comments
 (0)