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