Browse Source

Add support for the Source Import API

Fixes #291
Fixes #351
Will Norris 10 years ago
parent
commit
a68a8707f3
3 changed files with 554 additions and 0 deletions
  1. +3
    -0
      github/github.go
  2. +326
    -0
      github/migrations_source_import.go
  3. +225
    -0
      github/migrations_source_import_test.go

+ 3
- 0
github/github.go View File

@ -66,6 +66,9 @@ const (
// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
// https://developer.github.com/changes/2016-02-19-source-import-preview-api/
mediaTypeImportPreview = "application/vnd.github.barred-rock-preview"
)
// A Client manages communication with the GitHub API.


+ 326
- 0
github/migrations_source_import.go View File

@ -0,0 +1,326 @@
// Copyright 2016 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import "fmt"
// Import represents a repository import request.
type Import struct {
// The URL of the originating repository.
VCSURL *string `json:"vcs_url,omitempty"`
// The originating VCS type. Can be one of 'subversion', 'git',
// 'mercurial', or 'tfvc'. Without this parameter, the import job will
// take additional time to detect the VCS type before beginning the
// import. This detection step will be reflected in the response.
VCS *string `json:"vcs,omitempty"`
// VCSUsername and VCSPassword are only used for StartImport calls that
// are importing a password-protected repository.
VCSUsername *string `json:"vcs_username,omitempty"`
VCSPassword *string `json:"vcs_password,omitempty"`
// For a tfvc import, the name of the project that is being imported.
TFVCProject *string `json:"tfvc_project,omitempty"`
// LFS related fields that may be preset in the Import Progress response
// Describes whether the import has been opted in or out of using Git
// LFS. The value can be 'opt_in', 'opt_out', or 'undecided' if no
// action has been taken.
UseLFS *string `json:"use_lfs,omitempty"`
// Describes whether files larger than 100MB were found during the
// importing step.
HasLargeFiles *bool `json:"has_large_files,omitempty"`
// The total size in gigabytes of files larger than 100MB found in the
// originating repository.
LargeFilesSize *int `json:"large_files_size,omitempty"`
// The total number of files larger than 100MB found in the originating
// repository. To see a list of these files, call LargeFiles.
LargeFilesCount *int `json:"large_files_count,omitempty"`
// Identifies the current status of an import. An import that does not
// have errors will progress through these steps:
//
// detecting - the "detection" step of the import is in progress
// because the request did not include a VCS parameter. The
// import is identifying the type of source control present at
// the URL.
// importing - the "raw" step of the import is in progress. This is
// where commit data is fetched from the original repository.
// The import progress response will include CommitCount (the
// total number of raw commits that will be imported) and
// Percent (0 - 100, the current progress through the import).
// mapping - the "rewrite" step of the import is in progress. This
// is where SVN branches are converted to Git branches, and
// where author updates are applied. The import progress
// response does not include progress information.
// pushing - the "push" step of the import is in progress. This is
// where the importer updates the repository on GitHub. The
// import progress response will include PushPercent, which is
// the percent value reported by git push when it is "Writing
// objects".
// complete - the import is complete, and the repository is ready
// on GitHub.
//
// If there are problems, you will see one of these in the status field:
//
// auth_failed - the import requires authentication in order to
// connect to the original repository. Make an UpdateImport
// request, and include VCSUsername and VCSPassword.
// error - the import encountered an error. The import progress
// response will include the FailedStep and an error message.
// Contact GitHub support for more information.
// detection_needs_auth - the importer requires authentication for
// the originating repository to continue detection. Make an
// UpdatImport request, and include VCSUsername and
// VCSPassword.
// detection_found_nothing - the importer didn't recognize any
// source control at the URL.
// detection_found_multiple - the importer found several projects
// or repositories at the provided URL. When this is the case,
// the Import Progress response will also include a
// ProjectChoices field with the possible project choices as
// values. Make an UpdateImport request, and include VCS and
// (if applicable) TFVCProject.
Status *string `json:"status,omitempty"`
CommitCount *int `json:"commit_count,omitempty"`
StatusText *string `json:"status_text,omitempty"`
AuthorsCount *int `json:"authors_count,omitempty"`
Percent *int `json:"percent,omitempty"`
PushPercent *int `json:"push_percent,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
AuthorsURL *string `json:"authors_url,omitempty"`
RepositoryURL *string `json:"repository_url,omitempty"`
Message *string `json:"message,omitempty"`
FailedStep *string `json:"failed_step,omitempty"`
// Human readable display name, provided when the Import appears as
// part of ProjectChoices.
HumanName *string `json:"human_name,omitempty"`
// When the importer finds several projects or repositories at the
// provided URLs, this will identify the available choices. Call
// UpdateImport with the selected Import value.
ProjectChoices []Import `json:"project_choices,omitempty"`
}
func (i Import) String() string {
return Stringify(i)
}
// SourceImportAuthor identifies an author imported from a source repository.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-commit-authors
type SourceImportAuthor struct {
ID *int `json:"id,omitempty"`
RemoteID *string `json:"remote_id,omitempty"`
RemoteName *string `json:"remote_name,omitempty"`
Email *string `json:"email,omitempty"`
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
ImportURL *string `json:"import_url,omitempty"`
}
func (a SourceImportAuthor) String() string {
return Stringify(a)
}
// LargeFile identifies a file larger than 100MB found during a repository import.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-large-files
type LargeFile struct {
RefName *string `json:"ref_name,omitempty"`
Path *string `json:"path,omitempty"`
OID *string `json:"oid,omitempty"`
Size *int `json:"size,omitempty"`
}
func (f LargeFile) String() string {
return Stringify(f)
}
// StartImport initiates a repository import.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#start-an-import
func (s *MigrationService) StartImport(owner, repo string, in *Import) (*Import, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/import", owner, repo)
req, err := s.client.NewRequest("PUT", u, in)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
out := new(Import)
resp, err := s.client.Do(req, out)
if err != nil {
return nil, resp, err
}
return out, resp, err
}
// QueryImport queries for the status and progress of an ongoing repository import.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-import-progress
func (s *MigrationService) ImportProgress(owner, repo string) (*Import, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/import", owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
out := new(Import)
resp, err := s.client.Do(req, out)
if err != nil {
return nil, resp, err
}
return out, resp, err
}
// UpdateImport initiates a repository import.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#update-existing-import
func (s *MigrationService) UpdateImport(owner, repo string, in *Import) (*Import, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/import", owner, repo)
req, err := s.client.NewRequest("PATCH", u, in)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
out := new(Import)
resp, err := s.client.Do(req, out)
if err != nil {
return nil, resp, err
}
return out, resp, err
}
// CommitAuthors gets the authors mapped from the original repository.
//
// Each type of source control system represents authors in a different way.
// For example, a Git commit author has a display name and an email address,
// but a Subversion commit author just has a username. The GitHub Importer will
// make the author information valid, but the author might not be correct. For
// example, it will change the bare Subversion username "hubot" into something
// like "hubot <hubot@12341234-abab-fefe-8787-fedcba987654>".
//
// This method and MapCommitAuthor allow you to provide correct Git author
// information.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-commit-authors
func (s *MigrationService) CommitAuthors(owner, repo string) ([]SourceImportAuthor, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/import/authors", owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
authors := new([]SourceImportAuthor)
resp, err := s.client.Do(req, authors)
if err != nil {
return nil, resp, err
}
return *authors, resp, err
}
// MapCommitAuthor updates an author's identity for the import. Your
// application can continue updating authors any time before you push new
// commits to the repository.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#map-a-commit-author
func (s *MigrationService) MapCommitAuthor(owner, repo string, id int, author *SourceImportAuthor) (*SourceImportAuthor, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/import/authors/%v", owner, repo, id)
req, err := s.client.NewRequest("PATCH", u, author)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
out := new(SourceImportAuthor)
resp, err := s.client.Do(req, out)
if err != nil {
return nil, resp, err
}
return out, resp, err
}
// SetLFSPreference sets whether imported repositories should use Git LFS for
// files larger than 100MB. Only the UseLFS field on the provided Import is
// used.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#set-git-lfs-preference
func (s *MigrationService) SetLFSPreference(owner, repo string, in *Import) (*Import, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/import/lfs", owner, repo)
req, err := s.client.NewRequest("PATCH", u, in)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
out := new(Import)
resp, err := s.client.Do(req, out)
if err != nil {
return nil, resp, err
}
return out, resp, err
}
// LargeFiles lists files larger than 100MB found during the import.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-large-files
func (s *MigrationService) LargeFiles(owner, repo string) ([]LargeFile, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/import/large_files", owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
files := new([]LargeFile)
resp, err := s.client.Do(req, files)
if err != nil {
return nil, resp, err
}
return *files, resp, err
}
// CancelImport stops an import for a repository.
//
// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#cancel-an-import
func (s *MigrationService) CancelImport(owner, repo string) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/import", owner, repo)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeImportPreview)
return s.client.Do(req, nil)
}

+ 225
- 0
github/migrations_source_import_test.go View File

@ -0,0 +1,225 @@
// Copyright 2016 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestMigrationService_StartImport(t *testing.T) {
setup()
defer teardown()
input := &Import{
VCS: String("git"),
VCSURL: String("url"),
VCSUsername: String("u"),
VCSPassword: String("p"),
}
mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) {
v := new(Import)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeImportPreview)
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, `{"status":"importing"}`)
})
got, _, err := client.Migrations.StartImport("o", "r", input)
if err != nil {
t.Errorf("StartImport returned error: %v", err)
}
want := &Import{Status: String("importing")}
if !reflect.DeepEqual(got, want) {
t.Errorf("StartImport = %+v, want %+v", got, want)
}
}
func TestMigrationService_ImportProgress(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeImportPreview)
fmt.Fprint(w, `{"status":"complete"}`)
})
got, _, err := client.Migrations.ImportProgress("o", "r")
if err != nil {
t.Errorf("ImportProgress returned error: %v", err)
}
want := &Import{Status: String("complete")}
if !reflect.DeepEqual(got, want) {
t.Errorf("ImportProgress = %+v, want %+v", got, want)
}
}
func TestMigrationService_UpdateImport(t *testing.T) {
setup()
defer teardown()
input := &Import{
VCS: String("git"),
VCSURL: String("url"),
VCSUsername: String("u"),
VCSPassword: String("p"),
}
mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) {
v := new(Import)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
testHeader(t, r, "Accept", mediaTypeImportPreview)
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, `{"status":"importing"}`)
})
got, _, err := client.Migrations.UpdateImport("o", "r", input)
if err != nil {
t.Errorf("UpdateImport returned error: %v", err)
}
want := &Import{Status: String("importing")}
if !reflect.DeepEqual(got, want) {
t.Errorf("UpdateImport = %+v, want %+v", got, want)
}
}
func TestMigrationService_CommitAuthors(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/import/authors", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeImportPreview)
fmt.Fprint(w, `[{"id":1,"name":"a"},{"id":2,"name":"b"}]`)
})
got, _, err := client.Migrations.CommitAuthors("o", "r")
if err != nil {
t.Errorf("CommitAuthors returned error: %v", err)
}
want := []SourceImportAuthor{
{ID: Int(1), Name: String("a")},
{ID: Int(2), Name: String("b")},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("CommitAuthors = %+v, want %+v", got, want)
}
}
func TestMigrationService_MapCommitAuthor(t *testing.T) {
setup()
defer teardown()
input := &SourceImportAuthor{Name: String("n"), Email: String("e")}
mux.HandleFunc("/repos/o/r/import/authors/1", func(w http.ResponseWriter, r *http.Request) {
v := new(SourceImportAuthor)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
testHeader(t, r, "Accept", mediaTypeImportPreview)
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
fmt.Fprint(w, `{"id": 1}`)
})
got, _, err := client.Migrations.MapCommitAuthor("o", "r", 1, input)
if err != nil {
t.Errorf("MapCommitAuthor returned error: %v", err)
}
want := &SourceImportAuthor{ID: Int(1)}
if !reflect.DeepEqual(got, want) {
t.Errorf("MapCommitAuthor = %+v, want %+v", got, want)
}
}
func TestMigrationService_SetLFSPreference(t *testing.T) {
setup()
defer teardown()
input := &Import{UseLFS: String("opt_in")}
mux.HandleFunc("/repos/o/r/import/lfs", func(w http.ResponseWriter, r *http.Request) {
v := new(Import)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
testHeader(t, r, "Accept", mediaTypeImportPreview)
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, `{"status":"importing"}`)
})
got, _, err := client.Migrations.SetLFSPreference("o", "r", input)
if err != nil {
t.Errorf("SetLFSPreference returned error: %v", err)
}
want := &Import{Status: String("importing")}
if !reflect.DeepEqual(got, want) {
t.Errorf("SetLFSPreference = %+v, want %+v", got, want)
}
}
func TestMigrationService_LargeFiles(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/import/large_files", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeImportPreview)
fmt.Fprint(w, `[{"oid":"a"},{"oid":"b"}]`)
})
got, _, err := client.Migrations.LargeFiles("o", "r")
if err != nil {
t.Errorf("LargeFiles returned error: %v", err)
}
want := []LargeFile{
{OID: String("a")},
{OID: String("b")},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("LargeFiles = %+v, want %+v", got, want)
}
}
func TestMigrationService_CancelImport(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
testHeader(t, r, "Accept", mediaTypeImportPreview)
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Migrations.CancelImport("o", "r")
if err != nil {
t.Errorf("CancelImport returned error: %v", err)
}
}

Loading…
Cancel
Save