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