From d54ea1d6a88406fc29db2596ce71f5c00aa4d205 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Mon, 21 Sep 2015 14:50:57 +0300 Subject: [PATCH 01/24] Add stash support --- repository.go | 4 + stash.go | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++ stash_test.go | 198 ++++++++++++++++++++++++++++++ wrapper.c | 8 ++ 4 files changed, 544 insertions(+) create mode 100644 stash.go create mode 100644 stash_test.go diff --git a/repository.go b/repository.go index d8e398b..4bf8531 100644 --- a/repository.go +++ b/repository.go @@ -30,6 +30,9 @@ type Repository struct { // Tags represents the collection of tags and can be used to create, // list and iterate tags in this repository. Tags TagsCollection + // Stashes represents the collection of stashes and can be used to + // save, apply and iterate over stash states in this repository. + Stashes StashCollection } func newRepositoryFromC(ptr *C.git_repository) *Repository { @@ -40,6 +43,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository { repo.References.repo = repo repo.Notes.repo = repo repo.Tags.repo = repo + repo.Stashes.repo = repo runtime.SetFinalizer(repo, (*Repository).Free) diff --git a/stash.go b/stash.go new file mode 100644 index 0000000..5142b82 --- /dev/null +++ b/stash.go @@ -0,0 +1,334 @@ +package git + +/* +#include + +extern void _go_git_setup_stash_apply_progress_callbacks(git_stash_apply_options *opts); +extern int _go_git_stash_foreach(git_repository *repo, void *payload); +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +// StashFlag are flags that affect the stash save operation. +type StashFlag int + +const ( + // StashDefault represents no option, default. + StashDefault StashFlag = C.GIT_STASH_DEFAULT + + // StashKeepIndex leaves all changes already added to the + // index intact in the working directory. + StashKeepIndex StashFlag = C.GIT_STASH_KEEP_INDEX + + // StashIncludeUntracked means all untracked files are also + // stashed and then cleaned up from the working directory. + StashIncludeUntracked StashFlag = C.GIT_STASH_INCLUDE_UNTRACKED + + // StashIncludeIgnored means all ignored files are also + // stashed and then cleaned up from the working directory. + StashIncludeIgnored StashFlag = C.GIT_STASH_INCLUDE_IGNORED +) + +// StashCollection represents the possible operations that can be +// performed on the collection of stashes for a repository. +type StashCollection struct { + repo *Repository +} + +// Save saves the local modifications to a new stash. +// +// Stasher is the identity of the person performing the stashing. +// Message is the optional description along with the stashed state. +// Flags control the stashing process and are given as bitwise OR. +func (c *StashCollection) Save( + stasher *Signature, message string, flags StashFlag) (*Oid, error) { + + oid := new(Oid) + + stasherC, err := stasher.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(stasherC) + + messageC := C.CString(message) + defer C.free(unsafe.Pointer(messageC)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_stash_save( + oid.toC(), c.repo.ptr, + stasherC, messageC, C.uint(flags)) + + if ret < 0 { + return nil, MakeGitError(ret) + } + return oid, nil +} + +// StashApplyFlag are flags that affect the stash apply operation. +type StashApplyFlag int + +const ( + // StashApplyDefault is the default. + StashApplyDefault StashApplyFlag = C.GIT_STASH_APPLY_DEFAULT + + // StashApplyReinstateIndex will try to reinstate not only the + // working tree's changes, but also the index's changes. + StashApplyReinstateIndex StashApplyFlag = C.GIT_STASH_APPLY_REINSTATE_INDEX +) + +// StashApplyProgress are flags describing the progress of the apply operation. +type StashApplyProgress int + +const ( + // StashApplyProgressNone means loading the stashed data from the object store. + StashApplyProgressNone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_NONE + + // StashApplyProgressLoadingStash means the stored index is being analyzed. + StashApplyProgressLoadingStash StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH + + // StashApplyProgressAnalyzeIndex means the stored index is being analyzed. + StashApplyProgressAnalyzeIndex StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX + + // StashApplyProgressAnalyzeModified means the modified files are being analyzed. + StashApplyProgressAnalyzeModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED + + // StashApplyProgressAnalyzeUntracked means the untracked and ignored files are being analyzed. + StashApplyProgressAnalyzeUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED + + // StashApplyProgressCheckoutUntracked means the untracked files are being written to disk. + StashApplyProgressCheckoutUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED + + // StashApplyProgressCheckoutModified means the modified files are being written to disk. + StashApplyProgressCheckoutModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED + + // StashApplyProgressDone means the stash was applied successfully. + StashApplyProgressDone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_DONE +) + +// StashApplyProgressCallback is the apply operation notification callback. +type StashApplyProgressCallback func(progress StashApplyProgress) error + +type stashApplyProgressData struct { + Callback StashApplyProgressCallback + Error error +} + +//export stashApplyProgressCb +func stashApplyProgressCb(progress C.git_stash_apply_progress_t, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*stashApplyProgressData) + if !ok { + panic("could not retrieve data for handle") + } + + if data != nil { + err := data.Callback(StashApplyProgress(progress)) + if err != nil { + data.Error = err + return C.GIT_EUSER + } + } + return 0 +} + +// StashApplyOptions represents options to control the apply operation. +type StashApplyOptions struct { + Flags StashApplyFlag + CheckoutOptions CheckoutOpts // options to use when writing files to the working directory + ProgressCallback StashApplyProgressCallback // optional callback to notify the consumer of application progress +} + +// DefaultStashApplyOptions initializes the structure with default values. +func DefaultStashApplyOptions() (StashApplyOptions, error) { + optsC := C.git_stash_apply_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_stash_apply_init_options(&optsC, C.GIT_STASH_APPLY_OPTIONS_VERSION) + if ecode < 0 { + return StashApplyOptions{}, MakeGitError(ecode) + } + defer freeStashApplyOptions(&optsC) + + return StashApplyOptions{ + Flags: StashApplyFlag(optsC.flags), + CheckoutOptions: checkoutOptionsFromC(&optsC.checkout_options), + }, nil +} + +func (opts *StashApplyOptions) toC() ( + optsC *C.git_stash_apply_options, progressData *stashApplyProgressData) { + + if opts != nil { + progressData = &stashApplyProgressData{ + Callback: opts.ProgressCallback, + } + + optsC = &C.git_stash_apply_options{ + version: C.GIT_STASH_APPLY_OPTIONS_VERSION, + flags: C.git_stash_apply_flags(opts.Flags), + checkout_options: *opts.CheckoutOptions.toC(), + } + if opts.ProgressCallback != nil { + C._go_git_setup_stash_apply_progress_callbacks(optsC) + optsC.progress_payload = pointerHandles.Track(progressData) + } + } + return +} + +func freeStashApplyOptions(optsC *C.git_stash_apply_options) { + if optsC != nil { + freeCheckoutOpts(&optsC.checkout_options) + if optsC.progress_payload != nil { + pointerHandles.Untrack(optsC.progress_payload) + } + } +} + +// Apply applies a single stashed state from the stash list. +// +// If local changes in the working directory conflict with changes in the +// stash then ErrConflict will be returned. In this case, the index +// will always remain unmodified and all files in the working directory will +// remain unmodified. However, if you are restoring untracked files or +// ignored files and there is a conflict when applying the modified files, +// then those files will remain in the working directory. +// +// If passing the StashApplyReinstateIndex flag and there would be conflicts +// when reinstating the index, the function will return ErrConflict +// and both the working directory and index will be left unmodified. +// +// Note that a minimum checkout strategy of 'CheckoutSafe' is implied. +// +// 'index' is the position within the stash list. 0 points to the most +// recent stashed state. +// +// Returns error code ErrNotFound if there's no stashed state for the given +// index, error code ErrConflict if local changes in the working directory +// conflict with changes in the stash, the user returned error from the +// StashApplyProgressCallback, if any, or other error code. +// +// Error codes can be interogated with IsErrorCode(err, ErrNotFound). +func (c *StashCollection) Apply(index int, opts StashApplyOptions) error { + optsC, progressData := opts.toC() + defer freeStashApplyOptions(optsC) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_stash_apply(c.repo.ptr, C.size_t(index), optsC) + if ret == C.GIT_EUSER { + return progressData.Error + } + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +// StashCallback is called per entry when interating over all +// the stashed states. +// +// 'index' is the position of the current stash in the stash list, +// 'message' is the message used when creating the stash and 'id' +// is the commit id of the stash. +type StashCallback func(index int, message string, id *Oid) error + +type stashCallbackData struct { + Callback StashCallback + Error error +} + +//export stashForeachCb +func stashForeachCb(index C.size_t, message *C.char, id *C.git_oid, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*stashCallbackData) + if !ok { + panic("could not retrieve data for handle") + } + + err := data.Callback(int(index), C.GoString(message), newOidFromC(id)) + if err != nil { + data.Error = err + return C.GIT_EUSER + } + return 0 +} + +// Foreach loops over all the stashed states and calls the callback +// for each one. +// +// If callback returns an error, this will stop looping. +func (c *StashCollection) Foreach(callback StashCallback) error { + data := stashCallbackData{ + Callback: callback, + } + + handle := pointerHandles.Track(&data) + defer pointerHandles.Untrack(handle) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C._go_git_stash_foreach(c.repo.ptr, handle) + if ret == C.GIT_EUSER { + return data.Error + } + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +// Drop removes a single stashed state from the stash list. +// +// 'index' is the position within the stash list. 0 points +// to the most recent stashed state. +// +// Returns error code ErrNotFound if there's no stashed +// state for the given index. +func (c *StashCollection) Drop(index int) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_stash_drop(c.repo.ptr, C.size_t(index)) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +// Pop applies a single stashed state from the stash list +// and removes it from the list if successful. +// +// 'index' is the position within the stash list. 0 points +// to the most recent stashed state. +// +// 'opts' controls how stashes are applied. +// +// Returns error code ErrNotFound if there's no stashed +// state for the given index. +func (c *StashCollection) Pop(index int, opts StashApplyOptions) error { + optsC, progressData := opts.toC() + defer freeStashApplyOptions(optsC) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_stash_pop(c.repo.ptr, C.size_t(index), optsC) + if ret == C.GIT_EUSER { + return progressData.Error + } + if ret < 0 { + return MakeGitError(ret) + } + return nil +} diff --git a/stash_test.go b/stash_test.go new file mode 100644 index 0000000..180a16b --- /dev/null +++ b/stash_test.go @@ -0,0 +1,198 @@ +package git + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "reflect" + "runtime" + "testing" + "time" +) + +func TestStash(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + prepareStashRepo(t, repo) + + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "random@hacker.com", + When: time.Now(), + } + + stash1, err := repo.Stashes.Save(sig, "First stash", StashDefault) + checkFatal(t, err) + + _, err = repo.LookupCommit(stash1) + checkFatal(t, err) + + b, err := ioutil.ReadFile(pathInRepo(repo, "README")) + checkFatal(t, err) + if string(b) == "Update README goes to stash\n" { + t.Errorf("README still contains the uncommitted changes") + } + + if !fileExistsInRepo(repo, "untracked.txt") { + t.Errorf("untracked.txt doesn't exist in the repo; should be untracked") + } + + // Apply: default + + opts, err := DefaultStashApplyOptions() + checkFatal(t, err) + + err = repo.Stashes.Apply(0, opts) + checkFatal(t, err) + + b, err = ioutil.ReadFile(pathInRepo(repo, "README")) + checkFatal(t, err) + if string(b) != "Update README goes to stash\n" { + t.Errorf("README changes aren't here") + } + + // Apply: no stash for the given index + + err = repo.Stashes.Apply(1, opts) + if !IsErrorCode(err, ErrNotFound) { + t.Errorf("expecting GIT_ENOTFOUND error code %d, got %v", ErrNotFound, err) + } + + // Apply: callback stopped + + opts.ProgressCallback = func(progress StashApplyProgress) error { + if progress == StashApplyProgressCheckoutModified { + return fmt.Errorf("Stop") + } + return nil + } + + err = repo.Stashes.Apply(0, opts) + if err.Error() != "Stop" { + t.Errorf("expecting error 'Stop', got %v", err) + } + + // Create second stash with ignored files + + os.MkdirAll(pathInRepo(repo, "tmp"), os.ModeDir|os.ModePerm) + err = ioutil.WriteFile(pathInRepo(repo, "tmp/ignored.txt"), []byte("Ignore me\n"), 0644) + checkFatal(t, err) + + stash2, err := repo.Stashes.Save(sig, "Second stash", StashIncludeIgnored) + checkFatal(t, err) + + if fileExistsInRepo(repo, "tmp/ignored.txt") { + t.Errorf("tmp/ignored.txt should not exist anymore in the work dir") + } + + // Stash foreach + + expected := []stash{ + {0, "On master: Second stash", stash2.String()}, + {1, "On master: First stash", stash1.String()}, + } + checkStashes(t, repo, expected) + + // Stash pop + + opts, _ = DefaultStashApplyOptions() + err = repo.Stashes.Pop(1, opts) + checkFatal(t, err) + + b, err = ioutil.ReadFile(pathInRepo(repo, "README")) + checkFatal(t, err) + if string(b) != "Update README goes to stash\n" { + t.Errorf("README changes aren't here") + } + + expected = []stash{ + {0, "On master: Second stash", stash2.String()}, + } + checkStashes(t, repo, expected) + + // Stash drop + + err = repo.Stashes.Drop(0) + checkFatal(t, err) + + expected = []stash{} + checkStashes(t, repo, expected) +} + +type stash struct { + index int + msg string + id string +} + +func checkStashes(t *testing.T, repo *Repository, expected []stash) { + var actual []stash + + repo.Stashes.Foreach(func(index int, msg string, id *Oid) error { + stash := stash{index, msg, id.String()} + if len(expected) > len(actual) { + if s := expected[len(actual)]; s.id == "" { + stash.id = "" // don't check id + } + } + actual = append(actual, stash) + return nil + }) + + if len(expected) > 0 && !reflect.DeepEqual(expected, actual) { + // The failure happens at wherever we were called, not here + _, file, line, ok := runtime.Caller(1) + if !ok { + t.Fatalf("Unable to get caller") + } + t.Errorf("%v:%v: expecting %#v\ngot %#v", path.Base(file), line, expected, actual) + } +} + +func prepareStashRepo(t *testing.T, repo *Repository) { + seedTestRepo(t, repo) + + err := ioutil.WriteFile(pathInRepo(repo, ".gitignore"), []byte("tmp\n"), 0644) + checkFatal(t, err) + + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "random@hacker.com", + When: time.Now(), + } + + idx, err := repo.Index() + checkFatal(t, err) + err = idx.AddByPath(".gitignore") + checkFatal(t, err) + treeID, err := idx.WriteTree() + checkFatal(t, err) + err = idx.Write() + checkFatal(t, err) + + currentBranch, err := repo.Head() + checkFatal(t, err) + currentTip, err := repo.LookupCommit(currentBranch.Target()) + checkFatal(t, err) + + message := "Add .gitignore\n" + tree, err := repo.LookupTree(treeID) + checkFatal(t, err) + _, err = repo.CreateCommit("HEAD", sig, sig, message, tree, currentTip) + checkFatal(t, err) + + err = ioutil.WriteFile(pathInRepo(repo, "README"), []byte("Update README goes to stash\n"), 0644) + checkFatal(t, err) + + err = ioutil.WriteFile(pathInRepo(repo, "untracked.txt"), []byte("Hello, World\n"), 0644) + checkFatal(t, err) +} + +func fileExistsInRepo(repo *Repository, name string) bool { + if _, err := os.Stat(pathInRepo(repo, name)); err != nil { + return false + } + return true +} diff --git a/wrapper.c b/wrapper.c index 2b1a180..a01867c 100644 --- a/wrapper.c +++ b/wrapper.c @@ -141,4 +141,12 @@ int _go_git_tag_foreach(git_repository *repo, void *payload) return git_tag_foreach(repo, (git_tag_foreach_cb)&gitTagForeachCb, payload); } +void _go_git_setup_stash_apply_progress_callbacks(git_stash_apply_options *opts) { + opts->progress_cb = (git_stash_apply_progress_cb)stashApplyProgressCb; +} + +int _go_git_stash_foreach(git_repository *repo, void *payload) { + return git_stash_foreach(repo, (git_stash_cb)&stashForeachCb, payload); +} + /* EOF */ From 5191254a66ac049611fb16e4bb5f273aeb2ac322 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Sat, 20 Feb 2016 14:33:47 +0200 Subject: [PATCH 02/24] Fix problems based on PR comments https://github.com/libgit2/git2go/pull/257#discussion_r53432957 https://github.com/libgit2/git2go/pull/257#discussion_r53443418 --- stash.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/stash.go b/stash.go index 5142b82..7a4fc93 100644 --- a/stash.go +++ b/stash.go @@ -155,8 +155,6 @@ func DefaultStashApplyOptions() (StashApplyOptions, error) { if ecode < 0 { return StashApplyOptions{}, MakeGitError(ecode) } - defer freeStashApplyOptions(&optsC) - return StashApplyOptions{ Flags: StashApplyFlag(optsC.flags), CheckoutOptions: checkoutOptionsFromC(&optsC.checkout_options), @@ -172,10 +170,10 @@ func (opts *StashApplyOptions) toC() ( } optsC = &C.git_stash_apply_options{ - version: C.GIT_STASH_APPLY_OPTIONS_VERSION, - flags: C.git_stash_apply_flags(opts.Flags), - checkout_options: *opts.CheckoutOptions.toC(), + version: C.GIT_STASH_APPLY_OPTIONS_VERSION, + flags: C.git_stash_apply_flags(opts.Flags), } + populateCheckoutOpts(&optsC.checkout_options, &opts.CheckoutOptions) if opts.ProgressCallback != nil { C._go_git_setup_stash_apply_progress_callbacks(optsC) optsC.progress_payload = pointerHandles.Track(progressData) From dc4409793db0205ce7c0783a10363d7d67aee674 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Sat, 20 Feb 2016 14:34:29 +0200 Subject: [PATCH 03/24] Remove Untrack() from free() function https://github.com/libgit2/git2go/pull/257#discussion_r53443211 --- stash.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/stash.go b/stash.go index 7a4fc93..8d8f984 100644 --- a/stash.go +++ b/stash.go @@ -182,12 +182,16 @@ func (opts *StashApplyOptions) toC() ( return } +// should be called after every call to toC() as deferred. +func untrackStashApplyOptionsCallback(optsC *C.git_stash_apply_options) { + if optsC != nil && optsC.progress_payload != nil { + pointerHandles.Untrack(optsC.progress_payload) + } +} + func freeStashApplyOptions(optsC *C.git_stash_apply_options) { if optsC != nil { freeCheckoutOpts(&optsC.checkout_options) - if optsC.progress_payload != nil { - pointerHandles.Untrack(optsC.progress_payload) - } } } @@ -217,6 +221,7 @@ func freeStashApplyOptions(optsC *C.git_stash_apply_options) { // Error codes can be interogated with IsErrorCode(err, ErrNotFound). func (c *StashCollection) Apply(index int, opts StashApplyOptions) error { optsC, progressData := opts.toC() + defer untrackStashApplyOptionsCallback(optsC) defer freeStashApplyOptions(optsC) runtime.LockOSThread() @@ -316,6 +321,7 @@ func (c *StashCollection) Drop(index int) error { // state for the given index. func (c *StashCollection) Pop(index int, opts StashApplyOptions) error { optsC, progressData := opts.toC() + defer untrackStashApplyOptionsCallback(optsC) defer freeStashApplyOptions(optsC) runtime.LockOSThread() From 71ff6ab0d5afb13c555da799ee69b055acd3954c Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Sat, 20 Feb 2016 14:58:48 +0200 Subject: [PATCH 04/24] Fix error after updating to latest changes --- stash.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stash.go b/stash.go index 8d8f984..809732e 100644 --- a/stash.go +++ b/stash.go @@ -62,7 +62,7 @@ func (c *StashCollection) Save( ret := C.git_stash_save( oid.toC(), c.repo.ptr, - stasherC, messageC, C.uint(flags)) + stasherC, messageC, C.uint32_t(flags)) if ret < 0 { return nil, MakeGitError(ret) From 1e8b7ef38075268665a7157b072a8760374787aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=B8dtang?= Date: Tue, 29 Mar 2016 20:37:22 +0200 Subject: [PATCH 05/24] Add "Conflicted" git.Delta and git.Status. --- diff.go | 1 + status.go | 1 + 2 files changed, 2 insertions(+) diff --git a/diff.go b/diff.go index 565fcee..38505d2 100644 --- a/diff.go +++ b/diff.go @@ -34,6 +34,7 @@ const ( DeltaIgnored Delta = C.GIT_DELTA_IGNORED DeltaUntracked Delta = C.GIT_DELTA_UNTRACKED DeltaTypeChange Delta = C.GIT_DELTA_TYPECHANGE + DeltaConflicted Delta = C.GIT_DELTA_CONFLICTED ) type DiffLineType int diff --git a/status.go b/status.go index 068a474..e68e6e9 100644 --- a/status.go +++ b/status.go @@ -25,6 +25,7 @@ const ( StatusWtTypeChange Status = C.GIT_STATUS_WT_TYPECHANGE StatusWtRenamed Status = C.GIT_STATUS_WT_RENAMED StatusIgnored Status = C.GIT_STATUS_IGNORED + StatusConflicted Status = C.GIT_STATUS_CONFLICTED ) type StatusEntry struct { From 094bb3767b9168b81e5977ee0358c389e91f3920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=B8dtang?= Date: Tue, 29 Mar 2016 20:41:46 +0200 Subject: [PATCH 06/24] Add unimplemented diff delta/flag values. --- diff.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/diff.go b/diff.go index 38505d2..5b96fac 100644 --- a/diff.go +++ b/diff.go @@ -20,6 +20,7 @@ const ( DiffFlagBinary DiffFlag = C.GIT_DIFF_FLAG_BINARY DiffFlagNotBinary DiffFlag = C.GIT_DIFF_FLAG_NOT_BINARY DiffFlagValidOid DiffFlag = C.GIT_DIFF_FLAG_VALID_ID + DiffFlagExists DiffFlag = C.GIT_DIFF_FLAG_EXISTS ) type Delta int @@ -34,6 +35,7 @@ const ( DeltaIgnored Delta = C.GIT_DELTA_IGNORED DeltaUntracked Delta = C.GIT_DELTA_UNTRACKED DeltaTypeChange Delta = C.GIT_DELTA_TYPECHANGE + DeltaUnreadable Delta = C.GIT_DELTA_UNREADABLE DeltaConflicted Delta = C.GIT_DELTA_CONFLICTED ) @@ -373,6 +375,7 @@ const ( DiffIgnoreFilemode DiffOptionsFlag = C.GIT_DIFF_IGNORE_FILEMODE DiffIgnoreSubmodules DiffOptionsFlag = C.GIT_DIFF_IGNORE_SUBMODULES DiffIgnoreCase DiffOptionsFlag = C.GIT_DIFF_IGNORE_CASE + DiffIncludeCaseChange DiffOptionsFlag = C.GIT_DIFF_INCLUDE_CASECHANGE DiffDisablePathspecMatch DiffOptionsFlag = C.GIT_DIFF_DISABLE_PATHSPEC_MATCH DiffSkipBinaryCheck DiffOptionsFlag = C.GIT_DIFF_SKIP_BINARY_CHECK From b99dbb1361938e74312ec8a432730a8bb6ee801f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=B8dtang?= Date: Tue, 29 Mar 2016 20:42:19 +0200 Subject: [PATCH 07/24] Add Repository.IsHeadUnborn --- repository.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/repository.go b/repository.go index 2e6b81d..caf7bbe 100644 --- a/repository.go +++ b/repository.go @@ -268,6 +268,17 @@ func (v *Repository) IsHeadDetached() (bool, error) { return ret != 0, nil } +func (v *Repository) IsHeadUnborn() (bool, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_repository_head_unborn(v.ptr) + if ret < 0 { + return false, MakeGitError(ret) + } + return ret != 0, nil +} + func (v *Repository) Walk() (*RevWalk, error) { var walkPtr *C.git_revwalk From 726331dfde5cddabb8ded9967b581efcea77b765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=B8dtang?= Date: Tue, 29 Mar 2016 20:42:30 +0200 Subject: [PATCH 08/24] Add Repository.IsEmpty --- repository.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/repository.go b/repository.go index caf7bbe..6ed6066 100644 --- a/repository.go +++ b/repository.go @@ -279,6 +279,18 @@ func (v *Repository) IsHeadUnborn() (bool, error) { return ret != 0, nil } +func (v *Repository) IsEmpty() (bool, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_repository_is_empty(v.ptr) + if ret < 0 { + return false, MakeGitError(ret) + } + + return ret != 0, nil +} + func (v *Repository) Walk() (*RevWalk, error) { var walkPtr *C.git_revwalk From 2be7d7987b40fad0ee064e2feb5a132fd9ed3c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=B8dtang?= Date: Tue, 29 Mar 2016 20:42:41 +0200 Subject: [PATCH 09/24] Add Repository.IsShallow --- repository.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/repository.go b/repository.go index 6ed6066..f0a2c74 100644 --- a/repository.go +++ b/repository.go @@ -291,6 +291,17 @@ func (v *Repository) IsEmpty() (bool, error) { return ret != 0, nil } +func (v *Repository) IsShallow() (bool, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_repository_is_shallow(v.ptr) + if ret < 0 { + return false, MakeGitError(ret) + } + return ret != 0, nil +} + func (v *Repository) Walk() (*RevWalk, error) { var walkPtr *C.git_revwalk From 9163ca7d5072114975bed794226dd90d37d856b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 21 Apr 2016 16:34:51 +0200 Subject: [PATCH 10/24] Update to 1dc4491 --- blob.go | 76 +++++++++++++++++++++++++++++++++++++++----------- remote.go | 55 ++++++++++++++++++++++++++++++++---- remote_test.go | 8 +++--- vendor/libgit2 | 2 +- wrapper.c | 28 ++++++++++--------- 5 files changed, 128 insertions(+), 41 deletions(-) diff --git a/blob.go b/blob.go index 1a86e60..2daebe9 100644 --- a/blob.go +++ b/blob.go @@ -4,15 +4,13 @@ package git #include #include -extern int _go_git_blob_create_fromchunks(git_oid *id, - git_repository *repo, - const char *hintpath, - void *payload); - +int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len); +void _go_git_writestream_free(git_writestream *stream); */ import "C" import ( "io" + "reflect" "runtime" "unsafe" ) @@ -78,27 +76,71 @@ func blobChunkCb(buffer *C.char, maxLen C.size_t, handle unsafe.Pointer) int { return len(goBuf) } -func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunkCallback) (*Oid, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - +func (repo *Repository) CreateFromStream(hintPath string) (*BlobWriteStream, error) { var chintPath *C.char = nil + var stream *C.git_writestream + if len(hintPath) > 0 { chintPath = C.CString(hintPath) defer C.free(unsafe.Pointer(chintPath)) } - oid := C.git_oid{} - payload := &BlobCallbackData{Callback: callback} - handle := pointerHandles.Track(payload) - defer pointerHandles.Untrack(handle) + runtime.LockOSThread() + defer runtime.UnlockOSThread() - ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle) - if payload.Error != nil { - return nil, payload.Error - } + ecode := C.git_blob_create_fromstream(&stream, repo.ptr, chintPath) if ecode < 0 { return nil, MakeGitError(ecode) } + + return newBlobWriteStreamFromC(stream), nil +} + +type BlobWriteStream struct { + ptr *C.git_writestream +} + +func newBlobWriteStreamFromC(ptr *C.git_writestream) *BlobWriteStream { + stream := &BlobWriteStream{ + ptr: ptr, + } + + runtime.SetFinalizer(stream, (*BlobWriteStream).Free) + return stream +} + +// Implement io.Writer +func (stream *BlobWriteStream) Write(p []byte) (int, error) { + header := (*reflect.SliceHeader)(unsafe.Pointer(&p)) + ptr := (*C.char)(unsafe.Pointer(header.Data)) + size := C.size_t(header.Len) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C._go_git_writestream_write(stream.ptr, ptr, size) + if ecode < 0 { + return 0, MakeGitError(ecode) + } + + return len(p), nil +} + +func (stream *BlobWriteStream) Free() { + runtime.SetFinalizer(stream, nil) + C._go_git_writestream_free(stream.ptr) +} + +func (stream *BlobWriteStream) Commit() (*Oid, error) { + oid := C.git_oid{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_blob_create_fromstream_commit(&oid, stream.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newOidFromC(&oid), nil } diff --git a/remote.go b/remote.go index 8a57280..bacdfa3 100644 --- a/remote.go +++ b/remote.go @@ -117,6 +117,30 @@ type FetchOptions struct { Headers []string } +type ProxyType uint + +const ( + // Do not attempt to connect through a proxy + // + // If built against lbicurl, it itself may attempt to connect + // to a proxy if the environment variables specify it. + ProxyTypeNone ProxyType = C.GIT_PROXY_NONE + + // Try to auto-detect the proxy from the git configuration. + ProxyTypeAuto ProxyType = C.GIT_PROXY_AUTO + + // Connect via the URL given in the options + ProxyTypeSpecified ProxyType = C.GIT_PROXY_SPECIFIED +) + +type ProxyOptions struct { + // The type of proxy to use (or none) + Type ProxyType + + // The proxy's URL + Url string +} + type Remote struct { ptr *C.git_remote callbacks RemoteCallbacks @@ -320,6 +344,20 @@ func pushUpdateReferenceCallback(refname, status *C.char, data unsafe.Pointer) i return int(callbacks.PushUpdateReferenceCallback(C.GoString(refname), C.GoString(status))) } +func populateProxyOptions(ptr *C.git_proxy_options, opts *ProxyOptions) { + C.git_proxy_init_options(ptr, C.GIT_PROXY_OPTIONS_VERSION) + if opts == nil { + return + } + + ptr._type = C.git_proxy_t(opts.Type) + ptr.url = C.CString(opts.Url) +} + +func freeProxyOptions(ptr *C.git_proxy_options) { + C.free(unsafe.Pointer(ptr.url)) +} + func RemoteIsValidName(name string) bool { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -654,12 +692,12 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error return nil } -func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, headers []string) error { - return o.Connect(ConnectDirectionFetch, callbacks, headers) +func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error { + return o.Connect(ConnectDirectionFetch, callbacks, proxyOpts, headers) } -func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error { - return o.Connect(ConnectDirectionPush, callbacks, headers) +func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error { + return o.Connect(ConnectDirectionPush, callbacks, proxyOpts, headers) } // Connect opens a connection to a remote. @@ -669,19 +707,24 @@ func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error // 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 { +func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error { var ccallbacks C.git_remote_callbacks populateRemoteCallbacks(&ccallbacks, callbacks) + var cproxy C.git_proxy_options + populateProxyOptions(&cproxy, proxyOpts) + defer freeProxyOptions(&cproxy) + 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, &cheaders); ret != 0 { + if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cproxy, &cheaders); ret != 0 { return MakeGitError(ret) } return nil diff --git a/remote_test.go b/remote_test.go index 978b803..539b4b1 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, nil) + err = remote.ConnectFetch(nil, 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, nil) + err = remote.ConnectFetch(nil, 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, nil) + err = remote.ConnectFetch(nil, 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, nil) + err = rr.ConnectFetch(nil, nil, nil) checkFatal(t, err) err = rr.Prune(nil) diff --git a/vendor/libgit2 b/vendor/libgit2 index 785d8c4..1dc4491 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit 785d8c48ea8725691da3c50e7dae8751523d4c30 +Subproject commit 1dc449105b329ea4f8ea9982bc2da869d231c04a diff --git a/wrapper.c b/wrapper.c index e39665a..7407611 100644 --- a/wrapper.c +++ b/wrapper.c @@ -108,19 +108,6 @@ void _go_git_setup_callbacks(git_remote_callbacks *callbacks) { callbacks->push_update_reference = (push_update_reference_cb) pushUpdateReferenceCallback; } -int _go_blob_chunk_cb(char *buffer, size_t maxLen, void *payload) -{ - return blobChunkCb(buffer, maxLen, payload); -} - -int _go_git_blob_create_fromchunks(git_oid *id, - git_repository *repo, - const char *hintpath, - void *payload) -{ - return git_blob_create_fromchunks(id, repo, hintpath, _go_blob_chunk_cb, payload); -} - int _go_git_index_add_all(git_index *index, const git_strarray *pathspec, unsigned int flags, void *callback) { git_index_matched_path_cb cb = callback ? (git_index_matched_path_cb) &indexMatchedPathCallback : NULL; return git_index_add_all(index, pathspec, flags, cb, callback); @@ -172,4 +159,19 @@ int _go_git_stash_foreach(git_repository *repo, void *payload) { return git_stash_foreach(repo, (git_stash_cb)&stashForeachCb, payload); } +int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len) +{ + return stream->write(stream, buffer, len); +} + +int _go_git_writestream_close(git_writestream *stream) +{ + return stream->close(stream); +} + +void _go_git_writestream_free(git_writestream *stream) +{ + stream->free(stream); +} + /* EOF */ From cf7553e72cea560d3398b7f0a5e50f812e91443a Mon Sep 17 00:00:00 2001 From: Kirill Smelkov Date: Tue, 19 Jul 2016 22:48:52 +0300 Subject: [PATCH 11/24] odb: Expose git_odb_object_type() as OdbObject.Type() It might be needed when one is writing `git cat-file --batch` equivalent which has output format SP SP LF LF --- odb.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/odb.go b/odb.go index 9c6baa3..28bfad6 100644 --- a/odb.go +++ b/odb.go @@ -226,6 +226,10 @@ func (object *OdbObject) Len() (len uint64) { return uint64(C.git_odb_object_size(object.ptr)) } +func (object *OdbObject) Type() ObjectType { + return ObjectType(C.git_odb_object_type(object.ptr)) +} + func (object *OdbObject) Data() (data []byte) { var c_blob unsafe.Pointer = C.git_odb_object_data(object.ptr) var blob []byte From 37d3c2d9ad4c4e970cac02faec8ad184412c34e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 5 Aug 2016 15:15:22 +0200 Subject: [PATCH 12/24] Update libgit2 to 73dab769 This version reloads the index on checkout, which showed we were not persisting the updated index to disk and thus would have conflicts on checkout. --- git_test.go | 4 ++++ vendor/libgit2 | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/git_test.go b/git_test.go index 58caf71..6a58844 100644 --- a/git_test.go +++ b/git_test.go @@ -58,6 +58,8 @@ func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) { checkFatal(t, err) err = idx.AddByPath("README") checkFatal(t, err) + err = idx.Write() + checkFatal(t, err) treeId, err := idx.WriteTree() checkFatal(t, err) @@ -91,6 +93,8 @@ func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) { checkFatal(t, err) err = idx.AddByPath("README") checkFatal(t, err) + err = idx.Write() + checkFatal(t, err) treeId, err := idx.WriteTree() checkFatal(t, err) diff --git a/vendor/libgit2 b/vendor/libgit2 index 1dc4491..73dab76 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit 1dc449105b329ea4f8ea9982bc2da869d231c04a +Subproject commit 73dab7692e780e1df96093a54854795428eb66b4 From 92fa6357aee0c477e7090f6baf6251e969541123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 27 Aug 2016 21:11:22 +0200 Subject: [PATCH 13/24] Bring back the Makefile from 'next' --- Makefile | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9c42283..3040857 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,11 @@ default: test -test: - go run script/check-MakeGitError-thread-lock.go - go test ./... +build-libgit2: + ./script/build-libgit2-static.sh -install: - go install ./... +test: build-libgit2 + go run script/check-MakeGitError-thread-lock.go + ./script/with-static.sh go test ./... + +install: build-libgit2 + ./script/with-static.sh go install ./... From 8eb8fa3725d8d857c58fcb0b7d64a85d8b7bebdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 27 Aug 2016 21:35:26 +0200 Subject: [PATCH 14/24] Also remove a pkg-config directive that snuck in --- git.go | 1 - 1 file changed, 1 deletion(-) diff --git a/git.go b/git.go index ed891e6..a3c40e3 100644 --- a/git.go +++ b/git.go @@ -3,7 +3,6 @@ package git /* #include #include -#cgo pkg-config: libgit2 #if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 24 # error "Invalid libgit2 version; this git2go supports libgit2 v0.24" From d2b8c99ba7169a312f317e1222af4a7b561e377b Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Fri, 9 Sep 2016 15:27:07 +0200 Subject: [PATCH 15/24] Add method to fetch raw commit message The existing `Commit.Message()` returns the trimmed commit message. In some cases it is important to retrieve the exact commit message, even if it contains surrounding newlines. This adds a new `Commit.RawMessage()` to be able to do that. --- commit.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/commit.go b/commit.go index 6830da3..07b7c37 100644 --- a/commit.go +++ b/commit.go @@ -22,6 +22,10 @@ func (c Commit) Message() string { return C.GoString(C.git_commit_message(c.cast_ptr)) } +func (c Commit) RawMessage() string { + return C.GoString(C.git_commit_message_raw(c.cast_ptr)) +} + func (c Commit) Summary() string { return C.GoString(C.git_commit_summary(c.cast_ptr)) } From e8062bcaddc2533f28b9af6323f507a96bc1b092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 31 Oct 2016 19:31:43 +0100 Subject: [PATCH 16/24] Update to libgit2 a051ee3 --- vendor/libgit2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/libgit2 b/vendor/libgit2 index 73dab76..a051ee3 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit 73dab7692e780e1df96093a54854795428eb66b4 +Subproject commit a051ee31f2a3790410c2b482611f691135d2eae0 From adb1770ff3f9d7b242ba8b8eac31186db92a46d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 31 Oct 2016 21:09:24 +0100 Subject: [PATCH 17/24] Add Features() to retrieve the compile-time features of libgit2 --- features.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 features.go diff --git a/features.go b/features.go new file mode 100644 index 0000000..f6474a0 --- /dev/null +++ b/features.go @@ -0,0 +1,30 @@ +package git + +/* +#include +*/ +import "C" + +type Feature int + +const ( + // libgit2 was built with threading support + FeatureThreads Feature = C.GIT_FEATURE_THREADS + + // libgit2 was built with HTTPS support built-in + FeatureHttps Feature = C.GIT_FEATURE_HTTPS + + // libgit2 was build with SSH support built-in + FeatureSsh Feature = C.GIT_FEATURE_SSH + + // libgit2 was built with nanosecond support for files + FeatureNSec Feature = C.GIT_FEATURE_NSEC +) + +// Features returns a bit-flag of Feature values indicating which features the +// loaded libgit2 library has. +func Features() Feature { + features := C.git_libgit2_features() + + return Feature(features) +} From a37f7f30ff94e32b20866cf2fa28496c60826278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 31 Oct 2016 21:12:03 +0100 Subject: [PATCH 18/24] Panic if libgit2 is not thread-aware Go calling C is inherently multi-threaded. If libgit2 cannot handle threading, then we're going to crash at some random point. Crash right at the start so we know what's happening. --- git.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/git.go b/git.go index a3c40e3..53b1a12 100644 --- a/git.go +++ b/git.go @@ -124,6 +124,15 @@ func init() { C.git_libgit2_init() + // Due to the multithreaded nature of Go and its interaction with + // calling C functions, we cannot work with a library that was not built + // with multi-threading support. The most likely outcome is a segfault + // or panic at an incomprehensible time, so let's make it easy by + // panicking right here. + if Features()&FeatureThreads == 0 { + panic("libgit2 was not built with threading support") + } + // This is not something we should be doing, as we may be // stomping all over someone else's setup. The user should do // this themselves or use some binding/wrapper which does it From 9c5fb973fcc1acd7cd55e120fefef101be46e60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 19 Dec 2015 01:30:28 +0100 Subject: [PATCH 19/24] Get rid of the with-static.sh script CGO can perform variable substitution in the directives, so we don't need to use a script to set up the variables; we can let the go tool do it for us. --- Makefile | 4 ++-- README.md | 11 +++++------ git.go | 4 ++++ script/with-static.sh | 12 ------------ 4 files changed, 11 insertions(+), 20 deletions(-) delete mode 100755 script/with-static.sh diff --git a/Makefile b/Makefile index 3040857..39fc558 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ build-libgit2: test: build-libgit2 go run script/check-MakeGitError-thread-lock.go - ./script/with-static.sh go test ./... + go test ./... install: build-libgit2 - ./script/with-static.sh go install ./... + go install ./... diff --git a/README.md b/README.md index 315032f..bd918d6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ to use a version of git2go which will work against libgit2 v0.22 and dynamically import "github.com/libgit2/git2go" -to use the version which works against the latest release. +to use the 'master' branch, which works against the latest release of libgit2, whichever that one is at the time. ### From `next` @@ -44,15 +44,14 @@ libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. F Running the tests ----------------- -For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper +For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires building a local libgit2 library, so the Makefile provides a wrapper that makes sure it's built make test -Alternatively, if you want to pass arguments to `go test`, you can use the script that sets it all up +Alternatively, you can build the library manually first and then run the tests - ./script/with-static.sh go test -v - -which will run the specified arguments with the correct environment variables. + ./script/build-libgit2-static.sh + go test -v License ------- diff --git a/git.go b/git.go index 53b1a12..77394cf 100644 --- a/git.go +++ b/git.go @@ -1,6 +1,10 @@ package git /* +#cgo CFLAGS: -I${SRCDIR}/vendor/libgit2/include +#cgo LDFLAGS: -L${SRCDIR}/vendor/libgit2/build/ -lgit2 +#cgo windows LDFLAGS: -lwinhttp +#cgo !windows pkg-config: --static ${SRCDIR}/vendor/libgit2/build/libgit2.pc #include #include diff --git a/script/with-static.sh b/script/with-static.sh deleted file mode 100755 index 3f60e31..0000000 --- a/script/with-static.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -set -ex - -export BUILD="$PWD/vendor/libgit2/build" -export PCFILE="$BUILD/libgit2.pc" - -FLAGS=$(pkg-config --static --libs $PCFILE) || exit 1 -export CGO_LDFLAGS="$BUILD/libgit2.a -L$BUILD ${FLAGS}" -export CGO_CFLAGS="-I$PWD/vendor/libgit2/include" - -$@ From 7f426f2435087a35155b4e8b859fb52f09f79a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 27 Aug 2016 21:58:05 +0200 Subject: [PATCH 20/24] Only test against 1.5 and up Go 1.5 is the first one which supports the variable replacement we're using for the flags. Any older versions aren't supported by the Go team now that 1.7 is out, so you should be on one of these versions. --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 79ad168..fb68ca9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,6 @@ sudo: required install: ./script/install-libgit2.sh go: - - 1.1 - - 1.2 - - 1.3 - - 1.4 - 1.5 - 1.6 - 1.7 From a41e2b66449f05093de71f8debc71092f0a8117e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 19 Nov 2016 15:09:41 +0100 Subject: [PATCH 21/24] Update to 0.25-rc1 --- git.go | 4 ++-- vendor/libgit2 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git.go b/git.go index 77394cf..cb973ee 100644 --- a/git.go +++ b/git.go @@ -8,8 +8,8 @@ package git #include #include -#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 24 -# error "Invalid libgit2 version; this git2go supports libgit2 v0.24" +#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 25 +# error "Invalid libgit2 version; this git2go supports libgit2 v0.25" #endif */ diff --git a/vendor/libgit2 b/vendor/libgit2 index a051ee3..ae5838f 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit a051ee31f2a3790410c2b482611f691135d2eae0 +Subproject commit ae5838f118a4819e608990a815bf8fc482be5772 From 5d0a4c752a74258a5f42e40fccd2908ac4e336b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 9 Jan 2017 22:13:18 +0000 Subject: [PATCH 22/24] Bump vendored libgit2 to ee89941fa --- vendor/libgit2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/libgit2 b/vendor/libgit2 index ae5838f..ee89941 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit ae5838f118a4819e608990a815bf8fc482be5772 +Subproject commit ee89941fa2b861332defb5e3a8ce12c23c496ed7 From 9f4e0a46b6f4cefd652f1e20c068e5545d7cfc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 20 Jan 2017 13:13:49 +0000 Subject: [PATCH 23/24] Update libgit2 to df4dfaad --- describe_test.go | 6 ++++-- vendor/libgit2 | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/describe_test.go b/describe_test.go index f0a45f4..3181f23 100644 --- a/describe_test.go +++ b/describe_test.go @@ -93,14 +93,16 @@ func TestDescribeCommit(t *testing.T) { func checkDescribeNoRefsFound(t *testing.T, err error) { // The failure happens at wherever we were called, not here _, file, line, ok := runtime.Caller(1) + expectedString := "no reference found, cannot describe anything" if !ok { t.Fatalf("Unable to get caller") } - if err == nil || !strings.Contains(err.Error(), "No reference found, cannot describe anything") { + if err == nil || !strings.Contains(err.Error(), expectedString) { t.Fatalf( - "%s:%v: was expecting error 'No reference found, cannot describe anything', got %v", + "%s:%v: was expecting error %v, got %v", path.Base(file), line, + expectedString, err, ) } diff --git a/vendor/libgit2 b/vendor/libgit2 index ee89941..df4dfaa 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit ee89941fa2b861332defb5e3a8ce12c23c496ed7 +Subproject commit df4dfaadcf709646ebab2e57e3589952cf1ac809 From b020c1140a2cb8be18141498a6cab5440409dc24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 20 Jan 2017 13:48:39 +0000 Subject: [PATCH 24/24] Update the description of the branches in README. --- README.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index bd918d6..4ecfa34 100644 --- a/README.md +++ b/README.md @@ -2,44 +2,47 @@ git2go ====== [![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=master)](https://travis-ci.org/libgit2/git2go) +Go bindings for [libgit2](http://libgit2.github.com/). -Go bindings for [libgit2](http://libgit2.github.com/). The `master` branch follows the latest libgit2 release. The versioned branches indicate which libgit2 version they work against. +### Which branch to use + +The numbered branches work against the version of libgit2 as specified by their number. You can import them in your project via gopkg.in, e.g. if you have libgit2 v0.25 installed you'd import with + + import "gopkg.in/libgit2/git2go.v25" + +which will ensure there are no sudden changes to the API. + +The `master` branch follows the tip of libgit2 itself (with some lag) and as such has no guarantees on its own API nor does it have expectations the stability of libgit2's. Thus this only supports statically linking against libgit2. 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 manager and then install git2go as usual. +This project wraps the functionality provided by libgit2. It thus needs it in order to perform the work. -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. +This project wraps the functionality provided by libgit2. If you're using a versioned branch, install it to your system via your system's package manager and then install git2go. -### Stable version -git2go has `master` which tracks the latest release of libgit2, and versioned branches which indicate which version of libgit2 they work against. Install the development package on your system via your favourite package manager or from source and you can use a service like gopkg.in to use the appropriate version. For the libgit2 v0.22 case, you can use +### Versioned branch, dynamic linking - import "gopkg.in/libgit2/git2go.v22" +When linking dynamically against a released version of libgit2, install it via your system's package manager. CGo will take care of finding its pkg-config file and set up the linking. Import via gopkg.in, e.g. to work against libgit2 v0.25 -to use a version of git2go which will work against libgit2 v0.22 and dynamically link to the library. You can use + import "gopkg.in/libgit2/git2go.v25" - import "github.com/libgit2/git2go" +### Master branch, or static linking -to use the 'master' branch, which works against the latest release of libgit2, whichever that one is at the time. - -### From `next` - -The `next` branch follows libgit2's master branch, which means there is no stable API or ABI to link against. git2go can statically link against a vendored version of libgit2. +If using `master` or building a branch statically, we need to build libgit2 first. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL (outside of Windows or macOS) and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively. Note that even if libgit2 is included in the resulting binary, its dependencies will not be. Run `go get -d github.com/libgit2/git2go` to download the code and go to your `$GOPATH/src/github.com/libgit2/git2go` directory. From there, we need to build the C code and put it into the resulting go binary. - git checkout next git submodule update --init # get libgit2 make install -will compile libgit2. Run `go install` so that it's statically linked to the git2go package. +will compile libgit2, link it into git2go and install it. Parallelism and network operations ---------------------------------- -libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information. +libgit2 may use OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information. Running the tests -----------------