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

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