Support XDG Base Directory Specification

Respect XDG_CONFIG_HOME and XDG_CONFIG_DIRS.
https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables

Fixes #1048, closes #1061
This commit is contained in:
Mislav Marohnić 2018-06-07 19:57:02 +02:00
Родитель 02db852769
Коммит f8bd902145
4 изменённых файлов: 129 добавлений и 16 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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",