mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-09 17:37:20 -06:00
If the stderr output is not handled, then it needs to use RunWithStderr to make sure the git error message is included in the returned error. Otherwise, when the git command fails, the error log won't show useful information. Ref: * #39795 : no useful information in log messages * https://github.com/go-gitea/gitea/pull/40160#pullrequestreview-5752720866
127 lines
4.2 KiB
Go
127 lines
4.2 KiB
Go
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package git
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"gitea.dev/modules/git/gitcmd"
|
|
"gitea.dev/modules/git/gitrepo"
|
|
)
|
|
|
|
type lineCountWriter struct {
|
|
numLines int
|
|
}
|
|
|
|
// Write counts the number of newlines in the provided bytestream
|
|
func (l *lineCountWriter) Write(p []byte) (n int, err error) {
|
|
n = len(p)
|
|
l.numLines += bytes.Count(p, []byte{'\000'})
|
|
return n, err
|
|
}
|
|
|
|
// GetDiffNumChangedFiles counts the number of changed files
|
|
// This is substantially quicker than shortstat but...
|
|
func (repo *Repository) GetDiffNumChangedFiles(ctx context.Context, base, head string, directComparison bool) (int, error) {
|
|
// Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly
|
|
w := &lineCountWriter{}
|
|
|
|
separator := "..."
|
|
if directComparison {
|
|
separator = ".."
|
|
}
|
|
|
|
if err := gitcmd.NewCommand("diff", "-z", "--name-only").
|
|
AddDynamicArguments(base + separator + head).
|
|
AddArguments("--").
|
|
WithRepo(repo).
|
|
WithStdoutCopy(w).
|
|
RunWithStderr(ctx); err != nil {
|
|
return 0, err
|
|
}
|
|
return w.numLines, nil
|
|
}
|
|
|
|
// GetDiff generates and returns patch data between given revisions, optimized for human readability
|
|
func (repo *Repository) GetDiff(ctx context.Context, compareArg string, w io.Writer) error {
|
|
return gitcmd.NewCommand("diff", "-p").AddDynamicArguments(compareArg).
|
|
WithRepo(repo).
|
|
WithStdoutCopy(w).
|
|
RunWithStderr(ctx)
|
|
}
|
|
|
|
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
|
|
func (repo *Repository) GetDiffBinary(ctx context.Context, compareArg string, w io.Writer) error {
|
|
return gitcmd.NewCommand("diff", "-p", "--binary", "--histogram").
|
|
AddDynamicArguments(compareArg).
|
|
WithRepo(repo).
|
|
WithStdoutCopy(w).
|
|
RunWithStderr(ctx)
|
|
}
|
|
|
|
// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`
|
|
func (repo *Repository) GetPatch(ctx context.Context, compareArg string, w io.Writer) error {
|
|
return gitcmd.NewCommand("format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg).
|
|
WithRepo(repo).
|
|
WithStdoutCopy(w).
|
|
RunWithStderr(ctx)
|
|
}
|
|
|
|
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
|
|
// If base is undefined empty SHA (zeros), it only returns the files changed in the head commit
|
|
// If base is the SHA of an empty tree (EmptyTreeSHA), it returns the files changes from the initial commit to the head commit
|
|
func (repo *Repository) GetFilesChangedBetween(ctx context.Context, base, head string) ([]string, error) {
|
|
objectFormat, err := repo.GetObjectFormat(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd := gitcmd.NewCommand("diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
|
|
if base == objectFormat.EmptyObjectID().String() {
|
|
cmd.AddDynamicArguments(head)
|
|
} else {
|
|
cmd.AddDynamicArguments(base, head)
|
|
}
|
|
stdout, _, err := cmd.WithRepo(repo).RunStdString(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
split := strings.Split(stdout, "\000")
|
|
|
|
// Because Git will always emit filenames with a terminal NUL ignore the last entry in the split - which will always be empty.
|
|
if len(split) > 0 {
|
|
split = split[:len(split)-1]
|
|
}
|
|
|
|
return split, err
|
|
}
|
|
|
|
// ReadPatchCommit will check if a diff patch exists and return stats
|
|
func (repo *Repository) ReadPatchCommit(prID int64) (commitSHA string, err error) {
|
|
// Migrated repositories download patches to "pulls" location
|
|
repoFS := gitrepo.RepoLocalFS(repo)
|
|
loadPatch, err := repoFS.Open(fmt.Sprintf("pulls/%d.patch", prID))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer loadPatch.Close()
|
|
// Read only the first line of the patch - usually it contains the first commit made in patch
|
|
scanner := bufio.NewScanner(loadPatch)
|
|
scanner.Scan()
|
|
// Parse the Patch stats, sometimes Migration returns a 404 for the patch file
|
|
commitSHAGroups := globalVars().patchCommits.FindStringSubmatch(scanner.Text())
|
|
if len(commitSHAGroups) != 0 {
|
|
commitSHA = commitSHAGroups[1]
|
|
} else {
|
|
return "", errors.New("patch file doesn't contain valid commit ID")
|
|
}
|
|
return commitSHA, nil
|
|
}
|