From bc80beb8432091bdf1190ad6b803060324802685 Mon Sep 17 00:00:00 2001 From: lye Date: Thu, 20 Feb 2014 00:25:30 -0600 Subject: [PATCH] Add partial diff/patch functionality. This commit adds barebones capacity to generate diffs from two trees and to emit those as git-style diffs (via `Patch.String`), or to enumerate the files/hunks/lines in the diff to emit the data yourself. The walk functions have been implemented in the same manner as the Odb walking methods. Note that not all of the functionality is implemented for either the `git_diff_*` nor the `git_patch_*` functions, and there are unexposed constants which would likely be useful to add. --- diff.go | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++ patch.go | 37 ++++++++ repository.go | 31 +++++++ wrapper.c | 21 +++++ 4 files changed, 318 insertions(+) create mode 100644 diff.go create mode 100644 patch.go diff --git a/diff.go b/diff.go new file mode 100644 index 0000000..d9570c9 --- /dev/null +++ b/diff.go @@ -0,0 +1,229 @@ +package git + +/* +#include + +extern int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLine, void *payload); +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type DiffFile struct { + file C.git_diff_file +} + +func (df *DiffFile) Oid() *Oid { + return newOidFromC(&df.file.oid) +} + +func (df *DiffFile) Path() string { + return C.GoString(df.file.path) +} + +func (df *DiffFile) Size() int { + return int(df.file.size) +} + +func (df *DiffFile) Flags() uint32 { + return uint32(df.file.flags) +} + +func (df *DiffFile) Mode() uint16 { + return uint16(df.file.mode) +} + +type DiffDelta struct { + delta C.git_diff_delta +} + +func (dd *DiffDelta) Status() int { + return int(dd.delta.status) +} + +func (dd *DiffDelta) Flags() uint32 { + return uint32(dd.delta.flags) +} + +func (dd *DiffDelta) Similarity() uint16 { + return uint16(dd.delta.similarity) +} + +func (dd *DiffDelta) OldFile() *DiffFile { + return &DiffFile{dd.delta.old_file} +} + +func (dd *DiffDelta) NewFile() *DiffFile { + return &DiffFile{dd.delta.new_file} +} + +type DiffHunk struct { + hunk C.git_diff_hunk + DiffDelta +} + +func (dh *DiffHunk) OldStart() int { + return int(dh.hunk.old_start) +} + +func (dh *DiffHunk) OldLines() int { + return int(dh.hunk.old_lines) +} + +func (dh *DiffHunk) NewStart() int { + return int(dh.hunk.new_start) +} + +func (dh *DiffHunk) NewLines() int { + return int(dh.hunk.new_lines) +} + +func (dh *DiffHunk) Header() string { + return C.GoStringN(&dh.hunk.header[0], C.int(dh.hunk.header_len)) +} + +type DiffLine struct { + line C.git_diff_line + DiffHunk +} + +func (dl *DiffLine) Origin() byte { + return byte(dl.line.origin) +} + +func (dl *DiffLine) OldLineno() int { + return int(dl.line.old_lineno) +} + +func (dl *DiffLine) NewLineno() int { + return int(dl.line.new_lineno) +} + +func (dl *DiffLine) NumLines() int { + return int(dl.line.num_lines) +} + +func (dl *DiffLine) Content() string { + return C.GoStringN(dl.line.content, C.int(dl.line.content_len)) +} + +func (dl *DiffLine) ContentOffset() int { + return int(dl.line.content_offset) +} + +type Diff struct { + ptr *C.git_diff +} + +func newDiff(ptr *C.git_diff) *Diff { + if ptr == nil { + return nil + } + + diff := &Diff{ + ptr: ptr, + } + + runtime.SetFinalizer(diff, (*Diff).Free) + return diff +} + +func (diff *Diff) Free() { + runtime.SetFinalizer(diff, nil) + C.git_diff_free(diff.ptr) +} + +func (diff *Diff) forEachFileWrap(ch chan *DiffDelta) { + C._go_git_diff_foreach(diff.ptr, 1, 0, 0, unsafe.Pointer(&ch)) + close(ch) +} + +func (diff *Diff) ForEachFile() chan *DiffDelta { + ch := make(chan *DiffDelta, 0) + go diff.forEachFileWrap(ch) + return ch +} + +//export diffForEachFileCb +func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, payload unsafe.Pointer) int { + ch := *(*chan *DiffDelta)(payload) + + select { + case ch <-&DiffDelta{*delta}: + case <-ch: + return -1 + } + + return 0 +} + +func (diff *Diff) forEachHunkWrap(ch chan *DiffHunk) { + C._go_git_diff_foreach(diff.ptr, 0, 1, 0, unsafe.Pointer(&ch)) + close(ch) +} + +func (diff *Diff) ForEachHunk() chan *DiffHunk { + ch := make(chan *DiffHunk, 0) + go diff.forEachHunkWrap(ch) + return ch +} + +//export diffForEachHunkCb +func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, payload unsafe.Pointer) int { + ch := *(*chan *DiffHunk)(payload) + + select { + case ch <-&DiffHunk{*hunk, DiffDelta{*delta}}: + case <-ch: + return -1 + } + + return 0 +} + +func (diff *Diff) forEachLineWrap(ch chan *DiffLine) { + C._go_git_diff_foreach(diff.ptr, 0, 0, 1, unsafe.Pointer(&ch)) + close(ch) +} + +func (diff *Diff) ForEachLine() chan *DiffLine { + ch := make(chan *DiffLine, 0) + go diff.forEachLineWrap(ch) + return ch +} + +//export diffForEachLineCb +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 { + case ch <-&DiffLine{*line, DiffHunk{*hunk, DiffDelta{*delta}}}: + case <-ch: + return -1 + } + + return 0 +} + +func (diff *Diff) NumDeltas() int { + return int(C.git_diff_num_deltas(diff.ptr)) +} + +func (diff *Diff) GetDelta(index int) *DiffDelta { + ptr := C.git_diff_get_delta(diff.ptr, C.size_t(index)) + if ptr == nil { + return nil + } + + return &DiffDelta{*ptr} +} + +func (diff *Diff) Patch(deltaIndex int) *Patch { + var patchPtr *C.git_patch + + C.git_patch_from_diff(&patchPtr, diff.ptr, C.size_t(deltaIndex)) + + return newPatch(patchPtr) +} diff --git a/patch.go b/patch.go new file mode 100644 index 0000000..561786e --- /dev/null +++ b/patch.go @@ -0,0 +1,37 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" +) + +type Patch struct { + ptr *C.git_patch +} + +func newPatch(ptr *C.git_patch) *Patch { + if ptr == nil { + return nil + } + + patch := &Patch{ + ptr: ptr, + } + + runtime.SetFinalizer(patch, (*Patch).Free) + return patch +} + +func (patch *Patch) Free() { + runtime.SetFinalizer(patch, nil) + C.git_patch_free(patch.ptr) +} + +func (patch *Patch) String() string { + var cptr *C.char + C.git_patch_to_str(&cptr, patch.ptr) + return C.GoString(cptr) +} diff --git a/repository.go b/repository.go index 3a9068d..58d3487 100644 --- a/repository.go +++ b/repository.go @@ -146,6 +146,20 @@ func (v *Repository) LookupReference(name string) (*Reference, error) { return newReferenceFromC(ptr), nil } +func (v *Repository) Head() (*Reference, error) { + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_repository_head(&ptr, v.ptr) + if ecode < 0 { + return nil, LastError() + } + + return newReferenceFromC(ptr), nil +} + func (v *Repository) CreateReference(name string, oid *Oid, force bool) (*Reference, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -241,6 +255,23 @@ func (v *Repository) CreateCommit( 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() { runtime.SetFinalizer(v, nil) C.git_odb_free(v.ptr) diff --git a/wrapper.c b/wrapper.c index 2af3974..93331ad 100644 --- a/wrapper.c +++ b/wrapper.c @@ -24,4 +24,25 @@ int _go_git_odb_foreach(git_odb *db, void *payload) { return git_odb_foreach(db, (git_odb_foreach_cb)&odbForEachCb, payload); } + +int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLine, void *payload) +{ + git_diff_file_cb fcb = NULL; + git_diff_hunk_cb hcb = NULL; + git_diff_line_cb lcb = NULL; + + if (eachFile) { + fcb = (git_diff_file_cb)&diffForEachFileCb; + } + + if (eachHunk) { + hcb = (git_diff_hunk_cb)&diffForEachHunkCb; + } + + if (eachLine) { + lcb = (git_diff_line_cb)&diffForEachLineCb; + } + + return git_diff_foreach(diff, fcb, hcb, lcb, payload); +} /* EOF */