From 9397af0854575913f72b10a7ebf474ef023e2e69 Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Wed, 16 Sep 2015 16:08:37 -0400 Subject: [PATCH 01/27] Expose baseline field in CheckoutOptions --- checkout.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/checkout.go b/checkout.go index e0c067e..a2e312b 100644 --- a/checkout.go +++ b/checkout.go @@ -44,7 +44,8 @@ type CheckoutOpts struct { FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY TargetDirectory string // Alternative checkout path to workdir - Paths []string + Paths []string + Baseline *Tree } func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts { @@ -90,6 +91,10 @@ func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.gi ptr.paths.count = C.size_t(len(opts.Paths)) } + if opts.Baseline != nil { + ptr.baseline = opts.Baseline.cast_ptr + } + return ptr } @@ -156,4 +161,4 @@ func (v *Repository) CheckoutTree(tree *Tree, opts *CheckoutOpts) error { } return nil -} \ No newline at end of file +} From b8283e72771866cae56c9036ace2ff80f125ff50 Mon Sep 17 00:00:00 2001 From: FUJII Ryota Date: Mon, 28 Sep 2015 20:38:04 +0900 Subject: [PATCH 02/27] Add EntryByPath method to Index --- index.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/index.go b/index.go index f4c0c1e..06495b9 100644 --- a/index.go +++ b/index.go @@ -331,6 +331,17 @@ func (v *Index) EntryByIndex(index uint) (*IndexEntry, error) { return newIndexEntryFromC(centry), nil } +func (v *Index) EntryByPath(path string, stage int) (*IndexEntry, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + centry := C.git_index_get_bypath(v.ptr, C.CString(path), C.int(stage)) + if centry == nil { + return nil, MakeGitError(C.GIT_ENOTFOUND) + } + return newIndexEntryFromC(centry), nil +} + func (v *Index) HasConflicts() bool { return C.git_index_has_conflicts(v.ptr) != 0 } From b1d97c1ebd3c66e7311c11c02575d66f41bab953 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Tue, 6 Oct 2015 15:12:49 -0500 Subject: [PATCH 03/27] Fix typo in README: manaager -> manager --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5e6100..315032f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Go bindings for [libgit2](http://libgit2.github.com/). The `master` branch follo Installing ---------- -This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manaager and then install git2go as usual. +This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manager and then install git2go as usual. Otherwise (`next` which tracks an unstable version), we need to build libgit2 as well. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively. From 80cf533fe4e48ddfab3015d9570f2833951c1dea Mon Sep 17 00:00:00 2001 From: David Pierce Date: Sat, 26 Sep 2015 15:37:48 -0700 Subject: [PATCH 04/27] Config#LookupString uses git_buf to load value --- config.go | 8 ++++--- config_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 config_test.go diff --git a/config.go b/config.go index 9d25e35..c4c4028 100644 --- a/config.go +++ b/config.go @@ -115,18 +115,20 @@ func (c *Config) LookupInt64(name string) (int64, error) { } func (c *Config) LookupString(name string) (string, error) { - var ptr *C.char cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) + valBuf := C.git_buf{} + runtime.LockOSThread() defer runtime.UnlockOSThread() - if ret := C.git_config_get_string(&ptr, c.ptr, cname); ret < 0 { + if ret := C.git_config_get_string_buf(&valBuf, c.ptr, cname); ret < 0 { return "", MakeGitError(ret) } + defer C.git_buf_free(&valBuf) - return C.GoString(ptr), nil + return C.GoString(valBuf.ptr), nil } func (c *Config) LookupBool(name string) (bool, error) { diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..e4a2c1f --- /dev/null +++ b/config_test.go @@ -0,0 +1,58 @@ +package git + +import ( + "os" + "testing" +) + +func setupConfig() (*Config, error) { + var ( + c *Config + err error + p string + ) + + p, err = ConfigFindGlobal() + if err != nil { + return nil, err + } + + c, err = OpenOndisk(nil, p) + if err != nil { + return nil, err + } + + c.SetString("foo.bar", "baz") + + return c, err +} + +func cleanupConfig() { + os.Remove(tempConfig) +} + +func TestConfigLookupString(t *testing.T) { + var ( + err error + val string + c *Config + ) + + c, err = setupConfig() + defer cleanupConfig() + if err != nil { + t.Errorf("Setup error: '%v'. Expected none\n", err) + t.FailNow() + } + defer c.Free() + + val, err = c.LookupString("foo.bar") + if err != nil { + t.Errorf("Got error: '%v', expected none\n", err) + t.FailNow() + } + + if val != "baz" { + t.Errorf("Got '%s', expected 'bar'\n", val) + } +} From 81e0b16d9fbbfa916b34d0fa38967a14f8796f49 Mon Sep 17 00:00:00 2001 From: David Pierce Date: Sat, 26 Sep 2015 16:11:49 -0700 Subject: [PATCH 05/27] Tests config lookup methods --- config_test.go | 73 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/config_test.go b/config_test.go index e4a2c1f..8c2decc 100644 --- a/config_test.go +++ b/config_test.go @@ -5,24 +5,23 @@ import ( "testing" ) +var tempConfig = "./temp.gitconfig" + func setupConfig() (*Config, error) { var ( c *Config err error - p string ) - p, err = ConfigFindGlobal() - if err != nil { - return nil, err - } - - c, err = OpenOndisk(nil, p) + c, err = OpenOndisk(nil, tempConfig) if err != nil { return nil, err } c.SetString("foo.bar", "baz") + c.SetBool("foo.bool", true) + c.SetInt32("foo.int32", 32) + c.SetInt64("foo.int64", 64) return c, err } @@ -31,28 +30,66 @@ func cleanupConfig() { os.Remove(tempConfig) } -func TestConfigLookupString(t *testing.T) { +type TestRunner func(*Config, *testing.T) + +var tests = []TestRunner{ + // LookupString + func(c *Config, t *testing.T) { + val, err := c.LookupString("foo.bar") + if err != nil { + t.Errorf("Got LookupString error: '%v', expected none\n", err) + } + if val != "baz" { + t.Errorf("Got '%s' from LookupString, expected 'bar'\n", val) + } + }, + // LookupBool + func(c *Config, t *testing.T) { + val, err := c.LookupBool("foo.bool") + if err != nil { + t.Errorf("Got LookupBool error: '%v', expected none\n", err) + } + if !val { + t.Errorf("Got %b from LookupBool, expected 'false'\n", val) + } + }, + // LookupInt32 + func(c *Config, t *testing.T) { + val, err := c.LookupInt32("foo.int32") + if err != nil { + t.Errorf("Got LookupInt32 error: '%v', expected none\n", err) + } + if val != 32 { + t.Errorf("Got %v, expected 32\n", val) + } + }, + // LookupInt64 + func(c *Config, t *testing.T) { + val, err := c.LookupInt64("foo.int64") + if err != nil { + t.Errorf("Got LookupInt64 error: '%v', expected none\n", err) + } + if val != 64 { + t.Errorf("Got %v, expected 64\n", val) + } + }, +} + +func TestConfigLookups(t *testing.T) { var ( err error - val string c *Config ) c, err = setupConfig() defer cleanupConfig() + if err != nil { t.Errorf("Setup error: '%v'. Expected none\n", err) - t.FailNow() } defer c.Free() - val, err = c.LookupString("foo.bar") - if err != nil { - t.Errorf("Got error: '%v', expected none\n", err) - t.FailNow() - } - - if val != "baz" { - t.Errorf("Got '%s', expected 'bar'\n", val) + for _, test := range tests { + test(c, t) } } From 22495763b73d1560c5c8b8182ee8b723adee1bcd Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Tue, 13 Oct 2015 11:31:00 -0400 Subject: [PATCH 06/27] Expose AddGitIgnoreRules and ClearGitIgnoreRules funcs --- repository.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/repository.go b/repository.go index 62fde6d..12638e1 100644 --- a/repository.go +++ b/repository.go @@ -433,3 +433,24 @@ func (r *Repository) StateCleanup() error { } return nil } +func (r *Repository) AddGitIgnoreRules(rules string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + crules := C.CString(rules) + defer C.free(unsafe.Pointer(crules)) + if ret := C.git_ignore_add_rule(r.ptr, crules); ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (r *Repository) ClearGitIgnoreRules() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_ignore_clear_internal_rules(r.ptr); ret < 0 { + return MakeGitError(ret) + } + return nil +} From 56cc9e1b0eafb1275be42ca60cfaf78297df8d60 Mon Sep 17 00:00:00 2001 From: FUJII Ryota Date: Fri, 9 Oct 2015 16:28:08 +0900 Subject: [PATCH 07/27] Add support for ignore --- ignore.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 ignore.go 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 +} From 367cd8eb9b84538933774befa76c099298c32c81 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Mon, 26 Oct 2015 16:20:18 +0200 Subject: [PATCH 08/27] Update libgit2 to 821131f The API changes are: - `*Remote.Connect` ```go // from: func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks) error // to: func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, headers []string) error ``` - `*Remote.ConnectFetch` - `headers` was added as above - `*Remote.ConnectPush` - `headers` was added as above --- config.go | 21 +++++++++++++++++++++ git.go | 22 ++++++++++++++++++++-- index.go | 18 ++++++++++++++++++ merge.go | 7 +++++++ remote.go | 40 ++++++++++++++++++++++++++++++++++------ remote_test.go | 8 ++++---- vendor/libgit2 | 2 +- 7 files changed, 105 insertions(+), 13 deletions(-) diff --git a/config.go b/config.go index 9d25e35..73365c8 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 @@ -410,3 +413,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/git.go b/git.go index 34d58e6..2b2a909 100644 --- a/git.go +++ b/git.go @@ -52,6 +52,7 @@ const ( // No error ErrOk ErrorCode = C.GIT_OK + // Generic error ErrGeneric ErrorCode = C.GIT_ERROR // Requested object could not be found @@ -62,10 +63,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 @@ -82,12 +85,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/index.go b/index.go index 06495b9..1875e32 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 } diff --git a/merge.go b/merge.go index a52e8f8..53d5a72 100644 --- a/merge.go +++ b/merge.go @@ -81,7 +81,14 @@ func (r *Repository) AnnotatedCommitFromRef(ref *Reference) (*AnnotatedCommit, e type MergeTreeFlag int const ( + // 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_TREE_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_TREE_FAIL_ON_CONFLICT ) type MergeOptions struct { diff --git a/remote.go b/remote.go index b3aba54..a216513 100644 --- a/remote.go +++ b/remote.go @@ -112,6 +112,9 @@ type FetchOptions struct { // // The default is to auto-follow tags. 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 { @@ -594,6 +600,10 @@ func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) { 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) } @@ -626,6 +640,7 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error var coptions C.git_fetch_options populateFetchOptions(&coptions, opts) defer untrackCalbacksPayload(&coptions.callbacks) + defer freeStrarray(&coptions.custom_headers) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -637,22 +652,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 { +// 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 @@ -713,6 +740,7 @@ func (o *Remote) Push(refspecs []string, opts *PushOptions) error { var coptions C.git_push_options populatePushOptions(&coptions, opts) defer untrackCalbacksPayload(&coptions.callbacks) + defer freeStrarray(&coptions.custom_headers) runtime.LockOSThread() defer runtime.UnlockOSThread() diff --git a/remote_test.go b/remote_test.go index dac3dbe..978b803 100644 --- a/remote_test.go +++ b/remote_test.go @@ -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/vendor/libgit2 b/vendor/libgit2 index ed38e26..821131f 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit ed38e26db5435b519d8b796e4b6c2c660fe982b5 +Subproject commit 821131fdaee74526d84aaf1c6ceddc2139c551df From 3b5633de219eec2908723a8e557758cc055d84d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 26 Oct 2015 21:22:22 +0100 Subject: [PATCH 09/27] Mention that MergeAnalysis is a bitmask --- merge.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/merge.go b/merge.go index bab53e0..272bf6a 100644 --- a/merge.go +++ b/merge.go @@ -178,6 +178,9 @@ const ( MergePreferenceFastForwardOnly MergePreference = C.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY ) +// MergeAnalysis returns the possible actions which could be taken by +// a 'git-merge' command. There may be multiple answers, so the first +// return value is a bitmask of MergeAnalysis values. func (r *Repository) MergeAnalysis(theirHeads []*AnnotatedCommit) (MergeAnalysis, MergePreference, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() From ae107d5f56a0cb2b9703ad3733143e0f6acc01de Mon Sep 17 00:00:00 2001 From: FUJII Ryota Date: Tue, 27 Oct 2015 15:20:50 +0900 Subject: [PATCH 10/27] Fix memory leaks in NewIndex() and OpenIndex() --- index.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.go b/index.go index 0174dc1..1eb5e9d 100644 --- a/index.go +++ b/index.go @@ -97,7 +97,7 @@ func NewIndex() (*Index, error) { return nil, MakeGitError(err) } - return &Index{ptr: ptr}, nil + return newIndexFromC(ptr), nil } // OpenIndex creates a new index at the given path. If the file does @@ -115,7 +115,7 @@ func OpenIndex(path string) (*Index, error) { return nil, MakeGitError(err) } - return &Index{ptr: ptr}, nil + return newIndexFromC(ptr), nil } // Path returns the index' path on disk or an empty string if it From f18ea412dc8d3a43930bb7f9143dcc2e01d60158 Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Mon, 2 Nov 2015 15:47:59 -0500 Subject: [PATCH 11/27] config_test: properly detect failed config writes This patch fixes the setup stage of the config tests to notice when the writes fail (eg $PWD is a read-only filesystem) and to correctly skip the entire test function as a result. --- config_test.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/config_test.go b/config_test.go index 8c2decc..fea8d8a 100644 --- a/config_test.go +++ b/config_test.go @@ -18,10 +18,22 @@ func setupConfig() (*Config, error) { return nil, err } - c.SetString("foo.bar", "baz") - c.SetBool("foo.bool", true) - c.SetInt32("foo.int32", 32) - c.SetInt64("foo.int64", 64) + err = c.SetString("foo.bar", "baz") + if err != nil { + return nil, err + } + err = c.SetBool("foo.bool", true) + if err != nil { + return nil, err + } + err = c.SetInt32("foo.int32", 32) + if err != nil { + return nil, err + } + err = c.SetInt64("foo.int64", 64) + if err != nil { + return nil, err + } return c, err } @@ -86,6 +98,7 @@ func TestConfigLookups(t *testing.T) { if err != nil { t.Errorf("Setup error: '%v'. Expected none\n", err) + return } defer c.Free() From 714cd56c715d22e9759413783c9c4be0018193e0 Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Mon, 2 Nov 2015 15:51:03 -0500 Subject: [PATCH 12/27] odb: remove debug fmt.Printlns These appear to be left over debug statements, and they also look like they were intended to be fmt.Printf calls anyway. --- odb.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/odb.go b/odb.go index be0870e..b15851f 100644 --- a/odb.go +++ b/odb.go @@ -11,7 +11,6 @@ import ( "reflect" "runtime" "unsafe" - "fmt" ) type Odb struct { @@ -107,9 +106,7 @@ func odbForEachCb(id *C.git_oid, handle unsafe.Pointer) int { } err := data.callback(newOidFromC(id)) - fmt.Println("err %v", err) if err != nil { - fmt.Println("returning EUSER") data.err = err return C.GIT_EUSER } @@ -130,7 +127,6 @@ func (v *Odb) ForEach(callback OdbForEachCallback) error { defer pointerHandles.Untrack(handle) ret := C._go_git_odb_foreach(v.ptr, handle) - fmt.Println("ret %v", ret); if ret == C.GIT_EUSER { return data.err } else if ret < 0 { From 92d736d12c5263826662ad26a152b7f027aa1efa Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Thu, 12 Nov 2015 21:15:24 -0500 Subject: [PATCH 13/27] Fix Fetch/Push memory allocation problems The Fetch/Push operations didn't allocate the git_*_options structure and this causes a memory problem in the libgit2 code. Following the example of Clone operation, the Fetch/Push functions allocates the options structure before calling the C. --- remote.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/remote.go b/remote.go index b2fb96f..330f202 100644 --- a/remote.go +++ b/remote.go @@ -623,14 +623,16 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error crefspecs.strings = makeCStringsFromStrings(refspecs) defer freeStrarray(&crefspecs) - var coptions C.git_fetch_options - populateFetchOptions(&coptions, opts); + coptions := (*C.git_fetch_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_fetch_options{})))) + defer C.free(unsafe.Pointer(coptions)) + + populateFetchOptions(coptions, opts) defer untrackCalbacksPayload(&coptions.callbacks) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_fetch(o.ptr, &crefspecs, &coptions, cmsg) + ret := C.git_remote_fetch(o.ptr, &crefspecs, coptions, cmsg) if ret < 0 { return MakeGitError(ret) } @@ -710,14 +712,16 @@ func (o *Remote) Push(refspecs []string, opts *PushOptions) error { crefspecs.strings = makeCStringsFromStrings(refspecs) defer freeStrarray(&crefspecs) - var coptions C.git_push_options - populatePushOptions(&coptions, opts) + coptions := (*C.git_push_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_push_options{})))) + defer C.free(unsafe.Pointer(coptions)) + + populatePushOptions(coptions, opts) defer untrackCalbacksPayload(&coptions.callbacks) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_push(o.ptr, &crefspecs, &coptions) + ret := C.git_remote_push(o.ptr, &crefspecs, coptions) if ret < 0 { return MakeGitError(ret) } From 1cdf1d70a2c08b1b87611be11cb448075ea45f2b Mon Sep 17 00:00:00 2001 From: FUJII Ryota Date: Wed, 16 Dec 2015 16:37:50 +0900 Subject: [PATCH 14/27] Fix a memory leak in Index.EntryByPath() --- index.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.go b/index.go index 1875e32..8417b65 100644 --- a/index.go +++ b/index.go @@ -350,10 +350,13 @@ func (v *Index) EntryByIndex(index uint) (*IndexEntry, error) { } 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, C.CString(path), C.int(stage)) + centry := C.git_index_get_bypath(v.ptr, cpath, C.int(stage)) if centry == nil { return nil, MakeGitError(C.GIT_ENOTFOUND) } From 20ab28bfeacbfd0c0b1a83e471de8de5610f9de8 Mon Sep 17 00:00:00 2001 From: FUJII Ryota Date: Mon, 21 Dec 2015 18:19:03 +0900 Subject: [PATCH 15/27] Add Index.Find() and Index.FindPrefix() --- index.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/index.go b/index.go index 8417b65..16e63a1 100644 --- a/index.go +++ b/index.go @@ -363,6 +363,36 @@ func (v *Index) EntryByPath(path string, stage int) (*IndexEntry, error) { 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 } From 51d3ead30d58d523cef5aaea1666c8e28a94dc42 Mon Sep 17 00:00:00 2001 From: Michael Gehring Date: Mon, 4 Jan 2016 15:02:21 +0100 Subject: [PATCH 16/27] Don't drop CreateBlobFromChunks hintPath argument --- blob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blob.go b/blob.go index b1fc78a..382bb9e 100644 --- a/blob.go +++ b/blob.go @@ -84,7 +84,7 @@ func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunk var chintPath *C.char = nil if len(hintPath) > 0 { - C.CString(hintPath) + chintPath = C.CString(hintPath) defer C.free(unsafe.Pointer(chintPath)) } oid := C.git_oid{} From 42b11d403d91e25754fe60c63c26371fbb7a89a9 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 7 Jan 2016 18:37:46 -0800 Subject: [PATCH 17/27] handles, merge, odb: changes for Go 1.6 pointer passing rules See http://tip.golang.org/cmd/cgo/#hdr-Passing_pointers . --- handles.go | 6 +++--- merge.go | 34 +++++++++++++++++++--------------- odb.go | 5 +++-- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/handles.go b/handles.go index a062231..a855717 100644 --- a/handles.go +++ b/handles.go @@ -52,12 +52,12 @@ func (v *HandleList) Track(pointer interface{}) unsafe.Pointer { v.Unlock() - return unsafe.Pointer(&slot) + return unsafe.Pointer(uintptr(slot)) } // Untrack stops tracking the pointer given by the handle func (v *HandleList) Untrack(handle unsafe.Pointer) { - slot := *(*int)(handle) + slot := int(uintptr(handle)) v.Lock() @@ -69,7 +69,7 @@ func (v *HandleList) Untrack(handle unsafe.Pointer) { // Get retrieves the pointer from the given handle func (v *HandleList) Get(handle unsafe.Pointer) interface{} { - slot := *(*int)(handle) + slot := int(uintptr(handle)) v.RLock() diff --git a/merge.go b/merge.go index 272bf6a..535a5e7 100644 --- a/merge.go +++ b/merge.go @@ -85,8 +85,8 @@ const ( ) type MergeOptions struct { - Version uint - TreeFlags MergeTreeFlag + Version uint + TreeFlags MergeTreeFlag RenameThreshold uint TargetLimit uint @@ -98,7 +98,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.tree_flags), RenameThreshold: uint(opts.rename_threshold), TargetLimit: uint(opts.target_limit), FileFavor: MergeFileFavor(opts.file_favor), @@ -262,10 +262,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)) @@ -321,17 +321,21 @@ type MergeFileInput struct { } // populate a C struct with merge file input, make sure to use freeMergeFileInput to clean up allocs -func populateCMergeFileInput(c *C.git_merge_file_input, input MergeFileInput) { +func populateCMergeFileInput(c *C.git_merge_file_input, input MergeFileInput) *C.char { c.path = C.CString(input.Path) + var toFree *C.char if input.Contents != nil { - c.ptr = (*C.char)(unsafe.Pointer(&input.Contents[0])) + toFree = C.CString(string(input.Contents)) + c.ptr = toFree c.size = C.size_t(len(input.Contents)) } c.mode = C.uint(input.Mode) + return toFree } -func freeCMergeFileInput(c *C.git_merge_file_input) { +func freeCMergeFileInput(c *C.git_merge_file_input, toFree *C.char) { C.free(unsafe.Pointer(c.path)) + C.free(unsafe.Pointer(toFree)) } type MergeFileFlags int @@ -382,12 +386,12 @@ func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInp var cours C.git_merge_file_input var ctheirs C.git_merge_file_input - populateCMergeFileInput(&cancestor, ancestor) - defer freeCMergeFileInput(&cancestor) - populateCMergeFileInput(&cours, ours) - defer freeCMergeFileInput(&cours) - populateCMergeFileInput(&ctheirs, theirs) - defer freeCMergeFileInput(&ctheirs) + t := populateCMergeFileInput(&cancestor, ancestor) + defer freeCMergeFileInput(&cancestor, t) + t = populateCMergeFileInput(&cours, ours) + defer freeCMergeFileInput(&cours, t) + t = populateCMergeFileInput(&ctheirs, theirs) + defer freeCMergeFileInput(&ctheirs, t) var copts *C.git_merge_file_options if options != nil { diff --git a/odb.go b/odb.go index b15851f..8e8fba0 100644 --- a/odb.go +++ b/odb.go @@ -61,12 +61,13 @@ func (v *Odb) Exists(oid *Oid) bool { func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) { oid = new(Oid) - hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + cstr := C.CString(string(data)) + defer C.free(unsafe.Pointer(cstr)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_odb_write(oid.toC(), v.ptr, unsafe.Pointer(hdr.Data), C.size_t(hdr.Len), C.git_otype(otype)) + ret := C.git_odb_write(oid.toC(), v.ptr, unsafe.Pointer(cstr), C.size_t(len(data)), C.git_otype(otype)) if ret < 0 { return nil, MakeGitError(ret) From 1bc7cf60bd05958ad619c22373183afffb913a0c Mon Sep 17 00:00:00 2001 From: Michael Gehring Date: Mon, 4 Jan 2016 14:47:02 +0100 Subject: [PATCH 18/27] Add missing RepositoryOpenExtended arguments Fixes #277 --- repository.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/repository.go b/repository.go index d8e398b..398f91a 100644 --- a/repository.go +++ b/repository.go @@ -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) } From 773ac24a16da6261e0f31b9354a9c890b1002422 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Tue, 2 Feb 2016 19:02:15 +0100 Subject: [PATCH 19/27] Expose git_odb_read_header as Odb.ReadHeader. This function is much faster for discovering sizes for a given OID. --- odb.go | 15 +++++++++++++++ odb_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/odb.go b/odb.go index b15851f..d881f63 100644 --- a/odb.go +++ b/odb.go @@ -54,6 +54,21 @@ func (v *Odb) AddBackend(backend *OdbBackend, priority int) (err error) { return nil } +func (v *Odb) ReadHeader(oid *Oid) (uint64, ObjectType, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var sz C.size_t + var cotype C.git_otype + + ret := C.git_odb_read_header(&sz, &cotype, v.ptr, oid.toC()) + if ret < 0 { + return 0, C.GIT_OBJ_BAD, MakeGitError(ret) + } + + return uint64(sz), ObjectType(cotype), nil +} + func (v *Odb) Exists(oid *Oid) bool { ret := C.git_odb_exists(v.ptr, oid.toC()) return ret != 0 diff --git a/odb_test.go b/odb_test.go index 0d765b9..dfd2ad0 100644 --- a/odb_test.go +++ b/odb_test.go @@ -6,6 +6,34 @@ import ( "testing" ) +func TestOdbReadHeader(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + _, _ = seedTestRepo(t, repo) + odb, err := repo.Odb() + if err != nil { + t.Fatalf("Odb: %v", err) + } + data := []byte("hello") + id, err := odb.Write(data, ObjectBlob) + if err != nil { + t.Fatalf("odb.Write: %v", err) + } + + sz, typ, err := odb.ReadHeader(id) + if err != nil { + t.Fatalf("ReadHeader: %v", err) + } + + if sz != uint64(len(data)) { + t.Errorf("ReadHeader got size %d, want %d", sz, len(data)) + } + if typ != ObjectBlob { + t.Errorf("ReadHeader got object type %s", typ) + } +} + func TestOdbStream(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) From b876e836fa76747b34832f42c354934854601f7d Mon Sep 17 00:00:00 2001 From: Orivej Desh Date: Sun, 7 Feb 2016 03:12:35 +0000 Subject: [PATCH 20/27] Add RevWalk.SimplifyFirstParent() --- walk.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/walk.go b/walk.go index c314f60..60e618d 100644 --- a/walk.go +++ b/walk.go @@ -194,6 +194,10 @@ func (v *RevWalk) Iterate(fun RevWalkIterator) (err error) { return nil } +func (v *RevWalk) SimplifyFirstParent() { + C.git_revwalk_simplify_first_parent(v.ptr) +} + func (v *RevWalk) Sorting(sm SortType) { C.git_revwalk_sorting(v.ptr, C.uint(sm)) } From a1f25eafec55509d49dffb4c84f7c5b729e6a85e Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 16 Feb 2016 21:34:43 -0800 Subject: [PATCH 21/27] handles, merge: simplify code, don't copy file contents --- handles.go | 14 +++++------- merge.go | 65 ++++++++++++++++++++++++++---------------------------- wrapper.c | 23 +++++++++++++++++++ 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/handles.go b/handles.go index a855717..f5d30f0 100644 --- a/handles.go +++ b/handles.go @@ -10,15 +10,14 @@ type HandleList struct { sync.RWMutex // stores the Go pointers handles []interface{} - // Indicates which indices are in use, and keeps a pointer to slot int variable (the handle) - // in the Go world, so that the Go garbage collector does not free it. - set map[int]*int + // Indicates which indices are in use. + set map[int]bool } func NewHandleList() *HandleList { return &HandleList{ handles: make([]interface{}, 5), - set: make(map[int]*int), + set: make(map[int]bool), } } @@ -26,8 +25,7 @@ func NewHandleList() *HandleList { // list. You must only run this function while holding a write lock. func (v *HandleList) findUnusedSlot() int { for i := 1; i < len(v.handles); i++ { - _, isUsed := v.set[i] - if !isUsed { + if !v.set[i] { return i } } @@ -48,7 +46,7 @@ func (v *HandleList) Track(pointer interface{}) unsafe.Pointer { slot := v.findUnusedSlot() v.handles[slot] = pointer - v.set[slot] = &slot // Keep a pointer to slot in Go world, so it's not freed by GC. + v.set[slot] = true v.Unlock() @@ -73,7 +71,7 @@ func (v *HandleList) Get(handle unsafe.Pointer) interface{} { v.RLock() - if _, ok := v.set[slot]; !ok { + if !v.set[slot] { panic(fmt.Sprintf("invalid pointer handle: %p", handle)) } diff --git a/merge.go b/merge.go index 535a5e7..06f5676 100644 --- a/merge.go +++ b/merge.go @@ -6,6 +6,7 @@ package git extern git_annotated_commit** _go_git_make_merge_head_array(size_t len); extern void _go_git_annotated_commit_array_set(git_annotated_commit** array, git_annotated_commit* ptr, size_t n); extern git_annotated_commit* _go_git_annotated_commit_array_get(git_annotated_commit** array, size_t n); +extern int _go_git_merge_file(git_merge_file_result*, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, git_merge_file_options*); */ import "C" @@ -85,8 +86,8 @@ const ( ) type MergeOptions struct { - Version uint - TreeFlags MergeTreeFlag + Version uint + TreeFlags MergeTreeFlag RenameThreshold uint TargetLimit uint @@ -98,7 +99,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.tree_flags), RenameThreshold: uint(opts.rename_threshold), TargetLimit: uint(opts.target_limit), FileFavor: MergeFileFavor(opts.file_favor), @@ -262,10 +263,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)) @@ -320,24 +321,6 @@ type MergeFileInput struct { Contents []byte } -// populate a C struct with merge file input, make sure to use freeMergeFileInput to clean up allocs -func populateCMergeFileInput(c *C.git_merge_file_input, input MergeFileInput) *C.char { - c.path = C.CString(input.Path) - var toFree *C.char - if input.Contents != nil { - toFree = C.CString(string(input.Contents)) - c.ptr = toFree - c.size = C.size_t(len(input.Contents)) - } - c.mode = C.uint(input.Mode) - return toFree -} - -func freeCMergeFileInput(c *C.git_merge_file_input, toFree *C.char) { - C.free(unsafe.Pointer(c.path)) - C.free(unsafe.Pointer(toFree)) -} - type MergeFileFlags int const ( @@ -382,16 +365,26 @@ func freeCMergeFileOptions(c *C.git_merge_file_options) { func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInput, options *MergeFileOptions) (*MergeFileResult, error) { - var cancestor C.git_merge_file_input - var cours C.git_merge_file_input - var ctheirs C.git_merge_file_input + ancestorPath := C.CString(ancestor.Path) + defer C.free(unsafe.Pointer(ancestorPath)) + var ancestorContents *byte + if len(ancestor.Contents) > 0 { + ancestorContents = &ancestor.Contents[0] + } - t := populateCMergeFileInput(&cancestor, ancestor) - defer freeCMergeFileInput(&cancestor, t) - t = populateCMergeFileInput(&cours, ours) - defer freeCMergeFileInput(&cours, t) - t = populateCMergeFileInput(&ctheirs, theirs) - defer freeCMergeFileInput(&ctheirs, t) + oursPath := C.CString(ours.Path) + defer C.free(unsafe.Pointer(oursPath)) + var oursContents *byte + if len(ours.Contents) > 0 { + oursContents = &ours.Contents[0] + } + + theirsPath := C.CString(theirs.Path) + defer C.free(unsafe.Pointer(theirsPath)) + var theirsContents *byte + if len(theirs.Contents) > 0 { + theirsContents = &theirs.Contents[0] + } var copts *C.git_merge_file_options if options != nil { @@ -408,7 +401,11 @@ func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInp defer runtime.UnlockOSThread() var result C.git_merge_file_result - ecode := C.git_merge_file(&result, &cancestor, &cours, &ctheirs, copts) + ecode := C._go_git_merge_file(&result, + (*C.char)(unsafe.Pointer(ancestorContents)), C.size_t(len(ancestor.Contents)), ancestorPath, C.uint(ancestor.Mode), + (*C.char)(unsafe.Pointer(oursContents)), C.size_t(len(ours.Contents)), oursPath, C.uint(ours.Mode), + (*C.char)(unsafe.Pointer(theirsContents)), C.size_t(len(theirs.Contents)), theirsPath, C.uint(theirs.Mode), + copts) if ecode < 0 { return nil, MakeGitError(ecode) } diff --git a/wrapper.c b/wrapper.c index 2b1a180..a0688c0 100644 --- a/wrapper.c +++ b/wrapper.c @@ -141,4 +141,27 @@ int _go_git_tag_foreach(git_repository *repo, void *payload) return git_tag_foreach(repo, (git_tag_foreach_cb)&gitTagForeachCb, payload); } +int _go_git_merge_file(git_merge_file_result* out, char* ancestorContents, size_t ancestorLen, char* ancestorPath, unsigned int ancestorMode, char* oursContents, size_t oursLen, char* oursPath, unsigned int oursMode, char* theirsContents, size_t theirsLen, char* theirsPath, unsigned int theirsMode, git_merge_file_options* copts) { + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_input ours = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_input theirs = GIT_MERGE_FILE_INPUT_INIT; + + ancestor.ptr = ancestorContents; + ancestor.size = ancestorLen; + ancestor.path = ancestorPath; + ancestor.mode = ancestorMode; + + ours.ptr = oursContents; + ours.size = oursLen; + ours.path = oursPath; + ours.mode = oursMode; + + theirs.ptr = theirsContents; + theirs.size = theirsLen; + theirs.path = theirsPath; + theirs.mode = theirsMode; + + return git_merge_file(out, &ancestor, &ours, &theirs, copts); +} + /* EOF */ From ddbf1baab1f6d066f2bda591c1e68340845392c8 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 16 Feb 2016 21:36:57 -0800 Subject: [PATCH 22/27] merge: remove whitespace change --- merge.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merge.go b/merge.go index 06f5676..756c792 100644 --- a/merge.go +++ b/merge.go @@ -265,8 +265,8 @@ func (r *Repository) MergeBases(one, two *Oid) ([]*Oid, error) { oids := make([]*Oid, coids.count) 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)) From dc8b154f4f1b346fb6b8dee99fcfa6e4ca2d2d24 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 16 Feb 2016 21:40:05 -0800 Subject: [PATCH 23/27] odb: don't copy buffer --- odb.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/odb.go b/odb.go index dfad507..9c6baa3 100644 --- a/odb.go +++ b/odb.go @@ -76,13 +76,15 @@ func (v *Odb) Exists(oid *Oid) bool { func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) { oid = new(Oid) - cstr := C.CString(string(data)) - defer C.free(unsafe.Pointer(cstr)) + var cptr unsafe.Pointer + if len(data) > 0 { + cptr = unsafe.Pointer(&data[0]) + } runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_odb_write(oid.toC(), v.ptr, unsafe.Pointer(cstr), C.size_t(len(data)), C.git_otype(otype)) + ret := C.git_odb_write(oid.toC(), v.ptr, cptr, C.size_t(len(data)), C.git_otype(otype)) if ret < 0 { return nil, MakeGitError(ret) From aa59dccea724221f99ea57a8f803101b786809ef Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Tue, 16 Feb 2016 17:22:43 +0100 Subject: [PATCH 24/27] Upgrade to libgit2 to 0f9d15493d5d8ad4353dd7beed52c9567334f6e5 --- blame.go | 6 +++--- diff.go | 6 +++--- merge.go | 10 +++++----- vendor/libgit2 | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) 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/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/merge.go b/merge.go index 0b0a8f1..9ca50fd 100644 --- a/merge.go +++ b/merge.go @@ -84,11 +84,11 @@ const ( // 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_TREE_FIND_RENAMES + 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_TREE_FAIL_ON_CONFLICT + MergeTreeFailOnConflict MergeTreeFlag = C.GIT_MERGE_FAIL_ON_CONFLICT ) type MergeOptions struct { @@ -105,7 +105,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), @@ -131,7 +131,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), @@ -374,7 +374,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/vendor/libgit2 b/vendor/libgit2 index 821131f..0f9d154 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit 821131fdaee74526d84aaf1c6ceddc2139c551df +Subproject commit 0f9d15493d5d8ad4353dd7beed52c9567334f6e5 From 090dc7ee3901d08d46b02d42b4f1fe248ecc3e6c Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Wed, 27 Jan 2016 18:11:12 +0100 Subject: [PATCH 25/27] Use Filemode in TreeBuilder.Insert, and add test coverage for some TreeBuilder methods. --- tree.go | 2 +- tree_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 8288176..eba9f3d 100644 --- a/tree.go +++ b/tree.go @@ -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) + } +} From f1240e6565dfb70cfd22f4025c1453ca92ce0743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 18 Feb 2016 17:07:33 +0100 Subject: [PATCH 26/27] handles: use real pointers to keep track of handles With the change to 1.6 rules, we couldn't use the Go pointers, so we went with casting the list indices into pointers. The runtime does not like this, however. It will sometimes detect that we have a pointer with a very small value and consider it an invalid pointer, bringing down the application with it. Work around that by asking libc for the smallest amount of memory it'll give us so we have an actual allocated pointer to use. We then use this pointer value as the key in our map to find the Go object we're tracking. --- handles.go | 56 +++++++++++++++--------------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/handles.go b/handles.go index f5d30f0..d27d3c3 100644 --- a/handles.go +++ b/handles.go @@ -1,5 +1,9 @@ package git +/* +#include +*/ +import "C" import ( "fmt" "sync" @@ -9,75 +13,45 @@ import ( type HandleList struct { sync.RWMutex // stores the Go pointers - handles []interface{} - // Indicates which indices are in use. - set map[int]bool + handles map[unsafe.Pointer]interface{} } func NewHandleList() *HandleList { return &HandleList{ - handles: make([]interface{}, 5), - set: make(map[int]bool), + handles: make(map[unsafe.Pointer]interface{}), } } -// findUnusedSlot finds the smallest-index empty space in our -// list. You must only run this function while holding a write lock. -func (v *HandleList) findUnusedSlot() int { - for i := 1; i < len(v.handles); i++ { - if !v.set[i] { - return i - } - } - - // reaching here means we've run out of entries so append and - // return the new index, which is equal to the old length. - slot := len(v.handles) - v.handles = append(v.handles, nil) - - return slot -} - // Track adds the given pointer to the list of pointers to track and // returns a pointer value which can be passed to C as an opaque // pointer. func (v *HandleList) Track(pointer interface{}) unsafe.Pointer { + handle := C.malloc(1) + v.Lock() - - slot := v.findUnusedSlot() - v.handles[slot] = pointer - v.set[slot] = true - + v.handles[handle] = pointer v.Unlock() - return unsafe.Pointer(uintptr(slot)) + return handle } // Untrack stops tracking the pointer given by the handle func (v *HandleList) Untrack(handle unsafe.Pointer) { - slot := int(uintptr(handle)) - v.Lock() - - v.handles[slot] = nil - delete(v.set, slot) - + delete(v.handles, handle) + C.free(handle) v.Unlock() } // Get retrieves the pointer from the given handle func (v *HandleList) Get(handle unsafe.Pointer) interface{} { - slot := int(uintptr(handle)) - v.RLock() + defer v.RUnlock() - if !v.set[slot] { + ptr, ok := v.handles[handle] + if !ok { panic(fmt.Sprintf("invalid pointer handle: %p", handle)) } - ptr := v.handles[slot] - - v.RUnlock() - return ptr } From 251d89e1d41037185df0ea89e9aab208efc40d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 19 Feb 2016 13:59:50 +0100 Subject: [PATCH 27/27] Update vendored libgit2 --- vendor/libgit2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/libgit2 b/vendor/libgit2 index 0f9d154..f596946 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit 0f9d15493d5d8ad4353dd7beed52c9567334f6e5 +Subproject commit f596946f09f3c1e51239a24ff41e27f2c1ffa2b7