bridge_pull.go

  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}