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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
package repository
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"gitlab.com/gitlab-org/gitaly/proto/v15/go/gitalypb"
"gitlab.com/gitlab-org/gitaly/v15/internal/git"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo"
"gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/service"
"gitlab.com/gitlab-org/gitaly/v15/internal/tempdir"
"gitlab.com/gitlab-org/gitaly/v15/streamio"
)
const (
mirrorRefSpec = "+refs/*:refs/*"
)
func (s *server) FetchBundle(stream gitalypb.RepositoryService_FetchBundleServer) error {
firstRequest, err := stream.Recv()
if err != nil {
return structerr.NewInternal("first request: %w", err)
}
if err := service.ValidateRepository(firstRequest.GetRepository()); err != nil {
return structerr.NewInvalidArgument("%w", err)
}
firstRead := true
reader := streamio.NewReader(func() ([]byte, error) {
if firstRead {
firstRead = false
return firstRequest.GetData(), nil
}
request, err := stream.Recv()
return request.GetData(), err
})
ctx := stream.Context()
repo := s.localrepo(firstRequest.GetRepository())
updateHead := firstRequest.GetUpdateHead()
tmpDir, err := tempdir.New(ctx, repo.GetStorageName(), s.locator)
if err != nil {
return structerr.NewInternal("%w", err)
}
bundlePath := filepath.Join(tmpDir.Path(), "repo.bundle")
file, err := os.Create(bundlePath)
if err != nil {
return structerr.NewInternal("%w", err)
}
_, err = io.Copy(file, reader)
if err != nil {
return structerr.NewInternal("copy bundle: %w", err)
}
config := []git.ConfigPair{
{Key: "remote.inmemory.url", Value: bundlePath},
{Key: "remote.inmemory.fetch", Value: mirrorRefSpec},
}
opts := localrepo.FetchOpts{
CommandOptions: []git.CmdOpt{git.WithConfigEnv(config...)},
}
if err := repo.FetchRemote(ctx, "inmemory", opts); err != nil {
return structerr.NewInternal("%w", err)
}
if updateHead {
if err := s.updateHeadFromBundle(ctx, repo, bundlePath); err != nil {
return structerr.NewInternal("%w", err)
}
}
return stream.SendAndClose(&gitalypb.FetchBundleResponse{})
}
// updateHeadFromBundle updates HEAD from a bundle file
func (s *server) updateHeadFromBundle(ctx context.Context, repo *localrepo.Repo, bundlePath string) error {
head, err := s.findBundleHead(ctx, repo, bundlePath)
if err != nil {
return fmt.Errorf("update head from bundle: %w", err)
}
if head == nil {
return nil
}
branch, err := repo.GuessHead(ctx, *head)
if err != nil {
return fmt.Errorf("update head from bundle: %w", err)
}
if err := repo.SetDefaultBranch(ctx, s.txManager, branch); err != nil {
return fmt.Errorf("update head from bundle: %w", err)
}
return nil
}
// findBundleHead tries to extract HEAD and its target from a bundle. Returns
// nil when HEAD is not found.
func (s *server) findBundleHead(ctx context.Context, repo git.RepositoryExecutor, bundlePath string) (*git.Reference, error) {
cmd, err := repo.Exec(ctx, git.Command{
Name: "bundle",
Action: "list-heads",
Args: []string{bundlePath, "HEAD"},
})
if err != nil {
return nil, err
}
decoder := git.NewShowRefDecoder(cmd)
for {
var ref git.Reference
err := decoder.Decode(&ref)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if ref.Name != "HEAD" {
continue
}
return &ref, nil
}
return nil, nil
}
|