Skip to content

Commit 2344567

Browse files
authored
Merge pull request #28 from per1234/tags-order
Improve order of release processing
2 parents 04aca59 + d825cc2 commit 2344567

File tree

7 files changed

+382
-11
lines changed

7 files changed

+382
-11
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module arduino.cc/repository
33
go 1.14
44

55
require (
6+
github.com/arduino/go-paths-helper v1.5.0
67
github.com/arduino/golang-concurrent-workers v0.0.0-20170202182617-6710cdc954bc
78
github.com/blang/semver v3.5.1+incompatible
89
github.com/go-git/go-git/v5 v5.3.0

go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBb
55
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
66
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
77
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
8+
github.com/arduino/go-paths-helper v1.5.0 h1:RVo189hD+GhUS1rQ3gixwK1nSbvVR8MGIGa7Gxv2bdM=
9+
github.com/arduino/go-paths-helper v1.5.0/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU=
810
github.com/arduino/golang-concurrent-workers v0.0.0-20170202182617-6710cdc954bc h1:PzGY1Ppud/Ng+LFHU16oOrWhYsnSLYurwiHlbVc/FJ0=
911
github.com/arduino/golang-concurrent-workers v0.0.0-20170202182617-6710cdc954bc/go.mod h1:E+WBbLkFBdPp+N+yijgbdDI33mr5pm6j42RYLN5K4do=
1012
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -67,6 +69,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
6769
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6870
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6971
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
72+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
7073
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
7174
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
7275
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

libraries/git_integration_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import (
3030
"testing"
3131

3232
"arduino.cc/repository/libraries/db"
33+
"arduino.cc/repository/libraries/gitutils"
3334
"github.com/go-git/go-git/v5"
34-
"github.com/go-git/go-git/v5/plumbing"
3535
"github.com/stretchr/testify/require"
3636
)
3737

@@ -66,7 +66,7 @@ func TestUpdateLibraryJson(t *testing.T) {
6666
repoTree, err := r.Repository.Worktree()
6767
require.NoError(t, err)
6868
// Annotated tags have their own hash, different from the commit hash, so the tag must be resolved before checkout
69-
resolvedTag, err := r.Repository.ResolveRevision(plumbing.Revision(tag.Hash().String()))
69+
resolvedTag, err := gitutils.ResolveTag(tag, r.Repository)
7070
require.NoError(t, err)
7171
err = repoTree.Checkout(&git.CheckoutOptions{Hash: *resolvedTag, Force: true})
7272
require.NoError(t, err)

libraries/gitutils/gitutils.go

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// This file is part of libraries-repository-engine.
2+
//
3+
// Copyright 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
//
18+
// You can be released from the requirements of the above licenses by purchasing
19+
// a commercial license. Buying such a license is mandatory if you want to
20+
// modify or otherwise use the software for commercial activities involving the
21+
// Arduino software without disclosing the source code of your own applications.
22+
// To purchase a commercial license, send an email to [email protected].
23+
24+
package gitutils
25+
26+
import (
27+
"sort"
28+
29+
"github.com/go-git/go-git/v5"
30+
"github.com/go-git/go-git/v5/plumbing"
31+
"github.com/go-git/go-git/v5/plumbing/object"
32+
)
33+
34+
// ResolveTag returns the commit hash associated with a tag.
35+
func ResolveTag(tag *plumbing.Reference, repository *git.Repository) (*plumbing.Hash, error) {
36+
// Annotated tags have their own hash, different from the commit hash, so the tag must be resolved to get the has for
37+
// the associated commit.
38+
// Tags may point to any Git object. Although not common, this can include tree and blob objects in addition to commits.
39+
// Resolving non-commit objects results in an error.
40+
return repository.ResolveRevision(plumbing.Revision(tag.Hash().String()))
41+
}
42+
43+
// SortedCommitTags returns the repository's commit object tags sorted by their chronological order in the current branch's history.
44+
// Tags for commits not in the branch's history are returned in lexicographical order relative to their adjacent tags.
45+
func SortedCommitTags(repository *git.Repository) ([]*plumbing.Reference, error) {
46+
/*
47+
Given a repository tag structure like so (I've omitted 1.0.3-1.0.9 as irrelevant):
48+
49+
* HEAD -> main, tag: 1.0.11
50+
* tag: 1.0.10
51+
* tag: 1.0.2
52+
| * tag: 1.0.2-rc2, development-branch
53+
| * tag: 1.0.2-rc1
54+
|/
55+
* tag: 1.0.1
56+
* tag: 1.0.0
57+
58+
The raw tags order is lexicographical:
59+
1.0.0
60+
1.0.1
61+
1.0.10
62+
1.0.2
63+
1.0.2-rc1
64+
1.0.2-rc2
65+
66+
This order is not meaningful. More meaningful would be to order the tags according to the chronology of the
67+
branch's commit history:
68+
1.0.0
69+
1.0.1
70+
1.0.2
71+
1.0.10
72+
73+
This leaves the question of how to handle tags from other branches, which is likely why a sensible sorting
74+
capability was not provided. However, even if the sorting of those tags is not optimal, a meaningful sort of the
75+
current branch's tags will be a significant improvement over the default behavior.
76+
*/
77+
78+
headRef, err := repository.Head()
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
headCommit, err := repository.CommitObject(headRef.Hash())
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
commits := object.NewCommitIterCTime(headCommit, nil, nil) // Iterator for the head commit and parents in reverse chronological commit time order.
89+
commitMap := make(map[plumbing.Hash]int) // commitMap associates each hash with its chronological position in the branch history.
90+
var commitIndex int
91+
for { // Iterate over all commits.
92+
commit, err := commits.Next()
93+
if err != nil {
94+
// Reached end of commits
95+
break
96+
}
97+
commitMap[commit.Hash] = commitIndex
98+
99+
commitIndex-- // Decrement to reflect reverse chronological order.
100+
}
101+
102+
tags, err := repository.Tags() // Get an iterator of the refs of the repository's tags. These are returned in a useless lexicographical order (e.g, 1.0.10 < 1.0.2), so it's necessary to cross-reference them against the commits, which are in a meaningful order.
103+
104+
type tagDataType struct {
105+
tag *plumbing.Reference
106+
position int
107+
}
108+
var tagData []tagDataType
109+
associatedCommitIndex := commitIndex // Initialize to index of oldest commit in case the first tags aren't in the branch.
110+
var tagIndex int
111+
for { // Iterate over all tag refs.
112+
tag, err := tags.Next()
113+
if err != nil {
114+
// Reached end of tags
115+
break
116+
}
117+
118+
// Annotated tags have their own hash, different from the commit hash, so tags must be resolved before
119+
// cross-referencing against the commit hashes.
120+
resolvedTag, err := ResolveTag(tag, repository)
121+
if err != nil {
122+
// Non-commit object tags are not included in the sorted list.
123+
continue
124+
}
125+
126+
commitIndex, ok := commitMap[*resolvedTag]
127+
if ok {
128+
// There is a commit in the branch associated with the tag.
129+
associatedCommitIndex = commitIndex
130+
}
131+
132+
tagData = append(
133+
tagData,
134+
tagDataType{
135+
tag: tag,
136+
position: associatedCommitIndex*10000 + tagIndex, // Leave intervals between positions to allow the insertion of unassociated tags in the existing lexicographical order relative to the last associated tag.
137+
},
138+
)
139+
140+
tagIndex++
141+
}
142+
143+
// Sort the tags according to the branch's history where possible.
144+
sort.SliceStable(
145+
tagData,
146+
// "less" function
147+
func(thisIndex, otherIndex int) bool {
148+
return tagData[thisIndex].position < tagData[otherIndex].position
149+
},
150+
)
151+
152+
var sortedTags []*plumbing.Reference
153+
for _, tagDatum := range tagData {
154+
sortedTags = append(sortedTags, tagDatum.tag)
155+
}
156+
157+
return sortedTags, nil
158+
}

0 commit comments

Comments
 (0)