bridge_pull.go

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