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(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}