diff --git a/status.go b/status.go new file mode 100644 index 0000000..a95c302 --- /dev/null +++ b/status.go @@ -0,0 +1,191 @@ +package git + +/* +#include +#include +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +type Status int + +const ( + StatusCurrent Status = C.GIT_STATUS_CURRENT + StatusIndexNew = C.GIT_STATUS_INDEX_NEW + StatusIndexModified = C.GIT_STATUS_INDEX_MODIFIED + StatusIndexDeleted = C.GIT_STATUS_INDEX_DELETED + StatusIndexRenamed = C.GIT_STATUS_INDEX_RENAMED + StatusIndexTypeChange = C.GIT_STATUS_INDEX_TYPECHANGE + StatusWtNew = C.GIT_STATUS_WT_NEW + StatusWtModified = C.GIT_STATUS_WT_MODIFIED + StatusWtDeleted = C.GIT_STATUS_WT_DELETED + StatusWtTypeChange = C.GIT_STATUS_WT_TYPECHANGE + StatusWtRenamed = C.GIT_STATUS_WT_RENAMED + StatusIgnored = C.GIT_STATUS_IGNORED +) + +type StatusEntry struct { + Status Status + HeadToIndex DiffDelta + IndexToWorkdir DiffDelta +} + +func statusEntryFromC(statusEntry *C.git_status_entry) StatusEntry { + var headToIndex DiffDelta = DiffDelta{} + var indexToWorkdir DiffDelta = DiffDelta{} + + // Based on the libgit2 status example, head_to_index can be null in some cases + if statusEntry.head_to_index != nil { + headToIndex = diffDeltaFromC(statusEntry.head_to_index) + } + if statusEntry.index_to_workdir != nil { + indexToWorkdir = diffDeltaFromC(statusEntry.index_to_workdir) + } + + return StatusEntry { + Status: Status(statusEntry.status), + HeadToIndex: headToIndex, + IndexToWorkdir: indexToWorkdir, + } +} + +type StatusList struct { + ptr *C.git_status_list +} + +func newStatusListFromC(ptr *C.git_status_list) *StatusList { + if ptr == nil { + return nil + } + + statusList := &StatusList{ + ptr: ptr, + } + + runtime.SetFinalizer(statusList, (*StatusList).Free) + return statusList +} + +func (statusList *StatusList) Free() { + if statusList.ptr == nil { + return + } + runtime.SetFinalizer(statusList, nil) + C.git_status_list_free(statusList.ptr) + statusList.ptr = nil +} + +func (statusList *StatusList) ByIndex(index int) (StatusEntry, error) { + if statusList.ptr == nil { + return StatusEntry{}, ErrInvalid + } + ptr := C.git_status_byindex(statusList.ptr, C.size_t(index)) + return statusEntryFromC(ptr), nil +} + +func (statusList *StatusList) EntryCount() (int, error) { + if statusList.ptr == nil { + return -1, ErrInvalid + } + return int(C.git_status_list_entrycount(statusList.ptr)), nil +} + +type StatusOpt int + +const ( + StatusOptIncludeUntracked StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNTRACKED + StatusOptIncludeIgnored = C.GIT_STATUS_OPT_INCLUDE_IGNORED + StatusOptIncludeUnmodified = C.GIT_STATUS_OPT_INCLUDE_UNMODIFIED + StatusOptExcludeSubmodules = C.GIT_STATUS_OPT_EXCLUDE_SUBMODULES + StatusOptRecurseUntrackedDirs = C.GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS + StatusOptDisablePathspecMatch = C.GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH + StatusOptRecurseIgnoredDirs = C.GIT_STATUS_OPT_RECURSE_IGNORED_DIRS + StatusOptRenamesHeadToIndex = C.GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX + StatusOptRenamesIndexToWorkdir = C.GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR + StatusOptSortCaseSensitively = C.GIT_STATUS_OPT_SORT_CASE_SENSITIVELY + StatusOptSortCaseInsensitively = C.GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY + StatusOptRenamesFromRewrites = C.GIT_STATUS_OPT_RENAMES_FROM_REWRITES + StatusOptNoRefresh = C.GIT_STATUS_OPT_NO_REFRESH + StatusOptUpdateIndex = C.GIT_STATUS_OPT_UPDATE_INDEX +) + +type StatusShow int + +const ( + StatusShowIndexAndWorkdir StatusShow = C.GIT_STATUS_SHOW_INDEX_AND_WORKDIR + StatusShowIndexOnly = C.GIT_STATUS_SHOW_INDEX_ONLY + StatusShowWorkdirOnly = C.GIT_STATUS_SHOW_WORKDIR_ONLY +) + +type StatusOptions struct { + Show StatusShow + Flags StatusOpt + Pathspec []string +} + +func (opts *StatusOptions) toC() *C.git_status_options { + if opts == nil { + return nil + } + + cpathspec := C.git_strarray{} + if opts.Pathspec != nil { + cpathspec.count = C.size_t(len(opts.Pathspec)) + cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) + defer freeStrarray(&cpathspec) + } + + copts := &C.git_status_options{ + version: C.GIT_STATUS_OPTIONS_VERSION, + show: C.git_status_show_t(opts.Show), + flags: C.uint(opts.Flags), + pathspec: cpathspec, + } + + return copts +} + +func (v *Repository) StatusList(opts *StatusOptions) (*StatusList, error) { + var ptr *C.git_status_list + var copts *C.git_status_options + + if opts != nil { + copts = opts.toC() + } else { + copts = &C.git_status_options{} + ret := C.git_status_init_options(copts, C.GIT_STATUS_OPTIONS_VERSION) + if ret < 0 { + return nil, MakeGitError(ret) + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_status_list_new(&ptr, v.ptr, copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newStatusListFromC(ptr), nil +} + + +func (v *Repository) StatusFile(path string) (Status, error) { + var statusFlags C.uint + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_status_file(&statusFlags, v.ptr, cPath) + if ret < 0 { + return 0, MakeGitError(ret) + } + return Status(statusFlags), nil +} diff --git a/status_test.go b/status_test.go new file mode 100644 index 0000000..4be4824 --- /dev/null +++ b/status_test.go @@ -0,0 +1,58 @@ +package git + +import ( + "io/ioutil" + "os" + "path" + "testing" +) + +func TestStatusFile(t *testing.T) { + repo := createTestRepo(t) + defer repo.Free() + defer os.RemoveAll(repo.Workdir()) + + err := ioutil.WriteFile(path.Join(path.Dir(repo.Workdir()), "hello.txt"), []byte("Hello, World"), 0644) + checkFatal(t, err) + + status, err := repo.StatusFile("hello.txt") + checkFatal(t, err) + + if status != StatusWtNew { + t.Fatal("Incorrect status flags: ", status) + } +} + +func TestStatusList(t *testing.T) { + repo := createTestRepo(t) + // This commits the test repo README, so it doesn't show up in the status list and there's a head to compare to + seedTestRepo(t, repo) + defer repo.Free() + defer os.RemoveAll(repo.Workdir()) + + err := ioutil.WriteFile(path.Join(path.Dir(repo.Workdir()), "hello.txt"), []byte("Hello, World"), 0644) + checkFatal(t, err) + + opts := &StatusOptions{} + opts.Show = StatusShowIndexAndWorkdir + opts.Flags = StatusOptIncludeUntracked | StatusOptRenamesHeadToIndex | StatusOptSortCaseSensitively + + statusList, err := repo.StatusList(opts) + checkFatal(t, err) + + entryCount, err := statusList.EntryCount() + checkFatal(t, err) + + if entryCount != 1 { + t.Fatal("Incorrect number of status entries: ", entryCount) + } + + entry, err := statusList.ByIndex(0) + checkFatal(t, err) + if entry.Status != StatusWtNew { + t.Fatal("Incorrect status flags: ", entry.Status) + } + if entry.IndexToWorkdir.NewFile.Path != "hello.txt" { + t.Fatal("Incorrect entry path: ", entry.IndexToWorkdir.NewFile.Path) + } +} diff --git a/wrapper.c b/wrapper.c index 2fd8fb7..45c4358 100644 --- a/wrapper.c +++ b/wrapper.c @@ -45,7 +45,7 @@ void _go_git_refdb_backend_free(git_refdb_backend *backend) int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLine, void *payload) { - git_diff_file_cb fcb = NULL; + git_diff_file_cb fcb = NULL; git_diff_hunk_cb hcb = NULL; git_diff_line_cb lcb = NULL;