cleanup and refactor diff / patch

This commit is contained in:
Jesse Ezell 2014-03-20 21:56:41 -07:00
parent 9acd67e388
commit d0b334b244
6 changed files with 252 additions and 124 deletions

258
diff.go
View File

@ -12,72 +12,75 @@ import (
) )
type DiffFlag int type DiffFlag int
const ( const (
DiffFlagBinary = DiffFlag(C.GIT_DIFF_FLAG_BINARY) DiffFlagBinary DiffFlag = C.GIT_DIFF_FLAG_BINARY
DiffFlagNotBinary = C.GIT_DIFF_FLAG_NOT_BINARY DiffFlagNotBinary = C.GIT_DIFF_FLAG_NOT_BINARY
DiffFlagValidOid = C.GIT_DIFF_FLAG_VALID_OID DiffFlagValidOid = C.GIT_DIFF_FLAG_VALID_OID
) )
type Delta int type Delta int
const ( const (
DeltaUnmodified = Delta(C.GIT_DELTA_UNMODIFIED) DeltaUnmodified Delta = C.GIT_DELTA_UNMODIFIED
DeltaAdded = C.GIT_DELTA_ADDED DeltaAdded = C.GIT_DELTA_ADDED
DeltaDeleted = C.GIT_DELTA_DELETED DeltaDeleted = C.GIT_DELTA_DELETED
DeltaModified = C.GIT_DELTA_MODIFIED DeltaModified = C.GIT_DELTA_MODIFIED
DeltaRenamed = C.GIT_DELTA_RENAMED DeltaRenamed = C.GIT_DELTA_RENAMED
DeltaCopied = C.GIT_DELTA_COPIED DeltaCopied = C.GIT_DELTA_COPIED
DeltaIgnored = C.GIT_DELTA_IGNORED DeltaIgnored = C.GIT_DELTA_IGNORED
DeltaUntracked = C.GIT_DELTA_UNTRACKED DeltaUntracked = C.GIT_DELTA_UNTRACKED
DeltaTypeChange = C.GIT_DELTA_TYPECHANGE DeltaTypeChange = C.GIT_DELTA_TYPECHANGE
) )
type DiffLineType int type DiffLineType int
const ( const (
DiffLineContext = DiffLineType(C.GIT_DIFF_LINE_CONTEXT) DiffLineContext DiffLineType = C.GIT_DIFF_LINE_CONTEXT
DiffLineAddition = C.GIT_DIFF_LINE_ADDITION DiffLineAddition = C.GIT_DIFF_LINE_ADDITION
DiffLineDeletion = C.GIT_DIFF_LINE_DELETION DiffLineDeletion = C.GIT_DIFF_LINE_DELETION
DiffLineContextEOFNL = C.GIT_DIFF_LINE_CONTEXT_EOFNL DiffLineContextEOFNL = C.GIT_DIFF_LINE_CONTEXT_EOFNL
DiffLineAddEOFNL = C.GIT_DIFF_LINE_ADD_EOFNL DiffLineAddEOFNL = C.GIT_DIFF_LINE_ADD_EOFNL
DiffLineDelEOFNL = C.GIT_DIFF_LINE_DEL_EOFNL DiffLineDelEOFNL = C.GIT_DIFF_LINE_DEL_EOFNL
DiffLineFileHdr = C.GIT_DIFF_LINE_FILE_HDR DiffLineFileHdr = C.GIT_DIFF_LINE_FILE_HDR
DiffLineHunkHdr = C.GIT_DIFF_LINE_HUNK_HDR DiffLineHunkHdr = C.GIT_DIFF_LINE_HUNK_HDR
DiffLineBinary = C.GIT_DIFF_LINE_BINARY DiffLineBinary = C.GIT_DIFF_LINE_BINARY
) )
type DiffFile struct { type DiffFile struct {
Path string Path string
Oid *Oid Oid *Oid
Size int Size int
Flags DiffFlag Flags DiffFlag
Mode uint16 Mode uint16
} }
func newDiffFile(file *C.git_diff_file) *DiffFile { func newDiffFileFromC(file *C.git_diff_file) *DiffFile {
return &DiffFile{ return &DiffFile{
Path: C.GoString(file.path), Path: C.GoString(file.path),
Oid: newOidFromC(&file.oid), Oid: newOidFromC(&file.oid),
Size: int(file.size), Size: int(file.size),
Flags: DiffFlag(file.flags), Flags: DiffFlag(file.flags),
Mode: uint16(file.mode), Mode: uint16(file.mode),
} }
} }
type DiffDelta struct { type DiffDelta struct {
Status Delta Status Delta
Flags DiffFlag Flags DiffFlag
Similarity uint16 Similarity uint16
OldFile *DiffFile OldFile *DiffFile
NewFile *DiffFile NewFile *DiffFile
} }
func newDiffDelta(delta *C.git_diff_delta) *DiffDelta { func newDiffDeltaFromC(delta *C.git_diff_delta) *DiffDelta {
return &DiffDelta{ return &DiffDelta{
Status: Delta(delta.status), Status: Delta(delta.status),
Flags: DiffFlag(delta.flags), Flags: DiffFlag(delta.flags),
Similarity: uint16(delta.similarity), Similarity: uint16(delta.similarity),
OldFile: newDiffFile(&delta.old_file), OldFile: newDiffFileFromC(&delta.old_file),
NewFile: newDiffFile(&delta.new_file), NewFile: newDiffFileFromC(&delta.new_file),
} }
} }
@ -86,38 +89,38 @@ type DiffHunk struct {
OldLines int OldLines int
NewStart int NewStart int
NewLines int NewLines int
Header string Header string
DiffDelta DiffDelta
} }
func newDiffHunk(delta *C.git_diff_delta, hunk *C.git_diff_hunk) *DiffHunk { func newDiffHunkFromC(delta *C.git_diff_delta, hunk *C.git_diff_hunk) *DiffHunk {
return &DiffHunk{ return &DiffHunk{
OldStart: int(hunk.old_start), OldStart: int(hunk.old_start),
OldLines: int(hunk.old_lines), OldLines: int(hunk.old_lines),
NewStart: int(hunk.new_start), NewStart: int(hunk.new_start),
NewLines: int(hunk.new_lines), NewLines: int(hunk.new_lines),
Header: C.GoStringN(&hunk.header[0], C.int(hunk.header_len)), Header: C.GoStringN(&hunk.header[0], C.int(hunk.header_len)),
DiffDelta: *newDiffDelta(delta), DiffDelta: *newDiffDeltaFromC(delta),
} }
} }
type DiffLine struct { type DiffLine struct {
Origin DiffLineType Origin DiffLineType
OldLineno int OldLineno int
NewLineno int NewLineno int
NumLines int NumLines int
Content string Content string
DiffHunk DiffHunk
} }
func newDiffLine(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line) *DiffLine { func newDiffLineFromC(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line) *DiffLine {
return &DiffLine{ return &DiffLine{
Origin: DiffLineType(line.origin), Origin: DiffLineType(line.origin),
OldLineno: int(line.old_lineno), OldLineno: int(line.old_lineno),
NewLineno: int(line.new_lineno), NewLineno: int(line.new_lineno),
NumLines: int(line.num_lines), NumLines: int(line.num_lines),
Content: C.GoStringN(line.content, C.int(line.content_len)), Content: C.GoStringN(line.content, C.int(line.content_len)),
DiffHunk: *newDiffHunk(delta, hunk), DiffHunk: *newDiffHunkFromC(delta, hunk),
} }
} }
@ -125,7 +128,7 @@ type Diff struct {
ptr *C.git_diff ptr *C.git_diff
} }
func newDiff(ptr *C.git_diff) *Diff { func newDiffFromC(ptr *C.git_diff) *Diff {
if ptr == nil { if ptr == nil {
return nil return nil
} }
@ -138,100 +141,165 @@ func newDiff(ptr *C.git_diff) *Diff {
return diff return diff
} }
func (diff *Diff) Free() { func (diff *Diff) Free() error {
if diff.ptr != nil {
return ErrInvalid
}
runtime.SetFinalizer(diff, nil) runtime.SetFinalizer(diff, nil)
C.git_diff_free(diff.ptr) C.git_diff_free(diff.ptr)
return nil
} }
func (diff *Diff) forEachFileWrap(ch chan *DiffDelta) { type diffForEachFileData struct {
C._go_git_diff_foreach(diff.ptr, 1, 0, 0, unsafe.Pointer(&ch)) Callback DiffForEachFileCallback
close(ch) Error error
} }
func (diff *Diff) ForEachFile() chan *DiffDelta { func (diff *Diff) ForEachFile(cb DiffForEachFileCallback) error {
ch := make(chan *DiffDelta, 0) if diff.ptr != nil {
go diff.forEachFileWrap(ch) return ErrInvalid
return ch }
data := &diffForEachFileData{
Callback: cb,
}
ecode := C._go_git_diff_foreach(diff.ptr, 1, 0, 0, unsafe.Pointer(&data))
if ecode < 0 {
return data.Error
}
return nil
} }
//export diffForEachFileCb //export diffForEachFileCb
func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, payload unsafe.Pointer) int { func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, payload unsafe.Pointer) int {
ch := *(*chan *DiffDelta)(payload) data := *diffForEachFileData(payload)
select { err := data.Callback(newDiffDeltaFromC(delta))
case ch <-newDiffDelta(delta): if err != nil {
case <-ch: data.Error = err
return -1 return -1
} }
return 0 return 0
} }
func (diff *Diff) forEachHunkWrap(ch chan *DiffHunk) { type diffForEachHunkData struct {
C._go_git_diff_foreach(diff.ptr, 0, 1, 0, unsafe.Pointer(&ch)) Callback DiffForEachHunkCallback
close(ch) Error error
} }
func (diff *Diff) ForEachHunk() chan *DiffHunk { type DiffForEachHunkCallback func(*DiffHunk) error
ch := make(chan *DiffHunk, 0)
go diff.forEachHunkWrap(ch) func (diff *Diff) ForEachHunk(cb DiffForEachHunkCallback) error {
return ch if diff.ptr != nil {
return ErrInvalid
}
data := &diffForEachHunkData{
Callback: cb,
}
ecode := C._go_git_diff_foreach(diff.ptr, 0, 1, 0, unsafe.Pointer(data))
if ecode < 0 {
return data.Error
}
return nil
} }
//export diffForEachHunkCb //export diffForEachHunkCb
func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, payload unsafe.Pointer) int { func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, payload unsafe.Pointer) int {
ch := *(*chan *DiffHunk)(payload) data := *diffForEachHunkData(payload)
select { err := data.Callback(newDiffHunkFromC(delta, hunk))
case ch <-newDiffHunk(delta, hunk): if err < 0 {
case <-ch: data.Error = err
return -1 return -1
} }
return 0 return 0
} }
func (diff *Diff) forEachLineWrap(ch chan *DiffLine) { type diffForEachLineData struct {
C._go_git_diff_foreach(diff.ptr, 0, 0, 1, unsafe.Pointer(&ch)) Callback DiffForEachLineCallback
close(ch) Error error
} }
func (diff *Diff) ForEachLine() chan *DiffLine { type DiffForEachLineCallback func(*DiffLine) error
ch := make(chan *DiffLine, 0)
go diff.forEachLineWrap(ch) func (diff *Diff) ForEachLine(cb DiffForEachLineCallback) error {
return ch if diff.ptr != nil {
return ErrInvalid
}
data := &diffForEachLineData{
Callback: cb,
}
ecode := C._go_git_diff_foreach(diff.ptr, 0, 0, 1, unsafe.Pointer(data))
if ecode < 0 {
return data.Error
}
return nil
} }
//export diffForEachLineCb //export diffForEachLineCb
func diffForEachLineCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line, payload unsafe.Pointer) int { func diffForEachLineCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line, payload unsafe.Pointer) int {
ch := *(*chan *DiffLine)(payload)
select { data := *diffForEachLineData(payload)
case ch <-newDiffLine(delta, hunk, line):
case <-ch: err := data.Callback(newDiffLineFromC(delta, hunk, line))
if err != nil {
data.Error = err
return -1 return -1
} }
return 0 return 0
} }
func (diff *Diff) NumDeltas() int { func (diff *Diff) NumDeltas() (int, error) {
return int(C.git_diff_num_deltas(diff.ptr)) if diff.ptr != nil {
return -1, ErrInvalid
}
return int(C.git_diff_num_deltas(diff.ptr)), nil
} }
func (diff *Diff) GetDelta(index int) *DiffDelta { func (diff *Diff) GetDelta(index int) (*DiffDelta, error) {
if diff.ptr != nil {
return nil, ErrInvalid
}
ptr := C.git_diff_get_delta(diff.ptr, C.size_t(index)) ptr := C.git_diff_get_delta(diff.ptr, C.size_t(index))
if ptr == nil { if ptr == nil {
return nil return nil
} }
return newDiffDelta(ptr) return newDiffDeltaFromC(ptr), nil
} }
func (diff *Diff) Patch(deltaIndex int) *Patch { func (diff *Diff) Patch(deltaIndex int) (*Patch, error) {
if diff.ptr != nil {
return nil, ErrInvalid
}
var patchPtr *C.git_patch var patchPtr *C.git_patch
C.git_patch_from_diff(&patchPtr, diff.ptr, C.size_t(deltaIndex)) ecode := C.git_patch_from_diff(&patchPtr, diff.ptr, C.size_t(deltaIndex))
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newPatch(patchPtr) return newPatchFromC(patchPtr), nil
}
func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree) *Diff {
var diffPtr *C.git_diff
var oldPtr, newPtr *C.git_tree
if oldTree != nil {
oldPtr = oldTree.gitObject.ptr
}
if newTree != nil {
newPtr = newTree.gitObject.ptr
}
C.git_diff_tree_to_tree(&diffPtr, v.ptr, oldPtr, newPtr, nil)
return newDiff(diffPtr)
} }

41
diff_test.go Normal file
View File

@ -0,0 +1,41 @@
package git
import (
"testing"
)
func TestDiffTreeToTree(t *testing.T) {
repo := createTestRepo(t)
defer repo.Free()
defer os.RemoveAll(repo.Workdir())
_, originalTreeId := seedTestRepo(t, repo)
originalTree, err := repo.LookupTree(originalTreeId)
checkFatal(t, err)
updateReadme(t, repo, "file changed\n")
_, newTreeId := seedTestRepo(t, repo)
newTree, err := repo.LookupTree(newTreeId)
checkFatal(t, err)
diff, err := repo.DiffTreeToTree(originalTreeId, newTreeId)
checkFatal(t, err)
files := make([]string, 0)
err := diff.ForEachFile(func(file *DiffFile) error {
files = append(files, file.Path)
return nil
})
checkFatal(t, err)
if len(files) != 0 {
t.Fatal("Incorrect number of files in diff")
}
if files[0] != "README" {
t.Fatal("File in diff was expected to be README")
}
}

13
git.go
View File

@ -10,8 +10,8 @@ import (
"bytes" "bytes"
"errors" "errors"
"runtime" "runtime"
"unsafe"
"strings" "strings"
"unsafe"
) )
const ( const (
@ -22,6 +22,7 @@ const (
var ( var (
ErrIterOver = errors.New("Iteration is over") ErrIterOver = errors.New("Iteration is over")
ErrInvalid = errors.New("Invalid state for operation")
) )
func init() { func init() {
@ -93,7 +94,7 @@ func (oid *Oid) Equal(oid2 *Oid) bool {
} }
func (oid *Oid) IsZero() bool { func (oid *Oid) IsZero() bool {
for _, a := range(oid.bytes) { for _, a := range oid.bytes {
if a != '0' { if a != '0' {
return false return false
} }
@ -131,10 +132,10 @@ func ShortenOids(ids []*Oid, minlen int) (int, error) {
type GitError struct { type GitError struct {
Message string Message string
Code int Code int
} }
func (e GitError) Error() string{ func (e GitError) Error() string {
return e.Message return e.Message
} }
@ -147,14 +148,14 @@ func LastError() error {
} }
func cbool(b bool) C.int { func cbool(b bool) C.int {
if (b) { if b {
return C.int(1) return C.int(1)
} }
return C.int(0) return C.int(0)
} }
func ucbool(b bool) C.uint { func ucbool(b bool) C.uint {
if (b) { if b {
return C.uint(1) return C.uint(1)
} }
return C.uint(0) return C.uint(0)

View File

@ -1,8 +1,8 @@
package git package git
import ( import (
"testing"
"io/ioutil" "io/ioutil"
"testing"
"time" "time"
) )
@ -14,7 +14,7 @@ func createTestRepo(t *testing.T) *Repository {
checkFatal(t, err) checkFatal(t, err)
tmpfile := "README" tmpfile := "README"
err = ioutil.WriteFile(path + "/" + tmpfile, []byte("foo\n"), 0644) err = ioutil.WriteFile(path+"/"+tmpfile, []byte("foo\n"), 0644)
checkFatal(t, err) checkFatal(t, err)
return repo return repo
@ -45,3 +45,31 @@ func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) {
return commitId, treeId return commitId, treeId
} }
func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) {
loc, err := time.LoadLocation("Europe/Berlin")
checkFatal(t, err)
sig := &Signature{
Name: "Rand Om Hacker",
Email: "random@hacker.com",
When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
}
tmpfile := "README"
err = ioutil.WriteFile(repo.Path()+"/"+tmpfile, []byte(content), 0644)
checkFatal(t, err)
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
message := "This is a commit\n"
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
checkFatal(t, err)
return commitId, treeId
}

View File

@ -12,7 +12,7 @@ type Patch struct {
ptr *C.git_patch ptr *C.git_patch
} }
func newPatch(ptr *C.git_patch) *Patch { func newPatchFromC(ptr *C.git_patch) *Patch {
if ptr == nil { if ptr == nil {
return nil return nil
} }
@ -25,13 +25,20 @@ func newPatch(ptr *C.git_patch) *Patch {
return patch return patch
} }
func (patch *Patch) Free() { func (patch *Patch) Free() error {
if patch.ptr == nil {
return ErrInvalid
}
runtime.SetFinalizer(patch, nil) runtime.SetFinalizer(patch, nil)
C.git_patch_free(patch.ptr) C.git_patch_free(patch.ptr)
return nil
} }
func (patch *Patch) String() string { func (patch *Patch) String() (string, error) {
if diff.ptr != nil {
return "", ErrInvalid
}
var cptr *C.char var cptr *C.char
C.git_patch_to_str(&cptr, patch.ptr) C.git_patch_to_str(&cptr, patch.ptr)
return C.GoString(cptr) return C.GoString(cptr), nil
} }

View File

@ -255,23 +255,6 @@ func (v *Repository) CreateCommit(
return oid, nil return oid, nil
} }
func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree) *Diff {
var diffPtr *C.git_diff
var oldPtr, newPtr *C.git_tree
if oldTree != nil {
oldPtr = oldTree.gitObject.ptr
}
if newTree != nil {
newPtr = newTree.gitObject.ptr
}
C.git_diff_tree_to_tree(&diffPtr, v.ptr, oldPtr, newPtr, nil)
return newDiff(diffPtr)
}
func (v *Odb) Free() { func (v *Odb) Free() {
runtime.SetFinalizer(v, nil) runtime.SetFinalizer(v, nil)
C.git_odb_free(v.ptr) C.git_odb_free(v.ptr)