diff --git a/features/authentication.feature b/features/authentication.feature index 6ad2808b..bfe65796 100644 --- a/features/authentication.feature +++ b/features/authentication.feature @@ -171,6 +171,57 @@ Feature: OAuth authentication Then the output should not contain "github.com password for mislav" And the file "../home/.config/hub" should contain "oauth_token: OTOKEN" + Scenario: XDG: legacy config found, credentials from GITHUB_USER & GITHUB_PASSWORD + Given I am "mislav" on github.com with OAuth token "LTOKEN" + And the GitHub API server: + """ + post('/authorizations') { + assert_basic_auth 'mislav', 'kitty' + status 201 + json :token => 'OTOKEN' + } + get('/user') { + json :login => 'mislav' + } + post('/user/repos') { + halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' + status 201 + json :full_name => 'mislav/dotfiles' + } + """ + And $GITHUB_USER is "mislav" + And $GITHUB_PASSWORD is "kitty" + And $XDG_CONFIG_HOME is "$HOME/.xdg" + When I successfully run `hub create` + Then the file "../home/.xdg/hub" should contain "oauth_token: OTOKEN" + And the stderr should contain exactly: + """ + Notice: config file found but not respected at: $HOME/.config/hub + You might want to move it to `$HOME/.xdg/hub' to avoid re-authenticating.\n + """ + + Scenario: XDG: config from secondary directories + Given I am "mislav" on github.com with OAuth token "OTOKEN" + And the GitHub API server: + """ + get('/user') { + json :login => 'mislav' + } + post('/user/repos') { + halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' + status 201 + json :full_name => 'mislav/dotfiles' + } + """ + And $GITHUB_USER is "mislav" + And $GITHUB_PASSWORD is "kitty" + And $XDG_CONFIG_HOME is "$HOME/.xdg" + And $XDG_CONFIG_DIRS is "/etc/xdg-nonsense:$HOME/.xdg-dir" + When I move the file named "../home/.config/hub" to "../home/.xdg-dir/hub" + And I successfully run `hub create` + Then the file "../home/.xdg/hub" should not exist + And the stderr should contain exactly "" + Scenario: Credentials from GITHUB_TOKEN Given the GitHub API server: """ @@ -390,7 +441,7 @@ Feature: OAuth authentication When I run `hub create` interactively Then the output should contain: """ - /home/.config/hub: permission denied\n + $HOME/.config/hub: permission denied\n """ And the exit status should be 1 And the file "../home/.config/hub" should not exist diff --git a/features/steps.rb b/features/steps.rb index 1b49d12f..3f09f142 100644 --- a/features/steps.rb +++ b/features/steps.rb @@ -40,7 +40,7 @@ Given(/^I am "([^"]*)" on ([\S]+)(?: with OAuth token "([^"]*)")?$/) do |name, h end Given(/^\$(\w+) is "([^"]*)"$/) do |name, value| - set_env name, value + set_env name, value.gsub(/\$([A-Z_]+)/) { ENV.fetch($1) } end Given(/^I am in "([^"]*)" git repo$/) do |dir_name| @@ -153,6 +153,13 @@ Given(/^the current dir is not a repo$/) do end end +When(/^I move the file named "([^"]+)" to "([^"]+)"?$/) do |source, dest| + in_current_dir do + FileUtils.mkdir_p(File.dirname(dest)) + FileUtils.mv(source, dest) + end +end + Given(/^the GitHub API server:$/) do |endpoints_str| @server = Hub::LocalServer.start_sinatra do eval endpoints_str, binding diff --git a/features/support/env.rb b/features/support/env.rb index 603bbad3..7085f578 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -191,4 +191,16 @@ World Module.new { def shell_escape(message) message.to_s.gsub(/['"\\ $]/) { |m| "\\#{m}" } end + + %w[output_from stdout_from stderr_from all_stdout all_stderr].each do |m| + define_method(m) do |*args| + home = ENV['HOME'].to_s + output = super(*args) + if home.empty? + output + else + output.gsub(home, '$HOME') + end + end + end } diff --git a/github/config.go b/github/config.go index c57a2055..e0f67eae 100644 --- a/github/config.go +++ b/github/config.go @@ -17,15 +17,6 @@ import ( "golang.org/x/crypto/ssh/terminal" ) -var defaultConfigsFile string - -func init() { - homeDir, err := homedir.Dir() - utils.Check(err) - - defaultConfigsFile = filepath.Join(homeDir, ".config", "hub") -} - type yamlHost struct { User string `yaml:"user"` OAuthToken string `yaml:"oauth_token"` @@ -243,13 +234,65 @@ func (c *Config) selectHost() *Host { return c.Hosts[i-1] } +var defaultConfigsFile string + func configsFile() string { - configsFile := os.Getenv("HUB_CONFIG") - if configsFile == "" { - configsFile = defaultConfigsFile + if configFromEnv := os.Getenv("HUB_CONFIG"); configFromEnv != "" { + return configFromEnv + } + if defaultConfigsFile == "" { + var err error + defaultConfigsFile, err = determineConfigLocation() + utils.Check(err) + } + return defaultConfigsFile +} + +func homeConfig() (string, error) { + if home, err := homedir.Dir(); err != nil { + return "", err + } else { + return filepath.Join(home, ".config"), nil + } +} + +func determineConfigLocation() (string, error) { + var err error + + xdgHome := os.Getenv("XDG_CONFIG_HOME") + configDir := xdgHome + if configDir == "" { + if configDir, err = homeConfig(); err != nil { + return "", err + } } - return configsFile + xdgDirs := os.Getenv("XDG_CONFIG_DIRS") + if xdgDirs == "" { + xdgDirs = "/etc/xdg" + } + searchDirs := append([]string{configDir}, strings.Split(xdgDirs, ":")...) + + for _, dir := range searchDirs { + filename := filepath.Join(dir, "hub") + if _, err := os.Stat(filename); err == nil { + return filename, nil + } + } + + configFile := filepath.Join(configDir, "hub") + + if configDir == xdgHome { + if homeDir, _ := homeConfig(); homeDir != "" { + legacyConfig := filepath.Join(homeDir, "hub") + if _, err = os.Stat(legacyConfig); err == nil { + ui.Errorf("Notice: config file found but not respected at: %s\n", legacyConfig) + ui.Errorf("You might want to move it to `%s' to avoid re-authenticating.\n", configFile) + } + } + } + + return configFile, nil } var currentConfig *Config @@ -315,7 +358,7 @@ func CheckWriteable(filename string) error { // Public for testing purpose func CreateTestConfigs(user, token string) *Config { f, _ := ioutil.TempFile("", "test-config") - defaultConfigsFile = f.Name() + os.Setenv("HUB_CONFIG", f.Name()) host := &Host{ User: "jingweno",