diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bb3ba..06c1211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Utils Changelog +## v0.0.6 + +* Added `HasChanges` to `external_git`. +* Added `SetPullRequestAutoComplete` to `azuredevops`. +* Added `wiki` to `azuredevops`. +* Added `identity` to `azuredevops`. +* Added create and init repository to `azuredevops`. +* Added `getProjectUUID` to `azuredevops`. + +## v0.0.5 + +* Improved error handling in `azuredevops` package. + ## v0.0.4 * Updated `azuredevops` package to include pull request support. diff --git a/azuredevops/artifacts.go b/azuredevops/artifacts.go new file mode 100644 index 0000000..6914bb6 --- /dev/null +++ b/azuredevops/artifacts.go @@ -0,0 +1,19 @@ +package azuredevops + +import ( + "github.com/microsoft/azure-devops-go-api/azuredevops/feed" +) + +// GetPackageVersion gets all GitRepository +func (a *AzureDevOps) GetPackageVersion(projectName string, feedName string) (*[]feed.Package, error) { + feedClient, err := feed.NewClient(a.ctx, a.connection) + if err != nil { + return nil, err + } + + getPackagesArgs := feed.GetPackagesArgs{ + FeedId: &feedName, + Project: &projectName, + } + return feedClient.GetPackages(a.ctx, getPackagesArgs) +} diff --git a/azuredevops/git.go b/azuredevops/git.go new file mode 100644 index 0000000..0fad4ea --- /dev/null +++ b/azuredevops/git.go @@ -0,0 +1,28 @@ +package azuredevops + +import ( + "github.com/microsoft/azure-devops-go-api/azuredevops/git" +) + +// GetPackageVersion gets all GitRepository +func (a *AzureDevOps) GetFileContent(projectName string, repoName string, version string) (*git.GitItem, error) { + client, err := git.NewClient(a.ctx, a.connection) + if err != nil { + return nil, err + } + + path := "README.md" + includeContent := true + gitVersionDescriptor := git.GitVersionDescriptor{ + VersionType: &git.GitVersionTypeValues.Tag, + Version: &version, + } + getItemArgs := git.GetItemArgs{ + RepositoryId: &repoName, + Project: &projectName, + Path: &path, + IncludeContent: &includeContent, + VersionDescriptor: &gitVersionDescriptor, + } + return client.GetItem(a.ctx, getItemArgs) +} diff --git a/azuredevops/identity.go b/azuredevops/identity.go new file mode 100644 index 0000000..3c1800f --- /dev/null +++ b/azuredevops/identity.go @@ -0,0 +1,18 @@ +package azuredevops + +import ( + "github.com/google/uuid" + "github.com/microsoft/azure-devops-go-api/azuredevops/location" +) + +// GetIdentityId gets the UUID of the authenticated user. Yes this is weird, see https://github.com/microsoft/azure-devops-python-api/issues/188#issuecomment-494858123 +func (a *AzureDevOps) GetIdentityId() (*uuid.UUID, error) { + client := location.NewClient(a.ctx, a.connection) + + self, err := client.GetConnectionData(a.ctx, location.GetConnectionDataArgs{}) + if err != nil { + return nil, err + } + + return self.AuthenticatedUser.Id, nil +} diff --git a/azuredevops/project.go b/azuredevops/project.go new file mode 100644 index 0000000..0bdbfd8 --- /dev/null +++ b/azuredevops/project.go @@ -0,0 +1,30 @@ +package azuredevops + +import ( + "fmt" + + "github.com/google/uuid" + "github.com/microsoft/azure-devops-go-api/azuredevops/core" +) + +// getProjectUUID gets the UUID of a project +func (a *AzureDevOps) getProjectUUID(projectName string) (*uuid.UUID, error) { + client, err := core.NewClient(a.ctx, a.connection) + if err != nil { + return nil, err + } + + getProjectsArgs := core.GetProjectsArgs{} + projects, err := client.GetProjects(a.ctx, getProjectsArgs) + if err != nil { + return nil, err + } + + for _, project := range *&projects.Value { + if *project.Name == projectName { + return project.Id, nil + } + } + + return nil, fmt.Errorf("project %s not found", projectName) +} diff --git a/azuredevops/pull_request.go b/azuredevops/pull_request.go index ecef49f..4fd3f77 100644 --- a/azuredevops/pull_request.go +++ b/azuredevops/pull_request.go @@ -1,7 +1,9 @@ package azuredevops import ( + "github.com/google/uuid" "github.com/microsoft/azure-devops-go-api/azuredevops/git" + "github.com/microsoft/azure-devops-go-api/azuredevops/webapi" ) // AbandonPullRequest abandons a GitPullRequest @@ -74,3 +76,31 @@ func (a *AzureDevOps) CreatePullRequest(projectName string, repositoryName strin } return client.CreatePullRequest(a.ctx, createPullRequestArgs) } + +// CompletePullRequest completes a GitPullRequest +func (a *AzureDevOps) SetPullRequestAutoComplete(projectName string, repositoryName string, pullRequestId int, userId *uuid.UUID) error { + client, err := git.NewClient(a.ctx, a.connection) + if err != nil { + return err + } + + id := userId.String() + deleteSourceBranch := true + + _, err = client.UpdatePullRequest(a.ctx, git.UpdatePullRequestArgs{ + Project: &projectName, + PullRequestId: &pullRequestId, + RepositoryId: &repositoryName, + GitPullRequestToUpdate: &git.GitPullRequest{ + AutoCompleteSetBy: &webapi.IdentityRef{ + Id: &id, + }, + CompletionOptions: &git.GitPullRequestCompletionOptions{ + DeleteSourceBranch: &deleteSourceBranch, + MergeStrategy: &git.GitPullRequestMergeStrategyValues.Squash, + }, + }, + }) + + return err +} diff --git a/azuredevops/repository.go b/azuredevops/repository.go index 7a7fc3d..d6d6c28 100644 --- a/azuredevops/repository.go +++ b/azuredevops/repository.go @@ -2,7 +2,11 @@ package azuredevops import ( "fmt" + "os" + "path/filepath" + egit "github.com/frontierdigital/utils/git/external_git" + "github.com/frontierdigital/utils/output" "github.com/microsoft/azure-devops-go-api/azuredevops/git" "golang.org/x/exp/slices" ) @@ -36,3 +40,114 @@ func (a *AzureDevOps) GetRepository(projectName string, name string) (*git.GitRe return &(*repositories)[repositoryIdx], nil } + +// configureRepository configures a repository +func configureRepository(repo *egit.ExternalGit, gitEmail string, gitUsername string) error { + err := repo.SetConfig("user.email", gitEmail) + if err != nil { + return err + } + err = repo.SetConfig("user.name", gitUsername) + if err != nil { + return err + } + return nil +} + +// createRepositoryIfNotExists creates a repository if it does not exist +func createRepositoryIfNotExists(a *AzureDevOps, projectName string, repoName string, gitEmail string, gitUsername string, adoPat string) (*git.GitRepository, *string, error) { + client, err := git.NewClient(a.ctx, a.connection) + if err != nil { + return nil, nil, err + } + + getRepositoryArgs := git.GetRepositoryArgs{ + RepositoryId: &repoName, + Project: &projectName, + } + + r, err := client.GetRepository(a.ctx, getRepositoryArgs) + + if err == nil { + repoPath, err := os.MkdirTemp("", "") + if err != nil { + return nil, nil, err + } + repo := egit.NewGit(repoPath) + err = repo.CloneOverHttp(*r.RemoteUrl, adoPat, "x-oauth-basic") + if err != nil { + return nil, nil, err + } + err = configureRepository(repo, gitEmail, gitUsername) + if err != nil { + return nil, nil, err + } + return r, &repoPath, nil + } + + createRepositoryArgs := git.CreateRepositoryArgs{ + GitRepositoryToCreate: &git.GitRepositoryCreateOptions{ + Name: &repoName, + }, + Project: &projectName, + } + + r, err = client.CreateRepository(a.ctx, createRepositoryArgs) + + if err != nil { + return nil, nil, err + } + + localPath, err := initRepository("frontierdigital", projectName, repoName, gitEmail, gitUsername, adoPat) + + if err != nil { + return nil, nil, err + } + + return r, localPath, nil +} + +// initRepository creates a main branch +func initRepository(organisationName string, projectName string, repoName string, gitEmail string, gitUsername string, adoPat string) (*string, error) { + repoUrl := fmt.Sprintf("https://dev.azure.com/%s/%s/_git/%s", organisationName, projectName, repoName) + + repoPath, err := os.MkdirTemp("", "") + if err != nil { + return nil, err + } + repo := egit.NewGit(repoPath) + err = repo.CloneOverHttp(repoUrl, adoPat, "x-oauth-basic") + if err != nil { + return nil, err + } + err = configureRepository(repo, gitEmail, gitUsername) + if err != nil { + return nil, err + } + + file, err := os.Create(filepath.Join(repoPath, "README.md")) + if err != nil { + return nil, err + } + defer file.Close() + + err = repo.AddAll() + if err != nil { + return nil, err + } + + commitMessage := "Initial Commit" + _, err = repo.Commit(commitMessage) + if err != nil { + return nil, err + } + + err = repo.Push(false) + if err != nil { + return nil, err + } + + output.PrintlnfInfo("Pushed.") + + return &repoPath, nil +} diff --git a/azuredevops/wiki.go b/azuredevops/wiki.go new file mode 100644 index 0000000..95c44d4 --- /dev/null +++ b/azuredevops/wiki.go @@ -0,0 +1,59 @@ +package azuredevops + +import ( + "github.com/microsoft/azure-devops-go-api/azuredevops/git" + "github.com/microsoft/azure-devops-go-api/azuredevops/wiki" +) + +func (a *AzureDevOps) CreateWikiIfNotExists(projectName string, wikiName string, gitEmail string, gitUsername string, adoPat string) (*string, error) { + client, err := wiki.NewClient(a.ctx, a.connection) + if err != nil { + return nil, err + } + + getWikiArgs := wiki.GetWikiArgs{ + Project: &projectName, + WikiIdentifier: &wikiName, + } + + r, localPath, err := createRepositoryIfNotExists(a, projectName, wikiName, gitEmail, gitUsername, adoPat) + if err != nil { + return nil, err + } + + _, err = client.GetWiki(a.ctx, getWikiArgs) + + if err == nil { + return localPath, nil + } + + projectId, err := a.getProjectUUID(projectName) + if err != nil { + return nil, err + } + + branch := "main" + mappedPath := "/" + wikiCreateArgs := wiki.CreateWikiArgs{ + Project: &projectName, + WikiCreateParams: &wiki.WikiCreateParametersV2{ + MappedPath: &mappedPath, + Name: &wikiName, + ProjectId: projectId, + Type: &wiki.WikiTypeValues.CodeWiki, + RepositoryId: r.Id, + Version: &git.GitVersionDescriptor{ + VersionType: &git.GitVersionTypeValues.Branch, + Version: &branch, + }, + }, + } + _, err = client.CreateWiki(a.ctx, wikiCreateArgs) + + if err != nil { + return nil, err + } + + return localPath, nil + +} diff --git a/git/external_git/status.go b/git/external_git/status.go new file mode 100644 index 0000000..f2a0ddb --- /dev/null +++ b/git/external_git/status.go @@ -0,0 +1,18 @@ +package external_git + +// HasChanges checks for changes in the current branch +func (g *ExternalGit) HasChanges() (bool, error) { + var args []string + args = []string{"status", "-s"} + + content, err := g.Exec(args...) + if err != nil { + return false, err + } + + if content == "" { + return false, nil + } + + return true, nil +} diff --git a/git/external_git/status_test.go b/git/external_git/status_test.go new file mode 100644 index 0000000..dd63f50 --- /dev/null +++ b/git/external_git/status_test.go @@ -0,0 +1 @@ +package external_git