Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/api/builds.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ type RunBuildOptions struct {
AgentID int
Tags []string
PersonalChangeID string
Revision string // Base revision (commit SHA) for personal builds
Revision string // Base revision (commit SHA or changelist number) for personal builds
}

// RunBuild runs a new build with full options
Expand Down Expand Up @@ -184,7 +184,7 @@ func (c *Client) RunBuild(buildTypeID string, opts RunBuildOptions) (*Build, err

if opts.Revision != "" {
vcsBranch := opts.Branch
if vcsBranch != "" && !strings.HasPrefix(vcsBranch, "refs/") {
if vcsBranch != "" && !strings.HasPrefix(vcsBranch, "refs/") && !strings.HasPrefix(vcsBranch, "//") {
vcsBranch = "refs/heads/" + vcsBranch
}
req.Revisions = &Revisions{
Expand Down
26 changes: 26 additions & 0 deletions internal/api/perforce_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build integration

package api_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestPerforceUploadDiffChanges(T *testing.T) {
T.Parallel()

patch := []byte(`--- a/depot/main/test.txt
+++ b/depot/main/test.txt
@@ -1 +1 @@
-Hello from Perforce
+Hello from Perforce - modified in personal build
`)

changeID, err := client.UploadDiffChanges(patch, "Perforce personal build test")
require.NoError(T, err)
assert.NotEmpty(T, changeID)
T.Logf("Uploaded Perforce diff as change ID: %s", changeID)
}
4 changes: 2 additions & 2 deletions internal/cmd/git_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ func TestLoadLocalChanges(t *testing.T) {

_, err := loadLocalChanges("git")
assert.Error(t, err)
assert.Contains(t, err.Error(), "no uncommitted changes")
assert.Contains(t, err.Error(), "no local changes found")
})

t.Run("git source not in repo", func(t *testing.T) {
Expand All @@ -370,7 +370,7 @@ func TestLoadLocalChanges(t *testing.T) {

_, err := loadLocalChanges("git")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not a git repository")
assert.Contains(t, err.Error(), "no supported VCS detected")
})

t.Run("file source", func(t *testing.T) {
Expand Down
30 changes: 12 additions & 18 deletions internal/cmd/run_analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,19 @@ func runRunChanges(runID string, opts *runChangesOptions) error {

fmt.Printf("CHANGES (%d %s)\n\n", changes.Count, english.PluralWord(changes.Count, "commit", "commits"))

var firstSHA, lastSHA string
vcs := DetectVCS()
if vcs == nil {
vcs = &GitProvider{}
}

var firstRev, lastRev string
for i, c := range changes.Change {
if i == 0 {
lastSHA = c.Version
lastRev = c.Version
}
firstSHA = c.Version
firstRev = c.Version

sha := c.Version
if len(sha) > 7 {
sha = sha[:7]
}
rev := vcs.FormatRevision(c.Version)

date := ""
if c.Date != "" {
Expand All @@ -79,7 +81,7 @@ func runRunChanges(runID string, opts *runChangesOptions) error {
}
}

fmt.Printf("%s %s %s\n", output.Yellow(sha), output.Faint(c.Username), output.Faint(date))
fmt.Printf("%s %s %s\n", output.Yellow(rev), output.Faint(c.Username), output.Faint(date))

comment := strings.TrimSpace(c.Comment)
if idx := strings.Index(comment, "\n"); idx > 0 {
Expand All @@ -104,16 +106,8 @@ func runRunChanges(runID string, opts *runChangesOptions) error {
fmt.Println()
}

if firstSHA != "" && lastSHA != "" && firstSHA != lastSHA {
first := firstSHA
last := lastSHA
if len(first) > 7 {
first = first[:7]
}
if len(last) > 7 {
last = last[:7]
}
fmt.Printf("%s git diff %s^..%s\n", output.Faint("# For full diff:"), first, last)
if firstRev != "" && lastRev != "" && firstRev != lastRev {
fmt.Printf("%s %s\n", output.Faint("# For full diff:"), vcs.DiffHint(firstRev, lastRev))
}

return nil
Expand Down
119 changes: 67 additions & 52 deletions internal/cmd/run_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func newRunStartCmd() *cobra.Command {
cmd.Flags().StringVarP(&opts.comment, "comment", "m", "", "Run comment")
cmd.Flags().StringSliceVarP(&opts.tags, "tag", "t", nil, "Run tags (can be repeated)")
cmd.Flags().BoolVar(&opts.personal, "personal", false, "Run as personal build")
localChangesFlag := cmd.Flags().VarPF(&localChangesValue{val: &opts.localChanges}, "local-changes", "l", "Include local changes (git, -, or path; default: git)")
localChangesFlag := cmd.Flags().VarPF(&localChangesValue{val: &opts.localChanges}, "local-changes", "l", "Include local changes (git, p4, auto, -, or path; default: git)")
localChangesFlag.NoOptDefVal = "git"
cmd.Flags().BoolVar(&opts.noPush, "no-push", false, "Skip auto-push of branch to remote")
cmd.Flags().BoolVar(&opts.cleanSources, "clean", false, "Clean sources before run")
Expand Down Expand Up @@ -138,68 +138,71 @@ func runRunStart(jobID string, opts *runStartOptions) error {
}

var headCommit string
if opts.localChanges != "" && opts.branch == "" {
if !isGitRepo() {
return tcerrors.WithSuggestion(
"not a git repository",
"Run this command from within a git repository, or specify --branch explicitly",
)
}
branch, err := getCurrentBranch()
if err != nil {
return err
var personalChangeID string
var localPatch []byte

if opts.localChanges != "" {
vcs := DetectVCS()

if opts.branch == "" {
if vcs == nil {
return tcerrors.WithSuggestion(
"no supported VCS detected",
"Run this command from within a git repository or Perforce workspace, or specify --branch explicitly",
)
}
branch, err := vcs.GetCurrentBranch()
if err != nil {
return err
}
opts.branch = branch
output.Info("Using current %s branch: %s", vcs.Name(), branch)
}
opts.branch = branch
output.Info("Using current branch: %s", branch)
}

if opts.localChanges != "" && !opts.noPush {
if !branchExistsOnRemote(opts.branch) {
if !opts.noPush && vcs != nil && !vcs.BranchExistsOnRemote(opts.branch) {
output.Info("Pushing branch to remote...")
if err := pushBranch(opts.branch); err != nil {
if err := vcs.PushBranch(opts.branch); err != nil {
return err
}
output.Success("Branch pushed to remote")
}
}

if opts.localChanges != "" {
commit, err := getHeadCommit()
if vcs != nil {
commit, err := vcs.GetHeadRevision()
if err != nil {
return err
}
headCommit = commit
}

patch, err := loadLocalChanges(opts.localChanges)
if err != nil {
return err
}
headCommit = commit
localPatch = patch
opts.personal = true
}

client, err := getClient()
if err != nil {
return err
}

var personalChangeID string
if opts.localChanges != "" {
patch, err := loadLocalChanges(opts.localChanges)
if err != nil {
return err
}

if localPatch != nil {
output.Info("Uploading local changes...")
description := opts.comment
if description == "" {
description = "Personal build with local changes"
}

changeID, err := client.UploadDiffChanges(patch, description)
changeID, err := client.UploadDiffChanges(localPatch, description)
if err != nil {
return fmt.Errorf("failed to upload changes: %w", err)
}
personalChangeID = changeID
output.Success("Uploaded changes (ID: %s)", changeID)

opts.personal = true
}

build, err := client.RunBuild(jobID, api.RunBuildOptions{
buildOpts := api.RunBuildOptions{
Branch: opts.branch,
Params: opts.params,
SystemProps: opts.systemProps,
Expand All @@ -214,7 +217,9 @@ func runRunStart(jobID string, opts *runStartOptions) error {
Tags: opts.tags,
PersonalChangeID: personalChangeID,
Revision: headCommit,
})
}

build, err := client.RunBuild(jobID, buildOpts)
if err != nil {
return err
}
Expand Down Expand Up @@ -518,24 +523,6 @@ func (v *localChangesValue) Type() string {

func loadLocalChanges(source string) ([]byte, error) {
switch source {
case "git":
if !isGitRepo() {
return nil, tcerrors.WithSuggestion(
"not a git repository",
"Run this command from within a git repository, or use --local-changes <path> to specify a diff file",
)
}
patch, err := getGitDiff()
if err != nil {
return nil, err
}
if len(patch) == 0 {
return nil, tcerrors.WithSuggestion(
"no uncommitted changes found",
"Make some changes to your files before running a personal build, or use --local-changes <path> to specify a diff file",
)
}
return patch, nil
case "-":
patch, err := io.ReadAll(os.Stdin)
if err != nil {
Expand All @@ -548,6 +535,8 @@ func loadLocalChanges(source string) ([]byte, error) {
)
}
return patch, nil
case "git", "p4", "perforce", "auto":
return loadVCSDiff(source)
default:
patch, err := os.ReadFile(source)
if err != nil {
Expand All @@ -569,6 +558,32 @@ func loadLocalChanges(source string) ([]byte, error) {
}
}

func loadVCSDiff(source string) ([]byte, error) {
var vcs VCSProvider
if source == "auto" {
vcs = DetectVCS()
} else if p := DetectVCSByName(source); p != nil && p.IsAvailable() {
vcs = p
}
if vcs == nil {
return nil, tcerrors.WithSuggestion(
"no supported VCS detected",
"Run this command from within a git repository or Perforce workspace, or use --local-changes <path>",
)
}
patch, err := vcs.GetLocalDiff()
if err != nil {
return nil, err
}
if len(patch) == 0 {
return nil, tcerrors.WithSuggestion(
"no local changes found",
"Make some changes before running a personal build, or use --local-changes <path>",
)
}
return patch, nil
}

func getGitDiff() ([]byte, error) {
untrackedFiles, err := getUntrackedFiles()
if err != nil {
Expand Down
31 changes: 31 additions & 0 deletions internal/cmd/vcs_git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmd

import "fmt"

type GitProvider struct{}

func (g *GitProvider) Name() string { return "git" }
func (g *GitProvider) IsAvailable() bool { return isGitRepo() }
func (g *GitProvider) GetCurrentBranch() (string, error) { return getCurrentBranch() }
func (g *GitProvider) GetHeadRevision() (string, error) { return getHeadCommit() }
func (g *GitProvider) GetLocalDiff() ([]byte, error) { return getGitDiff() }
func (g *GitProvider) BranchExistsOnRemote(b string) bool { return branchExistsOnRemote(b) }
func (g *GitProvider) PushBranch(b string) error { return pushBranch(b) }

func (g *GitProvider) FormatRevision(rev string) string {
if len(rev) > 7 {
return rev[:7]
}
return rev
}

func (g *GitProvider) DiffHint(firstRev, lastRev string) string {
first, last := firstRev, lastRev
if len(first) > 7 {
first = first[:7]
}
if len(last) > 7 {
last = last[:7]
}
return fmt.Sprintf("git diff %s^..%s", first, last)
}
Loading