1package launchpad
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/MichaelMure/git-bug/bridge/core"
9 "github.com/MichaelMure/git-bug/bug"
10 "github.com/MichaelMure/git-bug/cache"
11 "github.com/MichaelMure/git-bug/entity"
12 "github.com/MichaelMure/git-bug/util/text"
13)
14
15type launchpadImporter struct {
16 conf core.Configuration
17}
18
19func (li *launchpadImporter) Init(_ context.Context, repo *cache.RepoCache, conf core.Configuration) error {
20 li.conf = conf
21 return nil
22}
23
24func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson) (*cache.IdentityCache, error) {
25 // Look first in the cache
26 i, err := repo.ResolveIdentityImmutableMetadata(metaKeyLaunchpadLogin, owner.Login)
27 if err == nil {
28 return i, nil
29 }
30 if entity.IsErrMultipleMatch(err) {
31 return nil, err
32 }
33
34 return repo.NewIdentityRaw(
35 owner.Name,
36 "",
37 owner.Login,
38 "",
39 nil,
40 map[string]string{
41 metaKeyLaunchpadLogin: owner.Login,
42 },
43 )
44}
45
46func (li *launchpadImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan core.ImportResult, error) {
47 out := make(chan core.ImportResult)
48 lpAPI := new(launchpadAPI)
49
50 err := lpAPI.Init()
51 if err != nil {
52 return nil, err
53 }
54
55 lpBugs, err := lpAPI.SearchTasks(ctx, li.conf["project"])
56 if err != nil {
57 return nil, err
58 }
59
60 go func() {
61 for _, lpBug := range lpBugs {
62 select {
63 case <-ctx.Done():
64 return
65 default:
66 lpBugID := fmt.Sprintf("%d", lpBug.ID)
67 b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
68 return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
69 excerpt.CreateMetadata[metaKeyLaunchpadID] == lpBugID
70 })
71 if err != nil && err != bug.ErrBugNotExist {
72 out <- core.NewImportError(err, entity.Id(lpBugID))
73 return
74 }
75
76 owner, err := li.ensurePerson(repo, lpBug.Owner)
77 if err != nil {
78 out <- core.NewImportError(err, entity.Id(lpBugID))
79 return
80 }
81
82 if err == bug.ErrBugNotExist {
83 createdAt, _ := time.Parse(time.RFC3339, lpBug.CreatedAt)
84 b, _, err = repo.NewBugRaw(
85 owner,
86 createdAt.Unix(),
87 text.CleanupOneLine(lpBug.Title),
88 text.Cleanup(lpBug.Description),
89 nil,
90 map[string]string{
91 core.MetaKeyOrigin: target,
92 metaKeyLaunchpadID: lpBugID,
93 },
94 )
95 if err != nil {
96 out <- core.NewImportError(err, entity.Id(lpBugID))
97 return
98 }
99
100 out <- core.NewImportBug(b.Id())
101
102 }
103
104 /* Handle messages */
105 if len(lpBug.Messages) == 0 {
106 return
107 }
108
109 // The Launchpad API returns the bug description as the first
110 // comment, so skip it.
111 for _, lpMessage := range lpBug.Messages[1:] {
112 _, err := b.ResolveOperationWithMetadata(metaKeyLaunchpadID, lpMessage.ID)
113 if err != nil && err != cache.ErrNoMatchingOp {
114 out <- core.NewImportError(err, entity.Id(lpMessage.ID))
115 return
116 }
117
118 // If this comment already exists, we are probably
119 // updating an existing bug. We do not want to duplicate
120 // the comments, so let us just skip this one.
121 // TODO: Can Launchpad comments be edited?
122 if err == nil {
123 continue
124 }
125
126 owner, err := li.ensurePerson(repo, lpMessage.Owner)
127 if err != nil {
128 out <- core.NewImportError(err, "")
129 return
130 }
131
132 // This is a new comment, we can add it.
133 createdAt, _ := time.Parse(time.RFC3339, lpMessage.CreatedAt)
134 op, err := b.AddCommentRaw(
135 owner,
136 createdAt.Unix(),
137 text.Cleanup(lpMessage.Content),
138 nil,
139 map[string]string{
140 metaKeyLaunchpadID: lpMessage.ID,
141 })
142 if err != nil {
143 out <- core.NewImportError(err, b.Id())
144 return
145 }
146
147 out <- core.NewImportComment(op.Id())
148 }
149
150 if !b.NeedCommit() {
151 out <- core.NewImportNothing(b.Id(), "no imported operation")
152 } else if err := b.Commit(); err != nil {
153 out <- core.NewImportError(err, "")
154 return
155 }
156 }
157 }
158 }()
159
160 return out, nil
161}