| // Copyright 2019 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "go.fuchsia.dev/jiri" |
| "go.fuchsia.dev/jiri/cmdline" |
| "go.fuchsia.dev/jiri/project" |
| ) |
| |
| var cmdGenGitModule = &cmdline.Command{ |
| Runner: jiri.RunnerFunc(runGenGitModule), |
| Name: "generate-gitmodules", |
| Short: "Create a .gitmodule and a .gitattributes files for git submodule repository", |
| Long: ` |
| The "jiri generate-gitmodules command captures the current project state and |
| create a .gitmodules file and an optional .gitattributes file for building |
| a git submodule based super repository. |
| `, |
| ArgsName: "<.gitmodule path> [<.gitattributes path>]", |
| ArgsLong: ` |
| <.gitmodule path> is the path to the output .gitmodule file. |
| <.gitattributes path> is the path to the output .gitattribute file, which is optional.`, |
| } |
| |
| var genGitModuleFlags struct { |
| genScript string |
| redirectRoot bool |
| } |
| |
| func init() { |
| flags := &cmdGenGitModule.Flags |
| flags.StringVar(&genGitModuleFlags.genScript, "generate-script", "", "File to save generated git commands for seting up a superproject.") |
| 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.") |
| } |
| |
| type projectTree struct { |
| project *project.Project |
| children map[string]*projectTree |
| } |
| |
| type projectTreeRoot struct { |
| root *projectTree |
| dropped project.Projects |
| } |
| |
| func runGenGitModule(jirix *jiri.X, args []string) error { |
| gitmodulesPath := ".gitmodules" |
| gitattributesPath := "" |
| if len(args) >= 1 { |
| gitmodulesPath = args[0] |
| } |
| if len(args) == 2 { |
| gitattributesPath = args[1] |
| } |
| |
| if len(args) > 2 { |
| return jirix.UsageErrorf("unexpected number of arguments") |
| } |
| |
| localProjects, err := project.LocalProjects(jirix, project.FullScan) |
| if err != nil { |
| return err |
| } |
| return writeGitModules(jirix, localProjects, gitmodulesPath, gitattributesPath) |
| } |
| |
| func writeGitModules(jirix *jiri.X, projects project.Projects, gitmodulesPath, gitattributesPath string) error { |
| projEntries, treeRoot, err := project.GenerateSubmoduleTree(jirix, projects) |
| if err != nil { |
| return err |
| } |
| |
| // Start creating .gitmodule and set up script. |
| var gitmoduleBuf bytes.Buffer |
| var commandBuf bytes.Buffer |
| var gitattributeBuf bytes.Buffer |
| commandBuf.WriteString("#!/bin/sh\n") |
| |
| // Special hack for fuchsia.git |
| // When -redir-root is set to true, fuchsia.git will be added as submodule |
| // to fuchsia-mirror directory |
| reRootRepoName := "" |
| if genGitModuleFlags.redirectRoot { |
| // looking for root repository, there should be no more than 1 |
| rIndex := -1 |
| for i, v := range projEntries { |
| if v.Path == "." || v.Path == "" || v.Path == string(filepath.Separator) { |
| if rIndex == -1 { |
| rIndex = i |
| } else { |
| return fmt.Errorf("more than 1 project defined at path \".\", projects %+v:%+v", projEntries[rIndex], projEntries[i]) |
| } |
| } |
| } |
| if rIndex != -1 { |
| v := projEntries[rIndex] |
| v.Name = v.Name + "-mirror" |
| v.Path = v.Name |
| reRootRepoName = v.Path |
| gitmoduleBuf.WriteString(moduleDecl(v)) |
| gitmoduleBuf.WriteString("\n") |
| commandBuf.WriteString(commandDecl(v)) |
| commandBuf.WriteString("\n") |
| if v.GitAttributes != "" { |
| gitattributeBuf.WriteString(attributeDecl(v)) |
| gitattributeBuf.WriteString("\n") |
| } |
| } |
| } |
| |
| for _, v := range projEntries { |
| if reRootRepoName != "" && reRootRepoName == v.Path { |
| return fmt.Errorf("path collision for root repo and project %+v", v) |
| } |
| if _, ok := treeRoot.Dropped[v.Key()]; ok { |
| jirix.Logger.Debugf("dropped project %+v", v) |
| continue |
| } |
| gitmoduleBuf.WriteString(moduleDecl(v)) |
| gitmoduleBuf.WriteString("\n") |
| commandBuf.WriteString(commandDecl(v)) |
| commandBuf.WriteString("\n") |
| if v.GitAttributes != "" { |
| gitattributeBuf.WriteString(attributeDecl(v)) |
| gitattributeBuf.WriteString("\n") |
| } |
| } |
| jirix.Logger.Debugf("generated gitmodule content \n%v\n", gitmoduleBuf.String()) |
| if err := os.WriteFile(gitmodulesPath, gitmoduleBuf.Bytes(), 0644); err != nil { |
| return err |
| } |
| |
| if genGitModuleFlags.genScript != "" { |
| jirix.Logger.Debugf("generated set up script for gitmodule content \n%v\n", commandBuf.String()) |
| if err := os.WriteFile(genGitModuleFlags.genScript, commandBuf.Bytes(), 0755); err != nil { |
| return err |
| } |
| } |
| |
| if gitattributesPath != "" { |
| jirix.Logger.Debugf("generated gitattributes content \n%v\n", gitattributeBuf.String()) |
| if err := os.WriteFile(gitattributesPath, gitattributeBuf.Bytes(), 0644); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func makePathRel(basepath, targpath string) (string, error) { |
| if filepath.IsAbs(targpath) { |
| relPath, err := filepath.Rel(basepath, targpath) |
| if err != nil { |
| return "", err |
| } |
| return relPath, nil |
| } |
| return targpath, nil |
| } |
| |
| func moduleDecl(p project.Project) string { |
| lines := []string{fmt.Sprintf("[submodule \"%s\"]", p.Path)} |
| if p.Name != "" { |
| lines = append(lines, "name = "+p.Name) |
| } |
| lines = append(lines, "path = "+p.Path) |
| lines = append(lines, "url = "+p.Remote) |
| return strings.Join(lines, "\n\t") |
| } |
| |
| func commandDecl(p project.Project) string { |
| tmpl := "git update-index --add --cacheinfo 160000 %s \"%s\"" |
| return fmt.Sprintf(tmpl, p.Revision, p.Path) |
| } |
| |
| func attributeDecl(p project.Project) string { |
| tmpl := "%s %s" |
| attrs := strings.ReplaceAll(p.GitAttributes, ",", " ") |
| return fmt.Sprintf(tmpl, p.Path, strings.TrimSpace(attrs)) |
| } |