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.
This commit is contained in:
lye 2014-02-20 00:25:30 -06:00
parent 66e1c47619
commit bc80beb843
4 changed files with 318 additions and 0 deletions

229
diff.go Normal file
View File

@ -0,0 +1,229 @@
package git
/*
#include <git2.h>
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)
}

37
patch.go Normal file
View File

@ -0,0 +1,37 @@
package git
/*
#include <git2.h>
*/
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)
}

View File

@ -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)

View File

@ -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 */