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  }