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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
package export
import (
"context"
"fmt"
"log"
"net/url"
"os"
"os/exec"
"path"
"strings"
"git.sr.ht/~emersion/gqlclient"
"git.sr.ht/~xenrox/hut/srht/gitsrht"
)
const gitRepositoryDir = "repository.git"
type GitExporter struct {
client *gqlclient.Client
baseURL string
baseCloneURL string
}
func NewGitExporter(client *gqlclient.Client, baseURL string) *GitExporter {
return &GitExporter{
client: client,
baseURL: baseURL,
}
}
// A subset of gitsrht.Repository which only contains the fields we want to
// export (i.e. the ones filled in by the GraphQL query)
type GitRepoInfo struct {
Info
Description *string `json:"description"`
Visibility gitsrht.Visibility `json:"visibility"`
Readme *string `json:"readme"`
Head *string `json:"head"`
}
func (ex *GitExporter) Export(ctx context.Context, dir string) error {
var cursor *gitsrht.Cursor
for {
repos, err := gitsrht.ExportRepositories(ex.client, ctx, cursor)
if err != nil {
return err
}
for _, repo := range repos.Results {
base := path.Join(dir, repo.Name)
if err := ex.exportRepository(ctx, &repo, base); err != nil {
return err
}
}
cursor = repos.Cursor
if cursor == nil {
break
}
}
return nil
}
func (ex *GitExporter) ExportResource(ctx context.Context, dir, owner, resource string) error {
user, err := gitsrht.ExportRepository(ex.client, ctx, owner, resource)
if err != nil {
return err
}
return ex.exportRepository(ctx, user.Repository, dir)
}
func (ex *GitExporter) exportRepository(ctx context.Context, repo *gitsrht.Repository, base string) error {
// Cache base clone URL in exporter.
if ex.baseCloneURL == "" {
settings, err := gitsrht.SshSettings(ex.client, ctx)
if err != nil {
return err
}
sshUser := settings.Settings.SshUser
baseURL, err := url.Parse(ex.baseURL)
if err != nil {
panic(err)
}
ex.baseCloneURL = fmt.Sprintf("%s@%s", sshUser, baseURL.Host)
}
// TODO: Should we fetch & store ACLs?
infoPath := path.Join(base, infoFilename)
clonePath := path.Join(base, gitRepositoryDir)
cloneURL := fmt.Sprintf("%s:%s/%s", ex.baseCloneURL, repo.Owner.CanonicalName, repo.Name)
if _, err := os.Stat(clonePath); err == nil {
log.Printf("\tSkipping %s (already exists)", repo.Name)
return nil
}
if err := os.MkdirAll(base, 0o755); err != nil {
return err
}
log.Printf("\tCloning %s", repo.Name)
cmd := exec.CommandContext(ctx, "git", "clone", "--mirror", cloneURL, clonePath)
if err := cmd.Run(); err != nil {
return err
}
var head *string
if repo.HEAD != nil {
h := strings.TrimPrefix(repo.HEAD.Name, "refs/heads/")
head = &h
}
repoInfo := GitRepoInfo{
Info: Info{
Service: "git.sr.ht",
Name: repo.Name,
},
Description: repo.Description,
Visibility: repo.Visibility,
Readme: repo.Readme,
Head: head,
}
return writeJSON(infoPath, &repoInfo)
}
func (ex *GitExporter) ImportResource(ctx context.Context, dir string) error {
settings, err := gitsrht.SshSettings(ex.client, ctx)
if err != nil {
return fmt.Errorf("failed to get Git SSH settings: %v", err)
}
sshUser := settings.Settings.SshUser
baseURL, err := url.Parse(ex.baseURL)
if err != nil {
panic(err)
}
var info GitRepoInfo
if err := readJSON(path.Join(dir, infoFilename), &info); err != nil {
return err
}
g, err := gitsrht.CreateRepository(ex.client, ctx, info.Name, info.Visibility, info.Description, nil)
if err != nil {
return fmt.Errorf("failed to create Git repository: %v", err)
}
clonePath := path.Join(dir, gitRepositoryDir)
cloneURL := fmt.Sprintf("%s@%s:%s/%s", sshUser, baseURL.Host, g.Owner.CanonicalName, info.Name)
cmd := exec.Command("git", "-C", clonePath, "push", "--mirror", cloneURL)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to push Git repository: %v", err)
}
if _, err := gitsrht.UpdateRepository(ex.client, ctx, g.Id, gitsrht.RepoInput{
Readme: info.Readme,
HEAD: info.Head,
}); err != nil {
return fmt.Errorf("failed to update Git repository: %v", err)
}
return nil
}
|