@@ -35,9 +35,9 @@ func objects(
35
35
ignore []plumbing.Hash ,
36
36
allowMissingObjects bool ,
37
37
) ([]plumbing.Hash , error ) {
38
-
39
38
seen := hashListToSet (ignore )
40
39
result := make (map [plumbing.Hash ]bool )
40
+ visited := make (map [plumbing.Hash ]bool )
41
41
42
42
walkerFunc := func (h plumbing.Hash ) {
43
43
if ! seen [h ] {
@@ -47,7 +47,7 @@ func objects(
47
47
}
48
48
49
49
for _ , h := range objects {
50
- if err := processObject (s , h , seen , ignore , walkerFunc ); err != nil {
50
+ if err := processObject (s , h , seen , visited , ignore , walkerFunc ); err != nil {
51
51
if allowMissingObjects && err == plumbing .ErrObjectNotFound {
52
52
continue
53
53
}
@@ -64,6 +64,7 @@ func processObject(
64
64
s storer.EncodedObjectStorer ,
65
65
h plumbing.Hash ,
66
66
seen map [plumbing.Hash ]bool ,
67
+ visited map [plumbing.Hash ]bool ,
67
68
ignore []plumbing.Hash ,
68
69
walkerFunc func (h plumbing.Hash ),
69
70
) error {
@@ -83,12 +84,12 @@ func processObject(
83
84
84
85
switch do := do .(type ) {
85
86
case * object.Commit :
86
- return reachableObjects (do , seen , ignore , walkerFunc )
87
+ return reachableObjects (do , seen , visited , ignore , walkerFunc )
87
88
case * object.Tree :
88
89
return iterateCommitTrees (seen , do , walkerFunc )
89
90
case * object.Tag :
90
91
walkerFunc (do .Hash )
91
- return processObject (s , do .Target , seen , ignore , walkerFunc )
92
+ return processObject (s , do .Target , seen , visited , ignore , walkerFunc )
92
93
case * object.Blob :
93
94
walkerFunc (do .Hash )
94
95
default :
@@ -103,34 +104,60 @@ func processObject(
103
104
// objects from the specified commit. To avoid to iterate over seen commits,
104
105
// if a commit hash is into the 'seen' set, we will not iterate all his trees
105
106
// and blobs objects.
107
+ // We assume all commits have the same parents, unless a commit has no parents.
108
+ // So when we've visited a commit before, we can stop iterating commits, as we've
109
+ // already processed all its ancestors before as well. `visited` keeps track of
110
+ // all the commits that have been visited that had parents.
106
111
func reachableObjects (
107
112
commit * object.Commit ,
108
113
seen map [plumbing.Hash ]bool ,
114
+ visited map [plumbing.Hash ]bool ,
109
115
ignore []plumbing.Hash ,
110
- cb func (h plumbing.Hash )) error {
111
-
116
+ cb func (h plumbing.Hash ),
117
+ ) error {
112
118
i := object .NewCommitPreorderIter (commit , ignore )
113
- return i .ForEach (func (commit * object.Commit ) error {
119
+ for {
120
+ commit , err := i .Next ()
121
+ if err == io .EOF {
122
+ break
123
+ }
124
+
125
+ if err != nil {
126
+ return err
127
+ }
128
+
129
+ if visited [commit .Hash ] {
130
+ break
131
+ }
132
+
114
133
if seen [commit .Hash ] {
115
- return nil
134
+ continue
116
135
}
117
136
118
137
cb (commit .Hash )
138
+ if commit .NumParents () > 0 {
139
+ visited [commit .Hash ] = true
140
+ }
119
141
120
142
tree , err := commit .Tree ()
121
143
if err != nil {
122
144
return err
123
145
}
124
146
125
- return iterateCommitTrees (seen , tree , cb )
126
- })
147
+ if err := iterateCommitTrees (seen , tree , cb ); err != nil {
148
+ return err
149
+ }
150
+ }
151
+
152
+ return nil
127
153
}
128
154
129
155
// iterateCommitTrees iterate all reachable trees from the given commit
130
156
func iterateCommitTrees (
131
157
seen map [plumbing.Hash ]bool ,
132
158
tree * object.Tree ,
133
- cb func (h plumbing.Hash )) error {
159
+ cb func (h plumbing.Hash ),
160
+ ) error {
134
161
if seen [tree .Hash ] {
135
162
return nil
136
163
}
0 commit comments