mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 02:46:04 +01:00 
			
		
		
		
	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>
This commit is contained in:
		| @@ -242,7 +242,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte { | |||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
|  |  | ||||||
| // ParseTreeLine reads an entry from a tree in a cat-file --batch stream | // ParseCatFileTreeLine reads an entry from a tree in a cat-file --batch stream | ||||||
| // This carefully avoids allocations - except where fnameBuf is too small. | // This carefully avoids allocations - except where fnameBuf is too small. | ||||||
| // It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations | // It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations | ||||||
| // | // | ||||||
| @@ -250,7 +250,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte { | |||||||
| // <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH> | // <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH> | ||||||
| // | // | ||||||
| // We don't attempt to convert the raw HASH to save a lot of time | // We don't attempt to convert the raw HASH to save a lot of time | ||||||
| func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { | func ParseCatFileTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { | ||||||
| 	var readBytes []byte | 	var readBytes []byte | ||||||
|  |  | ||||||
| 	// Read the Mode & fname | 	// Read the Mode & fname | ||||||
| @@ -260,7 +260,7 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu | |||||||
| 	} | 	} | ||||||
| 	idx := bytes.IndexByte(readBytes, ' ') | 	idx := bytes.IndexByte(readBytes, ' ') | ||||||
| 	if idx < 0 { | 	if idx < 0 { | ||||||
| 		log.Debug("missing space in readBytes ParseTreeLine: %s", readBytes) | 		log.Debug("missing space in readBytes ParseCatFileTreeLine: %s", readBytes) | ||||||
| 		return mode, fname, sha, n, &ErrNotExist{} | 		return mode, fname, sha, n, &ErrNotExist{} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										78
									
								
								modules/git/parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								modules/git/parse.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
| @@ -10,8 +10,6 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| ) | ) | ||||||
| @@ -21,71 +19,30 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { | |||||||
| 	return parseTreeEntries(data, nil) | 	return parseTreeEntries(data, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
| var sepSpace = []byte{' '} | // parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory | ||||||
|  |  | ||||||
| func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { | func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { | ||||||
| 	var err error |  | ||||||
| 	entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) | 	entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) | ||||||
| 	for pos := 0; pos < len(data); { | 	for pos := 0; pos < len(data); { | ||||||
| 		// expect line to be of the form: |  | ||||||
| 		// <mode> <type> <sha> <space-padded-size>\t<filename> |  | ||||||
| 		// <mode> <type> <sha>\t<filename> |  | ||||||
| 		posEnd := bytes.IndexByte(data[pos:], '\n') | 		posEnd := bytes.IndexByte(data[pos:], '\n') | ||||||
| 		if posEnd == -1 { | 		if posEnd == -1 { | ||||||
| 			posEnd = len(data) | 			posEnd = len(data) | ||||||
| 		} else { | 		} else { | ||||||
| 			posEnd += pos | 			posEnd += pos | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		line := data[pos:posEnd] | 		line := data[pos:posEnd] | ||||||
| 		posTab := bytes.IndexByte(line, '\t') | 		lsTreeLine, err := parseLsTreeLine(line) | ||||||
| 		if posTab == -1 { |  | ||||||
| 			return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		entry := new(TreeEntry) |  | ||||||
| 		entry.ptree = ptree |  | ||||||
|  |  | ||||||
| 		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 |  | ||||||
| 			entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64) |  | ||||||
| 			entry.sized = true |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		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 { | 		if err != nil { | ||||||
| 			return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err) | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | 		entry := &TreeEntry{ | ||||||
| 		if len(entryName) > 0 && entryName[0] == '"' { | 			ptree:     ptree, | ||||||
| 			entry.name, err = strconv.Unquote(string(entryName)) | 			ID:        lsTreeLine.ID, | ||||||
| 			if err != nil { | 			entryMode: lsTreeLine.EntryMode, | ||||||
| 				return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err) | 			name:      lsTreeLine.Name, | ||||||
|  | 			size:      lsTreeLine.Size.Value(), | ||||||
|  | 			sized:     lsTreeLine.Size.Has(), | ||||||
| 		} | 		} | ||||||
| 		} else { |  | ||||||
| 			entry.name = string(entryName) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		pos = posEnd + 1 | 		pos = posEnd + 1 | ||||||
| 		entries = append(entries, entry) | 		entries = append(entries, entry) | ||||||
| 	} | 	} | ||||||
| @@ -100,7 +57,7 @@ func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio. | |||||||
|  |  | ||||||
| loop: | loop: | ||||||
| 	for sz > 0 { | 	for sz > 0 { | ||||||
| 		mode, fname, sha, count, err := ParseTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf) | 		mode, fname, sha, count, err := ParseCatFileTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			if err == io.EOF { | 			if err == io.EOF { | ||||||
| 				break loop | 				break loop | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err | |||||||
| 			case "tree": | 			case "tree": | ||||||
| 				var n int64 | 				var n int64 | ||||||
| 				for n < size { | 				for n < size { | ||||||
| 					mode, fname, binObjectID, count, err := git.ParseTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf) | 					mode, fname, binObjectID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return nil, err | 						return nil, err | ||||||
| 					} | 					} | ||||||
|   | |||||||
							
								
								
									
										66
									
								
								modules/git/submodule.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								modules/git/submodule.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								modules/git/submodule_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								modules/git/submodule_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								modules/git/tests/repos/repo4_submodules/HEAD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								modules/git/tests/repos/repo4_submodules/HEAD
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ref: refs/heads/master | ||||||
							
								
								
									
										4
									
								
								modules/git/tests/repos/repo4_submodules/config
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								modules/git/tests/repos/repo4_submodules/config
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | [core] | ||||||
|  | 	repositoryformatversion = 0 | ||||||
|  | 	filemode = true | ||||||
|  | 	bare = true | ||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | x<01><>[ | ||||||
|  | <EFBFBD>0E<><45>*<2A>_<EFBFBD><5F>$M<10>5tifBk Iŕ<49>7<>k~<7E><>9ܘ<39><DC98>ܠ<1B><><11>.j<><6A>	<09>O<><0C><>"z<>`<60>#I<>irF<72><46><EFBFBD><CDB9>$%<25><><18><>|4)<29><>?t<><74>=<3D><1D>:K<><4B><EFBFBD>#[$D<15><><1F><>^<5E><><EFBFBD><EFBFBD><EFBFBD>Ӓy<D392>HU/<2F>f?G | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | e1e59caba97193d48862d6809912043871f37437 | ||||||
| @@ -17,7 +17,7 @@ func NewTree(repo *Repository, id ObjectID) *Tree { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // SubTree get a sub tree by the sub dir path | // SubTree get a subtree by the sub dir path | ||||||
| func (t *Tree) SubTree(rpath string) (*Tree, error) { | func (t *Tree) SubTree(rpath string) (*Tree, error) { | ||||||
| 	if len(rpath) == 0 { | 	if len(rpath) == 0 { | ||||||
| 		return t, nil | 		return t, nil | ||||||
| @@ -63,7 +63,7 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error | |||||||
| 	return filelist, err | 	return filelist, err | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetTreePathLatestCommitID returns the latest commit of a tree path | // GetTreePathLatestCommit returns the latest commit of a tree path | ||||||
| func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { | func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { | ||||||
| 	stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1"). | 	stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1"). | ||||||
| 		AddDynamicArguments(refName).AddDashesAndList(treePath). | 		AddDynamicArguments(refName).AddDashesAndList(treePath). | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { | |||||||
| 			ptree:     t, | 			ptree:     t, | ||||||
| 			ID:        t.ID, | 			ID:        t.ID, | ||||||
| 			name:      "", | 			name:      "", | ||||||
| 			fullName:  "", |  | ||||||
| 			entryMode: EntryModeTree, | 			entryMode: EntryModeTree, | ||||||
| 		}, nil | 		}, nil | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -10,22 +10,16 @@ import "code.gitea.io/gitea/modules/log" | |||||||
| // TreeEntry the leaf in the git tree | // TreeEntry the leaf in the git tree | ||||||
| type TreeEntry struct { | type TreeEntry struct { | ||||||
| 	ID    ObjectID | 	ID    ObjectID | ||||||
|  |  | ||||||
| 	ptree *Tree | 	ptree *Tree | ||||||
|  |  | ||||||
| 	entryMode EntryMode | 	entryMode EntryMode | ||||||
| 	name      string | 	name      string | ||||||
|  |  | ||||||
| 	size      int64 | 	size      int64 | ||||||
| 	sized     bool | 	sized     bool | ||||||
| 	fullName string |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Name returns the name of the entry | // Name returns the name of the entry | ||||||
| func (te *TreeEntry) Name() string { | func (te *TreeEntry) Name() string { | ||||||
| 	if te.fullName != "" { |  | ||||||
| 		return te.fullName |  | ||||||
| 	} |  | ||||||
| 	return te.name | 	return te.name | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| @@ -123,7 +122,7 @@ func (gt *GiteaTemplate) Globs() []glob.Glob { | |||||||
| 	return gt.globs | 	return gt.globs | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { | func readGiteaTemplateFile(tmpDir string) (*GiteaTemplate, error) { | ||||||
| 	gtPath := filepath.Join(tmpDir, ".gitea", "template") | 	gtPath := filepath.Join(tmpDir, ".gitea", "template") | ||||||
| 	if _, err := os.Stat(gtPath); os.IsNotExist(err) { | 	if _, err := os.Stat(gtPath); os.IsNotExist(err) { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| @@ -136,12 +135,55 @@ func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	gt := &GiteaTemplate{ | 	return &GiteaTemplate{Path: gtPath, Content: content}, nil | ||||||
| 		Path:    gtPath, | } | ||||||
| 		Content: content, |  | ||||||
|  | func processGiteaTemplateFile(tmpDir string, templateRepo, generateRepo *repo_model.Repository, giteaTemplateFile *GiteaTemplate) error { | ||||||
|  | 	if err := util.Remove(giteaTemplateFile.Path); err != nil { | ||||||
|  | 		return fmt.Errorf("remove .giteatemplate: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if len(giteaTemplateFile.Globs()) == 0 { | ||||||
|  | 		return nil // Avoid walking tree if there are no globs | ||||||
|  | 	} | ||||||
|  | 	tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" | ||||||
|  | 	return filepath.WalkDir(tmpDirSlash, func(path string, d os.DirEntry, walkErr error) error { | ||||||
|  | 		if walkErr != nil { | ||||||
|  | 			return walkErr | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	return gt, nil | 		if d.IsDir() { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) | ||||||
|  | 		for _, g := range giteaTemplateFile.Globs() { | ||||||
|  | 			if g.Match(base) { | ||||||
|  | 				content, err := os.ReadFile(path) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				generatedContent := []byte(generateExpansion(string(content), templateRepo, generateRepo, false)) | ||||||
|  | 				if err := os.WriteFile(path, generatedContent, 0o644); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, generateExpansion(base, templateRepo, generateRepo, true))) | ||||||
|  |  | ||||||
|  | 				// Create parent subdirectories if needed or continue silently if it exists | ||||||
|  | 				if err = os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Substitute filename variables | ||||||
|  | 				if err = os.Rename(path, substPath); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) // end: WalkDir | ||||||
| } | } | ||||||
|  |  | ||||||
| func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository, tmpDir string) error { | func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository, tmpDir string) error { | ||||||
| @@ -167,81 +209,43 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r | |||||||
| 		return fmt.Errorf("git clone: %w", err) | 		return fmt.Errorf("git clone: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := util.RemoveAll(path.Join(tmpDir, ".git")); err != nil { | 	// Get active submodules from the template | ||||||
|  | 	submodules, err := git.GetTemplateSubmoduleCommits(ctx, tmpDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("GetTemplateSubmoduleCommits: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = util.RemoveAll(filepath.Join(tmpDir, ".git")); err != nil { | ||||||
| 		return fmt.Errorf("remove git dir: %w", err) | 		return fmt.Errorf("remove git dir: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Variable expansion | 	// Variable expansion | ||||||
| 	gt, err := checkGiteaTemplate(tmpDir) | 	giteaTemplateFile, err := readGiteaTemplateFile(tmpDir) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("checkGiteaTemplate: %w", err) | 		return fmt.Errorf("readGiteaTemplateFile: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if gt != nil { | 	if giteaTemplateFile != nil { | ||||||
| 		if err := util.Remove(gt.Path); err != nil { | 		err = processGiteaTemplateFile(tmpDir, templateRepo, generateRepo, giteaTemplateFile) | ||||||
| 			return fmt.Errorf("remove .giteatemplate: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Avoid walking tree if there are no globs |  | ||||||
| 		if len(gt.Globs()) > 0 { |  | ||||||
| 			tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" |  | ||||||
| 			if err := filepath.WalkDir(tmpDirSlash, func(path string, d os.DirEntry, walkErr error) error { |  | ||||||
| 				if walkErr != nil { |  | ||||||
| 					return walkErr |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				if d.IsDir() { |  | ||||||
| 					return nil |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) |  | ||||||
| 				for _, g := range gt.Globs() { |  | ||||||
| 					if g.Match(base) { |  | ||||||
| 						content, err := os.ReadFile(path) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 						if err := os.WriteFile(path, | 	if err = git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil { | ||||||
| 							[]byte(generateExpansion(string(content), templateRepo, generateRepo, false)), |  | ||||||
| 							0o644); err != nil { |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 						substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, | 	if stdout, _, err := git.NewCommand(ctx, "remote", "add", "origin").AddDynamicArguments(repo.RepoPath()). | ||||||
| 							generateExpansion(base, templateRepo, generateRepo, true))) |  | ||||||
|  |  | ||||||
| 						// Create parent subdirectories if needed or continue silently if it exists |  | ||||||
| 						if err := os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil { |  | ||||||
| 							return err |  | ||||||
| 						} |  | ||||||
|  |  | ||||||
| 						// Substitute filename variables |  | ||||||
| 						if err := os.Rename(path, substPath); err != nil { |  | ||||||
| 							return err |  | ||||||
| 						} |  | ||||||
|  |  | ||||||
| 						break |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 				return nil |  | ||||||
| 			}); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	repoPath := repo.RepoPath() |  | ||||||
| 	if stdout, _, err := git.NewCommand(ctx, "remote", "add", "origin").AddDynamicArguments(repoPath). |  | ||||||
| 		RunStdString(&git.RunOpts{Dir: tmpDir, Env: env}); err != nil { | 		RunStdString(&git.RunOpts{Dir: tmpDir, Env: env}); err != nil { | ||||||
| 		log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) | 		log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) | ||||||
| 		return fmt.Errorf("git remote add: %w", err) | 		return fmt.Errorf("git remote add: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if err = git.AddTemplateSubmoduleIndexes(ctx, tmpDir, submodules); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to add submodules: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// set default branch based on whether it's specified in the newly generated repo or not | 	// set default branch based on whether it's specified in the newly generated repo or not | ||||||
| 	defaultBranch := repo.DefaultBranch | 	defaultBranch := repo.DefaultBranch | ||||||
| 	if strings.TrimSpace(defaultBranch) == "" { | 	if strings.TrimSpace(defaultBranch) == "" { | ||||||
|   | |||||||
| @@ -19,9 +19,9 @@ | |||||||
| 					{{svg "octicon-file-submodule"}} | 					{{svg "octicon-file-submodule"}} | ||||||
| 					{{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} | 					{{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} | ||||||
| 					{{if $refURL}} | 					{{if $refURL}} | ||||||
| 						<a class="muted" href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a href="{{$refURL}}/commit/{{PathEscape $subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a> | 						<a class="muted" href="{{$refURL}}">{{$entry.Name}}</a> <span class="at">@</span> <a href="{{$refURL}}/commit/{{PathEscape $subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a> | ||||||
| 					{{else}} | 					{{else}} | ||||||
| 						{{$entry.Name}}<span class="at">@</span>{{ShortSha $subModuleFile.RefID}} | 						{{$entry.Name}} <span class="at">@</span> {{ShortSha $subModuleFile.RefID}} | ||||||
| 					{{end}} | 					{{end}} | ||||||
| 				{{else}} | 				{{else}} | ||||||
| 					{{if $entry.IsDir}} | 					{{if $entry.IsDir}} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user