2016-11-03 18:16:01 -04:00
|
|
|
// Copyright 2015 The Gogs Authors. All rights reserved.
|
2019-04-19 08:17:27 -04:00
|
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
2022-11-27 13:20:29 -05:00
|
|
|
// SPDX-License-Identifier: MIT
|
2016-11-03 18:16:01 -04:00
|
|
|
|
|
|
|
package git
|
|
|
|
|
2019-04-19 08:17:27 -04:00
|
|
|
import (
|
2019-10-11 20:13:27 -04:00
|
|
|
"bytes"
|
2024-08-12 11:16:55 -04:00
|
|
|
"io"
|
2019-05-11 11:29:17 -04:00
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2019-04-19 08:17:27 -04:00
|
|
|
)
|
|
|
|
|
2019-05-11 11:29:17 -04:00
|
|
|
// CommitTreeOpts represents the possible options to CommitTree
|
|
|
|
type CommitTreeOpts struct {
|
2019-10-16 09:42:42 -04:00
|
|
|
Parents []string
|
|
|
|
Message string
|
|
|
|
KeyID string
|
|
|
|
NoGPGSign bool
|
|
|
|
AlwaysSign bool
|
2019-05-11 11:29:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommitTree creates a commit from a given tree id for the user with provided message
|
2023-12-13 16:02:00 -05:00
|
|
|
func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opts CommitTreeOpts) (ObjectID, error) {
|
2019-07-31 15:19:47 -04:00
|
|
|
commitTimeStr := time.Now().Format(time.RFC3339)
|
2019-05-11 11:29:17 -04:00
|
|
|
|
|
|
|
// Because this may call hooks we should pass in the environment
|
|
|
|
env := append(os.Environ(),
|
2020-09-19 12:44:55 -04:00
|
|
|
"GIT_AUTHOR_NAME="+author.Name,
|
|
|
|
"GIT_AUTHOR_EMAIL="+author.Email,
|
2019-05-11 11:29:17 -04:00
|
|
|
"GIT_AUTHOR_DATE="+commitTimeStr,
|
2020-09-19 12:44:55 -04:00
|
|
|
"GIT_COMMITTER_NAME="+committer.Name,
|
|
|
|
"GIT_COMMITTER_EMAIL="+committer.Email,
|
2019-05-11 11:29:17 -04:00
|
|
|
"GIT_COMMITTER_DATE="+commitTimeStr,
|
|
|
|
)
|
2022-10-23 10:44:45 -04:00
|
|
|
cmd := NewCommand(repo.Ctx, "commit-tree").AddDynamicArguments(tree.ID.String())
|
2019-05-11 11:29:17 -04:00
|
|
|
|
|
|
|
for _, parent := range opts.Parents {
|
2022-10-23 10:44:45 -04:00
|
|
|
cmd.AddArguments("-p").AddDynamicArguments(parent)
|
2019-05-11 11:29:17 -04:00
|
|
|
}
|
|
|
|
|
2019-10-11 20:13:27 -04:00
|
|
|
messageBytes := new(bytes.Buffer)
|
|
|
|
_, _ = messageBytes.WriteString(opts.Message)
|
|
|
|
_, _ = messageBytes.WriteString("\n")
|
2019-05-11 11:29:17 -04:00
|
|
|
|
2022-06-16 11:47:44 -04:00
|
|
|
if opts.KeyID != "" || opts.AlwaysSign {
|
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-03 21:30:43 -05:00
|
|
|
cmd.AddOptionFormat("-S%s", opts.KeyID)
|
2019-05-11 11:29:17 -04:00
|
|
|
}
|
|
|
|
|
2022-06-16 11:47:44 -04:00
|
|
|
if opts.NoGPGSign {
|
2019-05-11 11:29:17 -04:00
|
|
|
cmd.AddArguments("--no-gpg-sign")
|
|
|
|
}
|
|
|
|
|
2019-10-11 20:13:27 -04:00
|
|
|
stdout := new(bytes.Buffer)
|
|
|
|
stderr := new(bytes.Buffer)
|
2022-06-09 21:57:49 -04:00
|
|
|
err := cmd.Run(&RunOpts{
|
2022-03-31 22:55:30 -04:00
|
|
|
Env: env,
|
|
|
|
Dir: repo.Path,
|
|
|
|
Stdin: messageBytes,
|
|
|
|
Stdout: stdout,
|
|
|
|
Stderr: stderr,
|
2022-02-11 07:47:22 -05:00
|
|
|
})
|
2019-05-11 11:29:17 -04:00
|
|
|
if err != nil {
|
2023-12-13 16:02:00 -05:00
|
|
|
return nil, ConcatenateError(err, stderr.String())
|
2019-05-11 11:29:17 -04:00
|
|
|
}
|
2023-12-19 02:20:47 -05:00
|
|
|
return NewIDFromString(strings.TrimSpace(stdout.String()))
|
2019-05-11 11:29:17 -04:00
|
|
|
}
|
2024-08-12 11:16:55 -04:00
|
|
|
|
|
|
|
func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
|
|
|
wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
_, _ = wr.Write([]byte(id.String() + "\n"))
|
|
|
|
|
|
|
|
// ignore the SHA
|
|
|
|
_, typ, size, err := ReadBatchLine(rd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch typ {
|
|
|
|
case "tag":
|
|
|
|
resolvedID := id
|
|
|
|
data, err := io.ReadAll(io.LimitReader(rd, size))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tag, err := parseTagData(id.Type(), data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
commit, err := tag.Commit(repo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
commit.Tree.ResolvedID = resolvedID
|
|
|
|
return &commit.Tree, nil
|
|
|
|
case "commit":
|
|
|
|
commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if _, err := rd.Discard(1); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
commit.Tree.ResolvedID = commit.ID
|
|
|
|
return &commit.Tree, nil
|
|
|
|
case "tree":
|
|
|
|
tree := NewTree(repo, id)
|
|
|
|
tree.ResolvedID = id
|
|
|
|
objectFormat, err := repo.GetObjectFormat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tree.entries, err = catBatchParseTreeEntries(objectFormat, tree, rd, size)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tree.entriesParsed = true
|
|
|
|
return tree, nil
|
|
|
|
default:
|
|
|
|
if err := DiscardFull(rd, size+1); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, ErrNotExist{
|
|
|
|
ID: id.String(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTree find the tree object in the repository.
|
|
|
|
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
|
|
|
objectFormat, err := repo.GetObjectFormat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(idStr) != objectFormat.FullLength() {
|
|
|
|
res, err := repo.GetRefCommitID(idStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(res) > 0 {
|
|
|
|
idStr = res
|
|
|
|
}
|
|
|
|
}
|
|
|
|
id, err := NewIDFromString(idStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return repo.getTree(id)
|
|
|
|
}
|