1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
package repository
import (
"context"
"errors"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
"gitlab.com/gitlab-org/gitaly/internal/git"
"gitlab.com/gitlab-org/gitaly/internal/git/localrepo"
"gitlab.com/gitlab-org/gitaly/internal/git/remoterepo"
"gitlab.com/gitlab-org/gitaly/internal/gitalyssh"
"gitlab.com/gitlab-org/gitaly/internal/helper"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
func (s *server) FetchSourceBranch(ctx context.Context, req *gitalypb.FetchSourceBranchRequest) (*gitalypb.FetchSourceBranchResponse, error) {
if err := git.ValidateRevision(req.GetSourceBranch()); err != nil {
return nil, helper.ErrInvalidArgument(err)
}
if err := git.ValidateRevision(req.GetTargetRef()); err != nil {
return nil, helper.ErrInvalidArgument(err)
}
targetRepo := localrepo.New(s.gitCmdFactory, req.GetRepository(), s.cfg)
sourceRepo, err := remoterepo.New(ctx, req.GetSourceRepository(), s.conns)
if err != nil {
return nil, helper.ErrInternal(err)
}
var sourceOid git.ObjectID
var containsObject bool
// If source and target repository are the same, then we know that both
// are local. We can thus optimize and locally resolve the reference
// instead of using an RPC call. We also know that we can always skip
// the fetch as the object will be available.
if helper.RepoPathEqual(req.GetRepository(), req.GetSourceRepository()) {
var err error
sourceOid, err = targetRepo.ResolveRevision(ctx, git.Revision(req.GetSourceBranch()))
if err != nil {
if errors.Is(err, git.ErrReferenceNotFound) {
return &gitalypb.FetchSourceBranchResponse{Result: false}, nil
}
return nil, helper.ErrInternal(err)
}
containsObject = true
} else {
var err error
sourceOid, err = sourceRepo.ResolveRevision(ctx, git.Revision(req.GetSourceBranch()))
if err != nil {
if errors.Is(err, git.ErrReferenceNotFound) {
return &gitalypb.FetchSourceBranchResponse{Result: false}, nil
}
return nil, helper.ErrInternal(err)
}
// Otherwise, if the source is a remote repository, we check
// whether the target repo already contains the desired object.
// If so, we can skip the fetch.
containsObject, err = targetRepo.HasRevision(ctx, sourceOid.Revision()+"^{commit}")
if err != nil {
return nil, helper.ErrInternal(err)
}
}
// There's no need to perform the fetch if we already have the object
// available.
if !containsObject {
env, err := gitalyssh.UploadPackEnv(ctx, s.cfg, &gitalypb.SSHUploadPackRequest{Repository: req.SourceRepository})
if err != nil {
return nil, err
}
cmd, err := s.gitCmdFactory.New(ctx, req.Repository,
git.SubCmd{
Name: "fetch",
Args: []string{gitalyssh.GitalyInternalURL, sourceOid.String()},
Flags: []git.Option{git.Flag{Name: "--no-tags"}, git.Flag{Name: "--quiet"}},
},
git.WithEnv(env...),
git.WithRefTxHook(ctx, req.Repository, s.cfg),
)
if err != nil {
return nil, err
}
if err := cmd.Wait(); err != nil {
// Design quirk: if the fetch fails, this RPC returns Result: false, but no error.
ctxlogrus.Extract(ctx).
WithField("oid", sourceOid.String()).
WithError(err).Warn("git fetch failed")
return &gitalypb.FetchSourceBranchResponse{Result: false}, nil
}
}
if err := targetRepo.UpdateRef(ctx, git.ReferenceName(req.GetTargetRef()), sourceOid, ""); err != nil {
return nil, err
}
return &gitalypb.FetchSourceBranchResponse{Result: true}, nil
}
|