Skip to content

Commit

Permalink
fix: don't touch .ddev dir in every ddev command, avoid overwriti…
Browse files Browse the repository at this point in the history
…ng `.ddev/.gitignore` if unchanged, fixes php-perfect/ddev-intellij-plugin#361 (ddev#6510)
  • Loading branch information
stasadev authored Sep 5, 2024
1 parent ccac0fb commit d7c0aff
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 29 deletions.
4 changes: 4 additions & 0 deletions cmd/ddev/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func addCustomCommands(rootCmd *cobra.Command) error {
}

for _, commandSet := range []string{projectCommandPath, globalCommandPath} {
// If the item isn't a directory, skip it.
if !fileutil.IsDirectory(commandSet) {
continue
}
commandDirs, err := fileutil.ListFilesInDirFullPath(commandSet, false)
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions cmd/ddev/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ func init() {
// Determine if Docker is running by getting the version.
// This helps to prevent a user from seeing the Cobra error: "Error: unknown command "<custom command>" for ddev"
_, err := dockerutil.GetDockerVersion()
// ddev --version may be called without Docker available.
if err != nil && len(os.Args) > 1 && os.Args[1] != "--version" && os.Args[1] != "hostname" {

if err != nil && !dockerutil.CanRunWithoutDocker() {
util.Failed("Could not connect to a Docker provider. Please start or install a Docker provider.\nFor install help go to: https://ddev.readthedocs.io/en/stable/users/install/docker-installation/")
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/ddevapp/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ func PopulateExamplesCommandsHomeadditions(appName string) error {
return err
}

// We don't want to populate the project's .ddev directory
// unless the project name is explicitly specified.
if appName == "" {
return nil
}

app, err := GetActiveApp(appName)
// If we have an error from GetActiveApp, it means we're not in a project directory
// That is not an error. It means we can not do this work, so return nil.
Expand Down
13 changes: 10 additions & 3 deletions pkg/ddevapp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1437,9 +1437,16 @@ func PrepDdevDirectory(app *DdevApp) error {
}
}

err = os.MkdirAll(filepath.Join(dir, "web-entrypoint.d"), 0755)
if err != nil {
return err
// Pre-create a few dirs so we can be sure they are owned by the user and not root.
dirs := []string{
"web-entrypoint.d",
"xhprof",
}
for _, subdir := range dirs {
err = os.MkdirAll(filepath.Join(dir, subdir), 0755)
if err != nil {
return err
}
}

// Some of the listed items are wildcards or directories, and if they are, there's an error
Expand Down
11 changes: 6 additions & 5 deletions pkg/ddevapp/ddevapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -1295,18 +1295,19 @@ Fix with 'ddev config global --required-docker-compose-version="" --use-docker-c
util.Warning("Unable to PrepDdevDirectory: %v", err)
}

// The .ddev directory may still need to be populated, especially in tests
err = PopulateExamplesCommandsHomeadditions(app.Name)
if err != nil {
return err
}
// Make sure that any ports allocated are available.
// and of course add to global project list as well
err = app.UpdateGlobalProjectList()
if err != nil {
return err
}

// The .ddev directory may still need to be populated, especially in tests
err = PopulateExamplesCommandsHomeadditions(app.Name)
if err != nil {
return err
}

err = DownloadMutagenIfNeededAndEnabled(app)
if err != nil {
return err
Expand Down
49 changes: 31 additions & 18 deletions pkg/ddevapp/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ddevapp

import (
"bytes"
"fmt"
"os"
"path"
Expand Down Expand Up @@ -228,6 +229,7 @@ type ignoreTemplateContents struct {
// Each value in ignores will be added as a new line to the .gitignore.
func CreateGitIgnore(targetDir string, ignores ...string) error {
gitIgnoreFilePath := filepath.Join(targetDir, ".gitignore")
existingContent := ""

if fileutil.FileExists(gitIgnoreFilePath) {
sigFound, err := fileutil.FgrepStringInFile(gitIgnoreFilePath, nodeps.DdevFileSignature)
Expand All @@ -240,50 +242,61 @@ func CreateGitIgnore(targetDir string, ignores ...string) error {
util.Warning("User-managed %s will not be managed/overwritten by ddev", gitIgnoreFilePath)
return nil
}
// Read the existing content for future comparison.
if gitIgnoreFileBytes, err := os.ReadFile(gitIgnoreFilePath); err == nil {
existingContent = string(gitIgnoreFileBytes)
}
// Otherwise, remove the existing file to prevent surprising template results
err = os.Remove(gitIgnoreFilePath)
if err != nil {
return err
if existingContent == "" {
err = os.Remove(gitIgnoreFilePath)
if err != nil {
return err
}
}
}

err := os.MkdirAll(targetDir, 0777)
if err != nil {
return err
}

generatedIgnores := []string{}
// Get the content for the .gitignore file.
var generatedIgnores []string
for _, p := range ignores {
pFullPath := filepath.Join(targetDir, p)
sigFound, err := fileutil.FgrepStringInFile(pFullPath, nodeps.DdevFileSignature)
//if err != nil {
// util.Warning("file not found: %s: %v", p, err)
//}
if sigFound || err != nil {
generatedIgnores = append(generatedIgnores, p)
}
}

tmpl, err := template.New("gitignore").Funcs(getTemplateFuncMap()).Parse(gitIgnoreTemplate)
t, err := template.New("gitignore").Funcs(getTemplateFuncMap()).Parse(gitIgnoreTemplate)
if err != nil {
return err
}

file, err := os.OpenFile(gitIgnoreFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer util.CheckClose(file)

parms := ignoreTemplateContents{
// Execute the template into the buffer.
var buf bytes.Buffer
ignoredItems := ignoreTemplateContents{
Signature: nodeps.DdevFileSignature,
IgnoredItems: generatedIgnores,
}

//nolint: revive
if err = tmpl.Execute(file, parms); err != nil {
if err = t.Execute(&buf, ignoredItems); err != nil {
return err
}
// Only write the file if the generated content differs from the existing content.
if buf.String() != existingContent {
file, err := os.OpenFile(gitIgnoreFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer util.CheckClose(file)

// Write the new content to the file.
if _, err = buf.WriteTo(file); err != nil {
return err
}
}
return nil
}

Expand Down
23 changes: 22 additions & 1 deletion pkg/dockerutil/dockerutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"slices"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -159,7 +160,7 @@ func GetDockerClient() (context.Context, *dockerClient.Client) {
if DockerHost == "" {
DockerContext, DockerHost, err = GetDockerContext()
// ddev --version may be called without Docker client or context available, ignore err
if err != nil && len(os.Args) > 1 && os.Args[1] != "--version" && os.Args[1] != "hostname" {
if err != nil && !CanRunWithoutDocker() {
util.Failed("Unable to get Docker context: %v", err)
}
util.Debug("GetDockerClient: DockerContext=%s, DockerHost=%s", DockerContext, DockerHost)
Expand Down Expand Up @@ -1904,3 +1905,23 @@ func GetContainerNames(containers []dockerTypes.Container) []string {
func IsErrNotFound(err error) bool {
return dockerClient.IsErrNotFound(err)
}

// CanRunWithoutDocker returns true if the command or flag can run without Docker.
func CanRunWithoutDocker() bool {
if len(os.Args) < 2 {
return true
}
// Check the first arg
if slices.Contains([]string{"-v", "--version", "-h", "--help", "help", "hostname"}, os.Args[1]) {
return true
}
// Check the last arg
if slices.Contains([]string{"-h", "--help"}, os.Args[len(os.Args)-1]) {
// Some commands don't support Cobra help, because they are wrappers
if slices.Contains([]string{"composer"}, os.Args[1]) {
return false
}
return true
}
return false
}

0 comments on commit d7c0aff

Please sign in to comment.