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