Ejemplo n.º 1
0
func runUninstall(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	if err := initCommand(ctx, args); err != nil {
		return err
	}
	if allFlag && targetFlag.IsSet() {
		return fmt.Errorf("don't specify a target in conjunction with --all")
	}
	if allFlag {
		for _, name := range args {
			profile := profiles.LookupProfile(name)
			mgr := profiles.LookupManager(name)
			if profile == nil || mgr == nil {
				continue
			}
			mgr.SetRoot(rootDir)
			for _, target := range profile.Targets() {
				if err := mgr.Uninstall(ctx, *target); err != nil {
					logResult(ctx, "Uninstall", mgr, *target, err)
					return err
				}
				logResult(ctx, "Uninstall", mgr, *target, nil)
			}
		}
	} else {
		applyCommand(args, env, ctx, targetFlag,
			func(mgr profiles.Manager, ctx *tool.Context, target profiles.Target) error {
				err := mgr.Uninstall(ctx, target)
				logResult(ctx, "Uninstall", mgr, target, err)
				return err
			})
	}
	return profiles.Write(ctx, manifestFlag)
}
Ejemplo n.º 2
0
func runRebuild(env *cmdline.Env, args []string) (e error) {
	ctx := tool.NewContextFromEnv(env)
	_, tools, err := project.ReadManifest(ctx)
	if err != nil {
		return err
	}

	// Create a temporary directory in which tools will be built.
	tmpDir, err := ctx.Run().TempDir("", "tmp-jiri-rebuild")
	if err != nil {
		return fmt.Errorf("TempDir() failed: %v", err)
	}

	// Make sure we cleanup the temp directory.
	defer collect.Error(func() error { return ctx.Run().RemoveAll(tmpDir) }, &e)

	// Paranoid sanity checking.
	if _, ok := tools[project.JiriName]; !ok {
		return fmt.Errorf("tool %q not found", project.JiriName)
	}

	// Build and install tools.
	if err := project.BuildTools(ctx, tools, tmpDir); err != nil {
		return err
	}
	return project.InstallTools(ctx, tmpDir)
}
Ejemplo n.º 3
0
Archivo: cl.go Proyecto: 4shome/go.jiri
func runCLNew(env *cmdline.Env, args []string) error {
	if got, want := len(args), 1; got != want {
		return env.UsageErrorf("unexpected number of arguments: got %v, want %v", got, want)
	}
	ctx := tool.NewContextFromEnv(env)
	return newCL(ctx, args)
}
Ejemplo n.º 4
0
func runInstall(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	if err := initCommand(ctx, args); err != nil {
		return err
	}
	names := []string{}
	if len(args) == 0 {
		for _, name := range profiles.Managers() {
			names = append(names, name)
		}
	}
	for _, name := range args {
		if p := profiles.LookupProfileTarget(name, targetFlag); p != nil {
			fmt.Fprintf(ctx.Stdout(), "%v %v is already installed as %v\n", name, targetFlag, p)
			continue
		}
		names = append(names, name)
	}
	if err := applyCommand(names, env, ctx, targetFlag,
		func(mgr profiles.Manager, ctx *tool.Context, target profiles.Target) error {
			err := mgr.Install(ctx, target)
			logResult(ctx, "Install:", mgr, target, err)
			return err
		}); err != nil {
		return err
	}
	return profiles.Write(ctx, manifestFlag)
}
Ejemplo n.º 5
0
func runRecreate(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	if err := profiles.Read(ctx, manifestFlag); err != nil {
		fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err)
		return err
	}
	profileNames := args
	if len(args) == 0 {
		profileNames = profiles.Profiles()
	}
	prefix := "jiri v23-profile install"
	for _, name := range profileNames {
		profile := profiles.LookupProfile(name)
		if profile == nil {
			return fmt.Errorf("Profile %v is not installed", name)
		}
		for _, target := range profile.Targets() {
			fmt.Fprintf(ctx.Stdout(), "%s --target=%s", prefix, target)
			cmdEnv := target.CommandLineEnv()
			if len(cmdEnv.Vars) > 0 {
				fmt.Fprintf(ctx.Stdout(), " --env=\"%s\"", strings.Join(cmdEnv.Vars, ","))
			}
			fmt.Fprintf(ctx.Stdout(), " %s\n", name)
		}
	}
	return nil
}
Ejemplo n.º 6
0
Archivo: cl.go Proyecto: 4shome/go.jiri
func runCLCleanup(env *cmdline.Env, args []string) error {
	if len(args) == 0 {
		return env.UsageErrorf("cleanup requires at least one argument")
	}
	ctx := tool.NewContextFromEnv(env)
	return cleanupCL(ctx, args)
}
Ejemplo n.º 7
0
// NewX returns a new execution environment, given a cmdline env.
// It also prepends $JIRI_ROOT/.jiri_root/bin to the PATH.
func NewX(env *cmdline.Env) (*X, error) {
	ctx := tool.NewContextFromEnv(env)
	root, err := findJiriRoot(ctx.Timer())
	if err != nil {
		return nil, err
	}
	x := &X{
		Context: ctx,
		Root:    root,
		Usage:   env.UsageErrorf,
	}
	if ctx.Env()[PreservePathEnv] == "" {
		// Prepend $JIRI_ROOT/.jiri_root/bin to the PATH, so execing a binary will
		// invoke the one in that directory, if it exists.  This is crucial for jiri
		// subcommands, where we want to invoke the binary that jiri installed, not
		// whatever is in the user's PATH.
		//
		// Note that we must modify the actual os env variable with os.SetEnv and
		// also the ctx.env, so that execing a binary through the os/exec package
		// and with ctx.Run both have the correct behavior.
		newPath := envvar.PrependUniqueToken(ctx.Env()["PATH"], string(os.PathListSeparator), x.BinDir())
		ctx.Env()["PATH"] = newPath
		if err := os.Setenv("PATH", newPath); err != nil {
			return nil, err
		}
	}
	return x, nil
}
Ejemplo n.º 8
0
func runList(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	if showManifestFlag {
		data, err := ctx.Run().ReadFile(manifestFlag)
		if err != nil {
			return err
		}
		fmt.Fprintln(ctx.Stdout(), string(data))
		return nil
	}
	if verboseFlag {
		fmt.Fprintf(ctx.Stdout(), "Manifest: %s\n", manifestFlag)
	}
	if availableFlag {
		if verboseFlag {
			fmt.Fprintf(ctx.Stdout(), "Available Profiles:\n")
			for _, name := range profiles.Managers() {
				mgr := profiles.LookupManager(name)
				vi := mgr.VersionInfo()
				fmt.Fprintf(ctx.Stdout(), "%s: versions: %s - %s\n", name, vi.Default(), strings.Join(vi.Supported(), " "))
			}
		} else {
			fmt.Fprintf(ctx.Stdout(), "%s\n", strings.Join(profiles.Managers(), ", "))
		}
	}
	if err := profiles.Read(ctx, manifestFlag); err != nil {
		fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err)
		return err
	}
	profileNames := args
	if len(args) == 0 {
		profileNames = profiles.Profiles()
	}
	availableNames := []string{}
	for _, name := range profileNames {
		if profiles.LookupProfile(name) != nil {
			availableNames = append(availableNames, name)
		}
	}
	if verboseFlag {
		fmt.Fprintf(ctx.Stdout(), "Installed Profiles: ")
		fmt.Fprintf(ctx.Stdout(), "%s\n", strings.Join(profiles.Profiles(), ", "))
		for _, name := range availableNames {
			profile := profiles.LookupProfile(name)
			fmt.Fprintf(ctx.Stdout(), "Profile: %s @ %s\n", profile.Name, profile.Root)
			for _, target := range profile.Targets() {
				fmt.Fprintf(ctx.Stdout(), "\t%s\n", target.DebugString())
			}
		}
	} else {
		for _, name := range availableNames {
			profile := profiles.LookupProfile(name)
			for _, target := range profile.Targets() {
				fmt.Fprintf(ctx.Stdout(), "%s %s\n", name, target)
			}
		}
	}
	return nil
}
Ejemplo n.º 9
0
func runProjectShellPrompt(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)

	states, err := project.GetProjectStates(ctx, checkDirtyFlag)
	if err != nil {
		return err
	}
	names := []string{}
	for name := range states {
		names = append(names, name)
	}
	sort.Strings(names)

	// Get the name of the current project.
	currentProjectName, err := project.CurrentProjectName(ctx)
	if err != nil {
		return err
	}
	var statuses []string
	for _, name := range names {
		state := states[name]
		status := ""
		if checkDirtyFlag {
			if state.HasUncommitted {
				status += "*"
			}
			if state.HasUntracked {
				status += "%"
			}
		}
		short := state.CurrentBranch + status
		long := filepath.Base(name) + ":" + short
		if name == currentProjectName {
			if showNameFlag {
				statuses = append([]string{long}, statuses...)
			} else {
				statuses = append([]string{short}, statuses...)
			}
		} else {
			pristine := state.CurrentBranch == "master"
			if checkDirtyFlag {
				pristine = pristine && !state.HasUncommitted && !state.HasUntracked
			}
			if !pristine {
				statuses = append(statuses, long)
			}
		}
	}
	fmt.Println(strings.Join(statuses, ","))
	return nil
}
Ejemplo n.º 10
0
// runProjectPoll generates a description of changes that exist
// remotely but do not exist locally.
func runProjectPoll(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	projectSet := map[string]struct{}{}
	if len(args) > 0 {
		config, err := util.LoadConfig(ctx)
		if err != nil {
			return err
		}
		// Compute a map from tests to projects that can change the
		// outcome of the test.
		testProjects := map[string][]string{}
		for _, project := range config.Projects() {
			for _, test := range config.ProjectTests([]string{project}) {
				testProjects[test] = append(testProjects[test], project)
			}
		}
		for _, arg := range args {
			projects, ok := testProjects[arg]
			if !ok {
				return fmt.Errorf("failed to find any projects for test %q", arg)
			}
			set.String.Union(projectSet, set.String.FromSlice(projects))
		}
	}
	update, err := project.PollProjects(ctx, projectSet)
	if err != nil {
		return err
	}

	// Remove projects with empty changes.
	for project := range update {
		if changes := update[project]; len(changes) == 0 {
			delete(update, project)
		}
	}

	// Print update if it is not empty.
	if len(update) > 0 {
		bytes, err := json.MarshalIndent(update, "", "  ")
		if err != nil {
			return fmt.Errorf("MarshalIndent() failed: %v", err)
		}
		fmt.Fprintf(env.Stdout, "%s\n", bytes)
	}
	return nil
}
Ejemplo n.º 11
0
func runEnv(env *cmdline.Env, args []string) error {
	if len(profileFlag) == 0 {
		return fmt.Errorf("no profile was specified using --profile")
	}
	ctx := tool.NewContextFromEnv(env)
	if err := profiles.Read(ctx, manifestFlag); err != nil {
		return fmt.Errorf("Failed to read manifest: %v", err)
	}
	profile := profiles.LookupProfile(profileFlag)
	if profile == nil {
		return fmt.Errorf("profile %q is not installed", profileFlag)
	}
	target := profiles.FindTarget(profile.Targets(), &targetFlag)
	if target == nil {
		return fmt.Errorf("target %q is not installed for profile %q", targetFlag, profileFlag)
	}
	vars := envvar.SliceToMap(target.Env.Vars)
	buf := bytes.Buffer{}
	if len(args) == 0 {
		for k, v := range vars {
			buf.WriteString(fmt.Sprintf("%s=%q ", k, v))
		}
		for k, fn := range pseudoVariables {
			buf.WriteString(fmt.Sprintf("%s=%q ", k, fn(target)))
		}
	} else {
		for _, arg := range args {
			name := strings.TrimSuffix(arg, "=")
			trimmed := name != arg
			for k, fn := range pseudoVariables {
				if k == name {
					buf.WriteString(expr(k, fn(target), trimmed))
				}
			}
			for k, v := range vars {
				if k == name {
					buf.WriteString(expr(k, v, trimmed))
				}
			}
		}
	}
	fmt.Fprintf(ctx.Stdout(), strings.TrimSuffix(buf.String(), " ")+"\n")
	return nil
}
Ejemplo n.º 12
0
func runSnapshotCreate(env *cmdline.Env, args []string) error {
	if len(args) != 1 {
		return env.UsageErrorf("unexpected number of arguments")
	}
	label := args[0]
	ctx := tool.NewContextFromEnv(env)
	if err := checkSnapshotDir(ctx); err != nil {
		return err
	}
	snapshotDir, err := getSnapshotDir()
	if err != nil {
		return err
	}
	snapshotFile := filepath.Join(snapshotDir, "labels", label, time.Now().Format(timeFormatFlag))
	// Either atomically create a new snapshot that captures the project
	// state and push the changes to the remote repository (if
	// applicable), or fail with no effect.
	createFn := func() error {
		revision, err := ctx.Git().CurrentRevision()
		if err != nil {
			return err
		}
		if err := createSnapshot(ctx, snapshotDir, snapshotFile, label); err != nil {
			// Clean up on all errors.
			ctx.Git().Reset(revision)
			ctx.Git().RemoveUntrackedFiles()
			return err
		}
		return nil
	}

	// Execute the above function in the snapshot directory.
	p := project.Project{
		Path:     snapshotDir,
		Protocol: "git",
		Revision: "HEAD",
	}
	if err := project.ApplyToLocalMaster(ctx, project.Projects{p.Name: p}, createFn); err != nil {
		return err
	}
	return nil
}
Ejemplo n.º 13
0
func runInfo(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	profileNames := args
	if len(args) == 0 {
		profileNames = profiles.Managers()
	}
	_, width, err := textutil.TerminalSize()
	if err != nil {
		width = 80
	}
	w := textutil.NewUTF8LineWriter(ctx.Stdout(), width)
	defer w.Flush()
	for _, name := range profileNames {
		mgr := profiles.LookupManager(name)
		if mgr == nil {
			return fmt.Errorf("profile %q is not available", name)
		}
		fmt.Fprintf(w, "%s: %s\n\n", name, mgr.Info())
	}
	return nil
}
Ejemplo n.º 14
0
func runUpdate(env *cmdline.Env, _ []string) error {
	ctx := tool.NewContextFromEnv(env)

	// Create a snapshot of the current state of all projects and
	// write it to the $JIRI_ROOT/.update_history folder.
	root, err := project.JiriRoot()
	if err != nil {
		return err
	}
	snapshotFile := filepath.Join(root, ".update_history", time.Now().Format(time.RFC3339))
	if err := project.CreateSnapshot(ctx, snapshotFile); err != nil {
		return err
	}

	// Update all projects to their latest version.
	// Attempt <attemptsFlag> times before failing.
	updateFn := func() error {
		return project.UpdateUniverse(ctx, gcFlag)
	}
	return retry.Function(ctx, updateFn, retry.AttemptsOpt(attemptsFlag))
}
Ejemplo n.º 15
0
Archivo: cl.go Proyecto: 4shome/go.jiri
// runCLMail is a wrapper that sets up and runs a review instance.
func runCLMail(env *cmdline.Env, _ []string) error {
	ctx := tool.NewContextFromEnv(env)

	// Sanity checks for the <presubmitFlag> flag.
	if !checkPresubmitFlag() {
		return env.UsageErrorf("invalid value for the -presubmit flag. Valid values: %s.",
			strings.Join(gerrit.PresubmitTestTypes(), ","))
	}

	host := hostFlag
	if host == "" {
		var err error
		if host, err = project.GerritHost(ctx); err != nil {
			return err
		}
	}

	// Create and run the review.

	review, err := newReview(ctx, gerrit.CLOpts{
		Autosubmit:   autosubmitFlag,
		Ccs:          parseEmails(ccsFlag),
		Draft:        draftFlag,
		Edit:         editFlag,
		Host:         host,
		Presubmit:    gerrit.PresubmitTestType(presubmitFlag),
		RemoteBranch: remoteBranchFlag,
		Reviewers:    parseEmails(reviewersFlag),
		Verify:       verifyFlag,
	})
	if err != nil {
		return err
	}
	if confirmed, err := review.confirmFlagChanges(); err != nil {
		return err
	} else if !confirmed {
		return nil
	}
	return review.run()
}
Ejemplo n.º 16
0
func runProjectClean(env *cmdline.Env, args []string) (e error) {
	ctx := tool.NewContextFromEnv(env)
	localProjects, err := project.LocalProjects(ctx, project.FullScan)
	if err != nil {
		return err
	}
	projects := map[string]project.Project{}
	if len(args) > 0 {
		for _, arg := range args {
			if p, ok := localProjects[arg]; ok {
				projects[p.Name] = p
			} else {
				fmt.Fprintf(ctx.Stderr(), "Local project %q not found.\n", p.Name)
			}
		}
	} else {
		projects = localProjects
	}
	if err := project.CleanupProjects(ctx, projects, cleanupBranchesFlag); err != nil {
		return err
	}
	return nil
}
Ejemplo n.º 17
0
// runProjectList generates a listing of local projects.
func runProjectList(env *cmdline.Env, _ []string) error {
	ctx := tool.NewContextFromEnv(env)
	states, err := project.GetProjectStates(ctx, noPristineFlag)
	if err != nil {
		return err
	}
	names := []string{}
	for name := range states {
		names = append(names, name)
	}
	sort.Strings(names)

	for _, name := range names {
		state := states[name]
		if noPristineFlag {
			pristine := len(state.Branches) == 1 && state.CurrentBranch == "master" && !state.HasUncommitted && !state.HasUntracked
			if pristine {
				continue
			}
		}
		fmt.Fprintf(ctx.Stdout(), "project=%q path=%q\n", path.Base(name), state.Project.Path)
		if branchesFlag {
			for _, branch := range state.Branches {
				s := "  "
				if branch.Name == state.CurrentBranch {
					s += "* "
				}
				s += branch.Name
				if branch.HasGerritMessage {
					s += " (exported to gerrit)"
				}
				fmt.Fprintf(ctx.Stdout(), "%v\n", s)
			}
		}
	}
	return nil
}
Ejemplo n.º 18
0
func runCleanup(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	if len(args) == 0 {
		args = profiles.Managers()
	}
	if err := initCommand(ctx, args); err != nil {
		return err
	}
	for _, n := range args {
		mgr := profiles.LookupManager(n)
		vi := mgr.VersionInfo()
		profile := profiles.LookupProfile(n)
		for _, target := range profile.Targets() {
			if vi.IsOlderThanDefault(target.Version()) {
				err := mgr.Uninstall(ctx, *target)
				logResult(ctx, "Cleanup", mgr, *target, err)
				if err != nil {
					return err
				}
			}
		}
	}
	return profiles.Write(ctx, manifestFlag)
}
Ejemplo n.º 19
0
func runUpdate(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	if len(args) == 0 {
		args = profiles.Managers()
	}
	if err := initCommand(ctx, args); err != nil {
		return err
	}
	for _, n := range args {
		mgr := profiles.LookupManager(n)
		profile := profiles.LookupProfile(n)
		if profile == nil {
			continue
		}
		vi := mgr.VersionInfo()
		mgr.SetRoot(rootDir)
		for _, target := range profile.Targets() {
			if vi.IsNewerThanDefault(target.Version()) {
				if verboseFlag {
					fmt.Fprintf(ctx.Stdout(), "Updating %s %s from %q to %s\n", n, target, target.Version(), vi)
				}
				target.SetVersion(vi.Default())
				err := mgr.Install(ctx, *target)
				logResult(ctx, "Update", mgr, *target, err)
				if err != nil {
					return err
				}
			} else {
				if verboseFlag {
					fmt.Fprintf(ctx.Stdout(), "%s %s at %q is up to date(%s)\n", n, target, target.Version(), vi)
				}
			}
		}
	}
	return profiles.Write(ctx, manifestFlag)
}
Ejemplo n.º 20
0
func runContributors(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)

	projects, err := project.LocalProjects(ctx, project.FastScan)
	if err != nil {
		return err
	}
	projectNames := map[string]struct{}{}
	if len(args) != 0 {
		projectNames = set.String.FromSlice(args)
	} else {
		for name, _ := range projects {
			projectNames[name] = struct{}{}
		}
	}

	aliases, err := loadAliases(ctx)
	if err != nil {
		return err
	}
	contributors := map[string]*contributor{}
	for name, _ := range projectNames {
		project, ok := projects[name]
		if !ok {
			continue
		}
		if err := ctx.Run().Chdir(project.Path); err != nil {
			return err
		}
		switch project.Protocol {
		case "git":
			lines, err := listCommitters(ctx)
			if err != nil {
				return err
			}
			for _, line := range lines {
				matches := contributorRE.FindStringSubmatch(line)
				if got, want := len(matches), 4; got != want {
					return fmt.Errorf("unexpected length of %v: got %v, want %v", matches, got, want)
				}
				count, err := strconv.Atoi(strings.TrimSpace(matches[1]))
				if err != nil {
					return fmt.Errorf("Atoi(%v) failed: %v", strings.TrimSpace(matches[1]), err)
				}
				c := &contributor{
					count: count,
					email: strings.TrimSpace(matches[3]),
					name:  strings.TrimSpace(matches[2]),
				}
				if c.email == "*****@*****.**" || c.email == "*****@*****.**" {
					continue
				}
				c.email, c.name = canonicalize(aliases, c.email, c.name)
				if existing, ok := contributors[c.name]; ok {
					existing.count += c.count
				} else {
					contributors[c.name] = c
				}
			}
		}
	}
	names := []string{}
	for name, _ := range contributors {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		c := contributors[name]
		if countFlag {
			fmt.Fprintf(env.Stdout, "%4d ", c.count)
		}
		fmt.Fprintf(env.Stdout, "%v <%v>\n", c.name, c.email)
	}
	return nil
}
Ejemplo n.º 21
0
Archivo: cl.go Proyecto: 4shome/go.jiri
func runCLSync(env *cmdline.Env, _ []string) error {
	ctx := tool.NewContextFromEnv(env)
	return syncCL(ctx)
}
Ejemplo n.º 22
0
func runSnapshotList(env *cmdline.Env, args []string) error {
	ctx := tool.NewContextFromEnv(env)
	if err := checkSnapshotDir(ctx); err != nil {
		return err
	}

	snapshotDir, err := getSnapshotDir()
	if err != nil {
		return err
	}
	if len(args) == 0 {
		// Identify all known snapshot labels, using a
		// heuristic that looks for all symbolic links <foo>
		// in the snapshot directory that point to a file in
		// the "labels/<foo>" subdirectory of the snapshot
		// directory.
		fileInfoList, err := ioutil.ReadDir(snapshotDir)
		if err != nil {
			return fmt.Errorf("ReadDir(%v) failed: %v", snapshotDir, err)
		}
		for _, fileInfo := range fileInfoList {
			if fileInfo.Mode()&os.ModeSymlink != 0 {
				path := filepath.Join(snapshotDir, fileInfo.Name())
				dst, err := filepath.EvalSymlinks(path)
				if err != nil {
					return fmt.Errorf("EvalSymlinks(%v) failed: %v", path, err)
				}
				if strings.HasSuffix(filepath.Dir(dst), filepath.Join("labels", fileInfo.Name())) {
					args = append(args, fileInfo.Name())
				}
			}
		}
	}

	// Check that all labels exist.
	failed := false
	for _, label := range args {
		labelDir := filepath.Join(snapshotDir, "labels", label)
		if _, err := ctx.Run().Stat(labelDir); err != nil {
			if !os.IsNotExist(err) {
				return err
			}
			failed = true
			fmt.Fprintf(env.Stderr, "snapshot label %q not found", label)
		}
	}
	if failed {
		return cmdline.ErrExitCode(2)
	}

	// Print snapshots for all labels.
	sort.Strings(args)
	for _, label := range args {
		// Scan the snapshot directory "labels/<label>" printing
		// all snapshots.
		labelDir := filepath.Join(snapshotDir, "labels", label)
		fileInfoList, err := ioutil.ReadDir(labelDir)
		if err != nil {
			return fmt.Errorf("ReadDir(%v) failed: %v", labelDir, err)
		}
		fmt.Fprintf(env.Stdout, "snapshots of label %q:\n", label)
		for _, fileInfo := range fileInfoList {
			fmt.Fprintf(env.Stdout, "  %v\n", fileInfo.Name())
		}
	}
	return nil
}