Skip to content

Commit daebfc7

Browse files
authored
feat: introduce StrictConflicts setting to optionally enforce file ownership conflict errors (#205)
1 parent d8c108a commit daebfc7

6 files changed

Lines changed: 39 additions & 13 deletions

File tree

cli/install/install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (cmd *installCmd) SetFlags(f *flag.FlagSet) {
5858
f.BoolVar(&cmd.dbOnly, "db_only", false, "only make changes to DB, don't perform install system actions")
5959
f.StringVar(&cmd.sources, "sources", "", "comma separated list of sources, setting this overrides local .repo files")
6060
f.BoolVar(&cmd.dryRun, "dry_run", false, "show what would be installed but do not install")
61-
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files")
61+
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files (only required if StrictConflicts is enabled in config)")
6262
}
6363

6464
func (cmd *installCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...any) subcommands.ExitStatus {

cli/update/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (cmd *updateCmd) SetFlags(f *flag.FlagSet) {
5353
f.BoolVar(&cmd.dbOnly, "db_only", false, "only make changes to DB, don't perform install system actions")
5454
f.StringVar(&cmd.sources, "sources", "", "comma separated list of sources, setting this overrides local .repo files")
5555
f.BoolVar(&cmd.dryRun, "dry_run", false, "check for updates and print them, but do not prompt to install")
56-
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files")
56+
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files (only required if StrictConflicts is enabled in config)")
5757
}
5858

5959
func (cmd *updateCmd) Execute(ctx context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {

googet.goospec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{{$version := "3.3.1@0" -}}
1+
{{$version := "3.3.2@0" -}}
22
{
33
"name": "googet",
44
"version": "{{$version}}",
@@ -15,6 +15,7 @@
1515
"path": "install.ps1"
1616
},
1717
"releaseNotes": [
18+
"3.3.2 - Feat: introduce StrictConflicts setting to optionally enforce file ownership conflict errors.",
1819
"3.3.1 - Fix: Prevent path traversal vulnerabilities in file unpacking and add workflow permissions.",
1920
"3.3.0 - Refactor: Update FindRepoLatest to prioritize repo priority, version, and architecture with lock support.",
2021
"3.3.0 - Refactor: Add file ownership conflict checks to prevent overwrites.",

install/install.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/google/googet/v2/goolib"
3232
"github.com/google/googet/v2/oswrap"
3333
"github.com/google/googet/v2/remove"
34+
"github.com/google/googet/v2/settings"
3435
"github.com/google/googet/v2/system"
3536
"github.com/google/logger"
3637
)
@@ -410,10 +411,14 @@ func makeInstallFunction(src, dst string, insFiles map[string]string, dbOnly, fo
410411
outPath := filepath.Join(dst, strings.TrimPrefix(path, src))
411412

412413
if owner, ok := conflictMap[outPath]; ok && !fi.IsDir() {
413-
if !force {
414+
if settings.StrictConflicts && !force {
414415
return fmt.Errorf("file conflict: %s is already owned by package %s", outPath, owner)
415416
}
416-
logger.Infof("Warning: file conflict: %s is already owned by package %s, overwriting due to force flag", outPath, owner)
417+
if force {
418+
logger.Infof("Warning: file conflict: %s is already owned by package %s, overwriting due to force flag", outPath, owner)
419+
} else {
420+
logger.Infof("Warning: file conflict: %s is already owned by package %s, overwriting because `StrictConflicts` is not set", outPath, owner)
421+
}
417422
fmt.Printf("Warning: file conflict: %s is already owned by package %s, overwriting...\n", outPath, owner)
418423
}
419424

install/install_test.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,11 +572,11 @@ func TestMakeInstallFunction(t *testing.T) {
572572
fi, _ := f.Stat()
573573
f.Close()
574574

575-
// Test 1: Conflict without force -> Error
575+
// Test 1: Conflict without force -> Success by default
576576
fnBlock := makeInstallFunction(srcDir, dstDir, make(map[string]string), false, false, cm)
577577
errBlock := fnBlock(filepath.Join(srcDir, "conflicting_file"), fi, nil)
578-
if errBlock == nil {
579-
t.Errorf("expected conflict error, got nil")
578+
if errBlock != nil {
579+
t.Errorf("expected no conflict error by default, got %v", errBlock)
580580
}
581581

582582
// Test 2: Conflict with force -> Success
@@ -585,4 +585,20 @@ func TestMakeInstallFunction(t *testing.T) {
585585
if errForce != nil {
586586
t.Errorf("expected no error with force, got %v", errForce)
587587
}
588+
589+
// Test 3: Conflict without force in strict mode -> Error
590+
settings.StrictConflicts = true
591+
defer func() { settings.StrictConflicts = false }()
592+
fnStrict := makeInstallFunction(srcDir, dstDir, make(map[string]string), false, false, cm)
593+
errStrict := fnStrict(filepath.Join(srcDir, "conflicting_file"), fi, nil)
594+
if errStrict == nil {
595+
t.Errorf("expected conflict error in strict mode, got nil")
596+
}
597+
598+
// Test 4: Conflict with force in strict mode -> Success
599+
fnStrictForce := makeInstallFunction(srcDir, dstDir, make(map[string]string), false, true, cm)
600+
errStrictForce := fnStrictForce(filepath.Join(srcDir, "conflicting_file"), fi, nil)
601+
if errStrictForce != nil {
602+
t.Errorf("expected no error with force in strict mode, got %v", errStrictForce)
603+
}
588604
}

settings/settings.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ var (
2929
ProxyServer string
3030
// AllowUnsafeURL allows HTTP repos; set from googet.conf.
3131
AllowUnsafeURL bool
32+
// StrictConflicts enables strict enforcement of file ownership conflicts.
33+
StrictConflicts bool
3234
)
3335

3436
// Initialize reads the initial settings.
@@ -76,11 +78,12 @@ func RepoDir() string {
7678

7779
// conf represents a googet configuration file.
7880
type conf struct {
79-
Archs []string
80-
CacheLife string
81-
LockFileMaxAge string
82-
ProxyServer string
83-
AllowUnsafeURL bool
81+
Archs []string
82+
CacheLife string
83+
LockFileMaxAge string
84+
ProxyServer string
85+
AllowUnsafeURL bool
86+
StrictConflicts bool
8487
}
8588

8689
// unmarshalConfFile returns a conf from a YAML configuration file.
@@ -138,4 +141,5 @@ func readConf(filename string) {
138141
}
139142

140143
AllowUnsafeURL = gc.AllowUnsafeURL
144+
StrictConflicts = gc.StrictConflicts
141145
}

0 commit comments

Comments
 (0)