diff --git a/blame.go b/blame.go index c24c934..b07d6bc 100644 --- a/blame.go +++ b/blame.go @@ -58,8 +58,8 @@ func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error) version: C.GIT_BLAME_OPTIONS_VERSION, flags: C.uint32_t(opts.Flags), min_match_characters: C.uint16_t(opts.MinMatchCharacters), - min_line: C.uint32_t(opts.MinLine), - max_line: C.uint32_t(opts.MaxLine), + min_line: C.size_t(opts.MinLine), + max_line: C.size_t(opts.MaxLine), } if opts.NewestCommit != nil { copts.newest_commit = *opts.NewestCommit.toC() @@ -100,7 +100,7 @@ func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) { } func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) { - ptr := C.git_blame_get_hunk_byline(blame.ptr, C.uint32_t(lineno)) + ptr := C.git_blame_get_hunk_byline(blame.ptr, C.size_t(lineno)) if ptr == nil { return BlameHunk{}, ErrInvalid } diff --git a/blob.go b/blob.go index 382bb9e..1a86e60 100644 --- a/blob.go +++ b/blob.go @@ -18,7 +18,7 @@ import ( ) type Blob struct { - gitObject + Object cast_ptr *C.git_blob } diff --git a/commit.go b/commit.go index 52f7c01..6830da3 100644 --- a/commit.go +++ b/commit.go @@ -14,7 +14,7 @@ import ( // Commit type Commit struct { - gitObject + Object cast_ptr *C.git_commit } @@ -37,7 +37,7 @@ func (c Commit) Tree() (*Tree, error) { return nil, MakeGitError(err) } - return allocObject((*C.git_object)(ptr), c.repo).(*Tree), nil + return allocTree(ptr, c.repo), nil } func (c Commit) TreeId() *Oid { @@ -61,7 +61,7 @@ func (c *Commit) Parent(n uint) *Commit { return nil } - return allocObject((*C.git_object)(cobj), c.repo).(*Commit) + return allocCommit(cobj, c.repo) } func (c *Commit) ParentId(n uint) *Oid { diff --git a/config.go b/config.go index c4c4028..7408fbc 100644 --- a/config.go +++ b/config.go @@ -12,6 +12,9 @@ import ( type ConfigLevel int const ( + // System-wide on Windows, for compatibility with portable git + ConfigLevelProgramdata ConfigLevel = C.GIT_CONFIG_LEVEL_PROGRAMDATA + // System-wide configuration file; /etc/gitconfig on Linux systems ConfigLevelSystem ConfigLevel = C.GIT_CONFIG_LEVEL_SYSTEM @@ -412,3 +415,21 @@ func ConfigFindXDG() (string, error) { return C.GoString(buf.ptr), nil } + +// ConfigFindProgramdata locate the path to the configuration file in ProgramData. +// +// Look for the file in %PROGRAMDATA%\Git\config used by portable git. +func ConfigFindProgramdata() (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_find_programdata(&buf) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} diff --git a/describe.go b/describe.go index c6f9a79..d75dbcb 100644 --- a/describe.go +++ b/describe.go @@ -127,7 +127,7 @@ func (c *Commit) Describe(opts *DescribeOptions) (*DescribeResult, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_describe_commit(&resultPtr, c.gitObject.ptr, cDescribeOpts) + ecode := C.git_describe_commit(&resultPtr, c.ptr, cDescribeOpts) if ecode < 0 { return nil, MakeGitError(ecode) } diff --git a/diff.go b/diff.go index de56374..565fcee 100644 --- a/diff.go +++ b/diff.go @@ -550,7 +550,7 @@ func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *d if opts.NotifyCallback != nil { C._go_git_setup_diff_notify_callbacks(copts) - copts.notify_payload = pointerHandles.Track(notifyData) + copts.payload = pointerHandles.Track(notifyData) } } return @@ -562,8 +562,8 @@ func freeDiffOptions(copts *C.git_diff_options) { freeStrarray(&cpathspec) C.free(unsafe.Pointer(copts.old_prefix)) C.free(unsafe.Pointer(copts.new_prefix)) - if copts.notify_payload != nil { - pointerHandles.Untrack(copts.notify_payload) + if copts.payload != nil { + pointerHandles.Untrack(copts.payload) } } } diff --git a/git.go b/git.go index 7c7f99c..45b01d5 100644 --- a/git.go +++ b/git.go @@ -53,6 +53,7 @@ const ( // No error ErrOk ErrorCode = C.GIT_OK + // Generic error ErrGeneric ErrorCode = C.GIT_ERROR // Requested object could not be found @@ -63,10 +64,12 @@ const ( ErrAmbigious ErrorCode = C.GIT_EAMBIGUOUS // Output buffer too short to hold data ErrBuffs ErrorCode = C.GIT_EBUFS + // GIT_EUSER is a special error that is never generated by libgit2 // code. You can return it from a callback (e.g to stop an iteration) // to know that it was generated by the callback and not by libgit2. ErrUser ErrorCode = C.GIT_EUSER + // Operation not allowed on bare repository ErrBareRepo ErrorCode = C.GIT_EBAREREPO // HEAD refers to branch with no commits @@ -83,12 +86,27 @@ const ( ErrLocked ErrorCode = C.GIT_ELOCKED // Reference value does not match expected ErrModified ErrorCode = C.GIT_EMODIFIED + // Authentication failed + ErrAuth ErrorCode = C.GIT_EAUTH + // Server certificate is invalid + ErrCertificate ErrorCode = C.GIT_ECERTIFICATE + // Patch/merge has already been applied + ErrApplied ErrorCode = C.GIT_EAPPLIED + // The requested peel operation is not possible + ErrPeel ErrorCode = C.GIT_EPEEL + // Unexpected EOF + ErrEOF ErrorCode = C.GIT_EEOF + // Uncommitted changes in index prevented operation + ErrUncommitted ErrorCode = C.GIT_EUNCOMMITTED + // The operation is not valid for a directory + ErrDirectory ErrorCode = C.GIT_EDIRECTORY + // A merge conflict exists and cannot continue + ErrMergeConflict ErrorCode = C.GIT_EMERGECONFLICT + // Internal only ErrPassthrough ErrorCode = C.GIT_PASSTHROUGH // Signals end of iteration with iterator ErrIterOver ErrorCode = C.GIT_ITEROVER - // Authentication failed - ErrAuth ErrorCode = C.GIT_EAUTH ) var ( diff --git a/ignore.go b/ignore.go new file mode 100644 index 0000000..6b12348 --- /dev/null +++ b/ignore.go @@ -0,0 +1,51 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +func (v *Repository) AddIgnoreRule(rules string) error { + crules := C.CString(rules) + defer C.free(unsafe.Pointer(crules)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_ignore_add_rule(v.ptr, crules) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (v *Repository) ClearInternalIgnoreRules() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_ignore_clear_internal_rules(v.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (v *Repository) IsPathIgnored(path string) (bool, error) { + var ignored C.int + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_ignore_path_is_ignored(&ignored, v.ptr, cpath) + if ret < 0 { + return false, MakeGitError(ret) + } + return ignored == 1, nil +} diff --git a/index.go b/index.go index 1eb5e9d..ae94864 100644 --- a/index.go +++ b/index.go @@ -26,6 +26,24 @@ const ( IndexAddCheckPathspec IndexAddOpts = C.GIT_INDEX_ADD_CHECK_PATHSPEC ) +type IndexStageOpts int + +const ( + // IndexStageAny matches any index stage. + // + // Some index APIs take a stage to match; pass this value to match + // any entry matching the path regardless of stage. + IndexStageAny IndexStageOpts = C.GIT_INDEX_STAGE_ANY + // IndexStageNormal is a normal staged file in the index. + IndexStageNormal IndexStageOpts = C.GIT_INDEX_STAGE_NORMAL + // IndexStageAncestor is the ancestor side of a conflict. + IndexStageAncestor IndexStageOpts = C.GIT_INDEX_STAGE_ANCESTOR + // IndexStageOurs is the "ours" side of a conflict. + IndexStageOurs IndexStageOpts = C.GIT_INDEX_STAGE_OURS + // IndexStageTheirs is the "theirs" side of a conflict. + IndexStageTheirs IndexStageOpts = C.GIT_INDEX_STAGE_THEIRS +) + type Index struct { ptr *C.git_index } @@ -51,8 +69,8 @@ func newIndexEntryFromC(entry *C.git_index_entry) *IndexEntry { return nil } return &IndexEntry{ - IndexTime { int32(entry.ctime.seconds), uint32(entry.ctime.nanoseconds) }, - IndexTime { int32(entry.mtime.seconds), uint32(entry.mtime.nanoseconds) }, + IndexTime{int32(entry.ctime.seconds), uint32(entry.ctime.nanoseconds)}, + IndexTime{int32(entry.mtime.seconds), uint32(entry.mtime.nanoseconds)}, Filemode(entry.mode), uint32(entry.uid), uint32(entry.gid), @@ -280,7 +298,7 @@ func (v *Index) ReadTree(tree *Tree) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_index_read_tree(v.ptr, tree.cast_ptr); + ret := C.git_index_read_tree(v.ptr, tree.cast_ptr) if ret < 0 { return MakeGitError(ret) } @@ -331,6 +349,50 @@ func (v *Index) EntryByIndex(index uint) (*IndexEntry, error) { return newIndexEntryFromC(centry), nil } +func (v *Index) EntryByPath(path string, stage int) (*IndexEntry, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + centry := C.git_index_get_bypath(v.ptr, cpath, C.int(stage)) + if centry == nil { + return nil, MakeGitError(C.GIT_ENOTFOUND) + } + return newIndexEntryFromC(centry), nil +} + +func (v *Index) Find(path string) (uint, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var pos C.size_t + ret := C.git_index_find(&pos, v.ptr, cpath) + if ret < 0 { + return uint(0), MakeGitError(ret) + } + return uint(pos), nil +} + +func (v *Index) FindPrefix(prefix string) (uint, error) { + cprefix := C.CString(prefix) + defer C.free(unsafe.Pointer(cprefix)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var pos C.size_t + ret := C.git_index_find_prefix(&pos, v.ptr, cprefix) + if ret < 0 { + return uint(0), MakeGitError(ret) + } + return uint(pos), nil +} + func (v *Index) HasConflicts() bool { return C.git_index_has_conflicts(v.ptr) != 0 } diff --git a/index_test.go b/index_test.go index 7c65f4f..5f6b375 100644 --- a/index_test.go +++ b/index_test.go @@ -32,10 +32,11 @@ func TestIndexReadTree(t *testing.T) { ref, err := repo.Head() checkFatal(t, err) - obj, err := ref.Peel(ObjectTree); + obj, err := ref.Peel(ObjectTree) checkFatal(t, err) - tree := obj.(*Tree) + tree, err := obj.AsTree() + checkFatal(t, err) idx, err := NewIndex() checkFatal(t, err) diff --git a/merge.go b/merge.go index 756c792..b3fd818 100644 --- a/merge.go +++ b/merge.go @@ -82,12 +82,19 @@ func (r *Repository) AnnotatedCommitFromRef(ref *Reference) (*AnnotatedCommit, e type MergeTreeFlag int const ( - MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_TREE_FIND_RENAMES + // Detect renames that occur between the common ancestor and the "ours" + // side or the common ancestor and the "theirs" side. This will enable + // the ability to merge between a modified and renamed file. + MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_FIND_RENAMES + // If a conflict occurs, exit immediately instead of attempting to + // continue resolving conflicts. The merge operation will fail with + // GIT_EMERGECONFLICT and no index will be returned. + MergeTreeFailOnConflict MergeTreeFlag = C.GIT_MERGE_FAIL_ON_CONFLICT ) type MergeOptions struct { - Version uint - TreeFlags MergeTreeFlag + Version uint + TreeFlags MergeTreeFlag RenameThreshold uint TargetLimit uint @@ -99,7 +106,7 @@ type MergeOptions struct { func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions { return MergeOptions{ Version: uint(opts.version), - TreeFlags: MergeTreeFlag(opts.tree_flags), + TreeFlags: MergeTreeFlag(opts.flags), RenameThreshold: uint(opts.rename_threshold), TargetLimit: uint(opts.target_limit), FileFavor: MergeFileFavor(opts.file_favor), @@ -125,7 +132,7 @@ func (mo *MergeOptions) toC() *C.git_merge_options { } return &C.git_merge_options{ version: C.uint(mo.Version), - tree_flags: C.git_merge_tree_flag_t(mo.TreeFlags), + flags: C.git_merge_flag_t(mo.TreeFlags), rename_threshold: C.uint(mo.RenameThreshold), target_limit: C.uint(mo.TargetLimit), file_favor: C.git_merge_file_favor_t(mo.FileFavor), @@ -263,10 +270,10 @@ func (r *Repository) MergeBases(one, two *Oid) ([]*Oid, error) { } oids := make([]*Oid, coids.count) - hdr := reflect.SliceHeader { + hdr := reflect.SliceHeader{ Data: uintptr(unsafe.Pointer(coids.ids)), - Len: int(coids.count), - Cap: int(coids.count), + Len: int(coids.count), + Cap: int(coids.count), } goSlice := *(*[]C.git_oid)(unsafe.Pointer(&hdr)) @@ -354,7 +361,7 @@ func populateCMergeFileOptions(c *C.git_merge_file_options, options MergeFileOpt c.our_label = C.CString(options.OurLabel) c.their_label = C.CString(options.TheirLabel) c.favor = C.git_merge_file_favor_t(options.Favor) - c.flags = C.uint(options.Flags) + c.flags = C.git_merge_file_flag_t(options.Flags) } func freeCMergeFileOptions(c *C.git_merge_file_options) { diff --git a/merge_test.go b/merge_test.go index ad01319..8059727 100644 --- a/merge_test.go +++ b/merge_test.go @@ -115,7 +115,10 @@ func appendCommit(t *testing.T, repo *Repository) (*Oid, *Oid) { parent, err := ref.Peel(ObjectCommit) checkFatal(t, err) - commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree, parent.(*Commit)) + parentCommit, err := parent.AsCommit() + checkFatal(t, err) + + commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree, parentCommit) checkFatal(t, err) return commitId, treeId diff --git a/object.go b/object.go index 6ecebf8..1981980 100644 --- a/object.go +++ b/object.go @@ -4,7 +4,11 @@ package git #include */ import "C" -import "runtime" +import ( + "errors" + "fmt" + "runtime" +) type ObjectType int @@ -17,15 +21,7 @@ const ( ObjectTag ObjectType = C.GIT_OBJ_TAG ) -type Object interface { - Free() - Id() *Oid - Type() ObjectType - Owner() *Repository - Peel(t ObjectType) (Object, error) -} - -type gitObject struct { +type Object struct { ptr *C.git_object repo *Repository } @@ -49,23 +45,128 @@ func (t ObjectType) String() string { return "" } -func (o gitObject) Id() *Oid { +func (o *Object) Id() *Oid { return newOidFromC(C.git_object_id(o.ptr)) } -func (o gitObject) Type() ObjectType { +func (o *Object) Type() ObjectType { return ObjectType(C.git_object_type(o.ptr)) } // Owner returns a weak reference to the repository which owns this -// object -func (o gitObject) Owner() *Repository { +// object. This won't keep the underlying repository alive. +func (o *Object) Owner() *Repository { return &Repository{ ptr: C.git_object_owner(o.ptr), } } -func (o *gitObject) Free() { +func dupObject(obj *Object, kind ObjectType) (*C.git_object, error) { + if obj.Type() != kind { + return nil, errors.New(fmt.Sprintf("object is not a %v", kind)) + } + + var cobj *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_object_dup(&cobj, obj.ptr); err < 0 { + return nil, MakeGitError(err) + } + + return cobj, nil +} + +func allocTree(ptr *C.git_tree, repo *Repository) *Tree { + tree := &Tree{ + Object: Object{ + ptr: (*C.git_object)(ptr), + repo: repo, + }, + cast_ptr: ptr, + } + runtime.SetFinalizer(tree, (*Tree).Free) + + return tree +} + +func (o *Object) AsTree() (*Tree, error) { + cobj, err := dupObject(o, ObjectTree) + if err != nil { + return nil, err + } + + return allocTree((*C.git_tree)(cobj), o.repo), nil +} + +func allocCommit(ptr *C.git_commit, repo *Repository) *Commit { + commit := &Commit{ + Object: Object{ + ptr: (*C.git_object)(ptr), + repo: repo, + }, + cast_ptr: ptr, + } + runtime.SetFinalizer(commit, (*Commit).Free) + + return commit +} + +func (o *Object) AsCommit() (*Commit, error) { + cobj, err := dupObject(o, ObjectCommit) + if err != nil { + return nil, err + } + + return allocCommit((*C.git_commit)(cobj), o.repo), nil +} + +func allocBlob(ptr *C.git_blob, repo *Repository) *Blob { + blob := &Blob{ + Object: Object{ + ptr: (*C.git_object)(ptr), + repo: repo, + }, + cast_ptr: ptr, + } + runtime.SetFinalizer(blob, (*Blob).Free) + + return blob +} + +func (o *Object) AsBlob() (*Blob, error) { + cobj, err := dupObject(o, ObjectBlob) + if err != nil { + return nil, err + } + + return allocBlob((*C.git_blob)(cobj), o.repo), nil +} + +func allocTag(ptr *C.git_tag, repo *Repository) *Tag { + tag := &Tag{ + Object: Object{ + ptr: (*C.git_object)(ptr), + repo: repo, + }, + cast_ptr: ptr, + } + runtime.SetFinalizer(tag, (*Tag).Free) + + return tag +} + +func (o *Object) AsTag() (*Tag, error) { + cobj, err := dupObject(o, ObjectTag) + if err != nil { + return nil, err + } + + return allocTag((*C.git_tag)(cobj), o.repo), nil +} + +func (o *Object) Free() { runtime.SetFinalizer(o, nil) C.git_object_free(o.ptr) } @@ -82,7 +183,7 @@ func (o *gitObject) Free() { // // If peeling a tag we discover an object which cannot be peeled to the target // type due to the object model, an error will be returned. -func (o *gitObject) Peel(t ObjectType) (Object, error) { +func (o *Object) Peel(t ObjectType) (*Object, error) { var cobj *C.git_object runtime.LockOSThread() @@ -95,44 +196,12 @@ func (o *gitObject) Peel(t ObjectType) (Object, error) { return allocObject(cobj, o.repo), nil } -func allocObject(cobj *C.git_object, repo *Repository) Object { - obj := gitObject{ +func allocObject(cobj *C.git_object, repo *Repository) *Object { + obj := &Object{ ptr: cobj, repo: repo, } + runtime.SetFinalizer(obj, (*Object).Free) - switch ObjectType(C.git_object_type(cobj)) { - case ObjectCommit: - commit := &Commit{ - gitObject: obj, - cast_ptr: (*C.git_commit)(cobj), - } - runtime.SetFinalizer(commit, (*Commit).Free) - return commit - - case ObjectTree: - tree := &Tree{ - gitObject: obj, - cast_ptr: (*C.git_tree)(cobj), - } - runtime.SetFinalizer(tree, (*Tree).Free) - return tree - - case ObjectBlob: - blob := &Blob{ - gitObject: obj, - cast_ptr: (*C.git_blob)(cobj), - } - runtime.SetFinalizer(blob, (*Blob).Free) - return blob - case ObjectTag: - tag := &Tag{ - gitObject: obj, - cast_ptr: (*C.git_tag)(cobj), - } - runtime.SetFinalizer(tag, (*Tag).Free) - return tag - } - - return nil + return obj } diff --git a/object_test.go b/object_test.go index ef6c5a1..2ae2a6a 100644 --- a/object_test.go +++ b/object_test.go @@ -10,12 +10,12 @@ func TestObjectPoymorphism(t *testing.T) { commitId, treeId := seedTestRepo(t, repo) - var obj Object + var obj *Object commit, err := repo.LookupCommit(commitId) checkFatal(t, err) - obj = commit + obj = &commit.Object if obj.Type() != ObjectCommit { t.Fatalf("Wrong object type, expected commit, have %v", obj.Type()) } @@ -27,13 +27,13 @@ func TestObjectPoymorphism(t *testing.T) { tree, err := repo.LookupTree(treeId) checkFatal(t, err) - obj = tree + obj = &tree.Object if obj.Type() != ObjectTree { t.Fatalf("Wrong object type, expected tree, have %v", obj.Type()) } - tree2, ok := obj.(*Tree) - if !ok { + tree2, err := obj.AsTree() + if err != nil { t.Fatalf("Converting back to *Tree is not ok") } @@ -46,16 +46,16 @@ func TestObjectPoymorphism(t *testing.T) { t.Fatal("Wrong filemode for \"README\"") } - _, ok = obj.(*Commit) - if ok { + _, err = obj.AsCommit() + if err == nil { t.Fatalf("*Tree is somehow the same as *Commit") } obj, err = repo.Lookup(tree.Id()) checkFatal(t, err) - _, ok = obj.(*Tree) - if !ok { + _, err = obj.AsTree() + if err != nil { t.Fatalf("Lookup creates the wrong type") } @@ -99,8 +99,8 @@ func TestObjectOwner(t *testing.T) { tree, err := repo.LookupTree(treeId) checkFatal(t, err) - checkOwner(t, repo, commit) - checkOwner(t, repo, tree) + checkOwner(t, repo, commit.Object) + checkOwner(t, repo, tree.Object) } func TestObjectPeel(t *testing.T) { @@ -109,7 +109,7 @@ func TestObjectPeel(t *testing.T) { commitID, treeID := seedTestRepo(t, repo) - var obj Object + var obj *Object commit, err := repo.LookupCommit(commitID) checkFatal(t, err) diff --git a/reference.go b/reference.go index 140082f..463f2fc 100644 --- a/reference.go +++ b/reference.go @@ -263,7 +263,7 @@ func (v *Reference) Delete() error { return nil } -func (v *Reference) Peel(t ObjectType) (Object, error) { +func (v *Reference) Peel(t ObjectType) (*Object, error) { var cobj *C.git_object runtime.LockOSThread() diff --git a/remote.go b/remote.go index 330f202..8a57280 100644 --- a/remote.go +++ b/remote.go @@ -72,12 +72,12 @@ type RemoteCallbacks struct { type FetchPrune uint const ( - // Use the setting from the configuration + // Use the setting from the configuration FetchPruneUnspecified FetchPrune = C.GIT_FETCH_PRUNE_UNSPECIFIED // Force pruning on - FetchPruneOn FetchPrune = C.GIT_FETCH_PRUNE + FetchPruneOn FetchPrune = C.GIT_FETCH_PRUNE // Force pruning off - FetchNoPrune FetchPrune = C.GIT_FETCH_NO_PRUNE + FetchNoPrune FetchPrune = C.GIT_FETCH_NO_PRUNE ) type DownloadTags uint @@ -88,20 +88,20 @@ const ( DownloadTagsUnspecified DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED // Ask the server for tags pointing to objects we're already // downloading. - DownloadTagsAuto DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_AUTO + DownloadTagsAuto DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_AUTO // Don't ask for any tags beyond the refspecs. - DownloadTagsNone DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_NONE + DownloadTagsNone DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_NONE // Ask for the all the tags. - DownloadTagsAll DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_ALL + DownloadTagsAll DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_ALL ) type FetchOptions struct { // Callbacks to use for this fetch operation RemoteCallbacks RemoteCallbacks // Whether to perform a prune after the fetch - Prune FetchPrune + Prune FetchPrune // Whether to write the results to FETCH_HEAD. Defaults to // on. Leave this default in order to behave like git. UpdateFetchhead bool @@ -111,7 +111,10 @@ type FetchOptions struct { // downloading all of them. // // The default is to auto-follow tags. - DownloadTags DownloadTags + DownloadTags DownloadTags + + // Headers are extra headers for the fetch operation. + Headers []string } type Remote struct { @@ -157,6 +160,9 @@ type PushOptions struct { RemoteCallbacks RemoteCallbacks PbParallelism uint + + // Headers are extra headers for the push operation. + Headers []string } type RemoteHead struct { @@ -588,12 +594,16 @@ func (o *Remote) RefspecCount() uint { func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) { C.git_fetch_init_options(options, C.GIT_FETCH_OPTIONS_VERSION) if opts == nil { - return; + return } populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks) options.prune = C.git_fetch_prune_t(opts.Prune) options.update_fetchhead = cbool(opts.UpdateFetchhead) options.download_tags = C.git_remote_autotag_option_t(opts.DownloadTags) + + options.custom_headers = C.git_strarray{} + options.custom_headers.count = C.size_t(len(opts.Headers)) + options.custom_headers.strings = makeCStringsFromStrings(opts.Headers) } func populatePushOptions(options *C.git_push_options, opts *PushOptions) { @@ -604,6 +614,10 @@ func populatePushOptions(options *C.git_push_options, opts *PushOptions) { options.pb_parallelism = C.uint(opts.PbParallelism) + options.custom_headers = C.git_strarray{} + options.custom_headers.count = C.size_t(len(opts.Headers)) + options.custom_headers.strings = makeCStringsFromStrings(opts.Headers) + populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks) } @@ -611,7 +625,7 @@ func populatePushOptions(options *C.git_push_options, opts *PushOptions) { // to use for this fetch, use an empty list to use the refspecs from // the configuration; msg specifies what to use for the reflog // entries. Leave "" to use defaults. -func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error { +func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error { var cmsg *C.char = nil if msg != "" { cmsg = C.CString(msg) @@ -628,6 +642,7 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error populateFetchOptions(coptions, opts) defer untrackCalbacksPayload(&coptions.callbacks) + defer freeStrarray(&coptions.custom_headers) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -639,22 +654,34 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error return nil } -func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks) error { - return o.Connect(ConnectDirectionFetch, callbacks) +func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, headers []string) error { + return o.Connect(ConnectDirectionFetch, callbacks, headers) } -func (o *Remote) ConnectPush(callbacks *RemoteCallbacks) error { - return o.Connect(ConnectDirectionPush, callbacks) +func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error { + return o.Connect(ConnectDirectionPush, callbacks, headers) } -func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks) error { - var ccallbacks C.git_remote_callbacks; +// Connect opens a connection to a remote. +// +// The transport is selected based on the URL. The direction argument +// is due to a limitation of the git protocol (over TCP or SSH) which +// starts up a specific binary which can only do the one or the other. +// +// 'headers' are extra HTTP headers to use in this connection. +func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, headers []string) error { + var ccallbacks C.git_remote_callbacks populateRemoteCallbacks(&ccallbacks, callbacks) + cheaders := C.git_strarray{} + cheaders.count = C.size_t(len(headers)) + cheaders.strings = makeCStringsFromStrings(headers) + defer freeStrarray(&cheaders) + runtime.LockOSThread() defer runtime.UnlockOSThread() - if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks); ret != 0 { + if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cheaders); ret != 0 { return MakeGitError(ret) } return nil @@ -717,6 +744,7 @@ func (o *Remote) Push(refspecs []string, opts *PushOptions) error { populatePushOptions(coptions, opts) defer untrackCalbacksPayload(&coptions.callbacks) + defer freeStrarray(&coptions.custom_headers) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -733,7 +761,7 @@ func (o *Remote) PruneRefs() bool { } func (o *Remote) Prune(callbacks *RemoteCallbacks) error { - var ccallbacks C.git_remote_callbacks; + var ccallbacks C.git_remote_callbacks populateRemoteCallbacks(&ccallbacks, callbacks) runtime.LockOSThread() diff --git a/remote_test.go b/remote_test.go index 73c637f..978b803 100644 --- a/remote_test.go +++ b/remote_test.go @@ -39,7 +39,7 @@ func TestCertificateCheck(t *testing.T) { remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - options := FetchOptions { + options := FetchOptions{ RemoteCallbacks: RemoteCallbacks{ CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode { return assertHostname(cert, valid, hostname, t) @@ -58,7 +58,7 @@ func TestRemoteConnect(t *testing.T) { remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - err = remote.ConnectFetch(nil) + err = remote.ConnectFetch(nil, nil) checkFatal(t, err) } @@ -69,7 +69,7 @@ func TestRemoteLs(t *testing.T) { remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - err = remote.ConnectFetch(nil) + err = remote.ConnectFetch(nil, nil) checkFatal(t, err) heads, err := remote.Ls() @@ -87,7 +87,7 @@ func TestRemoteLsFiltering(t *testing.T) { remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - err = remote.ConnectFetch(nil) + err = remote.ConnectFetch(nil, nil) checkFatal(t, err) heads, err := remote.Ls("master") @@ -166,7 +166,7 @@ func TestRemotePrune(t *testing.T) { rr, err := repo.Remotes.Lookup("origin") checkFatal(t, err) - err = rr.ConnectFetch(nil) + err = rr.ConnectFetch(nil, nil) checkFatal(t, err) err = rr.Prune(nil) diff --git a/repository.go b/repository.go index 12638e1..77e9f9c 100644 --- a/repository.go +++ b/repository.go @@ -12,11 +12,11 @@ import ( // Repository type Repository struct { - ptr *C.git_repository + ptr *C.git_repository // Remotes represents the collection of remotes and can be // used to add, remove and configure remotes for this // repository. - Remotes RemoteCollection + Remotes RemoteCollection // Submodules represents the collection of submodules and can // be used to add, remove and configure submodules in this // repostiory. @@ -26,7 +26,7 @@ type Repository struct { References ReferenceCollection // Notes represents the collection of notes and can be used to // read, write and delete notes from this repository. - Notes NoteCollection + Notes NoteCollection // Tags represents the collection of tags and can be used to create, // list and iterate tags in this repository. Tags TagsCollection @@ -35,10 +35,10 @@ type Repository struct { func newRepositoryFromC(ptr *C.git_repository) *Repository { repo := &Repository{ptr: ptr} - repo.Remotes.repo = repo + repo.Remotes.repo = repo repo.Submodules.repo = repo repo.References.repo = repo - repo.Notes.repo = repo + repo.Notes.repo = repo repo.Tags.repo = repo runtime.SetFinalizer(repo, (*Repository).Free) @@ -62,15 +62,29 @@ func OpenRepository(path string) (*Repository, error) { return newRepositoryFromC(ptr), nil } -func OpenRepositoryExtended(path string) (*Repository, error) { +type RepositoryOpenFlag int + +const ( + RepositoryOpenNoSearch RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_NO_SEARCH + RepositoryOpenCrossFs RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_CROSS_FS + RepositoryOpenBare RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_BARE +) + +func OpenRepositoryExtended(path string, flags RepositoryOpenFlag, ceiling string) (*Repository, error) { cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) + var cceiling *C.char = nil + if len(ceiling) > 0 { + cceiling = C.CString(ceiling) + defer C.free(unsafe.Pointer(cceiling)) + } + runtime.LockOSThread() defer runtime.UnlockOSThread() var ptr *C.git_repository - ret := C.git_repository_open_ext(&ptr, cpath, 0, nil) + ret := C.git_repository_open_ext(&ptr, cpath, C.uint(flags), cceiling) if ret < 0 { return nil, MakeGitError(ret) } @@ -145,7 +159,7 @@ func (v *Repository) Index() (*Index, error) { return newIndexFromC(ptr), nil } -func (v *Repository) lookupType(id *Oid, t ObjectType) (Object, error) { +func (v *Repository) lookupType(id *Oid, t ObjectType) (*Object, error) { var ptr *C.git_object runtime.LockOSThread() @@ -159,7 +173,7 @@ func (v *Repository) lookupType(id *Oid, t ObjectType) (Object, error) { return allocObject(ptr, v), nil } -func (v *Repository) Lookup(id *Oid) (Object, error) { +func (v *Repository) Lookup(id *Oid) (*Object, error) { return v.lookupType(id, ObjectAny) } @@ -169,7 +183,7 @@ func (v *Repository) LookupTree(id *Oid) (*Tree, error) { return nil, err } - return obj.(*Tree), nil + return obj.AsTree() } func (v *Repository) LookupCommit(id *Oid) (*Commit, error) { @@ -178,7 +192,7 @@ func (v *Repository) LookupCommit(id *Oid) (*Commit, error) { return nil, err } - return obj.(*Commit), nil + return obj.AsCommit() } func (v *Repository) LookupBlob(id *Oid) (*Blob, error) { @@ -187,7 +201,7 @@ func (v *Repository) LookupBlob(id *Oid) (*Blob, error) { return nil, err } - return obj.(*Blob), nil + return obj.AsBlob() } func (v *Repository) LookupTag(id *Oid) (*Tag, error) { @@ -196,7 +210,7 @@ func (v *Repository) LookupTag(id *Oid) (*Tag, error) { return nil, err } - return obj.(*Tag), nil + return obj.AsTag() } func (v *Repository) Head() (*Reference, error) { diff --git a/reset.go b/reset.go index b5b7435..9da7625 100644 --- a/reset.go +++ b/reset.go @@ -17,7 +17,7 @@ const ( func (r *Repository) ResetToCommit(commit *Commit, resetType ResetType, opts *CheckoutOpts) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_reset(r.ptr, commit.gitObject.ptr, C.git_reset_t(resetType), opts.toC()) + ret := C.git_reset(r.ptr, commit.ptr, C.git_reset_t(resetType), opts.toC()) if ret < 0 { return MakeGitError(ret) diff --git a/revparse.go b/revparse.go index 7eb04f1..950932b 100644 --- a/revparse.go +++ b/revparse.go @@ -20,16 +20,16 @@ const ( ) type Revspec struct { - to Object - from Object + to *Object + from *Object flags RevparseFlag } -func (rs *Revspec) To() Object { +func (rs *Revspec) To() *Object { return rs.to } -func (rs *Revspec) From() Object { +func (rs *Revspec) From() *Object { return rs.from } @@ -38,8 +38,8 @@ func (rs *Revspec) Flags() RevparseFlag { } func newRevspecFromC(ptr *C.git_revspec, repo *Repository) *Revspec { - var to Object - var from Object + var to *Object + var from *Object if ptr.to != nil { to = allocObject(ptr.to, repo) @@ -73,7 +73,7 @@ func (r *Repository) Revparse(spec string) (*Revspec, error) { return newRevspecFromC(&crevspec, r), nil } -func (v *Repository) RevparseSingle(spec string) (Object, error) { +func (v *Repository) RevparseSingle(spec string) (*Object, error) { cspec := C.CString(spec) defer C.free(unsafe.Pointer(cspec)) @@ -90,7 +90,7 @@ func (v *Repository) RevparseSingle(spec string) (Object, error) { return allocObject(ptr, v), nil } -func (r *Repository) RevparseExt(spec string) (Object, *Reference, error) { +func (r *Repository) RevparseExt(spec string) (*Object, *Reference, error) { cspec := C.CString(spec) defer C.free(unsafe.Pointer(cspec)) diff --git a/revparse_test.go b/revparse_test.go index 091a76b..75e9ffd 100644 --- a/revparse_test.go +++ b/revparse_test.go @@ -46,7 +46,7 @@ func TestRevparseExt(t *testing.T) { } } -func checkObject(t *testing.T, obj Object, id *Oid) { +func checkObject(t *testing.T, obj *Object, id *Oid) { if obj == nil { t.Fatalf("bad object") } diff --git a/tag.go b/tag.go index ca85156..8957430 100644 --- a/tag.go +++ b/tag.go @@ -13,7 +13,7 @@ import ( // Tag type Tag struct { - gitObject + Object cast_ptr *C.git_tag } @@ -30,7 +30,7 @@ func (t Tag) Tagger() *Signature { return newSignatureFromC(cast_ptr) } -func (t Tag) Target() Object { +func (t Tag) Target() *Object { var ptr *C.git_object ret := C.git_tag_target(&ptr, t.cast_ptr) @@ -70,7 +70,7 @@ func (c *TagsCollection) Create( } defer C.git_signature_free(taggerSig) - ctarget := commit.gitObject.ptr + ctarget := commit.ptr runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -102,7 +102,7 @@ func (c *TagsCollection) CreateLightweight(name string, commit *Commit, force bo cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - ctarget := commit.gitObject.ptr + ctarget := commit.ptr runtime.LockOSThread() defer runtime.UnlockOSThread() diff --git a/tree.go b/tree.go index f543c11..eba9f3d 100644 --- a/tree.go +++ b/tree.go @@ -23,7 +23,7 @@ const ( ) type Tree struct { - gitObject + Object cast_ptr *C.git_tree } @@ -149,7 +149,7 @@ func (v *TreeBuilder) Free() { C.git_treebuilder_free(v.ptr) } -func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) error { +func (v *TreeBuilder) Insert(filename string, id *Oid, filemode Filemode) error { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) diff --git a/tree_test.go b/tree_test.go index 4c6a4ed..fae395a 100644 --- a/tree_test.go +++ b/tree_test.go @@ -20,3 +20,44 @@ func TestTreeEntryById(t *testing.T) { t.Fatalf("entry id %v was not found", id) } } + +func TestTreeBuilderInsert(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + subTree, err := repo.TreeBuilder() + if err != nil { + t.Fatalf("TreeBuilder: %v", err) + } + defer subTree.Free() + + odb, err := repo.Odb() + if err != nil { + t.Fatalf("repo.Odb: %v", err) + } + blobId, err := odb.Write([]byte("hello"), ObjectBlob) + if err != nil { + t.Fatalf("odb.Write: %v", err) + } + if err = subTree.Insert("subfile", blobId, FilemodeBlobExecutable); err != nil { + t.Fatalf("TreeBuilder.Insert: %v", err) + } + treeID, err := subTree.Write() + if err != nil { + t.Fatalf("TreeBuilder.Write: %v", err) + } + + tree, err := repo.LookupTree(treeID) + if err != nil { + t.Fatalf("LookupTree: %v", err) + } + + entry, err := tree.EntryByPath("subfile") + if err != nil { + t.Fatalf("tree.EntryByPath(%q): %v", "subfile", err) + } + + if !entry.Id.Equal(blobId) { + t.Fatalf("got oid %v, want %v", entry.Id, blobId) + } +}