Skip to content

Support Apple Diff timestamps #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ type FileDiff struct {
// the original name of the file
OrigName string
// the original timestamp (nil if not present)
OrigTime *time.Time
OrigTime *time.Time
OrigTimeHasTZ bool
// the new name of the file (often same as OrigName)
NewName string
// the new timestamp (nil if not present)
NewTime *time.Time
NewTime *time.Time
NewTimeHasTZ bool
// extended header lines (e.g., git's "new mode <mode>", "rename from <path>", etc.)
Extended []string
// hunks that were changed from orig to new
Hunks []*Hunk

includeTZ bool
}

// A Hunk represents a series of changes (additions or deletions) in a file's
Expand Down Expand Up @@ -120,11 +124,20 @@ const onlyInMessage = "Only in %s: %s\n"
// See https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html.
const diffTimeParseLayout = "2006-01-02 15:04:05 -0700"

// Apple's diff is based on freebsd diff, which uses a timestamp format that does
// not include the timezone offset.
const diffTimeParseWithoutTZLayout = "2006-01-02 15:04:05"

// 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"

// diffTimeFormatWithoutTZLayout is the layout used to format (i.e., print) the time in unified diff file
// header timestamps without the timezone offset and the fractional seconds. This is used when the diff
// does not include the timezone offset and fractional seconds.
const diffTimeFormatWithoutTZLayout = "2006-01-02 15:04:05"

func (s *Stat) add(o Stat) {
s.Added += o.Added
s.Changed += o.Changed
Expand Down
1 change: 1 addition & 0 deletions diff/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,7 @@ func TestParseMultiFileDiffAndPrintMultiFileDiff(t *testing.T) {
}{
{filename: "sample_multi_file.diff", wantFileDiffs: 2},
{filename: "sample_multi_file_single.diff", wantFileDiffs: 1},
{filename: "sample_multi_file_single_apple.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},
Expand Down
43 changes: 27 additions & 16 deletions diff/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,18 @@ func (r *FileDiffReader) ReadAllHeaders() (*FileDiff, error) {
}

var origTime, newTime *time.Time
fd.OrigName, fd.NewName, origTime, newTime, err = r.ReadFileHeaders()
var origTZ, newTZ bool
fd.OrigName, fd.NewName, origTime, newTime, origTZ, newTZ, err = r.ReadFileHeaders()
if err != nil {
return nil, err
}
if origTime != nil {
fd.OrigTime = origTime
fd.OrigTimeHasTZ = origTZ
}
if newTime != nil {
fd.NewTime = newTime
fd.NewTimeHasTZ = newTZ
}

return fd, nil
Expand All @@ -247,22 +250,21 @@ func (r *FileDiffReader) HunksReader() *HunksReader {
// start with "---" and "+++" with the orig/new file names and
// timestamps). Or which starts with "Only in " with dir path and filename.
// "Only in" message is supported in POSIX locale: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/diff.html#tag_20_34_10
func (r *FileDiffReader) ReadFileHeaders() (origName, newName string, origTimestamp, newTimestamp *time.Time, err error) {
func (r *FileDiffReader) ReadFileHeaders() (origName, newName string, origTimestamp, newTimestamp *time.Time, origTZ, newTZ bool, err error) {
if r.fileHeaderLine != nil {
if isOnlyMessage, source, filename := parseOnlyInMessage(r.fileHeaderLine); isOnlyMessage {
return filepath.Join(string(source), string(filename)),
"", nil, nil, nil
"", nil, nil, false, false, nil
}
}

origName, origTimestamp, err = r.readOneFileHeader([]byte("--- "))
origName, origTimestamp, origTZ, err = r.readOneFileHeader([]byte("--- "))
if err != nil {
return "", "", nil, nil, err
return "", "", nil, nil, false, false, err
}

newName, newTimestamp, err = r.readOneFileHeader([]byte("+++ "))
newName, newTimestamp, newTZ, err = r.readOneFileHeader([]byte("+++ "))
if err != nil {
return "", "", nil, nil, err
return "", "", nil, nil, false, false, err
}

unquotedOrigName, err := strconv.Unquote(origName)
Expand All @@ -274,29 +276,29 @@ func (r *FileDiffReader) ReadFileHeaders() (origName, newName string, origTimest
newName = unquotedNewName
}

return origName, newName, origTimestamp, newTimestamp, nil
return origName, newName, origTimestamp, newTimestamp, origTZ, newTZ, 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) {
func (r *FileDiffReader) readOneFileHeader(prefix []byte) (filename string, timestamp *time.Time, hadTZ bool, err error) {
var line []byte

if r.fileHeaderLine == nil {
var err error
line, err = r.reader.readLine()
if err == io.EOF {
return "", nil, &ParseError{r.line, r.offset, ErrNoFileHeader}
return "", nil, false, &ParseError{r.line, r.offset, ErrNoFileHeader}
} else if err != nil {
return "", nil, err
return "", nil, false, err
}
} else {
line = r.fileHeaderLine
r.fileHeaderLine = nil
}

if !bytes.HasPrefix(line, prefix) {
return "", nil, &ParseError{r.line, r.offset, ErrBadFileHeader}
return "", nil, false, &ParseError{r.line, r.offset, ErrBadFileHeader}
}

r.offset += int64(len(line))
Expand All @@ -306,16 +308,25 @@ func (r *FileDiffReader) readOneFileHeader(prefix []byte) (filename string, time
trimmedLine := strings.TrimSpace(string(line)) // filenames that contain spaces may be terminated by a tab
parts := strings.SplitN(trimmedLine, "\t", 2)
filename = parts[0]
hadTZ = true
if len(parts) == 2 {
var ts time.Time
// Timestamp is optional, but this header has it.
ts, err := time.Parse(diffTimeParseLayout, parts[1])
ts, err = time.Parse(diffTimeParseLayout, parts[1])
if err != nil {
return "", nil, err
var err1 error
ts, err1 = time.Parse(diffTimeParseWithoutTZLayout, parts[1])
if err1 != nil {
return "", nil, hadTZ, err
}
hadTZ = false
ts = ts.In(time.Now().Location())
err = nil
}
timestamp = &ts
}

return filename, timestamp, err
return filename, timestamp, hadTZ, err
}

// OverflowError is returned when we have overflowed into the start
Expand Down
12 changes: 8 additions & 4 deletions diff/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ func PrintFileDiff(d *FileDiff) ([]byte, error) {
return buf.Bytes(), nil
}

if err := printFileHeader(&buf, "--- ", d.OrigName, d.OrigTime); err != nil {
if err := printFileHeader(&buf, "--- ", d.OrigName, d.OrigTime, d.OrigTimeHasTZ); err != nil {
return nil, err
}
if err := printFileHeader(&buf, "+++ ", d.NewName, d.NewTime); err != nil {
if err := printFileHeader(&buf, "+++ ", d.NewName, d.NewTime, d.NewTimeHasTZ); err != nil {
return nil, err
}

Expand All @@ -67,12 +67,16 @@ func PrintFileDiff(d *FileDiff) ([]byte, error) {
return buf.Bytes(), nil
}

func printFileHeader(w io.Writer, prefix string, filename string, timestamp *time.Time) error {
func printFileHeader(w io.Writer, prefix string, filename string, timestamp *time.Time, tz bool) error {
if _, err := fmt.Fprint(w, prefix, filename); err != nil {
return err
}
format := diffTimeFormatLayout
if !tz {
format = diffTimeFormatWithoutTZLayout
}
if timestamp != nil {
if _, err := fmt.Fprint(w, "\t", timestamp.Format(diffTimeFormatLayout)); err != nil {
if _, err := fmt.Fprint(w, "\t", timestamp.Format(format)); err != nil {
return err
}
}
Expand Down
29 changes: 29 additions & 0 deletions diff/testdata/sample_multi_file_single_apple.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
diff -u a/oldname1 b/newname1
--- oldname1 2009-10-11 15:12:20
+++ newname1 2009-10-11 15:12:30
@@ -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