Merge pull request #4942 from cli/repo-sync-merge-upstream
repo sync: Use the new merge-upstream API if available
This commit is contained in:
commit
eaa64df801
3 changed files with 128 additions and 68 deletions
|
|
@ -3,7 +3,9 @@ package sync
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/cli/cli/v2/api"
|
"github.com/cli/cli/v2/api"
|
||||||
"github.com/cli/cli/v2/internal/ghrepo"
|
"github.com/cli/cli/v2/internal/ghrepo"
|
||||||
|
|
@ -27,6 +29,39 @@ func latestCommit(client *api.Client, repo ghrepo.Interface, branch string) (com
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type upstreamMergeErr struct{ error }
|
||||||
|
|
||||||
|
var upstreamMergeUnavailableErr = upstreamMergeErr{errors.New("upstream merge API is unavailable")}
|
||||||
|
|
||||||
|
func triggerUpstreamMerge(client *api.Client, repo ghrepo.Interface, branch string) (string, error) {
|
||||||
|
var payload bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&payload).Encode(map[string]interface{}{
|
||||||
|
"branch": branch,
|
||||||
|
}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
MergeType string `json:"merge_type"`
|
||||||
|
BaseBranch string `json:"base_branch"`
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("repos/%s/%s/merge-upstream", repo.RepoOwner(), repo.RepoName())
|
||||||
|
var httpErr api.HTTPError
|
||||||
|
if err := client.REST(repo.RepoHost(), "POST", path, &payload, &response); err != nil {
|
||||||
|
if errors.As(err, &httpErr) {
|
||||||
|
switch httpErr.StatusCode {
|
||||||
|
case http.StatusUnprocessableEntity, http.StatusConflict:
|
||||||
|
return "", upstreamMergeErr{errors.New(httpErr.Message)}
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return "", upstreamMergeUnavailableErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return response.BaseBranch, nil
|
||||||
|
}
|
||||||
|
|
||||||
func syncFork(client *api.Client, repo ghrepo.Interface, branch, SHA string, force bool) error {
|
func syncFork(client *api.Client, repo ghrepo.Interface, branch, SHA string, force bool) error {
|
||||||
path := fmt.Sprintf("repos/%s/%s/git/refs/heads/%s", repo.RepoOwner(), repo.RepoName(), branch)
|
path := fmt.Sprintf("repos/%s/%s/git/refs/heads/%s", repo.RepoOwner(), repo.RepoName(), branch)
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/MakeNowJust/heredoc"
|
"github.com/MakeNowJust/heredoc"
|
||||||
"github.com/cli/cli/v2/api"
|
"github.com/cli/cli/v2/api"
|
||||||
|
|
@ -177,38 +178,19 @@ func syncRemoteRepo(opts *SyncOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.SrcArg == "" {
|
if opts.SrcArg != "" {
|
||||||
opts.IO.StartProgressIndicator()
|
|
||||||
srcRepo, err = api.RepoParent(apiClient, destRepo)
|
|
||||||
opts.IO.StopProgressIndicator()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if srcRepo == nil {
|
|
||||||
return fmt.Errorf("can't determine source repository for %s because repository is not fork", ghrepo.FullName(destRepo))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
srcRepo, err = ghrepo.FromFullName(opts.SrcArg)
|
srcRepo, err = ghrepo.FromFullName(opts.SrcArg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if destRepo.RepoHost() != srcRepo.RepoHost() {
|
if srcRepo != nil && destRepo.RepoHost() != srcRepo.RepoHost() {
|
||||||
return fmt.Errorf("can't sync repositories from different hosts")
|
return fmt.Errorf("can't sync repositories from different hosts")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Branch == "" {
|
|
||||||
opts.IO.StartProgressIndicator()
|
|
||||||
opts.Branch, err = api.RepoDefaultBranch(apiClient, srcRepo)
|
|
||||||
opts.IO.StopProgressIndicator()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.IO.StartProgressIndicator()
|
opts.IO.StartProgressIndicator()
|
||||||
err = executeRemoteRepoSync(apiClient, destRepo, srcRepo, opts)
|
baseBranchLabel, err := executeRemoteRepoSync(apiClient, destRepo, srcRepo, opts)
|
||||||
opts.IO.StopProgressIndicator()
|
opts.IO.StopProgressIndicator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, divergingError) {
|
if errors.Is(err, divergingError) {
|
||||||
|
|
@ -219,11 +201,15 @@ func syncRemoteRepo(opts *SyncOptions) error {
|
||||||
|
|
||||||
if opts.IO.IsStdoutTTY() {
|
if opts.IO.IsStdoutTTY() {
|
||||||
cs := opts.IO.ColorScheme()
|
cs := opts.IO.ColorScheme()
|
||||||
fmt.Fprintf(opts.IO.Out, "%s Synced the \"%s\" branch from %s to %s\n",
|
branchName := opts.Branch
|
||||||
|
if idx := strings.Index(baseBranchLabel, ":"); idx >= 0 {
|
||||||
|
branchName = baseBranchLabel[idx+1:]
|
||||||
|
}
|
||||||
|
fmt.Fprintf(opts.IO.Out, "%s Synced the \"%s:%s\" branch from \"%s\"\n",
|
||||||
cs.SuccessIcon(),
|
cs.SuccessIcon(),
|
||||||
opts.Branch,
|
destRepo.RepoOwner(),
|
||||||
ghrepo.FullName(srcRepo),
|
branchName,
|
||||||
ghrepo.FullName(destRepo))
|
baseBranchLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -294,20 +280,47 @@ func executeLocalRepoSync(srcRepo ghrepo.Interface, remote string, opts *SyncOpt
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeRemoteRepoSync(client *api.Client, destRepo, srcRepo ghrepo.Interface, opts *SyncOptions) error {
|
func executeRemoteRepoSync(client *api.Client, destRepo, srcRepo ghrepo.Interface, opts *SyncOptions) (string, error) {
|
||||||
commit, err := latestCommit(client, srcRepo, opts.Branch)
|
branchName := opts.Branch
|
||||||
|
if branchName == "" {
|
||||||
|
var err error
|
||||||
|
branchName, err = api.RepoDefaultBranch(client, destRepo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiErr upstreamMergeErr
|
||||||
|
if baseBranch, err := triggerUpstreamMerge(client, destRepo, branchName); err == nil {
|
||||||
|
return baseBranch, nil
|
||||||
|
} else if !errors.As(err, &apiErr) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcRepo == nil {
|
||||||
|
var err error
|
||||||
|
srcRepo, err = api.RepoParent(client, destRepo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if srcRepo == nil {
|
||||||
|
return "", fmt.Errorf("can't determine source repository for %s because repository is not fork", ghrepo.FullName(destRepo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commit, err := latestCommit(client, srcRepo, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is not a great way to detect the error returned by the API
|
// This is not a great way to detect the error returned by the API
|
||||||
// Unfortunately API returns 422 for multiple reasons
|
// Unfortunately API returns 422 for multiple reasons
|
||||||
notFastForwardErrorMessage := regexp.MustCompile(`^Update is not a fast forward$`)
|
notFastForwardErrorMessage := regexp.MustCompile(`^Update is not a fast forward$`)
|
||||||
err = syncFork(client, destRepo, opts.Branch, commit.Object.SHA, opts.Force)
|
err = syncFork(client, destRepo, branchName, commit.Object.SHA, opts.Force)
|
||||||
var httpErr api.HTTPError
|
var httpErr api.HTTPError
|
||||||
if err != nil && errors.As(err, &httpErr) && notFastForwardErrorMessage.MatchString(httpErr.Message) {
|
if err != nil && errors.As(err, &httpErr) && notFastForwardErrorMessage.MatchString(httpErr.Message) {
|
||||||
return divergingError
|
return "", divergingError
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return fmt.Sprintf("%s:%s", srcRepo.RepoOwner(), branchName), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -262,10 +262,26 @@ func Test_SyncRun(t *testing.T) {
|
||||||
wantStdout: "✓ Synced the \"trunk\" branch from OWNER/REPO to local repository\n",
|
wantStdout: "✓ Synced the \"trunk\" branch from OWNER/REPO to local repository\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sync remote fork with parent - tty",
|
name: "sync remote fork with parent with new api - tty",
|
||||||
tty: true,
|
tty: true,
|
||||||
opts: &SyncOptions{
|
opts: &SyncOptions{
|
||||||
DestArg: "OWNER/REPO-FORK",
|
DestArg: "FORKOWNER/REPO-FORK",
|
||||||
|
},
|
||||||
|
httpStubs: func(reg *httpmock.Registry) {
|
||||||
|
reg.Register(
|
||||||
|
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||||
|
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
||||||
|
reg.Register(
|
||||||
|
httpmock.REST("POST", "repos/FORKOWNER/REPO-FORK/merge-upstream"),
|
||||||
|
httpmock.StatusStringResponse(200, `{"base_branch": "OWNER:trunk"}`))
|
||||||
|
},
|
||||||
|
wantStdout: "✓ Synced the \"FORKOWNER:trunk\" branch from \"OWNER:trunk\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sync remote fork with parent using api fallback - tty",
|
||||||
|
tty: true,
|
||||||
|
opts: &SyncOptions{
|
||||||
|
DestArg: "FORKOWNER/REPO-FORK",
|
||||||
},
|
},
|
||||||
httpStubs: func(reg *httpmock.Registry) {
|
httpStubs: func(reg *httpmock.Registry) {
|
||||||
reg.Register(
|
reg.Register(
|
||||||
|
|
@ -274,34 +290,31 @@ func Test_SyncRun(t *testing.T) {
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||||
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
||||||
|
reg.Register(
|
||||||
|
httpmock.REST("POST", "repos/FORKOWNER/REPO-FORK/merge-upstream"),
|
||||||
|
httpmock.StatusStringResponse(404, `{}`))
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
||||||
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.REST("PATCH", "repos/OWNER/REPO-FORK/git/refs/heads/trunk"),
|
httpmock.REST("PATCH", "repos/FORKOWNER/REPO-FORK/git/refs/heads/trunk"),
|
||||||
httpmock.StringResponse(`{}`))
|
httpmock.StringResponse(`{}`))
|
||||||
},
|
},
|
||||||
wantStdout: "✓ Synced the \"trunk\" branch from OWNER/REPO to OWNER/REPO-FORK\n",
|
wantStdout: "✓ Synced the \"FORKOWNER:trunk\" branch from \"OWNER:trunk\"\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sync remote fork with parent - notty",
|
name: "sync remote fork with parent - notty",
|
||||||
tty: false,
|
tty: false,
|
||||||
opts: &SyncOptions{
|
opts: &SyncOptions{
|
||||||
DestArg: "OWNER/REPO-FORK",
|
DestArg: "FORKOWNER/REPO-FORK",
|
||||||
},
|
},
|
||||||
httpStubs: func(reg *httpmock.Registry) {
|
httpStubs: func(reg *httpmock.Registry) {
|
||||||
reg.Register(
|
|
||||||
httpmock.GraphQL(`query RepositoryFindParent\b`),
|
|
||||||
httpmock.StringResponse(`{"data":{"repository":{"parent":{"name":"REPO","owner":{"login": "OWNER"}}}}}`))
|
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||||
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
httpmock.REST("POST", "repos/FORKOWNER/REPO-FORK/merge-upstream"),
|
||||||
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
httpmock.StatusStringResponse(200, `{"base_branch": "OWNER:trunk"}`))
|
||||||
reg.Register(
|
|
||||||
httpmock.REST("PATCH", "repos/OWNER/REPO-FORK/git/refs/heads/trunk"),
|
|
||||||
httpmock.StringResponse(`{}`))
|
|
||||||
},
|
},
|
||||||
wantStdout: "",
|
wantStdout: "",
|
||||||
},
|
},
|
||||||
|
|
@ -310,11 +323,15 @@ func Test_SyncRun(t *testing.T) {
|
||||||
tty: true,
|
tty: true,
|
||||||
opts: &SyncOptions{
|
opts: &SyncOptions{
|
||||||
DestArg: "OWNER/REPO",
|
DestArg: "OWNER/REPO",
|
||||||
|
Branch: "trunk",
|
||||||
},
|
},
|
||||||
httpStubs: func(reg *httpmock.Registry) {
|
httpStubs: func(reg *httpmock.Registry) {
|
||||||
|
reg.Register(
|
||||||
|
httpmock.REST("POST", "repos/OWNER/REPO/merge-upstream"),
|
||||||
|
httpmock.StatusStringResponse(422, `{"message": "Validation Failed"}`))
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.GraphQL(`query RepositoryFindParent\b`),
|
httpmock.GraphQL(`query RepositoryFindParent\b`),
|
||||||
httpmock.StringResponse(`{"data":{"repository":{}}}`))
|
httpmock.StringResponse(`{"data":{"repository":{"parent":null}}}`))
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errMsg: "can't determine source repository for OWNER/REPO because repository is not fork",
|
errMsg: "can't determine source repository for OWNER/REPO because repository is not fork",
|
||||||
|
|
@ -325,19 +342,14 @@ func Test_SyncRun(t *testing.T) {
|
||||||
opts: &SyncOptions{
|
opts: &SyncOptions{
|
||||||
DestArg: "OWNER/REPO",
|
DestArg: "OWNER/REPO",
|
||||||
SrcArg: "OWNER2/REPO2",
|
SrcArg: "OWNER2/REPO2",
|
||||||
|
Branch: "trunk",
|
||||||
},
|
},
|
||||||
httpStubs: func(reg *httpmock.Registry) {
|
httpStubs: func(reg *httpmock.Registry) {
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
httpmock.REST("POST", "repos/OWNER/REPO/merge-upstream"),
|
||||||
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
httpmock.StatusStringResponse(200, `{"base_branch": "OWNER2:trunk"}`))
|
||||||
reg.Register(
|
|
||||||
httpmock.REST("GET", "repos/OWNER2/REPO2/git/refs/heads/trunk"),
|
|
||||||
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
|
||||||
reg.Register(
|
|
||||||
httpmock.REST("PATCH", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
|
||||||
httpmock.StringResponse(`{}`))
|
|
||||||
},
|
},
|
||||||
wantStdout: "✓ Synced the \"trunk\" branch from OWNER2/REPO2 to OWNER/REPO\n",
|
wantStdout: "✓ Synced the \"OWNER:trunk\" branch from \"OWNER2:trunk\"\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sync remote fork with parent and specified branch",
|
name: "sync remote fork with parent and specified branch",
|
||||||
|
|
@ -348,16 +360,10 @@ func Test_SyncRun(t *testing.T) {
|
||||||
},
|
},
|
||||||
httpStubs: func(reg *httpmock.Registry) {
|
httpStubs: func(reg *httpmock.Registry) {
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.GraphQL(`query RepositoryFindParent\b`),
|
httpmock.REST("POST", "repos/OWNER/REPO-FORK/merge-upstream"),
|
||||||
httpmock.StringResponse(`{"data":{"repository":{"parent":{"name":"REPO","owner":{"login": "OWNER"}}}}}`))
|
httpmock.StatusStringResponse(200, `{"base_branch": "OWNER:test"}`))
|
||||||
reg.Register(
|
|
||||||
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/test"),
|
|
||||||
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
|
||||||
reg.Register(
|
|
||||||
httpmock.REST("PATCH", "repos/OWNER/REPO-FORK/git/refs/heads/test"),
|
|
||||||
httpmock.StringResponse(`{}`))
|
|
||||||
},
|
},
|
||||||
wantStdout: "✓ Synced the \"test\" branch from OWNER/REPO to OWNER/REPO-FORK\n",
|
wantStdout: "✓ Synced the \"OWNER:test\" branch from \"OWNER:test\"\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sync remote fork with parent and force specified",
|
name: "sync remote fork with parent and force specified",
|
||||||
|
|
@ -373,6 +379,9 @@ func Test_SyncRun(t *testing.T) {
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||||
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
||||||
|
reg.Register(
|
||||||
|
httpmock.REST("POST", "repos/OWNER/REPO-FORK/merge-upstream"),
|
||||||
|
httpmock.StatusStringResponse(409, `{"message": "Merge conflict"}`))
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
||||||
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
||||||
|
|
@ -380,7 +389,7 @@ func Test_SyncRun(t *testing.T) {
|
||||||
httpmock.REST("PATCH", "repos/OWNER/REPO-FORK/git/refs/heads/trunk"),
|
httpmock.REST("PATCH", "repos/OWNER/REPO-FORK/git/refs/heads/trunk"),
|
||||||
httpmock.StringResponse(`{}`))
|
httpmock.StringResponse(`{}`))
|
||||||
},
|
},
|
||||||
wantStdout: "✓ Synced the \"trunk\" branch from OWNER/REPO to OWNER/REPO-FORK\n",
|
wantStdout: "✓ Synced the \"OWNER:trunk\" branch from \"OWNER:trunk\"\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sync remote fork with parent and not fast forward merge",
|
name: "sync remote fork with parent and not fast forward merge",
|
||||||
|
|
@ -395,6 +404,9 @@ func Test_SyncRun(t *testing.T) {
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||||
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
|
||||||
|
reg.Register(
|
||||||
|
httpmock.REST("POST", "repos/OWNER/REPO-FORK/merge-upstream"),
|
||||||
|
httpmock.StatusStringResponse(409, `{"message": "Merge conflict"}`))
|
||||||
reg.Register(
|
reg.Register(
|
||||||
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
httpmock.REST("GET", "repos/OWNER/REPO/git/refs/heads/trunk"),
|
||||||
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
httpmock.StringResponse(`{"object":{"sha":"0xDEADBEEF"}}`))
|
||||||
|
|
@ -454,11 +466,11 @@ func Test_SyncRun(t *testing.T) {
|
||||||
defer reg.Verify(t)
|
defer reg.Verify(t)
|
||||||
err := syncRun(tt.opts)
|
err := syncRun(tt.opts)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.EqualError(t, err, tt.errMsg)
|
||||||
assert.Equal(t, tt.errMsg, err.Error())
|
|
||||||
return
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("syncRun() unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue