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