go.fuchsia.dev/jiri@v0.0.0-20240502161911-b66513b29486/cmd/jiri/import.go (about)

     1  // Copyright 2015 The Vanadium Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"encoding/json"
     9  	"encoding/xml"
    10  	"fmt"
    11  	"os"
    12  
    13  	"go.fuchsia.dev/jiri"
    14  	"go.fuchsia.dev/jiri/cmdline"
    15  	"go.fuchsia.dev/jiri/project"
    16  )
    17  
    18  var (
    19  	// Flags for configuring project attributes for remote imports.
    20  	flagImportName, flagImportRemoteBranch, flagImportRoot string
    21  	// Flags for controlling the behavior of the command.
    22  	flagImportOverwrite  bool
    23  	flagImportOut        string
    24  	flagImportDelete     bool
    25  	flagImportRevision   string
    26  	flagImportList       bool
    27  	flagImportJsonOutput string
    28  )
    29  
    30  func init() {
    31  	cmdImport.Flags.StringVar(&flagImportName, "name", "manifest", `The name of the remote manifest project.`)
    32  	cmdImport.Flags.StringVar(&flagImportRemoteBranch, "remote-branch", "main", `The branch of the remote manifest project to track, without the leading "origin/".`)
    33  	cmdImport.Flags.StringVar(&flagImportRevision, "revision", "", `Revision to check out for the remote.`)
    34  	cmdImport.Flags.StringVar(&flagImportRoot, "root", "", `Root to store the manifest project locally.`)
    35  
    36  	cmdImport.Flags.BoolVar(&flagImportOverwrite, "overwrite", false, `Write a new .jiri_manifest file with the given specification.  If it already exists, the existing content will be ignored and the file will be overwritten.`)
    37  	cmdImport.Flags.StringVar(&flagImportOut, "out", "", `The output file.  Uses <root>/.jiri_manifest if unspecified.  Uses stdout if set to "-".`)
    38  	cmdImport.Flags.BoolVar(&flagImportDelete, "delete", false, `Delete existing import. Import is matched using <manifest>, <remote> and name. <remote> is optional.`)
    39  	cmdImport.Flags.BoolVar(&flagImportList, "list", false, `List all the imports from .jiri_manifest. This flag doesn't accept any arguments. -json-out flag can be used to specify json output file.`)
    40  	cmdImport.Flags.StringVar(&flagImportJsonOutput, "json-output", "", `Json output file from -list flag.`)
    41  }
    42  
    43  var cmdImport = &cmdline.Command{
    44  	Runner: jiri.RunnerFunc(runImport),
    45  	Name:   "import",
    46  	Short:  "Adds imports to .jiri_manifest file",
    47  	Long: `
    48  Command "import" adds imports to the [root]/.jiri_manifest file, which specifies
    49  manifest information for the jiri tool.  The file is created if it doesn't
    50  already exist, otherwise additional imports are added to the existing file.
    51  
    52  An <import> element is added to the manifest representing a remote manifest
    53  import.  The manifest file path is relative to the root directory of the remote
    54  import repository.
    55  
    56  Example:
    57    $ jiri import myfile https://foo.com/bar.git
    58  
    59  Run "jiri help manifest" for details on manifests.
    60  `,
    61  	ArgsName: "<manifest> <remote>",
    62  	ArgsLong: `
    63  <manifest> specifies the manifest file to use.
    64  
    65  <remote> specifies the remote manifest repository.
    66  `,
    67  }
    68  
    69  func isFile(file string) (bool, error) {
    70  	fileInfo, err := os.Stat(file)
    71  	if err != nil {
    72  		if os.IsNotExist(err) {
    73  			return false, nil
    74  		}
    75  		return false, err
    76  	}
    77  	return !fileInfo.IsDir(), nil
    78  }
    79  
    80  type Import struct {
    81  	Manifest     string `json:"manifest"`
    82  	Name         string `json:"name"`
    83  	Remote       string `json:"remote"`
    84  	Revision     string `json:"revision"`
    85  	RemoteBranch string `json:"remoteBranch"`
    86  	Root         string `json:"root"`
    87  }
    88  
    89  func getListObject(imports []project.Import) []Import {
    90  	arr := []Import{}
    91  	for _, i := range imports {
    92  		i.RemoveDefaults()
    93  		obj := Import{
    94  			Manifest:     i.Manifest,
    95  			Name:         i.Name,
    96  			Remote:       i.Remote,
    97  			Revision:     i.Revision,
    98  			RemoteBranch: i.RemoteBranch,
    99  			Root:         i.Root,
   100  		}
   101  		arr = append(arr, obj)
   102  	}
   103  	return arr
   104  }
   105  
   106  func runImport(jirix *jiri.X, args []string) error {
   107  	if flagImportDelete && flagImportOverwrite {
   108  		return jirix.UsageErrorf("cannot use -delete and -overwrite together")
   109  	}
   110  	if flagImportList && flagImportOverwrite {
   111  		return jirix.UsageErrorf("cannot use -list and -overwrite together")
   112  	}
   113  	if flagImportDelete && flagImportList {
   114  		return jirix.UsageErrorf("cannot use -delete and -list together")
   115  	}
   116  
   117  	if flagImportList && len(args) != 0 {
   118  		return jirix.UsageErrorf("wrong number of arguments with list flag: %v", len(args))
   119  	}
   120  	if flagImportDelete && len(args) != 1 && len(args) != 2 {
   121  		return jirix.UsageErrorf("wrong number of arguments with delete flag")
   122  	} else if !flagImportDelete && !flagImportList && len(args) != 2 {
   123  		return jirix.UsageErrorf("wrong number of arguments")
   124  	}
   125  
   126  	// Initialize manifest.
   127  	var manifest *project.Manifest
   128  	manifestExists, err := isFile(jirix.JiriManifestFile())
   129  	if err != nil {
   130  		return err
   131  	}
   132  	if !flagImportOverwrite && manifestExists {
   133  		m, err := project.ManifestFromFile(jirix, jirix.JiriManifestFile())
   134  		if err != nil {
   135  			return err
   136  		}
   137  		manifest = m
   138  	}
   139  	if manifest == nil {
   140  		manifest = &project.Manifest{}
   141  	}
   142  
   143  	if flagImportList {
   144  		imports := getListObject(manifest.Imports)
   145  		if flagImportJsonOutput == "" {
   146  			for _, i := range imports {
   147  				fmt.Printf("* import\t%s\n", i.Name)
   148  				fmt.Printf("  Manifest:\t%s\n", i.Manifest)
   149  				fmt.Printf("  Remote:\t%s\n", i.Remote)
   150  				fmt.Printf("  Revision:\t%s\n", i.Revision)
   151  				fmt.Printf("  RemoteBranch:\t%s\n", i.RemoteBranch)
   152  				fmt.Printf("  Root:\t%s\n", i.Root)
   153  			}
   154  			return nil
   155  		} else {
   156  			out, err := json.MarshalIndent(imports, "", "  ")
   157  			if err != nil {
   158  				return fmt.Errorf("failed to serialize JSON output: %s\n", err)
   159  			}
   160  			return os.WriteFile(flagImportJsonOutput, out, 0644)
   161  		}
   162  	}
   163  
   164  	if flagImportDelete {
   165  		var tempImports []project.Import
   166  		deletedImports := make(map[string]project.Import)
   167  		for _, imp := range manifest.Imports {
   168  			if imp.Manifest == args[0] && imp.Name == flagImportName {
   169  				match := true
   170  				if len(args) == 2 {
   171  					match = false
   172  					if imp.Remote == args[1] {
   173  						match = true
   174  					}
   175  				}
   176  				if match {
   177  					deletedImports[imp.Name+"~"+imp.Manifest+"~"+imp.Remote] = imp
   178  					continue
   179  				}
   180  			}
   181  			tempImports = append(tempImports, imp)
   182  		}
   183  		if len(deletedImports) > 1 {
   184  			return fmt.Errorf("More than 1 import meets your criteria. Please provide remote.")
   185  		} else if len(deletedImports) == 1 {
   186  			var data []byte
   187  			for _, i := range deletedImports {
   188  				data, err = xml.Marshal(i)
   189  				if err != nil {
   190  					return err
   191  				}
   192  				break
   193  			}
   194  			jirix.Logger.Infof("Deleted one import:\n%s", string(data))
   195  		}
   196  		manifest.Imports = tempImports
   197  	} else {
   198  		for _, imp := range manifest.Imports {
   199  			if imp.Manifest == args[0] && imp.Remote == args[1] && imp.Name == flagImportName {
   200  				//Already exists, skip
   201  				jirix.Logger.Debugf("Skip import. Duplicate entry")
   202  				return nil
   203  			}
   204  		}
   205  		// There's not much error checking when writing the .jiri_manifest file;
   206  		// errors will be reported when "jiri update" is run.
   207  		manifest.Imports = append(manifest.Imports, project.Import{
   208  			Manifest:     args[0],
   209  			Name:         flagImportName,
   210  			Remote:       args[1],
   211  			RemoteBranch: flagImportRemoteBranch,
   212  			Revision:     flagImportRevision,
   213  			Root:         flagImportRoot,
   214  		})
   215  	}
   216  
   217  	// Write output to stdout or file.
   218  	outFile := flagImportOut
   219  	if outFile == "" {
   220  		outFile = jirix.JiriManifestFile()
   221  	}
   222  	if outFile == "-" {
   223  		bytes, err := manifest.ToBytes()
   224  		if err != nil {
   225  			return err
   226  		}
   227  		_, err = os.Stdout.Write(bytes)
   228  		return err
   229  	}
   230  	return manifest.ToFile(jirix, outFile)
   231  }