github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/bobgit/add.go (about) 1 package bobgit 2 3 import ( 4 "bufio" 5 "bytes" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/Benchkram/bob/bobgit/pathspec" 11 "github.com/Benchkram/bob/pkg/bobutil" 12 "github.com/Benchkram/bob/pkg/cmdutil" 13 "github.com/Benchkram/bob/pkg/usererror" 14 "github.com/Benchkram/errz" 15 ) 16 17 // pathspecItem stores a git pathspec with the repository 18 // path relative to the bob root 19 type pathspecItem struct { 20 pathspec string 21 repo string 22 } 23 24 // Add run git add commands by travsersing all the repositories 25 // inside the bob workspace. 26 // 27 // if target is provided "." it selects all modified files 28 // in all repos by running `git add . --dry-run`, then run 29 // `git add {filename}` for all selected files. 30 // 31 // else for subdirectory e.g. `subdir/.` it selectes all files 32 // on that subdir and also all repository under that subtree 33 // and run `git add --dry-run` followed by `git add {filename}` 34 // 35 // else for specific file under any repository on bob workspace 36 // select the specific repository and run `git add --dry-run` followed 37 // by `git add {filename}`. 38 // 39 // Run all the steps iterativley for multiple targets. 40 func Add(targets ...string) (err error) { 41 pathlist := []pathspecItem{} 42 43 for _, target := range targets { 44 defer errz.Recover(&err) 45 46 bobRoot, err := bobutil.FindBobRoot() 47 errz.Fatal(err) 48 49 target, err = convertPathRelativeToRoot(bobRoot, target) 50 if err != nil { 51 return usererror.Wrap(err) 52 } 53 54 err = os.Chdir(bobRoot) 55 errz.Fatal(err) 56 57 // Assure toplevel is a git repo 58 isGit, err := isGitRepo(bobRoot) 59 errz.Fatal(err) 60 if !isGit { 61 return usererror.Wrap(ErrCouldNotFindGitDir) 62 } 63 64 ps := pathspec.New(target) 65 66 // search for git repos inside bobRoot/. 67 allRepos, err := findRepos(bobRoot) 68 errz.Fatal(err) 69 70 filteredRepos := ps.SelectReposByPath(allRepos) 71 72 for _, name := range filteredRepos { 73 thistarget, err := ps.GetRelativePathspec(name) 74 errz.Fatal(err) 75 76 if name == "." { 77 name = strings.TrimSuffix(name, ".") 78 } 79 80 output, err := cmdutil.GitAddDry(name, thistarget) 81 if err != nil { 82 return usererror.Wrapm(err, "Failed to Add files to git.") 83 } 84 85 filenames := parseAddDryOutput(output) 86 87 for _, f := range filenames { 88 pathspecItem := pathspecItem{ 89 repo: name, 90 pathspec: f, 91 } 92 pathlist = append(pathlist, pathspecItem) 93 } 94 } 95 } 96 97 for _, pi := range pathlist { 98 err = cmdutil.GitAdd(pi.repo, pi.pathspec) 99 if err != nil { 100 return usererror.Wrapm(err, "Failed to Add files to git.") 101 } 102 } 103 104 return nil 105 } 106 107 // parseAddDryOutput parse the output from `git add --dry-run` 108 // and returns a list of filenames 109 // 110 // Example output: 111 // $ git add . --dry-run 112 // add 'bobgit/add.go' 113 // add 'bobgit/bobgit.go' 114 // add 'qq' 115 // 116 func parseAddDryOutput(buf []byte) (_ []string) { 117 fileNames := []string{} 118 119 scanner := bufio.NewScanner(bytes.NewBuffer(buf)) 120 for scanner.Scan() { 121 line := scanner.Text() 122 nameStart := strings.Index(line, "'") + 1 123 nameEnd := strings.Index(line[nameStart:], "'") + nameStart 124 fileName := line[nameStart:nameEnd] 125 fileNames = append(fileNames, fileName) 126 } 127 128 return fileNames 129 } 130 131 // convertPathRelativeToRoot returns the relative targetpath from 132 // the provided root. 133 // 134 // Example: `../sample/path` => `bobroot/sample/path`. 135 // 136 // also handles git pathspec features like `.` 137 // 138 // Example: `sample/.` => `bobroot/sample/.` 139 func convertPathRelativeToRoot(root string, target string) (string, error) { 140 dir, err := filepath.Abs(target) 141 errz.Fatal(err) 142 143 if dir == root { 144 return ".", nil 145 } 146 147 if !strings.HasPrefix(dir, root) { 148 return target, ErrOutsideBobWorkspace 149 } 150 relativepath, err := filepath.Rel(root, dir) 151 errz.Fatal(err) 152 153 if target == "." || target == "" || strings.HasSuffix(target, "/.") { 154 relativepath = relativepath + "/." 155 } 156 157 return relativepath, nil 158 }