-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Automatically configure user.name and user.email in .gitconfig #2981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
1e75526
5a2ea9b
1fe721a
a3a6ce5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -212,26 +212,29 @@ func TestAgent(t *testing.T) { | |
| StartupScript: fmt.Sprintf("echo %s > %s", content, tempPath), | ||
| }, 0) | ||
|
|
||
| var gotContent string | ||
| require.Eventually(t, func() bool { | ||
| content, err := os.ReadFile(tempPath) | ||
| if err != nil { | ||
| return false | ||
| } | ||
| if len(content) == 0 { | ||
| return false | ||
| } | ||
| if runtime.GOOS == "windows" { | ||
| // Windows uses UTF16! 🪟🪟🪟 | ||
| content, _, err = transform.Bytes(unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder(), content) | ||
| require.NoError(t, err) | ||
| } | ||
| gotContent = string(content) | ||
| return true | ||
| }, 15*time.Second, 100*time.Millisecond) | ||
| gotContent := readFileContents(t, tempPath) | ||
| require.Equal(t, content, strings.TrimSpace(gotContent)) | ||
| }) | ||
|
|
||
| t.Run("GitAutoconfig", func(t *testing.T) { | ||
| t.Parallel() | ||
| configPath := filepath.Join(os.TempDir(), "gitconfig") | ||
|
|
||
| initialContent := "[user]\nemail = [email protected]\n" | ||
| err := os.WriteFile(configPath, []byte(initialContent), 0600) | ||
| require.NoError(t, err) | ||
|
|
||
| setupAgent(t, agent.Metadata{ | ||
| OwnerUsername: "Kermit the Frog", | ||
| OwnerEmail: "[email protected]", | ||
| GitConfigPath: configPath, | ||
| }, 0) | ||
|
|
||
| gotContent := readFileContents(t, configPath) | ||
| require.Contains(t, gotContent, "name = Kermit the Frog") | ||
| require.Contains(t, gotContent, "email = [email protected]") | ||
| }) | ||
|
|
||
| t.Run("ReconnectingPTY", func(t *testing.T) { | ||
| t.Parallel() | ||
| if runtime.GOOS == "windows" { | ||
|
|
@@ -436,7 +439,7 @@ func setupSSHSession(t *testing.T, options agent.Metadata) *ssh.Session { | |
|
|
||
| func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) *agent.Conn { | ||
| client, server := provisionersdk.TransportPipe() | ||
| closer := agent.New(func(ctx context.Context, logger slog.Logger) (agent.Metadata, *peerbroker.Listener, error) { | ||
| a := agent.New(func(ctx context.Context, logger slog.Logger) (agent.Metadata, *peerbroker.Listener, error) { | ||
| listener, err := peerbroker.Listen(server, nil) | ||
| return metadata, listener, err | ||
| }, &agent.Options{ | ||
|
|
@@ -446,7 +449,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) | |
| t.Cleanup(func() { | ||
| _ = client.Close() | ||
| _ = server.Close() | ||
| _ = closer.Close() | ||
| _ = a.Close() | ||
| }) | ||
| api := proto.NewDRPCPeerBrokerClient(provisionersdk.Conn(client)) | ||
| stream, err := api.NegotiateConnection(context.Background()) | ||
|
|
@@ -458,6 +461,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) | |
| t.Cleanup(func() { | ||
| _ = conn.Close() | ||
| }) | ||
| require.Eventually(t, a.SetupComplete, 10*time.Second, 100*time.Millisecond) | ||
|
|
||
| return &agent.Conn{ | ||
| Negotiator: api, | ||
|
|
@@ -495,3 +499,14 @@ func assertWritePayload(t *testing.T, w io.Writer, payload []byte) { | |
| assert.NoError(t, err, "write payload") | ||
| assert.Equal(t, len(payload), n, "payload length does not match") | ||
| } | ||
|
|
||
| func readFileContents(t *testing.T, path string) string { | ||
| content, err := os.ReadFile(path) | ||
| require.NoError(t, err) | ||
| if runtime.GOOS == "windows" { | ||
| // Windows uses UTF16! 🪟🪟🪟 | ||
| content, _, err = transform.Bytes(unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder(), content) | ||
| require.NoError(t, err) | ||
| } | ||
| return string(content) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package agent | ||
|
|
||
| import ( | ||
| "context" | ||
| "os/exec" | ||
| "os/user" | ||
| "strings" | ||
|
|
||
| "golang.org/x/xerrors" | ||
| ) | ||
|
|
||
| var errNoGitAvailable = xerrors.New("Git does not seem to be installed") | ||
|
|
||
| func setupGitconfig(ctx context.Context, configPath string, params map[string]string) error { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced we should be touching anything in the user's home directory like this. I'd be in favor of removing these variables and exposing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a fair point. So are you suggesting putting something like this into the Terraform startup script? One of the goals discussed in #2665 was to make this continue to work "automatically", but if it's handled by a short, easy-to-paste snippet that we put in our example templates, that's probably just as good.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with @kylecarbs that we probably shouldn't touch this file. For instance, this would break the use-case where a user stores their git config in Regarding adding It's unfortunate the env variables can't be used as fallback, they have the benefit of being outside the users configuration.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be nice if we had the startup scripts/tasks in the terraform file that run after the agent starts. The git setup could be a configurable task rather than special thing we always run.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'd be nice if we could specify task order, and checking out dotfiles would be part of this task order. Every task would run in the users full shell environment (e.g. login shell), then exit. For instance:
The
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mafredri I'm proposing a template author adds their own environment variables: resource "coder_agent" "testing" {
env {
GIT_AUTHOR_NAME = "${data.coder_workspace.me.owner_name}"
GIT_AUTHOR_EMAIL = "${data.coder_workspace.me.owner_email}"
}
}This can just be a note in our docs for guidance. |
||
| if configPath == "" { | ||
| return nil | ||
| } | ||
| if strings.HasPrefix(configPath, "~/") { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work on Windows?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's admittedly a bit janky, but it does work, because we're passing the literal string I guess it would be a bit cleaner (though more verbose) to turn |
||
| currentUser, err := user.Current() | ||
| if err != nil { | ||
| return xerrors.Errorf("get current user: %w", err) | ||
| } | ||
| configPath = currentUser.HomeDir + "/" + configPath[2:] | ||
dwahler marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| cmd := exec.CommandContext(ctx, "git", "--version") | ||
| err := cmd.Run() | ||
| if err != nil { | ||
| return errNoGitAvailable | ||
| } | ||
|
|
||
| for name, value := range params { | ||
| err = setGitConfigIfUnset(ctx, configPath, name, value) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func setGitConfigIfUnset(ctx context.Context, configPath, name, value string) error { | ||
| cmd := exec.CommandContext(ctx, "git", "config", "--file", configPath, "--get", name) | ||
| err := cmd.Run() | ||
| if err == nil { | ||
| // an exit status of 0 means the value exists, so there's nothing to do | ||
| return nil | ||
| } | ||
| // an exit status of 1 means the value is unset | ||
| if cmd.ProcessState.ExitCode() != 1 { | ||
| return xerrors.Errorf("getting %s: %w", name, err) | ||
| } | ||
|
|
||
| cmd = exec.CommandContext(ctx, "git", "config", "--file", configPath, "--add", name, value) | ||
| _, err = cmd.Output() | ||
| if err != nil { | ||
| return xerrors.Errorf("setting %s=%s: %w", name, value, err) | ||
| } | ||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: nice unit test!