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}