gitserver: Add RepoCloneProgress endpoint (#10041)

This is similar to /repos, but only returns clone progress and is much
faster as it only needs to check the disk once.
This commit is contained in:
Ryan Slade 2020-04-21 10:15:27 +02:00 committed by GitHub
parent 6fa2652bd1
commit b1ddeff4a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 135 additions and 7 deletions

View File

@ -151,7 +151,7 @@ func (r *repositoryConnectionResolver) compute(ctx context.Context) ([]*types.Re
for i, repo := range repos {
repoNames[i] = repo.Name
}
response, err := gitserver.DefaultClient.RepoInfo(ctx, repoNames...)
response, err := gitserver.DefaultClient.RepoCloneProgress(ctx, repoNames...)
if err != nil {
r.err = err
return

View File

@ -51,6 +51,19 @@ func (s *Server) repoInfo(ctx context.Context, repo api.RepoName) (*protocol.Rep
return &resp, nil
}
func (s *Server) repoCloneProgress(repo api.RepoName) (*protocol.RepoCloneProgress, error) {
dir := s.dir(repo)
resp := protocol.RepoCloneProgress{
Cloned: repoCloned(dir),
}
resp.CloneProgress, resp.CloneInProgress = s.locker.Status(dir)
if isAlwaysCloningTest(repo) {
resp.CloneInProgress = true
resp.CloneProgress = "This will never finish cloning"
}
return &resp, nil
}
func (s *Server) handleRepoInfo(w http.ResponseWriter, r *http.Request) {
var req protocol.RepoInfoRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@ -76,6 +89,31 @@ func (s *Server) handleRepoInfo(w http.ResponseWriter, r *http.Request) {
}
}
func (s *Server) handleRepoCloneProgress(w http.ResponseWriter, r *http.Request) {
var req protocol.RepoCloneProgressRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
resp := protocol.RepoCloneProgressResponse{
Results: make(map[api.RepoName]*protocol.RepoCloneProgress, len(req.Repos)),
}
for _, repoName := range req.Repos {
result, err := s.repoCloneProgress(repoName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp.Results[repoName] = result
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (s *Server) handleRepoDelete(w http.ResponseWriter, r *http.Request) {
var req protocol.RepoDeleteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {

View File

@ -230,6 +230,7 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("/is-repo-cloneable", s.handleIsRepoCloneable)
mux.HandleFunc("/is-repo-cloned", s.handleIsRepoCloned)
mux.HandleFunc("/repos", s.handleRepoInfo)
mux.HandleFunc("/repo-clone-progress", s.handleRepoCloneProgress)
mux.HandleFunc("/delete", s.handleRepoDelete)
mux.HandleFunc("/repo-update", s.handleRepoUpdate)
mux.HandleFunc("/getGitolitePhabricatorMetadata", s.handleGetGitolitePhabricatorMetadata)

View File

@ -659,6 +659,76 @@ func (c *Client) IsRepoCloned(ctx context.Context, repo api.RepoName) (bool, err
return cloned, nil
}
func (c *Client) RepoCloneProgress(ctx context.Context, repos ...api.RepoName) (*protocol.RepoCloneProgressResponse, error) {
numPossibleShards := len(c.Addrs(ctx))
shards := make(map[string]*protocol.RepoCloneProgressRequest, (len(repos)/numPossibleShards)*2) // 2x because it may not be a perfect division
for _, r := range repos {
addr := c.AddrForRepo(ctx, r)
shard := shards[addr]
if shard == nil {
shard = new(protocol.RepoCloneProgressRequest)
shards[addr] = shard
}
shard.Repos = append(shard.Repos, r)
}
type op struct {
req *protocol.RepoCloneProgressRequest
res *protocol.RepoCloneProgressResponse
err error
}
ch := make(chan op, len(shards))
for _, req := range shards {
go func(o op) {
var resp *http.Response
resp, o.err = c.httpPost(ctx, o.req.Repos[0], "repo-clone-progress", o.req)
if o.err != nil {
ch <- o
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
o.err = &url.Error{
URL: resp.Request.URL.String(),
Op: "RepoCloneProgress",
Err: errors.Errorf("RepoCloneProgress: http status %d", resp.StatusCode),
}
ch <- o
return // we never get an error status code AND result
}
o.res = new(protocol.RepoCloneProgressResponse)
o.err = json.NewDecoder(resp.Body).Decode(o.res)
ch <- o
}(op{req: req})
}
err := new(multierror.Error)
res := protocol.RepoCloneProgressResponse{
Results: make(map[api.RepoName]*protocol.RepoCloneProgress),
}
for i := 0; i < cap(ch); i++ {
o := <-ch
if o.err != nil {
err = multierror.Append(err, o.err)
continue
}
for repo, info := range o.res.Results {
res.Results[repo] = info
}
}
return &res, err.ErrorOrNil()
}
// RepoInfo retrieves information about one or more repositories on gitserver.
//
// The repository not existing is not an error; in that case, RepoInfoResponse.Results[i].Cloned

View File

@ -96,18 +96,18 @@ type IsRepoClonedRequest struct {
Repo api.RepoName
}
// RepoInfoRequest is a request for information about multiple repositories on gitserver.
type RepoInfoRequest struct {
// Repos are the repositories to get information about.
Repos []api.RepoName
}
// RepoDeleteRequest is a request to delete a repository clone on gitserver
type RepoDeleteRequest struct {
// Repo is the repository to delete.
Repo api.RepoName
}
// RepoInfoRequest is a request for information about multiple repositories on gitserver.
type RepoInfoRequest struct {
// Repos are the repositories to get information about.
Repos []api.RepoName
}
// RepoInfo is the information requests about a single repository
// via a RepoInfoRequest.
type RepoInfo struct {
@ -131,6 +131,25 @@ type RepoInfoResponse struct {
Results map[api.RepoName]*RepoInfo
}
// RepoCloneProgressRequest is a request for information about the clone progress of multiple
// repositories on gitserver.
type RepoCloneProgressRequest struct {
Repos []api.RepoName
}
// RepoCloneProgress is information about the clone progress of a repo
type RepoCloneProgress struct {
CloneInProgress bool // whether the repository is currently being cloned
CloneProgress string // a progress message from the running clone command.
Cloned bool // whether the repository has been cloned successfully
}
// RepoCloneProgressResponse is the response to a repository clone progress request
// for multiple repositories at the same time.
type RepoCloneProgressResponse struct {
Results map[api.RepoName]*RepoCloneProgress
}
// CreateCommitFromPatchRequest is the request information needed for creating
// the simulated staging area git object for a repo.
type CreateCommitFromPatchRequest struct {