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