2019-12-12 16:18:07 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-12-12 16:18:07 +03:00
package git
import (
2024-03-29 15:25:54 +03:00
"bufio"
2019-12-12 16:18:07 +03:00
"bytes"
2021-09-09 23:13:36 +03:00
"context"
2019-12-12 16:18:07 +03:00
"fmt"
2024-03-29 15:25:54 +03:00
"io"
2021-09-09 23:13:36 +03:00
"os"
2024-03-24 14:44:30 +03:00
"strings"
2024-03-29 15:25:54 +03:00
"sync/atomic"
2021-09-20 22:46:51 +03:00
2024-02-26 14:52:59 +03:00
"code.gitea.io/gitea/modules/optional"
2019-12-12 16:18:07 +03:00
)
2024-03-24 14:44:30 +03:00
var LinguistAttributes = [ ] string { "linguist-vendored" , "linguist-generated" , "linguist-language" , "gitlab-language" , "linguist-documentation" , "linguist-detectable" }
2019-12-12 16:18:07 +03:00
2024-03-29 15:25:54 +03:00
// newCheckAttrStdoutReader parses the nul-byte separated output of git check-attr on each call of
// the returned function. The first reading error will stop the reading and be returned on all
// subsequent calls.
func newCheckAttrStdoutReader ( r io . Reader , count int ) func ( ) ( map [ string ] GitAttribute , error ) {
scanner := bufio . NewScanner ( r )
// adapted from bufio.ScanLines to split on nul-byte \x00
scanner . Split ( func ( data [ ] byte , atEOF bool ) ( advance int , token [ ] byte , err error ) {
if atEOF && len ( data ) == 0 {
return 0 , nil , nil
}
if i := bytes . IndexByte ( data , '\x00' ) ; i >= 0 {
// We have a full nul-terminated line.
return i + 1 , data [ 0 : i ] , nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len ( data ) , data , nil
}
// Request more data.
return 0 , nil , nil
} )
var err error
nextText := func ( ) string {
if err != nil {
return ""
}
if ! scanner . Scan ( ) {
err = scanner . Err ( )
if err == nil {
err = io . ErrUnexpectedEOF
}
return ""
}
return scanner . Text ( )
}
nextAttribute := func ( ) ( string , GitAttribute , error ) {
nextText ( ) // discard filename
key := nextText ( )
value := GitAttribute ( nextText ( ) )
return key , value , err
}
return func ( ) ( map [ string ] GitAttribute , error ) {
values := make ( map [ string ] GitAttribute , count )
for range count {
k , v , err := nextAttribute ( )
if err != nil {
return values , err
}
values [ k ] = v
}
return values , scanner . Err ( )
}
}
2024-03-24 14:44:30 +03:00
// GitAttribute exposes an attribute from the .gitattribute file
type GitAttribute string //nolint:revive
2021-11-17 23:37:00 +03:00
2024-03-24 14:44:30 +03:00
// IsSpecified returns true if the gitattribute is set and not empty
func ( ca GitAttribute ) IsSpecified ( ) bool {
return ca != "" && ca != "unspecified"
}
2021-11-17 23:37:00 +03:00
2024-03-24 14:44:30 +03:00
// String returns the value of the attribute or "" if unspecified
func ( ca GitAttribute ) String ( ) string {
if ! ca . IsSpecified ( ) {
return ""
2021-11-17 23:37:00 +03:00
}
2024-03-24 14:44:30 +03:00
return string ( ca )
}
2021-11-17 23:37:00 +03:00
2024-03-24 14:44:30 +03:00
// Prefix returns the value of the attribute before any question mark '?'
//
// sometimes used within gitlab-language: https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type
func ( ca GitAttribute ) Prefix ( ) string {
s := ca . String ( )
if i := strings . IndexByte ( s , '?' ) ; i >= 0 {
return s [ : i ]
2019-12-12 16:18:07 +03:00
}
2024-03-24 14:44:30 +03:00
return s
}
2019-12-12 16:18:07 +03:00
2024-03-24 14:44:30 +03:00
// Bool returns true if "set"/"true", false if "unset"/"false", none otherwise
func ( ca GitAttribute ) Bool ( ) optional . Option [ bool ] {
switch ca {
case "set" , "true" :
return optional . Some ( true )
case "unset" , "false" :
return optional . Some ( false )
2019-12-12 16:18:07 +03:00
}
2024-03-24 14:44:30 +03:00
return optional . None [ bool ] ( )
}
2019-12-12 16:18:07 +03:00
2024-03-29 15:25:54 +03:00
// gitCheckAttrCommand prepares the "git check-attr" command for later use as one-shot or streaming
2024-05-09 16:49:37 +03:00
// instantiation.
2024-03-24 14:44:30 +03:00
func ( repo * Repository ) gitCheckAttrCommand ( treeish string , attributes ... string ) ( * Command , * RunOpts , context . CancelFunc , error ) {
if len ( attributes ) == 0 {
return nil , nil , nil , fmt . Errorf ( "no provided attributes to check-attr" )
2019-12-12 16:18:07 +03:00
}
2024-03-24 14:44:30 +03:00
env := os . Environ ( )
2024-03-29 15:25:54 +03:00
var removeTempFiles context . CancelFunc = func ( ) { }
2019-12-12 16:18:07 +03:00
2024-03-24 14:44:30 +03:00
// git < 2.40 cannot run check-attr on bare repo, but needs INDEX + WORK_TREE
hasIndex := treeish == ""
if ! hasIndex && ! SupportCheckAttrOnBare {
indexFilename , worktree , cancel , err := repo . ReadTreeToTemporaryIndex ( treeish )
if err != nil {
return nil , nil , nil , err
2019-12-12 16:18:07 +03:00
}
2024-03-29 15:25:54 +03:00
removeTempFiles = cancel
2021-09-09 23:13:36 +03:00
2024-03-24 14:44:30 +03:00
env = append ( env , "GIT_INDEX_FILE=" + indexFilename , "GIT_WORK_TREE=" + worktree )
2021-09-09 23:13:36 +03:00
2024-03-24 14:44:30 +03:00
hasIndex = true
2021-09-09 23:13:36 +03:00
2024-03-24 14:44:30 +03:00
// clear treeish to read from provided index/work_tree
treeish = ""
}
2021-09-09 23:13:36 +03:00
2024-03-29 15:25:54 +03:00
cmd := NewCommand ( repo . Ctx , "check-attr" , "-z" )
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
2024-03-24 14:44:30 +03:00
if hasIndex {
cmd . AddArguments ( "--cached" )
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
}
2024-03-24 14:44:30 +03:00
if len ( treeish ) > 0 {
cmd . AddArguments ( "--source" )
cmd . AddDynamicArguments ( treeish )
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
}
2024-03-24 14:44:30 +03:00
cmd . AddDynamicArguments ( attributes ... )
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
2024-02-13 11:03:22 +03:00
// Version 2.43.1 has a bug where the behavior of `GIT_FLUSH` is flipped.
// Ref: https://lore.kernel.org/git/CABn0oJvg3M_kBW-u=j3QhKnO=6QOzk-YFTgonYw_UvFS1NTX4g@mail.gmail.com
if InvertedGitFlushEnv {
2024-03-24 14:44:30 +03:00
env = append ( env , "GIT_FLUSH=0" )
2024-02-13 11:03:22 +03:00
} else {
2024-03-24 14:44:30 +03:00
env = append ( env , "GIT_FLUSH=1" )
2024-02-13 11:03:22 +03:00
}
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
2024-03-24 14:44:30 +03:00
return cmd , & RunOpts {
Env : env ,
Dir : repo . Path ,
2024-03-29 15:25:54 +03:00
} , removeTempFiles , nil
}
// GitAttributeFirst returns the first specified attribute of the given filename.
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func ( repo * Repository ) GitAttributeFirst ( treeish , filename string , attributes ... string ) ( GitAttribute , error ) {
values , err := repo . GitAttributes ( treeish , filename , attributes ... )
if err != nil {
return "" , err
}
for _ , a := range attributes {
if values [ a ] . IsSpecified ( ) {
return values [ a ] , nil
}
}
return "" , nil
2024-03-24 14:44:30 +03:00
}
2021-09-20 22:46:51 +03:00
2024-03-29 15:25:54 +03:00
// GitAttributes returns the gitattribute of the given filename.
2024-03-24 14:44:30 +03:00
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func ( repo * Repository ) GitAttributes ( treeish , filename string , attributes ... string ) ( map [ string ] GitAttribute , error ) {
2024-03-29 15:25:54 +03:00
cmd , runOpts , removeTempFiles , err := repo . gitCheckAttrCommand ( treeish , attributes ... )
2021-09-09 23:13:36 +03:00
if err != nil {
2024-03-24 14:44:30 +03:00
return nil , err
2021-09-09 23:13:36 +03:00
}
2024-03-29 15:25:54 +03:00
defer removeTempFiles ( )
2021-09-09 23:13:36 +03:00
2024-03-24 14:44:30 +03:00
stdOut := new ( bytes . Buffer )
runOpts . Stdout = stdOut
2021-09-09 23:13:36 +03:00
stdErr := new ( bytes . Buffer )
2024-03-24 14:44:30 +03:00
runOpts . Stderr = stdErr
2021-09-09 23:13:36 +03:00
2024-03-24 14:44:30 +03:00
cmd . AddDashesAndList ( filename )
2021-09-20 22:46:51 +03:00
2024-03-24 14:44:30 +03:00
if err := cmd . Run ( runOpts ) ; err != nil {
return nil , fmt . Errorf ( "failed to run check-attr: %w\n%s\n%s" , err , stdOut . String ( ) , stdErr . String ( ) )
2021-09-09 23:13:36 +03:00
}
2024-03-29 15:25:54 +03:00
return newCheckAttrStdoutReader ( stdOut , len ( attributes ) ) ( )
2021-09-09 23:13:36 +03:00
}
2022-06-16 18:47:44 +03:00
2024-03-29 15:25:54 +03:00
// GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID
// to retrieve the attributes of multiple files. The AttributeChecker must be closed after use.
2024-03-24 14:44:30 +03:00
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func ( repo * Repository ) GitAttributeChecker ( treeish string , attributes ... string ) ( AttributeChecker , error ) {
2024-03-29 15:25:54 +03:00
cmd , runOpts , removeTempFiles , err := repo . gitCheckAttrCommand ( treeish , attributes ... )
2022-06-16 18:47:44 +03:00
if err != nil {
2024-03-24 14:44:30 +03:00
return AttributeChecker { } , err
2022-06-16 18:47:44 +03:00
}
2024-03-29 15:25:54 +03:00
cmd . AddArguments ( "--stdin" )
2024-03-24 14:44:30 +03:00
2024-03-29 15:25:54 +03:00
// os.Pipe is needed (and not io.Pipe), otherwise cmd.Wait will wait for the stdinReader
// to be closed before returning (which would require another goroutine)
// https://go.dev/issue/23019
stdinReader , stdinWriter , err := os . Pipe ( ) // reader closed in goroutine / writer closed on ac.Close
2024-03-24 14:44:30 +03:00
if err != nil {
return AttributeChecker { } , err
2022-06-16 18:47:44 +03:00
}
2024-03-29 15:25:54 +03:00
stdoutReader , stdoutWriter := io . Pipe ( ) // closed in goroutine
2024-03-24 14:44:30 +03:00
2024-03-29 15:25:54 +03:00
ac := AttributeChecker {
removeTempFiles : removeTempFiles , // called on ac.Close
stdinWriter : stdinWriter ,
readStdout : newCheckAttrStdoutReader ( stdoutReader , len ( attributes ) ) ,
err : & atomic . Value { } ,
}
2024-03-24 14:44:30 +03:00
go func ( ) {
defer stdinReader . Close ( )
2024-03-29 15:25:54 +03:00
defer stdoutWriter . Close ( ) // in case of a panic (no-op if already closed by CloseWithError at the end)
2024-03-24 14:44:30 +03:00
stdErr := new ( bytes . Buffer )
runOpts . Stdin = stdinReader
2024-03-29 15:25:54 +03:00
runOpts . Stdout = stdoutWriter
2024-03-24 14:44:30 +03:00
runOpts . Stderr = stdErr
2024-03-29 15:25:54 +03:00
2024-03-24 14:44:30 +03:00
err := cmd . Run ( runOpts )
2024-03-29 15:25:54 +03:00
// if the context was cancelled, Run error is irrelevant
if e := cmd . parentContext . Err ( ) ; e != nil {
err = e
2024-03-24 14:44:30 +03:00
}
2024-03-29 15:25:54 +03:00
if err != nil { // decorate the returned error
err = fmt . Errorf ( "git check-attr (stderr: %q): %w" , strings . TrimSpace ( stdErr . String ( ) ) , err )
ac . err . Store ( err )
}
stdoutWriter . CloseWithError ( err )
2024-03-24 14:44:30 +03:00
} ( )
return ac , nil
}
2022-06-16 18:47:44 +03:00
2024-03-24 14:44:30 +03:00
type AttributeChecker struct {
2024-03-29 15:25:54 +03:00
removeTempFiles context . CancelFunc
stdinWriter io . WriteCloser
readStdout func ( ) ( map [ string ] GitAttribute , error )
err * atomic . Value
2022-06-16 18:47:44 +03:00
}
2024-02-26 14:52:59 +03:00
2024-03-24 14:44:30 +03:00
func ( ac AttributeChecker ) CheckPath ( path string ) ( map [ string ] GitAttribute , error ) {
if _ , err := ac . stdinWriter . Write ( [ ] byte ( path + "\x00" ) ) ; err != nil {
2024-03-29 15:25:54 +03:00
// try to return the Run error if available, since it is likely more helpful
// than just "broken pipe"
if aerr , _ := ac . err . Load ( ) . ( error ) ; aerr != nil {
return nil , aerr
2024-02-26 14:52:59 +03:00
}
2024-03-29 15:25:54 +03:00
return nil , fmt . Errorf ( "git check-attr: %w" , err )
2024-02-26 14:52:59 +03:00
}
2024-03-29 15:25:54 +03:00
return ac . readStdout ( )
2024-03-24 14:44:30 +03:00
}
func ( ac AttributeChecker ) Close ( ) error {
2024-03-29 15:25:54 +03:00
ac . removeTempFiles ( )
2024-03-24 14:44:30 +03:00
return ac . stdinWriter . Close ( )
2024-02-26 14:52:59 +03:00
}