You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: multi_tags_watcher.go
    path of file: ./repos/keel/trigger/poll
    
    the code of the file until where you have to start completion: package poll

import (
	"sort"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/keel-hq/keel/extension/credentialshelper"
	"github.com/keel-hq/keel/provider"
	"github.com/keel-hq/keel/registry"
	"github.com/keel-hq/keel/types"

	"github.com/prometheus/client_golang/prometheus"

	log "github.com/sirupsen/logrus"
)

// WatchRepositoryTagsJob - watch all tags
type WatchRepositoryTagsJob struct {
	providers      provider.Providers
	registryClient registry.Client
	details        *watchDetails

	// latests map[string]string // a map of prerelease tags and their corresponding latest versions
}

// NewWatchRepositoryTagsJob - new tags watcher job
func NewWatchRepositoryTagsJob(providers provider.Providers, registryClient registry.Client, details *watchDetails) *WatchRepositoryTagsJob {
	return &WatchRepositoryTagsJob{
		providers:      providers,
		registryClient: registryClient,
		details:        details,
		// latests:        details.trackedImage.SemverPreReleaseTags,
	}
}

// Run - main function to check schedule
func (j *WatchRepositoryTagsJob) Run() {
	j.details.mu.RLock()
	defer j.details.mu.RUnlock()

	reg := j.details.trackedImage.Image.Scheme() + "://" + j.details.trackedImage.Image.Registry()
	if j.details.latest == "" {
		j.details.latest = j.details.trackedImage.Image.Tag()
	}

	registryOpts := registry.Opts{
		Registry: reg,
		Name:     j.details.trackedImage.Image.ShortName(),
		Tag:      j.details.latest,
	}

	creds, err := credentials
    
func (j *WatchRepositoryTagsJob) computeEvents(tags []string) ([]types.Event, error) {
	trackedImages, err := j.providers.TrackedImages()
	if err != nil {
		return nil, err
	}

	events := []types.Event{}

	// Keep only semver tags, sorted desc (to optimize process)
	versions := semverSort(tags)

	for _, trackedImage := range getRelatedTrackedImages(j.details.trackedImage, trackedImages) {
		// Current version tag might not be a valid semver one
		currentVersion, invalidCurrentVersion := semver.NewVersion(trackedImage.Image.Tag())
		// matches, going through tags
		for _, version := range versions {
			if invalidCurrentVersion == nil && (currentVersion.GreaterThan(version) || currentVersion.Equal(version)) {
				// Current tag is a valid semver, and is bigger than currently tested one
				// -> we can stop now, nothing will be worth upgrading in the rest of the sorted list
				break
			}
			update, err := trackedImage.Policy.ShouldUpdate(trackedImage.Image.Tag(), version.Original())
			// log.WithFields(log.Fields{
			// 	"current_tag": j.details.trackedImage.Image.Tag(),
			// 	"image_name":  j.details.trackedImage.Image.Remote(),
			// }).Debug("trigger.poll.WatchRepositoryTagsJob: tag: ", version.Original(), "; update: ", update, "; err:", err)
			if err != nil {
				continue
			}
			if update && !exists(version.Original(), events) {
				event := types.Event{
					Repository: types.Repository{
						Name: j.details.trackedImage.Image.Repository(),
						Tag:  version.Original(),
					},
					TriggerName: types.TriggerTypePoll.String(),
				}
				events = append(events, event)
				// Only keep first match per image (should be the highest usable version)
				break
			}

		}

	}
	log.WithFields(log.Fields{
		"current_tag": j.details.trackedImage.Image.Tag(),
		"image_name":  j.details.trackedImage.Image.Remote(),
	}).Debug("trigger.poll.WatchRepositoryTagsJob: events: ", events)

	return events, nil
}