// Saves all updated/new files to index, so they are available for push/pull. // Saves new file stats & current checksum of the file shown as new/changed. func Commit(index *veb.Index, log *veb.Log) error { defer log.Un(log.Trace(COMMIT)) var timer veb.Timer timer.Start() // check for changes files := make(chan veb.IndexEntry, CHAN_SIZE) go index.Check(files) // start handler pool working on files updates := make(chan veb.IndexEntry, CHAN_SIZE) done := make(chan int, MAX_HANDLERS) for i := 0; i < MAX_HANDLERS; i++ { go func() { for f := range files { // calculate checksum hash err := veb.Xsum(&f, log) if err != nil { log.Err().Println("checksum for verify failed:", err) } updates <- f } done <- 1 }() } // done listener go func() { for i := 0; i < MAX_HANDLERS; i++ { <-done } close(updates) }() // update index var retVal error = nil numCommits := 0 numErrors := 0 first := true for f := range updates { err := index.Update(&f) if err != nil { log.Err().Println("index update failed:", err) fmt.Println("Error: Couldn't commit", f.Path, ":", err) retVal = fmt.Errorf("veb commit failed") numErrors++ } else { if first { fmt.Println("----------------") fmt.Println("Committed files:") fmt.Println("----------------") first = false } fmt.Println(INDENT_F, f.Path) numCommits++ } } // save index once everything's done index.Save() // info timer.Stop() fmt.Println("\nsummary:", numCommits, "commits,", numErrors, "errors in", timer.Duration()) log.Info().Printf("%s (%d commits, %d errors) took %v\n", COMMIT, numCommits, numErrors, timer.Duration()) return retVal }
// Compares local index against remote index, then copies the differing files // to the remote location if remote doesn't have same checksum. // Updates remote's index with the new file information after each file success, // but doesn't /save/ remote's index to disk until finished. func Push(local *veb.Index, log *veb.Log) error { defer log.Un(log.Trace(PUSH)) var timer veb.Timer timer.Start() // open remote's index if local.Remote == "" { return fmt.Errorf("No remote veb repository. Use 'veb remote' to set one.") } remote, err := veb.Load(local.Remote, log) // TODO: have log indicate local vs remote if err != nil { return fmt.Errorf("veb could not load remote index: %v", err) } // get new/changed files for local & remote // we'll ignore these, as they haven't been committed locIgnore := make(chan veb.IndexEntry, CHAN_SIZE) remIgnore := make(chan veb.IndexEntry, CHAN_SIZE) go local.Check(locIgnore) go remote.Check(remIgnore) // notify user of ignored files cmt := true cmtMsg := func() { if cmt { fmt.Println("use 'veb status' to check new/changed files") fmt.Println("use 'veb commit' to add new/changed files to repository") cmt = false } } first := true locFilter := make(map[string]bool) remFilter := make(map[string]bool) for f := range locIgnore { if first { cmtMsg() fmt.Println("\n--------------------") fmt.Println("LOCAL ignored files:") fmt.Println("--------------------") first = false } fmt.Println(INDENT_F, f.Path) // filename locFilter[f.Path] = true // add to filter } first = true for f := range remIgnore { if first { cmtMsg() fmt.Println("\n---------------------") fmt.Println("REMOTE ignored files:") fmt.Println("---------------------") first = false } fmt.Println(INDENT_F, f.Path) // filename remFilter[f.Path] = true // add to filter } // make list of files to check files := make(chan veb.IndexEntry, CHAN_SIZE) numIgnored := 0 go func() { for p, f := range local.Files { // ignore if it's one of the new/changed files _, skipL := locFilter[p] _, skipR := remFilter[p] if skipL || skipR { numIgnored++ } else { files <- f } } close(files) }() // send files to remote if xsums differ done := make(chan int, MAX_HANDLERS) updates := make(chan veb.IndexEntry, CHAN_SIZE) errored := make(chan veb.IndexEntry, CHAN_SIZE) numErrored := 0 numPushed := 0 numNoChange := 0 for i := 0; i < MAX_HANDLERS; i++ { go func() { for f := range files { // compare checksum hashes _, ok := remote.Files[f.Path] // does file exist in remote yet? if !ok || !bytes.Equal(f.Xsum, remote.Files[f.Path].Xsum) { // TODO: verify f.Xsum == local file's actual xsum // - don't want corrupted files getting across. err := pushFile(local.Root, remote.Root, f, log) if err != nil { // notify of error, but continue with rest of files // TODO: get the error out too errored <- f numErrored++ } else { // save entry so index can be updated updates <- f numPushed++ } } else { numNoChange++ } } done <- 1 }() } // listeners var retVal error = nil numListeners := 3 quit := make(chan int, numListeners) go func() { for i := 0; i < MAX_HANDLERS; i++ { <-done } close(updates) close(errored) quit <- 1 }() go func() { for f := range errored { // clear status line w/ 80 spaces & carriage return fmt.Printf("\r%sError: could not push to remote: %s\n", " \r", f.Path) if retVal == nil { retVal = fmt.Errorf("error transferring files to remote") } } quit <- 1 }() go func() { first := true for f := range updates { if first { fmt.Printf("%s%s\n%s\n%s\n", "\r \r", "\n-------------", "Pushed files:", "-------------") first = false } // clear status line w/ 80 spaces & carriage return fmt.Printf("\r%s%s %s\n", " \r", INDENT_F, f.Path) remote.Update(&f) } quit <- 1 }() // print status while waiting for everyone to finish for len(quit) < numListeners { fmt.Printf("\rstatus: %4d ignored, %4d errors, %4d pushed, %4d unchanged", numIgnored, numErrored, numPushed, numNoChange) } // save remote index's updates remote.Save() // print outro timer.Stop() fmt.Println("\r ") fmt.Printf("status: %4d ignored, %4d errors, %4d pushed, %4d unchanged in %v\n", numIgnored, numErrored, numPushed, numNoChange, timer.Duration()) // info log timer.Stop() log.Info().Printf("%s (%d ignored, %d errors, %d pushed, %d unchanged) took %v\n", PUSH, numIgnored, numErrored, numPushed, numNoChange, timer.Duration()) return retVal }
// Check for updated/new files in repo, then nicely print out results. // Doesn't check file content (that's saved for verify). This is just // to /quickly/ find new or modified files via file.Lstat(). func Status(index *veb.Index, log *veb.Log) error { defer log.Un(log.Trace(STATUS)) var timer veb.Timer timer.Start() // check for changes files := make(chan veb.IndexEntry, CHAN_SIZE) go index.Check(files) // parse into new vs changed newFiles := make([]string, 0) changedFiles := make([]string, 0) var noTime time.Time for f := range files { // new file? if f.Name == "" && f.Size == 0 && f.Mode == 0 && f.ModTime == noTime { // new file. newFiles = append(newFiles, f.Path) } else { // changed file. changedFiles = append(changedFiles, f.Path) } } // print new files if len(newFiles) > 0 { fmt.Println("----------") fmt.Println("New files:") fmt.Println("----------") for _, f := range newFiles { // print file name fmt.Println(INDENT_F, f) // get stats for file info line fi, err := os.Stat(f) if err != nil { log.Err().Println(err) // print file info oops fmt.Println(INDENT_I, "could not get file info") } else { // print file info size := ByteSize(fi.Size()) fmt.Printf("%s %s, modified on (%v)\n", INDENT_I, size, fi.ModTime()) } fmt.Printf("\n") } fmt.Printf("\n") } // print changed files if len(changedFiles) > 0 { fmt.Println("--------------") fmt.Println("Changed files:") fmt.Println("--------------") for _, f := range changedFiles { // print file name fmt.Println(INDENT_F, f) // get stats for file info line fi, err := os.Stat(f) if err != nil { log.Err().Println(err) // print file info oops fmt.Println(INDENT_I, "could not get file info") } else { // figure out filesize curSize := ByteSize(fi.Size()) prevSize := ByteSize(index.Files[f].Size) sizeChange := curSize - prevSize direction := "increased" if sizeChange < 0 { direction = "decreased" sizeChange = -sizeChange // absolute value } sanity := false // print size change // e.g. // - filesize decreased 4.00MB (6.02GB -> 6.01GB) if sizeChange != 0 { fmt.Printf("%s filesize %s %s (%s -> %s)\n", INDENT_I, direction, sizeChange, prevSize, curSize) sanity = true } // print mtime if index.Files[f].ModTime != fi.ModTime() { fmt.Printf("%s modified on (%v)\n", INDENT_I, fi.ModTime()) sanity = true } // print mode if index.Files[f].Mode != fi.Mode() { fmt.Printf("%s file mode changed (%v -> %v)\n", INDENT_I, index.Files[f].Mode, fi.Mode()) sanity = true } // sanity check & snark if !sanity { fmt.Printf("%s ...well /something/ changed. Dunno what. *shrugs*\n", INDENT_I) } } fmt.Printf("\n") } fmt.Printf("\n") } // print outro if len(changedFiles) == 0 && len(newFiles) == 0 { fmt.Println("No changes or new files.") } else { fmt.Println("MAKE SURE CHANGED FILES ARE THINGS YOU'VE ACTUALLY CHANGED") fmt.Println(" (use 'veb fix <file>' if a file has been corrupted in this repository)") fmt.Println(" (use 'veb push', 'veb pull', or 'veb sync' to commit changed/new files)") } timer.Stop() fmt.Printf("\nsummary: %d new, %d changed (%v)\n", len(newFiles), len(changedFiles), timer.Duration()) log.Info().Printf("%s (%d new, %d changed) took %v\n", STATUS, len(newFiles), len(changedFiles), timer.Duration()) return nil }