mirror of https://github.com/go-gitea/gitea
Inherit submodules from template repository content (#16237)
Fix #10316 --------- Signed-off-by: Steffen Schröter <steffen@vexar.de> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>pull/33070/head
parent
92a2900a2d
commit
57eb9d0b64
@ -0,0 +1,78 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"code.gitea.io/gitea/modules/optional" |
||||
) |
||||
|
||||
var sepSpace = []byte{' '} |
||||
|
||||
type LsTreeEntry struct { |
||||
ID ObjectID |
||||
EntryMode EntryMode |
||||
Name string |
||||
Size optional.Option[int64] |
||||
} |
||||
|
||||
func parseLsTreeLine(line []byte) (*LsTreeEntry, error) { |
||||
// expect line to be of the form:
|
||||
// <mode> <type> <sha> <space-padded-size>\t<filename>
|
||||
// <mode> <type> <sha>\t<filename>
|
||||
|
||||
var err error |
||||
posTab := bytes.IndexByte(line, '\t') |
||||
if posTab == -1 { |
||||
return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line) |
||||
} |
||||
|
||||
entry := new(LsTreeEntry) |
||||
|
||||
entryAttrs := line[:posTab] |
||||
entryName := line[posTab+1:] |
||||
|
||||
entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace) |
||||
_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
|
||||
entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace) |
||||
if len(entryAttrs) > 0 { |
||||
entrySize := entryAttrs // the last field is the space-padded-size
|
||||
size, _ := strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64) |
||||
entry.Size = optional.Some(size) |
||||
} |
||||
|
||||
switch string(entryMode) { |
||||
case "100644": |
||||
entry.EntryMode = EntryModeBlob |
||||
case "100755": |
||||
entry.EntryMode = EntryModeExec |
||||
case "120000": |
||||
entry.EntryMode = EntryModeSymlink |
||||
case "160000": |
||||
entry.EntryMode = EntryModeCommit |
||||
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
|
||||
entry.EntryMode = EntryModeTree |
||||
default: |
||||
return nil, fmt.Errorf("unknown type: %v", string(entryMode)) |
||||
} |
||||
|
||||
entry.ID, err = NewIDFromString(string(entryObjectID)) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err) |
||||
} |
||||
|
||||
if len(entryName) > 0 && entryName[0] == '"' { |
||||
entry.Name, err = strconv.Unquote(string(entryName)) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err) |
||||
} |
||||
} else { |
||||
entry.Name = string(entryName) |
||||
} |
||||
return entry, nil |
||||
} |
@ -0,0 +1,66 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"bufio" |
||||
"context" |
||||
"fmt" |
||||
"os" |
||||
|
||||
"code.gitea.io/gitea/modules/log" |
||||
) |
||||
|
||||
type TemplateSubmoduleCommit struct { |
||||
Path string |
||||
Commit string |
||||
} |
||||
|
||||
// GetTemplateSubmoduleCommits returns a list of submodules paths and their commits from a repository
|
||||
// This function is only for generating new repos based on existing template, the template couldn't be too large.
|
||||
func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submoduleCommits []TemplateSubmoduleCommit, _ error) { |
||||
stdoutReader, stdoutWriter, err := os.Pipe() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
opts := &RunOpts{ |
||||
Dir: repoPath, |
||||
Stdout: stdoutWriter, |
||||
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { |
||||
_ = stdoutWriter.Close() |
||||
defer stdoutReader.Close() |
||||
|
||||
scanner := bufio.NewScanner(stdoutReader) |
||||
for scanner.Scan() { |
||||
entry, err := parseLsTreeLine(scanner.Bytes()) |
||||
if err != nil { |
||||
cancel() |
||||
return err |
||||
} |
||||
if entry.EntryMode == EntryModeCommit { |
||||
submoduleCommits = append(submoduleCommits, TemplateSubmoduleCommit{Path: entry.Name, Commit: entry.ID.String()}) |
||||
} |
||||
} |
||||
return scanner.Err() |
||||
}, |
||||
} |
||||
err = NewCommand(ctx, "ls-tree", "-r", "--", "HEAD").Run(opts) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err) |
||||
} |
||||
return submoduleCommits, nil |
||||
} |
||||
|
||||
// AddTemplateSubmoduleIndexes Adds the given submodules to the git index.
|
||||
// It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir.
|
||||
func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error { |
||||
for _, submodule := range submodules { |
||||
cmd := NewCommand(ctx, "update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path) |
||||
if stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}); err != nil { |
||||
log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err) |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,48 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"context" |
||||
"os" |
||||
"path/filepath" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestGetTemplateSubmoduleCommits(t *testing.T) { |
||||
testRepoPath := filepath.Join(testReposDir, "repo4_submodules") |
||||
submodules, err := GetTemplateSubmoduleCommits(DefaultContext, testRepoPath) |
||||
require.NoError(t, err) |
||||
|
||||
assert.Len(t, submodules, 2) |
||||
|
||||
assert.EqualValues(t, "<°)))><", submodules[0].Path) |
||||
assert.EqualValues(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit) |
||||
|
||||
assert.EqualValues(t, "libtest", submodules[1].Path) |
||||
assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[1].Commit) |
||||
} |
||||
|
||||
func TestAddTemplateSubmoduleIndexes(t *testing.T) { |
||||
ctx := context.Background() |
||||
tmpDir := t.TempDir() |
||||
var err error |
||||
_, _, err = NewCommand(ctx, "init").RunStdString(&RunOpts{Dir: tmpDir}) |
||||
require.NoError(t, err) |
||||
_ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755) |
||||
err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}}) |
||||
require.NoError(t, err) |
||||
_, _, err = NewCommand(ctx, "add", "--all").RunStdString(&RunOpts{Dir: tmpDir}) |
||||
require.NoError(t, err) |
||||
_, _, err = NewCommand(ctx, "-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(&RunOpts{Dir: tmpDir}) |
||||
require.NoError(t, err) |
||||
submodules, err := GetTemplateSubmoduleCommits(DefaultContext, tmpDir) |
||||
require.NoError(t, err) |
||||
assert.Len(t, submodules, 1) |
||||
assert.EqualValues(t, "new-dir", submodules[0].Path) |
||||
assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[0].Commit) |
||||
} |
@ -0,0 +1 @@ |
||||
ref: refs/heads/master |
@ -0,0 +1,4 @@ |
||||
[core] |
||||
repositoryformatversion = 0 |
||||
filemode = true |
||||
bare = true |
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
||||
e1e59caba97193d48862d6809912043871f37437 |
Loading…
Reference in new issue