1package _select
2
3import (
4 "fmt"
5 "io"
6 "io/ioutil"
7 "os"
8 "path"
9
10 "github.com/pkg/errors"
11
12 "github.com/MichaelMure/git-bug/bug"
13 "github.com/MichaelMure/git-bug/cache"
14 "github.com/MichaelMure/git-bug/entity"
15 "github.com/MichaelMure/git-bug/repository"
16)
17
18const selectFile = "select"
19
20var ErrNoValidId = errors.New("you must provide a bug id or use the \"select\" command first")
21
22// ResolveBug first try to resolve a bug using the first argument of the command
23// line. If it fails, it fallback to the select mechanism.
24//
25// Returns:
26// - the bug if any
27// - the new list of command line arguments with the bug prefix removed if it
28// has been used
29// - an error if the process failed
30func ResolveBug(repo *cache.RepoCache, args []string) (*cache.BugCache, []string, error) {
31 // At first, try to use the first argument as a bug prefix
32 if len(args) > 0 {
33 b, err := repo.ResolveBugPrefix(args[0])
34
35 if err == nil {
36 return b, args[1:], nil
37 }
38
39 if err != bug.ErrBugNotExist {
40 return nil, nil, err
41 }
42 }
43
44 // first arg is not a valid bug prefix, we can safely use the preselected bug if any
45
46 b, err := selected(repo)
47
48 // selected bug is invalid
49 if err == bug.ErrBugNotExist {
50 // we clear the selected bug
51 err = Clear(repo)
52 if err != nil {
53 return nil, nil, err
54 }
55 return nil, nil, ErrNoValidId
56 }
57
58 // another error when reading the bug
59 if err != nil {
60 return nil, nil, err
61 }
62
63 // bug is successfully retrieved
64 if b != nil {
65 return b, args, nil
66 }
67
68 // no selected bug and no valid first argument
69 return nil, nil, ErrNoValidId
70}
71
72func ResolveComment(repo *cache.RepoCache, fullId string) (*cache.BugCache, entity.Id, error) {
73 bugId, _ := bug.SplitCommentId(fullId)
74 b, _, err := ResolveBug(repo, []string{bugId})
75 if err != nil {
76 return nil, entity.UnsetId, err
77 }
78
79 matching := make([]entity.Id, 0, 5)
80
81 for _, comment := range b.Snapshot().Comments {
82 if comment.Id().HasPrefix(fullId) {
83 matching = append(matching, comment.Id())
84 }
85 }
86
87 if len(matching) > 1 {
88 return nil, entity.UnsetId, entity.NewErrMultipleMatch("comment", matching)
89 } else if len(matching) == 0 {
90 return nil, entity.UnsetId, errors.New("comment doesn't exist")
91 }
92
93 return b, matching[0], nil
94}
95
96// Select will select a bug for future use
97func Select(repo *cache.RepoCache, id entity.Id) error {
98 selectPath := selectFilePath(repo)
99
100 f, err := os.OpenFile(selectPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
101 if err != nil {
102 return err
103 }
104
105 _, err = f.WriteString(id.String())
106 if err != nil {
107 return err
108 }
109
110 return f.Close()
111}
112
113// Clear will clear the selected bug, if any
114func Clear(repo *cache.RepoCache) error {
115 selectPath := selectFilePath(repo)
116
117 return os.Remove(selectPath)
118}
119
120func selected(repo *cache.RepoCache) (*cache.BugCache, error) {
121 selectPath := selectFilePath(repo)
122
123 f, err := os.Open(selectPath)
124 if err != nil {
125 if os.IsNotExist(err) {
126 return nil, nil
127 } else {
128 return nil, err
129 }
130 }
131
132 buf, err := ioutil.ReadAll(io.LimitReader(f, 100))
133 if err != nil {
134 return nil, err
135 }
136 if len(buf) == 100 {
137 return nil, fmt.Errorf("the select file should be < 100 bytes")
138 }
139
140 id := entity.Id(buf)
141 if err := id.Validate(); err != nil {
142 err = os.Remove(selectPath)
143 if err != nil {
144 return nil, errors.Wrap(err, "error while removing invalid select file")
145 }
146
147 return nil, fmt.Errorf("select file in invalid, removing it")
148 }
149
150 b, err := repo.ResolveBug(id)
151 if err != nil {
152 return nil, err
153 }
154
155 err = f.Close()
156 if err != nil {
157 return nil, err
158 }
159
160 return b, nil
161}
162
163func selectFilePath(repo repository.RepoCommon) string {
164 return path.Join(repo.GetPath(), "git-bug", selectFile)
165}