1package bridgecmd
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "sync"
8 "time"
9
10 "github.com/araddon/dateparse"
11 "github.com/pkg/errors"
12 "github.com/spf13/cobra"
13
14 "github.com/MichaelMure/git-bug/bridge"
15 "github.com/MichaelMure/git-bug/bridge/core"
16 "github.com/MichaelMure/git-bug/commands/completion"
17 "github.com/MichaelMure/git-bug/commands/execenv"
18 "github.com/MichaelMure/git-bug/util/interrupt"
19)
20
21type bridgePullOptions struct {
22 importSince string
23 noResume bool
24}
25
26func newBridgePullCommand() *cobra.Command {
27 env := execenv.NewEnv()
28 options := bridgePullOptions{}
29
30 cmd := &cobra.Command{
31 Use: "pull [NAME]",
32 Short: "Pull updates from a remote bug tracker",
33 PreRunE: execenv.LoadBackend(env),
34 RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
35 return runBridgePull(env, options, args)
36 }),
37 Args: cobra.MaximumNArgs(1),
38 ValidArgsFunction: completion.Bridge(env),
39 }
40
41 flags := cmd.Flags()
42 flags.SortFlags = false
43
44 flags.BoolVarP(&options.noResume, "no-resume", "n", false, "force importing all bugs")
45 flags.StringVarP(&options.importSince, "since", "s", "", "import only bugs updated after the given date (ex: \"200h\" or \"june 2 2019\")")
46
47 return cmd
48}
49
50func runBridgePull(env *execenv.Env, opts bridgePullOptions, args []string) error {
51 if opts.noResume && opts.importSince != "" {
52 return fmt.Errorf("only one of --no-resume and --since flags should be used")
53 }
54
55 var b *core.Bridge
56 var err error
57
58 if len(args) == 0 {
59 b, err = bridge.DefaultBridge(env.Backend)
60 } else {
61 b, err = bridge.LoadBridge(env.Backend, args[0])
62 }
63
64 if err != nil {
65 return err
66 }
67
68 parentCtx := context.Background()
69 ctx, cancel := context.WithCancel(parentCtx)
70 defer cancel()
71
72 // buffered channel to avoid send block at the end
73 done := make(chan struct{}, 1)
74
75 var mu sync.Mutex
76 interruptCount := 0
77 interrupt.RegisterCleaner(func() error {
78 mu.Lock()
79 if interruptCount > 0 {
80 env.Err.Println("Received another interrupt before graceful stop, terminating...")
81 os.Exit(0)
82 }
83
84 interruptCount++
85 mu.Unlock()
86
87 env.Err.Println("Received interrupt signal, stopping the import...\n(Hit ctrl-c again to kill the process.)")
88
89 // send signal to stop the importer
90 cancel()
91
92 // block until importer gracefully shutdown
93 <-done
94 return nil
95 })
96
97 var events <-chan core.ImportResult
98 switch {
99 case opts.noResume:
100 events, err = b.ImportAllSince(ctx, time.Time{})
101 case opts.importSince != "":
102 since, err2 := parseSince(opts.importSince)
103 if err2 != nil {
104 return errors.Wrap(err2, "import time parsing")
105 }
106 events, err = b.ImportAllSince(ctx, since)
107 default:
108 events, err = b.ImportAll(ctx)
109 }
110
111 if err != nil {
112 return err
113 }
114
115 importedIssues := 0
116 importedIdentities := 0
117 for result := range events {
118 switch result.Event {
119 case core.ImportEventNothing:
120 // filtered
121
122 case core.ImportEventBug:
123 importedIssues++
124 env.Out.Println(result.String())
125
126 case core.ImportEventIdentity:
127 importedIdentities++
128 env.Out.Println(result.String())
129
130 case core.ImportEventError:
131 if result.Err != context.Canceled {
132 env.Out.Println(result.String())
133 }
134
135 default:
136 env.Out.Println(result.String())
137 }
138 }
139
140 env.Out.Printf("imported %d issues and %d identities with %s bridge\n", importedIssues, importedIdentities, b.Name)
141
142 // send done signal
143 close(done)
144
145 return nil
146}
147
148func parseSince(since string) (time.Time, error) {
149 duration, err := time.ParseDuration(since)
150 if err == nil {
151 return time.Now().Add(-duration), nil
152 }
153
154 return dateparse.ParseLocal(since)
155}