<commit_before>package gorules

type Expression interface {
	Evaluate() (bool, error)
}

// type ConjunctionExpression interface {
// 	Add(*Expression)
// }

type ValueExpression struct {
	Operator Operator `json:"operator"`
	Path     string   `json:"path"`
	Value    string   `json:"value"`
	Target   string   `json:"target"`
}

// Evaluate ...
func (v ValueExpression) Evaluate() (bool, error) {
	operatorFunc := OperatorFuncList[v.Operator]
	result, err := operatorFunc(v.Value, v.Target)
	return result, err
}

func CreateValueExpression(operatorText string, path string, value string) Expression {
	operator, err := ToOperator(operatorText)
	if err == nil {
		return &ValueExpression{Operator: operator, Path: path, Value: value}
	}
	panic(err)
}

func CreateValueExpressionWithTarget(operatorText string, path string, value string, target string) Expression {
	operator, err := ToOperator(operatorText)
	if err == nil {
		return &ValueExpression{Operator: operator, Path: path, Value: value, Target: target}
	}
	panic(err)
}

type ConjunctionExpression struct {
	Conjunction Conjunction   `json:"conjunction"`
	Expressions []*Expression `json:"expressions"`
}

// Evaluate ...
func (v ConjunctionExpression) Evaluate() (bool, error) {

	evaluator, accumlator := ConjunctionExpressionProps(v.Conjunction)

	for _, e := range v.Expressions {
		var isTrue, _ = evaluator(accumlator, (*e))
		accumlator = CreateBoolExpression(isTrue)
	}
	return accumlator.Evaluate()
}

func CreateConjunctionExpression(conjunction Conjunction) func(Expression) Expression {
	return func(expr Expression) Expression {
		conj := &ConjunctionExpression{Conjunction: conjunction}
		conj.Expressions = make([]*Expression, 1)
		conj.Add(&expr)
		return conj
	}
}

var CreateAndConjunctionExpression func(Expression) Expression = CreateConjunctionExpression(And)

var CreateOrConjunctionExpression func(Expression) Expression = CreateConjunctionExpression(Or)

func (conj *ConjunctionExpression) Add(expr *Expression) {
	conj.Expressions = append(conj.Expressions, expr)
}

func IsConjunctionExpression(expr Expression) bool {
	_, ok := expr.(*ConjunctionExpression)
	return ok
}

// func CreateAndExpression(e Expression) *AndExpression {
// 	a := &AndExpression{}
// 	a.expressions = make([]*Expression, 1)
// 	a.Add(&e)
// 	return a
// }

// func CreateOrExpression(e Expression) *OrExpression {
// 	o := &OrExpression{}
// 	o.expressions = make([]*Expression, 1)
// 	o.Add(&e)
// 	return o
// }

// type AndExpression struct {
// 	expressions []*Expression
// }

// func (v AndExpression) Evaluate() (bool, error) {
// 	return true, nil
// }

// func (v *AndExpression) Add(e *Expression) {
// 	v.expressions = append(v.expressions, e)
// }

// type OrExpression struct {
// 	expressions []*Expression
// }

// func (o OrExpression) Evaluate() (bool, error) {
// 	return true, nil
// }

// func (v *OrExpression) Add(e *Expression) {
// 	v.expressions = append(v.expressions, e)
// }

//-------------------------------------

type BoolValueExpression struct {
	Type bool `json:"type"`
}

func (v BoolValueExpression) Evaluate() (bool, error) {
	if v.Type {
		return true, nil
	}
	return false, nil
}

func CreateBoolExpression(bool_type bool) Expression {
	if bool_type {
		return &BoolValueExpression{Type: true}
	}
	return &BoolValueExpression{Type: false}
}

// TrueExpression always evaluates to True
var TrueExpression = CreateBoolExpression(true)

// FalseExpression always evaluates to False
var FalseExpression = CreateBoolExpression(false)

//-------------------------------------
<commit_msg>removed comments not necessary<commit_after>package gorules

type Expression interface {
	Evaluate() (bool, error)
}

type ValueExpression struct {
	Operator Operator `json:"operator"`
	Path     string   `json:"path"`
	Value    string   `json:"value"`
	Target   string   `json:"target"`
}

// Evaluate ...
func (v ValueExpression) Evaluate() (bool, error) {
	operatorFunc := OperatorFuncList[v.Operator]
	result, err := operatorFunc(v.Value, v.Target)
	return result, err
}

func CreateValueExpression(operatorText string, path string, value string) Expression {
	operator, err := ToOperator(operatorText)
	if err == nil {
		return &ValueExpression{Operator: operator, Path: path, Value: value}
	}
	panic(err)
}

func CreateValueExpressionWithTarget(operatorText string, path string, value string, target string) Expression {
	operator, err := ToOperator(operatorText)
	if err == nil {
		return &ValueExpression{Operator: operator, Path: path, Value: value, Target: target}
	}
	panic(err)
}

type ConjunctionExpression struct {
	Conjunction Conjunction   `json:"conjunction"`
	Expressions []*Expression `json:"expressions"`
}

// Evaluate ...
func (v ConjunctionExpression) Evaluate() (bool, error) {

	evaluator, accumlator := ConjunctionExpressionProps(v.Conjunction)

	for _, e := range v.Expressions {
		var isTrue, _ = evaluator(accumlator, (*e))
		accumlator = CreateBoolExpression(isTrue)
	}
	return accumlator.Evaluate()
}

func CreateConjunctionExpression(conjunction Conjunction) func(Expression) Expression {
	return func(expr Expression) Expression {
		conj := &ConjunctionExpression{Conjunction: conjunction}
		conj.Expressions = make([]*Expression, 1)
		conj.Add(&expr)
		return conj
	}
}

var CreateAndConjunctionExpression func(Expression) Expression = CreateConjunctionExpression(And)

var CreateOrConjunctionExpression func(Expression) Expression = CreateConjunctionExpression(Or)

func (conj *ConjunctionExpression) Add(expr *Expression) {
	conj.Expressions = append(conj.Expressions, expr)
}

func IsConjunctionExpression(expr Expression) bool {
	_, ok := expr.(*ConjunctionExpression)
	return ok
}

type BoolValueExpression struct {
	Type bool `json:"type"`
}

func (v BoolValueExpression) Evaluate() (bool, error) {
	if v.Type {
		return true, nil
	}
	return false, nil
}

func CreateBoolExpression(bool_type bool) Expression {
	if bool_type {
		return &BoolValueExpression{Type: true}
	}
	return &BoolValueExpression{Type: false}
}

// TrueExpression always evaluates to True
var TrueExpression = CreateBoolExpression(true)

// FalseExpression always evaluates to False
var FalseExpression = CreateBoolExpression(false)

//-------------------------------------
<|endoftext|>


<commit_before>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gg

import (
	"bytes"
	"encoding/base64"
	"image"
	"image/color"
	"image/png"
	"math"
	"sort"
	"strconv"

	"github.com/aclements/go-gg/generic"
	"github.com/aclements/go-gg/table"
	"github.com/ajstarks/svgo"
)

type marker interface {
	mark(env *renderEnv, canvas *svg.SVG)
}

type plotMark struct {
	m      marker
	groups []table.GroupID
}

type markPath struct {
	x, y, stroke, fill *scaledData
}

func (m *markPath) mark(env *renderEnv, canvas *svg.SVG) {
	// XXX What ensures these type assertions will succeed,
	// especially if it's an identity scale? Maybe identity scales
	// still need to coerce their results to the right type.
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)
	// XXX Strokes may not be Gray16, but I don't have a good way
	// to convert from a sequence of things that implement
	// color.Color to a sequence of color.Color.
	var strokes = []color.Gray16{color.Black}
	if m.stroke != nil {
		strokes = env.get(m.stroke).([]color.Gray16)
	}
	var fill color.Color = color.Transparent
	if m.fill != nil {
		fill = env.getFirst(m.fill).(color.Color)
	}

	drawPath(canvas, xs, ys, strokes, fill)
}

type markSteps struct {
	dir StepMode

	x, y, stroke, fill *scaledData
}

func (m *markSteps) mark(env *renderEnv, canvas *svg.SVG) {
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)
	// XXX Strokes may not be Gray16.
	var strokes = []color.Gray16{color.Black}
	if m.stroke != nil {
		strokes = env.get(m.stroke).([]color.Gray16)
	}
	var fill color.Color = color.Transparent
	if m.fill != nil {
		fill = env.getFirst(m.fill).(color.Color)
	}

	if len(xs) == 0 {
		return
	}

	// Create intermediate points.
	xs2, ys2 := make([]float64, 2*len(xs)), make([]float64, 2*len(ys))
	var strokes2 []color.Gray16
	if m.stroke == nil {
		strokes2 = strokes
	} else {
		strokes2 = make([]color.Gray16, 2*len(xs))
	}
	for i := range xs2 {
		switch m.dir {
		case StepHV, StepVH:
			xs2[i], ys2[i] = xs[i/2], ys[i/2]
		case StepHMid, StepVMid:
			if i == 0 || i == len(xs2)-1 {
				xs2[i], ys2[i] = xs[i/2], ys[i/2]
				break
			}
			var p1, p2 int
			if i%2 == 0 {
				// Interpolate i/2-1 and i/2.
				p1, p2 = i/2-1, i/2
			} else {
				// Interpolate i/2 and i/2+1.
				p1, p2 = i/2, i/2+1
			}
			if m.dir == StepHMid {
				xs2[i], ys2[i] = (xs[p1]+xs[p2])/2, ys[i/2]
			} else {
				xs2[i], ys2[i] = xs[i/2], (ys[p1]+ys[p2])/2
			}
		}
		if m.stroke != nil {
			strokes2[i] = strokes[i/2]
		}
	}
	if m.dir == StepHV {
		xs2 = xs2[1:]
	} else if m.dir == StepVH {
		ys2 = ys2[1:]
	}
	if m.stroke != nil {
		strokes2 = strokes2[:len(strokes2)-1]
	}

	drawPath(canvas, xs2, ys2, strokes2, fill)
}

func drawPath(canvas *svg.SVG, xs, ys []float64, strokes []color.Gray16, fill color.Color) {
	switch len(xs) {
	case 0:
		return
	case 1:
		// TODO: Depending on the stroke cap, this *could* be
		// well-defined.
		Warning.Print("cannot draw path through 1 point; ignoring")
		return
	}

	// Is the stroke constant?
	stroke := strokes[0]
	for _, s := range strokes {
		if s != stroke {
			Warning.Print("multi-color stroke not implemented")
			break
		}
	}

	// Constant stroke. Use one path.
	path := []byte("M")
	for i := range xs {
		path = append(path, ' ')
		path = strconv.AppendFloat(path, xs[i], 'g', 6, 64)
		path = append(path, ' ')
		path = strconv.AppendFloat(path, ys[i], 'g', 6, 64)
	}

	// XXX Stroke width

	style := cssPaint("stroke", stroke) + ";" + cssPaint("fill", fill) + ";stroke-width:3"
	canvas.Path(string(path), style)
}

type markPoint struct {
	x, y *scaledData
}

func (m *markPoint) mark(env *renderEnv, canvas *svg.SVG) {
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)

	for i := range xs {
		canvas.Circle(int(xs[i]), int(ys[i]), 4)
	}
}

type markTiles struct {
	x, y, fill *scaledData
}

func (m *markTiles) mark(env *renderEnv, canvas *svg.SVG) {
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)
	// TODO: Should the Scaler (or Ranger) ensure that the values
	// are color.Color? How would this work with an identity
	// scaler?
	var fills []color.Color
	generic.ConvertSlice(&fills, env.get(m.fill))

	// TODO: We can't use an <image> this if the width and height
	// are specified, or if there is a stroke.
	// minx, maxx := stats.Bounds(xs)
	// miny, maxy := stats.Bounds(ys)

	// Compute image bounds.
	imageBounds := func(vals []float64) (float64, float64, float64, bool) {
		// Reduce to unique values.
		unique := []float64{}
		uset := map[float64]bool{}
		for _, v := range vals {
			if !uset[v] {
				if math.IsNaN(v) || math.IsInf(v, 0) {
					continue
				}
				unique = append(unique, v)
				uset[v] = true
			}
		}

		var minGap float64
		regular := true
		switch len(unique) {
		case 0:
			return 0, 0, -1, false
		case 1:
			minGap = 1.0
		default:
			sort.Float64s(unique)
			minGap = unique[1] - unique[0]
			for i, u := range unique[1:] {
				minGap = math.Min(minGap, u-unique[i])
			}
			// Consider the spacing "regular" if every
			// point is within a 1000th of a multiple of
			// minGap.
			for _, u := range unique {
				_, error := math.Modf((u - unique[0]) / minGap)
				if 0.001 <= error && error <= 0.999 {
					regular = false
					break
				}
			}
		}
		return unique[0], unique[len(unique)-1], minGap, regular
	}
	xmin, xmax, xgap, xreg := imageBounds(xs)
	ymin, ymax, ygap, yreg := imageBounds(ys)
	if xgap == -1 || ygap == -1 {
		return
	}
	if !xreg || !yreg {
		// TODO: Can't use an image.
		panic("not implemented: irregular tile spacing")
	}

	// TODO: If there are a small number of cells, just make the
	// rectangles since it's hard to reliably disable
	// interpolation (e.g., the below doesn't work in rsvg).

	// Create the image.
	iw, ih := round((xmax-xmin+xgap)/xgap), round((ymax-ymin+ygap)/ygap)
	img := image.NewRGBA(image.Rect(0, 0, iw, ih))
	for i := range xs {
		img.Set(round((xs[i]-xmin)/xgap), round((ys[i]-ymin)/ygap), fills[i])
	}

	// Encode the image.
	uri := bytes.NewBufferString("data:image/png;base64,")
	w := base64.NewEncoder(base64.StdEncoding, uri)
	if err := png.Encode(w, img); err != nil {
		Warning.Println("error encoding image:", err)
		return
	}
	w.Close()
	canvas.Image(round(xmin-xgap/2), round(ymin-ygap/2),
		round(xmax-xmin+xgap), int(ymax-ymin+ygap),
		uri.String(), `preserveAspectRatio="none" style="image-rendering:optimizeSpeed;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated"`)
}

// cssPaint returns a CSS fragment for setting CSS property prop to
// color c.
func cssPaint(prop string, c color.Color) string {
	r, g, b, a := c.RGBA()
	if a == 0 {
		// No paint.
		return prop + ":none"
	}

	if a != 0xffff {
		// Undo alpha pre-multiplication.
		r = r * 0xffff / a
		g = g * 0xffff / a
		b = b * 0xffff / a
	}
	r, g, b = r>>8, g>>8, b>>8

	css := prop + ":#"
	if r>>4 == r&0xF && g>>4 == g&0xF && b>>4 == b&0xF {
		// Use #rgb form.
		css += strconv.FormatInt(int64(r>>4), 16) + strconv.FormatInt(int64(g>>4), 16) + strconv.FormatInt(int64(b>>4), 16)
	} else {
		// Use #rrggbb form.
		css += strconv.FormatInt(int64(r), 16) + strconv.FormatInt(int64(g), 16) + strconv.FormatInt(int64(b), 16)
	}

	if a != 0xffff {
		// SVG 1.1 only supports CSS2 color formats, which
		// unfortunately does not include rgba, so we have to
		// use a separate CSS property.
		css += ";" + prop + "-opacity:" + strconv.FormatFloat(float64(a)/0xffff, 'g', 0, 64)
	}
	return css
}
<commit_msg>gg: fix CSS color formatting<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gg

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"image"
	"image/color"
	"image/png"
	"math"
	"sort"
	"strconv"

	"github.com/aclements/go-gg/generic"
	"github.com/aclements/go-gg/table"
	"github.com/ajstarks/svgo"
)

type marker interface {
	mark(env *renderEnv, canvas *svg.SVG)
}

type plotMark struct {
	m      marker
	groups []table.GroupID
}

type markPath struct {
	x, y, stroke, fill *scaledData
}

func (m *markPath) mark(env *renderEnv, canvas *svg.SVG) {
	// XXX What ensures these type assertions will succeed,
	// especially if it's an identity scale? Maybe identity scales
	// still need to coerce their results to the right type.
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)
	// XXX Strokes may not be Gray16, but I don't have a good way
	// to convert from a sequence of things that implement
	// color.Color to a sequence of color.Color.
	var strokes = []color.Gray16{color.Black}
	if m.stroke != nil {
		strokes = env.get(m.stroke).([]color.Gray16)
	}
	var fill color.Color = color.Transparent
	if m.fill != nil {
		fill = env.getFirst(m.fill).(color.Color)
	}

	drawPath(canvas, xs, ys, strokes, fill)
}

type markSteps struct {
	dir StepMode

	x, y, stroke, fill *scaledData
}

func (m *markSteps) mark(env *renderEnv, canvas *svg.SVG) {
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)
	// XXX Strokes may not be Gray16.
	var strokes = []color.Gray16{color.Black}
	if m.stroke != nil {
		strokes = env.get(m.stroke).([]color.Gray16)
	}
	var fill color.Color = color.Transparent
	if m.fill != nil {
		fill = env.getFirst(m.fill).(color.Color)
	}

	if len(xs) == 0 {
		return
	}

	// Create intermediate points.
	xs2, ys2 := make([]float64, 2*len(xs)), make([]float64, 2*len(ys))
	var strokes2 []color.Gray16
	if m.stroke == nil {
		strokes2 = strokes
	} else {
		strokes2 = make([]color.Gray16, 2*len(xs))
	}
	for i := range xs2 {
		switch m.dir {
		case StepHV, StepVH:
			xs2[i], ys2[i] = xs[i/2], ys[i/2]
		case StepHMid, StepVMid:
			if i == 0 || i == len(xs2)-1 {
				xs2[i], ys2[i] = xs[i/2], ys[i/2]
				break
			}
			var p1, p2 int
			if i%2 == 0 {
				// Interpolate i/2-1 and i/2.
				p1, p2 = i/2-1, i/2
			} else {
				// Interpolate i/2 and i/2+1.
				p1, p2 = i/2, i/2+1
			}
			if m.dir == StepHMid {
				xs2[i], ys2[i] = (xs[p1]+xs[p2])/2, ys[i/2]
			} else {
				xs2[i], ys2[i] = xs[i/2], (ys[p1]+ys[p2])/2
			}
		}
		if m.stroke != nil {
			strokes2[i] = strokes[i/2]
		}
	}
	if m.dir == StepHV {
		xs2 = xs2[1:]
	} else if m.dir == StepVH {
		ys2 = ys2[1:]
	}
	if m.stroke != nil {
		strokes2 = strokes2[:len(strokes2)-1]
	}

	drawPath(canvas, xs2, ys2, strokes2, fill)
}

func drawPath(canvas *svg.SVG, xs, ys []float64, strokes []color.Gray16, fill color.Color) {
	switch len(xs) {
	case 0:
		return
	case 1:
		// TODO: Depending on the stroke cap, this *could* be
		// well-defined.
		Warning.Print("cannot draw path through 1 point; ignoring")
		return
	}

	// Is the stroke constant?
	stroke := strokes[0]
	for _, s := range strokes {
		if s != stroke {
			Warning.Print("multi-color stroke not implemented")
			break
		}
	}

	// Constant stroke. Use one path.
	path := []byte("M")
	for i := range xs {
		path = append(path, ' ')
		path = strconv.AppendFloat(path, xs[i], 'g', 6, 64)
		path = append(path, ' ')
		path = strconv.AppendFloat(path, ys[i], 'g', 6, 64)
	}

	// XXX Stroke width

	style := cssPaint("stroke", stroke) + ";" + cssPaint("fill", fill) + ";stroke-width:3"
	canvas.Path(string(path), style)
}

type markPoint struct {
	x, y *scaledData
}

func (m *markPoint) mark(env *renderEnv, canvas *svg.SVG) {
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)

	for i := range xs {
		canvas.Circle(int(xs[i]), int(ys[i]), 4)
	}
}

type markTiles struct {
	x, y, fill *scaledData
}

func (m *markTiles) mark(env *renderEnv, canvas *svg.SVG) {
	xs, ys := env.get(m.x).([]float64), env.get(m.y).([]float64)
	// TODO: Should the Scaler (or Ranger) ensure that the values
	// are color.Color? How would this work with an identity
	// scaler?
	var fills []color.Color
	generic.ConvertSlice(&fills, env.get(m.fill))

	// TODO: We can't use an <image> this if the width and height
	// are specified, or if there is a stroke.
	// minx, maxx := stats.Bounds(xs)
	// miny, maxy := stats.Bounds(ys)

	// Compute image bounds.
	imageBounds := func(vals []float64) (float64, float64, float64, bool) {
		// Reduce to unique values.
		unique := []float64{}
		uset := map[float64]bool{}
		for _, v := range vals {
			if !uset[v] {
				if math.IsNaN(v) || math.IsInf(v, 0) {
					continue
				}
				unique = append(unique, v)
				uset[v] = true
			}
		}

		var minGap float64
		regular := true
		switch len(unique) {
		case 0:
			return 0, 0, -1, false
		case 1:
			minGap = 1.0
		default:
			sort.Float64s(unique)
			minGap = unique[1] - unique[0]
			for i, u := range unique[1:] {
				minGap = math.Min(minGap, u-unique[i])
			}
			// Consider the spacing "regular" if every
			// point is within a 1000th of a multiple of
			// minGap.
			for _, u := range unique {
				_, error := math.Modf((u - unique[0]) / minGap)
				if 0.001 <= error && error <= 0.999 {
					regular = false
					break
				}
			}
		}
		return unique[0], unique[len(unique)-1], minGap, regular
	}
	xmin, xmax, xgap, xreg := imageBounds(xs)
	ymin, ymax, ygap, yreg := imageBounds(ys)
	if xgap == -1 || ygap == -1 {
		return
	}
	if !xreg || !yreg {
		// TODO: Can't use an image.
		panic("not implemented: irregular tile spacing")
	}

	// TODO: If there are a small number of cells, just make the
	// rectangles since it's hard to reliably disable
	// interpolation (e.g., the below doesn't work in rsvg).

	// Create the image.
	iw, ih := round((xmax-xmin+xgap)/xgap), round((ymax-ymin+ygap)/ygap)
	img := image.NewRGBA(image.Rect(0, 0, iw, ih))
	for i := range xs {
		img.Set(round((xs[i]-xmin)/xgap), round((ys[i]-ymin)/ygap), fills[i])
	}

	// Encode the image.
	uri := bytes.NewBufferString("data:image/png;base64,")
	w := base64.NewEncoder(base64.StdEncoding, uri)
	if err := png.Encode(w, img); err != nil {
		Warning.Println("error encoding image:", err)
		return
	}
	w.Close()
	canvas.Image(round(xmin-xgap/2), round(ymin-ygap/2),
		round(xmax-xmin+xgap), int(ymax-ymin+ygap),
		uri.String(), `preserveAspectRatio="none" style="image-rendering:optimizeSpeed;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated"`)
}

// cssPaint returns a CSS fragment for setting CSS property prop to
// color c.
func cssPaint(prop string, c color.Color) string {
	r, g, b, a := c.RGBA()
	if a == 0 {
		// No paint.
		return prop + ":none"
	}

	if a != 0xffff {
		// Undo alpha pre-multiplication.
		r = r * 0xffff / a
		g = g * 0xffff / a
		b = b * 0xffff / a
	}
	r, g, b = r>>8, g>>8, b>>8

	css := prop
	if r>>4 == r&0xF && g>>4 == g&0xF && b>>4 == b&0xF {
		// Use #rgb form.
		css += fmt.Sprintf(":#%x%x%x", r>>4, g>>4, b>>4)
	} else {
		// Use #rrggbb form.
		css += fmt.Sprintf(":#%02x%02x%02x", r, g, b)
	}

	if a != 0xffff {
		// SVG 1.1 only supports CSS2 color formats, which
		// unfortunately does not include rgba, so we have to
		// use a separate CSS property.
		css += ";" + prop + "-opacity:" + strconv.FormatFloat(float64(a)/0xffff, 'g', 0, 64)
	}
	return css
}
<|endoftext|>


<commit_before>package analytics

import (
	"encoding/json"
	"fmt"
	"io"
	"time"

	"github.com/gofrs/uuid"
)

type event struct {
	ID         string     `json:"id"`
	EventName  string     `json:"event_name"`
	Timestamp  int64      `json:"timestamp"`
	Properties Properties `json:"properties"`
}

func newEvent(name string, properties []Properties) event {
	return event{
		ID:         uuid.Must(uuid.NewV4()).String(),
		EventName:  name,
		Timestamp:  time.Now().UnixNano() / int64(time.Millisecond),
		Properties: merge(properties),
	}
}

func (e event) toJSON(writer io.Writer) {
	if err := json.NewEncoder(writer).Encode(e); err != nil {
		panic(fmt.Sprintf("Analytics event should be serializable to JSON: %s", err.Error()))
	}
}

func merge(properties []Properties) Properties {
	if len(properties) == 0 {
		return nil
	}
	m := map[string]interface{}{}
	for _, p := range properties {
		for key, value := range p {
			m[key] = value
		}
	}
	return m
}
<commit_msg>Change timestamp of analytics events to microseconds (#154)<commit_after>package analytics

import (
	"encoding/json"
	"fmt"
	"io"
	"time"

	"github.com/gofrs/uuid"
)

type event struct {
	ID         string     `json:"id"`
	EventName  string     `json:"event_name"`
	Timestamp  int64      `json:"timestamp"`
	Properties Properties `json:"properties"`
}

func newEvent(name string, properties []Properties) event {
	return event{
		ID:         uuid.Must(uuid.NewV4()).String(),
		EventName:  name,
		Timestamp:  time.Now().UnixNano() / int64(time.Microsecond),
		Properties: merge(properties),
	}
}

func (e event) toJSON(writer io.Writer) {
	if err := json.NewEncoder(writer).Encode(e); err != nil {
		panic(fmt.Sprintf("Analytics event should be serializable to JSON: %s", err.Error()))
	}
}

func merge(properties []Properties) Properties {
	if len(properties) == 0 {
		return nil
	}
	m := map[string]interface{}{}
	for _, p := range properties {
		for key, value := range p {
			m[key] = value
		}
	}
	return m
}
<|endoftext|>


<commit_before>// Copyright 2012 Francisco Souza. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package git

// #cgo pkg-config: libgit2
// #include <git2.h>
import "C"

import (
	"unsafe"
)

// Commit represents a git commit.
type Commit struct {
	commit *C.struct_git_commit
}

// Free is used to deallocate a commit object.
func (c *Commit) Free() {
	C.git_commit_free(c.commit)
}

// Id returns the hash of the commit.
func (c *Commit) Id() string {
	oid := C.git_commit_id(c.commit)
	defer C.free(unsafe.Pointer(oid))
	id := C.git_oid_allocfmt(oid)
	defer C.free(unsafe.Pointer(id))
	return C.GoString(id)
}

// Repository is the basic type of the git package, it represents a git
// repository.
type Repository struct {
	repository *C.struct_git_repository
}

// InitRepository inits a new repository.
//
// If the path does not exist, it will be created.
//
// Returns an instance of Repository, or an error in case of failure.
func InitRepository(path string, bare bool) (*Repository, error) {
	var cbare C.unsigned = 0
	if bare {
		cbare = 1
	}
	repo := new(Repository)
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	if C.git_repository_init(&repo.repository, cpath, cbare) != C.GIT_OK {
		return nil, lastErr()
	}
	return repo, nil
}

// OpenRepository opens a repository by its path.
//
// Returns an error in case of failure (e.g.: if the path does not exist; if
// it exists but is not a git repository or if the path exists, is a git
// repository, but the user does not have access to it).
func OpenRepository(path string) (*Repository, error) {
	repo := new(Repository)
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	if C.git_repository_open(&repo.repository, cpath) != C.GIT_OK {
		return nil, lastErr()
	}
	return repo, nil
}

// Config returns a Config instance, representing the configuration of the
// repository.
func (r *Repository) Config() (*Config, error) {
	conf := new(Config)
	if C.git_repository_config(&conf.config, r.repository) != C.GIT_OK {
		return nil, lastErr()
	}
	return conf, nil
}

// Free is used to deallocate the repository. It should be called to finish the
// repository. It's a good practice to use it with the defer statement:
//
//     repo, err := git.GetRepository("/path/to/repository")
//     // check error
//     defer repo.Free()
//     // use repo
func (r *Repository) Free() {
	C.git_repository_free(r.repository)
}

// Head returns the commit at the head of the repository.
func (r *Repository) Head() (*Commit, error) {
	var reference *C.struct_git_reference
	if C.git_repository_head(&reference, r.repository) != C.GIT_OK {
		return nil, lastErr()
	}
	defer C.git_reference_free(reference)
	c := new(Commit)
	if C.git_commit_lookup(&c.commit, r.repository, C.git_reference_oid(reference)) != C.GIT_OK {
		return nil, lastErr()
	}
	return c, nil
}

// Config represents the configuration of a git repository.
//
// You can use it retrieve or define configurations of the repository.
type Config struct {
	config *C.struct_git_config
}

// Free is used to deallocate the Config instance. It should be called to
// finish the instance. You can use it with the defer statement:
//
//     // get repository instance
//     config, err := repo.Config()
//     // check error
//     defer config.Free()
func (c *Config) Free() {
	C.git_config_free(c.config)
}

// GetBool is used to get boolean config values.
//
// The dot notation is used for configuration parameters. Example:
//
//     v, err := config.GetBool("core.ignorecase")
//     // check errors and use v
func (c *Config) GetBool(name string) (bool, error) {
	var v C.int
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_get_bool(&v, c.config, cname) != C.GIT_OK {
		return false, lastErr()
	}
	return v == 1, nil
}

// SetBool is used to add a boolean setting to the configuration file.
//
// The format of the configuration parameter is the same as in GetBool. If the
// configuration parameter is not declared in the config file, it will be
// created. Example of use:
//
//     err := config.SetBool("core.ignorecase", true)
//     if err != nil {
//         panic(err)
//     }
func (c *Config) SetBool(name string, value bool) error {
	var v C.int = 0
	if value {
		v = 1
	}
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_set_bool(c.config, cname, v) != C.GIT_OK {
		return lastErr()
	}
	return nil
}

// GetString is used to get string config values.
//
// The format of the configuration parameter is the same as in GetBool.
func (c *Config) GetString(name string) (string, error) {
	var v *C.char
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_get_string(&v, c.config, cname) != C.GIT_OK {
		return "", lastErr()
	}
	return C.GoString(v), nil
}

// SetString is used to add a string setting to the config file.
//
// The format of the configuration parameter is the same as in GetBool. If the
// parameter is not declared in the config file, it will be created.
func (c *Config) SetString(name, value string) error {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	cvalue := C.CString(value)
	defer C.free(unsafe.Pointer(cvalue))
	if C.git_config_set_string(c.config, cname, cvalue) != C.GIT_OK {
		return lastErr()
	}
	return nil
}

// GetInt64 is used to get int64 config values.
//
// The format of the configuration parameter is the same as in GetBool.
func (c *Config) GetInt64(name string) (int64, error) {
	var v C.int64_t
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_get_int64(&v, c.config, cname) != C.GIT_OK {
		return 0, lastErr()
	}
	return int64(v), nil
}

// SetInt64 is used to add a int64 setting to the config file.
//
// The format of the configuration parameter is the same as in GetBool. If the
// parameter is not declared in the config file, it will be created.
func (c *Config) SetInt64(name string, value int64) error {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_set_int64(c.config, cname, C.int64_t(value)) != C.GIT_OK {
		return lastErr()
	}
	return nil
}

// GitError is the type used for errors in this package.
type GitError string

func (err GitError) Error() string {
	return string(err)
}

func lastErr() GitError {
	err := C.giterr_last()
	defer C.free(unsafe.Pointer(err))
	return GitError(C.GoString(err.message))
}
<commit_msg>repository: improve doc for OpenRepository function<commit_after>// Copyright 2012 Francisco Souza. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package git

// #cgo pkg-config: libgit2
// #include <git2.h>
import "C"

import (
	"unsafe"
)

// Commit represents a git commit.
type Commit struct {
	commit *C.struct_git_commit
}

// Free is used to deallocate a commit object.
func (c *Commit) Free() {
	C.git_commit_free(c.commit)
}

// Id returns the hash of the commit.
func (c *Commit) Id() string {
	oid := C.git_commit_id(c.commit)
	defer C.free(unsafe.Pointer(oid))
	id := C.git_oid_allocfmt(oid)
	defer C.free(unsafe.Pointer(id))
	return C.GoString(id)
}

// Repository is the basic type of the git package, it represents a git
// repository.
type Repository struct {
	repository *C.struct_git_repository
}

// InitRepository inits a new repository.
//
// If the path does not exist, it will be created.
//
// Returns an instance of Repository, or an error in case of failure.
func InitRepository(path string, bare bool) (*Repository, error) {
	var cbare C.unsigned = 0
	if bare {
		cbare = 1
	}
	repo := new(Repository)
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	if C.git_repository_init(&repo.repository, cpath, cbare) != C.GIT_OK {
		return nil, lastErr()
	}
	return repo, nil
}

// OpenRepository opens a repository by its path.
//
// Returns an error in case of failure (e.g.: if the path does not exist; if
// it exists but is not a git repository or if the path exists, is a git
// repository, but the user does not have access to it).
func OpenRepository(path string) (*Repository, error) {
	repo := new(Repository)
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	if C.git_repository_open(&repo.repository, cpath) != C.GIT_OK {
		return nil, lastErr()
	}
	return repo, nil
}

// Config returns a Config instance, representing the configuration of the
// repository.
func (r *Repository) Config() (*Config, error) {
	conf := new(Config)
	if C.git_repository_config(&conf.config, r.repository) != C.GIT_OK {
		return nil, lastErr()
	}
	return conf, nil
}

// Free is used to deallocate the repository. It should be called to finish the
// repository. It's a good practice to use it with the defer statement:
//
//     repo, err := git.GetRepository("/path/to/repository")
//     // check error
//     defer repo.Free()
//     // use repo
func (r *Repository) Free() {
	C.git_repository_free(r.repository)
}

// Head returns the commit at the head of the repository.
func (r *Repository) Head() (*Commit, error) {
	var reference *C.struct_git_reference
	if C.git_repository_head(&reference, r.repository) != C.GIT_OK {
		return nil, lastErr()
	}
	defer C.git_reference_free(reference)
	c := new(Commit)
	if C.git_commit_lookup(&c.commit, r.repository, C.git_reference_oid(reference)) != C.GIT_OK {
		return nil, lastErr()
	}
	return c, nil
}

// Config represents the configuration of a git repository.
//
// You can use it to retrieve or to define settings on the repository.
type Config struct {
	config *C.struct_git_config
}

// Free is used to deallocate the Config instance. It should be called to
// finish the instance. You can use it with the defer statement:
//
//     // get repository instance
//     config, err := repo.Config()
//     // check error
//     defer config.Free()
func (c *Config) Free() {
	C.git_config_free(c.config)
}

// GetBool is used to get boolean config values.
//
// The dot notation is used for configuration parameters. Example:
//
//     v, err := config.GetBool("core.ignorecase")
//     // check errors and use v
func (c *Config) GetBool(name string) (bool, error) {
	var v C.int
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_get_bool(&v, c.config, cname) != C.GIT_OK {
		return false, lastErr()
	}
	return v == 1, nil
}

// SetBool is used to add a boolean setting to the configuration file.
//
// The format of the configuration parameter is the same as in GetBool. If the
// configuration parameter is not declared in the config file, it will be
// created. Example of use:
//
//     err := config.SetBool("core.ignorecase", true)
//     if err != nil {
//         panic(err)
//     }
func (c *Config) SetBool(name string, value bool) error {
	var v C.int = 0
	if value {
		v = 1
	}
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_set_bool(c.config, cname, v) != C.GIT_OK {
		return lastErr()
	}
	return nil
}

// GetString is used to get string config values.
//
// The format of the configuration parameter is the same as in GetBool.
func (c *Config) GetString(name string) (string, error) {
	var v *C.char
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_get_string(&v, c.config, cname) != C.GIT_OK {
		return "", lastErr()
	}
	return C.GoString(v), nil
}

// SetString is used to add a string setting to the config file.
//
// The format of the configuration parameter is the same as in GetBool. If the
// parameter is not declared in the config file, it will be created.
func (c *Config) SetString(name, value string) error {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	cvalue := C.CString(value)
	defer C.free(unsafe.Pointer(cvalue))
	if C.git_config_set_string(c.config, cname, cvalue) != C.GIT_OK {
		return lastErr()
	}
	return nil
}

// GetInt64 is used to get int64 config values.
//
// The format of the configuration parameter is the same as in GetBool.
func (c *Config) GetInt64(name string) (int64, error) {
	var v C.int64_t
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_get_int64(&v, c.config, cname) != C.GIT_OK {
		return 0, lastErr()
	}
	return int64(v), nil
}

// SetInt64 is used to add a int64 setting to the config file.
//
// The format of the configuration parameter is the same as in GetBool. If the
// parameter is not declared in the config file, it will be created.
func (c *Config) SetInt64(name string, value int64) error {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	if C.git_config_set_int64(c.config, cname, C.int64_t(value)) != C.GIT_OK {
		return lastErr()
	}
	return nil
}

// GitError is the type used for errors in this package.
type GitError string

func (err GitError) Error() string {
	return string(err)
}

func lastErr() GitError {
	err := C.giterr_last()
	defer C.free(unsafe.Pointer(err))
	return GitError(C.GoString(err.message))
}
<|endoftext|>


<commit_before>package git

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	version "github.com/hashicorp/go-version"
)

const (
	hashPrefix    = "commit: "
	datePrefix    = "date: "
	authorPrefix  = "author: "
	messagePrefix = "message: "
)

//=======================================
// Models
//=======================================

// CommitModel ...
type CommitModel struct {
	Hash    string
	Message string
	Date    time.Time
	Author  string
	Tag     string
}

//=======================================
// Utility
//=======================================

func parseDate(unixTimeStampStr string) (time.Time, error) {
	i, err := strconv.ParseInt(unixTimeStampStr, 10, 64)
	if err != nil {
		return time.Time{}, err
	}
	if i < 0 {
		return time.Time{}, fmt.Errorf("Invalid time stamp (%s)", unixTimeStampStr)
	}
	tm := time.Unix(i, 0)

	return tm, nil
}

func parseCommitList(commitListStr string) ([]CommitModel, error) {
	commits := []CommitModel{}

	commitLine := ""

	commitStart := false

	commitListSplit := strings.Split(commitListStr, "\n")
	for _, line := range commitListSplit {
		if strings.HasPrefix(line, hashPrefix) {
			commitStart = true
		}

		if commitStart {
			if commitLine != "" {
				commit, err := parseCommit(commitLine)
				if err != nil {
					return []CommitModel{}, err
				}

				commits = append(commits, commit)
			}

			commitStart = false
			commitLine = ""
		}

		if commitLine == "" {
			commitLine = line
		} else {
			commitLine += fmt.Sprintf("\n%s", line)
		}
	}

	return commits, nil
}

func parseCommit(commitLineStr string) (CommitModel, error) {
	// commit b738dee2d32def019a4d553249004364046dc1bd
	// commit: b738dee2d32def019a4d553249004364046dc1bd
	// date: 1455631980
	// author: Viktor Benei
	// message: Merge branch 'master' of github.com:bitrise-tools/releaseman

	/*
		commit: 7d3243a6e91aa46f28ed3811bb4bc26a05ce0b02
		date: 1455788198
		author: Krisztián Gödrei
		message: FIX: parsing git commits
	*/
	hash := ""
	dateStr := ""
	author := ""
	message := ""

	commitSplits := splitByNewLineAndStrip(commitLineStr)
	if len(commitSplits) < 4 {
		return CommitModel{}, fmt.Errorf("(%s), error: <4 parts", commitLineStr)
	}

	messageStart := false
	for _, line := range commitSplits {
		if strings.HasPrefix(line, hashPrefix) {
			hash = strings.TrimPrefix(line, hashPrefix)
		} else if strings.HasPrefix(line, datePrefix) {
			dateStr = strings.TrimPrefix(line, datePrefix)
		} else if strings.HasPrefix(line, authorPrefix) {
			author = strings.TrimPrefix(line, authorPrefix)
		} else if strings.HasPrefix(line, messagePrefix) {
			messageStart = true
		}

		if messageStart {
			if strings.HasPrefix(line, messagePrefix) {
				message += strings.TrimPrefix(line, messagePrefix)
			} else {
				message += fmt.Sprintf("\n%s", line)
			}
		}
	}

	if hash == "" || dateStr == "" || author == "" {
		return CommitModel{}, fmt.Errorf("(%s), error: some required fields are missing", commitLineStr)
	}

	date, err := parseDate(dateStr)
	if err != nil {
		return CommitModel{}, err
	}

	return CommitModel{
		Hash:    hash,
		Message: message,
		Date:    date,
		Author:  author,
	}, nil
}

//=======================================
// Git functions
//=======================================

// LocalBranches ...
func LocalBranches() ([]string, error) {
	out, err := NewPrintableCommand("git", "branch", "--list").Run()
	if err != nil {
		return []string{}, err
	}
	return splitByNewLineAndStrip(out), nil
}

// VersionTaggedCommits ...
func VersionTaggedCommits() ([]CommitModel, error) {
	out, err := NewPrintableCommand("git", "tag", "--list").Run()
	if err != nil {
		return []CommitModel{}, err
	}
	taggedCommits := []CommitModel{}
	tags := splitByNewLineAndStrip(out)
	for _, tag := range tags {
		// is tag sem-ver tag?
		_, err := version.NewVersion(tag)
		if err != nil {
			continue
		}

		out, err = NewPrintableCommand("git", "rev-list", "-n", "1", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`, tag).Run()
		if err != nil {
			return []CommitModel{}, err
		}

		commit, err := parseCommit(Strip(out))
		if err != nil {
			return []CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
		}
		commit.Tag = tag

		taggedCommits = append(taggedCommits, commit)
	}
	return taggedCommits, nil
}

// CurrentBranchName ...
func CurrentBranchName() (string, error) {
	out, err := NewPrintableCommand("git", "symbolic-ref", "--short", "HEAD").Run()
	if err != nil {
		return "", err
	}
	return Strip(out), nil

}

// AreUncommitedChanges ...
func AreUncommitedChanges() (bool, error) {
	out, err := NewPrintableCommand("git", "status", "--porcelain").Run()
	if err != nil {
		return false, err
	}
	return (out != ""), nil
}

// GetChangedFiles ...
func GetChangedFiles() ([]string, error) {
	out, err := NewPrintableCommand("git", "status", "--porcelain").Run()
	if err != nil {
		return []string{}, err
	}

	changes := []string{}
	changeList := splitByNewLineAndStrip(out)
	for _, change := range changeList {
		changeSplits := strings.Split(change, " ")

		normalizedChangeSplits := changeSplits[1:len(changeSplits)]
		normalizedChangeStr := strings.Join(normalizedChangeSplits, " ")

		changes = append(changes, normalizedChangeStr)
	}

	return changes, nil
}

// CheckoutBranch ...
func CheckoutBranch(branch string) error {
	if _, err := NewPrintableCommand("git", "checkout", branch).Run(); err != nil {
		return err
	}
	return nil
}

// FirstCommit ...
func FirstCommit() (CommitModel, error) {
	out, err := NewPrintableCommand("git", "rev-list", "--max-parents=0", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`, "HEAD").Run()
	if err != nil {
		return CommitModel{}, err
	}
	commit, err := parseCommit(Strip(out))
	if err != nil {
		return CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
	}
	return commit, nil
}

// LatestCommit ...
func LatestCommit() (CommitModel, error) {
	out, err := NewPrintableCommand("git", "log", "-1", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`).Run()
	if err != nil {
		return CommitModel{}, err
	}
	commit, err := parseCommit(Strip(out))
	if err != nil {
		return CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
	}
	return commit, nil
}

// CommitOfTag ...
func CommitOfTag(tag string) (CommitModel, error) {
	out, err := NewPrintableCommand("git", "show-ref", tag).Run()
	if err != nil {
		return CommitModel{}, err
	}
	commit, err := parseCommit(out)
	if err != nil {
		return CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
	}
	return commit, nil
}

// GetCommitsBetween ...
func GetCommitsBetween(startDate, endDate time.Time) ([]CommitModel, error) {
	out, err := NewPrintableCommand("git", "log", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`, "--reverse").Run()
	if err != nil {
		return []CommitModel{}, err
	}

	commits, err := parseCommitList(out)
	if err != nil {
		return []CommitModel{}, err
	}

	relevantCommits := []CommitModel{}
	isRelevantCommit := false

	for _, commit := range commits {
		if !isRelevantCommit && startDate.Sub(commit.Date) < 0 {
			isRelevantCommit = true
		}

		if isRelevantCommit {
			relevantCommits = append(relevantCommits, commit)
		}

		if isRelevantCommit && endDate.Sub(commit.Date) < 0 {
			break
		}
	}

	return relevantCommits, nil
}

// Add ...
func Add(files []string) error {
	for _, file := range files {
		if _, err := NewPrintableCommand("git", "add", file).Run(); err != nil {
			return err
		}
	}
	return nil
}

// Commit ...
func Commit(message string) error {
	if _, err := NewPrintableCommand("git", "commit", "-m", message).Run(); err != nil {
		return err
	}
	return nil
}

// Merge ...
func Merge(branch, commitMessage string) error {
	if _, err := NewPrintableCommand("git", "merge", branch, "--no-ff", "-m", commitMessage).Run(); err != nil {
		return err
	}
	return nil
}

// Tag ...
func Tag(version string) error {
	if _, err := NewPrintableCommand("git", "tag", version).Run(); err != nil {
		return err
	}
	return nil
}
<commit_msg>commit range fix<commit_after>package git

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	version "github.com/hashicorp/go-version"
)

const (
	hashPrefix    = "commit: "
	datePrefix    = "date: "
	authorPrefix  = "author: "
	messagePrefix = "message: "
)

//=======================================
// Models
//=======================================

// CommitModel ...
type CommitModel struct {
	Hash    string
	Message string
	Date    time.Time
	Author  string
	Tag     string
}

//=======================================
// Utility
//=======================================

func parseDate(unixTimeStampStr string) (time.Time, error) {
	i, err := strconv.ParseInt(unixTimeStampStr, 10, 64)
	if err != nil {
		return time.Time{}, err
	}
	if i < 0 {
		return time.Time{}, fmt.Errorf("Invalid time stamp (%s)", unixTimeStampStr)
	}
	tm := time.Unix(i, 0)

	return tm, nil
}

func parseCommitList(commitListStr string) ([]CommitModel, error) {
	commits := []CommitModel{}

	commitLine := ""

	commitStart := false

	commitListSplit := strings.Split(commitListStr, "\n")
	for _, line := range commitListSplit {
		if strings.HasPrefix(line, hashPrefix) {
			commitStart = true
		}

		if commitStart {
			if commitLine != "" {
				commit, err := parseCommit(commitLine)
				if err != nil {
					return []CommitModel{}, err
				}

				commits = append(commits, commit)
			}

			commitStart = false
			commitLine = ""
		}

		if commitLine == "" {
			commitLine = line
		} else {
			commitLine += fmt.Sprintf("\n%s", line)
		}
	}

	return commits, nil
}

func parseCommit(commitLineStr string) (CommitModel, error) {
	// commit b738dee2d32def019a4d553249004364046dc1bd
	// commit: b738dee2d32def019a4d553249004364046dc1bd
	// date: 1455631980
	// author: Viktor Benei
	// message: Merge branch 'master' of github.com:bitrise-tools/releaseman

	/*
		commit: 7d3243a6e91aa46f28ed3811bb4bc26a05ce0b02
		date: 1455788198
		author: Krisztián Gödrei
		message: FIX: parsing git commits
	*/
	hash := ""
	dateStr := ""
	author := ""
	message := ""

	commitSplits := splitByNewLineAndStrip(commitLineStr)
	if len(commitSplits) < 4 {
		return CommitModel{}, fmt.Errorf("(%s), error: <4 parts", commitLineStr)
	}

	messageStart := false
	for _, line := range commitSplits {
		if strings.HasPrefix(line, hashPrefix) {
			hash = strings.TrimPrefix(line, hashPrefix)
		} else if strings.HasPrefix(line, datePrefix) {
			dateStr = strings.TrimPrefix(line, datePrefix)
		} else if strings.HasPrefix(line, authorPrefix) {
			author = strings.TrimPrefix(line, authorPrefix)
		} else if strings.HasPrefix(line, messagePrefix) {
			messageStart = true
		}

		if messageStart {
			if strings.HasPrefix(line, messagePrefix) {
				message += strings.TrimPrefix(line, messagePrefix)
			} else {
				message += fmt.Sprintf("\n%s", line)
			}
		}
	}

	if hash == "" || dateStr == "" || author == "" {
		return CommitModel{}, fmt.Errorf("(%s), error: some required fields are missing", commitLineStr)
	}

	date, err := parseDate(dateStr)
	if err != nil {
		return CommitModel{}, err
	}

	return CommitModel{
		Hash:    hash,
		Message: message,
		Date:    date,
		Author:  author,
	}, nil
}

//=======================================
// Git functions
//=======================================

// LocalBranches ...
func LocalBranches() ([]string, error) {
	out, err := NewPrintableCommand("git", "branch", "--list").Run()
	if err != nil {
		return []string{}, err
	}
	return splitByNewLineAndStrip(out), nil
}

// VersionTaggedCommits ...
func VersionTaggedCommits() ([]CommitModel, error) {
	out, err := NewPrintableCommand("git", "tag", "--list").Run()
	if err != nil {
		return []CommitModel{}, err
	}
	taggedCommits := []CommitModel{}
	tags := splitByNewLineAndStrip(out)
	for _, tag := range tags {
		// is tag sem-ver tag?
		_, err := version.NewVersion(tag)
		if err != nil {
			continue
		}

		out, err = NewPrintableCommand("git", "rev-list", "-n", "1", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`, tag).Run()
		if err != nil {
			return []CommitModel{}, err
		}

		commit, err := parseCommit(Strip(out))
		if err != nil {
			return []CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
		}
		commit.Tag = tag

		taggedCommits = append(taggedCommits, commit)
	}
	return taggedCommits, nil
}

// CurrentBranchName ...
func CurrentBranchName() (string, error) {
	out, err := NewPrintableCommand("git", "symbolic-ref", "--short", "HEAD").Run()
	if err != nil {
		return "", err
	}
	return Strip(out), nil

}

// AreUncommitedChanges ...
func AreUncommitedChanges() (bool, error) {
	out, err := NewPrintableCommand("git", "status", "--porcelain").Run()
	if err != nil {
		return false, err
	}
	return (out != ""), nil
}

// GetChangedFiles ...
func GetChangedFiles() ([]string, error) {
	out, err := NewPrintableCommand("git", "status", "--porcelain").Run()
	if err != nil {
		return []string{}, err
	}

	changes := []string{}
	changeList := splitByNewLineAndStrip(out)
	for _, change := range changeList {
		changeSplits := strings.Split(change, " ")

		normalizedChangeSplits := changeSplits[1:len(changeSplits)]
		normalizedChangeStr := strings.Join(normalizedChangeSplits, " ")

		changes = append(changes, normalizedChangeStr)
	}

	return changes, nil
}

// CheckoutBranch ...
func CheckoutBranch(branch string) error {
	if _, err := NewPrintableCommand("git", "checkout", branch).Run(); err != nil {
		return err
	}
	return nil
}

// FirstCommit ...
func FirstCommit() (CommitModel, error) {
	out, err := NewPrintableCommand("git", "rev-list", "--max-parents=0", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`, "HEAD").Run()
	if err != nil {
		return CommitModel{}, err
	}
	commit, err := parseCommit(Strip(out))
	if err != nil {
		return CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
	}
	return commit, nil
}

// LatestCommit ...
func LatestCommit() (CommitModel, error) {
	out, err := NewPrintableCommand("git", "log", "-1", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`).Run()
	if err != nil {
		return CommitModel{}, err
	}
	commit, err := parseCommit(Strip(out))
	if err != nil {
		return CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
	}
	return commit, nil
}

// CommitOfTag ...
func CommitOfTag(tag string) (CommitModel, error) {
	out, err := NewPrintableCommand("git", "show-ref", tag).Run()
	if err != nil {
		return CommitModel{}, err
	}
	commit, err := parseCommit(out)
	if err != nil {
		return CommitModel{}, fmt.Errorf("Failed to parse commit: %#v", err)
	}
	return commit, nil
}

// GetCommitsBetween ...
func GetCommitsBetween(startDate, endDate time.Time) ([]CommitModel, error) {
	out, err := NewPrintableCommand("git", "log", `--pretty=format:commit: %H%ndate: %ct%nauthor: %an%nmessage: %s`, "--reverse").Run()
	if err != nil {
		return []CommitModel{}, err
	}

	commits, err := parseCommitList(out)
	if err != nil {
		return []CommitModel{}, err
	}

	relevantCommits := []CommitModel{}
	isRelevantCommit := false

	for _, commit := range commits {
		if !isRelevantCommit && startDate.Sub(commit.Date) < 0 {
			isRelevantCommit = true
		}

		if isRelevantCommit {
			relevantCommits = append(relevantCommits, commit)
		}

		if isRelevantCommit && endDate.Sub(commit.Date) < 0 {
			relevantCommits = append(relevantCommits, commit)
			break
		}
	}

	return relevantCommits, nil
}

// Add ...
func Add(files []string) error {
	for _, file := range files {
		if _, err := NewPrintableCommand("git", "add", file).Run(); err != nil {
			return err
		}
	}
	return nil
}

// Commit ...
func Commit(message string) error {
	if _, err := NewPrintableCommand("git", "commit", "-m", message).Run(); err != nil {
		return err
	}
	return nil
}

// Merge ...
func Merge(branch, commitMessage string) error {
	if _, err := NewPrintableCommand("git", "merge", branch, "--no-ff", "-m", commitMessage).Run(); err != nil {
		return err
	}
	return nil
}

// Tag ...
func Tag(version string) error {
	if _, err := NewPrintableCommand("git", "tag", version).Run(); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package git

import (
	"archive/tar"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var logger = log.New(os.Stderr, "", 0)

type Repository struct {
	Origin    string
	LocalPath string
}

func (repo *Repository) cacheDir() string {
	return os.Getenv("HOME") + "/.dgtk/cache/git_repositories"
}

func (repo *Repository) Fetch() error {
	logger.Println("fetching origin")
	_, e := repo.executeGitCommand("fetch")
	return e
}

const githubPrefix = "git@github.com:"

func (repo *Repository) cachePath() string {
	name := repo.Name()
	if strings.HasPrefix(repo.Origin, githubPrefix) {
		name = strings.TrimPrefix(repo.Origin, githubPrefix)
		return repo.cacheDir() + "/github.com/" + name
	}
	return repo.cacheDir() + "/" + repo.Name()
}

func (repo *Repository) clone() error {
	logger.Printf("cloning %s into %s", repo.Origin, repo.cachePath())
	cmd := exec.Command("git", "clone", "--bare", repo.Origin, repo.cachePath())
	if b, e := cmd.CombinedOutput(); e != nil {
		logger.Printf("ERROR: %s", strings.TrimSpace(string(b)))
		return e
	}
	cmd = exec.Command("git", "--git-dir="+repo.cachePath(), "config", "remote.origin.fetch", "refs/heads/*:refs/heads/*")
	if b, e := cmd.CombinedOutput(); e != nil {
		logger.Printf("ERROR: %s", strings.TrimSpace(string(b)))
		return e
	}
	return nil
}

func (repo *Repository) Init() error {
	e := os.MkdirAll(repo.cacheDir(), 0755)
	if e != nil {
		return e
	}

	if !fileExists(repo.cachePath()) {
		if e := repo.clone(); e != nil {
			return e
		}
	} else {
		logger.Printf("already cloned %s to %s", repo.Origin, repo.cachePath())
	}
	return nil
}

func (repo *Repository) createGitCommand(gitCommand ...string) *exec.Cmd {
	return exec.Command("git", append([]string{"--git-dir=" + repo.cachePath()}, gitCommand...)...)
}

func (repo *Repository) executeGitCommand(gitCommand ...string) (b []byte, e error) {
	cmd := repo.createGitCommand(gitCommand...)
	b, e = cmd.CombinedOutput()
	if e != nil {
		logger.Printf("ERROR: %s (%v)", strings.TrimSpace(string(b)), cmd)
		return b, e
	}
	return b, nil
}

func (repo *Repository) MostRecentCommitFor(pattern string) (commit string, e error) {
	commits, e := repo.Commits(&CommitOptions{Limit: 1, Pattern: pattern})
	if e != nil {
		return "", e
	}
	if len(commits) == 0 {
		return "", e
	}
	return commits[0].Checksum, nil
}

var validTar = regexp.MustCompile("^([0-9a-f]{40})$")

// Writes tgz archive to the given tar writer.
func (repo *Repository) WriteArchiveToTar(revision string, w *tar.Writer) (e error) {
	if !validTar.MatchString(revision) {
		return fmt.Errorf("revision %q not valid (must be 40 digit git sha)", revision)
	}

	mtime, e := repo.DateOf(revision, ".")
	if e != nil {
		return e
	}

	e = repo.addArchiveToTar(revision, mtime, w)
	if e != nil {
		return e
	}

	return addFileToArchive("REVISION", []byte(revision), mtime, w)
}

func (repo *Repository) WriteFilesToTar(revision string, w *tar.Writer, files ...string) (e error) {
	if len(files) == 0 {
		return fmt.Errorf("empty file list given")
	}

	if !validTar.MatchString(revision) {
		return fmt.Errorf("revision %q not valid (must be 40 digit git sha)", revision)
	}

	for _, file := range files {
		mtime, e := repo.DateOf(revision, file)
		if e != nil {
			return e
		}

		buf, e := repo.getFileAtRevision(revision, file)
		if e != nil {
			return e
		}

		if e = addFileToArchive(file, buf, mtime, w); e != nil {
			return e
		}
	}

	return nil
}

func (repo *Repository) getFileAtRevision(revision, file string) (content []byte, e error) {
	buf := bytes.NewBuffer(nil)

	cmd := repo.createGitCommand("show", revision+":"+file)
	cmd.Stdout = buf

	if e = cmd.Run(); e != nil {
		return nil, e
	}

	return buf.Bytes(), nil
}

func addFileToArchive(name string, content []byte, modTime time.Time, w *tar.Writer) error {
	e := w.WriteHeader(&tar.Header{Name: name, Size: int64(len(content)), ModTime: modTime, Mode: 0644})
	if e != nil {
		return e
	}
	_, e = w.Write(content)
	return e
}

func (repo *Repository) addArchiveToTar(revision string, mtime time.Time, w *tar.Writer) (e error) {
	filename := repo.Name() + ".tar.gz"

	buf := bytes.NewBuffer(nil)

	cmd := repo.createGitCommand("archive", "--format=tar.gz", revision)
	cmd.Stdout = buf

	if e = cmd.Run(); e != nil {
		return e
	}

	e = w.WriteHeader(&tar.Header{Name: filename, Size: int64(buf.Len()), ModTime: mtime, Mode: 0644})
	if e != nil {
		return e
	}

	_, e = io.Copy(w, buf)
	return e
}

func (repo *Repository) Name() string {
	return strings.TrimSuffix(filepath.Base(repo.Origin), ".git")
}

func (repo *Repository) DateOf(revision, file string) (time.Time, error) {
	b, e := repo.executeGitCommand("log", "-1", "--format='%ct'", revision, "--", file)
	if e != nil {
		return time.Now(), e
	}
	d, e := strconv.Atoi(strings.Trim(string(b), "'\n"))
	if e != nil {
		return time.Now(), e
	}

	return time.Unix(int64(d), 0), nil
}

func (repo *Repository) Commits(options *CommitOptions) (commits []*Commit, e error) {
	if options == nil {
		options = &CommitOptions{Limit: 10}
	}
	path := repo.LocalPath
	if path == "" {
		path = repo.cachePath()
	}
	parts := []string{"log", "-n", strconv.Itoa(options.Limit), "--pretty=format:%H\t%at\t%s"}
	if options.Pattern != "" {
		parts = append(parts, options.Pattern)
	}
	b, e := repo.executeGitCommand(parts...)
	if e != nil {
		return nil, e
	}
	lines := strings.Split(string(b), "\n")
	commits = make([]*Commit, 0, len(lines))

	for _, line := range lines {
		parts := strings.SplitN(line, "\t", 3)
		if len(parts) == 3 {
			if t, e := strconv.ParseInt(parts[1], 10, 64); e == nil {
				commits = append(commits, &Commit{Checksum: parts[0], AuthorDate: time.Unix(t, 0), Message: parts[2]})
			} else {
				logger.Printf("ERROR: %s", e.Error())
			}
		}
	}
	return commits, nil
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil || !os.IsNotExist(err)
}
<commit_msg>remove cachePath in favour of localPath (which also sets LocalPath if not present)<commit_after>package git

import (
	"archive/tar"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var logger = log.New(os.Stderr, "", 0)

type Repository struct {
	Origin    string
	LocalPath string
}

func (repo *Repository) cacheDir() string {
	return os.Getenv("HOME") + "/.dgtk/cache/git_repositories"
}

func (repo *Repository) Fetch() error {
	logger.Println("fetching origin")
	_, e := repo.executeGitCommand("fetch")
	return e
}

const githubPrefix = "git@github.com:"

func (repo *Repository) clone() error {
	logger.Printf("cloning %s into %s", repo.Origin, repo.localPath())
	cmd := exec.Command("git", "clone", "--bare", repo.Origin, repo.localPath())
	if b, e := cmd.CombinedOutput(); e != nil {
		logger.Printf("ERROR: %s", strings.TrimSpace(string(b)))
		return e
	}
	cmd = exec.Command("git", "--git-dir="+repo.localPath(), "config", "remote.origin.fetch", "refs/heads/*:refs/heads/*")
	if b, e := cmd.CombinedOutput(); e != nil {
		logger.Printf("ERROR: %s", strings.TrimSpace(string(b)))
		return e
	}
	return nil
}

func (repo *Repository) Init() error {
	e := os.MkdirAll(repo.cacheDir(), 0755)
	if e != nil {
		return e
	}

	if !fileExists(repo.localPath()) {
		if e := repo.clone(); e != nil {
			return e
		}
	} else {
		logger.Printf("already cloned %s to %s", repo.Origin, repo.localPath())
	}
	return nil
}

func (repo *Repository) createGitCommand(gitCommand ...string) *exec.Cmd {
	return exec.Command("git", append([]string{"--git-dir=" + repo.localPath()}, gitCommand...)...)
}

func (repo *Repository) executeGitCommand(gitCommand ...string) (b []byte, e error) {
	cmd := repo.createGitCommand(gitCommand...)
	b, e = cmd.CombinedOutput()
	if e != nil {
		logger.Printf("ERROR: %s (%v)", strings.TrimSpace(string(b)), cmd)
		return b, e
	}
	return b, nil
}

func (repo *Repository) MostRecentCommitFor(pattern string) (commit string, e error) {
	commits, e := repo.Commits(&CommitOptions{Limit: 1, Pattern: pattern})
	if e != nil {
		return "", e
	}
	if len(commits) == 0 {
		return "", e
	}
	return commits[0].Checksum, nil
}

var validTar = regexp.MustCompile("^([0-9a-f]{40})$")

// Writes tgz archive to the given tar writer.
func (repo *Repository) WriteArchiveToTar(revision string, w *tar.Writer) (e error) {
	if !validTar.MatchString(revision) {
		return fmt.Errorf("revision %q not valid (must be 40 digit git sha)", revision)
	}

	mtime, e := repo.DateOf(revision, ".")
	if e != nil {
		return e
	}

	e = repo.addArchiveToTar(revision, mtime, w)
	if e != nil {
		return e
	}

	return addFileToArchive("REVISION", []byte(revision), mtime, w)
}

func (repo *Repository) WriteFilesToTar(revision string, w *tar.Writer, files ...string) (e error) {
	if len(files) == 0 {
		return fmt.Errorf("empty file list given")
	}

	if !validTar.MatchString(revision) {
		return fmt.Errorf("revision %q not valid (must be 40 digit git sha)", revision)
	}

	for _, file := range files {
		mtime, e := repo.DateOf(revision, file)
		if e != nil {
			return e
		}

		buf, e := repo.getFileAtRevision(revision, file)
		if e != nil {
			return e
		}

		if e = addFileToArchive(file, buf, mtime, w); e != nil {
			return e
		}
	}

	return nil
}

func (repo *Repository) getFileAtRevision(revision, file string) (content []byte, e error) {
	buf := bytes.NewBuffer(nil)

	cmd := repo.createGitCommand("show", revision+":"+file)
	cmd.Stdout = buf

	if e = cmd.Run(); e != nil {
		return nil, e
	}

	return buf.Bytes(), nil
}

func addFileToArchive(name string, content []byte, modTime time.Time, w *tar.Writer) error {
	e := w.WriteHeader(&tar.Header{Name: name, Size: int64(len(content)), ModTime: modTime, Mode: 0644})
	if e != nil {
		return e
	}
	_, e = w.Write(content)
	return e
}

func (repo *Repository) addArchiveToTar(revision string, mtime time.Time, w *tar.Writer) (e error) {
	filename := repo.Name() + ".tar.gz"

	buf := bytes.NewBuffer(nil)

	cmd := repo.createGitCommand("archive", "--format=tar.gz", revision)
	cmd.Stdout = buf

	if e = cmd.Run(); e != nil {
		return e
	}

	e = w.WriteHeader(&tar.Header{Name: filename, Size: int64(buf.Len()), ModTime: mtime, Mode: 0644})
	if e != nil {
		return e
	}

	_, e = io.Copy(w, buf)
	return e
}

func (repo *Repository) Name() string {
	return strings.TrimSuffix(filepath.Base(repo.Origin), ".git")
}

func (repo *Repository) DateOf(revision, file string) (time.Time, error) {
	b, e := repo.executeGitCommand("log", "-1", "--format='%ct'", revision, "--", file)
	if e != nil {
		return time.Now(), e
	}
	d, e := strconv.Atoi(strings.Trim(string(b), "'\n"))
	if e != nil {
		return time.Now(), e
	}

	return time.Unix(int64(d), 0), nil
}

func (repo *Repository) localPath() string {
	if repo.LocalPath == "" {
		name := repo.Name()
		if strings.HasPrefix(repo.Origin, githubPrefix) {
			name = strings.TrimPrefix(repo.Origin, githubPrefix)
			repo.LocalPath = repo.cacheDir() + "/github.com/" + name
		} else {
			repo.LocalPath = repo.cacheDir() + "/" + repo.Name()
		}
	}
	return repo.LocalPath
}

func (repo *Repository) Commits(options *CommitOptions) (commits []*Commit, e error) {
	if options == nil {
		options = &CommitOptions{Limit: 10}
	}

	parts := []string{"log", "-n", strconv.Itoa(options.Limit), "--pretty=format:%H\t%at\t%s"}
	if options.Pattern != "" {
		parts = append(parts, options.Pattern)
	}
	b, e := repo.executeGitCommand(parts...)
	if e != nil {
		return nil, e
	}
	lines := strings.Split(string(b), "\n")
	commits = make([]*Commit, 0, len(lines))

	for _, line := range lines {
		parts := strings.SplitN(line, "\t", 3)
		if len(parts) == 3 {
			if t, e := strconv.ParseInt(parts[1], 10, 64); e == nil {
				commits = append(commits, &Commit{Checksum: parts[0], AuthorDate: time.Unix(t, 0), Message: parts[2]})
			} else {
				logger.Printf("ERROR: %s", e.Error())
			}
		}
	}
	return commits, nil
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil || !os.IsNotExist(err)
}
<|endoftext|>


<commit_before>package app

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/unit"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/errors"
	"github.com/timeredbull/tsuru/log"
	"github.com/timeredbull/tsuru/repository"
	"io/ioutil"
	"launchpad.net/mgo/bson"
	"net/http"
)

func sendProjectChangeToGitosis(kind int, team *auth.Team, app *App) {
	ch := repository.Change{
		Kind: kind,
		Args: map[string]string{"group": team.Name, "project": app.Name},
	}
	repository.Ag.Process(ch)
}

func getAppOrError(name string, u *auth.User) (App, error) {
	app := App{Name: name}
	err := app.Get()
	if err != nil {
		return app, &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	if !app.CheckUserAccess(u) {
		return app, &errors.Http{Code: http.StatusForbidden, Message: "User does not have access to this app"}
	}
	return app, nil
}

func CloneRepositoryHandler(w http.ResponseWriter, r *http.Request) error {
	var output []byte
	app := App{Name: r.URL.Query().Get(":name")}
	err := app.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	output, err = repository.Clone(app.Name, app.Machine)
	if err != nil {
		output, err = repository.Pull(app.Name, app.Machine)
		if err != nil {
			return &errors.Http{Code: http.StatusInternalServerError, Message: string(output)}
		}
	}
	u := unit.Unit{Name: app.Name, Machine: app.Machine}
	err = u.ExecuteHook("dependencies")
	if err != nil {
		log.Printf(err.Error())
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	fmt.Fprint(w, string(output))
	return nil
}

func AppDelete(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	app, err := getAppOrError(r.URL.Query().Get(":name"), u)
	if err != nil {
		return err
	}
	app.Destroy()
	for _, t := range app.Teams {
		sendProjectChangeToGitosis(repository.RemoveProject, &t, &app)
	}
	fmt.Fprint(w, "success")
	return nil
}

func AppList(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	var apps []App
	err := db.Session.Apps().Find(bson.M{"teams.users.email": u.Email}).All(&apps)
	if err != nil {
		return err
	}
	if len(apps) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return nil
	}
	b, err := json.Marshal(apps)
	if err != nil {
		return err
	}
	fmt.Fprint(w, bytes.NewBuffer(b).String())
	return nil
}

func AppInfo(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	app, err := getAppOrError(r.URL.Query().Get(":name"), u)
	if err != nil {
		return err
	}
	b, err := json.Marshal(app)
	if err != nil {
		return err
	}
	fmt.Fprint(w, bytes.NewBuffer(b).String())
	return nil
}

func createApp(app *App, u *auth.User) ([]byte, error) {
	err := db.Session.Teams().Find(bson.M{"users.email": u.Email}).All(&app.Teams)
	if err != nil {
		return nil, err
	}
	if len(app.Teams) < 1 {
		msg := "In order to create an app, you should be member of at least one team"
		return nil, &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	err = app.Create()
	if err != nil {
		return nil, err
	}
	for _, t := range app.Teams {
		sendProjectChangeToGitosis(repository.AddProject, &t, app)
	}
	msg := map[string]string{
		"status":         "success",
		"repository_url": repository.GetUrl(app.Name),
	}
	return json.Marshal(msg)
}

func CreateAppHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	var app App
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(body, &app)
	if err != nil {
		return err
	}
	jsonMsg, err := createApp(&app, u)
	if err != nil {
		return err
	}
	fmt.Fprint(w, bytes.NewBuffer(jsonMsg).String())
	return nil
}

func grantAccessToTeam(appName, teamName string, u *auth.User) error {
	t := new(auth.Team)
	app := &App{Name: appName}
	err := app.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	if !app.CheckUserAccess(u) {
		return &errors.Http{Code: http.StatusUnauthorized, Message: "User unauthorized"}
	}
	err = db.Session.Teams().Find(bson.M{"name": teamName}).One(t)
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "Team not found"}
	}
	err = app.GrantAccess(t)
	if err != nil {
		return &errors.Http{Code: http.StatusConflict, Message: err.Error()}
	}
	err = db.Session.Apps().Update(bson.M{"name": app.Name}, app)
	if err != nil {
		return err
	}
	sendProjectChangeToGitosis(repository.AddProject, t, app)
	return nil
}

func GrantAccessToTeamHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	appName := r.URL.Query().Get(":app")
	teamName := r.URL.Query().Get(":team")
	return grantAccessToTeam(appName, teamName, u)
}

func revokeAccessFromTeam(appName, teamName string, u *auth.User) error {
	t := new(auth.Team)
	app := &App{Name: appName}
	err := app.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	if !app.CheckUserAccess(u) {
		return &errors.Http{Code: http.StatusUnauthorized, Message: "User unauthorized"}
	}
	err = db.Session.Teams().Find(bson.M{"name": teamName}).One(t)
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "Team not found"}
	}
	if len(app.Teams) == 1 {
		msg := "You can not revoke the access from this team, because it is the unique team with access to the app, and an app can not be orphaned"
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	err = app.RevokeAccess(t)
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: err.Error()}
	}
	err = db.Session.Apps().Update(bson.M{"name": app.Name}, app)
	if err != nil {
		return err
	}
	sendProjectChangeToGitosis(repository.RemoveProject, t, app)
	return nil
}

func RevokeAccessFromTeamHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	appName := r.URL.Query().Get(":app")
	teamName := r.URL.Query().Get(":team")
	return revokeAccessFromTeam(appName, teamName, u)
}
<commit_msg>Loging error when executing Already up-to-date. in app's vm<commit_after>package app

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/unit"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/errors"
	"github.com/timeredbull/tsuru/log"
	"github.com/timeredbull/tsuru/repository"
	"io/ioutil"
	"launchpad.net/mgo/bson"
	"net/http"
)

func sendProjectChangeToGitosis(kind int, team *auth.Team, app *App) {
	ch := repository.Change{
		Kind: kind,
		Args: map[string]string{"group": team.Name, "project": app.Name},
	}
	repository.Ag.Process(ch)
}

func getAppOrError(name string, u *auth.User) (App, error) {
	app := App{Name: name}
	err := app.Get()
	if err != nil {
		return app, &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	if !app.CheckUserAccess(u) {
		return app, &errors.Http{Code: http.StatusForbidden, Message: "User does not have access to this app"}
	}
	return app, nil
}

func CloneRepositoryHandler(w http.ResponseWriter, r *http.Request) error {
	var output []byte
	app := App{Name: r.URL.Query().Get(":name")}
	err := app.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	output, err = repository.Clone(app.Name, app.Machine)
	if err != nil {
		output, err = repository.Pull(app.Name, app.Machine)
		if err != nil {
			log.Printf(string(output))
			return &errors.Http{Code: http.StatusInternalServerError, Message: string(output)}
		}
	}
	u := unit.Unit{Name: app.Name, Machine: app.Machine}
	err = u.ExecuteHook("dependencies")
	if err != nil {
		log.Printf(err.Error())
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	fmt.Fprint(w, string(output))
	return nil
}

func AppDelete(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	app, err := getAppOrError(r.URL.Query().Get(":name"), u)
	if err != nil {
		return err
	}
	app.Destroy()
	for _, t := range app.Teams {
		sendProjectChangeToGitosis(repository.RemoveProject, &t, &app)
	}
	fmt.Fprint(w, "success")
	return nil
}

func AppList(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	var apps []App
	err := db.Session.Apps().Find(bson.M{"teams.users.email": u.Email}).All(&apps)
	if err != nil {
		return err
	}
	if len(apps) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return nil
	}
	b, err := json.Marshal(apps)
	if err != nil {
		return err
	}
	fmt.Fprint(w, bytes.NewBuffer(b).String())
	return nil
}

func AppInfo(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	app, err := getAppOrError(r.URL.Query().Get(":name"), u)
	if err != nil {
		return err
	}
	b, err := json.Marshal(app)
	if err != nil {
		return err
	}
	fmt.Fprint(w, bytes.NewBuffer(b).String())
	return nil
}

func createApp(app *App, u *auth.User) ([]byte, error) {
	err := db.Session.Teams().Find(bson.M{"users.email": u.Email}).All(&app.Teams)
	if err != nil {
		return nil, err
	}
	if len(app.Teams) < 1 {
		msg := "In order to create an app, you should be member of at least one team"
		return nil, &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	err = app.Create()
	if err != nil {
		return nil, err
	}
	for _, t := range app.Teams {
		sendProjectChangeToGitosis(repository.AddProject, &t, app)
	}
	msg := map[string]string{
		"status":         "success",
		"repository_url": repository.GetUrl(app.Name),
	}
	return json.Marshal(msg)
}

func CreateAppHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	var app App
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(body, &app)
	if err != nil {
		return err
	}
	jsonMsg, err := createApp(&app, u)
	if err != nil {
		return err
	}
	fmt.Fprint(w, bytes.NewBuffer(jsonMsg).String())
	return nil
}

func grantAccessToTeam(appName, teamName string, u *auth.User) error {
	t := new(auth.Team)
	app := &App{Name: appName}
	err := app.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	if !app.CheckUserAccess(u) {
		return &errors.Http{Code: http.StatusUnauthorized, Message: "User unauthorized"}
	}
	err = db.Session.Teams().Find(bson.M{"name": teamName}).One(t)
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "Team not found"}
	}
	err = app.GrantAccess(t)
	if err != nil {
		return &errors.Http{Code: http.StatusConflict, Message: err.Error()}
	}
	err = db.Session.Apps().Update(bson.M{"name": app.Name}, app)
	if err != nil {
		return err
	}
	sendProjectChangeToGitosis(repository.AddProject, t, app)
	return nil
}

func GrantAccessToTeamHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	appName := r.URL.Query().Get(":app")
	teamName := r.URL.Query().Get(":team")
	return grantAccessToTeam(appName, teamName, u)
}

func revokeAccessFromTeam(appName, teamName string, u *auth.User) error {
	t := new(auth.Team)
	app := &App{Name: appName}
	err := app.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
	}
	if !app.CheckUserAccess(u) {
		return &errors.Http{Code: http.StatusUnauthorized, Message: "User unauthorized"}
	}
	err = db.Session.Teams().Find(bson.M{"name": teamName}).One(t)
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "Team not found"}
	}
	if len(app.Teams) == 1 {
		msg := "You can not revoke the access from this team, because it is the unique team with access to the app, and an app can not be orphaned"
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	err = app.RevokeAccess(t)
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: err.Error()}
	}
	err = db.Session.Apps().Update(bson.M{"name": app.Name}, app)
	if err != nil {
		return err
	}
	sendProjectChangeToGitosis(repository.RemoveProject, t, app)
	return nil
}

func RevokeAccessFromTeamHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	appName := r.URL.Query().Get(":app")
	teamName := r.URL.Query().Get(":team")
	return revokeAccessFromTeam(appName, teamName, u)
}
<|endoftext|>


<commit_before>/*
http://www.apache.org/licenses/LICENSE-2.0.txt

Copyright 2015 Intel Corporation

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 pipeline

import (
	"strings"
)

type Pipe interface {
	Process(in chan interface{}) chan interface{}
}

type Pipeline struct {
	head chan interface{}
	tail chan interface{}
}

func (p *Pipeline) Enqueue(item interface{}) {
	p.head <- item
}

func (p *Pipeline) Dequeue(handler func(interface{})) {
	for i := range p.tail {
		handler(i)
	}
}

func (p *Pipeline) Output() chan interface{} {
	return p.tail
}

func (p *Pipeline) Close() {
	close(p.head)
}

func NewPipeline(pipes ...Pipe) Pipeline {
	head := make(chan interface{})
	var next_chan chan interface{}
	for _, pipe := range pipes {
		if next_chan == nil {
			next_chan = pipe.Process(head)
		} else {
			next_chan = pipe.Process(next_chan)
		}
	}
	return Pipeline{head: head, tail: next_chan}
}

type DoNothing struct{}

func (dn DoNothing) Process(in chan interface{}) chan interface{} {
	out := make(chan interface{})
	go func() {
		for i := range in {
			out <- i
		}
		close(out)
	}()
	return out
}

type StringContains struct {
	str string
}

func (t StringContains) Process(in chan interface{}) chan interface{} {
	out := make(chan interface{})
	go func() {
		for i := range in {
			if !strings.Contains(i.(string), t.str) {
				out <- i.(string)
			}
		}
		close(out)
	}()
	return out
}
<commit_msg>Pipelines reworked<commit_after>/*
http://www.apache.org/licenses/LICENSE-2.0.txt

Copyright 2015 Intel Corporation

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 pipeline

import (
	"strings"
)

type Processor interface {
	Run(input, output Pipe)
}

type Pipe chan interface{}

func (p Pipe) Next(proc Processor) Pipe {
	outPipe := make(Pipe)
	go proc.Run(p, outPipe)
	return outPipe
}

func (p Pipe) Destination(pipes ...Pipe) {
	go func() {
		for input := range p {
			for _, output := range pipes {
				output <- input
			}
		}

		for _, output := range pipes {
			close(output)
		}
	}()
}

func (p Pipe) Clone(n int) []Pipe {
	newPipes := make([]Pipe, n)
	for i, _ := range newPipes {
		newPipes[i] = make(Pipe)
	}

	p.Destination(newPipes...)

	return newPipes
}

func Pipeline(input Pipe, processors ...Processor) Pipe {
	lastOutput := input
	for _, proc := range processors {
		lastOutput = lastOutput.Next(proc)
	}
	return lastOutput
}

type WaitForCompletion struct {
}

func (w WaitForCompletion) Run(input, output Pipe) {
	for v := range input {
		_ = v
	}

	output <- true

	close(output)
}

type Filter struct {
	FilterFunc func(item interface{}) bool
}

func (self Filter) Run(input, output Pipe) {
	for v := range input {
		if self.FilterFunc(v) {
			output <- v
		}
	}

	close(output)
}

type Collect struct {
}

func (self Collect) Run(input, output Pipe) {
	group := []interface{}{}
	for v := range input {
		group = append(group, v)
	}

	output <- group

	close(output)
}

const SKIP_ALL = -1

type Skip struct {
	Count int
}

func (self Skip) Run(input, output Pipe) {
	i := 0
	for v := range input {
		if i < self.Count {
			i++
			continue
		}
		output <- v
	}
	close(output)
}

type LastValue struct {
	last interface{}
}

func (self *LastValue) Run(input, output Pipe) {
	for v := range input {
		self.last = v
		output <- v
	}
	close(output)
}

func (self *LastValue) Last() interface{} {
	return self.last
}

type Nonblocking struct {
}

func (self Nonblocking) Run(input, output Pipe) {
	for v := range input {
		select {
		case output <- v:
		default:
		}
	}
	close(output)
}

type StringContains struct {
	Str string
}

func (self StringContains) Run(input, output Pipe) {
	for v := range input {
		if strings.Contains(v.(string), self.Str) {
			output <- v
		}
	}
	close(output)
}
<|endoftext|>


<commit_before>// Copyright Istio Authors. 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 build

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"istio.io/pkg/log"
	"istio.io/release-builder/pkg/model"
	"istio.io/release-builder/pkg/util"
)

// Structs for json data required
type Response struct {
	Progress string
	Results  Results
}

type Results struct {
	Status string
}

var alwaysGenerateBaseImage = func() bool {
	b, err := strconv.ParseBool(os.Getenv("ALWAYS_GENERATE_BASE_IMAGE"))
	if err != nil {
		return false
	}
	return b
}()

// Scanner checks the base image for any CVEs.
func Scanner(manifest model.Manifest, githubToken, git, branch string) error {
	// Retrieve BASE_VERSION from the istio/istio Makefile
	istioDir := manifest.RepoDir("istio")
	var out bytes.Buffer
	grepCmd := exec.Command("grep", "BASE_VERSION", istioDir+"/Makefile.core.mk")
	grepCmd.Stdout = &out
	err := grepCmd.Run()
	if err != nil {
		return fmt.Errorf("grep error: %v", err)
	}
	baseVersion := strings.TrimSpace(strings.Split(out.String(), " ")[2]) // Assumes line of the form BASE_VERSION ?= baseVersion

	// Call image scanner passing in base image name. If request times out, retry the request
	baseImageName := "istio/base:" + baseVersion
	trivyScanOutput, err := util.RunWithOutput("trivy", "--ignore-unfixed", "--no-progress", "--exit-code", "2", baseImageName)
	if err == nil {
		log.Infof("Base image scan of %s was successful", baseImageName)
		if alwaysGenerateBaseImage {
			log.Infof("Generating base image anyways due to ALWAYS_GENERATE_BASE_IMAGE=true")
		} else {
			return nil
		}
	}

	//--exit-code 2 above states to return 2 if vulnerabilities are found. If we get a different error code or we can't check the error code, bail out
	if exitError, ok := err.(*exec.ExitError); ok {
		// Scanner failed with an exit code indicating a failure other than vulnerabilities found
		if exitError.ExitCode() != 2 {
			return fmt.Errorf("base image scan of %s failed with error:\n %s", baseImageName, err.Error())
		}
	} else {
		// Scanner failed, but not with an ExitError
		return fmt.Errorf("base image scan of %s failed. Unable to process exit code:\n %s", baseImageName, err.Error())
	}

	// Else build a new set of images.
	// Time format chosen for consistency with build tools tag:
	// https://github.com/istio/tools/blob/ee7da00900dc878a2e865e43250c34735f130b7a/docker/build-tools/build-and-push.sh#L27
	const timeFormat = "2006-01-02T15-04-05"
	tag := fmt.Sprintf("%s-%s", manifest.Version, time.Now().Format(timeFormat))
	log.Infof("new base tag: %s", tag)

	// Setup for multiarch build.
	// See https://medium.com/@artur.klauser/building-multi-architecture-docker-images-with-buildx-27d80f7e2408 for more info
	if err := util.VerboseCommand("docker",
		"run", "--rm", "--privileged", "multiarch/qemu-user-static", "--reset", "-p", "yes").Run(); err != nil {
		return fmt.Errorf("failed to run qemu-user-static container: %v", err)
	}
	if err := util.VerboseCommand("docker",
		"buildx", "create", "--name", "multi-arch", "--platform", "linux/amd64,linux/arm64", "--use").Run(); err != nil {
		return fmt.Errorf("failed to set multi-arch as current builder instance: %v", err)
	}

	// Run the script to create the base images
	buildImageEnv := []string{
		"DOCKER_ARCHITECTURES=linux/amd64,linux/arm64",
		"HUBS=docker.io/istio gcr.io/istio-release",
	}
	if manifest.Version == "master" {
		// Push :latest tag for master
		buildImageEnv = append(buildImageEnv, fmt.Sprintf("TAGS=%s %s", tag, "latest"))
	} else {
		buildImageEnv = append(buildImageEnv, "TAG="+tag)
	}
	cmd := util.VerboseCommand("tools/build-base-images.sh")
	cmd.Env = util.StandardEnv(manifest)
	cmd.Env = append(cmd.Env, buildImageEnv...)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	cmd.Dir = istioDir
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("failed to build base images: %v", err)
	}

	// Now create a PR to update the TAG to use the new images
	sedString := "s/BASE_VERSION ?=.*/BASE_VERSION ?= " + tag + "/"
	sedCmd := util.VerboseCommand("sed", "-i", sedString, "Makefile.core.mk")
	sedCmd.Dir = istioDir
	if err := sedCmd.Run(); err != nil {
		return fmt.Errorf("failed to run sed command: %v", err)
	}

	if err := util.CreatePR(
		manifest,
		"istio",
		"newBaseVersion"+tag,
		"Update BASE_VERSION to "+tag,
		fmt.Sprintf("```\n%s\n```", trivyScanOutput),
		false,
		githubToken,
		git,
		branch,
		[]string{"auto-merge"}); err != nil {
		return fmt.Errorf("failed PR creation: %v", err)
	}

	return nil
}
<commit_msg>Update trivy usage after version update (#983)<commit_after>// Copyright Istio Authors. 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 build

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"istio.io/pkg/log"
	"istio.io/release-builder/pkg/model"
	"istio.io/release-builder/pkg/util"
)

// Structs for json data required
type Response struct {
	Progress string
	Results  Results
}

type Results struct {
	Status string
}

var alwaysGenerateBaseImage = func() bool {
	b, err := strconv.ParseBool(os.Getenv("ALWAYS_GENERATE_BASE_IMAGE"))
	if err != nil {
		return false
	}
	return b
}()

// Scanner checks the base image for any CVEs.
func Scanner(manifest model.Manifest, githubToken, git, branch string) error {
	// Retrieve BASE_VERSION from the istio/istio Makefile
	istioDir := manifest.RepoDir("istio")
	var out bytes.Buffer
	grepCmd := exec.Command("grep", "BASE_VERSION", istioDir+"/Makefile.core.mk")
	grepCmd.Stdout = &out
	err := grepCmd.Run()
	if err != nil {
		return fmt.Errorf("grep error: %v", err)
	}
	baseVersion := strings.TrimSpace(strings.Split(out.String(), " ")[2]) // Assumes line of the form BASE_VERSION ?= baseVersion

	// Call image scanner passing in base image name. If request times out, retry the request
	baseImageName := "istio/base:" + baseVersion
	trivyScanOutput, err := util.RunWithOutput("trivy", "image", "--ignore-unfixed", "--no-progress", "--exit-code", "2", baseImageName)
	if err == nil {
		log.Infof("Base image scan of %s was successful", baseImageName)
		if alwaysGenerateBaseImage {
			log.Infof("Generating base image anyways due to ALWAYS_GENERATE_BASE_IMAGE=true")
		} else {
			return nil
		}
	}

	//--exit-code 2 above states to return 2 if vulnerabilities are found. If we get a different error code or we can't check the error code, bail out
	if exitError, ok := err.(*exec.ExitError); ok {
		// Scanner failed with an exit code indicating a failure other than vulnerabilities found
		if exitError.ExitCode() != 2 {
			return fmt.Errorf("base image scan of %s failed with error:\n %s", baseImageName, err.Error())
		}
	} else {
		// Scanner failed, but not with an ExitError
		return fmt.Errorf("base image scan of %s failed. Unable to process exit code:\n %s", baseImageName, err.Error())
	}

	// Else build a new set of images.
	// Time format chosen for consistency with build tools tag:
	// https://github.com/istio/tools/blob/ee7da00900dc878a2e865e43250c34735f130b7a/docker/build-tools/build-and-push.sh#L27
	const timeFormat = "2006-01-02T15-04-05"
	tag := fmt.Sprintf("%s-%s", manifest.Version, time.Now().Format(timeFormat))
	log.Infof("new base tag: %s", tag)

	// Setup for multiarch build.
	// See https://medium.com/@artur.klauser/building-multi-architecture-docker-images-with-buildx-27d80f7e2408 for more info
	if err := util.VerboseCommand("docker",
		"run", "--rm", "--privileged", "multiarch/qemu-user-static", "--reset", "-p", "yes").Run(); err != nil {
		return fmt.Errorf("failed to run qemu-user-static container: %v", err)
	}
	if err := util.VerboseCommand("docker",
		"buildx", "create", "--name", "multi-arch", "--platform", "linux/amd64,linux/arm64", "--use").Run(); err != nil {
		return fmt.Errorf("failed to set multi-arch as current builder instance: %v", err)
	}

	// Run the script to create the base images
	buildImageEnv := []string{
		"DOCKER_ARCHITECTURES=linux/amd64,linux/arm64",
		"HUBS=docker.io/istio gcr.io/istio-release",
	}
	if manifest.Version == "master" {
		// Push :latest tag for master
		buildImageEnv = append(buildImageEnv, fmt.Sprintf("TAGS=%s %s", tag, "latest"))
	} else {
		buildImageEnv = append(buildImageEnv, "TAG="+tag)
	}
	cmd := util.VerboseCommand("tools/build-base-images.sh")
	cmd.Env = util.StandardEnv(manifest)
	cmd.Env = append(cmd.Env, buildImageEnv...)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	cmd.Dir = istioDir
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("failed to build base images: %v", err)
	}

	// Now create a PR to update the TAG to use the new images
	sedString := "s/BASE_VERSION ?=.*/BASE_VERSION ?= " + tag + "/"
	sedCmd := util.VerboseCommand("sed", "-i", sedString, "Makefile.core.mk")
	sedCmd.Dir = istioDir
	if err := sedCmd.Run(); err != nil {
		return fmt.Errorf("failed to run sed command: %v", err)
	}

	if err := util.CreatePR(
		manifest,
		"istio",
		"newBaseVersion"+tag,
		"Update BASE_VERSION to "+tag,
		fmt.Sprintf("```\n%s\n```", trivyScanOutput),
		false,
		githubToken,
		git,
		branch,
		[]string{"auto-merge"}); err != nil {
		return fmt.Errorf("failed PR creation: %v", err)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.

package commands

import (
	"fmt"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/scaleway/scaleway-cli/pkg/api"
	"github.com/scaleway/scaleway-cli/pkg/utils"
)

// ExecArgs are flags for the `RunExec` function
type ExecArgs struct {
	Timeout float64
	Wait    bool
	Gateway string
	Server  string
	Command []string
}

// RunExec is the handler for 'scw exec'
func RunExec(ctx CommandContext, args ExecArgs) error {
	var fingerprints []string

	done := make(chan struct{})

	serverID, err := ctx.API.GetServerID(args.Server)
	if err != nil {
		return err
	}

	go func() {
		fingerprints = ctx.API.GetSSHFingerprintFromServer(serverID)
		close(done)
	}()
	// Resolve gateway
	if args.Gateway == "" {
		args.Gateway = ctx.Getenv("SCW_GATEWAY")
	}
	var gateway string

	if args.Gateway == serverID || args.Gateway == args.Server {
		log.Debugf("The server and the gateway are the same host, using direct access to the server")
		gateway = ""
	} else {
		gateway, err = api.ResolveGateway(ctx.API, args.Gateway)
		if err != nil {
			return fmt.Errorf("Cannot resolve Gateway '%s': %v", args.Gateway, err)
		}
		if gateway != "" {
			log.Debugf("The server will be accessed using the gateway '%s' as a SSH relay", gateway)
		}
	}

	var server *api.ScalewayServer
	if args.Wait {
		// --wait
		log.Debugf("Waiting for server to be ready")
		server, err = api.WaitForServerReady(ctx.API, serverID, gateway)
		if err != nil {
			return fmt.Errorf("Failed to wait for server to be ready, %v", err)
		}
	} else {
		// no --wait
		log.Debugf("scw won't wait for the server to be ready, if it is not, the command will fail")
		server, err = ctx.API.GetServer(serverID)
		if err != nil {
			return fmt.Errorf("Failed to get server information for %s: %v", serverID, err)
		}
	}

	if server.PublicAddress.IP == "" && gateway == "" {
		log.Warn(`Your host has no public IP address, you should use '--gateway', see 'scw help exec'`)
	}

	// --timeout
	if args.Timeout > 0 {
		log.Debugf("Setting up a global timeout of %d seconds", args.Timeout)
		// FIXME: avoid use of log.Fatalf here
		go func() {
			time.Sleep(time.Duration(args.Timeout*1000) * time.Millisecond)
			log.Fatalf("Operation timed out")
		}()
	}

	<-done
	if len(fingerprints) > 0 {
		for i := range fingerprints {
			fmt.Fprintf(ctx.Stdout, "%s\n", fingerprints[i])
		}
	}
	if err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.Command, !args.Wait, gateway); err != nil {
		return fmt.Errorf("Failed to run the command: %v", err)
	}

	log.Debugf("Command successfuly executed")
	return nil
}
<commit_msg>exec: Add warning to flush cache<commit_after>// Copyright (C) 2015 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.

package commands

import (
	"fmt"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/scaleway/scaleway-cli/pkg/api"
	"github.com/scaleway/scaleway-cli/pkg/utils"
)

// ExecArgs are flags for the `RunExec` function
type ExecArgs struct {
	Timeout float64
	Wait    bool
	Gateway string
	Server  string
	Command []string
}

// RunExec is the handler for 'scw exec'
func RunExec(ctx CommandContext, args ExecArgs) error {
	var fingerprints []string

	done := make(chan struct{})

	serverID, err := ctx.API.GetServerID(args.Server)
	if err != nil {
		return err
	}

	go func() {
		fingerprints = ctx.API.GetSSHFingerprintFromServer(serverID)
		close(done)
	}()
	// Resolve gateway
	if args.Gateway == "" {
		args.Gateway = ctx.Getenv("SCW_GATEWAY")
	}
	var gateway string

	if args.Gateway == serverID || args.Gateway == args.Server {
		log.Debugf("The server and the gateway are the same host, using direct access to the server")
		gateway = ""
	} else {
		gateway, err = api.ResolveGateway(ctx.API, args.Gateway)
		if err != nil {
			return fmt.Errorf("Cannot resolve Gateway '%s': %v", args.Gateway, err)
		}
		if gateway != "" {
			log.Debugf("The server will be accessed using the gateway '%s' as a SSH relay", gateway)
		}
	}

	var server *api.ScalewayServer
	if args.Wait {
		// --wait
		log.Debugf("Waiting for server to be ready")
		server, err = api.WaitForServerReady(ctx.API, serverID, gateway)
		if err != nil {
			return fmt.Errorf("Failed to wait for server to be ready, %v", err)
		}
	} else {
		// no --wait
		log.Debugf("scw won't wait for the server to be ready, if it is not, the command will fail")
		server, err = ctx.API.GetServer(serverID)
		if err != nil {
			rerr := fmt.Errorf("Failed to get server information for %s: %v", serverID, err)
			if err.Error() == `"`+serverID+`" not found` {
				return fmt.Errorf("%v\nmaybe try to flush the cache with : scw _flush-cache", rerr)
			}
			return rerr
		}
	}

	if server.PublicAddress.IP == "" && gateway == "" {
		log.Warn(`Your host has no public IP address, you should use '--gateway', see 'scw help exec'`)
	}

	// --timeout
	if args.Timeout > 0 {
		log.Debugf("Setting up a global timeout of %d seconds", args.Timeout)
		// FIXME: avoid use of log.Fatalf here
		go func() {
			time.Sleep(time.Duration(args.Timeout*1000) * time.Millisecond)
			log.Fatalf("Operation timed out")
		}()
	}

	<-done
	if len(fingerprints) > 0 {
		for i := range fingerprints {
			fmt.Fprintf(ctx.Stdout, "%s\n", fingerprints[i])
		}
	}
	if err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.Command, !args.Wait, gateway); err != nil {
		return fmt.Errorf("Failed to run the command: %v", err)
	}

	log.Debugf("Command successfuly executed")
	return nil
}
<|endoftext|>