Skip to content

Commit 46b1a65

Browse files
committed
Move tag checkout code to dedicated function
The code for checking out tags was duplicated. Already fairly complex, it turns out to need even more code to work reliably. For this reason, it will be beneficial to move it to a function in the gitutils package to avoid code duplication and facilitate testing.
1 parent 74efdf6 commit 46b1a65

File tree

4 files changed

+92
-35
lines changed

4 files changed

+92
-35
lines changed

internal/libraries/git_integration_test.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131

3232
"arduino.cc/repository/internal/libraries/db"
3333
"arduino.cc/repository/internal/libraries/gitutils"
34-
"github.com/go-git/go-git/v5"
3534
"github.com/stretchr/testify/require"
3635
)
3736

@@ -63,15 +62,7 @@ func TestUpdateLibraryJson(t *testing.T) {
6362
tag, err := tags.Next()
6463
require.NoError(t, err)
6564

66-
repoTree, err := r.Repository.Worktree()
67-
require.NoError(t, err)
68-
// Annotated tags have their own hash, different from the commit hash, so the tag must be resolved before checkout
69-
resolvedTag, err := gitutils.ResolveTag(tag, r.Repository)
70-
require.NoError(t, err)
71-
err = repoTree.Checkout(&git.CheckoutOptions{Hash: *resolvedTag, Force: true})
72-
require.NoError(t, err)
73-
err = repoTree.Clean(&git.CleanOptions{Dir: true})
74-
require.NoError(t, err)
65+
err = gitutils.CheckoutTag(r.Repository, tag)
7566

7667
library, err := GenerateLibraryFromRepo(r)
7768
require.NoError(t, err)

internal/libraries/gitutils/gitutils.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ import (
3131
"github.com/go-git/go-git/v5/plumbing/object"
3232
)
3333

34-
// ResolveTag returns the commit hash associated with a tag.
35-
func ResolveTag(tag *plumbing.Reference, repository *git.Repository) (*plumbing.Hash, error) {
34+
// resolveTag returns the commit hash associated with a tag.
35+
func resolveTag(tag *plumbing.Reference, repository *git.Repository) (*plumbing.Hash, error) {
3636
// Annotated tags have their own hash, different from the commit hash, so the tag must be resolved to get the has for
3737
// the associated commit.
3838
// Tags may point to any Git object. Although not common, this can include tree and blob objects in addition to commits.
@@ -117,7 +117,7 @@ func SortedCommitTags(repository *git.Repository) ([]*plumbing.Reference, error)
117117

118118
// Annotated tags have their own hash, different from the commit hash, so tags must be resolved before
119119
// cross-referencing against the commit hashes.
120-
resolvedTag, err := ResolveTag(tag, repository)
120+
resolvedTag, err := resolveTag(tag, repository)
121121
if err != nil {
122122
// Non-commit object tags are not included in the sorted list.
123123
continue
@@ -156,3 +156,27 @@ func SortedCommitTags(repository *git.Repository) ([]*plumbing.Reference, error)
156156

157157
return sortedTags, nil
158158
}
159+
160+
// CheckoutTag checks out the repository to the given tag.
161+
func CheckoutTag(repository *git.Repository, tag *plumbing.Reference) error {
162+
repoTree, err := repository.Worktree()
163+
if err != nil {
164+
return err
165+
}
166+
167+
// Annotated tags have their own hash, different from the commit hash, so the tag must be resolved before checkout
168+
resolvedTag, err := resolveTag(tag, repository)
169+
if err != nil {
170+
return err
171+
}
172+
173+
if err = repoTree.Checkout(&git.CheckoutOptions{Hash: *resolvedTag, Force: true}); err != nil {
174+
return err
175+
}
176+
177+
if err = repoTree.Clean(&git.CleanOptions{Dir: true}); err != nil {
178+
return err
179+
}
180+
181+
return nil
182+
}

internal/libraries/gitutils/gitutils_test.go

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func TestResolveTag(t *testing.T) {
8484
} {
8585
testName := fmt.Sprintf("%s, %s", testTable.objectTypeName, annotationConfig.descriptor)
8686
tag := makeTag(t, repository, testName, testTable.objectHash, annotationConfig.annotated)
87-
resolvedTag, err := ResolveTag(tag, repository)
87+
resolvedTag, err := resolveTag(tag, repository)
8888
testTable.errorAssertion(t, err, fmt.Sprintf("%s tag resolution error", testName))
8989
if err == nil {
9090
assert.Equal(t, testTable.objectHash, *resolvedTag, fmt.Sprintf("%s tag resolution", testName))
@@ -139,9 +139,69 @@ func TestSortedCommitTags(t *testing.T) {
139139
assert.Equal(t, tags, sorted)
140140
}
141141

142+
func TestCheckoutTag(t *testing.T) {
143+
// Create a folder for the test repository.
144+
repositoryPath, err := paths.TempDir().MkTempDir("gitutils-TestCheckoutTag-repo")
145+
require.NoError(t, err)
146+
147+
// Create test repository.
148+
repository, err := git.PlainInit(repositoryPath.String(), false)
149+
require.NoError(t, err)
150+
151+
// Generate meaningless commit history, creating some tags along the way.
152+
var tags []*plumbing.Reference
153+
tags = append(tags, makeTag(t, repository, "1.0.0", makeCommit(t, repository, repositoryPath), true))
154+
makeCommit(t, repository, repositoryPath)
155+
makeCommit(t, repository, repositoryPath)
156+
tags = append(tags, makeTag(t, repository, "1.0.1", makeCommit(t, repository, repositoryPath), true))
157+
makeCommit(t, repository, repositoryPath)
158+
makeTag(t, repository, "tree-tag", getTreeHash(t, repository), true)
159+
tags = append(tags, makeTag(t, repository, "1.0.2", makeCommit(t, repository, repositoryPath), true))
160+
makeTag(t, repository, "blob-tag", getBlobHash(t, repository), true)
161+
trackedFilePath, _ := commitFile(t, repository, repositoryPath)
162+
163+
for _, tag := range tags {
164+
// Put the repository into a dirty state.
165+
// Add an untracked file.
166+
_, err = paths.WriteToTempFile([]byte{}, repositoryPath, "gitutils-TestCheckoutTag-tempfile")
167+
require.NoError(t, err)
168+
// Modify a tracked file.
169+
err = trackedFilePath.WriteFile([]byte{42})
170+
require.NoError(t, err)
171+
// Create empty folder.
172+
emptyFolderPath, err := repositoryPath.MkTempDir("gitutils-TestCheckoutTag-emptyFolder")
173+
require.NoError(t, err)
174+
175+
err = CheckoutTag(repository, tag)
176+
assert.NoError(t, err, fmt.Sprintf("Checking out tag %s", tag))
177+
178+
expectedHash, err := resolveTag(tag, repository)
179+
require.NoError(t, err)
180+
headRef, err := repository.Head()
181+
require.NoError(t, err)
182+
assert.Equal(t, *expectedHash, headRef.Hash(), "HEAD is at tag")
183+
184+
// Check if cleanup was successful.
185+
tree, err := repository.Worktree()
186+
require.NoError(t, err)
187+
status, err := tree.Status()
188+
require.NoError(t, err)
189+
assert.True(t, status.IsClean(), "Repository is clean")
190+
emptyFolderExists, err := emptyFolderPath.ExistCheck()
191+
require.NoError(t, err)
192+
assert.False(t, emptyFolderExists, "Empty folder was removed")
193+
}
194+
}
195+
142196
// makeCommit creates a test commit in the given repository and returns its plumbing.Hash object.
143197
func makeCommit(t *testing.T, repository *git.Repository, repositoryPath *paths.Path) plumbing.Hash {
144-
_, err := paths.WriteToTempFile([]byte{}, repositoryPath, "gitutils-makeCommit-tempfile")
198+
_, hash := commitFile(t, repository, repositoryPath)
199+
return hash
200+
}
201+
202+
// commitFile commits a file in the given repository and returns its path and the commit's plumbing.Hash object.
203+
func commitFile(t *testing.T, repository *git.Repository, repositoryPath *paths.Path) (*paths.Path, plumbing.Hash) {
204+
filePath, err := paths.WriteToTempFile([]byte{}, repositoryPath, "gitutils-makeCommit-tempfile")
145205
require.Nil(t, err)
146206

147207
worktree, err := repository.Worktree()
@@ -163,7 +223,7 @@ func makeCommit(t *testing.T, repository *git.Repository, repositoryPath *paths.
163223
)
164224
require.Nil(t, err)
165225

166-
return commit
226+
return filePath, commit
167227
}
168228

169229
// getTreeHash returns the plumbing.Hash object for an arbitrary Git tree object.

sync_libraries.go

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import (
3737
"arduino.cc/repository/internal/libraries/gitutils"
3838
"arduino.cc/repository/internal/libraries/hash"
3939
cc "github.com/arduino/golang-concurrent-workers"
40-
"github.com/go-git/go-git/v5"
4140
"github.com/go-git/go-git/v5/plumbing"
4241
)
4342

@@ -249,27 +248,10 @@ func syncLibraryTaggedRelease(logger *log.Logger, repo *libraries.Repository, ta
249248

250249
// Checkout desired tag
251250
logger.Printf("Checking out tag: %s", tag.Name().Short())
252-
253-
repoTree, err := repo.Repository.Worktree()
254-
if err != nil {
255-
return err
256-
}
257-
258-
// Annotated tags have their own hash, different from the commit hash, so the tag must be resolved before checkout
259-
resolvedTag, err := gitutils.ResolveTag(tag, repo.Repository)
260-
if err != nil {
261-
// All unresolvable tags were already removed by gitutils.SortedCommitTags(), so there will never be an error under normal conditions.
262-
panic(err)
263-
}
264-
265-
if err = repoTree.Checkout(&git.CheckoutOptions{Hash: *resolvedTag, Force: true}); err != nil {
251+
if err := gitutils.CheckoutTag(repo.Repository, tag); err != nil {
266252
return fmt.Errorf("Error checking out repo: %s", err)
267253
}
268254

269-
if err = repoTree.Clean(&git.CleanOptions{Dir: true}); err != nil {
270-
return fmt.Errorf("Error cleaning repo: %s", err)
271-
}
272-
273255
// Create library metadata from library.properties
274256
library, err := libraries.GenerateLibraryFromRepo(repo)
275257
if err != nil {

0 commit comments

Comments
 (0)