Skip to content

Commit cae2a24

Browse files
committed
Remove all local values when unsetting profile keys
1 parent a8120c6 commit cae2a24

3 files changed

Lines changed: 110 additions & 6 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ git-profile unuse work
132132
Under the hood, this unsets the local Git config values, for example:
133133

134134
```bash
135-
git config --local --unset user.name
136-
git config --local --unset user.email
137-
git config --local --unset user.signingkey
135+
git config --local --unset-all user.name
136+
git config --local --unset-all user.email
137+
git config --local --unset-all user.signingkey
138138
```
139139

140140
Run without arguments to remove the currently applied profile:

‎cmd/unuse_git_test.go‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
12+
"github.com/matryer/is"
13+
)
14+
15+
func TestUnuseWithGitValues(t *testing.T) {
16+
for _, values := range [][]string{nil, {"work@example.com"}, {"work@example.com", "work@example.com", "extra@example.com"}} {
17+
t.Run(fmt.Sprintf("%d values", len(values)), func(t *testing.T) {
18+
is := is.New(t)
19+
repo, filename := setupCLIExitTest(t, false)
20+
_, err := exec.Command("git", "-C", repo, "config", "--local", "--unset", "user.email").CombinedOutput()
21+
is.NoErr(err)
22+
23+
for _, value := range values {
24+
_, err := exec.Command("git", "-C", repo, "config", "--local", "--add", "user.email", value).CombinedOutput()
25+
is.NoErr(err)
26+
}
27+
28+
globalPath := filepath.Join(t.TempDir(), "gitconfig")
29+
globalConfig := []byte("[user]\n\temail = global@example.com\n\temail = other@example.com\n")
30+
is.NoErr(os.WriteFile(globalPath, globalConfig, 0o600))
31+
t.Setenv("GIT_CONFIG_GLOBAL", globalPath)
32+
33+
// The second invocation must also succeed after the local keys are gone.
34+
for _, args := range [][]string{{"unuse"}, {"unuse", "work"}} {
35+
child := cliExitTestCommand(repo, filename, args...)
36+
37+
var stdout, stderr bytes.Buffer
38+
39+
child.Stdout = &stdout
40+
child.Stderr = &stderr
41+
is.NoErr(child.Run())
42+
is.Equal(child.ProcessState.ExitCode(), 0)
43+
is.True(strings.Contains(stdout.String(), "Successfully removed"))
44+
is.Equal(stderr.String(), "")
45+
46+
for _, key := range []string{"user.email", currentProfileKey} {
47+
query := exec.Command("git", "-C", repo, "config", "--local", "--get-all", key)
48+
out, err := query.Output()
49+
is.True(err != nil)
50+
is.Equal(query.ProcessState.ExitCode(), 1)
51+
is.Equal(string(out), "")
52+
}
53+
}
54+
55+
afterGlobal, err := os.ReadFile(globalPath)
56+
is.NoErr(err)
57+
is.Equal(afterGlobal, globalConfig)
58+
})
59+
}
60+
}
61+
62+
func TestUnuseReportsGitErrors(t *testing.T) {
63+
for _, failure := range []string{"locked config", "invalid key", "unwritable directory"} {
64+
t.Run(failure, func(t *testing.T) {
65+
is := is.New(t)
66+
repo, filename := setupCLIExitTest(t, false)
67+
gitDir := filepath.Join(repo, ".git")
68+
gitConfig := filepath.Join(gitDir, "config")
69+
before, err := os.ReadFile(gitConfig)
70+
is.NoErr(err)
71+
72+
switch failure {
73+
case "locked config":
74+
is.NoErr(os.WriteFile(gitConfig+".lock", []byte("locked"), 0o600))
75+
case "invalid key":
76+
is.NoErr(os.WriteFile(filename, []byte(`{"profiles":{"work":{"invalid":"value"}}}`), 0o600))
77+
case "unwritable directory":
78+
if os.Geteuid() == 0 {
79+
t.Skip("root can write to directories regardless of mode bits")
80+
}
81+
82+
info, err := os.Stat(gitDir)
83+
is.NoErr(err)
84+
t.Cleanup(func() { is.NoErr(os.Chmod(gitDir, info.Mode().Perm())) })
85+
is.NoErr(os.Chmod(gitDir, 0o500))
86+
}
87+
88+
child := cliExitTestCommand(repo, filename, "unuse", "work")
89+
90+
var stdout, stderr bytes.Buffer
91+
92+
child.Stdout = &stdout
93+
child.Stderr = &stderr
94+
is.True(child.Run() != nil)
95+
is.Equal(child.ProcessState.ExitCode(), 1)
96+
is.Equal(stdout.String(), "")
97+
is.True(strings.Contains(stderr.String(), "Unable to interact with git to remove current profile"))
98+
99+
after, err := os.ReadFile(gitConfig)
100+
is.NoErr(err)
101+
is.Equal(after, before)
102+
})
103+
}
104+
}

‎internal/git/git.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
// gitConfigKeyNotFound is the exit code git returns from
10-
// `git config --unset` when the key is not present.
10+
// `git config --unset-all` when the key is not present.
1111
const gitConfigKeyNotFound = 5
1212

1313
// Git is a vcs
@@ -49,10 +49,10 @@ func (g *Git) Set(key string, value string) error {
4949
return err
5050
}
5151

52-
// Unset removes a key from git local config.
52+
// Unset removes all values of a key from git local config.
5353
// A missing key is not treated as an error.
5454
func (g *Git) Unset(key string) error {
55-
_, err := g.command("config", "--local", "--unset", key).CombinedOutput()
55+
_, err := g.command("config", "--local", "--unset-all", key).CombinedOutput()
5656
if err != nil {
5757
var exitErr *exec.ExitError
5858
if errors.As(err, &exitErr) && exitErr.ExitCode() == gitConfigKeyNotFound {

0 commit comments

Comments
 (0)