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

     1  // Copyright 2019 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  	"bytes"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"go.fuchsia.dev/jiri"
    15  	"go.fuchsia.dev/jiri/cmdline"
    16  	"go.fuchsia.dev/jiri/project"
    17  )
    18  
    19  var cmdGenGitModule = &cmdline.Command{
    20  	Runner: jiri.RunnerFunc(runGenGitModule),
    21  	Name:   "generate-gitmodules",
    22  	Short:  "Create a .gitmodule and a .gitattributes files for git submodule repository",
    23  	Long: `
    24  The "jiri generate-gitmodules command captures the current project state and
    25  create a .gitmodules file and an optional .gitattributes file for building
    26  a git submodule based super repository.
    27  `,
    28  	ArgsName: "<.gitmodule path> [<.gitattributes path>]",
    29  	ArgsLong: `
    30  <.gitmodule path> is the path to the output .gitmodule file.
    31  <.gitattributes path> is the path to the output .gitattribute file, which is optional.`,
    32  }
    33  
    34  var genGitModuleFlags struct {
    35  	genScript    string
    36  	redirectRoot bool
    37  }
    38  
    39  func init() {
    40  	flags := &cmdGenGitModule.Flags
    41  	flags.StringVar(&genGitModuleFlags.genScript, "generate-script", "", "File to save generated git commands for seting up a superproject.")
    42  	flags.BoolVar(&genGitModuleFlags.redirectRoot, "redir-root", false, "When set to true, jiri will add the root repository as a submodule into {name}-mirror directory and create necessary setup commands in generated script.")
    43  }
    44  
    45  type projectTree struct {
    46  	project  *project.Project
    47  	children map[string]*projectTree
    48  }
    49  
    50  type projectTreeRoot struct {
    51  	root    *projectTree
    52  	dropped project.Projects
    53  }
    54  
    55  func runGenGitModule(jirix *jiri.X, args []string) error {
    56  	gitmodulesPath := ".gitmodules"
    57  	gitattributesPath := ""
    58  	if len(args) >= 1 {
    59  		gitmodulesPath = args[0]
    60  	}
    61  	if len(args) == 2 {
    62  		gitattributesPath = args[1]
    63  	}
    64  
    65  	if len(args) > 2 {
    66  		return jirix.UsageErrorf("unexpected number of arguments")
    67  	}
    68  
    69  	localProjects, err := project.LocalProjects(jirix, project.FullScan)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	return writeGitModules(jirix, localProjects, gitmodulesPath, gitattributesPath)
    74  }
    75  
    76  func writeGitModules(jirix *jiri.X, projects project.Projects, gitmodulesPath, gitattributesPath string) error {
    77  	projEntries, treeRoot, err := project.GenerateSubmoduleTree(jirix, projects)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	// Start creating .gitmodule and set up script.
    83  	var gitmoduleBuf bytes.Buffer
    84  	var commandBuf bytes.Buffer
    85  	var gitattributeBuf bytes.Buffer
    86  	commandBuf.WriteString("#!/bin/sh\n")
    87  
    88  	// Special hack for fuchsia.git
    89  	// When -redir-root is set to true, fuchsia.git will be added as submodule
    90  	// to fuchsia-mirror directory
    91  	reRootRepoName := ""
    92  	if genGitModuleFlags.redirectRoot {
    93  		// looking for root repository, there should be no more than 1
    94  		rIndex := -1
    95  		for i, v := range projEntries {
    96  			if v.Path == "." || v.Path == "" || v.Path == string(filepath.Separator) {
    97  				if rIndex == -1 {
    98  					rIndex = i
    99  				} else {
   100  					return fmt.Errorf("more than 1 project defined at path \".\", projects %+v:%+v", projEntries[rIndex], projEntries[i])
   101  				}
   102  			}
   103  		}
   104  		if rIndex != -1 {
   105  			v := projEntries[rIndex]
   106  			v.Name = v.Name + "-mirror"
   107  			v.Path = v.Name
   108  			reRootRepoName = v.Path
   109  			gitmoduleBuf.WriteString(moduleDecl(v))
   110  			gitmoduleBuf.WriteString("\n")
   111  			commandBuf.WriteString(commandDecl(v))
   112  			commandBuf.WriteString("\n")
   113  			if v.GitAttributes != "" {
   114  				gitattributeBuf.WriteString(attributeDecl(v))
   115  				gitattributeBuf.WriteString("\n")
   116  			}
   117  		}
   118  	}
   119  
   120  	for _, v := range projEntries {
   121  		if reRootRepoName != "" && reRootRepoName == v.Path {
   122  			return fmt.Errorf("path collision for root repo and project %+v", v)
   123  		}
   124  		if _, ok := treeRoot.Dropped[v.Key()]; ok {
   125  			jirix.Logger.Debugf("dropped project %+v", v)
   126  			continue
   127  		}
   128  		gitmoduleBuf.WriteString(moduleDecl(v))
   129  		gitmoduleBuf.WriteString("\n")
   130  		commandBuf.WriteString(commandDecl(v))
   131  		commandBuf.WriteString("\n")
   132  		if v.GitAttributes != "" {
   133  			gitattributeBuf.WriteString(attributeDecl(v))
   134  			gitattributeBuf.WriteString("\n")
   135  		}
   136  	}
   137  	jirix.Logger.Debugf("generated gitmodule content \n%v\n", gitmoduleBuf.String())
   138  	if err := os.WriteFile(gitmodulesPath, gitmoduleBuf.Bytes(), 0644); err != nil {
   139  		return err
   140  	}
   141  
   142  	if genGitModuleFlags.genScript != "" {
   143  		jirix.Logger.Debugf("generated set up script for gitmodule content \n%v\n", commandBuf.String())
   144  		if err := os.WriteFile(genGitModuleFlags.genScript, commandBuf.Bytes(), 0755); err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	if gitattributesPath != "" {
   150  		jirix.Logger.Debugf("generated gitattributes content \n%v\n", gitattributeBuf.String())
   151  		if err := os.WriteFile(gitattributesPath, gitattributeBuf.Bytes(), 0644); err != nil {
   152  			return err
   153  		}
   154  	}
   155  	return nil
   156  }
   157  
   158  func makePathRel(basepath, targpath string) (string, error) {
   159  	if filepath.IsAbs(targpath) {
   160  		relPath, err := filepath.Rel(basepath, targpath)
   161  		if err != nil {
   162  			return "", err
   163  		}
   164  		return relPath, nil
   165  	}
   166  	return targpath, nil
   167  }
   168  
   169  func moduleDecl(p project.Project) string {
   170  	tmpl := "[submodule \"%s\"]\n\tpath = %s\n\turl = %s"
   171  	return fmt.Sprintf(tmpl, p.Path, p.Path, p.Remote)
   172  }
   173  
   174  func commandDecl(p project.Project) string {
   175  	tmpl := "git update-index --add --cacheinfo 160000 %s \"%s\""
   176  	return fmt.Sprintf(tmpl, p.Revision, p.Path)
   177  }
   178  
   179  func attributeDecl(p project.Project) string {
   180  	tmpl := "%s %s"
   181  	attrs := strings.ReplaceAll(p.GitAttributes, ",", " ")
   182  	return fmt.Sprintf(tmpl, p.Path, strings.TrimSpace(attrs))
   183  }