Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions v3/UNRELEASED_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ After processing, the content will be moved to the main changelog and this file

## Changed
<!-- Changes in existing functionality -->
- Replace `github.com/go-git/go-git/v5` direct dependency with calls to the system `git` CLI (`internal/git` package). **Note: `git` must be installed on the system.** Graceful errors are returned when `git` is not found in `PATH`.


## Fixed
<!-- Bug fixes -->
Expand Down
2 changes: 1 addition & 1 deletion v3/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ require (
github.com/charmbracelet/huh v0.8.0
github.com/coder/websocket v1.8.14
github.com/ebitengine/purego v0.9.1
github.com/go-git/go-git/v5 v5.19.1
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e
github.com/go-ole/go-ole v1.3.0
github.com/godbus/dbus/v5 v5.2.2
Expand Down Expand Up @@ -55,6 +54,7 @@ require (
github.com/clipperhouse/uax29/v2 v2.4.0 // indirect
github.com/danieljoos/wincred v1.2.3 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-git/go-git/v5 v5.19.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/konoui/go-qsort v0.1.0 // indirect
github.com/lmittmann/tint v1.0.3 // indirect
Expand Down
31 changes: 6 additions & 25 deletions v3/internal/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import (
"regexp"
"strings"

"github.com/go-git/go-git/v5/config"
"github.com/wailsapp/wails/v3/internal/defaults"
"github.com/wailsapp/wails/v3/internal/term"

"github.com/go-git/go-git/v5"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v3/internal/defaults"
"github.com/wailsapp/wails/v3/internal/flags"
"github.com/wailsapp/wails/v3/internal/git"
"github.com/wailsapp/wails/v3/internal/templates"
"github.com/wailsapp/wails/v3/internal/term"
)

var DisableFooter bool
Expand Down Expand Up @@ -70,32 +68,15 @@ func gitURLToModulePath(gitURL string) string {
}

func initGitRepository(projectDir string, gitURL string) error {
// Initialize repository
repo, err := git.PlainInit(projectDir, false)
if err != nil {
if err := git.Init(projectDir); err != nil {
return fmt.Errorf("failed to initialize git repository: %w", err)
}

// Create remote
_, err = repo.CreateRemote(&config.RemoteConfig{
Name: "origin",
URLs: []string{gitURL},
})
if err != nil {
if err := git.RemoteAdd(projectDir, "origin", gitURL); err != nil {
return fmt.Errorf("failed to create git remote: %w", err)
}

// Stage all files
worktree, err := repo.Worktree()
if err != nil {
return fmt.Errorf("failed to get git worktree: %w", err)
}

_, err = worktree.Add(".")
if err != nil {
if err := git.AddAll(projectDir); err != nil {
return fmt.Errorf("failed to stage files: %w", err)
}

return nil
}

Expand Down
10 changes: 3 additions & 7 deletions v3/internal/doctor/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (

"github.com/wailsapp/wails/v3/internal/buildinfo"

"github.com/go-git/go-git/v5"
"github.com/jaypipes/ghw"
"github.com/wailsapp/wails/v3/internal/git"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v3/internal/lo"
"github.com/wailsapp/wails/v3/internal/operatingsystem"
Expand Down Expand Up @@ -78,12 +78,8 @@ func Run() (err error) {
if wailsPackage != nil && wailsPackage.Replace != nil {
wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path)
// Get the latest commit hash
repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, ".."))
if err == nil {
head, err := repo.Head()
if err == nil {
wailsVersion += " (" + head.Hash().String()[:8] + ")"
}
if hash, err := git.HeadHash(filepath.Join(wailsPackage.Replace.Path, "..")); err == nil {
wailsVersion += " (" + hash + ")"
}
}

Expand Down
73 changes: 73 additions & 0 deletions v3/internal/git/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package git

import (
"bytes"
"errors"
"fmt"
"os/exec"
"strings"
)

// ErrNotInstalled is returned when git is not found in PATH.
var ErrNotInstalled = errors.New("git is not installed; please install git from https://git-scm.com")

func isNotFound(err error) bool {
var execErr *exec.Error
return errors.As(err, &execErr) && errors.Is(execErr.Err, exec.ErrNotFound)
}

func run(args ...string) error {
out, err := exec.Command("git", args...).CombinedOutput()
if err != nil {
if isNotFound(err) {
return ErrNotInstalled
}
return fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, bytes.TrimSpace(out))
}
Comment on lines +25 to +26
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact git arguments before embedding them in errors.

strings.Join(args, " ") can expose credentials/tokens when url contains embedded auth (e.g., https://token@host/...) in Clone/RemoteAdd failures. Return a sanitized command string instead.

Suggested fix
 import (
 	"bytes"
 	"errors"
 	"fmt"
+	"net/url"
 	"os/exec"
 	"strings"
 )
@@
+func sanitizeArg(arg string) string {
+	u, err := url.Parse(arg)
+	if err != nil || u.User == nil {
+		return arg
+	}
+	u.User = url.UserPassword("****", "****")
+	return u.String()
+}
+
+func sanitizeArgs(args []string) string {
+	out := make([]string, len(args))
+	for i, a := range args {
+		out[i] = sanitizeArg(a)
+	}
+	return strings.Join(out, " ")
+}
+
 func run(args ...string) error {
 	out, err := exec.Command("git", args...).CombinedOutput()
@@
-		return fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, bytes.TrimSpace(out))
+		return fmt.Errorf("git %s: %w\n%s", sanitizeArgs(args), err, bytes.TrimSpace(out))
 	}
 	return nil
 }
@@
-		return "", fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, bytes.TrimSpace(out))
+		return "", fmt.Errorf("git %s: %w\n%s", sanitizeArgs(args), err, bytes.TrimSpace(out))
 	}

Also applies to: 36-37

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/git/git.go` around lines 25 - 26, Return includes raw git args
which can leak credentials; sanitize any URL auth before embedding args in error
messages. Implement a helper (e.g., sanitizeGitArgs or redactAuthFromURL) that
iterates over args and for any token of the form scheme://user@host or
containing userinfo removes/replaces the userinfo (or replaces entire auth
segment with "<redacted>" using url.Parse and clearing User) and use that
sanitized join in the fmt.Errorf call instead of strings.Join(args, " "). Apply
this change to the error returns that reference args (including the occurrences
around Clone and RemoteAdd).

return nil
}

func output(args ...string) (string, error) {
out, err := exec.Command("git", args...).CombinedOutput()
if err != nil {
if isNotFound(err) {
return "", ErrNotInstalled
}
return "", fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, bytes.TrimSpace(out))
}
return strings.TrimSpace(string(out)), nil
}

// HeadHash returns the short (8-character) commit hash of HEAD in dir.
func HeadHash(dir string) (string, error) {
hash, err := output("-C", dir, "rev-parse", "HEAD")
if err != nil {
return "", err
}
return hash[:8], nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard short hash output before slicing.

hash[:8] can panic if output is unexpectedly shorter than 8 chars. Add a length check and return an error instead.

Suggested fix
 func HeadHash(dir string) (string, error) {
 	hash, err := output("-C", dir, "rev-parse", "HEAD")
 	if err != nil {
 		return "", err
 	}
+	if len(hash) < 8 {
+		return "", fmt.Errorf("git rev-parse returned short hash %q", hash)
+	}
 	return hash[:8], nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/git/git.go` at line 47, The code slices hash with hash[:8] which
can panic if hash is shorter than 8 bytes; in the function that currently
returns "hash[:8], nil" (look for the function in git.go that constructs/returns
variable named hash) add a guard: check len(hash) >= 8 and if not return an
error (e.g., fmt.Errorf("short git hash: %d bytes", len(hash))) instead of
slicing, otherwise return the 8-byte prefix; add fmt import if necessary.

}

// Clone clones url into dir. If tag is non-empty, checks out that tag or branch.
func Clone(url, dir, tag string) error {
args := []string{"clone", "--quiet"}
if tag != "" {
args = append(args, "--branch", tag)
}
args = append(args, url, dir)
return run(args...)
}

// Init initializes a new git repository at dir.
func Init(dir string) error {
return run("-C", dir, "init", "--quiet")
}

// RemoteAdd adds a named remote to the repository at dir.
func RemoteAdd(dir, name, url string) error {
return run("-C", dir, "remote", "add", name, url)
}

// AddAll stages all files in the repository at dir.
func AddAll(dir string) error {
return run("-C", dir, "add", ".")
}
112 changes: 112 additions & 0 deletions v3/internal/git/git_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package git

import (
"errors"
"os"
"os/exec"
"path/filepath"
"testing"
)

// makeRepo creates a temp git repository with one commit and a v1.0.0 tag.
func makeRepo(t *testing.T) string {
t.Helper()
dir := t.TempDir()
cmds := [][]string{
{"-C", dir, "init", "--quiet"},
{"-C", dir, "-c", "user.email=t@t.com", "-c", "user.name=T", "commit", "--allow-empty", "--quiet", "-m", "init"},
{"-C", dir, "tag", "v1.0.0"},
}
for _, args := range cmds {
if out, err := exec.Command("git", args...).CombinedOutput(); err != nil {
t.Fatalf("setup git %v: %v\n%s", args, err, out)
}
}
return dir
}

func TestInit_Success(t *testing.T) {
if err := Init(t.TempDir()); err != nil {
t.Fatal(err)
}
}

func TestInit_Error(t *testing.T) {
// git -C on a non-existent path fails
if err := Init("/nonexistent_wails_test_path_xyz"); err == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use an OS-agnostic missing path in TestInit_Error.

"/nonexistent_wails_test_path_xyz" is platform-specific and can be flaky. Build a guaranteed-missing path under t.TempDir() instead.

Suggested fix
 func TestInit_Error(t *testing.T) {
 	// git -C on a non-existent path fails
-	if err := Init("/nonexistent_wails_test_path_xyz"); err == nil {
+	missing := filepath.Join(t.TempDir(), "does-not-exist")
+	if err := Init(missing); err == nil {
 		t.Fatal("expected error for nonexistent path")
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := Init("/nonexistent_wails_test_path_xyz"); err == nil {
func TestInit_Error(t *testing.T) {
// git -C on a non-existent path fails
missing := filepath.Join(t.TempDir(), "does-not-exist")
if err := Init(missing); err == nil {
t.Fatal("expected error for nonexistent path")
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/git/git_test.go` at line 36, TestInit_Error uses a hardcoded
absolute path that can be platform-flaky; change the test to construct a
guaranteed-missing path using the test's temporary directory (t.TempDir()) and
pass that to Init so the path is OS-agnostic and reliably absent; update the
TestInit_Error test to create a non-existent subpath under t.TempDir() (e.g.
filepath.Join(t.TempDir(), "nonexistent_subdir")) and assert Init(...) returns
an error.

t.Fatal("expected error for nonexistent path")
}
}

func TestRemoteAdd_Success(t *testing.T) {
dir := t.TempDir()
if err := Init(dir); err != nil {
t.Fatal(err)
}
if err := RemoteAdd(dir, "origin", "https://example.com/repo.git"); err != nil {
t.Fatal(err)
}
}

func TestAddAll_Success(t *testing.T) {
dir := t.TempDir()
if err := Init(dir); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0644); err != nil {
t.Fatal(err)
}
if err := AddAll(dir); err != nil {
t.Fatal(err)
}
}

func TestHeadHash_Success(t *testing.T) {
src := makeRepo(t)
hash, err := HeadHash(src)
if err != nil {
t.Fatal(err)
}
if len(hash) != 8 {
t.Errorf("expected 8-char hash, got %q (len %d)", hash, len(hash))
}
}

func TestHeadHash_Error(t *testing.T) {
// not a git repository
if _, err := HeadHash(t.TempDir()); err == nil {
t.Fatal("expected error for non-repo dir")
}
}

func TestClone_WithoutTag(t *testing.T) {
src := makeRepo(t)
dst := filepath.Join(t.TempDir(), "clone")
if err := Clone(src, dst, ""); err != nil {
t.Fatal(err)
}
}

func TestClone_WithTag(t *testing.T) {
src := makeRepo(t)
dst := filepath.Join(t.TempDir(), "clone")
if err := Clone(src, dst, "v1.0.0"); err != nil {
t.Fatal(err)
}
}

func TestRun_NotInstalled(t *testing.T) {
t.Setenv("PATH", "/nonexistent_path_that_does_not_exist")
err := Init(t.TempDir())
if !errors.Is(err, ErrNotInstalled) {
t.Fatalf("expected ErrNotInstalled, got %v", err)
}
}

func TestOutput_NotInstalled(t *testing.T) {
t.Setenv("PATH", "/nonexistent_path_that_does_not_exist")
_, err := HeadHash(t.TempDir())
if !errors.Is(err, ErrNotInstalled) {
t.Fatalf("expected ErrNotInstalled, got %v", err)
}
}
22 changes: 7 additions & 15 deletions v3/internal/templates/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ import (

"errors"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v3/internal/git"
"github.com/wailsapp/wails/v3/internal/debug"

"github.com/wailsapp/wails/v3/internal/flags"
Expand Down Expand Up @@ -220,27 +219,20 @@ func parseTemplate(templateFS fs.FS, templateName string) (Template, error) {
return result, nil
}

// Clones the given uri and returns the temporary cloned directory
// gitclone clones uri into a temporary directory and returns its path.
func gitclone(uri string) (string, error) {
// Create temporary directory
dirname, err := os.MkdirTemp("", "wails-template-*")
if err != nil {
return "", err
}

// Parse remote template url and version number
templateInfo := strings.Split(uri, "@")
cloneOption := &git.CloneOptions{
URL: templateInfo[0],
parts := strings.SplitN(uri, "@", 2)
url, tag := parts[0], ""
if len(parts) > 1 {
tag = parts[1]
}
Comment on lines +229 to 233
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for SCP-like URL patterns in test files and documentation
rg -n 'git@[a-zA-Z0-9.-]+:' --type-add 'docs:*.md' --type=docs --type=go

Repository: wailsapp/wails

Length of output: 407


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show parsing code around templates.go lines ~229-233
sed -n '200,260p' v3/internal/templates/templates.go

echo '---'
# Show gitURLToModulePath in init.go lines ~23-27 (and surrounding)
sed -n '1,120p' v3/internal/commands/init.go

echo '---'
# Search for SplitN(uri, "@", 2) and related parsing
rg -n 'SplitN\([^,]+,\s*"@",\s*2\)' -S v3/internal || true
rg -n 'LastIndex\([^,]*,\s*"@"\)' -S v3/internal || true

echo '---'
# Search for parsing "@", tag, or "@<version>" in templates/commands packages
rg -n '@\s*[vV]?[0-9]' -S v3/internal || true
rg -n 'module.*tag|tag.*module|`@v`' -S v3/internal || true

echo '---'
# Search for git@ URLs with an appended tag in tests/docs
rg -n 'git@[a-zA-Z0-9.-]+:[^ \t'\""]+@[^\s"'\"']+' -S v3/internal . || true

Repository: wailsapp/wails

Length of output: 50459


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show parsing code around templates.go lines ~229-233
sed -n '200,260p' v3/internal/templates/templates.go

echo '---'
# Show gitURLToModulePath in init.go lines ~23-27 (and surrounding)
sed -n '1,120p' v3/internal/commands/init.go

echo '---'
# Search for SplitN(uri, "@", 2) and related parsing
rg -n 'SplitN\([^,]+,\s*"@",\s*2\)' -S v3/internal || true
rg -n 'LastIndex\([^,]*,\s*"@"\)' -S v3/internal || true

echo '---'
# Search for parsing "@", tag, or "@<version>" in templates/commands packages
rg -n '@\s*[vV]?[0-9]' -S v3/internal || true
rg -n 'module.*tag|tag.*module|`@v`' -S v3/internal || true

echo '---'
# Search for git@ URLs with an appended tag in tests/docs
rg -n 'git@[a-zA-Z0-9.-]+:[^ \t'"']+@[^\s'"']+' -S v3/internal . || true

Repository: wailsapp/wails

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focus on template URI parsing and call sites
rg -n "getRemoteTemplate\(|gitclone\(" v3/internal/templates v3/internal/commands v3/internal | head -n 50

# Show templates.go around getRemoteTemplate and call sites for gitclone
sed -n '150,280p' v3/internal/templates/templates.go

# Show where uri passed into getRemoteTemplate; search for "-t" or Template flag usage
rg -n "TemplateName|remote template|sourceRemote|getRemoteTemplate" v3/internal/commands v3/internal/templates | head -n 80

# Inspect internal/git Clone
fd -t f -a "internal/git" >/dev/null 2>&1 || true
git_files=$(fd -t f "clone.go|git.go|repo.go|remote" v3/internal/git 2>/dev/null || true)
echo "git_files:"; echo "$git_files" | head
# list files
ls -la v3/internal/git || true

# print go files in v3/internal/git (small)
find v3/internal/git -maxdepth 1 -type f -name "*.go" -print

# show Clone implementation
rg -n "func\s+Clone\s*\(" v3/internal/git -S || true
rg -n "tag string" v3/internal/git -S || true

# Show git.Clone
clone_file=$(rg -n "func\s+Clone\s*\(" v3/internal/git -S --files-with-matches || true)
echo "clone_file(s):"
echo "$clone_file"
if [ -n "$clone_file" ]; then
  # show content of first match file around func
  f=$(echo "$clone_file" | head -n 1)
  rg -n "func\s+Clone\s*\(" "$f" -n
  start=$(rg -n "func\s+Clone\s*\(" "$f" | head -n 1 | cut -d: -f1)
  # print around start
  from=$((start-20)); to=$((start+120))
  sed -n "${from},${to}p" "$f"
fi

# Search for tests specifically for templates with @ tag in scp-like URLs
rg -n "wails.*init.*-t|template.*`@v`|`@v`[0-9]" v3/internal -S --glob='*test.go' || true
rg -n "SplitN\(uri, \"@\", 2\)" v3/internal -S || true

# Search for patterns of scp-like URL with `@ref` in tests/docs (limit output)
rg -n "git@[a-zA-Z0-9.-]+:[^\"'\s]+@v[0-9]" v3 -S --glob='*.md' --glob='*test.go' || true

Repository: wailsapp/wails

Length of output: 8285


Fix remote template @ref parsing for SCP-like SSH URLs.

v3/internal/templates/templates.go uses strings.SplitN(uri, "@", 2) and treats the suffix as the git ref passed to git clone --branch. For an SSH/SCP-style template like git@github.com:user/repo.git@v1.0.0, this incorrectly sets url="git" and tag="github.com:user/repo.git@v1.0.0".

Use “split on the last @” and only accept the suffix as a ref when it’s actually a ref (e.g., the suffix shouldn’t still look like SCP host/path by containing :), rather than blindly splitting on the first @.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/templates/templates.go` around lines 229 - 233, The current
parsing splits on the first "@" and breaks SCP-style SSH URLs; change it to
split on the last "@" instead by using strings.LastIndex(uri, "@") to derive url
and tag (url = uri[:i], tag = uri[i+1:]) and only treat the suffix as a ref when
it doesn’t look like an SCP host/path (e.g., if tag contains ":" or otherwise
looks like host:path treat it as part of the URL and set tag = ""), updating the
variables used later in templates.go (the uri/url/tag parsing logic)
accordingly.

if len(templateInfo) > 1 {
cloneOption.ReferenceName = plumbing.NewTagReferenceName(templateInfo[1])
}

_, err = git.PlainClone(dirname, false, cloneOption)

return dirname, err

return dirname, git.Clone(url, dirname, tag)
}
Comment on lines +222 to 236
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Temp directory not cleaned up when clone fails.

If git.Clone fails, gitclone returns the temp directory path along with the error. The caller's cleanup function is defined only after gitclone succeeds, so failed clones leave orphaned temp directories.

🛠️ Proposed fix
 func gitclone(uri string) (string, error) {
 	dirname, err := os.MkdirTemp("", "wails-template-*")
 	if err != nil {
 		return "", err
 	}

 	parts := strings.SplitN(uri, "@", 2)
 	url, tag := parts[0], ""
 	if len(parts) > 1 {
 		tag = parts[1]
 	}

-	return dirname, git.Clone(url, dirname, tag)
+	if err := git.Clone(url, dirname, tag); err != nil {
+		os.RemoveAll(dirname)
+		return "", err
+	}
+	return dirname, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// gitclone clones uri into a temporary directory and returns its path.
func gitclone(uri string) (string, error) {
// Create temporary directory
dirname, err := os.MkdirTemp("", "wails-template-*")
if err != nil {
return "", err
}
// Parse remote template url and version number
templateInfo := strings.Split(uri, "@")
cloneOption := &git.CloneOptions{
URL: templateInfo[0],
parts := strings.SplitN(uri, "@", 2)
url, tag := parts[0], ""
if len(parts) > 1 {
tag = parts[1]
}
if len(templateInfo) > 1 {
cloneOption.ReferenceName = plumbing.NewTagReferenceName(templateInfo[1])
}
_, err = git.PlainClone(dirname, false, cloneOption)
return dirname, err
return dirname, git.Clone(url, dirname, tag)
}
// gitclone clones uri into a temporary directory and returns its path.
func gitclone(uri string) (string, error) {
dirname, err := os.MkdirTemp("", "wails-template-*")
if err != nil {
return "", err
}
parts := strings.SplitN(uri, "@", 2)
url, tag := parts[0], ""
if len(parts) > 1 {
tag = parts[1]
}
if err := git.Clone(url, dirname, tag); err != nil {
os.RemoveAll(dirname)
return "", err
}
return dirname, nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/templates/templates.go` around lines 222 - 236, The gitclone
function currently creates a temp dir with os.MkdirTemp but returns that path
when git.Clone fails, leaving orphaned directories; update gitclone so that
after calling git.Clone(url, dirname, tag) any non-nil error triggers cleanup of
the temp directory (os.RemoveAll(dirname)) before returning the error (and do
not return the dirname on error), keeping the existing parsing of uri/tag and
preserving normal success behavior.


func getRemoteTemplate(uri string) (*Template, error) {
Expand Down
10 changes: 3 additions & 7 deletions v3/pkg/application/application_debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package application

import (
"github.com/go-git/go-git/v5"
"github.com/wailsapp/wails/v3/internal/git"
"github.com/wailsapp/wails/v3/internal/lo"
"github.com/wailsapp/wails/v3/internal/version"
"path/filepath"
Expand Down Expand Up @@ -53,12 +53,8 @@ func (a *App) logStartup() {
if wailsPackage != nil && wailsPackage.Replace != nil {
wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path)
// Get the latest commit hash
repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, ".."))
if err == nil {
head, err := repo.Head()
if err == nil {
wailsVersion += " (" + head.Hash().String()[:8] + ")"
}
if hash, err := git.HeadHash(filepath.Join(wailsPackage.Replace.Path, "..")); err == nil {
wailsVersion += " (" + hash + ")"
}
}
args = append(args, "Wails", wailsVersion)
Expand Down
10 changes: 3 additions & 7 deletions v3/pkg/doctor-ng/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"runtime/debug"
"strings"

"github.com/go-git/go-git/v5"
"github.com/wailsapp/wails/v3/internal/git"
"github.com/wailsapp/wails/v3/internal/lo"
"github.com/wailsapp/wails/v3/internal/operatingsystem"
"github.com/wailsapp/wails/v3/internal/version"
Expand Down Expand Up @@ -84,12 +84,8 @@ func (d *Doctor) collectBuildInfo() error {

if found && wailsPackage != nil && wailsPackage.Replace != nil {
wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path)
repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, ".."))
if err == nil {
head, err := repo.Head()
if err == nil {
wailsVersion += " (" + head.Hash().String()[:8] + ")"
}
if hash, err := git.HeadHash(filepath.Join(wailsPackage.Replace.Path, "..")); err == nil {
wailsVersion += " (" + hash + ")"
}
}

Expand Down
Loading