Initial import

This commit is contained in:
2018-03-01 22:52:31 +01:00
commit 2cb199692a
130 changed files with 44384 additions and 0 deletions

35
vendor/sourcegraph.com/sourcegraph/go-diff/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,35 @@
Copyright (c) 2014 Sourcegraph, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-----------------------------------------------------------------
Portions adapted from python-unidiff:
Copyright (c) 2012 Matias Bordese
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

View File

@@ -0,0 +1,76 @@
package diff
import "bytes"
// NOTE: types are code-generated in diff.pb.go.
//go:generate protoc -I../../../.. -I ../../../../github.com/gogo/protobuf/protobuf -I. --gogo_out=. diff.proto
// Stat computes the number of lines added/changed/deleted in all
// hunks in this file's diff.
func (d *FileDiff) Stat() Stat {
total := Stat{}
for _, h := range d.Hunks {
total.add(h.Stat())
}
return total
}
// Stat computes the number of lines added/changed/deleted in this
// hunk.
func (h *Hunk) Stat() Stat {
lines := bytes.Split(h.Body, []byte{'\n'})
var last byte
st := Stat{}
for _, line := range lines {
if len(line) == 0 {
last = 0
continue
}
switch line[0] {
case '-':
if last == '+' {
st.Added--
st.Changed++
last = 0 // next line can't change this one since this is already a change
} else {
st.Deleted++
last = line[0]
}
case '+':
if last == '-' {
st.Deleted--
st.Changed++
last = 0 // next line can't change this one since this is already a change
} else {
st.Added++
last = line[0]
}
default:
last = 0
}
}
return st
}
var (
hunkPrefix = []byte("@@ ")
)
const hunkHeader = "@@ -%d,%d +%d,%d @@"
// diffTimeParseLayout is the layout used to parse the time in unified diff file
// header timestamps.
// See https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html.
const diffTimeParseLayout = "2006-01-02 15:04:05 -0700"
// diffTimeFormatLayout is the layout used to format (i.e., print) the time in unified diff file
// header timestamps.
// See https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html.
const diffTimeFormatLayout = "2006-01-02 15:04:05.000000000 -0700"
func (s *Stat) add(o Stat) {
s.Added += o.Added
s.Changed += o.Changed
s.Deleted += o.Deleted
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
syntax = "proto3";
package diff;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "sourcegraph.com/sqs/pbtypes/timestamp.proto";
option (gogoproto.goproto_getters_all) = false;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
// A FileDiff represents a unified diff for a single file.
//
// A file unified diff has a header that resembles the following:
//
// --- oldname 2009-10-11 15:12:20.000000000 -0700
// +++ newname 2009-10-11 15:12:30.000000000 -0700
message FileDiff {
// the original name of the file
string OrigName = 1;
// the original timestamp (nil if not present)
pbtypes.Timestamp OrigTime = 2;
// the new name of the file (often same as OrigName)
string NewName = 3;
// the new timestamp (nil if not present)
pbtypes.Timestamp NewTime = 4;
// extended header lines (e.g., git's "new mode <mode>", "rename from <path>", etc.)
repeated string Extended = 5;
// hunks that were changed from orig to new
repeated Hunk Hunks = 6;
}
// A Hunk represents a series of changes (additions or deletions) in a file's
// unified diff.
message Hunk {
// starting line number in original file
int32 OrigStartLine = 1;
// number of lines the hunk applies to in the original file
int32 OrigLines = 2;
// if > 0, then the original file had a 'No newline at end of file' mark at this offset
int32 OrigNoNewlineAt = 3;
// starting line number in new file
int32 NewStartLine = 4;
// number of lines the hunk applies to in the new file
int32 NewLines = 5;
// optional section heading
string Section = 6;
// 0-indexed line offset in unified file diff (including section headers); this is
// only set when Hunks are read from entire file diff (i.e., when ReadAllHunks is
// called) This accounts for hunk headers, too, so the StartPosition of the first
// hunk will be 1.
int32 StartPosition = 7;
// hunk body (lines prefixed with '-', '+', or ' ')
bytes Body = 8;
}
// A Stat is a diff stat that represents the number of lines added/changed/deleted.
message Stat {
// number of lines added
int32 Added = 1 [(gogoproto.jsontag) = ""];
// number of lines changed
int32 Changed = 2 [(gogoproto.jsontag) = ""];
// number of lines deleted
int32 Deleted = 3 [(gogoproto.jsontag) = ""];
}

View File

@@ -0,0 +1,605 @@
package diff
import (
"bytes"
"io/ioutil"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
"github.com/shurcooL/go-goon"
"sourcegraph.com/sqs/pbtypes"
)
func init() {
// Diffs include times that by default are generated in the local
// timezone. To ensure that tests behave the same in all timezones
// (compared to the hard-coded expected output), force the test
// timezone to UTC.
//
// This is safe to do in tests but should not (and need not) be
// done for the main code.
time.Local = time.UTC
}
func TestParseHunkNoChunksize(t *testing.T) {
filename := "sample_no_chunksize.diff"
diffData, err := ioutil.ReadFile(filepath.Join("testdata", filename))
if err != nil {
t.Fatal(err)
}
diff, err := ParseHunks(diffData)
if err != nil {
t.Errorf("%s: got ParseHunks err %v, want %v", filename, err, nil)
}
if len(diff) != 1 {
t.Fatalf("%s: Got %d hunks, want only one", filename, len(diff))
}
correct := &Hunk{
NewLines: 1,
NewStartLine: 1,
OrigLines: 0,
OrigStartLine: 0,
StartPosition: 1,
}
h := diff[0]
h.Body = nil // We're not testing the body.
if !reflect.DeepEqual(h, correct) {
t.Errorf("%s: Got %#v, want %#v", filename, h, correct)
}
}
func TestParseHunksAndPrintHunks(t *testing.T) {
tests := []struct {
filename string
wantParseErr error
}{
{filename: "sample_hunk.diff"},
{filename: "sample_hunks.diff"},
{filename: "sample_bad_hunks.diff"},
{filename: "sample_hunks_no_newline.diff"},
{filename: "no_newline_both.diff"},
{filename: "no_newline_both2.diff"},
{filename: "no_newline_orig.diff"},
{filename: "no_newline_new.diff"},
{filename: "empty_orig.diff"},
{filename: "empty_new.diff"},
{filename: "oneline_hunk.diff"},
{filename: "empty.diff"},
}
for _, test := range tests {
diffData, err := ioutil.ReadFile(filepath.Join("testdata", test.filename))
if err != nil {
t.Fatal(err)
}
diff, err := ParseHunks(diffData)
if err != test.wantParseErr {
t.Errorf("%s: got ParseHunks err %v, want %v", test.filename, err, test.wantParseErr)
continue
}
if test.wantParseErr != nil {
continue
}
printed, err := PrintHunks(diff)
if err != nil {
t.Errorf("%s: PrintHunks: %s", test.filename, err)
}
if !bytes.Equal(printed, diffData) {
t.Errorf("%s: printed diff hunks != original diff hunks\n\n# PrintHunks output:\n%s\n\n# Original:\n%s", test.filename, printed, diffData)
}
}
}
func TestParseFileDiffHeaders(t *testing.T) {
tests := []struct {
filename string
wantDiff *FileDiff
}{
{
filename: "sample_file.diff",
wantDiff: &FileDiff{
OrigName: "oldname",
OrigTime: &pbtypes.Timestamp{Seconds: 1255273940},
NewName: "newname",
NewTime: &pbtypes.Timestamp{Seconds: 1255273950},
},
},
{
filename: "sample_file_no_fractional_seconds.diff",
wantDiff: &FileDiff{
OrigName: "goyaml.go",
OrigTime: &pbtypes.Timestamp{Seconds: 1322164040},
NewName: "goyaml.go",
NewTime: &pbtypes.Timestamp{Seconds: 1322486679},
},
},
{
filename: "sample_file_extended.diff",
wantDiff: &FileDiff{
OrigName: "oldname",
OrigTime: &pbtypes.Timestamp{Seconds: 1255273940},
NewName: "newname",
NewTime: &pbtypes.Timestamp{Seconds: 1255273950},
Extended: []string{
"diff --git a/vcs/git_cmd.go b/vcs/git_cmd.go",
"index aa4de15..7c048ab 100644",
},
},
},
{
filename: "sample_file_extended_empty_new.diff",
wantDiff: &FileDiff{
OrigName: "/dev/null",
OrigTime: nil,
NewName: "b/vendor/go/build/testdata/empty/dummy",
NewTime: nil,
Extended: []string{
"diff --git a/vendor/go/build/testdata/empty/dummy b/vendor/go/build/testdata/empty/dummy",
"new file mode 100644",
"index 0000000..e69de29",
},
},
},
{
filename: "sample_file_extended_empty_new_binary.diff",
wantDiff: &FileDiff{
OrigName: "/dev/null",
OrigTime: nil,
NewName: "b/diff/binary-image.png",
NewTime: nil,
Extended: []string{
"diff --git a/diff/binary-image.png b/diff/binary-image.png",
"new file mode 100644",
"index 0000000..b51756e",
"Binary files /dev/null and b/diff/binary-image.png differ",
},
},
},
{
filename: "sample_file_extended_empty_deleted.diff",
wantDiff: &FileDiff{
OrigName: "a/vendor/go/build/testdata/empty/dummy",
OrigTime: nil,
NewName: "/dev/null",
NewTime: nil,
Extended: []string{
"diff --git a/vendor/go/build/testdata/empty/dummy b/vendor/go/build/testdata/empty/dummy",
"deleted file mode 100644",
"index e69de29..0000000",
},
},
},
{
filename: "sample_file_extended_empty_deleted_binary.diff",
wantDiff: &FileDiff{
OrigName: "a/187/player/random/gopher-0.png",
OrigTime: nil,
NewName: "/dev/null",
NewTime: nil,
Extended: []string{
"diff --git a/187/player/random/gopher-0.png b/187/player/random/gopher-0.png",
"deleted file mode 100644",
"index aebdfc7..0000000",
"Binary files a/187/player/random/gopher-0.png and /dev/null differ",
},
},
},
{
filename: "sample_file_extended_empty_rename.diff",
wantDiff: &FileDiff{
OrigName: "a/docs/integrations/Email_Notifications.md",
OrigTime: nil,
NewName: "b/docs/integrations/email-notifications.md",
NewTime: nil,
Extended: []string{
"diff --git a/docs/integrations/Email_Notifications.md b/docs/integrations/email-notifications.md",
"similarity index 100%",
"rename from docs/integrations/Email_Notifications.md",
"rename to docs/integrations/email-notifications.md",
},
},
},
}
for _, test := range tests {
diffData, err := ioutil.ReadFile(filepath.Join("testdata", test.filename))
if err != nil {
t.Fatal(err)
}
diff, err := ParseFileDiff(diffData)
if err != nil {
t.Fatalf("%s: got ParseFileDiff error %v", test.filename, err)
}
diff.Hunks = nil
if got, want := diff, test.wantDiff; !reflect.DeepEqual(got, want) {
t.Errorf("%s:\n\ngot: %v\nwant: %v", test.filename, goon.Sdump(got), goon.Sdump(want))
}
}
}
func TestParseMultiFileDiffHeaders(t *testing.T) {
tests := []struct {
filename string
wantDiffs []*FileDiff
}{
{
filename: "sample_multi_file_new.diff",
wantDiffs: []*FileDiff{
{
OrigName: "/dev/null",
OrigTime: nil,
NewName: "b/_vendor/go/build/syslist_test.go",
NewTime: nil,
Extended: []string{
"diff --git a/_vendor/go/build/syslist_test.go b/_vendor/go/build/syslist_test.go",
"new file mode 100644",
"index 0000000..3be2928",
},
},
{
OrigName: "/dev/null",
OrigTime: nil,
NewName: "b/_vendor/go/build/testdata/empty/dummy",
NewTime: nil,
Extended: []string{
"diff --git a/_vendor/go/build/testdata/empty/dummy b/_vendor/go/build/testdata/empty/dummy",
"new file mode 100644",
"index 0000000..e69de29",
},
},
{
OrigName: "/dev/null",
OrigTime: nil,
NewName: "b/_vendor/go/build/testdata/multi/file.go",
NewTime: nil,
Extended: []string{
"diff --git a/_vendor/go/build/testdata/multi/file.go b/_vendor/go/build/testdata/multi/file.go",
"new file mode 100644",
"index 0000000..ee946eb",
},
},
},
},
{
filename: "sample_multi_file_deleted.diff",
wantDiffs: []*FileDiff{
{
OrigName: "a/vendor/go/build/syslist_test.go",
OrigTime: nil,
NewName: "/dev/null",
NewTime: nil,
Extended: []string{
"diff --git a/vendor/go/build/syslist_test.go b/vendor/go/build/syslist_test.go",
"deleted file mode 100644",
"index 3be2928..0000000",
},
},
{
OrigName: "a/vendor/go/build/testdata/empty/dummy",
OrigTime: nil,
NewName: "/dev/null",
NewTime: nil,
Extended: []string{
"diff --git a/vendor/go/build/testdata/empty/dummy b/vendor/go/build/testdata/empty/dummy",
"deleted file mode 100644",
"index e69de29..0000000",
},
},
{
OrigName: "a/vendor/go/build/testdata/multi/file.go",
OrigTime: nil,
NewName: "/dev/null",
NewTime: nil,
Extended: []string{
"diff --git a/vendor/go/build/testdata/multi/file.go b/vendor/go/build/testdata/multi/file.go",
"deleted file mode 100644",
"index ee946eb..0000000",
},
},
},
},
{
filename: "sample_multi_file_rename.diff",
wantDiffs: []*FileDiff{
{
OrigName: "a/README.md",
OrigTime: nil,
NewName: "b/README.md",
NewTime: nil,
Extended: []string{
"diff --git a/README.md b/README.md",
"index 5f3d591..96a24fa 100644",
},
},
{
OrigName: "a/docs/integrations/Email_Notifications.md",
OrigTime: nil,
NewName: "b/docs/integrations/email-notifications.md",
NewTime: nil,
Extended: []string{
"diff --git a/docs/integrations/Email_Notifications.md b/docs/integrations/email-notifications.md",
"similarity index 100%",
"rename from docs/integrations/Email_Notifications.md",
"rename to docs/integrations/email-notifications.md",
},
},
{
OrigName: "a/release_notes.md",
OrigTime: nil,
NewName: "b/release_notes.md",
NewTime: nil,
Extended: []string{
"diff --git a/release_notes.md b/release_notes.md",
"index f2ff13f..f060cb5 100644",
},
},
},
},
{
filename: "sample_multi_file_binary.diff",
wantDiffs: []*FileDiff{
{
OrigName: "a/README.md",
OrigTime: nil,
NewName: "b/README.md",
NewTime: nil,
Extended: []string{
"diff --git a/README.md b/README.md",
"index 7b73e04..36cde13 100644",
},
},
{
OrigName: "a/data/Font.png",
OrigTime: nil,
NewName: "b/data/Font.png",
NewTime: nil,
Extended: []string{
"diff --git a/data/Font.png b/data/Font.png",
"index 17a971d..599f8dd 100644",
"Binary files a/data/Font.png and b/data/Font.png differ",
},
},
{
OrigName: "a/main.go",
OrigTime: nil,
NewName: "b/main.go",
NewTime: nil,
Extended: []string{
"diff --git a/main.go b/main.go",
"index 1aced1e..98a982e 100644",
},
},
},
},
}
for _, test := range tests {
diffData, err := ioutil.ReadFile(filepath.Join("testdata", test.filename))
if err != nil {
t.Fatal(err)
}
diffs, err := ParseMultiFileDiff(diffData)
if err != nil {
t.Fatalf("%s: got ParseMultiFileDiff error %v", test.filename, err)
}
for i := range diffs {
diffs[i].Hunks = nil // This test focuses on things other than hunks, so don't compare them.
}
if got, want := diffs, test.wantDiffs; !reflect.DeepEqual(got, want) {
t.Errorf("%s:\n\ngot: %v\nwant: %v", test.filename, goon.Sdump(got), goon.Sdump(want))
}
}
}
func TestParseFileDiffAndPrintFileDiff(t *testing.T) {
tests := []struct {
filename string
wantParseErr error
}{
{filename: "sample_file.diff"},
{filename: "sample_file_no_timestamp.diff"},
{filename: "sample_file_extended.diff"},
{filename: "sample_file_extended_empty_new.diff"},
{filename: "sample_file_extended_empty_new_binary.diff"},
{filename: "sample_file_extended_empty_deleted.diff"},
{filename: "sample_file_extended_empty_deleted_binary.diff"},
{filename: "sample_file_extended_empty_rename.diff"},
{filename: "sample_file_extended_empty_binary.diff"},
{
filename: "empty.diff",
wantParseErr: &ParseError{0, 0, ErrExtendedHeadersEOF},
},
}
for _, test := range tests {
diffData, err := ioutil.ReadFile(filepath.Join("testdata", test.filename))
if err != nil {
t.Fatal(err)
}
diff, err := ParseFileDiff(diffData)
if !reflect.DeepEqual(err, test.wantParseErr) {
t.Errorf("%s: got ParseFileDiff err %v, want %v", test.filename, err, test.wantParseErr)
continue
}
if test.wantParseErr != nil {
continue
}
printed, err := PrintFileDiff(diff)
if err != nil {
t.Errorf("%s: PrintFileDiff: %s", test.filename, err)
}
if !bytes.Equal(printed, diffData) {
t.Errorf("%s: printed file diff != original file diff\n\n# PrintFileDiff output:\n%s\n\n# Original:\n%s", test.filename, printed, diffData)
}
}
}
func TestParseMultiFileDiffAndPrintMultiFileDiff(t *testing.T) {
tests := []struct {
filename string
wantParseErr error
wantFileDiffs int // How many instances of diff.FileDiff are expected.
}{
{filename: "sample_multi_file.diff", wantFileDiffs: 2},
{filename: "sample_multi_file_single.diff", wantFileDiffs: 1},
{filename: "sample_multi_file_new.diff", wantFileDiffs: 3},
{filename: "sample_multi_file_deleted.diff", wantFileDiffs: 3},
{filename: "sample_multi_file_rename.diff", wantFileDiffs: 3},
{filename: "sample_multi_file_binary.diff", wantFileDiffs: 3},
{filename: "long_line_multi.diff", wantFileDiffs: 3},
{filename: "empty.diff", wantFileDiffs: 0},
{filename: "empty_multi.diff", wantFileDiffs: 2},
}
for _, test := range tests {
diffData, err := ioutil.ReadFile(filepath.Join("testdata", test.filename))
if err != nil {
t.Fatal(err)
}
diffs, err := ParseMultiFileDiff(diffData)
if err != test.wantParseErr {
t.Errorf("%s: got ParseMultiFileDiff err %v, want %v", test.filename, err, test.wantParseErr)
continue
}
if test.wantParseErr != nil {
continue
}
if got, want := len(diffs), test.wantFileDiffs; got != want {
t.Errorf("%s: got %v instances of diff.FileDiff, expected %v", test.filename, got, want)
}
printed, err := PrintMultiFileDiff(diffs)
if err != nil {
t.Errorf("%s: PrintMultiFileDiff: %s", test.filename, err)
}
if !bytes.Equal(printed, diffData) {
t.Errorf("%s: printed multi-file diff != original multi-file diff\n\n# PrintMultiFileDiff output:\n%s\n\n# Original:\n%s", test.filename, printed, diffData)
}
}
}
func TestNoNewlineAtEnd(t *testing.T) {
diffs := map[string]struct {
diff string
trailingNewlineOK bool
}{
"orig": {
diff: `@@ -1,1 +1,1 @@
-a
\ No newline at end of file
+b
`,
trailingNewlineOK: true,
},
"new": {
diff: `@@ -1,1 +1,1 @@
-a
+b
\ No newline at end of file
`,
},
"both": {
diff: `@@ -1,1 +1,1 @@
-a
\ No newline at end of file
+b
\ No newline at end of file
`,
},
}
for label, test := range diffs {
hunks, err := ParseHunks([]byte(test.diff))
if err != nil {
t.Errorf("%s: ParseHunks: %s", label, err)
continue
}
for _, hunk := range hunks {
if body := string(hunk.Body); strings.Contains(body, "No newline") {
t.Errorf("%s: after parse, hunk body contains 'No newline...' string\n\nbody is:\n%s", label, body)
}
if !test.trailingNewlineOK {
if bytes.HasSuffix(hunk.Body, []byte{'\n'}) {
t.Errorf("%s: after parse, hunk body ends with newline\n\nbody is:\n%s", label, hunk.Body)
}
}
if dontWant := []byte("-a+b"); bytes.Contains(hunk.Body, dontWant) {
t.Errorf("%s: hunk body contains %q\n\nbody is:\n%s", label, dontWant, hunk.Body)
}
printed, err := PrintHunks(hunks)
if err != nil {
t.Errorf("%s: PrintHunks: %s", label, err)
continue
}
if printed := string(printed); printed != test.diff {
t.Errorf("%s: printed diff hunks != original diff hunks\n\n# PrintHunks output:\n%s\n\n# Original:\n%s", label, printed, test.diff)
}
}
}
}
func TestFileDiff_Stat(t *testing.T) {
tests := map[string]struct {
hunks []*Hunk
want Stat
}{
"no change": {
hunks: []*Hunk{
{Body: []byte(`@@ -0,0 +0,0
a
b
`)},
},
want: Stat{},
},
"added/deleted": {
hunks: []*Hunk{
{Body: []byte(`@@ -0,0 +0,0
+a
b
-c
d
`)},
},
want: Stat{Added: 1, Deleted: 1},
},
"changed": {
hunks: []*Hunk{
{Body: []byte(`@@ -0,0 +0,0
+a
+b
-c
-d
e
`)},
},
want: Stat{Added: 1, Changed: 1, Deleted: 1},
},
"many changes": {
hunks: []*Hunk{
{Body: []byte(`@@ -0,0 +0,0
+a
-b
+c
-d
e
`)},
},
want: Stat{Added: 0, Changed: 2, Deleted: 0},
},
}
for label, test := range tests {
fdiff := &FileDiff{Hunks: test.hunks}
stat := fdiff.Stat()
if !reflect.DeepEqual(stat, test.want) {
t.Errorf("%s: got diff stat %+v, want %+v", label, stat, test.want)
continue
}
}
}

View File

@@ -0,0 +1,2 @@
// Package diff provides a parser for unified diffs.
package diff // import "sourcegraph.com/sourcegraph/go-diff/diff"

View File

@@ -0,0 +1,625 @@
package diff
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"time"
"sourcegraph.com/sqs/pbtypes"
)
// ParseMultiFileDiff parses a multi-file unified diff. It returns an error if parsing failed as a whole, but does its
// best to parse as many files in the case of per-file errors. In the case of non-fatal per-file errors, the error
// return value is null and the Errs field in the returned MultiFileDiff is set.
func ParseMultiFileDiff(diff []byte) ([]*FileDiff, error) {
return NewMultiFileDiffReader(bytes.NewReader(diff)).ReadAllFiles()
}
// NewMultiFileDiffReader returns a new MultiFileDiffReader that reads
// a multi-file unified diff from r.
func NewMultiFileDiffReader(r io.Reader) *MultiFileDiffReader {
return &MultiFileDiffReader{reader: bufio.NewReader(r)}
}
// MultiFileDiffReader reads a multi-file unified diff.
type MultiFileDiffReader struct {
line int
offset int64
reader *bufio.Reader
// TODO(sqs): line and offset tracking in multi-file diffs is broken; add tests and fix
// nextFileFirstLine is a line that was read by a HunksReader that
// was how it determined the hunk was complete. But to determine
// that, it needed to read the first line of the next file. We
// store nextFileFirstLine so we can "give the first line back" to
// the next file.
nextFileFirstLine []byte
}
// ReadFile reads the next file unified diff (including headers and
// all hunks) from r. If there are no more files in the diff, it
// returns error io.EOF.
func (r *MultiFileDiffReader) ReadFile() (*FileDiff, error) {
fr := &FileDiffReader{
line: r.line,
offset: r.offset,
reader: r.reader,
fileHeaderLine: r.nextFileFirstLine,
}
r.nextFileFirstLine = nil
fd, err := fr.ReadAllHeaders()
if err != nil {
switch e := err.(type) {
case *ParseError:
if e.Err == ErrNoFileHeader || e.Err == ErrExtendedHeadersEOF {
return nil, io.EOF
}
case OverflowError:
r.nextFileFirstLine = []byte(e)
return fd, nil
default:
return nil, err
}
}
// Before reading hunks, check to see if there are any. If there
// aren't any, and there's another file after this file in the
// diff, then the hunks reader will complain ErrNoHunkHeader. It's
// not easy for us to tell from that error alone if that was
// caused by the lack of any hunks, or a malformatted hunk, so we
// need to perform the check here.
hr := fr.HunksReader()
line, err := readLine(r.reader)
if err != nil {
return fd, err
}
line = bytes.TrimSuffix(line, []byte{'\n'})
if bytes.HasPrefix(line, hunkPrefix) {
hr.nextHunkHeaderLine = line
fd.Hunks, err = hr.ReadAllHunks()
r.line = fr.line
r.offset = fr.offset
if err != nil {
if e0, ok := err.(*ParseError); ok {
if e, ok := e0.Err.(*ErrBadHunkLine); ok {
// This just means we finished reading the hunks for the
// current file. See the ErrBadHunkLine doc for more info.
r.nextFileFirstLine = e.Line
return fd, nil
}
}
return nil, err
}
} else {
// There weren't any hunks, so that line we peeked ahead at
// actually belongs to the next file. Put it back.
r.nextFileFirstLine = line
}
return fd, nil
}
// ReadAllFiles reads all file unified diffs (including headers and all
// hunks) remaining in r.
func (r *MultiFileDiffReader) ReadAllFiles() ([]*FileDiff, error) {
var ds []*FileDiff
for {
d, err := r.ReadFile()
if d != nil {
ds = append(ds, d)
}
if err == io.EOF {
return ds, nil
}
if err != nil {
return nil, err
}
}
}
// ParseFileDiff parses a file unified diff.
func ParseFileDiff(diff []byte) (*FileDiff, error) {
return NewFileDiffReader(bytes.NewReader(diff)).Read()
}
// NewFileDiffReader returns a new FileDiffReader that reads a file
// unified diff.
func NewFileDiffReader(r io.Reader) *FileDiffReader {
return &FileDiffReader{reader: bufio.NewReader(r)}
}
// FileDiffReader reads a unified file diff.
type FileDiffReader struct {
line int
offset int64
reader *bufio.Reader
// fileHeaderLine is the first file header line, set by:
//
// (1) ReadExtendedHeaders if it encroaches on a file header line
// (which it must to detect when extended headers are done); or
// (2) (*MultiFileDiffReader).ReadFile() if it encroaches on a
// file header line while reading the previous file's hunks (in a
// multi-file diff).
fileHeaderLine []byte
}
// Read reads a file unified diff, including headers and hunks, from r.
func (r *FileDiffReader) Read() (*FileDiff, error) {
fd, err := r.ReadAllHeaders()
if err != nil {
return nil, err
}
fd.Hunks, err = r.HunksReader().ReadAllHunks()
if err != nil {
return nil, err
}
return fd, nil
}
// ReadAllHeaders reads the file headers and extended headers (if any)
// from a file unified diff. It does not read hunks, and the returned
// FileDiff's Hunks field is nil. To read the hunks, call the
// (*FileDiffReader).HunksReader() method to get a HunksReader and
// read hunks from that.
func (r *FileDiffReader) ReadAllHeaders() (*FileDiff, error) {
var err error
fd := &FileDiff{}
fd.Extended, err = r.ReadExtendedHeaders()
if pe, ok := err.(*ParseError); ok && pe.Err == ErrExtendedHeadersEOF {
wasEmpty := handleEmpty(fd)
if wasEmpty {
return fd, nil
}
return fd, err
} else if _, ok := err.(OverflowError); ok {
handleEmpty(fd)
return fd, err
} else if err != nil {
return fd, err
}
var origTime, newTime *time.Time
fd.OrigName, fd.NewName, origTime, newTime, err = r.ReadFileHeaders()
if err != nil {
return nil, err
}
if origTime != nil {
ts := pbtypes.NewTimestamp(*origTime)
fd.OrigTime = &ts
}
if newTime != nil {
ts := pbtypes.NewTimestamp(*newTime)
fd.NewTime = &ts
}
return fd, nil
}
// HunksReader returns a new HunksReader that reads hunks from r. The
// HunksReader's line and offset (used in error messages) is set to
// start where the file diff header ended (which means errors have the
// correct position information).
func (r *FileDiffReader) HunksReader() *HunksReader {
return &HunksReader{
line: r.line,
offset: r.offset,
reader: r.reader,
}
}
// ReadFileHeaders reads the unified file diff header (the lines that
// start with "---" and "+++" with the orig/new file names and
// timestamps).
func (r *FileDiffReader) ReadFileHeaders() (origName, newName string, origTimestamp, newTimestamp *time.Time, err error) {
origName, origTimestamp, err = r.readOneFileHeader([]byte("--- "))
if err != nil {
return "", "", nil, nil, err
}
newName, newTimestamp, err = r.readOneFileHeader([]byte("+++ "))
if err != nil {
return "", "", nil, nil, err
}
return origName, newName, origTimestamp, newTimestamp, nil
}
// readOneFileHeader reads one of the file headers (prefix should be
// either "+++ " or "--- ").
func (r *FileDiffReader) readOneFileHeader(prefix []byte) (filename string, timestamp *time.Time, err error) {
var line []byte
if r.fileHeaderLine == nil {
var err error
line, err = readLine(r.reader)
if err == io.EOF {
return "", nil, &ParseError{r.line, r.offset, ErrNoFileHeader}
} else if err != nil {
return "", nil, err
}
} else {
line = r.fileHeaderLine
r.fileHeaderLine = nil
}
if !bytes.HasPrefix(line, prefix) {
return "", nil, &ParseError{r.line, r.offset, ErrBadFileHeader}
}
r.offset += int64(len(line))
r.line++
line = line[len(prefix):]
trimmedLine := strings.TrimSpace(string(line)) // filenames that contain spaces may be terminated by a tab
parts := strings.SplitN(trimmedLine, "\t", 2)
filename = parts[0]
if len(parts) == 2 {
// Timestamp is optional, but this header has it.
ts, err := time.Parse(diffTimeParseLayout, parts[1])
if err != nil {
return "", nil, err
}
timestamp = &ts
}
return filename, timestamp, err
}
// OverflowError is returned when we have overflowed into the start
// of the next file while reading extended headers.
type OverflowError string
func (e OverflowError) Error() string {
return fmt.Sprintf("overflowed into next file: %s", e)
}
// ReadExtendedHeaders reads the extended header lines, if any, from a
// unified diff file (e.g., git's "diff --git a/foo.go b/foo.go", "new
// mode <mode>", "rename from <path>", etc.).
func (r *FileDiffReader) ReadExtendedHeaders() ([]string, error) {
var xheaders []string
firstLine := true
for {
var line []byte
if r.fileHeaderLine == nil {
var err error
line, err = readLine(r.reader)
if err == io.EOF {
return xheaders, &ParseError{r.line, r.offset, ErrExtendedHeadersEOF}
} else if err != nil {
return xheaders, err
}
} else {
line = r.fileHeaderLine
r.fileHeaderLine = nil
}
if bytes.HasPrefix(line, []byte("diff --git ")) {
if firstLine {
firstLine = false
} else {
return xheaders, OverflowError(line)
}
}
if bytes.HasPrefix(line, []byte("--- ")) {
// We've reached the file header.
r.fileHeaderLine = line // pass to readOneFileHeader (see fileHeaderLine field doc)
return xheaders, nil
}
r.line++
r.offset += int64(len(line))
xheaders = append(xheaders, string(line))
}
}
// handleEmpty detects when FileDiff was an empty diff and will not have any hunks
// that follow. It updates fd fields from the parsed extended headers.
func handleEmpty(fd *FileDiff) (wasEmpty bool) {
switch {
case (len(fd.Extended) == 3 || len(fd.Extended) == 4 && strings.HasPrefix(fd.Extended[3], "Binary files ")) &&
strings.HasPrefix(fd.Extended[1], "new file mode ") && strings.HasPrefix(fd.Extended[0], "diff --git "):
names := strings.SplitN(fd.Extended[0][len("diff --git "):], " ", 2)
fd.OrigName = "/dev/null"
fd.NewName = names[1]
return true
case (len(fd.Extended) == 3 || len(fd.Extended) == 4 && strings.HasPrefix(fd.Extended[3], "Binary files ")) &&
strings.HasPrefix(fd.Extended[1], "deleted file mode ") && strings.HasPrefix(fd.Extended[0], "diff --git "):
names := strings.SplitN(fd.Extended[0][len("diff --git "):], " ", 2)
fd.OrigName = names[0]
fd.NewName = "/dev/null"
return true
case len(fd.Extended) == 4 && strings.HasPrefix(fd.Extended[2], "rename from ") && strings.HasPrefix(fd.Extended[3], "rename to ") && strings.HasPrefix(fd.Extended[0], "diff --git "):
names := strings.SplitN(fd.Extended[0][len("diff --git "):], " ", 2)
fd.OrigName = names[0]
fd.NewName = names[1]
return true
case len(fd.Extended) == 3 && strings.HasPrefix(fd.Extended[2], "Binary files ") && strings.HasPrefix(fd.Extended[0], "diff --git "):
names := strings.SplitN(fd.Extended[0][len("diff --git "):], " ", 2)
fd.OrigName = names[0]
fd.NewName = names[1]
return true
default:
return false
}
}
var (
// ErrNoFileHeader is when a file unified diff has no file header
// (i.e., the lines that begin with "---" and "+++").
ErrNoFileHeader = errors.New("expected file header, got EOF")
// ErrBadFileHeader is when a file unified diff has a malformed
// file header (i.e., the lines that begin with "---" and "+++").
ErrBadFileHeader = errors.New("bad file header")
// ErrExtendedHeadersEOF is when an EOF was encountered while reading extended file headers, which means that there were no ---/+++ headers encountered before hunks (if any) began.
ErrExtendedHeadersEOF = errors.New("expected file header while reading extended headers, got EOF")
)
// ParseHunks parses hunks from a unified diff. The diff must consist
// only of hunks and not include a file header; if it has a file
// header, use ParseFileDiff.
func ParseHunks(diff []byte) ([]*Hunk, error) {
r := NewHunksReader(bytes.NewReader(diff))
hunks, err := r.ReadAllHunks()
if err != nil {
return nil, err
}
return hunks, nil
}
// NewHunksReader returns a new HunksReader that reads unified diff hunks
// from r.
func NewHunksReader(r io.Reader) *HunksReader {
return &HunksReader{reader: bufio.NewReader(r)}
}
// A HunksReader reads hunks from a unified diff.
type HunksReader struct {
line int
offset int64
hunk *Hunk
reader *bufio.Reader
nextHunkHeaderLine []byte
}
// ReadHunk reads one hunk from r. If there are no more hunks, it
// returns error io.EOF.
func (r *HunksReader) ReadHunk() (*Hunk, error) {
r.hunk = nil
lastLineFromOrig := true
var line []byte
var err error
for {
if r.nextHunkHeaderLine != nil {
// Use stored hunk header line that was scanned in at the
// completion of the previous hunk's ReadHunk.
line = r.nextHunkHeaderLine
r.nextHunkHeaderLine = nil
} else {
line, err = readLine(r.reader)
if err != nil {
if err == io.EOF && r.hunk != nil {
return r.hunk, nil
}
return nil, err
}
}
// Record position.
r.line++
r.offset += int64(len(line))
if r.hunk == nil {
// Check for presence of hunk header.
if !bytes.HasPrefix(line, hunkPrefix) {
return nil, &ParseError{r.line, r.offset, ErrNoHunkHeader}
}
// Parse hunk header.
r.hunk = &Hunk{}
items := []interface{}{
&r.hunk.OrigStartLine, &r.hunk.OrigLines,
&r.hunk.NewStartLine, &r.hunk.NewLines,
}
header, section, err := normalizeHeader(string(line))
if err != nil {
return nil, &ParseError{r.line, r.offset, err}
}
n, err := fmt.Sscanf(header, hunkHeader, items...)
if err != nil {
return nil, err
}
if n < len(items) {
return nil, &ParseError{r.line, r.offset, &ErrBadHunkHeader{header: string(line)}}
}
r.hunk.Section = section
} else {
// Read hunk body line.
if bytes.HasPrefix(line, hunkPrefix) {
// Saw start of new hunk, so this hunk is
// complete. But we've already read in the next hunk's
// header, so we need to be sure that the next call to
// ReadHunk starts with that header.
r.nextHunkHeaderLine = line
// Rewind position.
r.line--
r.offset -= int64(len(line))
return r.hunk, nil
}
if len(line) >= 1 && !linePrefix(line[0]) {
// Bad hunk header line. If we're reading a multi-file
// diff, this may be the end of the current
// file. Return a "rich" error that lets our caller
// handle that case.
return r.hunk, &ParseError{r.line, r.offset, &ErrBadHunkLine{Line: line}}
}
if bytes.Equal(line, []byte(noNewlineMessage)) {
if lastLineFromOrig {
// Retain the newline in the body (otherwise the
// diff line would be like "-a+b", where "+b" is
// the the next line of the new file, which is not
// validly formatted) but record that the orig had
// no newline.
r.hunk.OrigNoNewlineAt = int32(len(r.hunk.Body))
} else {
// Remove previous line's newline.
if len(r.hunk.Body) != 0 {
r.hunk.Body = r.hunk.Body[:len(r.hunk.Body)-1]
}
}
continue
}
if len(line) > 0 {
lastLineFromOrig = line[0] == '-'
}
r.hunk.Body = append(r.hunk.Body, line...)
r.hunk.Body = append(r.hunk.Body, '\n')
}
}
}
const noNewlineMessage = `\ No newline at end of file`
// linePrefixes is the set of all characters a valid line in a diff
// hunk can start with. '\' can appear in diffs when no newline is
// present at the end of a file.
// See: 'http://www.gnu.org/software/diffutils/manual/diffutils.html#Incomplete-Lines'
var linePrefixes = []byte{' ', '-', '+', '\\'}
// linePrefix returns true if 'c' is in 'linePrefixes'.
func linePrefix(c byte) bool {
for _, p := range linePrefixes {
if p == c {
return true
}
}
return false
}
// normalizeHeader takes a header of the form:
// "@@ -linestart[,chunksize] +linestart[,chunksize] @@ section"
// and returns two strings, with the first in the form:
// "@@ -linestart,chunksize +linestart,chunksize @@".
// where linestart and chunksize are both integers. The second is the
// optional section header. chunksize may be omitted from the header
// if its value is 1. normalizeHeader returns an error if the header
// is not in the correct format.
func normalizeHeader(header string) (string, string, error) {
// Split the header into five parts: the first '@@', the two
// ranges, the last '@@', and the optional section.
pieces := strings.SplitN(header, " ", 5)
if len(pieces) < 4 {
return "", "", &ErrBadHunkHeader{header: header}
}
if pieces[0] != "@@" {
return "", "", &ErrBadHunkHeader{header: header}
}
for i := 1; i < 3; i++ {
if !strings.ContainsRune(pieces[i], ',') {
pieces[i] = pieces[i] + ",1"
}
}
if pieces[3] != "@@" {
return "", "", &ErrBadHunkHeader{header: header}
}
var section string
if len(pieces) == 5 {
section = pieces[4]
}
return strings.Join(pieces, " "), strings.TrimSpace(section), nil
}
// ReadAllHunks reads all remaining hunks from r. A successful call
// returns err == nil, not err == EOF. Because ReadAllHunks is defined
// to read until EOF, it does not treat end of file as an error to be
// reported.
func (r *HunksReader) ReadAllHunks() ([]*Hunk, error) {
var hunks []*Hunk
linesRead := int32(0)
for {
hunk, err := r.ReadHunk()
if err == io.EOF {
return hunks, nil
}
if hunk != nil {
linesRead++ // account for the hunk header line
hunk.StartPosition = linesRead
hunks = append(hunks, hunk)
linesRead += int32(bytes.Count(hunk.Body, []byte{'\n'}))
}
if err != nil {
return hunks, err
}
}
}
// A ParseError is a description of a unified diff syntax error.
type ParseError struct {
Line int // Line where the error occurred
Offset int64 // Offset where the error occurred
Err error // The actual error
}
func (e *ParseError) Error() string {
return fmt.Sprintf("line %d, char %d: %s", e.Line, e.Offset, e.Err)
}
// ErrNoHunkHeader indicates that a unified diff hunk header was
// expected but not found during parsing.
var ErrNoHunkHeader = errors.New("no hunk header")
// ErrBadHunkHeader indicates that a malformed unified diff hunk
// header was encountered during parsing.
type ErrBadHunkHeader struct {
header string
}
func (e *ErrBadHunkHeader) Error() string {
if e.header == "" {
return "bad hunk header"
}
return "bad hunk header: " + e.header
}
// ErrBadHunkLine is when a line not beginning with ' ', '-', '+', or
// '\' is encountered while reading a hunk. In the context of reading
// a single hunk or file, it is an unexpected error. In a multi-file
// diff, however, it indicates that the current file's diff is
// complete (and remaining diff data will describe another file
// unified diff).
type ErrBadHunkLine struct {
Line []byte
}
func (e *ErrBadHunkLine) Error() string {
m := "bad hunk line (does not start with ' ', '-', '+', or '\\')"
if len(e.Line) == 0 {
return m
}
return m + ": " + string(e.Line)
}

View File

@@ -0,0 +1,140 @@
package diff
import (
"bytes"
"fmt"
"io"
"time"
"sourcegraph.com/sqs/pbtypes"
)
// PrintMultiFileDiff prints a multi-file diff in unified diff format.
func PrintMultiFileDiff(ds []*FileDiff) ([]byte, error) {
var buf bytes.Buffer
for _, d := range ds {
diff, err := PrintFileDiff(d)
if err != nil {
return nil, err
}
if _, err := buf.Write(diff); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
// PrintFileDiff prints a FileDiff in unified diff format.
//
// TODO(sqs): handle escaping whitespace/etc. chars in filenames
func PrintFileDiff(d *FileDiff) ([]byte, error) {
var buf bytes.Buffer
for _, xheader := range d.Extended {
if _, err := fmt.Fprintln(&buf, xheader); err != nil {
return nil, err
}
}
if d.Hunks == nil {
return buf.Bytes(), nil
}
if err := printFileHeader(&buf, "--- ", d.OrigName, timePtr(d.OrigTime)); err != nil {
return nil, err
}
if err := printFileHeader(&buf, "+++ ", d.NewName, timePtr(d.NewTime)); err != nil {
return nil, err
}
ph, err := PrintHunks(d.Hunks)
if err != nil {
return nil, err
}
if _, err := buf.Write(ph); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func timePtr(ts *pbtypes.Timestamp) *time.Time {
if ts == nil {
return nil
}
t := ts.Time()
return &t
}
func printFileHeader(w io.Writer, prefix string, filename string, timestamp *time.Time) error {
if _, err := fmt.Fprint(w, prefix, filename); err != nil {
return err
}
if timestamp != nil {
if _, err := fmt.Fprint(w, "\t", timestamp.Format(diffTimeFormatLayout)); err != nil {
return err
}
}
if _, err := fmt.Fprintln(w); err != nil {
return err
}
return nil
}
// PrintHunks prints diff hunks in unified diff format.
func PrintHunks(hunks []*Hunk) ([]byte, error) {
var buf bytes.Buffer
for _, hunk := range hunks {
_, err := fmt.Fprintf(&buf,
"@@ -%d,%d +%d,%d @@", hunk.OrigStartLine, hunk.OrigLines, hunk.NewStartLine, hunk.NewLines,
)
if err != nil {
return nil, err
}
if hunk.Section != "" {
_, err := fmt.Fprint(&buf, " ", hunk.Section)
if err != nil {
return nil, err
}
}
if _, err := fmt.Fprintln(&buf); err != nil {
return nil, err
}
if hunk.OrigNoNewlineAt == 0 {
if _, err := buf.Write(hunk.Body); err != nil {
return nil, err
}
} else {
if _, err := buf.Write(hunk.Body[:hunk.OrigNoNewlineAt]); err != nil {
return nil, err
}
if err := printNoNewlineMessage(&buf); err != nil {
return nil, err
}
if _, err := buf.Write(hunk.Body[hunk.OrigNoNewlineAt:]); err != nil {
return nil, err
}
}
if !bytes.HasSuffix(hunk.Body, []byte{'\n'}) {
if _, err := fmt.Fprintln(&buf); err != nil {
return nil, err
}
if err := printNoNewlineMessage(&buf); err != nil {
return nil, err
}
}
}
return buf.Bytes(), nil
}
func printNoNewlineMessage(w io.Writer) error {
if _, err := w.Write([]byte(noNewlineMessage)); err != nil {
return err
}
if _, err := fmt.Fprintln(w); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,30 @@
package diff
import (
"bufio"
"bytes"
"io"
)
// readLine is a helper that mimics the functionality of calling bufio.Scanner.Scan() and
// bufio.Scanner.Bytes(), but without the token size limitation. It will read and return
// the next line in the Reader with the trailing newline stripped. It will return an
// io.EOF error when there is nothing left to read (at the start of the function call). It
// will return any other errors it receives from the underlying call to ReadBytes.
func readLine(r *bufio.Reader) ([]byte, error) {
line_, err := r.ReadBytes('\n')
if err == io.EOF {
if len(line_) == 0 {
return nil, io.EOF
}
// ReadBytes returned io.EOF, because it didn't find another newline, but there is
// still the remainder of the file to return as a line.
line := line_
return line, nil
} else if err != nil {
return nil, err
}
line := bytes.TrimSuffix(line_, []byte{'\n'})
return line, nil
}

View File

View File

@@ -0,0 +1,11 @@
diff --git Godeps/_workspace/src/sourcegraph.com/sourcegraph/go-diff/diff/testdata/empty.diff Godeps/_workspace/src/sourcegraph.com/sourcegraph/go-diff/diff/testdata/empty.diff
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git Godeps/_workspace/src/sourcegraph.com/sourcegraph/go-diff/diff/testdata/empty_new.diff Godeps/_workspace/src/sourcegraph.com/sourcegraph/go-diff/diff/testdata/empty_new.diff
new file mode 100644
index 0000000000000000000000000000000000000000..527e2e70f57b02e709f53e3ac2b7f59e2b5a46bc
--- /dev/null
+++ Godeps/_workspace/src/sourcegraph.com/sourcegraph/go-diff/diff/testdata/empty_new.diff
@@ -0,0 +1,2 @@
+@@ -1,1 +0,0 @@
+-b

View File

@@ -0,0 +1,2 @@
@@ -1,1 +0,0 @@
-b

View File

@@ -0,0 +1,2 @@
@@ -0,0 +1,1 @@
+b

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
@@ -1,1 +1,1 @@
-a
\ No newline at end of file
+b
\ No newline at end of file

View File

@@ -0,0 +1,8 @@
@@ -1,3 +1,3 @@
0
-a
-a
\ No newline at end of file
+b
+b
\ No newline at end of file

View File

@@ -0,0 +1,6 @@
@@ -1,3 +1,2 @@
a
-a
-a
+a
\ No newline at end of file

View File

@@ -0,0 +1,4 @@
@@ -1,1 +1,1 @@
-a
\ No newline at end of file
+b

View File

@@ -0,0 +1,3 @@
@@ -1,1 +1,1 @@
-a
+b

View File

@@ -0,0 +1,37 @@
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,13 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On
the other hand, a
misspelled word isn't
the end of the world.
@@ -22,3 +22,7 @@
this paragraph needs to
be changed. Things can
be added after it.
+
+This paragraph contains
+important new additions
+to this document.

View File

@@ -0,0 +1,31 @@
--- oldname 2009-10-11 15:12:20.000000000 +0000
+++ newname 2009-10-11 15:12:30.000000000 +0000
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,10 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On
the other hand, a
misspelled word isn't
the end of the world.

View File

@@ -0,0 +1,33 @@
diff --git a/vcs/git_cmd.go b/vcs/git_cmd.go
index aa4de15..7c048ab 100644
--- oldname 2009-10-11 15:12:20.000000000 +0000
+++ newname 2009-10-11 15:12:30.000000000 +0000
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,10 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On
the other hand, a
misspelled word isn't
the end of the world.

View File

@@ -0,0 +1,3 @@
diff --git a/data/Font.png b/data/Font.png
index 17a971d..599f8dd 100644
Binary files a/data/Font.png and b/data/Font.png differ

View File

@@ -0,0 +1,3 @@
diff --git a/vendor/go/build/testdata/empty/dummy b/vendor/go/build/testdata/empty/dummy
deleted file mode 100644
index e69de29..0000000

View File

@@ -0,0 +1,4 @@
diff --git a/187/player/random/gopher-0.png b/187/player/random/gopher-0.png
deleted file mode 100644
index aebdfc7..0000000
Binary files a/187/player/random/gopher-0.png and /dev/null differ

View File

@@ -0,0 +1,3 @@
diff --git a/vendor/go/build/testdata/empty/dummy b/vendor/go/build/testdata/empty/dummy
new file mode 100644
index 0000000..e69de29

View File

@@ -0,0 +1,4 @@
diff --git a/diff/binary-image.png b/diff/binary-image.png
new file mode 100644
index 0000000..b51756e
Binary files /dev/null and b/diff/binary-image.png differ

View File

@@ -0,0 +1,4 @@
diff --git a/docs/integrations/Email_Notifications.md b/docs/integrations/email-notifications.md
similarity index 100%
rename from docs/integrations/Email_Notifications.md
rename to docs/integrations/email-notifications.md

View File

@@ -0,0 +1,11 @@
--- goyaml.go 2011-11-24 19:47:20 +0000
+++ goyaml.go 2011-11-28 13:24:39 +0000
@@ -256,7 +256,7 @@
switch v.Kind() {
case reflect.String:
return len(v.String()) == 0
- case reflect.Interface:
+ case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Slice:
return v.Len() == 0

View File

@@ -0,0 +1,31 @@
--- oldname
+++ newname
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,10 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On
the other hand, a
misspelled word isn't
the end of the world.

View File

@@ -0,0 +1,10 @@
@@ -1,3 +1,9 @@ Section Header
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to

View File

@@ -0,0 +1,37 @@
@@ -1,3 +1,9 @@ Section Header
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,10 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On
the other hand, a
misspelled word isn't
the end of the world.
@@ -22,3 +22,7 @@
this paragraph needs to
be changed. Things can
be added after it.
+
+This paragraph contains
+important new additions
+to this document.

View File

@@ -0,0 +1,4 @@
@@ -1,1 +1,1 @@
-b
+b
\ No newline at end of file

View File

@@ -0,0 +1,63 @@
diff --ruN a/oldname1 b/newname1
old mode 0777
new mode 0755
--- oldname1 2009-10-11 15:12:20.000000000 +0000
+++ newname1 2009-10-11 15:12:30.000000000 +0000
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,10 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On
diff --ruN a/oldname2 b/newname2
--- oldname2 2009-10-11 15:12:20.000000000 +0000
+++ newname2 2009-10-11 15:12:30.000000000 +0000
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,10 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On
the other hand, a
misspelled word isn't
the end of the world.

View File

@@ -0,0 +1,29 @@
diff --git a/README.md b/README.md
index 7b73e04..36cde13 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
Conception-go [![Build Status](https://travis-ci.org/shurcooL/Conception-go.svg?branch=master)](https://travis-ci.org/shurcooL/Conception-go)
=============
+This is a change.
+
This is a work in progress Go implementation of [Conception](https://github.com/shurcooL/Conception#demonstration).
Conception is an experimental project. It's a platform for researching software development tools and techniques. It is driven by a set of guiding principles. Conception-go targets Go development.
diff --git a/data/Font.png b/data/Font.png
index 17a971d..599f8dd 100644
Binary files a/data/Font.png and b/data/Font.png differ
diff --git a/main.go b/main.go
index 1aced1e..98a982e 100644
--- a/main.go
+++ b/main.go
@@ -6710,6 +6710,8 @@ func init() {
}
func main() {
+ // Another plain text change.
+
//defer profile.Start(profile.CPUProfile).Stop()
//defer profile.Start(profile.MemProfile).Stop()

View File

@@ -0,0 +1,27 @@
diff --git a/vendor/go/build/syslist_test.go b/vendor/go/build/syslist_test.go
deleted file mode 100644
index 3be2928..0000000
--- a/vendor/go/build/syslist_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-func TestGoodOSArch(t *testing.T) {
- for _, test := range tests {
- if Default.goodOSArchFile(test.name, make(map[string]bool)) != test.result {
- t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result)
- }
- }
-}
diff --git a/vendor/go/build/testdata/empty/dummy b/vendor/go/build/testdata/empty/dummy
deleted file mode 100644
index e69de29..0000000
diff --git a/vendor/go/build/testdata/multi/file.go b/vendor/go/build/testdata/multi/file.go
deleted file mode 100644
index ee946eb..0000000
--- a/vendor/go/build/testdata/multi/file.go
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test data - not compiled.
-
-package main
-
-func main() {}

View File

@@ -0,0 +1,27 @@
diff --git a/_vendor/go/build/syslist_test.go b/_vendor/go/build/syslist_test.go
new file mode 100644
index 0000000..3be2928
--- /dev/null
+++ b/_vendor/go/build/syslist_test.go
@@ -0,0 +1,62 @@
+func TestGoodOSArch(t *testing.T) {
+ for _, test := range tests {
+ if Default.goodOSArchFile(test.name, make(map[string]bool)) != test.result {
+ t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result)
+ }
+ }
+}
diff --git a/_vendor/go/build/testdata/empty/dummy b/_vendor/go/build/testdata/empty/dummy
new file mode 100644
index 0000000..e69de29
diff --git a/_vendor/go/build/testdata/multi/file.go b/_vendor/go/build/testdata/multi/file.go
new file mode 100644
index 0000000..ee946eb
--- /dev/null
+++ b/_vendor/go/build/testdata/multi/file.go
@@ -0,0 +1,5 @@
+// Test data - not compiled.
+
+package main
+
+func main() {}

View File

@@ -0,0 +1,27 @@
diff --git a/README.md b/README.md
index 5f3d591..96a24fa 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,8 @@ and [view enterprise capabilities](https://www.example.com).*
## Installation
+Minor change here.
+
Follow the 5-minute
[installation instructions](https://www.example.com/.docs/getting-started/). For
more installation methods, check out the
diff --git a/docs/integrations/Email_Notifications.md b/docs/integrations/email-notifications.md
similarity index 100%
rename from docs/integrations/Email_Notifications.md
rename to docs/integrations/email-notifications.md
diff --git a/release_notes.md b/release_notes.md
index f2ff13f..f060cb5 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -1,3 +1,5 @@
+# new section
+
# dev
- Removed example pages and the `--auth.example-flag`

View File

@@ -0,0 +1,29 @@
diff --ruN a/oldname1 b/newname1
--- oldname1 2009-10-11 15:12:20.000000000 +0000
+++ newname1 2009-10-11 15:12:30.000000000 +0000
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -5,16 +11,10 @@
be shown if it doesn't
change. Otherwise, that
would not be helping to
-compress the size of the
-changes.
-
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
+compress anything.
It is important to spell
-check this dokument. On
+check this document. On

View File

@@ -0,0 +1,2 @@
@@ -0,0 +1 @@
+Added one line