Skip to content

Commit 7823b14

Browse files
author
Vicent Martí
committed
Merge pull request libgit2#26 from carlosmn/ref-iter
Implement a reference iterator
2 parents 81c9f8d + 931f187 commit 7823b14

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed

git.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package git
88
import "C"
99
import (
1010
"bytes"
11+
"errors"
1112
"unsafe"
1213
"strings"
1314
)
@@ -18,6 +19,10 @@ const (
1819
ENOTFOUND = C.GIT_ENOTFOUND
1920
)
2021

22+
var (
23+
ErrIterOver = errors.New("Iteration is over")
24+
)
25+
2126
func init() {
2227
C.git_threads_init()
2328
}

reference.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,76 @@ func (v *Reference) Free() {
111111
runtime.SetFinalizer(v, nil)
112112
C.git_reference_free(v.ptr)
113113
}
114+
115+
type ReferenceIterator struct {
116+
ptr *C.git_reference_iterator
117+
repo *Repository
118+
}
119+
120+
// NewReferenceIterator creates a new iterator over reference names
121+
func (repo *Repository) NewReferenceIterator() (*ReferenceIterator, error) {
122+
var ptr *C.git_reference_iterator
123+
ret := C.git_reference_iterator_new(&ptr, repo.ptr)
124+
if ret < 0 {
125+
return nil, LastError()
126+
}
127+
128+
iter := &ReferenceIterator{repo: repo, ptr: ptr}
129+
runtime.SetFinalizer(iter, (*ReferenceIterator).Free)
130+
return iter, nil
131+
}
132+
133+
// NewReferenceIteratorGlob creates an iterator over reference names
134+
// that match the speicified glob. The glob is of the usual fnmatch
135+
// type.
136+
func (repo *Repository) NewReferenceIteratorGlob(glob string) (*ReferenceIterator, error) {
137+
cstr := C.CString(glob)
138+
defer C.free(unsafe.Pointer(cstr))
139+
var ptr *C.git_reference_iterator
140+
ret := C.git_reference_iterator_glob_new(&ptr, repo.ptr, cstr)
141+
if ret < 0 {
142+
return nil, LastError()
143+
}
144+
145+
iter := &ReferenceIterator{repo: repo, ptr: ptr}
146+
runtime.SetFinalizer(iter, (*ReferenceIterator).Free)
147+
return iter, nil
148+
}
149+
150+
// Next retrieves the next reference name. If the iteration is over,
151+
// the returned error is git.ErrIterOver
152+
func (v *ReferenceIterator) Next() (string, error) {
153+
var ptr *C.char
154+
ret := C.git_reference_next(&ptr, v.ptr)
155+
if ret == ITEROVER {
156+
return "", ErrIterOver
157+
}
158+
if ret < 0 {
159+
return "", LastError()
160+
}
161+
162+
return C.GoString(ptr), nil
163+
}
164+
165+
// Create a channel from the iterator. You can use range on the
166+
// returned channel to iterate over all the references. The channel
167+
// will be closed in case any error is found.
168+
func (v *ReferenceIterator) Iter() <-chan string {
169+
ch := make(chan string)
170+
go func() {
171+
defer close(ch)
172+
name, err := v.Next()
173+
for err == nil {
174+
ch <- name
175+
name, err = v.Next()
176+
}
177+
}()
178+
179+
return ch
180+
}
181+
182+
// Free the reference iterator
183+
func (v *ReferenceIterator) Free() {
184+
runtime.SetFinalizer(v, nil)
185+
C.git_reference_iterator_free(v.ptr)
186+
}

reference_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package git
33
import (
44
"os"
55
"runtime"
6+
"sort"
67
"testing"
78
"time"
89
)
@@ -71,6 +72,97 @@ func TestRefModification(t *testing.T) {
7172

7273
}
7374

75+
func TestIterator(t *testing.T) {
76+
repo := createTestRepo(t)
77+
defer os.RemoveAll(repo.Workdir())
78+
79+
loc, err := time.LoadLocation("Europe/Berlin")
80+
checkFatal(t, err)
81+
sig := &Signature{
82+
Name: "Rand Om Hacker",
83+
84+
When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
85+
}
86+
87+
idx, err := repo.Index()
88+
checkFatal(t, err)
89+
err = idx.AddByPath("README")
90+
checkFatal(t, err)
91+
treeId, err := idx.WriteTree()
92+
checkFatal(t, err)
93+
94+
message := "This is a commit\n"
95+
tree, err := repo.LookupTree(treeId)
96+
checkFatal(t, err)
97+
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
98+
checkFatal(t, err)
99+
100+
_, err = repo.CreateReference("refs/heads/one", commitId, true)
101+
checkFatal(t, err)
102+
103+
_, err = repo.CreateReference("refs/heads/two", commitId, true)
104+
checkFatal(t, err)
105+
106+
_, err = repo.CreateReference("refs/heads/three", commitId, true)
107+
checkFatal(t, err)
108+
109+
iter, err := repo.NewReferenceIterator()
110+
checkFatal(t, err)
111+
112+
var list []string
113+
expected := []string{
114+
"refs/heads/master",
115+
"refs/heads/one",
116+
"refs/heads/three",
117+
"refs/heads/two",
118+
}
119+
120+
// test some manual iteration
121+
name, err := iter.Next()
122+
for err == nil {
123+
list = append(list, name)
124+
name, err = iter.Next()
125+
}
126+
if err != ErrIterOver {
127+
t.Fatal("Iteration not over")
128+
}
129+
130+
131+
sort.Strings(list)
132+
compareStringList(t, expected, list)
133+
134+
// test the channel iteration
135+
list = []string{}
136+
iter, err = repo.NewReferenceIterator()
137+
for name := range iter.Iter() {
138+
list = append(list, name)
139+
}
140+
141+
sort.Strings(list)
142+
compareStringList(t, expected, list)
143+
144+
iter, err = repo.NewReferenceIteratorGlob("refs/heads/t*")
145+
expected = []string{
146+
"refs/heads/three",
147+
"refs/heads/two",
148+
}
149+
150+
list = []string{}
151+
for name := range iter.Iter() {
152+
list = append(list, name)
153+
}
154+
155+
compareStringList(t, expected, list)
156+
}
157+
158+
func compareStringList(t *testing.T, expected, actual []string) {
159+
for i, v := range expected {
160+
if actual[i] != v {
161+
t.Fatalf("Bad list")
162+
}
163+
}
164+
}
165+
74166
func checkRefType(t *testing.T, ref *Reference, kind int) {
75167
if ref.Type() == kind {
76168
return

0 commit comments

Comments
 (0)