@@ -35,9 +35,9 @@ func objects(
3535 ignore []plumbing.Hash ,
3636 allowMissingObjects bool ,
3737) ([]plumbing.Hash , error ) {
38-
3938 seen := hashListToSet (ignore )
4039 result := make (map [plumbing.Hash ]bool )
40+ visited := make (map [plumbing.Hash ]bool )
4141
4242 walkerFunc := func (h plumbing.Hash ) {
4343 if ! seen [h ] {
@@ -47,7 +47,7 @@ func objects(
4747 }
4848
4949 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 {
5151 if allowMissingObjects && err == plumbing .ErrObjectNotFound {
5252 continue
5353 }
@@ -64,6 +64,7 @@ func processObject(
6464 s storer.EncodedObjectStorer ,
6565 h plumbing.Hash ,
6666 seen map [plumbing.Hash ]bool ,
67+ visited map [plumbing.Hash ]bool ,
6768 ignore []plumbing.Hash ,
6869 walkerFunc func (h plumbing.Hash ),
6970) error {
@@ -83,12 +84,12 @@ func processObject(
8384
8485 switch do := do .(type ) {
8586 case * object.Commit :
86- return reachableObjects (do , seen , ignore , walkerFunc )
87+ return reachableObjects (do , seen , visited , ignore , walkerFunc )
8788 case * object.Tree :
8889 return iterateCommitTrees (seen , do , walkerFunc )
8990 case * object.Tag :
9091 walkerFunc (do .Hash )
91- return processObject (s , do .Target , seen , ignore , walkerFunc )
92+ return processObject (s , do .Target , seen , visited , ignore , walkerFunc )
9293 case * object.Blob :
9394 walkerFunc (do .Hash )
9495 default :
@@ -103,34 +104,60 @@ func processObject(
103104// objects from the specified commit. To avoid to iterate over seen commits,
104105// if a commit hash is into the 'seen' set, we will not iterate all his trees
105106// 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.
106111func reachableObjects (
107112 commit * object.Commit ,
108113 seen map [plumbing.Hash ]bool ,
114+ visited map [plumbing.Hash ]bool ,
109115 ignore []plumbing.Hash ,
110- cb func (h plumbing.Hash )) error {
111-
116+ cb func (h plumbing.Hash ),
117+ ) error {
112118 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+
114133 if seen [commit .Hash ] {
115- return nil
134+ continue
116135 }
117136
118137 cb (commit .Hash )
138+ if commit .NumParents () > 0 {
139+ visited [commit .Hash ] = true
140+ }
119141
120142 tree , err := commit .Tree ()
121143 if err != nil {
122144 return err
123145 }
124146
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
127153}
128154
129155// iterateCommitTrees iterate all reachable trees from the given commit
130156func iterateCommitTrees (
131157 seen map [plumbing.Hash ]bool ,
132158 tree * object.Tree ,
133- cb func (h plumbing.Hash )) error {
159+ cb func (h plumbing.Hash ),
160+ ) error {
134161 if seen [tree .Hash ] {
135162 return nil
136163 }
0 commit comments