2016-11-04 01:16:01 +03:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2019-04-19 15:17:27 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2016-11-04 01:16:01 +03:00
package git
2019-04-19 15:17:27 +03:00
import (
2019-10-12 03:13:27 +03:00
"bytes"
2024-08-12 18:16:55 +03:00
"io"
2019-05-11 18:29:17 +03:00
"os"
"strings"
"time"
2019-04-19 15:17:27 +03:00
)
2019-05-11 18:29:17 +03:00
// CommitTreeOpts represents the possible options to CommitTree
type CommitTreeOpts struct {
2019-10-16 16:42:42 +03:00
Parents [ ] string
Message string
KeyID string
NoGPGSign bool
AlwaysSign bool
2019-05-11 18:29:17 +03:00
}
// CommitTree creates a commit from a given tree id for the user with provided message
2023-12-14 00:02:00 +03:00
func ( repo * Repository ) CommitTree ( author , committer * Signature , tree * Tree , opts CommitTreeOpts ) ( ObjectID , error ) {
2019-07-31 22:19:47 +03:00
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
2019-05-11 18:29:17 +03:00
// Because this may call hooks we should pass in the environment
env := append ( os . Environ ( ) ,
2020-09-19 19:44:55 +03:00
"GIT_AUTHOR_NAME=" + author . Name ,
"GIT_AUTHOR_EMAIL=" + author . Email ,
2019-05-11 18:29:17 +03:00
"GIT_AUTHOR_DATE=" + commitTimeStr ,
2020-09-19 19:44:55 +03:00
"GIT_COMMITTER_NAME=" + committer . Name ,
"GIT_COMMITTER_EMAIL=" + committer . Email ,
2019-05-11 18:29:17 +03:00
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
2022-10-23 17:44:45 +03:00
cmd := NewCommand ( repo . Ctx , "commit-tree" ) . AddDynamicArguments ( tree . ID . String ( ) )
2019-05-11 18:29:17 +03:00
for _ , parent := range opts . Parents {
2022-10-23 17:44:45 +03:00
cmd . AddArguments ( "-p" ) . AddDynamicArguments ( parent )
2019-05-11 18:29:17 +03:00
}
2019-10-12 03:13:27 +03:00
messageBytes := new ( bytes . Buffer )
_ , _ = messageBytes . WriteString ( opts . Message )
_ , _ = messageBytes . WriteString ( "\n" )
2019-05-11 18:29:17 +03:00
2022-06-16 18:47:44 +03: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-04 05:30:43 +03:00
cmd . AddOptionFormat ( "-S%s" , opts . KeyID )
2019-05-11 18:29:17 +03:00
}
2022-06-16 18:47:44 +03:00
if opts . NoGPGSign {
2019-05-11 18:29:17 +03:00
cmd . AddArguments ( "--no-gpg-sign" )
}
2019-10-12 03:13:27 +03:00
stdout := new ( bytes . Buffer )
stderr := new ( bytes . Buffer )
2022-06-10 04:57:49 +03:00
err := cmd . Run ( & RunOpts {
2022-04-01 05:55:30 +03:00
Env : env ,
Dir : repo . Path ,
Stdin : messageBytes ,
Stdout : stdout ,
Stderr : stderr ,
2022-02-11 15:47:22 +03:00
} )
2019-05-11 18:29:17 +03:00
if err != nil {
2023-12-14 00:02:00 +03:00
return nil , ConcatenateError ( err , stderr . String ( ) )
2019-05-11 18:29:17 +03:00
}
2023-12-19 10:20:47 +03:00
return NewIDFromString ( strings . TrimSpace ( stdout . String ( ) ) )
2019-05-11 18:29:17 +03:00
}
2024-08-12 18:16:55 +03:00
func ( repo * Repository ) getTree ( id ObjectID ) ( * Tree , error ) {
2024-08-20 20:04:57 +03:00
wr , rd , cancel , err := repo . CatFileBatch ( repo . Ctx )
if err != nil {
return nil , err
}
2024-08-12 18:16:55 +03:00
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 )
}