diff --git a/fakes/fake_git_hub.go b/fakes/fake_git_hub.go index aa2fb14..4d69aec 100644 --- a/fakes/fake_git_hub.go +++ b/fakes/fake_git_hub.go @@ -26,6 +26,15 @@ type FakeGitHub struct { result1 *github.RepositoryRelease result2 error } + UpdateReleaseStub func(release *github.RepositoryRelease) (*github.RepositoryRelease, error) + updateReleaseMutex sync.RWMutex + updateReleaseArgsForCall []struct { + release *github.RepositoryRelease + } + updateReleaseReturns struct { + result1 *github.RepositoryRelease + result2 error + } ListReleaseAssetsStub func(release *github.RepositoryRelease) ([]github.ReleaseAsset, error) listReleaseAssetsMutex sync.RWMutex listReleaseAssetsArgsForCall []struct { @@ -105,6 +114,39 @@ func (fake *FakeGitHub) CreateReleaseReturns(result1 *github.RepositoryRelease, }{result1, result2} } +func (fake *FakeGitHub) UpdateRelease(release *github.RepositoryRelease) (*github.RepositoryRelease, error) { + fake.updateReleaseMutex.Lock() + fake.updateReleaseArgsForCall = append(fake.updateReleaseArgsForCall, struct { + release *github.RepositoryRelease + }{release}) + fake.updateReleaseMutex.Unlock() + if fake.UpdateReleaseStub != nil { + return fake.UpdateReleaseStub(release) + } else { + return fake.updateReleaseReturns.result1, fake.updateReleaseReturns.result2 + } +} + +func (fake *FakeGitHub) UpdateReleaseCallCount() int { + fake.updateReleaseMutex.RLock() + defer fake.updateReleaseMutex.RUnlock() + return len(fake.updateReleaseArgsForCall) +} + +func (fake *FakeGitHub) UpdateReleaseArgsForCall(i int) *github.RepositoryRelease { + fake.updateReleaseMutex.RLock() + defer fake.updateReleaseMutex.RUnlock() + return fake.updateReleaseArgsForCall[i].release +} + +func (fake *FakeGitHub) UpdateReleaseReturns(result1 *github.RepositoryRelease, result2 error) { + fake.UpdateReleaseStub = nil + fake.updateReleaseReturns = struct { + result1 *github.RepositoryRelease + result2 error + }{result1, result2} +} + func (fake *FakeGitHub) ListReleaseAssets(release *github.RepositoryRelease) ([]github.ReleaseAsset, error) { fake.listReleaseAssetsMutex.Lock() fake.listReleaseAssetsArgsForCall = append(fake.listReleaseAssetsArgsForCall, struct { diff --git a/github.go b/github.go index 90dddd5..1dc725b 100644 --- a/github.go +++ b/github.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "os" "code.google.com/p/goauth2/oauth" @@ -13,6 +14,7 @@ import ( type GitHub interface { ListReleases() ([]github.RepositoryRelease, error) CreateRelease(release *github.RepositoryRelease) (*github.RepositoryRelease, error) + UpdateRelease(release *github.RepositoryRelease) (*github.RepositoryRelease, error) ListReleaseAssets(release *github.RepositoryRelease) ([]github.ReleaseAsset, error) UploadReleaseAsset(release *github.RepositoryRelease, name string, file *os.File) error @@ -59,6 +61,19 @@ func (g *GitHubClient) CreateRelease(release *github.RepositoryRelease) (*github return createdRelease, nil } +func (g *GitHubClient) UpdateRelease(release *github.RepositoryRelease) (*github.RepositoryRelease, error) { + if release.ID == nil { + return nil, errors.New("release did not have an ID: has it been saved yet?") + } + + updatedRelease, _, err := g.client.Repositories.EditRelease(g.user, g.repository, *release.ID, release) + if err != nil { + return &github.RepositoryRelease{}, err + } + + return updatedRelease, nil +} + func (g *GitHubClient) ListReleaseAssets(release *github.RepositoryRelease) ([]github.ReleaseAsset, error) { assets, _, err := g.client.Repositories.ListReleaseAssets(g.user, g.repository, *release.ID, nil) if err != nil { diff --git a/out_command.go b/out_command.go index eb19748..2e8f458 100644 --- a/out_command.go +++ b/out_command.go @@ -46,7 +46,28 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err Body: github.String(body), } - createdRelease, err := c.github.CreateRelease(release) + existingReleases, err := c.github.ListReleases() + if err != nil { + return OutResponse{}, err + } + + var existingRelease *github.RepositoryRelease + for _, e := range existingReleases { + if *e.TagName == tag { + existingRelease = &e + break + } + } + + if existingRelease != nil { + existingRelease.Name = github.String(name) + existingRelease.Body = github.String(body) + + release, err = c.github.UpdateRelease(existingRelease) + } else { + release, err = c.github.CreateRelease(release) + } + if err != nil { return OutResponse{}, err } @@ -64,7 +85,7 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err } name := filepath.Base(filePath) - err = c.github.UploadReleaseAsset(createdRelease, name, file) + err = c.github.UploadReleaseAsset(release, name, file) if err != nil { return OutResponse{}, err } @@ -77,7 +98,7 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err Version: Version{ Tag: tag, }, - Metadata: metadataFromRelease(createdRelease), + Metadata: metadataFromRelease(release), }, nil } diff --git a/out_command_test.go b/out_command_test.go index d1d3a38..be0e4ea 100644 --- a/out_command_test.go +++ b/out_command_test.go @@ -14,12 +14,19 @@ import ( "github.com/google/go-github/github" ) +func file(path, contents string) { + Ω(ioutil.WriteFile(path, []byte(contents), 0644)).Should(Succeed()) +} + var _ = Describe("Out Command", func() { var ( command *resource.OutCommand githubClient *fakes.FakeGitHub sourcesDir string + + request resource.OutRequest + response resource.OutResponse ) BeforeEach(func() { @@ -39,137 +46,142 @@ var _ = Describe("Out Command", func() { createdRel.Body = github.String("*markdown*") return &createdRel, nil } + + githubClient.UpdateReleaseStub = func(gh *github.RepositoryRelease) (*github.RepositoryRelease, error) { + return gh, nil + } + }) + + JustBeforeEach(func() { + var err error + response, err = command.Run(sourcesDir, request) + Ω(err).ShouldNot(HaveOccurred()) }) AfterEach(func() { Ω(os.RemoveAll(sourcesDir)).Should(Succeed()) }) - It("creates a release on GitHub", func() { - namePath := filepath.Join(sourcesDir, "name") - bodyPath := filepath.Join(sourcesDir, "body") - tagPath := filepath.Join(sourcesDir, "tag") + Context("when the release has already been created", func() { + BeforeEach(func() { + githubClient.ListReleasesReturns([]github.RepositoryRelease{ + {ID: github.Int(112), TagName: github.String("some-tag-name")}, + }, nil) - file(namePath, "v0.3.12") - file(bodyPath, "this is a great release") - file(tagPath, "0.3.12") + namePath := filepath.Join(sourcesDir, "name") + bodyPath := filepath.Join(sourcesDir, "body") + tagPath := filepath.Join(sourcesDir, "tag") - request := resource.OutRequest{ - Params: resource.OutParams{ - NamePath: "name", - BodyPath: "body", - TagPath: "tag", - }, - } + file(namePath, "v0.3.12") + file(bodyPath, "this is a great release") + file(tagPath, "some-tag-name") - _, err := command.Run(sourcesDir, request) - Ω(err).ShouldNot(HaveOccurred()) - - Ω(githubClient.CreateReleaseCallCount()).Should(Equal(1)) - release := githubClient.CreateReleaseArgsForCall(0) - - Ω(*release.Name).Should(Equal("v0.3.12")) - Ω(*release.TagName).Should(Equal("0.3.12")) - Ω(*release.Body).Should(Equal("this is a great release")) - }) - - It("works without a body", func() { - namePath := filepath.Join(sourcesDir, "name") - tagPath := filepath.Join(sourcesDir, "tag") - - file(namePath, "v0.3.12") - file(tagPath, "0.3.12") - - request := resource.OutRequest{ - Params: resource.OutParams{ - NamePath: "name", - TagPath: "tag", - }, - } - - _, err := command.Run(sourcesDir, request) - Ω(err).ShouldNot(HaveOccurred()) - - Ω(githubClient.CreateReleaseCallCount()).Should(Equal(1)) - release := githubClient.CreateReleaseArgsForCall(0) - - Ω(*release.Name).Should(Equal("v0.3.12")) - Ω(*release.TagName).Should(Equal("0.3.12")) - Ω(*release.Body).Should(Equal("")) - }) - - It("uploads matching file globs", func() { - namePath := filepath.Join(sourcesDir, "name") - bodyPath := filepath.Join(sourcesDir, "body") - tagPath := filepath.Join(sourcesDir, "tag") - - file(namePath, "v0.3.12") - file(bodyPath, "this is a great release") - file(tagPath, "0.3.12") - - globMatching := filepath.Join(sourcesDir, "great-file.tgz") - globNotMatching := filepath.Join(sourcesDir, "bad-file.txt") - - file(globMatching, "matching") - file(globNotMatching, "not matching") - - githubClient.CreateReleaseStub = func(gh *github.RepositoryRelease) (*github.RepositoryRelease, error) { - createdRel := *gh - createdRel.ID = github.Int(112) - return &createdRel, nil - } - - request := resource.OutRequest{ - Params: resource.OutParams{ - NamePath: "name", - BodyPath: "body", - TagPath: "tag", - - Globs: []string{ - "*.tgz", + request = resource.OutRequest{ + Params: resource.OutParams{ + NamePath: "name", + BodyPath: "body", + TagPath: "tag", }, - }, - } + } + }) - _, err := command.Run(sourcesDir, request) - Ω(err).ShouldNot(HaveOccurred()) + It("updates the existing release", func() { + Ω(githubClient.UpdateReleaseCallCount()).Should(Equal(1)) - Ω(githubClient.UploadReleaseAssetCallCount()).Should(Equal(1)) - release, name, file := githubClient.UploadReleaseAssetArgsForCall(0) - - Ω(*release.ID).Should(Equal(112)) - Ω(name).Should(Equal("great-file.tgz")) - Ω(file.Name()).Should(Equal(filepath.Join(sourcesDir, "great-file.tgz"))) + updatedRelease := githubClient.UpdateReleaseArgsForCall(0) + Ω(*updatedRelease.Name).Should(Equal("v0.3.12")) + Ω(*updatedRelease.Body).Should(Equal("this is a great release")) + }) }) - It("has some sweet metadata", func() { - namePath := filepath.Join(sourcesDir, "name") - bodyPath := filepath.Join(sourcesDir, "body") - tagPath := filepath.Join(sourcesDir, "tag") + Context("when the release has not already been created", func() { + BeforeEach(func() { + namePath := filepath.Join(sourcesDir, "name") + tagPath := filepath.Join(sourcesDir, "tag") - file(namePath, "v0.3.12") - file(bodyPath, "this is a great release") - file(tagPath, "0.3.12") + file(namePath, "v0.3.12") + file(tagPath, "0.3.12") - request := resource.OutRequest{ - Params: resource.OutParams{ - NamePath: "name", - BodyPath: "body", - TagPath: "tag", - }, - } + request = resource.OutRequest{ + Params: resource.OutParams{ + NamePath: "name", + TagPath: "tag", + }, + } + }) - outResponse, err := command.Run(sourcesDir, request) - Ω(err).ShouldNot(HaveOccurred()) + Context("with a body", func() { + BeforeEach(func() { + bodyPath := filepath.Join(sourcesDir, "body") + file(bodyPath, "this is a great release") + request.Params.BodyPath = "body" + }) - Ω(outResponse.Metadata).Should(ConsistOf( - resource.MetadataPair{Name: "url", Value: "http://google.com"}, - resource.MetadataPair{Name: "name", Value: "release-name", URL: "http://google.com"}, - resource.MetadataPair{Name: "body", Value: "*markdown*", Markdown: true}, - )) + It("creates a release on GitHub", func() { + Ω(githubClient.CreateReleaseCallCount()).Should(Equal(1)) + release := githubClient.CreateReleaseArgsForCall(0) + + Ω(*release.Name).Should(Equal("v0.3.12")) + Ω(*release.TagName).Should(Equal("0.3.12")) + Ω(*release.Body).Should(Equal("this is a great release")) + }) + }) + + Context("without a body", func() { + It("works", func() { + Ω(githubClient.CreateReleaseCallCount()).Should(Equal(1)) + release := githubClient.CreateReleaseArgsForCall(0) + + Ω(*release.Name).Should(Equal("v0.3.12")) + Ω(*release.TagName).Should(Equal("0.3.12")) + Ω(*release.Body).Should(Equal("")) + }) + }) + + Context("with file globs", func() { + BeforeEach(func() { + globMatching := filepath.Join(sourcesDir, "great-file.tgz") + globNotMatching := filepath.Join(sourcesDir, "bad-file.txt") + + file(globMatching, "matching") + file(globNotMatching, "not matching") + + request = resource.OutRequest{ + Params: resource.OutParams{ + NamePath: "name", + BodyPath: "body", + TagPath: "tag", + + Globs: []string{ + "*.tgz", + }, + }, + } + + bodyPath := filepath.Join(sourcesDir, "body") + file(bodyPath, "*markdown*") + request.Params.BodyPath = "body" + }) + + It("uploads matching file globs", func() { + Ω(githubClient.UploadReleaseAssetCallCount()).Should(Equal(1)) + release, name, file := githubClient.UploadReleaseAssetArgsForCall(0) + + Ω(*release.ID).Should(Equal(112)) + Ω(name).Should(Equal("great-file.tgz")) + Ω(file.Name()).Should(Equal(filepath.Join(sourcesDir, "great-file.tgz"))) + }) + + It("has some sweet metadata", func() { + outResponse, err := command.Run(sourcesDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(outResponse.Metadata).Should(ConsistOf( + resource.MetadataPair{Name: "url", Value: "http://google.com"}, + resource.MetadataPair{Name: "name", Value: "release-name", URL: "http://google.com"}, + resource.MetadataPair{Name: "body", Value: "*markdown*", Markdown: true}, + )) + }) + }) }) }) - -func file(path, contents string) { - Ω(ioutil.WriteFile(path, []byte(contents), 0644)).Should(Succeed()) -}