// CopyFileSetPerms copies from src to dst until either EOF is reached // on src or an error occurs. It will create the destination file with // the specified permissions and return the number of bytes written (int64) // and an error (nil if no error). // Note: if destination file exists it will be removed by this routine func CopyFileSetPerms(src, dst string, mode os.FileMode) (int64, error) { cleanSrc := filepath.Clean(src) cleanDst := filepath.Clean(dst) if cleanSrc == cleanDst { return 0, nil } sf, err := os.Open(cleanSrc) if err != nil { return 0, out.WrapErr(err, "Failed to open source file", 4004) } defer sf.Close() if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) { return 0, out.WrapErr(err, "Failed to clean/remove destination file", 4005) } oldUmask := syscall.Umask(0) defer syscall.Umask(oldUmask) df, err := os.OpenFile(cleanDst, syscall.O_CREAT|syscall.O_EXCL|syscall.O_APPEND|syscall.O_WRONLY, mode) if err != nil { return 0, out.WrapErr(err, "Failed to create destination file", 4006) } defer df.Close() bytes, err := io.Copy(df, sf) if err != nil { return bytes, out.WrapErr(err, "Failed to copy source file to destination", 4007) } return bytes, nil }
// Read will, given an io.Reader, attempt to scan in the codebase contents // and "fill out" the given Defn structure for you. What could // go wrong? If anything a non-nil error is returned. func (cb *Defn) Read(r io.Reader) error { codebaseMap := make(map[string]interface{}) decJSON := json.NewDecoder(r) err := decJSON.Decode(&codebaseMap) if err != nil { if serr, ok := err.(*json.SyntaxError); ok { return out.WrapErrf(err, 3001, "Failed to decode codebase JSON (Bad Char Offset: %v)", serr.Offset) } return out.WrapErr(err, "Failed to decode codebase JSON file", 3001) } config := &mapstructure.DecoderConfig{ WeaklyTypedInput: true, Result: cb, } decoder, err := mapstructure.NewDecoder(config) if err != nil { return out.WrapErr(err, "Failed to prepare codebase file decoder", 3002) } err = decoder.Decode(codebaseMap) if err != nil { return out.WrapErr(err, "Failed to decode codebase file contents", 3003) } // codebase definitions can be reduced in size if the person defining that // file uses variables to identify common repo references and such, lets // examine those vars and, if any, make sure we "expand" them so the codebase // definition is complete. Write() will "smart" subtitute the vars back. if err = cb.expandVarUse(); err != nil { return err } return nil }
// CopyFile copies from src to dst until either EOF is reached // on src or an error occurs. It verifies src exists and remove // the dst if it exists. func CopyFile(src, dst string) (int64, error) { cleanSrc := filepath.Clean(src) cleanDst := filepath.Clean(dst) if cleanSrc == cleanDst { return 0, nil } sf, err := os.Open(cleanSrc) if err != nil { return 0, out.WrapErr(err, "Failed to open source file", 4004) } defer sf.Close() if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) { return 0, out.WrapErr(err, "Failed to clean/remove destination file", 4005) } df, err := os.Create(cleanDst) if err != nil { return 0, out.WrapErr(err, "Failed to create destination file", 4006) } defer df.Close() bytes, err := io.Copy(df, sf) if err != nil { return bytes, out.WrapErr(err, "Failed to copy source file to destination", 4007) } return bytes, nil }
// GitUpdate performs a git fetch and merge to an existing checkout (ie: // a git pull). Params: // u (*GitUpdater): git upd struct, gives kind of update needed, stores cmds run // rev (Rev): optional; revision to update to (if given only 1 used) // Returns results (vcs cmds run, output) and any error that may have occurred func GitUpdate(u *GitUpdater, rev ...Rev) (Resulter, error) { // Perform required fetches optionally with pulls as well as handling // more specific fetches on single refs (or deletion of refs)... has // some handling of mirror/bare clones vs local clones and for std // clones can do rebase type pulls (if that section of the routine is // reached). results := newResults() path, _, err := u.Exists(LocalPath) if err != nil && path == "" { return results, out.WrapErr(err, "Existence check failed on local git clone", 4509) } if u.refs != nil { return gitUpdateRefs(u) } runOpt := "-C" runDir := u.LocalRepoPath() var result *Result if u.mirror { result, err = run(gitTool, runOpt, runDir, "remote", "update", "--prune", u.RemoteRepoName()) } else { result, err = run(gitTool, runOpt, runDir, "fetch", u.RemoteRepoName()) } results.add(result) if err != nil { return results, err } bareRepo := false gitDir, workTree, err := findGitDirs(runDir) if err != nil { return nil, err } if gitDir == runDir && workTree == "" { bareRepo = true } if !u.mirror && !bareRepo { // if not a mirror and a regular clone // Try and run a git pull to do the merge|rebase op rebaseStr := "" switch u.rebase { case RebaseFalse: rebaseStr = "--rebase=false" case RebaseTrue: rebaseStr = "--rebase=true" case RebasePreserve: rebaseStr = "--rebase=preserve" default: // likely RebaseUser, meaning don't provide any rebase opt } var pullResult *Result if rev == nil || (rev != nil && rev[0] == "") { pullResult, err = run(gitTool, runOpt, runDir, "pull", rebaseStr, u.RemoteRepoName()) } else { // if user asks for a specific version on pull, use that pullResult, err = run(gitTool, runOpt, runDir, "pull", rebaseStr, u.RemoteRepoName(), string(rev[0])) } results.add(pullResult) } return results, err }
// CreateIfNotExists creates a file or a directory only if it does not already exist. func CreateIfNotExists(path string, isDir bool) error { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { if isDir { return os.MkdirAll(path, 0755) } if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return out.WrapErr(err, "Failed to make directory path", 4001) } f, err := os.OpenFile(path, os.O_CREATE, 0755) if err != nil { return out.WrapErr(err, "Failed to create requested file", 4002) } f.Close() } } return nil }
// Exists checks if given file/dir exists. Note: for more specific checks on a // file or dir existence see dir.Exists() and file.Exists(). func Exists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, out.WrapErr(err, "Stat on path failed unexpectedly", 4000) }
// GitHookInstall is used to install a hook into a git clone, params: // h (*GitHookMgr): the hook mgr structure (find location of repo/etc) // path (string): where is the hook we wish to install? // name (string): what is the "git name" for the hook? // link (bool): is hook a symlink to hookPath, or full copy/install? // Returns full path/name to git hook installed along w/any error seen func GitHookInstall(h *GitHookMgr, path, name string, link bool) (string, error) { repoPath, _, err := h.Exists(LocalPath) hookInstallPath := "" if err == nil && repoPath != "" { // if the local path exists... hookInstallPath = filepath.Join(repoPath, ".git", "hooks", name) if isBareRepo(repoPath) { hookInstallPath = filepath.Join(repoPath, "hooks", name) } if there, err := file.Exists(hookInstallPath); err == nil && there { err = os.Remove(hookInstallPath) if err != nil { return "", out.WrapErr(err, "Failed to remove previously installed hook", 4510) } } if there, err := file.Exists(path); err != nil || !there { if err != nil { return "", out.WrapErr(err, "Hook install failed checking source hook existence", 4511) } return "", out.NewErrf(4512, "Hook install failed, hook source path does not exist:\n path: %s", path) } oldUmask := syscall.Umask(0) defer syscall.Umask(oldUmask) if link { // if symlink desired, try and create that err = os.Symlink(path, hookInstallPath) if err != nil { err = out.WrapErrf(err, 4513, "Hook install failed, failed to set up symlink:\n linktgt: %s\n link: %s\n", path, hookInstallPath) } } else { // otherwise try and copy in the hook file _, err = file.CopyFileSetPerms(path, hookInstallPath, 0775) if err != nil { err = out.WrapErrf(err, 4514, "Hook install failed, failed to copy hook file:\n hook source path %s\n hook install path: %s\n", path, hookInstallPath) } } } return hookInstallPath, err }
// FindDirInOrAbove will look for a given directory in or above the given // starting dir (iit will travese "up" the filesystem and examine parent // directories to see if they contain the given directory). If the findDir // dir is found then the dir it's found in will be returned, else "" (any // unexpected error will come back in the error return parameter) func FindDirInOrAbove(startDir string, findDir string) (string, error) { fullPath := filepath.Join(startDir, findDir) exists, err := Exists(fullPath) if err != nil { return "", out.WrapErr(err, "Problem checking directory existence", 4003) } if exists { return startDir, nil } baseDir := filepath.Dir(startDir) if baseDir == "." || (len(baseDir) == 1 && baseDir[0] == filepath.Separator) { return "", nil } return FindDirInOrAbove(baseDir, findDir) }
// Matches returns true if file matches any of the patterns // and isn't excluded by any of the subsequent patterns. func Matches(file string, patterns []string) (bool, error) { file = filepath.Clean(file) if file == "." { // Don't let them exclude everything, kind of silly. return false, nil } patterns, patDirs, _, err := CleanPatterns(patterns) if err != nil { return false, out.WrapErr(err, "Unable to clean all patterns", 4010) } return OptimizedMatches(file, patterns, patDirs) }
// Exists checks if given dir exists, if you want to check for a *file* // use the file.Exists() routine or if you want to check for both file and // directory use the path.Exists() routine. func Exists(dir string) (bool, error) { exists, err := path.Exists(dir) if err != nil { // error already wrapped by path.Exists() return exists, err } if exists { fileinfo, err := os.Stat(dir) if err != nil { return false, out.WrapErr(err, "Failed to stat directory, unable to verify existence", 4011) } if !fileinfo.IsDir() { exists = false err = out.NewErr("Item is not a directory hence directory existence check failed", 4012) } } return exists, err }
// RootDirFind gets the workspaces root dir (if there is one), note that this will // not use any "cached" values and it will store the result in globs (viper) // under the "wkspcRootDir" key (you can access that with any upper/lower case // as viper is case insensitive). func RootDirFind(path ...string) (string, error) { startDir := "" var err error if path == nil || path[0] == "" { startDir, err = os.Getwd() if err != nil { return "", out.WrapErr(err, "Unable to find the workspace root directory (get current working dir failed)", 4100) } } else { startDir = path[0] } rootDir, err := dir.FindDirInOrAbove(startDir, globs.GetString("wkspcMetaDirName")) if err == nil { // Cache the root dir in "globs" (viper) memory if no error globs.Set("wkspcRootDir", rootDir) } return rootDir, err }
// Exists checks if given file exists, if you want to check for a directory // use the dir.Exists() routine or if you want to check for both file and // directory use the path.Exists() routine. func Exists(file string) (bool, error) { exists, err := path.Exists(file) if err != nil { // error already wrapped by path.Exists() return exists, err } if exists { var fileinfo os.FileInfo fileinfo, err = os.Stat(file) if err != nil { return false, out.WrapErr(err, "Failed to stat file, unable to verify existence", 4013) } if fileinfo.IsDir() { exists = false err = out.NewErr("Item is a directory hence the file existence check failed", 4014) } } return exists, err }
// Get basically tries to find, get and read a codebase if it can, it // returns a codebase definition and any error's that may have occurred func (cb *Defn) Get(codebaseVerSel string) error { //eriknow: normally we would do any smart discovery of the code base // definition file here via 'findCodebase()' or something which // would be able to get it via local file (support RCS versioned), // or, more likely, the file is in a git clone with the codebase // definition in it which may also have devline definitions and // possibly code as well // - the name could be a partial or full URL, eg: // "dvln" or "github.com/dvln/dvln" or "http://github.com/dvln/dvln" // or "file:://path/to/somefile.json" or "/path/to/somefile.json" or // might use OS friendly paths like "\path\to\somefile.toml" // - if we think it's in a VCS we need to bring that clone into the // workspace if we have a workspace, if we don't have a workspace // we should bring it into a temp location or into ~/.dvlncfg/codebase/ // or something like that (flattened full path?)... we can "smart local clone" // this pkg into the workspace with hard links or whatever if later "get" of it // normally Exists() would do this part and try and get us a "real" name for the // codebase (full URL/etc... but the name should be simple in the file even if // the "full" name is a URL and such) fileName := fmt.Sprintf("/Users/brady/.dvlncfg/%s.codebase", codebaseVerSel) cbFile, locality, err := cb.Exists(fileName) if locality == NonExistent { cb.Name = "generated" cb.Desc = "Dynamically generated development line" return err // note, err may be nil as it's ok for codebase not to exist } //FIXME: erik: normally we would check and see if locality was 'RemoteURL' as well // and, if so, use the Get() routine to bring it down to our workspace // if we have one and to a tmp location if not (wsroot should be set // in viper if we have a workspace, note that if it's nested it may // be a child workspace root but that should be normal I think, consider) fileContents, err := ioutil.ReadFile(cbFile) if err != nil { msg := fmt.Sprintf("Codebase file \"%s\" read failed\n", cbFile) return out.WrapErr(err, msg, 3000) } err = cb.Read(bytes.NewReader(fileContents)) return err }
// OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go. // It will assume that the inputs have been preprocessed and therefore the function // doen't need to do as much error checking and clean-up. This was done to avoid // repeating these steps on each file being checked during the archive process. // The more generic fileutils.Matches() can't make these assumptions. func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { matched := false parentPath := filepath.Dir(file) parentPathDirs := strings.Split(parentPath, "/") for i, pattern := range patterns { negative := false if exclusion(pattern) { negative = true pattern = pattern[1:] } match, err := filepath.Match(pattern, file) if err != nil { return false, out.WrapErr(err, "Optimized matching, failed to match pattern", 4008) } if !match && parentPath != "." { // Check to see if the pattern matches one of our parent dirs. if len(patDirs[i]) <= len(parentPathDirs) { match, _ = filepath.Match(strings.Join(patDirs[i], "/"), strings.Join(parentPathDirs[:len(patDirs[i])], "/")) } } if match { matched = !negative } } if matched { out.Debugf("Skipping excluded path: %s", file) } return matched, nil }