enhance: download each action repository once per job (#1178)

A job downloads each action repository once, keyed on the clone URL and ref, so repeated `uses:` and different paths of one repository share a checkout. The download is reported once as `{org}/{repo}@{ref}`, the way actions/runner reports it.

The action itself is still read per step, because a repository without an action file gets a synthetic action built from that step's `with.args`.

Fixes https://gitea.com/gitea/runner/issues/1159

---------

Co-authored-by: silverwind <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1178
Reviewed-by: bircni <bircni@icloud.com>
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
Lunny Xiao
2026-08-22 17:01:39 +00:00
committed by silverwind
parent 2fa5fe7121
commit c0a07cfb72
11 changed files with 241 additions and 146 deletions
+6 -2
View File
@@ -458,10 +458,14 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
logger.Debugf("Unable to pull %s: %v", refName, err)
}
case isOfflineMode && reused:
reusedMsg = " (reused in offline mode)"
reusedMsg = " (offline mode)"
}
logger.Debugf("Cloned %s to %s%s", input.URL, input.Dir, reusedMsg)
if reused {
logger.Debugf("Reused %s at %s%s", input.URL, input.Dir, reusedMsg)
} else {
logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
}
if hash.String() != input.Ref && refType == "branch" {
logger.Debugf("Provided ref is not a sha. Updating branch ref after pull")
+16 -2
View File
@@ -373,22 +373,28 @@ func TestGitCloneExecutorOfflineMode(t *testing.T) {
// Prime the cache with an online clone of main.
cacheDir := t.TempDir()
logger, hook := logrustest.NewNullLogger()
logger.SetLevel(log.DebugLevel)
ctx := common.WithLogger(context.Background(), logger.WithField("job", "j1"))
require.NoError(t, NewGitCloneExecutor(NewGitCloneExecutorInput{
URL: remoteDir,
Ref: "main",
Dir: cacheDir,
})(context.Background()))
})(ctx))
assert.Contains(t, logMessages(hook), "Cloned "+remoteDir+" to "+cacheDir)
t.Run("cached branch resolves without fetching", func(t *testing.T) {
// Offline reuse of a cached branch must succeed even though ResolveRevision(input.Ref)
// finds no local refs/heads/<ref>.
hook.Reset()
err := NewGitCloneExecutor(NewGitCloneExecutorInput{
URL: remoteDir,
Ref: "main",
Dir: cacheDir,
OfflineMode: true,
})(context.Background())
})(ctx)
require.NoError(t, err)
assert.Contains(t, logMessages(hook), "Reused "+remoteDir+" at "+cacheDir+" (offline mode)")
out, err := exec.Command("git", "-C", cacheDir, "log", "--oneline", "-1", "--format=%s").Output()
require.NoError(t, err)
@@ -445,6 +451,14 @@ func TestGitCloneExecutorQuietDemotesCloneLine(t *testing.T) {
}
}
func logMessages(hook *logrustest.Hook) []string {
messages := []string{}
for _, entry := range hook.AllEntries() {
messages = append(messages, entry.Message)
}
return messages
}
func TestGitCloneExecutorShallow(t *testing.T) {
// Build a local "remote" with several commits on main plus a tag, so a full clone would pull noticeably more history than a shallow one.
remoteDir := t.TempDir()