2016-11-04 01:16:01 +03:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2019-02-03 06:35:17 +03:00
// Copyright 2018 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
import (
"bufio"
2017-03-22 13:43:54 +03:00
"bytes"
2022-01-20 02:26:57 +03:00
"context"
2021-02-10 10:00:57 +03:00
"errors"
2024-03-19 05:20:36 +03:00
"fmt"
2019-02-03 06:35:17 +03:00
"io"
2021-02-10 10:00:57 +03:00
"os/exec"
2016-11-04 01:16:01 +03:00
"strconv"
"strings"
2021-07-02 22:23:37 +03:00
"code.gitea.io/gitea/modules/log"
2022-05-08 15:32:45 +03:00
"code.gitea.io/gitea/modules/util"
2016-11-04 01:16:01 +03:00
)
// Commit represents a git commit.
type Commit struct {
Tree
2023-12-14 00:02:00 +03:00
ID ObjectID // The ID of this commit object
2016-11-04 01:16:01 +03:00
Author * Signature
Committer * Signature
CommitMessage string
2024-04-01 23:23:17 +03:00
Signature * CommitSignature
2016-11-04 01:16:01 +03:00
2023-12-14 00:02:00 +03:00
Parents [ ] ObjectID // ID strings
2016-12-22 12:30:52 +03:00
submoduleCache * ObjectCache
2016-11-04 01:16:01 +03:00
}
2024-04-01 23:23:17 +03:00
// CommitSignature represents a git commit signature part.
type CommitSignature struct {
2017-03-22 13:43:54 +03:00
Signature string
2022-01-20 20:46:10 +03:00
Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data
2017-03-22 13:43:54 +03:00
}
2016-11-04 01:16:01 +03:00
// Message returns the commit message. Same as retrieving CommitMessage directly.
func ( c * Commit ) Message ( ) string {
return c . CommitMessage
}
// Summary returns first line of commit message.
2023-12-05 09:34:24 +03:00
// The string is forced to be valid UTF8
2016-11-04 01:16:01 +03:00
func ( c * Commit ) Summary ( ) string {
2023-12-05 09:34:24 +03:00
return strings . ToValidUTF8 ( strings . Split ( strings . TrimSpace ( c . CommitMessage ) , "\n" ) [ 0 ] , "?" )
2016-11-04 01:16:01 +03:00
}
// ParentID returns oid of n-th parent (0-based index).
// It returns nil if no such parent exists.
2023-12-14 00:02:00 +03:00
func ( c * Commit ) ParentID ( n int ) ( ObjectID , error ) {
2020-01-15 11:32:57 +03:00
if n >= len ( c . Parents ) {
2023-12-14 00:02:00 +03:00
return nil , ErrNotExist { "" , "" }
2016-11-04 01:16:01 +03:00
}
2020-01-15 11:32:57 +03:00
return c . Parents [ n ] , nil
2016-11-04 01:16:01 +03:00
}
// Parent returns n-th parent (0-based index) of the commit.
func ( c * Commit ) Parent ( n int ) ( * Commit , error ) {
id , err := c . ParentID ( n )
if err != nil {
return nil , err
}
parent , err := c . repo . getCommit ( id )
if err != nil {
return nil , err
}
return parent , nil
}
// ParentCount returns number of parents of the commit.
// 0 if this is the root commit, otherwise 1,2, etc.
func ( c * Commit ) ParentCount ( ) int {
2020-01-15 11:32:57 +03:00
return len ( c . Parents )
2016-11-04 01:16:01 +03:00
}
// GetCommitByPath return the commit of relative path object.
func ( c * Commit ) GetCommitByPath ( relpath string ) ( * Commit , error ) {
2022-07-25 18:39:42 +03:00
if c . repo . LastCommitCache != nil {
return c . repo . LastCommitCache . GetCommitByPath ( c . ID . String ( ) , relpath )
}
2016-11-04 01:16:01 +03:00
return c . repo . getCommitByPathWithID ( c . ID , relpath )
}
2016-12-22 12:30:52 +03:00
// AddChanges marks local changes to be ready for commit.
2016-11-04 01:16:01 +03:00
func AddChanges ( repoPath string , all bool , files ... string ) error {
2022-01-25 21:15:58 +03:00
return AddChangesWithArgs ( repoPath , globalCommandArgs , all , files ... )
2019-11-27 03:35:52 +03:00
}
// AddChangesWithArgs marks local changes to be ready for commit.
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
func AddChangesWithArgs ( repoPath string , globalArgs TrustedCmdArgs , all bool , files ... string ) error {
cmd := NewCommandContextNoGlobals ( DefaultContext , globalArgs ... ) . AddArguments ( "add" )
2016-11-04 01:16:01 +03:00
if all {
cmd . AddArguments ( "--all" )
}
2022-10-23 17:44:45 +03:00
cmd . AddDashesAndList ( files ... )
_ , _ , err := cmd . RunStdString ( & RunOpts { Dir : repoPath } )
2016-11-04 01:16:01 +03:00
return err
}
2016-12-22 12:30:52 +03:00
// CommitChangesOptions the options when a commit created
2016-11-04 01:16:01 +03:00
type CommitChangesOptions struct {
Committer * Signature
Author * Signature
Message string
}
// CommitChanges commits local changes with given committer, author and message.
// If author is nil, it will be the same as committer.
func CommitChanges ( repoPath string , opts CommitChangesOptions ) error {
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
cargs := make ( TrustedCmdArgs , len ( globalCommandArgs ) )
2022-01-25 21:15:58 +03:00
copy ( cargs , globalCommandArgs )
2019-11-27 03:35:52 +03:00
return CommitChangesWithArgs ( repoPath , cargs , opts )
}
// CommitChangesWithArgs commits local changes with given committer, author and message.
// If author is nil, it will be the same as committer.
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
func CommitChangesWithArgs ( repoPath string , args TrustedCmdArgs , opts CommitChangesOptions ) error {
cmd := NewCommandContextNoGlobals ( DefaultContext , args ... )
2016-11-04 01:16:01 +03:00
if opts . Committer != nil {
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 . AddOptionValues ( "-c" , "user.name=" + opts . Committer . Name )
cmd . AddOptionValues ( "-c" , "user.email=" + opts . Committer . Email )
2016-11-04 01:16:01 +03:00
}
cmd . AddArguments ( "commit" )
if opts . Author == nil {
opts . Author = opts . Committer
}
if opts . Author != nil {
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 ( "--author='%s <%s>'" , opts . Author . Name , opts . Author . Email )
2016-11-04 01:16:01 +03:00
}
Use `--message=%s` for git commit message (#23028)
Close #23027
`git commit` message option _only_ supports 4 formats (well, only ....):
* `"commit", "-m", msg`
* `"commit", "-m{msg}"` (no space)
* `"commit", "--message", msg`
* `"commit", "--message={msg}"`
The long format with `=` is the best choice, and it's documented in `man
git-commit`:
`-m <msg>, --message=<msg> ...`
ps: I would suggest always use long format option for git command, as
much as possible.
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-21 09:12:57 +03:00
cmd . AddOptionFormat ( "--message=%s" , opts . Message )
2016-11-04 01:16:01 +03:00
2022-04-01 05:55:30 +03:00
_ , _ , err := cmd . RunStdString ( & RunOpts { Dir : repoPath } )
2016-11-04 01:16:01 +03:00
// No stderr but exit status 1 means nothing to commit.
if err != nil && err . Error ( ) == "exit status 1" {
return nil
}
return err
}
2019-11-07 21:09:51 +03:00
// AllCommitsCount returns count of all commits in repository
2022-01-20 02:26:57 +03:00
func AllCommitsCount ( ctx context . Context , repoPath string , hidePRRefs bool , files ... string ) ( int64 , error ) {
2022-10-23 17:44:45 +03:00
cmd := NewCommand ( ctx , "rev-list" )
2020-11-08 20:21:54 +03:00
if hidePRRefs {
2022-10-23 17:44:45 +03:00
cmd . AddArguments ( "--exclude=" + PullPrefix + "*" )
2020-11-08 20:21:54 +03:00
}
2022-10-23 17:44:45 +03:00
cmd . AddArguments ( "--all" , "--count" )
2020-11-08 20:21:54 +03:00
if len ( files ) > 0 {
2022-10-23 17:44:45 +03:00
cmd . AddDashesAndList ( files ... )
2020-11-08 20:21:54 +03:00
}
2022-04-01 05:55:30 +03:00
stdout , _ , err := cmd . RunStdString ( & RunOpts { Dir : repoPath } )
2019-11-07 21:09:51 +03:00
if err != nil {
return 0 , err
}
return strconv . ParseInt ( strings . TrimSpace ( stdout ) , 10 , 64 )
}
2023-05-08 10:10:53 +03:00
// CommitsCountOptions the options when counting commits
type CommitsCountOptions struct {
RepoPath string
Not string
Revision [ ] string
RelPath [ ] string
}
// CommitsCount returns number of total commits of until given revision.
func CommitsCount ( ctx context . Context , opts CommitsCountOptions ) ( int64 , error ) {
2022-02-06 22:01:47 +03:00
cmd := NewCommand ( ctx , "rev-list" , "--count" )
2023-05-08 10:10:53 +03:00
cmd . AddDynamicArguments ( opts . Revision ... )
if opts . Not != "" {
cmd . AddOptionValues ( "--not" , opts . Not )
2016-11-04 01:16:01 +03:00
}
2023-05-08 10:10:53 +03:00
if len ( opts . RelPath ) > 0 {
cmd . AddDashesAndList ( opts . RelPath ... )
}
stdout , _ , err := cmd . RunStdString ( & RunOpts { Dir : opts . RepoPath } )
2016-11-04 01:16:01 +03:00
if err != nil {
return 0 , err
}
return strconv . ParseInt ( strings . TrimSpace ( stdout ) , 10 , 64 )
}
2016-12-22 12:30:52 +03:00
// CommitsCount returns number of total commits of until current revision.
2016-11-04 01:16:01 +03:00
func ( c * Commit ) CommitsCount ( ) ( int64 , error ) {
2023-05-08 10:10:53 +03:00
return CommitsCount ( c . repo . Ctx , CommitsCountOptions {
RepoPath : c . repo . Path ,
Revision : [ ] string { c . ID . String ( ) } ,
} )
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
2023-04-29 15:34:14 +03:00
func ( c * Commit ) CommitsByRange ( page , pageSize int , not string ) ( [ ] * Commit , error ) {
return c . repo . commitsByRange ( c . ID , page , pageSize , not )
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
// CommitsBefore returns all the commits before current revision
2021-08-09 21:08:51 +03:00
func ( c * Commit ) CommitsBefore ( ) ( [ ] * Commit , error ) {
2016-11-04 01:16:01 +03:00
return c . repo . getCommitsBefore ( c . ID )
}
2019-12-16 09:20:25 +03:00
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents
2023-12-14 00:02:00 +03:00
func ( c * Commit ) HasPreviousCommit ( objectID ObjectID ) ( bool , error ) {
2021-02-10 10:00:57 +03:00
this := c . ID . String ( )
2023-12-14 00:02:00 +03:00
that := objectID . String ( )
2021-02-10 10:00:57 +03:00
if this == that {
return false , nil
}
2022-10-23 17:44:45 +03:00
_ , _ , err := NewCommand ( c . repo . Ctx , "merge-base" , "--is-ancestor" ) . AddDynamicArguments ( that , this ) . RunStdString ( & RunOpts { Dir : c . repo . Path } )
2022-06-16 18:47:44 +03:00
if err == nil {
return true , nil
}
var exitError * exec . ExitError
if errors . As ( err , & exitError ) {
if exitError . ProcessState . ExitCode ( ) == 1 && len ( exitError . Stderr ) == 0 {
return false , nil
2019-12-16 09:20:25 +03:00
}
}
2022-06-16 18:47:44 +03:00
return false , err
2019-12-16 09:20:25 +03:00
}
2023-03-09 21:14:22 +03:00
// IsForcePush returns true if a push from oldCommitHash to this is a force push
func ( c * Commit ) IsForcePush ( oldCommitID string ) ( bool , error ) {
2023-12-14 00:02:00 +03:00
objectFormat , err := c . repo . GetObjectFormat ( )
if err != nil {
return false , err
}
2023-12-17 14:56:08 +03:00
if oldCommitID == objectFormat . EmptyObjectID ( ) . String ( ) {
2023-03-09 21:14:22 +03:00
return false , nil
}
2023-12-14 00:02:00 +03:00
2023-03-09 21:14:22 +03:00
oldCommit , err := c . repo . GetCommit ( oldCommitID )
if err != nil {
return false , err
}
hasPreviousCommit , err := c . HasPreviousCommit ( oldCommit . ID )
return ! hasPreviousCommit , err
}
2016-12-22 12:30:52 +03:00
// CommitsBeforeLimit returns num commits before current revision
2021-08-09 21:08:51 +03:00
func ( c * Commit ) CommitsBeforeLimit ( num int ) ( [ ] * Commit , error ) {
2016-11-04 01:16:01 +03:00
return c . repo . getCommitsBeforeLimit ( c . ID , num )
}
2016-12-22 12:30:52 +03:00
// CommitsBeforeUntil returns the commits between commitID to current revision
2021-08-09 21:08:51 +03:00
func ( c * Commit ) CommitsBeforeUntil ( commitID string ) ( [ ] * Commit , error ) {
2016-11-04 01:16:01 +03:00
endCommit , err := c . repo . GetCommit ( commitID )
if err != nil {
return nil , err
}
return c . repo . CommitsBetween ( c , endCommit )
}
2019-04-12 05:28:44 +03:00
// SearchCommitsOptions specify the parameters for SearchCommits
type SearchCommitsOptions struct {
Keywords [ ] string
Authors , Committers [ ] string
After , Before string
All bool
}
2019-06-12 22:41:28 +03:00
// NewSearchCommitsOptions construct a SearchCommitsOption from a space-delimited search string
2019-04-12 05:28:44 +03:00
func NewSearchCommitsOptions ( searchString string , forAllRefs bool ) SearchCommitsOptions {
var keywords , authors , committers [ ] string
var after , before string
fields := strings . Fields ( searchString )
for _ , k := range fields {
switch {
case strings . HasPrefix ( k , "author:" ) :
authors = append ( authors , strings . TrimPrefix ( k , "author:" ) )
case strings . HasPrefix ( k , "committer:" ) :
committers = append ( committers , strings . TrimPrefix ( k , "committer:" ) )
case strings . HasPrefix ( k , "after:" ) :
after = strings . TrimPrefix ( k , "after:" )
case strings . HasPrefix ( k , "before:" ) :
before = strings . TrimPrefix ( k , "before:" )
default :
keywords = append ( keywords , k )
}
}
return SearchCommitsOptions {
Keywords : keywords ,
Authors : authors ,
Committers : committers ,
After : after ,
Before : before ,
All : forAllRefs ,
}
}
2016-12-22 12:30:52 +03:00
// SearchCommits returns the commits match the keyword before current revision
2021-08-09 21:08:51 +03:00
func ( c * Commit ) SearchCommits ( opts SearchCommitsOptions ) ( [ ] * Commit , error ) {
2019-04-12 05:28:44 +03:00
return c . repo . searchCommits ( c . ID , opts )
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
// GetFilesChangedSinceCommit get all changed file names between pastCommit to current revision
2016-11-04 01:16:01 +03:00
func ( c * Commit ) GetFilesChangedSinceCommit ( pastCommit string ) ( [ ] string , error ) {
2023-04-07 09:42:43 +03:00
return c . repo . GetFilesChangedBetween ( pastCommit , c . ID . String ( ) )
2016-11-04 01:16:01 +03:00
}
2024-03-11 12:24:23 +03:00
// FileChangedSinceCommit Returns true if the file given has changed since the past commit
2019-08-05 23:39:39 +03:00
// YOU MUST ENSURE THAT pastCommit is a valid commit ID.
2019-04-17 19:06:35 +03:00
func ( c * Commit ) FileChangedSinceCommit ( filename , pastCommit string ) ( bool , error ) {
return c . repo . FileChangedBetweenCommits ( filename , pastCommit , c . ID . String ( ) )
}
2019-09-16 12:03:22 +03:00
// HasFile returns true if the file given exists on this commit
// This does only mean it's there - it does not mean the file was changed during the commit.
func ( c * Commit ) HasFile ( filename string ) ( bool , error ) {
2019-10-04 22:58:54 +03:00
_ , err := c . GetBlobByPath ( filename )
if err != nil {
return false , err
}
return true , nil
2019-09-16 12:03:22 +03:00
}
2022-05-08 15:32:45 +03:00
// GetFileContent reads a file content as a string or returns false if this was not possible
func ( c * Commit ) GetFileContent ( filename string , limit int ) ( string , error ) {
entry , err := c . GetTreeEntryByPath ( filename )
if err != nil {
return "" , err
}
r , err := entry . Blob ( ) . DataAsync ( )
if err != nil {
return "" , err
}
defer r . Close ( )
if limit > 0 {
bs := make ( [ ] byte , limit )
n , err := util . ReadAtMost ( r , bs )
if err != nil {
return "" , err
}
return string ( bs [ : n ] ) , nil
}
bytes , err := io . ReadAll ( r )
if err != nil {
return "" , err
}
return string ( bytes ) , nil
}
2016-12-22 12:30:52 +03:00
// GetSubModules get all the sub modules of current revision git tree
func ( c * Commit ) GetSubModules ( ) ( * ObjectCache , error ) {
2016-11-04 01:16:01 +03:00
if c . submoduleCache != nil {
return c . submoduleCache , nil
}
entry , err := c . GetTreeEntryByPath ( ".gitmodules" )
if err != nil {
2017-06-23 03:06:43 +03:00
if _ , ok := err . ( ErrNotExist ) ; ok {
return nil , nil
}
2016-11-04 01:16:01 +03:00
return nil , err
}
2019-04-19 15:17:27 +03:00
rd , err := entry . Blob ( ) . DataAsync ( )
2016-11-04 01:16:01 +03:00
if err != nil {
return nil , err
}
2019-04-19 15:17:27 +03:00
defer rd . Close ( )
2016-11-04 01:16:01 +03:00
scanner := bufio . NewScanner ( rd )
c . submoduleCache = newObjectCache ( )
var ismodule bool
var path string
for scanner . Scan ( ) {
if strings . HasPrefix ( scanner . Text ( ) , "[submodule" ) {
ismodule = true
continue
}
if ismodule {
fields := strings . Split ( scanner . Text ( ) , "=" )
k := strings . TrimSpace ( fields [ 0 ] )
if k == "path" {
path = strings . TrimSpace ( fields [ 1 ] )
} else if k == "url" {
c . submoduleCache . Set ( path , & SubModule { path , strings . TrimSpace ( fields [ 1 ] ) } )
ismodule = false
}
}
}
2024-03-22 14:17:30 +03:00
if err = scanner . Err ( ) ; err != nil {
return nil , fmt . Errorf ( "GetSubModules scan: %w" , err )
2024-03-19 05:20:36 +03:00
}
2016-11-04 01:16:01 +03:00
return c . submoduleCache , nil
}
2016-12-22 12:30:52 +03:00
// GetSubModule get the sub module according entryname
2016-11-04 01:16:01 +03:00
func ( c * Commit ) GetSubModule ( entryname string ) ( * SubModule , error ) {
modules , err := c . GetSubModules ( )
if err != nil {
return nil , err
}
2017-06-23 03:06:43 +03:00
if modules != nil {
module , has := modules . Get ( entryname )
if has {
return module . ( * SubModule ) , nil
}
2016-11-04 01:16:01 +03:00
}
return nil , nil
}
2018-09-07 05:06:09 +03:00
2020-06-11 22:42:55 +03:00
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
2019-04-19 15:17:27 +03:00
func ( c * Commit ) GetBranchName ( ) ( string , error ) {
2022-10-23 17:44:45 +03:00
cmd := NewCommand ( c . repo . Ctx , "name-rev" )
2024-05-06 19:34:16 +03:00
if DefaultFeatures ( ) . CheckVersionAtLeast ( "2.13.0" ) {
2022-10-23 17:44:45 +03:00
cmd . AddArguments ( "--exclude" , "refs/tags/*" )
2020-07-28 17:11:05 +03:00
}
2022-10-23 17:44:45 +03:00
cmd . AddArguments ( "--name-only" , "--no-undefined" ) . AddDynamicArguments ( c . ID . String ( ) )
data , _ , err := cmd . RunStdString ( & RunOpts { Dir : c . repo . Path } )
2019-04-19 15:17:27 +03:00
if err != nil {
2020-05-23 22:49:48 +03:00
// handle special case where git can not describe commit
if strings . Contains ( err . Error ( ) , "cannot describe" ) {
return "" , nil
}
2019-04-19 15:17:27 +03:00
return "" , err
}
2020-05-20 15:47:24 +03:00
// name-rev commitID output will be "master" or "master~12"
return strings . SplitN ( strings . TrimSpace ( data ) , "~" , 2 ) [ 0 ] , nil
2019-04-19 15:17:27 +03:00
}
2019-02-03 06:35:17 +03:00
// CommitFileStatus represents status of files in a commit.
type CommitFileStatus struct {
Added [ ] string
Removed [ ] string
Modified [ ] string
}
// NewCommitFileStatus creates a CommitFileStatus
func NewCommitFileStatus ( ) * CommitFileStatus {
return & CommitFileStatus {
[ ] string { } , [ ] string { } , [ ] string { } ,
}
}
2021-07-02 22:23:37 +03:00
func parseCommitFileStatus ( fileStatus * CommitFileStatus , stdout io . Reader ) {
rd := bufio . NewReader ( stdout )
peek , err := rd . Peek ( 1 )
if err != nil {
if err != io . EOF {
log . Error ( "Unexpected error whilst reading from git log --name-status. Error: %v" , err )
}
return
}
if peek [ 0 ] == '\n' || peek [ 0 ] == '\x00' {
_ , _ = rd . Discard ( 1 )
}
for {
2024-04-19 17:41:03 +03:00
modifier , err := rd . ReadString ( '\x00' )
2021-07-02 22:23:37 +03:00
if err != nil {
if err != io . EOF {
log . Error ( "Unexpected error whilst reading from git log --name-status. Error: %v" , err )
}
return
}
file , err := rd . ReadString ( '\x00' )
if err != nil {
if err != io . EOF {
log . Error ( "Unexpected error whilst reading from git log --name-status. Error: %v" , err )
}
return
}
file = file [ : len ( file ) - 1 ]
switch modifier [ 0 ] {
case 'A' :
fileStatus . Added = append ( fileStatus . Added , file )
case 'D' :
fileStatus . Removed = append ( fileStatus . Removed , file )
case 'M' :
fileStatus . Modified = append ( fileStatus . Modified , file )
}
}
}
2019-02-03 06:35:17 +03:00
// GetCommitFileStatus returns file status of commit in given repository.
2022-01-20 02:26:57 +03:00
func GetCommitFileStatus ( ctx context . Context , repoPath , commitID string ) ( * CommitFileStatus , error ) {
2019-02-03 06:35:17 +03:00
stdout , w := io . Pipe ( )
done := make ( chan struct { } )
fileStatus := NewCommitFileStatus ( )
go func ( ) {
2021-07-02 22:23:37 +03:00
parseCommitFileStatus ( fileStatus , stdout )
close ( done )
2019-02-03 06:35:17 +03:00
} ( )
stderr := new ( bytes . Buffer )
2023-08-24 13:03:49 +03:00
err := NewCommand ( ctx , "log" , "--name-status" , "-m" , "--pretty=format:" , "--first-parent" , "--no-renames" , "-z" , "-1" ) . AddDynamicArguments ( commitID ) . Run ( & RunOpts {
2022-04-01 05:55:30 +03:00
Dir : repoPath ,
Stdout : w ,
Stderr : stderr ,
2022-02-11 15:47:22 +03:00
} )
2019-02-03 06:35:17 +03:00
w . Close ( ) // Close writer to exit parsing goroutine
if err != nil {
2020-12-17 17:00:47 +03:00
return nil , ConcatenateError ( err , stderr . String ( ) )
2019-02-03 06:35:17 +03:00
}
<- done
return fileStatus , nil
}
2018-09-07 05:06:09 +03:00
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
2022-01-20 02:26:57 +03:00
func GetFullCommitID ( ctx context . Context , repoPath , shortID string ) ( string , error ) {
2022-10-23 17:44:45 +03:00
commitID , _ , err := NewCommand ( ctx , "rev-parse" ) . AddDynamicArguments ( shortID ) . RunStdString ( & RunOpts { Dir : repoPath } )
2018-09-07 05:06:09 +03:00
if err != nil {
if strings . Contains ( err . Error ( ) , "exit status 128" ) {
return "" , ErrNotExist { shortID , "" }
}
return "" , err
}
return strings . TrimSpace ( commitID ) , nil
}
2019-10-16 16:42:42 +03:00
// GetRepositoryDefaultPublicGPGKey returns the default public key for this commit
func ( c * Commit ) GetRepositoryDefaultPublicGPGKey ( forceUpdate bool ) ( * GPGSettings , error ) {
if c . repo == nil {
return nil , nil
}
return c . repo . GetDefaultPublicGPGKey ( forceUpdate )
}