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 }