1package gitea
2
3import (
4 "context"
5 "fmt"
6 "strconv"
7 "time"
8
9 "code.gitea.io/sdk/gitea"
10
11 "github.com/MichaelMure/git-bug/bridge/core"
12 "github.com/MichaelMure/git-bug/bridge/core/auth"
13 "github.com/MichaelMure/git-bug/bridge/gitea/iterator"
14 "github.com/MichaelMure/git-bug/cache"
15 "github.com/MichaelMure/git-bug/entities/bug"
16 "github.com/MichaelMure/git-bug/entity"
17 "github.com/MichaelMure/git-bug/util/text"
18)
19
20// giteaImporter implement the Importer interface
21type giteaImporter struct {
22 conf core.Configuration
23
24 // default client
25 client *gitea.Client
26
27 // iterator
28 iterator *iterator.Iterator
29
30 // send only channel
31 out chan<- core.ImportResult
32}
33
34func (gi *giteaImporter) Init(_ context.Context, repo *cache.RepoCache, conf core.Configuration) error {
35 gi.conf = conf
36
37 creds, err := auth.List(repo,
38 auth.WithTarget(target),
39 auth.WithKind(auth.KindToken),
40 auth.WithMeta(auth.MetaKeyBaseURL, conf[confKeyBaseURL]),
41 auth.WithMeta(auth.MetaKeyLogin, conf[confKeyDefaultLogin]),
42 )
43 if err != nil {
44 return err
45 }
46
47 if len(creds) == 0 {
48 return ErrMissingIdentityToken
49 }
50
51 gi.client, err = buildClient(conf[confKeyBaseURL], creds[0].(*auth.Token))
52 if err != nil {
53 return err
54 }
55
56 return nil
57}
58
59// ImportAll iterate over all the configured repository issues (comments) and ensure the creation
60// of the missing issues / comments / label events / title changes ...
61func (gi *giteaImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan core.ImportResult, error) {
62 gi.iterator = iterator.NewIterator(ctx, gi.client, 10, gi.conf[confKeyOwner], gi.conf[confKeyProject], defaultTimeout, since)
63 out := make(chan core.ImportResult)
64 gi.out = out
65
66 go func() {
67 defer close(gi.out)
68
69 // Loop over all matching issues
70 for gi.iterator.NextIssue() {
71 issue := gi.iterator.IssueValue()
72
73 // create issue
74 b, err := gi.ensureIssue(repo, issue)
75 if err != nil {
76 err := fmt.Errorf("issue creation: %v", err)
77 out <- core.NewImportError(err, "")
78 return
79 }
80
81 // Loop over all comments
82
83 // Loop over all label events
84
85 if !b.NeedCommit() {
86 out <- core.NewImportNothing(b.Id(), "no imported operation")
87 } else if err := b.Commit(); err != nil {
88 // commit bug state
89 err := fmt.Errorf("bug commit: %v", err)
90 out <- core.NewImportError(err, "")
91 return
92 }
93 }
94
95 if err := gi.iterator.Error(); err != nil {
96 out <- core.NewImportError(err, "")
97 }
98 }()
99
100 return out, nil
101}
102
103func (gi *giteaImporter) ensureIssue(repo *cache.RepoCache, issue *gitea.Issue) (*cache.BugCache, error) {
104 // ensure issue author
105 author, err := gi.ensurePerson(repo, issue.Poster.UserName)
106 if err != nil {
107 return nil, err
108 }
109
110 giteaID := strconv.FormatInt(issue.Index, 10)
111
112 // resolve bug
113 b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
114 return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
115 excerpt.CreateMetadata[metaKeyGiteaID] == giteaID &&
116 excerpt.CreateMetadata[metaKeyGiteaBaseURL] == gi.conf[confKeyBaseURL] &&
117 excerpt.CreateMetadata[metaKeyGiteaOwner] == gi.conf[confKeyOwner] &&
118 excerpt.CreateMetadata[metaKeyGiteaProject] == gi.conf[confKeyProject]
119 })
120 if err == nil {
121 return b, nil
122 }
123 if err != bug.ErrBugNotExist {
124 return nil, err
125 }
126
127 // if bug was never imported, create bug
128 b, _, err = repo.NewBugRaw(
129 author,
130 issue.Created.Unix(),
131 text.CleanupOneLine(issue.Title),
132 text.Cleanup(issue.Body),
133 nil,
134 map[string]string{
135 core.MetaKeyOrigin: target,
136 metaKeyGiteaID: giteaID,
137 metaKeyGiteaOwner: gi.conf[confKeyOwner],
138 metaKeyGiteaProject: gi.conf[confKeyProject],
139 metaKeyGiteaBaseURL: gi.conf[confKeyBaseURL],
140 },
141 )
142
143 if err != nil {
144 return nil, err
145 }
146
147 // importing a new bug
148 gi.out <- core.NewImportBug(b.Id())
149
150 return b, nil
151}
152
153func (gi *giteaImporter) ensurePerson(repo *cache.RepoCache, loginName string) (*cache.IdentityCache, error) {
154 // Look first in the cache
155 i, err := repo.ResolveIdentityImmutableMetadata(metaKeyGiteaLogin, loginName)
156 if err == nil {
157 return i, nil
158 }
159 if entity.IsErrMultipleMatch(err) {
160 return nil, err
161 }
162
163 ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
164 defer cancel()
165 gi.client.SetContext(ctx)
166
167 user, _, err := gi.client.GetUserInfo(loginName)
168 if err != nil {
169 return nil, err
170 }
171
172 i, err = repo.NewIdentityRaw(
173 user.FullName,
174 user.Email,
175 user.UserName,
176 user.AvatarURL,
177 nil,
178 map[string]string{
179 // because Gitea
180 metaKeyGiteaLogin: user.UserName,
181 },
182 )
183 if err != nil {
184 return nil, err
185 }
186
187 gi.out <- core.NewImportIdentity(i.Id())
188 return i, nil
189}