Skip to content

Commit 260720c

Browse files
authored
Clear new locally created files before pushing a fix (#772)
1 parent 55d80fa commit 260720c

File tree

3 files changed

+157
-9
lines changed

3 files changed

+157
-9
lines changed

scanrepository/scanrepository.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"github.com/go-git/go-git/v5"
78
biutils "github.com/jfrog/build-info-go/utils"
89
"os"
910
"path/filepath"
@@ -313,7 +314,7 @@ func (cfp *ScanRepositoryCmd) fixMultiplePackages(fullProjectPath string, vulner
313314
return
314315
}
315316

316-
// fixIssuesSinglePR fixes all the vulnerabilities in a single aggregated pull request.
317+
// Fixes all the vulnerabilities in a single aggregated pull request.
317318
// If an existing aggregated fix is present, it checks for different scan results.
318319
// If the scan results are the same, no action is taken.
319320
// Otherwise, it performs a force push to the same branch and reopens the pull request if it was closed.
@@ -398,6 +399,9 @@ func (cfp *ScanRepositoryCmd) openFixingPullRequest(repository *utils.Repository
398399
return &utils.ErrNothingToCommit{PackageName: vulnDetails.ImpactedDependencyName}
399400
}
400401
commitMessage := cfp.gitManager.GenerateCommitMessage(vulnDetails.ImpactedDependencyName, vulnDetails.SuggestedFixedVersion)
402+
if err = cfp.cleanNewFilesMissingInRemote(); err != nil {
403+
log.Warn(fmt.Sprintf("failed fo clean untracked files from '%s' due to the following errors: %s", cfp.baseWd, err.Error()))
404+
}
401405
if err = cfp.gitManager.AddAllAndCommit(commitMessage); err != nil {
402406
return
403407
}
@@ -443,10 +447,13 @@ func (cfp *ScanRepositoryCmd) createOrUpdatePullRequest(repository *utils.Reposi
443447
return pullRequestInfo, utils.DeletePullRequestComments(repository, cfp.scanDetails.Client(), int(pullRequestInfo.ID))
444448
}
445449

446-
// openAggregatedPullRequest handles the opening or updating of a pull request when the aggregate mode is active.
450+
// Handles the opening or updating of a pull request when the aggregate mode is active.
447451
// If a pull request is already open, Frogbot will update the branch and the pull request body.
448452
func (cfp *ScanRepositoryCmd) openAggregatedPullRequest(repository *utils.Repository, fixBranchName string, pullRequestInfo *vcsclient.PullRequestInfo, vulnerabilities []*utils.VulnerabilityDetails) (err error) {
449453
commitMessage := cfp.gitManager.GenerateAggregatedCommitMessage(cfp.projectTech)
454+
if err = cfp.cleanNewFilesMissingInRemote(); err != nil {
455+
return
456+
}
450457
if err = cfp.gitManager.AddAllAndCommit(commitMessage); err != nil {
451458
return
452459
}
@@ -456,6 +463,38 @@ func (cfp *ScanRepositoryCmd) openAggregatedPullRequest(repository *utils.Reposi
456463
return cfp.handleFixPullRequestContent(repository, fixBranchName, pullRequestInfo, vulnerabilities...)
457464
}
458465

466+
func (cfp *ScanRepositoryCmd) cleanNewFilesMissingInRemote() error {
467+
// Open the local repository
468+
localRepo, err := git.PlainOpen(cfp.baseWd)
469+
if err != nil {
470+
return err
471+
}
472+
473+
// Getting the repository working tree
474+
worktree, err := localRepo.Worktree()
475+
if err != nil {
476+
return err
477+
}
478+
479+
// Getting the working tree status
480+
gitStatus, err := worktree.Status()
481+
if err != nil {
482+
return err
483+
}
484+
485+
for relativeFilePath, status := range gitStatus {
486+
if status.Worktree == git.Untracked {
487+
log.Debug(fmt.Sprintf("Untracking file '%s' that was created locally during the scan/fix process", relativeFilePath))
488+
fileDeletionErr := os.Remove(filepath.Join(cfp.baseWd, relativeFilePath))
489+
if fileDeletionErr != nil {
490+
err = errors.Join(err, fmt.Errorf("file '%s': %s\n", relativeFilePath, fileDeletionErr.Error()))
491+
continue
492+
}
493+
}
494+
}
495+
return err
496+
}
497+
459498
func (cfp *ScanRepositoryCmd) preparePullRequestDetails(vulnerabilitiesDetails ...*utils.VulnerabilityDetails) (prTitle, prBody string, otherComments []string, err error) {
460499
if cfp.dryRun && cfp.aggregateFixes {
461500
// For testings, don't compare pull request body as scan results order may change.

scanrepository/scanrepository_test.go

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@ package scanrepository
33
import (
44
"errors"
55
"fmt"
6-
"net/http/httptest"
7-
"os"
8-
"os/exec"
9-
"path/filepath"
10-
"strings"
11-
"testing"
12-
136
"github.com/google/go-github/v45/github"
147
biutils "github.com/jfrog/build-info-go/utils"
158
"github.com/jfrog/frogbot/v2/utils"
@@ -25,6 +18,12 @@ import (
2518
"github.com/jfrog/jfrog-client-go/xray/services"
2619
"github.com/stretchr/testify/assert"
2720
"github.com/stretchr/testify/require"
21+
"net/http/httptest"
22+
"os"
23+
"os/exec"
24+
"path/filepath"
25+
"strings"
26+
"testing"
2827
)
2928

3029
const rootTestDir = "scanrepository"
@@ -85,6 +84,7 @@ func TestScanRepositoryCmd_Run(t *testing.T) {
8584
configPath string
8685
expectedPackagesInBranch map[string][]string
8786
expectedVersionUpdatesInBranch map[string][]string
87+
expectedMissingFilesInBranch map[string][]string
8888
packageDescriptorPaths []string
8989
aggregateFixes bool
9090
allowPartialResults bool
@@ -100,6 +100,7 @@ func TestScanRepositoryCmd_Run(t *testing.T) {
100100
testName: "aggregate-multi-dir",
101101
expectedPackagesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"uuid", "minimatch", "mpath", "minimist"}},
102102
expectedVersionUpdatesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"^1.2.6", "^9.0.0", "^0.8.4", "^3.0.5"}},
103+
expectedMissingFilesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"npm1/package-lock.json", "npm2/package-lock.json"}},
103104
packageDescriptorPaths: []string{"npm1/package.json", "npm2/package.json"},
104105
aggregateFixes: true,
105106
configPath: "../testdata/scanrepository/cmd/aggregate-multi-dir/.frogbot/frogbot-config.yml",
@@ -108,6 +109,7 @@ func TestScanRepositoryCmd_Run(t *testing.T) {
108109
testName: "aggregate-multi-project",
109110
expectedPackagesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"uuid", "minimatch", "mpath"}, "frogbot-update-Pip-dependencies-master": {"pyjwt", "pexpect"}},
110111
expectedVersionUpdatesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"^9.0.0", "^0.8.4", "^3.0.5"}, "frogbot-update-Pip-dependencies-master": {"2.4.0"}},
112+
expectedMissingFilesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"npm/package-lock.json"}},
111113
packageDescriptorPaths: []string{"npm/package.json", "pip/requirements.txt"},
112114
aggregateFixes: true,
113115
configPath: "../testdata/scanrepository/cmd/aggregate-multi-project/.frogbot/frogbot-config.yml",
@@ -221,6 +223,14 @@ func TestScanRepositoryCmd_Run(t *testing.T) {
221223
assert.Contains(t, string(resultDiff), updatedVersion)
222224
}
223225
}
226+
227+
if len(test.expectedMissingFilesInBranch) > 0 {
228+
for branch, expectedMissingFiles := range test.expectedMissingFilesInBranch {
229+
resultDiff, err := verifyLockFileDiff(branch, expectedMissingFiles...)
230+
assert.NoError(t, err)
231+
assert.Empty(t, resultDiff)
232+
}
233+
}
224234
})
225235
}
226236
}
@@ -669,6 +679,72 @@ func TestPreparePullRequestDetails(t *testing.T) {
669679
assert.ElementsMatch(t, expectedExtraComments, extraComments)
670680
}
671681

682+
// This test simulates the cleaning action of cleanNewFilesMissingInRemote.
683+
// Every file that has been newly CREATED after cloning the repo (here - after creating .git repo) should be removed. Every other file should be kept.
684+
func TestCleanNewFilesMissingInRemote(t *testing.T) {
685+
testCases := []struct {
686+
name string
687+
relativeTestDirPath string
688+
createFileBeforeInit bool
689+
}{
690+
{
691+
name: "new_file_should_remain",
692+
relativeTestDirPath: filepath.Join(rootTestDir, "cmd", "aggregate"),
693+
createFileBeforeInit: true,
694+
},
695+
{
696+
name: "new_file_should_be_deleted",
697+
relativeTestDirPath: filepath.Join(rootTestDir, "cmd", "aggregate"),
698+
createFileBeforeInit: false,
699+
},
700+
}
701+
702+
baseDir, outerErr := os.Getwd()
703+
assert.NoError(t, outerErr)
704+
defer func() {
705+
assert.NoError(t, os.Chdir(baseDir))
706+
}()
707+
708+
for _, test := range testCases {
709+
t.Run(test.name, func(t *testing.T) {
710+
testDir, cleanup := utils.CopyTestdataProjectsToTemp(t, test.relativeTestDirPath)
711+
defer cleanup()
712+
713+
var file *os.File
714+
if test.createFileBeforeInit {
715+
var fileError error
716+
file, fileError = os.CreateTemp(testDir, test.name)
717+
assert.NoError(t, fileError)
718+
}
719+
720+
utils.CreateDotGitWithCommit(t, testDir, "1234", "")
721+
722+
if !test.createFileBeforeInit {
723+
var fileError error
724+
file, fileError = os.CreateTemp(testDir, test.name)
725+
assert.NoError(t, fileError)
726+
}
727+
728+
// Making a change in the file so it will be modified in the working tree
729+
_, err := file.WriteString("My initial string")
730+
assert.NoError(t, err)
731+
assert.NoError(t, file.Close())
732+
733+
scanRepoCmd := ScanRepositoryCmd{baseWd: testDir}
734+
assert.NoError(t, scanRepoCmd.cleanNewFilesMissingInRemote())
735+
736+
exists, err := fileutils.IsFileExists(file.Name(), false)
737+
assert.NoError(t, err)
738+
if test.createFileBeforeInit {
739+
assert.True(t, exists)
740+
} else {
741+
assert.False(t, exists)
742+
}
743+
})
744+
}
745+
746+
}
747+
672748
func verifyTechnologyNaming(t *testing.T, scanResponse []services.ScanResponse, expectedType string) {
673749
for _, resp := range scanResponse {
674750
for _, vulnerability := range resp.Vulnerabilities {
@@ -698,3 +774,24 @@ func verifyDependencyFileDiff(baseBranch string, fixBranch string, packageDescri
698774
}
699775
return
700776
}
777+
778+
func verifyLockFileDiff(branchToInspect string, lockFiles ...string) (output []byte, err error) {
779+
log.Debug(fmt.Sprintf("Checking lock files differences in %s between branches 'master' and '%s'", lockFiles, branchToInspect))
780+
// Suppress condition always false warning
781+
//goland:noinspection ALL
782+
var args []string
783+
if coreutils.IsWindows() {
784+
args = []string{"/c", "git", "ls-tree", branchToInspect, "--"}
785+
args = append(args, lockFiles...)
786+
output, err = exec.Command("cmd", args...).Output()
787+
} else {
788+
args = []string{"ls-tree", branchToInspect, "--"}
789+
args = append(args, lockFiles...)
790+
output, err = exec.Command("git", args...).Output()
791+
}
792+
var exitError *exec.ExitError
793+
if errors.As(err, &exitError) {
794+
err = errors.New("git error: " + string(exitError.Stderr))
795+
}
796+
return
797+
}

utils/git.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,18 @@ func (gm *GitManager) dryRunClone(destination string) error {
466466
return nil
467467
}
468468

469+
func (gm *GitManager) GetRemoteGitUrl() string {
470+
return gm.remoteGitUrl
471+
}
472+
473+
func (gm *GitManager) GetAuth() *githttp.BasicAuth {
474+
return gm.auth
475+
}
476+
477+
func (gm *GitManager) GetRemoteName() string {
478+
return gm.remoteName
479+
}
480+
469481
func toBasicAuth(username, token string) *githttp.BasicAuth {
470482
// The username can be anything except for an empty string
471483
if username == "" {

0 commit comments

Comments
 (0)