github.com/cheshirekow/buildtools@v0.0.0-20200224190056-5d637702fe81/differ/diff.go (about) 1 /* 2 Copyright 2016 Google Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package differ determines how to invoke diff in the given environment. 18 package differ 19 20 import ( 21 "fmt" 22 "os" 23 "os/exec" 24 "runtime" 25 "strings" 26 ) 27 28 // Invocation of different diff commands, according to environment variables. 29 30 // A Differ describes how to invoke diff. 31 type Differ struct { 32 Cmd string // command 33 MultiDiff bool // diff accepts list of multiple pairs 34 Args []string // accumulated arguments 35 } 36 37 // run runs the given command with args. 38 func (d *Differ) run(command string, args ...string) error { 39 // The special diff command ":" means don't run anything. 40 if d.Cmd == ":" { 41 return nil 42 } 43 44 // Pass args to bash and reference with $@ to avoid shell injection in args. 45 var cmd *exec.Cmd 46 if command == "FC" { 47 cmd = exec.Command(command, "/T") 48 } else { 49 cmd = exec.Command("/usr/bin/env", "bash", "-c", command+` "$@"`, "--") 50 } 51 cmd.Args = append(cmd.Args, args...) 52 cmd.Stdout = os.Stdout 53 cmd.Stderr = os.Stderr 54 if err := cmd.Start(); err != nil { 55 // Couldn't even start bash. Worth reporting. 56 return fmt.Errorf("buildifier: %s: %v", command, err) 57 } 58 59 // Assume bash reported anything else worth reporting. 60 // As long as the program started (above), we don't care about the 61 // exact exit status. In the most common case, the diff command 62 // will exit 1, because there are diffs, causing bash to exit 1. 63 return cmd.Wait() 64 } 65 66 // Show diffs old and new. 67 // For a single-pair diff program, Show runs the diff program before returning. 68 // For a multi-pair diff program, Show records the pair for later use by Run. 69 func (d *Differ) Show(old, new string) error { 70 if !d.MultiDiff { 71 return d.run(d.Cmd, old, new) 72 } 73 74 d.Args = append(d.Args, ":", old, new) 75 return nil 76 } 77 78 // Run runs any pending diffs. 79 // For a single-pair diff program, Show already ran diff; Run is a no-op. 80 // For a multi-pair diff program, Run displays the diffs queued by Show. 81 func (d *Differ) Run() error { 82 if !d.MultiDiff { 83 return nil 84 } 85 86 if len(d.Args) == 0 { 87 return nil 88 } 89 return d.run(d.Cmd, d.Args...) 90 } 91 92 // Find returns the differ to use, using various environment variables. 93 func Find() (*Differ, bool) { 94 d := &Differ{} 95 deprecationWarning := false 96 if cmd := os.Getenv("BUILDIFIER_DIFF"); cmd != "" { 97 deprecationWarning = true 98 d.Cmd = cmd 99 } 100 101 // Load MultiDiff setting from environment. 102 knowMultiDiff := false 103 if md := os.Getenv("BUILDIFIER_MULTIDIFF"); md == "0" || md == "1" { 104 deprecationWarning = true 105 d.MultiDiff = md == "1" 106 knowMultiDiff = true 107 } 108 109 if d.Cmd != "" { 110 if !knowMultiDiff { 111 lower := strings.ToLower(d.Cmd) 112 d.MultiDiff = strings.Contains(lower, "tkdiff") && 113 isatty(1) && os.Getenv("DISPLAY") != "" 114 } 115 } else { 116 if !knowMultiDiff { 117 d.MultiDiff = isatty(1) && os.Getenv("DISPLAY") != "" 118 if d.MultiDiff { 119 deprecationWarning = true 120 } 121 } 122 if d.MultiDiff { 123 d.Cmd = "tkdiff" 124 } else { 125 if runtime.GOOS == "windows" { 126 deprecationWarning = true 127 d.Cmd = "FC" 128 } else { 129 d.Cmd = "diff --unified" 130 } 131 } 132 } 133 return d, deprecationWarning 134 }