Skip to content

Commit

Permalink
⚡ QD-9824 Add information about changed files to scope files.
Browse files Browse the repository at this point in the history
  • Loading branch information
avafanasiev committed Aug 31, 2024
1 parent ac9c583 commit ad8de6f
Show file tree
Hide file tree
Showing 5 changed files with 413 additions and 91 deletions.
18 changes: 8 additions & 10 deletions core/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,18 +407,12 @@ func writeChangesFile(options *QodanaOptions, start string, end string) (string,
if start == "" || end == "" {
return "", fmt.Errorf("no commits given")
}
diff, err := platform.GitDiffNameOnly(options.ProjectDir, start, end)
changedFiles, err := platform.GitChangedFiles(options.ProjectDir, start, end)
if err != nil {
return "", err
}
emptyDiff := true
for _, e := range diff {
emptyDiff = emptyDiff && (len(e) == 0)
if !emptyDiff {
break
}
}
if emptyDiff {

if len(changedFiles.Files) == 0 {
return "", fmt.Errorf("nothing to compare between %s and %s", start, end)
}
file, err := os.CreateTemp("", "diff-scope.txt")
Expand All @@ -432,7 +426,11 @@ func writeChangesFile(options *QodanaOptions, start string, end string) (string,
}
}()

_, err = file.WriteString(strings.Join(diff, "\n"))
jsonChanges, err := json.MarshalIndent(changedFiles, "", " ")
if err != nil {
return "", err
}
_, err = file.WriteString(string(jsonChanges))
if err != nil {
return "", fmt.Errorf("failed to write scope file: %w", err)
}
Expand Down
6 changes: 4 additions & 2 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZ
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
Expand Down Expand Up @@ -733,10 +735,10 @@ github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o=
github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
Expand Down
79 changes: 0 additions & 79 deletions platform/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ package platform
import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
log "github.com/sirupsen/logrus"
"os"
"os/exec"
"path/filepath"
"strings"
)

// gitRun runs the git command in the given directory and returns an error if any.
Expand Down Expand Up @@ -99,18 +95,6 @@ func GitBranch(cwd string) (string, error) {
return ref.Name().Short(), nil
}

func GitDiffNameOnly(cwd string, diffStart string, diffEnd string) ([]string, error) {
repo, err := openRepository(cwd)
if err != nil {
return []string{""}, err
}
files, err := getChangedFilesBetweenCommits(repo, cwd, diffStart, diffEnd)
if err != nil {
return []string{""}, err
}
return files, nil
}

func GitCurrentRevision(cwd string) (string, error) {
repo, err := openRepository(cwd)
if err != nil {
Expand All @@ -126,69 +110,6 @@ func GitCurrentRevision(cwd string) (string, error) {
return hash.String(), nil
}

// getChangedFilesBetweenCommits retrieves changed files between two commit hashes
func getChangedFilesBetweenCommits(repo *git.Repository, cwd, hash1, hash2 string) ([]string, error) {
absCwd, err := filepath.Abs(cwd)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path of root folder %s: %v", cwd, err)
}
commit1, err := repo.CommitObject(plumbing.NewHash(hash1))
if err != nil {
return nil, fmt.Errorf("failed to find commit %s: %v", hash1, err)
}

commit2, err := repo.CommitObject(plumbing.NewHash(hash2))
if err != nil {
return nil, fmt.Errorf("failed to find commit %s: %v", hash2, err)
}

tree1, err := commit1.Tree()
if err != nil {
return nil, fmt.Errorf("failed to get tree for commit %s: %v", hash1, err)
}

tree2, err := commit2.Tree()
if err != nil {
return nil, fmt.Errorf("failed to get tree for commit %s: %v", hash2, err)
}

changes, err := object.DiffTree(tree1, tree2)
if err != nil {
return nil, fmt.Errorf("failed to get changes between commits %s and %s: %v", hash1, hash2, err)
}

changedFilesMap := make(map[string]struct{})

for _, change := range changes {
if change.From.Name != "" {
changedFilesMap[change.From.Name] = struct{}{}
}
if change.To.Name != "" {
changedFilesMap[change.To.Name] = struct{}{}
}
}

// Get the repository's working directory
worktree, err := repo.Worktree()
if err != nil {
return nil, fmt.Errorf("failed to get worktree: %v", err)
}
repoRoot := worktree.Filesystem.Root()
var changedFiles = make([]string, 0)
for file := range changedFilesMap {
absolutePath, err := filepath.Abs(filepath.Join(repoRoot, file))
if err != nil {
return nil, fmt.Errorf("failed to get absolute path for file %s: %v", file, err)
}
isInSubfolder := strings.HasPrefix(absolutePath, absCwd+string(filepath.Separator))
if isInSubfolder {
changedFiles = append(changedFiles, absolutePath)
}
}

return changedFiles, nil
}

// openRepository finds the repository root directory and opens the Git repository
func openRepository(path string) (*git.Repository, error) {
// Attempt to open the repository, starting from the given path and searching upwards
Expand Down
194 changes: 194 additions & 0 deletions platform/git_changes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* Copyright 2021-2024 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package platform

import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/diff"
"github.com/go-git/go-git/v5/plumbing/object"
"path/filepath"
"regexp"
"strings"
)

type ChangedRegion struct {
FirstLine int `json:"firstLine"`
Count int `json:"count"`
}

type ChangedFile struct {
Path string `json:"path"`
Added []*ChangedRegion `json:"added"`
Deleted []*ChangedRegion `json:"deleted"`
}

type ChangedFiles struct {
Files []*ChangedFile `json:"files"`
}

func GitDiffNameOnly(cwd string, diffStart string, diffEnd string) ([]string, error) {
repo, err := openRepository(cwd)
if err != nil {
return []string{""}, err
}
changedFiles, err := getChangedFilesBetweenCommits(repo, cwd, diffStart, diffEnd)
if err != nil {
return []string{""}, err
}

var changedFileNames = make([]string, 0, len(changedFiles.Files))
for _, file := range changedFiles.Files {
changedFileNames = append(changedFileNames, file.Path)
}

return changedFileNames, nil
}

func GitChangedFiles(cwd string, diffStart string, diffEnd string) (ChangedFiles, error) {
repo, err := openRepository(cwd)
if err != nil {
return ChangedFiles{}, err
}
return getChangedFilesBetweenCommits(repo, cwd, diffStart, diffEnd)
}

// getChangedFilesBetweenCommits retrieves changed files between two commit hashes
func getChangedFilesBetweenCommits(repo *git.Repository, cwd, hash1, hash2 string) (ChangedFiles, error) {
absCwd, err := filepath.Abs(cwd)
if err != nil {
return ChangedFiles{}, fmt.Errorf("failed to get absolute path of root folder %s: %v", cwd, err)
}
commit1, err := repo.CommitObject(plumbing.NewHash(hash1))
if err != nil {
return ChangedFiles{}, fmt.Errorf("failed to find commit %s: %v", hash1, err)
}

commit2, err := repo.CommitObject(plumbing.NewHash(hash2))
if err != nil {
return ChangedFiles{}, fmt.Errorf("failed to find commit %s: %v", hash2, err)
}

tree1, err := commit1.Tree()
if err != nil {
return ChangedFiles{}, fmt.Errorf("failed to get tree for commit %s: %v", hash1, err)
}

tree2, err := commit2.Tree()
if err != nil {
return ChangedFiles{}, fmt.Errorf("failed to get tree for commit %s: %v", hash2, err)
}

changes, err := object.DiffTree(tree1, tree2)
if err != nil {
return ChangedFiles{}, fmt.Errorf("failed to get changes between commits %s and %s: %v", hash1, hash2, err)
}

changedFilesMap := make(map[string]*ChangedFile)
repoRoot, err := getRepoRoot(repo, err)

for _, change := range changes {
var path = ""
if change.From.Name != "" {
path = change.From.Name
} else {
path = change.To.Name
}
if path == "" {
continue
}
absolutePath, err := filepath.Abs(filepath.Join(repoRoot, path))
if err != nil {
return ChangedFiles{}, fmt.Errorf("failed to get absolute path for file %s: %v", path, err)
}
if !strings.HasPrefix(absolutePath, absCwd+string(filepath.Separator)) {
continue
}

changedFile, exists := changedFilesMap[absolutePath]
if !exists {
changedFile = &ChangedFile{
Path: absolutePath,
Added: make([]*ChangedRegion, 0),
Deleted: make([]*ChangedRegion, 0),
}
changedFilesMap[absolutePath] = changedFile
}

patch, err := change.Patch()
if err != nil {
return ChangedFiles{}, err
}

if len(patch.FilePatches()) == 0 {
continue
}
filePatch := patch.FilePatches()[0]
added, deleted := computeChangedRegions(filePatch.Chunks())
changedFile.Added = added
changedFile.Deleted = deleted
}
files := make([]*ChangedFile, 0, len(changedFilesMap))
for _, file := range changedFilesMap {
files = append(files, file)
}

return ChangedFiles{Files: files}, nil
}

func getRepoRoot(repo *git.Repository, err error) (string, error) {
worktree, err := repo.Worktree()
if err != nil {
return "", fmt.Errorf("failed to get worktree: %v", err)
}
repoRoot := worktree.Filesystem.Root()
return repoRoot, nil
}

func computeChangedRegions(chunks []diff.Chunk) ([]*ChangedRegion, []*ChangedRegion) {
var toLine = 1
var added = make([]*ChangedRegion, 0, len(chunks))
var deleted = make([]*ChangedRegion, 0, len(chunks))
for _, chunk := range chunks {
lines := splitLines(chunk.Content())
nLines := len(lines)
switch chunk.Type() {
case diff.Equal:
// same line in origin and in modified
toLine += nLines
case diff.Delete:
// deleted from the origin file
deleted = append(deleted, &ChangedRegion{FirstLine: toLine, Count: nLines})
case diff.Add:
// added to the new file
added = append(added, &ChangedRegion{FirstLine: toLine, Count: nLines})
toLine += nLines
}
}
return added, deleted
}

var lineSplitter = regexp.MustCompile(`[^\n]*(\n|$)`)

func splitLines(s string) []string {
ret := lineSplitter.FindAllString(s, -1)
if ret[len(ret)-1] == "" {
ret = ret[:len(ret)-1]
}
return ret
}
Loading

0 comments on commit ad8de6f

Please sign in to comment.