nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: libgit2/git2go
Pull request #400 (2017-07-17 10:17:49)

Add binding for `git_index_add_frombuffer`
It would be nice to have this binding.


Patch:
diff --git a/index.go b/index.go
index dd13460..50538bd 100644
--- a/index.go
+++ b/index.go
@@ -90,7 +90,9 @@ func populateCIndexEntry(source *IndexEntry, dest *C.git_index_entry) {
 	dest.uid = C.uint32_t(source.Uid)
 	dest.gid = C.uint32_t(source.Gid)
 	dest.file_size = C.uint32_t(source.Size)
-	dest.id = *source.Id.toC()
+	if source.Id != nil {
+		dest.id = *source.Id.toC()
+	}
 	dest.path = C.CString(source.Path)
 }
 
@@ -195,6 +197,28 @@ func (v *Index) AddByPath(path string) error {
 	return nil
 }
 
+// AddFromBuffer adds or replaces an index entry from a buffer in memory
+func (v *Index) AddFromBuffer(entry *IndexEntry, buffer []byte) error {
+	var centry C.git_index_entry
+
+	populateCIndexEntry(entry, &centry)
+	defer freeCIndexEntry(&centry)
+
+	var cbuffer unsafe.Pointer
+	if len(buffer) > 0 {
+		cbuffer = unsafe.Pointer(&buffer[0])
+	}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if err := C.git_index_add_from_buffer(v.ptr, &centry, cbuffer, C.size_t(len(buffer))); err < 0 {
+		return MakeGitError(err)
+	}
+
+	return nil
+}
+
 func (v *Index) AddAll(pathspecs []string, flags IndexAddOpts, callback IndexMatchedPathCallback) error {
 	cpathspecs := C.git_strarray{}
 	cpathspecs.count = C.size_t(len(pathspecs))


Test patch:
diff --git a/index_test.go b/index_test.go
index 43644fa..5fa3f9f 100644
--- a/index_test.go
+++ b/index_test.go
@@ -165,6 +165,30 @@ func TestIndexRemoveDirectory(t *testing.T) {
 	}
 }
 
+func TestIndexAddFromBuffer(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	idx, err := repo.Index()
+	checkFatal(t, err)
+
+	entry := IndexEntry{
+		Path: "README",
+		Mode: FilemodeBlob,
+	}
+
+	err = idx.AddFromBuffer(&entry, []byte("foo\n"))
+	checkFatal(t, err)
+
+	treeId, err := idx.WriteTreeTo(repo)
+	checkFatal(t, err)
+
+	if treeId.String() != "b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e" {
+		t.Fatalf("%v", treeId.String())
+	}
+}
+
 func TestIndexAddAllNoCallback(t *testing.T) {
 	t.Parallel()
 	repo := createTestRepo(t)
Repository: libgit2/git2go
Pull request #429 (2018-02-07 05:57:28)

cherrypick: wrap git_cherrypick_commit
This PR comes with the same caveat as #423: It was implemented mainly by imitated nearby/similar code. Please review skeptically.

I think that CherryPickCommit would be better capitalization, but CherrypickCommit matches the capitalization of the existing Cherrypick method.


Patch:
diff --git a/cherrypick.go b/cherrypick.go
index 8983a7a..e86e940 100644
--- a/cherrypick.go
+++ b/cherrypick.go
@@ -73,3 +73,19 @@ func (v *Repository) Cherrypick(commit *Commit, opts CherrypickOptions) error {
 	}
 	return nil
 }
+
+func (r *Repository) CherrypickCommit(pick, our *Commit, opts CherrypickOptions) (*Index, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	cOpts := opts.MergeOpts.toC()
+
+	var ptr *C.git_index
+	ret := C.git_cherrypick_commit(&ptr, r.ptr, pick.cast_ptr, our.cast_ptr, C.uint(opts.Mainline), cOpts)
+	runtime.KeepAlive(pick)
+	runtime.KeepAlive(our)
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+	return newIndexFromC(ptr, r), nil
+}


Test patch:
diff --git a/cherrypick_test.go b/cherrypick_test.go
index bfa5ca8..19a9736 100644
--- a/cherrypick_test.go
+++ b/cherrypick_test.go
@@ -84,3 +84,57 @@ func TestCherrypick(t *testing.T) {
 		t.Fatal("Incorrect repository state: ", state)
 	}
 }
+
+func TestCherrypickCommit(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	c1, _ := seedTestRepo(t, repo)
+	c2, _ := updateReadme(t, repo, content)
+
+	commit1, err := repo.LookupCommit(c1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	commit2, err := repo.LookupCommit(c2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	checkout(t, repo, commit1)
+
+	if got := readReadme(t, repo); got == content {
+		t.Fatalf("README = %q, want %q", got, content)
+	}
+
+	opts, err := DefaultCherrypickOptions()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	idx, err := repo.CherrypickCommit(commit2, commit1, opts)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer idx.Free()
+
+	// The file is only updated in the index, not in the working directory.
+	if got := readReadme(t, repo); got == content {
+		t.Errorf("README = %q, want %q", got, content)
+	}
+	if got := repo.State(); got != RepositoryStateNone {
+		t.Errorf("repo.State() = %v, want %v", got, RepositoryStateCherrypick)
+	}
+
+	if got := idx.EntryCount(); got != 1 {
+		t.Fatalf("idx.EntryCount() = %v, want %v", got, 1)
+	}
+	entry, err := idx.EntryByIndex(0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if entry.Path != "README" {
+		t.Errorf("entry.Path = %v, want %v", entry.Path, "README")
+	}
+}
Repository: libgit2/git2go
Pull request #614 (2020-06-01 21:44:57)

Add support for parsing git trailers
Adds a wrapper for git_message_trailers which returns a slice of trailer
structs.

First pull request for git2go & first use of cgo, criticism welcomed, thanks!

lhchavez: > @lhchavez thanks for your detailed review, I believe I addressed all of you feedback, but please let me know if there is anything I missed!

it's all good! thanks for doing this!

Patch:
diff --git a/message.go b/message.go
new file mode 100644
index 0000000..255e8d7
--- /dev/null
+++ b/message.go
@@ -0,0 +1,42 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+import (
+	"runtime"
+	"unsafe"
+)
+
+// Trailer represents a single git message trailer.
+type Trailer struct {
+	Key   string
+	Value string
+}
+
+// MessageTrailers parses trailers out of a message, returning a slice of
+// Trailer structs. Trailers are key/value pairs in the last paragraph of a
+// message, not including any patches or conflicts that may be present.
+func MessageTrailers(message string) ([]Trailer, error) {
+	var trailersC C.git_message_trailer_array
+
+	messageC := C.CString(message)
+	defer C.free(unsafe.Pointer(messageC))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_message_trailers(&trailersC, messageC)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+	defer C.git_message_trailer_array_free(&trailersC)
+	trailers := make([]Trailer, trailersC.count)
+	var trailer *C.git_message_trailer
+	for i, p := 0, uintptr(unsafe.Pointer(trailersC.trailers)); i < int(trailersC.count); i, p = i+1, p+unsafe.Sizeof(C.git_message_trailer{}) {
+		trailer = (*C.git_message_trailer)(unsafe.Pointer(p))
+		trailers[i] = Trailer{Key: C.GoString(trailer.key), Value: C.GoString(trailer.value)}
+	}
+	return trailers, nil
+}


Test patch:
diff --git a/message_test.go b/message_test.go
new file mode 100644
index 0000000..f33ccb7
--- /dev/null
+++ b/message_test.go
@@ -0,0 +1,42 @@
+package git
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestTrailers(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		input    string
+		expected []Trailer
+	}{
+		{
+			"commit with zero trailers\n",
+			[]Trailer{},
+		},
+		{
+			"commit with one trailer\n\nCo-authored-by: Alice <alice@example.com>\n",
+			[]Trailer{
+				Trailer{Key: "Co-authored-by", Value: "Alice <alice@example.com>"},
+			},
+		},
+		{
+			"commit with two trailers\n\nCo-authored-by: Alice <alice@example.com>\nSigned-off-by: Bob <bob@example.com>\n",
+			[]Trailer{
+				Trailer{Key: "Co-authored-by", Value: "Alice <alice@example.com>"},
+				Trailer{Key: "Signed-off-by", Value: "Bob <bob@example.com>"}},
+		},
+	}
+	for _, test := range tests {
+		fmt.Printf("%s", test.input)
+		actual, err := MessageTrailers(test.input)
+		if err != nil {
+			t.Errorf("Trailers returned an unexpected error: %v", err)
+		}
+		if !reflect.DeepEqual(test.expected, actual) {
+			t.Errorf("expecting %#v\ngot %#v", test.expected, actual)
+		}
+	}
+}
Repository: libgit2/git2go
Pull request #670 (2020-10-27 01:40:59)

CI refresh (#666)
This change:

* Makes the Travis tests only run tip, since the rest of the Go versions are better served by GitHub Actions.
* Use Go 1.15 in the CI. This has been released for a while.

(cherry picked from commit f83530b18dc46867ed06fc261b309b8b545a3b6f)


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 42636bc..caa52a4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        go: [ '1.11', '1.12', '1.13', '1.14' ]
+        go: [ '1.11', '1.12', '1.13', '1.14', '1.15' ]
     name: Go ${{ matrix.go }}
 
     runs-on: ubuntu-20.04
@@ -77,7 +77,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v1
       with:
-        go-version: '1.14'
+        go-version: '1.15'
       id: go
     - name: Check out code into the Go module directory
       uses: actions/checkout@v1
@@ -99,7 +99,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v1
       with:
-        go-version: '1.14'
+        go-version: '1.15'
       id: go
     - name: Check out code into the Go module directory
       uses: actions/checkout@v1
@@ -121,7 +121,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v1
       with:
-        go-version: '1.14'
+        go-version: '1.15'
       id: go
     - name: Check out code into the Go module directory
       uses: actions/checkout@v1
diff --git a/.travis.yml b/.travis.yml
index c89cb5c..984e1c6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,6 @@
 language: go
 
 go:
-  - "1.7"
-  - "1.8"
-  - "1.9"
-  - "1.10"
-  - "1.11"
-  - "1.12"
-  - "1.13"
   - tip
 
 install:
@@ -17,10 +10,6 @@ install:
 script:
   - make test-static
 
-matrix:
-  allow_failures:
-    - go: tip
-
 git:
   submodules: true
 
diff --git a/settings.go b/settings.go
index 9f2ec02..9325618 100644
--- a/settings.go
+++ b/settings.go
@@ -93,6 +93,14 @@ func EnableCaching(enabled bool) error {
 	}
 }
 
+func EnableStrictHashVerification(enabled bool) error {
+	if enabled {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)
+	} else {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)
+	}
+}
+
 func CachedMemory() (current int, allowed int, err error) {
 	return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
 }


Test patch:
diff --git a/settings_test.go b/settings_test.go
index 150ee7c..47eb711 100644
--- a/settings_test.go
+++ b/settings_test.go
@@ -57,6 +57,14 @@ func TestEnableCaching(t *testing.T) {
 	checkFatal(t, err)
 }
 
+func TestEnableStrictHashVerification(t *testing.T) {
+	err := EnableStrictHashVerification(false)
+	checkFatal(t, err)
+
+	err = EnableStrictHashVerification(true)
+	checkFatal(t, err)
+}
+
 func TestCachedMemory(t *testing.T) {
 	current, allowed, err := CachedMemory()
 	checkFatal(t, err)
@@ -80,4 +88,4 @@ func TestSetCacheMaxSize(t *testing.T) {
 	// revert to default 256MB
 	err = SetCacheMaxSize(256 * 1024 * 1024)
 	checkFatal(t, err)
-}
\ No newline at end of file
+}
Repository: libgit2/git2go
Pull request #671 (2020-11-02 21:33:45)

Implement an option to control hash verification
Add a binding to enable/disable hash verification using the `GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION` option.

Change type: #minor


Patch:
diff --git a/settings.go b/settings.go
index 9f2ec02..9325618 100644
--- a/settings.go
+++ b/settings.go
@@ -93,6 +93,14 @@ func EnableCaching(enabled bool) error {
 	}
 }
 
+func EnableStrictHashVerification(enabled bool) error {
+	if enabled {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)
+	} else {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)
+	}
+}
+
 func CachedMemory() (current int, allowed int, err error) {
 	return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
 }


Test patch:
diff --git a/settings_test.go b/settings_test.go
index 150ee7c..47eb711 100644
--- a/settings_test.go
+++ b/settings_test.go
@@ -57,6 +57,14 @@ func TestEnableCaching(t *testing.T) {
 	checkFatal(t, err)
 }
 
+func TestEnableStrictHashVerification(t *testing.T) {
+	err := EnableStrictHashVerification(false)
+	checkFatal(t, err)
+
+	err = EnableStrictHashVerification(true)
+	checkFatal(t, err)
+}
+
 func TestCachedMemory(t *testing.T) {
 	current, allowed, err := CachedMemory()
 	checkFatal(t, err)
@@ -80,4 +88,4 @@ func TestSetCacheMaxSize(t *testing.T) {
 	// revert to default 256MB
 	err = SetCacheMaxSize(256 * 1024 * 1024)
 	checkFatal(t, err)
-}
\ No newline at end of file
+}
Repository: libgit2/git2go
Pull request #672 (2020-11-03 02:36:45)

feat: Implement an option to control hash verification (#671)
Add a binding to enable/disable hash verification using the `GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION` option.

Change type: #minor

(cherry picked from commit c3664193f3c05bd6ae48f153c6c41cd7d7a3d98b)


Patch:
diff --git a/settings.go b/settings.go
index 9f2ec02..9325618 100644
--- a/settings.go
+++ b/settings.go
@@ -93,6 +93,14 @@ func EnableCaching(enabled bool) error {
 	}
 }
 
+func EnableStrictHashVerification(enabled bool) error {
+	if enabled {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)
+	} else {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)
+	}
+}
+
 func CachedMemory() (current int, allowed int, err error) {
 	return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
 }


Test patch:
diff --git a/settings_test.go b/settings_test.go
index 150ee7c..47eb711 100644
--- a/settings_test.go
+++ b/settings_test.go
@@ -57,6 +57,14 @@ func TestEnableCaching(t *testing.T) {
 	checkFatal(t, err)
 }
 
+func TestEnableStrictHashVerification(t *testing.T) {
+	err := EnableStrictHashVerification(false)
+	checkFatal(t, err)
+
+	err = EnableStrictHashVerification(true)
+	checkFatal(t, err)
+}
+
 func TestCachedMemory(t *testing.T) {
 	current, allowed, err := CachedMemory()
 	checkFatal(t, err)
@@ -80,4 +88,4 @@ func TestSetCacheMaxSize(t *testing.T) {
 	// revert to default 256MB
 	err = SetCacheMaxSize(256 * 1024 * 1024)
 	checkFatal(t, err)
-}
\ No newline at end of file
+}
Repository: libgit2/git2go
Pull request #673 (2020-11-03 02:36:46)

feat: Implement an option to control hash verification (#671)
Add a binding to enable/disable hash verification using the `GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION` option.

Change type: #minor

(cherry picked from commit c3664193f3c05bd6ae48f153c6c41cd7d7a3d98b)


Patch:
diff --git a/settings.go b/settings.go
index 2472908..f278e7b 100644
--- a/settings.go
+++ b/settings.go
@@ -93,6 +93,14 @@ func EnableCaching(enabled bool) error {
 	}
 }
 
+func EnableStrictHashVerification(enabled bool) error {
+	if enabled {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)
+	} else {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)
+	}
+}
+
 func CachedMemory() (current int, allowed int, err error) {
 	return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
 }


Test patch:
diff --git a/settings_test.go b/settings_test.go
index 150ee7c..47eb711 100644
--- a/settings_test.go
+++ b/settings_test.go
@@ -57,6 +57,14 @@ func TestEnableCaching(t *testing.T) {
 	checkFatal(t, err)
 }
 
+func TestEnableStrictHashVerification(t *testing.T) {
+	err := EnableStrictHashVerification(false)
+	checkFatal(t, err)
+
+	err = EnableStrictHashVerification(true)
+	checkFatal(t, err)
+}
+
 func TestCachedMemory(t *testing.T) {
 	current, allowed, err := CachedMemory()
 	checkFatal(t, err)
@@ -80,4 +88,4 @@ func TestSetCacheMaxSize(t *testing.T) {
 	// revert to default 256MB
 	err = SetCacheMaxSize(256 * 1024 * 1024)
 	checkFatal(t, err)
-}
\ No newline at end of file
+}
Repository: libgit2/git2go
Pull request #675 (2020-11-03 02:52:17)

feat: Implement an option to control hash verification (#671)
Add a binding to enable/disable hash verification using the `GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION` option.

Change type: #minor

(cherry picked from commit c3664193f3c05bd6ae48f153c6c41cd7d7a3d98b)


Patch:
diff --git a/settings.go b/settings.go
index 9f2ec02..9325618 100644
--- a/settings.go
+++ b/settings.go
@@ -93,6 +93,14 @@ func EnableCaching(enabled bool) error {
 	}
 }
 
+func EnableStrictHashVerification(enabled bool) error {
+	if enabled {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)
+	} else {
+		return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)
+	}
+}
+
 func CachedMemory() (current int, allowed int, err error) {
 	return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
 }


Test patch:
diff --git a/settings_test.go b/settings_test.go
index 150ee7c..47eb711 100644
--- a/settings_test.go
+++ b/settings_test.go
@@ -57,6 +57,14 @@ func TestEnableCaching(t *testing.T) {
 	checkFatal(t, err)
 }
 
+func TestEnableStrictHashVerification(t *testing.T) {
+	err := EnableStrictHashVerification(false)
+	checkFatal(t, err)
+
+	err = EnableStrictHashVerification(true)
+	checkFatal(t, err)
+}
+
 func TestCachedMemory(t *testing.T) {
 	current, allowed, err := CachedMemory()
 	checkFatal(t, err)
@@ -80,4 +88,4 @@ func TestSetCacheMaxSize(t *testing.T) {
 	// revert to default 256MB
 	err = SetCacheMaxSize(256 * 1024 * 1024)
 	checkFatal(t, err)
-}
\ No newline at end of file
+}
Repository: libgit2/git2go
Pull request #681 (2020-11-10 16:43:32)

Add ReferenceNormalizeName
Not sure about the buffer allocation for passing to `git_reference_normalize_name`: Is using `malloc` correct? Is the size correct, or should it be a loop with retries with some increasing buffer size (But the function doesn't seem to tell you how big it should be...)?

segevfiner: Go 1.9?! Really... 🤷‍♂️ 

Patch:
diff --git a/.travis.yml b/.travis.yml
index 984e1c6..1717c8e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,9 @@
 language: go
 
+arch:
+  - AMD64
+  - ppc64le
+ 
 go:
   - tip
 
diff --git a/reference.go b/reference.go
index b5f5e47..7b5e3c2 100644
--- a/reference.go
+++ b/reference.go
@@ -488,3 +488,42 @@ func ReferenceIsValidName(name string) bool {
 	}
 	return false
 }
+
+const (
+	// This should match GIT_REFNAME_MAX in src/refs.h
+	_refnameMaxLength = C.size_t(1024)
+)
+
+type ReferenceFormat uint
+
+const (
+	ReferenceFormatNormal           ReferenceFormat = C.GIT_REFERENCE_FORMAT_NORMAL
+	ReferenceFormatAllowOnelevel    ReferenceFormat = C.GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL
+	ReferenceFormatRefspecPattern   ReferenceFormat = C.GIT_REFERENCE_FORMAT_REFSPEC_PATTERN
+	ReferenceFormatRefspecShorthand ReferenceFormat = C.GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND
+)
+
+// ReferenceNormalizeName normalizes the reference name and checks validity.
+//
+// This will normalize the reference name by removing any leading slash '/'
+// characters and collapsing runs of adjacent slashes between name components
+// into a single slash.
+//
+// See git_reference_symbolic_create() for rules about valid names.
+func ReferenceNormalizeName(name string, flags ReferenceFormat) (string, error) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	buf := (*C.char)(C.malloc(_refnameMaxLength))
+	defer C.free(unsafe.Pointer(buf))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_reference_normalize_name(buf, _refnameMaxLength, cname, C.uint(flags))
+	if ecode < 0 {
+		return "", MakeGitError(ecode)
+	}
+
+	return C.GoString(buf), nil
+}


Test patch:
diff --git a/reference_test.go b/reference_test.go
index b6721e1..e42db41 100644
--- a/reference_test.go
+++ b/reference_test.go
@@ -224,6 +224,29 @@ func TestReferenceIsValidName(t *testing.T) {
 	}
 }
 
+func TestReferenceNormalizeName(t *testing.T) {
+	t.Parallel()
+
+	ref, err := ReferenceNormalizeName("refs/heads//master", ReferenceFormatNormal)
+	checkFatal(t, err)
+
+	if ref != "refs/heads/master" {
+		t.Errorf("ReferenceNormalizeName(%q) = %q; want %q", "refs/heads//master", ref, "refs/heads/master")
+	}
+
+	ref, err = ReferenceNormalizeName("master", ReferenceFormatAllowOnelevel|ReferenceFormatRefspecShorthand)
+	checkFatal(t, err)
+
+	if ref != "master" {
+		t.Errorf("ReferenceNormalizeName(%q) = %q; want %q", "master", ref, "master")
+	}
+
+	ref, err = ReferenceNormalizeName("foo^", ReferenceFormatNormal)
+	if !IsErrorCode(err, ErrInvalidSpec) {
+		t.Errorf("foo^ should be invalid")
+	}
+}
+
 func compareStringList(t *testing.T, expected, actual []string) {
 	for i, v := range expected {
 		if actual[i] != v {
Repository: libgit2/git2go
Pull request #686 (2020-11-14 00:51:22)

Add ReferenceNormalizeName (#681)
(cherry picked from commit 2bd574b6bd75a044f526cf882161db08ac3cb633)


Patch:
diff --git a/reference.go b/reference.go
index b5f5e47..7b5e3c2 100644
--- a/reference.go
+++ b/reference.go
@@ -488,3 +488,42 @@ func ReferenceIsValidName(name string) bool {
 	}
 	return false
 }
+
+const (
+	// This should match GIT_REFNAME_MAX in src/refs.h
+	_refnameMaxLength = C.size_t(1024)
+)
+
+type ReferenceFormat uint
+
+const (
+	ReferenceFormatNormal           ReferenceFormat = C.GIT_REFERENCE_FORMAT_NORMAL
+	ReferenceFormatAllowOnelevel    ReferenceFormat = C.GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL
+	ReferenceFormatRefspecPattern   ReferenceFormat = C.GIT_REFERENCE_FORMAT_REFSPEC_PATTERN
+	ReferenceFormatRefspecShorthand ReferenceFormat = C.GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND
+)
+
+// ReferenceNormalizeName normalizes the reference name and checks validity.
+//
+// This will normalize the reference name by removing any leading slash '/'
+// characters and collapsing runs of adjacent slashes between name components
+// into a single slash.
+//
+// See git_reference_symbolic_create() for rules about valid names.
+func ReferenceNormalizeName(name string, flags ReferenceFormat) (string, error) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	buf := (*C.char)(C.malloc(_refnameMaxLength))
+	defer C.free(unsafe.Pointer(buf))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_reference_normalize_name(buf, _refnameMaxLength, cname, C.uint(flags))
+	if ecode < 0 {
+		return "", MakeGitError(ecode)
+	}
+
+	return C.GoString(buf), nil
+}


Test patch:
diff --git a/reference_test.go b/reference_test.go
index b6721e1..e42db41 100644
--- a/reference_test.go
+++ b/reference_test.go
@@ -224,6 +224,29 @@ func TestReferenceIsValidName(t *testing.T) {
 	}
 }
 
+func TestReferenceNormalizeName(t *testing.T) {
+	t.Parallel()
+
+	ref, err := ReferenceNormalizeName("refs/heads//master", ReferenceFormatNormal)
+	checkFatal(t, err)
+
+	if ref != "refs/heads/master" {
+		t.Errorf("ReferenceNormalizeName(%q) = %q; want %q", "refs/heads//master", ref, "refs/heads/master")
+	}
+
+	ref, err = ReferenceNormalizeName("master", ReferenceFormatAllowOnelevel|ReferenceFormatRefspecShorthand)
+	checkFatal(t, err)
+
+	if ref != "master" {
+		t.Errorf("ReferenceNormalizeName(%q) = %q; want %q", "master", ref, "master")
+	}
+
+	ref, err = ReferenceNormalizeName("foo^", ReferenceFormatNormal)
+	if !IsErrorCode(err, ErrInvalidSpec) {
+		t.Errorf("foo^ should be invalid")
+	}
+}
+
 func compareStringList(t *testing.T, expected, actual []string) {
 	for i, v := range expected {
 		if actual[i] != v {