%PDF- %PDF-
Direktori : /home/waritko/go/src/github.com/odeke-em/drive/src/ |
Current File : //home/waritko/go/src/github.com/odeke-em/drive/src/diff.go |
// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package drive import ( "fmt" "io" "io/ioutil" "math/rand" "os" "os/exec" "strings" ) // MaxFileSize is the max number of bytes we // can accept for diffing (Arbitrary value) const MaxFileSize = 50 * 1024 * 1024 var Ruler = strings.Repeat("*", 4) const ( DiffNone = 1 << iota DiffUnified ) type diffSt struct { change *Change diffProgPath string cwd string mask int printRuler bool // skipContentCheck when set prevents content // diffing, but only time differences. skipContentCheck bool // baseLocal when set uses local as the base // otherwise remote is used as the base. baseLocal bool } func (d diffSt) unified() bool { return (d.mask & DiffUnified) != 0 } func (g *Commands) Diff() (err error) { var cl []*Change spin := g.playabler() spin.play() defer spin.stop() clashes := []*Change{} for _, relToRootPath := range g.opts.Sources { fsPath := g.context.AbsPathOf(relToRootPath) ccl, cclashes, cErr := g.changeListResolve(relToRootPath, fsPath, true) // TODO: Show the conflicts if any clashes = append(clashes, cclashes...) if cErr != nil { if cErr != ErrClashesDetected { return cErr } else { err = combineErrors(err, cErr) } } if len(ccl) > 0 { cl = append(cl, ccl...) } } if !g.opts.IgnoreNameClashes && len(clashes) >= 1 { warnClashesPersist(g.log, clashes) if err != nil { return err } return ErrClashesDetected } spin.stop() var diffUtilPath string diffUtilPath, err = exec.LookPath("diff") if err != nil { return } dst := diffSt{ diffProgPath: diffUtilPath, cwd: ".", mask: g.opts.TypeMask, printRuler: len(cl) > 1, baseLocal: g.opts.BaseLocal, } metaPtr := g.opts.Meta if metaPtr != nil { meta := *metaPtr _, dst.skipContentCheck = meta[SkipContentCheckKey] } for _, c := range cl { dst.change = c dErr := g.perDiff(dst) if dErr != nil { g.log.LogErrln(dErr) } } return } func (g *Commands) perDiff(dSt diffSt) (err error) { change := dSt.change diffProgPath, cwd := dSt.diffProgPath, dSt.cwd l, r := change.Src, change.Dest if l == nil && r == nil { return illogicalStateErr(fmt.Errorf("Neither remote nor local exists")) } if r == nil && l != nil { return illogicalStateErr(fmt.Errorf("%s only on local", change.Path)) } if l == nil && r != nil { return illogicalStateErr(fmt.Errorf("%s only on remote", change.Path)) } // Pre-screening phase if r.IsDir && l.IsDir { // Note that if they are both directories, comparing times is spurious // see issues #304, #471, #477 and PR #478. return illogicalStateErr(fmt.Errorf("Both local and remote are directories")) } if r.IsDir && !l.IsDir { return illogicalStateErr(fmt.Errorf("Remote is a directory while local is an ordinary file")) } if l.IsDir && !r.IsDir { return illogicalStateErr(fmt.Errorf("Local is a directory while remote is an ordinary file")) } mask := fileDifferences(r, l, g.opts.IgnoreChecksum) if mask == DifferNone { // No output when "no changes found" return nil } typeName := "File" if l.IsDir { typeName = "Directory" } g.log.Logf("%s: %s\n", typeName, change.Path) if modTimeDiffers(mask) { g.log.Logf("* %-15s %-40s\n* %-15s %-40s\n", "local:", toUTCString(l.ModTime), "remote:", toUTCString(r.ModTime)) if mask == DifferModTime { // No further change return } } if l.Name != r.Name { g.log.Logf("%s %s\n%s\n\n", l.Name, r.Name, Ruler) } else { g.log.Logf("%s\n%s\n\n", l.Name, Ruler) } if dSt.skipContentCheck { return } defer func() { if dSt.printRuler { g.log.Logf("\n%s\n", Ruler) } }() if r.BlobAt == "" { return illogicalStateErr(fmt.Errorf("Cannot access download link for '%v'", r.Name)) } if r.Size > MaxFileSize { return contentTooLargeErr(fmt.Errorf("%s Remote too large for display \033[94m[%v bytes]\033[00m", change.Path, r.Size)) } if l.Size > MaxFileSize { return contentTooLargeErr(fmt.Errorf("%s Local too large for display \033[92m[%v bytes]\033[00m", change.Path, l.Size)) } var frTmp, fl *os.File var blob io.ReadCloser // Clean-up defer func() { if frTmp != nil { os.RemoveAll(frTmp.Name()) } if fl != nil { fl.Close() } if blob != nil { blob.Close() } }() blob, err = g.rem.Download(r.Id, "") if err != nil { return err } // Next step: Create a temp file with an obscure name unlikely to clash. tmpName := strings.Join([]string{ ".", fmt.Sprintf("tmp%v.tmp", rand.Int()), }, "x") frTmp, err = ioutil.TempFile(".", tmpName) if err != nil { return } _, err = io.Copy(frTmp, blob) if err != nil { return } diffArgs := []string{diffProgPath} if dSt.unified() { diffArgs = append(diffArgs, "-u") } // Next step: Determine which is the base file between local and remote first, other := frTmp.Name(), l.BlobAt if dSt.baseLocal { first, other = l.BlobAt, frTmp.Name() } diffArgs = append(diffArgs, first, other) diffCmd := exec.Cmd{ Args: diffArgs, Dir: cwd, Path: diffProgPath, Stdin: nil, Stdout: os.Stdout, Stderr: os.Stderr, } // Normally when elements differ diff returns a non-zero code _ = diffCmd.Run() return }