<commit_before>package gopencils

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

type respStruct struct {
	Login   string
	Id      int
	Name    string
	Message string
}
type binStruct struct {
	Args    string
	Headers map[string]string
	Url     string
	Json    map[string]interface{}
}

func TestResource_url(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.parseUrl(), "https://test-url.com/", "Parsed Url Should match")
	api.SetQuery(map[string]string{"key": "value"})
	assert.Equal(t, api.parseUrl(), "https://test-url.com/?key=value", "Parsed QueryString Url Should match")
	assert.Equal(t, api.Url, "", "Base Url Should be empty")
	assert.Equal(t, api.Api.Base, "https://test-url.com/", "Base Url Should be empty")
}

func TestResource_auth(t *testing.T) {
	api := Api("https://test-url.com", &BasicAuth{"username", "password"})
	assert.Equal(t, api.Api.BasicAuth.Username, "username", "Username should match")
	assert.Equal(t, api.Api.BasicAuth.Password, "password", "Password should match")
}

func TestResource_get(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/users/bndr", func(rw http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(rw, `{"login": "bndr","id": 1145456,"avatar_url": "https://avatars.githubusercontent.com/u/1145456?","gravatar_id": "8d05db0b0b8b74d5a0f93d0b1db22909","url": "https://api.github.com/users/bndr","html_url": "https://github.com/bndr","followers_url": "https://api.github.com/users/bndr/followers","following_url": "https://api.github.com/users/bndr/following{/other_user}","gists_url": "https://api.github.com/users/bndr/gists{/gist_id}","starred_url": "https://api.github.com/users/bndr/starred{/owner}{/repo}","subscriptions_url": "https://api.github.com/users/bndr/subscriptions","organizations_url": "https://api.github.com/users/bndr/orgs","repos_url": "https://api.github.com/users/bndr/repos","events_url": "https://api.github.com/users/bndr/events{/privacy}","received_events_url": "https://api.github.com/users/bndr/received_events","type": "User","site_admin": false,"name": "Vadim Kravcenko","company": "","blog": "http://vadimkravcenko.com","location": "Germany","email": "bndrzz@gmail.com","hireable": false,"bio": null,"public_repos": 17,"public_gists": 2,"followers": 13,"following": 0,"created_at": "2011-10-22T19:21:17Z","updated_at": "2014-07-20T10:24:25Z"}`)
	})
	mux.HandleFunc("/users/torvalds", func(rw http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(rw, `{"login": "torvalds","id": 1024025,"avatar_url": "https://avatars.githubusercontent.com/u/1024025?","gravatar_id": "fb47627bc8c0bcdb36321edfbf02e916","url": "https://api.github.com/users/torvalds","html_url": "https://github.com/torvalds","followers_url": "https://api.github.com/users/torvalds/followers","following_url": "https://api.github.com/users/torvalds/following{/other_user}","gists_url": "https://api.github.com/users/torvalds/gists{/gist_id}","starred_url": "https://api.github.com/users/torvalds/starred{/owner}{/repo}","subscriptions_url": "https://api.github.com/users/torvalds/subscriptions","organizations_url": "https://api.github.com/users/torvalds/orgs","repos_url": "https://api.github.com/users/torvalds/repos","events_url": "https://api.github.com/users/torvalds/events{/privacy}","received_events_url": "https://api.github.com/users/torvalds/received_events","type": "User","site_admin": false,"name": "Linus Torvalds","company": "Linux Foundation","blog": null,"location": "Portland, OR","email": null,"hireable": false,"bio": null,"public_repos": 2,"public_gists": 0,"followers": 17605,"following": 0,"created_at": "2011-09-03T15:26:22Z","updated_at": "2014-07-20T10:27:30Z"}`)
	})

	testServer := httptest.NewServer(mux)

	api := Api(testServer.URL)
	// Users endpoint
	users := api.Res("users")

	usernames := []string{"bndr", "torvalds"}

	for _, username := range usernames {
		// Create a new pointer to response Struct
		r := new(respStruct)
		// Get user with id i into the newly created response struct
		_, err := users.Id(username, r).Get()
		if err != nil {
			t.Log("Error Getting Data from Test API\nErr:", err)
		} else {
			assert.Equal(t, r.Message, "", "Error message must be empty")
			assert.Equal(t, r.Login, username, "Username should be equal")
		}
	}
	resp := new(respStruct)
	api.Res("users", resp).Id("bndr").Get()
	assert.Equal(t, resp.Login, "bndr")
	resp2 := new(respStruct)
	api.Res("users").Id("bndr", resp2).Get()
	assert.Equal(t, resp2.Login, "bndr")
}

func TestResource_create(t *testing.T) {
	api := Api("http://httpbin.org")
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("post", r).Post(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_update(t *testing.T) {
	api := Api("https://httpbin.org")
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("put", r).Put(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_delete(t *testing.T) {
	api := Api("https://httpbin.org")
	r := new(binStruct)
	api.Id("delete", r).Delete()
	assert.Equal(t, r.Url, "https://httpbin.org/delete", "Url must match")
}

func TestResource_id(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.Res("users").Id("test").Url, "users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id("test").parseUrl(), "https://test-url.com/users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id(123).Res("items").Id(111).Url, "users/123/items/111", "Multilevel Url should match")
}
<commit_msg>replaced tests against httpbin.org with httptest server<commit_after>package gopencils

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

type respStruct struct {
	Login   string
	Id      int
	Name    string
	Message string
}
type binStruct struct {
	Args    string
	Headers map[string]string
	Url     string
	Json    map[string]interface{}
}

func TestResource_url(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.parseUrl(), "https://test-url.com/", "Parsed Url Should match")
	api.SetQuery(map[string]string{"key": "value"})
	assert.Equal(t, api.parseUrl(), "https://test-url.com/?key=value", "Parsed QueryString Url Should match")
	assert.Equal(t, api.Url, "", "Base Url Should be empty")
	assert.Equal(t, api.Api.Base, "https://test-url.com/", "Base Url Should be empty")
}

func TestResource_auth(t *testing.T) {
	api := Api("https://test-url.com", &BasicAuth{"username", "password"})
	assert.Equal(t, api.Api.BasicAuth.Username, "username", "Username should match")
	assert.Equal(t, api.Api.BasicAuth.Password, "password", "Password should match")
}

func TestResource_get(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/users/bndr", func(rw http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(rw, `{"login": "bndr","id": 1145456,"avatar_url": "https://avatars.githubusercontent.com/u/1145456?","gravatar_id": "8d05db0b0b8b74d5a0f93d0b1db22909","url": "https://api.github.com/users/bndr","html_url": "https://github.com/bndr","followers_url": "https://api.github.com/users/bndr/followers","following_url": "https://api.github.com/users/bndr/following{/other_user}","gists_url": "https://api.github.com/users/bndr/gists{/gist_id}","starred_url": "https://api.github.com/users/bndr/starred{/owner}{/repo}","subscriptions_url": "https://api.github.com/users/bndr/subscriptions","organizations_url": "https://api.github.com/users/bndr/orgs","repos_url": "https://api.github.com/users/bndr/repos","events_url": "https://api.github.com/users/bndr/events{/privacy}","received_events_url": "https://api.github.com/users/bndr/received_events","type": "User","site_admin": false,"name": "Vadim Kravcenko","company": "","blog": "http://vadimkravcenko.com","location": "Germany","email": "bndrzz@gmail.com","hireable": false,"bio": null,"public_repos": 17,"public_gists": 2,"followers": 13,"following": 0,"created_at": "2011-10-22T19:21:17Z","updated_at": "2014-07-20T10:24:25Z"}`)
	})
	mux.HandleFunc("/users/torvalds", func(rw http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(rw, `{"login": "torvalds","id": 1024025,"avatar_url": "https://avatars.githubusercontent.com/u/1024025?","gravatar_id": "fb47627bc8c0bcdb36321edfbf02e916","url": "https://api.github.com/users/torvalds","html_url": "https://github.com/torvalds","followers_url": "https://api.github.com/users/torvalds/followers","following_url": "https://api.github.com/users/torvalds/following{/other_user}","gists_url": "https://api.github.com/users/torvalds/gists{/gist_id}","starred_url": "https://api.github.com/users/torvalds/starred{/owner}{/repo}","subscriptions_url": "https://api.github.com/users/torvalds/subscriptions","organizations_url": "https://api.github.com/users/torvalds/orgs","repos_url": "https://api.github.com/users/torvalds/repos","events_url": "https://api.github.com/users/torvalds/events{/privacy}","received_events_url": "https://api.github.com/users/torvalds/received_events","type": "User","site_admin": false,"name": "Linus Torvalds","company": "Linux Foundation","blog": null,"location": "Portland, OR","email": null,"hireable": false,"bio": null,"public_repos": 2,"public_gists": 0,"followers": 17605,"following": 0,"created_at": "2011-09-03T15:26:22Z","updated_at": "2014-07-20T10:27:30Z"}`)
	})

	testServer := httptest.NewServer(mux)

	api := Api(testServer.URL)
	// Users endpoint
	users := api.Res("users")

	usernames := []string{"bndr", "torvalds"}

	for _, username := range usernames {
		// Create a new pointer to response Struct
		r := new(respStruct)
		// Get user with id i into the newly created response struct
		_, err := users.Id(username, r).Get()
		if err != nil {
			t.Log("Error Getting Data from Test API\nErr:", err)
		} else {
			assert.Equal(t, r.Message, "", "Error message must be empty")
			assert.Equal(t, r.Login, username, "Username should be equal")
		}
	}
	resp := new(respStruct)
	api.Res("users", resp).Id("bndr").Get()
	assert.Equal(t, resp.Login, "bndr")
	resp2 := new(respStruct)
	api.Res("users").Id("bndr", resp2).Get()
	assert.Equal(t, resp2.Login, "bndr")
}

func TestResource_create(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		assert.Equal(t, req.Method, "POST", "unexpected Method")
		assert.Equal(t, req.URL.Path, "/post", "unexpected Path")
		assert.Equal(t, req.Header.Get("Content-Type"), "application/json", "Expected json content type")
		fmt.Fprintln(rw, `{"args": {},"data": "{\"Key\":\"Value1\"}","files": {},"form": {},"headers": {"Accept": "*/*",  "Connection": "close",  "Content-Length": "16",  "Content-Type": "application/json",  "Host": "httpbin.org",  "User-Agent": "curl/7.37.0",  "X-Request-Id": "6268bee8-2ea0-4144-802a-6166fe18d84f"},"json": {"Key": "Value1"},"origin": "95.91.230.168","url": "https://httpbin.org/post"}`)
	}))

	api := Api(testServer.URL)
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("post", r).Post(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_update(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		assert.Equal(t, req.Method, "PUT", "unexpected Method")
		assert.Equal(t, req.URL.Path, "/put", "unexpected Path")
		assert.Equal(t, req.Header.Get("Content-Type"), "application/json", "Expected json content type")
		fmt.Fprintln(rw, `{"args": {},"data": "{\"Key\":\"Value1\"}","files": {},"form": {},"headers": {"Accept": "*/*",  "Connection": "close",  "Content-Length": "16",  "Content-Type": "application/json",  "Host": "httpbin.org",  "User-Agent": "curl/7.37.0",  "X-Request-Id": "6268bee8-2ea0-4144-802a-6166fe18d84f"},"json": {"Key": "Value1"},"origin": "95.91.230.168","url": "https://httpbin.org/post"}`)
	}))

	api := Api(testServer.URL)
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("put", r).Put(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_delete(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		assert.Equal(t, req.Method, "DELETE", "unexpected Method")
		assert.Equal(t, req.URL.Path, "/delete", "unexpected Path")
		fmt.Fprintln(rw, `{"args": {},"data": "","files": {},"form": {},"headers": {"Accept": "*/*",  "Connection": "close",  "Host": "httpbin.org",  "User-Agent": "curl/7.37.0",  "X-Request-Id": "b29e2435-926f-4fb4-bd1d-ec1b179e1523"},"json": null,"origin": "95.91.230.168","url": "https://httpbin.org/delete"}`)
	}))

	api := Api(testServer.URL)
	r := new(binStruct)
	api.Id("delete", r).Delete()
	assert.Equal(t, r.Url, "https://httpbin.org/delete", "Url must match")
}

func TestResource_id(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.Res("users").Id("test").Url, "users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id("test").parseUrl(), "https://test-url.com/users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id(123).Res("items").Id(111).Url, "users/123/items/111", "Multilevel Url should match")
}
<|endoftext|>


<commit_before>package gopencils

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

type respStruct struct {
	Login   string
	Id      int
	Name    string
	Message string
}
type binStruct struct {
	Args    string
	Headers map[string]string
	Url     string
	Json    map[string]interface{}
}

func TestResource_url(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.parseUrl(), "https://test-url.com/", "Parsed Url Should match")
	api.SetQuery(map[string]string{"key": "value"})
	assert.Equal(t, api.parseUrl(), "https://test-url.com/?key=value", "Parsed QueryString Url Should match")
	assert.Equal(t, api.Url, "", "Base Url Should be empty")
	assert.Equal(t, api.Api.Base, "https://test-url.com/", "Base Url Should be empty")
}

func TestResource_auth(t *testing.T) {
	api := Api("https://test-url.com", &BasicAuth{"username", "password"})
	assert.Equal(t, api.Api.BasicAuth.Username, "username", "Username should match")
	assert.Equal(t, api.Api.BasicAuth.Password, "password", "Password should match")
}

func TestResource_get(t *testing.T) {
	api := Api("https://api.github.com")
	// Users endpoint
	users := api.Res("users")

	usernames := []string{"bndr", "torvalds"}

	for _, username := range usernames {
		// Create a new pointer to response Struct
		r := new(respStruct)
		// Get user with id i into the newly created response struct
		_, err := users.Id(username, r).Get()
		if err != nil {
			t.Log("Error Getting Data from Github API")
		} else {
			assert.Equal(t, r.Message, "", "Error message must be empty")
			assert.Equal(t, r.Login, username, "Username should be equal")
		}
	}
	resp := new(respStruct)
	api.Res("users", resp).Id("bndr").Get()
	assert.Equal(t, resp.Login, "bndr")
	resp2 := new(respStruct)
	api.Res("users").Id("bndr", resp2).Get()
	assert.Equal(t, resp2.Login, "bndr")
}

func TestResource_create(t *testing.T) {
	api := Api("http://httpbin.org")
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("post", r).Post(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_update(t *testing.T) {
	api := Api("https://httpbin.org")
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("put", r).Put(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_delete(t *testing.T) {
	api := Api("https://httpbin.org")
	r := new(binStruct)
	api.Id("delete", r).Delete()
	assert.Equal(t, r.Url, "https://httpbin.org/delete", "Url must match")
}

func TestResource_id(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.Res("users").Id("test").Url, "users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id("test").parseUrl(), "https://test-url.com/users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id(123).Res("items").Id(111).Url, "users/123/items/111", "Multilevel Url should match")
}
<commit_msg>use httptest.Sever for testing<commit_after>package gopencils

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

type respStruct struct {
	Login   string
	Id      int
	Name    string
	Message string
}
type binStruct struct {
	Args    string
	Headers map[string]string
	Url     string
	Json    map[string]interface{}
}

func TestResource_url(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.parseUrl(), "https://test-url.com/", "Parsed Url Should match")
	api.SetQuery(map[string]string{"key": "value"})
	assert.Equal(t, api.parseUrl(), "https://test-url.com/?key=value", "Parsed QueryString Url Should match")
	assert.Equal(t, api.Url, "", "Base Url Should be empty")
	assert.Equal(t, api.Api.Base, "https://test-url.com/", "Base Url Should be empty")
}

func TestResource_auth(t *testing.T) {
	api := Api("https://test-url.com", &BasicAuth{"username", "password"})
	assert.Equal(t, api.Api.BasicAuth.Username, "username", "Username should match")
	assert.Equal(t, api.Api.BasicAuth.Password, "password", "Password should match")
}

func TestResource_get(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/users/bndr", func(rw http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(rw, `{"login": "bndr","id": 1145456,"avatar_url": "https://avatars.githubusercontent.com/u/1145456?","gravatar_id": "8d05db0b0b8b74d5a0f93d0b1db22909","url": "https://api.github.com/users/bndr","html_url": "https://github.com/bndr","followers_url": "https://api.github.com/users/bndr/followers","following_url": "https://api.github.com/users/bndr/following{/other_user}","gists_url": "https://api.github.com/users/bndr/gists{/gist_id}","starred_url": "https://api.github.com/users/bndr/starred{/owner}{/repo}","subscriptions_url": "https://api.github.com/users/bndr/subscriptions","organizations_url": "https://api.github.com/users/bndr/orgs","repos_url": "https://api.github.com/users/bndr/repos","events_url": "https://api.github.com/users/bndr/events{/privacy}","received_events_url": "https://api.github.com/users/bndr/received_events","type": "User","site_admin": false,"name": "Vadim Kravcenko","company": "","blog": "http://vadimkravcenko.com","location": "Germany","email": "bndrzz@gmail.com","hireable": false,"bio": null,"public_repos": 17,"public_gists": 2,"followers": 13,"following": 0,"created_at": "2011-10-22T19:21:17Z","updated_at": "2014-07-20T10:24:25Z"}`)
	})
	mux.HandleFunc("/users/torvalds", func(rw http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(rw, `{"login": "torvalds","id": 1024025,"avatar_url": "https://avatars.githubusercontent.com/u/1024025?","gravatar_id": "fb47627bc8c0bcdb36321edfbf02e916","url": "https://api.github.com/users/torvalds","html_url": "https://github.com/torvalds","followers_url": "https://api.github.com/users/torvalds/followers","following_url": "https://api.github.com/users/torvalds/following{/other_user}","gists_url": "https://api.github.com/users/torvalds/gists{/gist_id}","starred_url": "https://api.github.com/users/torvalds/starred{/owner}{/repo}","subscriptions_url": "https://api.github.com/users/torvalds/subscriptions","organizations_url": "https://api.github.com/users/torvalds/orgs","repos_url": "https://api.github.com/users/torvalds/repos","events_url": "https://api.github.com/users/torvalds/events{/privacy}","received_events_url": "https://api.github.com/users/torvalds/received_events","type": "User","site_admin": false,"name": "Linus Torvalds","company": "Linux Foundation","blog": null,"location": "Portland, OR","email": null,"hireable": false,"bio": null,"public_repos": 2,"public_gists": 0,"followers": 17605,"following": 0,"created_at": "2011-09-03T15:26:22Z","updated_at": "2014-07-20T10:27:30Z"}`)
	})

	testServer := httptest.NewServer(mux)

	api := Api(testServer.URL)
	// Users endpoint
	users := api.Res("users")

	usernames := []string{"bndr", "torvalds"}

	for _, username := range usernames {
		// Create a new pointer to response Struct
		r := new(respStruct)
		// Get user with id i into the newly created response struct
		_, err := users.Id(username, r).Get()
		if err != nil {
			t.Log("Error Getting Data from Test API\nErr:", err)
		} else {
			assert.Equal(t, r.Message, "", "Error message must be empty")
			assert.Equal(t, r.Login, username, "Username should be equal")
		}
	}
	resp := new(respStruct)
	api.Res("users", resp).Id("bndr").Get()
	assert.Equal(t, resp.Login, "bndr")
	resp2 := new(respStruct)
	api.Res("users").Id("bndr", resp2).Get()
	assert.Equal(t, resp2.Login, "bndr")
}

func TestResource_create(t *testing.T) {
	api := Api("http://httpbin.org")
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("post", r).Post(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_update(t *testing.T) {
	api := Api("https://httpbin.org")
	payload := map[string]interface{}{"Key": "Value1"}
	r := new(binStruct)
	api.Res("put", r).Put(payload)
	assert.Equal(t, r.Json["Key"], "Value1", "Payload must match")
}

func TestResource_delete(t *testing.T) {
	api := Api("https://httpbin.org")
	r := new(binStruct)
	api.Id("delete", r).Delete()
	assert.Equal(t, r.Url, "https://httpbin.org/delete", "Url must match")
}

func TestResource_id(t *testing.T) {
	api := Api("https://test-url.com")
	assert.Equal(t, api.Res("users").Id("test").Url, "users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id("test").parseUrl(), "https://test-url.com/users/test", "Url should match")
	assert.Equal(t, api.Res("users").Id(123).Res("items").Id(111).Url, "users/123/items/111", "Multilevel Url should match")
}
<|endoftext|>


<commit_before>package resource

import (
	"errors"
	"fmt"
	"strings"

	"github.com/jinzhu/gorm"
	"github.com/qor/qor"
	"github.com/qor/qor/utils"
	"github.com/qor/roles"
)

// ToPrimaryQueryParams to primary query params
func (res *Resource) ToPrimaryQueryParams(primaryValue string, context *qor.Context) (string, []string) {
	if primaryValue != "" {
		scope := context.GetDB().NewScope(res.Value)

		// multiple primary fields
		if len(res.PrimaryFields) > 1 {
			if primaryValues := strings.Split(primaryValue, ","); len(primaryValues) == len(res.PrimaryFields) {
				sqls := []string{}
				for _, field := range res.PrimaryFields {
					sqls = append(sqls, fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(field.DBName)))
				}

				return strings.Join(sqls, " AND "), primaryValues
			}
		}

		// fallback to first configured primary field
		if len(res.PrimaryFields) > 0 {
			return fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(res.PrimaryFields[0].DBName)), []string{primaryValue}
		}

		// if no configured primary fields found
		if primaryField := scope.PrimaryField(); primaryField != nil {
			return fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(primaryField.DBName)), []string{primaryValue}
		}
	}

	return "", []string{}
}

// ToPrimaryQueryParamsFromMetaValue to primary query params from meta values
func (res *Resource) ToPrimaryQueryParamsFromMetaValue(metaValues *MetaValues, context *qor.Context) (string, []string) {
	var (
		sqls, primaryValues []string
		scope               = context.GetDB().NewScope(res.Value)
	)

	if metaValues != nil {
		for _, field := range res.PrimaryFields {
			if metaField := metaValues.Get(field.Name); metaField != nil {
				sqls = append(sqls, fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(field.DBName)))
				primaryValues = append(primaryValues, utils.ToString(metaField.Value))
			}
		}
	}

	return strings.Join(sqls, " AND "), primaryValues
}

func (res *Resource) findOneHandler(result interface{}, metaValues *MetaValues, context *qor.Context) error {
	if res.HasPermission(roles.Read, context) {
		var (
			primaryQuerySQL string
			primaryParams   []string
		)

		if metaValues == nil {
			primaryQuerySQL, primaryParams = res.ToPrimaryQueryParams(context.ResourceID, context)
		} else {
			primaryQuerySQL, primaryParams = res.ToPrimaryQueryParamsFromMetaValue(metaValues, context)
		}

		if primaryQuerySQL != "" {
			if metaValues != nil {
				if destroy := metaValues.Get("_destroy"); destroy != nil {
					if fmt.Sprint(destroy.Value) != "0" && res.HasPermission(roles.Delete, context) {
						context.GetDB().Delete(result, append([]string{primaryQuerySQL}, primaryParams...))
						return ErrProcessorSkipLeft
					}
				}
			}
			return context.GetDB().First(result, append([]string{primaryQuerySQL}, primaryParams...)).Error
		}

		return errors.New("failed to find")
	}
	return roles.ErrPermissionDenied
}

func (res *Resource) findManyHandler(result interface{}, context *qor.Context) error {
	if res.HasPermission(roles.Read, context) {
		db := context.GetDB()
		if _, ok := db.Get("qor:getting_total_count"); ok {
			return context.GetDB().Count(result).Error
		}
		return context.GetDB().Set("gorm:order_by_primary_key", "DESC").Find(result).Error
	}

	return roles.ErrPermissionDenied
}

func (res *Resource) saveHandler(result interface{}, context *qor.Context) error {
	if (context.GetDB().NewScope(result).PrimaryKeyZero() &&
		res.HasPermission(roles.Create, context)) || // has create permission
		res.HasPermission(roles.Update, context) { // has update permission
		return context.GetDB().Save(result).Error
	}
	return roles.ErrPermissionDenied
}

func (res *Resource) deleteHandler(result interface{}, context *qor.Context) error {
	if res.HasPermission(roles.Delete, context) {
		if primaryQuerySQL, primaryParams := res.ToPrimaryQueryParams(context.ResourceID, context); primaryQuerySQL != "" {
			if !context.GetDB().First(result, append([]string{primaryQuerySQL}, primaryParams...)).RecordNotFound() {
				return context.GetDB().Delete(result).Error
			}
		}
		return gorm.ErrRecordNotFound
	}
	return roles.ErrPermissionDenied
}

// CallFindOne call find one method
func (res *Resource) CallFindOne(result interface{}, metaValues *MetaValues, context *qor.Context) error {
	return res.FindOneHandler(result, metaValues, context)
}

// CallFindMany call find many method
func (res *Resource) CallFindMany(result interface{}, context *qor.Context) error {
	return res.FindManyHandler(result, context)
}

// CallSave call save method
func (res *Resource) CallSave(result interface{}, context *qor.Context) error {
	return res.SaveHandler(result, context)
}

// CallDelete call delete method
func (res *Resource) CallDelete(result interface{}, context *qor.Context) error {
	return res.DeleteHandler(result, context)
}
<commit_msg>Update CURD with multiple primary keys<commit_after>package resource

import (
	"errors"
	"fmt"
	"strings"

	"github.com/jinzhu/gorm"
	"github.com/qor/qor"
	"github.com/qor/qor/utils"
	"github.com/qor/roles"
)

// ToPrimaryQueryParams to primary query params
func (res *Resource) ToPrimaryQueryParams(primaryValue string, context *qor.Context) (string, []interface{}) {
	if primaryValue != "" {
		scope := context.GetDB().NewScope(res.Value)

		// multiple primary fields
		if len(res.PrimaryFields) > 1 {
			if primaryValueStrs := strings.Split(primaryValue, ","); len(primaryValueStrs) == len(res.PrimaryFields) {
				sqls := []string{}
				primaryValues := []interface{}{}
				for idx, field := range res.PrimaryFields {
					sqls = append(sqls, fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(field.DBName)))
					primaryValues = append(primaryValues, primaryValueStrs[idx])
				}

				return strings.Join(sqls, " AND "), primaryValues
			}
		}

		// fallback to first configured primary field
		if len(res.PrimaryFields) > 0 {
			return fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(res.PrimaryFields[0].DBName)), []interface{}{primaryValue}
		}

		// if no configured primary fields found
		if primaryField := scope.PrimaryField(); primaryField != nil {
			return fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(primaryField.DBName)), []interface{}{primaryValue}
		}
	}

	return "", []interface{}{}
}

// ToPrimaryQueryParamsFromMetaValue to primary query params from meta values
func (res *Resource) ToPrimaryQueryParamsFromMetaValue(metaValues *MetaValues, context *qor.Context) (string, []interface{}) {
	var (
		sqls          []string
		primaryValues []interface{}
		scope         = context.GetDB().NewScope(res.Value)
	)

	if metaValues != nil {
		for _, field := range res.PrimaryFields {
			if metaField := metaValues.Get(field.Name); metaField != nil {
				sqls = append(sqls, fmt.Sprintf("%v.%v = ?", scope.QuotedTableName(), scope.Quote(field.DBName)))
				primaryValues = append(primaryValues, utils.ToString(metaField.Value))
			}
		}
	}

	return strings.Join(sqls, " AND "), primaryValues
}

func (res *Resource) findOneHandler(result interface{}, metaValues *MetaValues, context *qor.Context) error {
	if res.HasPermission(roles.Read, context) {
		var (
			primaryQuerySQL string
			primaryParams   []interface{}
		)

		if metaValues == nil {
			primaryQuerySQL, primaryParams = res.ToPrimaryQueryParams(context.ResourceID, context)
		} else {
			primaryQuerySQL, primaryParams = res.ToPrimaryQueryParamsFromMetaValue(metaValues, context)
		}

		if primaryQuerySQL != "" {
			if metaValues != nil {
				if destroy := metaValues.Get("_destroy"); destroy != nil {
					if fmt.Sprint(destroy.Value) != "0" && res.HasPermission(roles.Delete, context) {
						context.GetDB().Delete(result, append([]interface{}{primaryQuerySQL}, primaryParams...)...)
						return ErrProcessorSkipLeft
					}
				}
			}
			return context.GetDB().First(result, append([]interface{}{primaryQuerySQL}, primaryParams...)...).Error
		}

		return errors.New("failed to find")
	}
	return roles.ErrPermissionDenied
}

func (res *Resource) findManyHandler(result interface{}, context *qor.Context) error {
	if res.HasPermission(roles.Read, context) {
		db := context.GetDB()
		if _, ok := db.Get("qor:getting_total_count"); ok {
			return context.GetDB().Count(result).Error
		}
		return context.GetDB().Set("gorm:order_by_primary_key", "DESC").Find(result).Error
	}

	return roles.ErrPermissionDenied
}

func (res *Resource) saveHandler(result interface{}, context *qor.Context) error {
	if (context.GetDB().NewScope(result).PrimaryKeyZero() &&
		res.HasPermission(roles.Create, context)) || // has create permission
		res.HasPermission(roles.Update, context) { // has update permission
		return context.GetDB().Save(result).Error
	}
	return roles.ErrPermissionDenied
}

func (res *Resource) deleteHandler(result interface{}, context *qor.Context) error {
	if res.HasPermission(roles.Delete, context) {
		if primaryQuerySQL, primaryParams := res.ToPrimaryQueryParams(context.ResourceID, context); primaryQuerySQL != "" {
			if !context.GetDB().First(result, append([]interface{}{primaryQuerySQL}, primaryParams...)...).RecordNotFound() {
				return context.GetDB().Delete(result).Error
			}
		}
		return gorm.ErrRecordNotFound
	}
	return roles.ErrPermissionDenied
}

// CallFindOne call find one method
func (res *Resource) CallFindOne(result interface{}, metaValues *MetaValues, context *qor.Context) error {
	return res.FindOneHandler(result, metaValues, context)
}

// CallFindMany call find many method
func (res *Resource) CallFindMany(result interface{}, context *qor.Context) error {
	return res.FindManyHandler(result, context)
}

// CallSave call save method
func (res *Resource) CallSave(result interface{}, context *qor.Context) error {
	return res.SaveHandler(result, context)
}

// CallDelete call delete method
func (res *Resource) CallDelete(result interface{}, context *qor.Context) error {
	return res.DeleteHandler(result, context)
}
<|endoftext|>


<commit_before>package startCmd

import (
	// Stdlib
	"errors"
	"fmt"
	"os"

	// Internal
	"github.com/salsaflow/salsaflow/action"
	"github.com/salsaflow/salsaflow/app"
	"github.com/salsaflow/salsaflow/app/appflags"
	"github.com/salsaflow/salsaflow/errs"
	"github.com/salsaflow/salsaflow/git"
	"github.com/salsaflow/salsaflow/git/gitutil"
	"github.com/salsaflow/salsaflow/log"
	"github.com/salsaflow/salsaflow/modules"
	"github.com/salsaflow/salsaflow/modules/common"
	"github.com/salsaflow/salsaflow/prompt"
	"github.com/salsaflow/salsaflow/prompt/storyprompt"

	// Other
	"github.com/extemporalgenome/slug"
	"gopkg.in/tchap/gocli.v2"
)

var Command = &gocli.Command{
	UsageLine: "start [-base=BASE] [-no_branch] [-push]",
	Short:     "start a new story",
	Long: `
  Start a new issue tracker story.

  The user is shown the list of stories that can be started.
  When they choose one, they are assigned to that story and the story
  is started in the issue tracker.

  Unless -no_branch is specified, the user is asked to insert
  the branch name to be used for the branch holding the story commits.
  The branch of the given name is created on top of the trunk branch
  and checked out. A custom base branch can be set by using -base.
  The story branch is then pushed in case -push is specified.
	`,
	Action: run,
}

var (
	flagBase     string
	flagNoBranch bool
	flagPush     bool
)

func init() {
	// Register flags.
	Command.Flags.StringVar(&flagBase, "base", flagBase,
		"the branch to base the story branch on")
	Command.Flags.BoolVar(&flagNoBranch, "no_branch", flagNoBranch,
		"do not create a new story branch")
	Command.Flags.BoolVar(&flagPush, "push", flagPush,
		"push the newly created story branch")

	// Register global flags.
	appflags.RegisterGlobalFlags(&Command.Flags)
}

func run(cmd *gocli.Command, args []string) {
	if len(args) != 0 {
		cmd.Usage()
		os.Exit(2)
	}

	app.InitOrDie()

	defer prompt.RecoverCancel()

	if err := runMain(); err != nil {
		errs.Fatal(err)
	}
}

func runMain() error {
	tracker, err := modules.GetIssueTracker()
	if err != nil {
		return err
	}

	// Fetch stories from the issue tracker.
	task := "Fetch stories from the issue tracker"
	log.Run(task)
	stories, err := tracker.StartableStories()
	if err != nil {
		return errs.NewError(task, err)
	}
	if len(stories) == 0 {
		return errs.NewError(task, errors.New("no startable stories found"))
	}

	// Filter out the stories that are not relevant,
	// i.e. not owned by the current user or assigned to someone else.
	task = "Fetch the current user record from the issue tracker"
	user, err := tracker.CurrentUser()
	if err != nil {
		return errs.NewError(task, err)
	}

	var filteredStories []common.Story
StoryLoop:
	for _, story := range stories {
		assignees := story.Assignees()
		// Include the story in case there is no assignee set yet.
		if len(assignees) == 0 {
			filteredStories = append(filteredStories, story)
			continue StoryLoop
		}
		// Include the story in case the current user is assigned.
		for _, assignee := range assignees {
			if assignee.Id() == user.Id() {
				filteredStories = append(filteredStories, story)
				continue StoryLoop
			}
		}
	}
	stories = filteredStories

	// Prompt the user to select a story.
	story, err := dialog(
		"\nYou can start working on one of the following stories:", stories)
	if err != nil {
		switch err {
		case prompt.ErrNoStories:
			return errors.New("no startable stories found")
		case prompt.ErrCanceled:
			prompt.PanicCancel()
		default:
			return err
		}
	}
	fmt.Println()

	// Create the story branch, optionally.
	if flagNoBranch {
		log.Log("Not creating any feature branch")
	} else {
		var act action.Action
		act, err = createBranch()
		if err != nil {
			return err
		}
		// Roll back on error.
		defer action.RollbackTaskOnError(&err, task, act)
	}

	// Add the current user to the list of story assignees.
	task = "Amend the list of story assignees"
	log.Run(task)
	originalAssignees := story.Assignees()
	if err := story.AddAssignee(user); err != nil {
		return errs.NewError(task, err)
	}
	defer action.RollbackTaskOnError(&err, task, action.ActionFunc(func() error {
		task := "Reset the list of story assignees"
		if err := story.SetAssignees(originalAssignees); err != nil {
			return errs.NewError(task, err)
		}
		return nil
	}))

	// Start the selected story. No need to roll back.
	task = "Start the selected story"
	log.Run(task)
	if err := story.Start(); err != nil {
		return errs.NewError(task, err)
	}

	return nil
}

func createBranch() (action.Action, error) {
	// Get the current branch name.
	originalBranch, err := gitutil.CurrentBranch()
	if err != nil {
		return nil, err
	}

	// Fetch the remote repository.
	task := "Fetch the remote repository"
	log.Run(task)

	gitConfig, err := git.LoadConfig()
	if err != nil {
		return nil, errs.NewError(task, err)
	}

	var (
		remoteName = gitConfig.RemoteName
		baseBranch = gitConfig.TrunkBranchName
	)
	if flagBase != "" {
		baseBranch = flagBase
	}

	// Fetch the remote repository.
	if err := git.UpdateRemotes(remoteName); err != nil {
		return nil, errs.NewError(task, err)
	}

	// Make sure the trunk branch is up to date.
	task = fmt.Sprintf("Make sure branch '%v' is up to date", baseBranch)
	log.Run(task)
	if err := git.CheckOrCreateTrackingBranch(baseBranch, remoteName); err != nil {
		return nil, errs.NewError(task, err)
	}

	// Prompt the user for the branch name.
	task = "Prompt the user for the branch name"
	line, err := prompt.Prompt(`
Please insert the branch slug now.
Insert an empty string to skip the branch creation step: `)
	if err != nil && err != prompt.ErrCanceled {
		return nil, errs.NewError(task, err)
	}

	sluggedLine := slug.Slug(line)
	if sluggedLine == "" {
		fmt.Println()
		log.Log("Not creating any feature branch")
		return nil, nil
	}

	branchName := "story/" + sluggedLine
	ok, err := prompt.Confirm(
		fmt.Sprintf(
			"\nThe branch that is going to be created will be called '%s'.\nIs that alright?",
			branchName),
		true)
	if err != nil {
		return nil, errs.NewError(task, err)
	}
	if !ok {
		panic(prompt.ErrCanceled)
	}
	fmt.Println()

	createTask := fmt.Sprintf(
		"Create branch '%v' on top of branch '%v'", branchName, baseBranch)
	log.Run(createTask)
	if err := git.Branch(branchName, baseBranch); err != nil {
		return nil, errs.NewError(createTask, err)
	}

	deleteTask := fmt.Sprintf("Delete branch '%v'", branchName)
	deleteBranch := func() error {
		// Roll back and delete the newly created branch.
		log.Rollback(createTask)
		if err := git.Branch("-D", branchName); err != nil {
			return errs.NewError(deleteTask, err)
		}
		return nil
	}

	// Checkout the newly created branch.
	checkoutTask := fmt.Sprintf("Checkout branch '%v'", branchName)
	log.Run(checkoutTask)
	if err := git.Checkout(branchName); err != nil {
		if err := deleteBranch(); err != nil {
			errs.Log(err)
		}
		return nil, errs.NewError(checkoutTask, err)
	}

	// Push the newly created branch unless -no_push.
	pushTask := fmt.Sprintf("Push branch '%v' to remote '%v'", branchName, remoteName)
	if flagPush {
		log.Run(pushTask)
		if err := git.Push(remoteName, branchName); err != nil {
			if err := deleteBranch(); err != nil {
				errs.Log(err)
			}
			return nil, errs.NewError(pushTask, err)
		}
	}

	return action.ActionFunc(func() error {
		// Checkout the original branch.
		log.Rollback(checkoutTask)
		if err := git.Checkout(originalBranch); err != nil {
			return errs.NewError(
				fmt.Sprintf("Checkout the original branch '%v'", originalBranch), err)
		}

		// Delete the newly created branch.
		deleteErr := deleteBranch()

		// In case we haven't pushed anything, we are done.
		if !flagPush {
			return deleteErr
		}

		// Delete the branch from the remote repository.
		log.Rollback(pushTask)
		if _, err := git.Run("push", "--delete", remoteName, branchName); err != nil {
			// In case deleteBranch failed, tell the user now
			// since we are not going to return that error.
			if deleteErr != nil {
				errs.Log(deleteErr)
			}

			return errs.NewError(
				fmt.Sprintf("Delete branch '%v' from remote '%v'", branchName, remoteName), err)
		}

		// Return deleteErr to make sure it propagates up.
		return deleteErr
	}), nil
}

func dialog(msg string, stories []common.Story) (common.Story, error) {
	fmt.Println(msg)
	fmt.Println()

	dialog := storyprompt.NewDialog()
	dialog.PushOptions(storyprompt.NewIndexOption())
	dialog.PushOptions(storyprompt.NewReturnOrAbortOptions()...)
	dialog.PushOptions(storyprompt.NewFilterOption())
	return dialog.Run(stories)
}
<commit_msg>story start: Actually roll back on error<commit_after>package startCmd

import (
	// Stdlib
	"errors"
	"fmt"
	"os"

	// Internal
	"github.com/salsaflow/salsaflow/action"
	"github.com/salsaflow/salsaflow/app"
	"github.com/salsaflow/salsaflow/app/appflags"
	"github.com/salsaflow/salsaflow/errs"
	"github.com/salsaflow/salsaflow/git"
	"github.com/salsaflow/salsaflow/git/gitutil"
	"github.com/salsaflow/salsaflow/log"
	"github.com/salsaflow/salsaflow/modules"
	"github.com/salsaflow/salsaflow/modules/common"
	"github.com/salsaflow/salsaflow/prompt"
	"github.com/salsaflow/salsaflow/prompt/storyprompt"

	// Other
	"github.com/extemporalgenome/slug"
	"gopkg.in/tchap/gocli.v2"
)

var Command = &gocli.Command{
	UsageLine: "start [-base=BASE] [-no_branch] [-push]",
	Short:     "start a new story",
	Long: `
  Start a new issue tracker story.

  The user is shown the list of stories that can be started.
  When they choose one, they are assigned to that story and the story
  is started in the issue tracker.

  Unless -no_branch is specified, the user is asked to insert
  the branch name to be used for the branch holding the story commits.
  The branch of the given name is created on top of the trunk branch
  and checked out. A custom base branch can be set by using -base.
  The story branch is then pushed in case -push is specified.
	`,
	Action: run,
}

var (
	flagBase     string
	flagNoBranch bool
	flagPush     bool
)

func init() {
	// Register flags.
	Command.Flags.StringVar(&flagBase, "base", flagBase,
		"the branch to base the story branch on")
	Command.Flags.BoolVar(&flagNoBranch, "no_branch", flagNoBranch,
		"do not create a new story branch")
	Command.Flags.BoolVar(&flagPush, "push", flagPush,
		"push the newly created story branch")

	// Register global flags.
	appflags.RegisterGlobalFlags(&Command.Flags)
}

func run(cmd *gocli.Command, args []string) {
	if len(args) != 0 {
		cmd.Usage()
		os.Exit(2)
	}

	app.InitOrDie()

	defer prompt.RecoverCancel()

	if err := runMain(); err != nil {
		errs.Fatal(err)
	}
}

func runMain() (err error) {
	tracker, err := modules.GetIssueTracker()
	if err != nil {
		return err
	}

	// Fetch stories from the issue tracker.
	task := "Fetch stories from the issue tracker"
	log.Run(task)
	stories, err := tracker.StartableStories()
	if err != nil {
		return errs.NewError(task, err)
	}
	if len(stories) == 0 {
		return errs.NewError(task, errors.New("no startable stories found"))
	}

	// Filter out the stories that are not relevant,
	// i.e. not owned by the current user or assigned to someone else.
	task = "Fetch the current user record from the issue tracker"
	user, err := tracker.CurrentUser()
	if err != nil {
		return errs.NewError(task, err)
	}

	var filteredStories []common.Story
StoryLoop:
	for _, story := range stories {
		assignees := story.Assignees()
		// Include the story in case there is no assignee set yet.
		if len(assignees) == 0 {
			filteredStories = append(filteredStories, story)
			continue StoryLoop
		}
		// Include the story in case the current user is assigned.
		for _, assignee := range assignees {
			if assignee.Id() == user.Id() {
				filteredStories = append(filteredStories, story)
				continue StoryLoop
			}
		}
	}
	stories = filteredStories

	// Prompt the user to select a story.
	story, err := dialog(
		"\nYou can start working on one of the following stories:", stories)
	if err != nil {
		switch err {
		case prompt.ErrNoStories:
			return errors.New("no startable stories found")
		case prompt.ErrCanceled:
			prompt.PanicCancel()
		default:
			return err
		}
	}
	fmt.Println()

	// Create the story branch, optionally.
	if flagNoBranch {
		log.Log("Not creating any feature branch")
	} else {
		var act action.Action
		act, err = createBranch()
		if err != nil {
			return err
		}
		// Roll back on error.
		defer action.RollbackTaskOnError(&err, task, act)
	}

	// Add the current user to the list of story assignees.
	task = "Amend the list of story assignees"
	log.Run(task)
	originalAssignees := story.Assignees()
	if err := story.AddAssignee(user); err != nil {
		return errs.NewError(task, err)
	}
	defer action.RollbackTaskOnError(&err, task, action.ActionFunc(func() error {
		task := "Reset the list of story assignees"
		if err := story.SetAssignees(originalAssignees); err != nil {
			return errs.NewError(task, err)
		}
		return nil
	}))

	// Start the selected story. No need to roll back.
	task = "Start the selected story"
	log.Run(task)
	return errs.Wrap(task, story.Start())
}

func createBranch() (action.Action, error) {
	// Get the current branch name.
	originalBranch, err := gitutil.CurrentBranch()
	if err != nil {
		return nil, err
	}

	// Fetch the remote repository.
	task := "Fetch the remote repository"
	log.Run(task)

	gitConfig, err := git.LoadConfig()
	if err != nil {
		return nil, errs.NewError(task, err)
	}

	var (
		remoteName = gitConfig.RemoteName
		baseBranch = gitConfig.TrunkBranchName
	)
	if flagBase != "" {
		baseBranch = flagBase
	}

	// Fetch the remote repository.
	if err := git.UpdateRemotes(remoteName); err != nil {
		return nil, errs.NewError(task, err)
	}

	// Make sure the trunk branch is up to date.
	task = fmt.Sprintf("Make sure branch '%v' is up to date", baseBranch)
	log.Run(task)
	if err := git.CheckOrCreateTrackingBranch(baseBranch, remoteName); err != nil {
		return nil, errs.NewError(task, err)
	}

	// Prompt the user for the branch name.
	task = "Prompt the user for the branch name"
	line, err := prompt.Prompt(`
Please insert the branch slug now.
Insert an empty string to skip the branch creation step: `)
	if err != nil && err != prompt.ErrCanceled {
		return nil, errs.NewError(task, err)
	}

	sluggedLine := slug.Slug(line)
	if sluggedLine == "" {
		fmt.Println()
		log.Log("Not creating any feature branch")
		return nil, nil
	}

	branchName := "story/" + sluggedLine
	ok, err := prompt.Confirm(
		fmt.Sprintf(
			"\nThe branch that is going to be created will be called '%s'.\nIs that alright?",
			branchName),
		true)
	if err != nil {
		return nil, errs.NewError(task, err)
	}
	if !ok {
		panic(prompt.ErrCanceled)
	}
	fmt.Println()

	createTask := fmt.Sprintf(
		"Create branch '%v' on top of branch '%v'", branchName, baseBranch)
	log.Run(createTask)
	if err := git.Branch(branchName, baseBranch); err != nil {
		return nil, errs.NewError(createTask, err)
	}

	deleteTask := fmt.Sprintf("Delete branch '%v'", branchName)
	deleteBranch := func() error {
		// Roll back and delete the newly created branch.
		log.Rollback(createTask)
		if err := git.Branch("-D", branchName); err != nil {
			return errs.NewError(deleteTask, err)
		}
		return nil
	}

	// Checkout the newly created branch.
	checkoutTask := fmt.Sprintf("Checkout branch '%v'", branchName)
	log.Run(checkoutTask)
	if err := git.Checkout(branchName); err != nil {
		if err := deleteBranch(); err != nil {
			errs.Log(err)
		}
		return nil, errs.NewError(checkoutTask, err)
	}

	// Push the newly created branch unless -no_push.
	pushTask := fmt.Sprintf("Push branch '%v' to remote '%v'", branchName, remoteName)
	if flagPush {
		log.Run(pushTask)
		if err := git.Push(remoteName, branchName); err != nil {
			if err := deleteBranch(); err != nil {
				errs.Log(err)
			}
			return nil, errs.NewError(pushTask, err)
		}
	}

	return action.ActionFunc(func() error {
		// Checkout the original branch.
		log.Rollback(checkoutTask)
		if err := git.Checkout(originalBranch); err != nil {
			return errs.NewError(
				fmt.Sprintf("Checkout the original branch '%v'", originalBranch), err)
		}

		// Delete the newly created branch.
		deleteErr := deleteBranch()

		// In case we haven't pushed anything, we are done.
		if !flagPush {
			return deleteErr
		}

		// Delete the branch from the remote repository.
		log.Rollback(pushTask)
		if _, err := git.Run("push", "--delete", remoteName, branchName); err != nil {
			// In case deleteBranch failed, tell the user now
			// since we are not going to return that error.
			if deleteErr != nil {
				errs.Log(deleteErr)
			}

			return errs.NewError(
				fmt.Sprintf("Delete branch '%v' from remote '%v'", branchName, remoteName), err)
		}

		// Return deleteErr to make sure it propagates up.
		return deleteErr
	}), nil
}

func dialog(msg string, stories []common.Story) (common.Story, error) {
	fmt.Println(msg)
	fmt.Println()

	dialog := storyprompt.NewDialog()
	dialog.PushOptions(storyprompt.NewIndexOption())
	dialog.PushOptions(storyprompt.NewReturnOrAbortOptions()...)
	dialog.PushOptions(storyprompt.NewFilterOption())
	return dialog.Run(stories)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/opencontainers/runtime-tools/generate/seccomp"
	"github.com/opencontainers/runtime-tools/validation/util"
)

func main() {
	g, err := util.GetDefaultGenerator()
	if err != nil {
		util.Fatal(err)
	}
	syscallArgs := seccomp.SyscallOpts{
		Action:  "errno",
		Syscall: "getcwd",
	}
	g.SetDefaultSeccompAction("allow")
	g.SetSyscallAction(syscallArgs)
	err = util.RuntimeInsideValidate(g, nil, nil)
	if err != nil {
		util.Fatal(err)
	}
}
<commit_msg>seccomp: add TAP plan to the output<commit_after>package main

import (
	tap "github.com/mndrix/tap-go"
	"github.com/opencontainers/runtime-tools/generate/seccomp"
	"github.com/opencontainers/runtime-tools/validation/util"
)

func main() {
	t := tap.New()
	t.Header(0)
	defer t.AutoPlan()
	g, err := util.GetDefaultGenerator()
	if err != nil {
		util.Fatal(err)
	}
	syscallArgs := seccomp.SyscallOpts{
		Action:  "errno",
		Syscall: "getcwd",
	}
	g.SetDefaultSeccompAction("allow")
	g.SetSyscallAction(syscallArgs)
	err = util.RuntimeInsideValidate(g, t, nil)
	t.Ok(err == nil, "seccomp action is added correctly")
	if err != nil {
		t.Fail(err.Error())
	}

}
<|endoftext|>


<commit_before>package slackapi

import (
	"encoding/json"
	"testing"
)

func CheckResponse(t *testing.T, x interface{}, y string) {
	out, err := json.Marshal(x)
	if err != nil {
		t.Fatal("json fromat;", err)
	}
	if string(out) != y {
		t.Fatalf("invalid json response;\n- %s\n+ %s\n", y, out)
	}
}

func TestAPITest(t *testing.T) {
	s := New()
	x := s.APITest()
	y := `{"ok":true}`
	CheckResponse(t, x, y)
}

func TestAppsList(t *testing.T) {
	s := New()
	x := s.AppsList()
	y := `{"ok":false,"error":"not_authed","apps":null,"cache_ts":""}`
	CheckResponse(t, x, y)
}

func TestAuthRevoke(t *testing.T) {
	s := New()
	x := s.AuthRevoke()
	y := `{"ok":false,"error":"not_authed","revoked":false}`
	CheckResponse(t, x, y)
}

func TestAuthTest(t *testing.T) {
	s := New()
	x, err := s.AuthTest()
	if err != nil {
		t.Fatal(err)
	}
	y := `{"ok":false,"error":"not_authed","team":"","team_id":"","url":"","user":"","user_id":""}`
	CheckResponse(t, x, y)
}

func TestBotsInfo(t *testing.T) {
	s := New()
	x := s.BotsInfo("user")
	y := `{"ok":false,"error":"not_authed","bot":{"id":"","deleted":false,"name":"","icons":null}}`
	CheckResponse(t, x, y)
}

func TestChannelsID(t *testing.T) {
	s := New()
	x := s.ChannelsID("channel")
	y := `"channel"`
	CheckResponse(t, x, y)
}

func TestChannelsMyHistory(t *testing.T) {
	s := New()
	x := s.ChannelsMyHistory("channel", "1234567890")
	y := `{"Filtered":0,"Latest":"","Messages":null,"Oldest":"","Total":0,"Username":""}`
	CheckResponse(t, x, y)
}

func TestChannelsPurgeHistory(t *testing.T) {
	s := New()
	x := s.ChannelsPurgeHistory("channel", "1234567890", true)
	y := `{"Deleted":0,"NotDeleted":0,"Messages":null}`
	CheckResponse(t, x, y)
}

func TestChannelsSetRetention(t *testing.T) {
	s := New()
	x := s.ChannelsSetRetention("channel", 1)
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestChannelsSuggestions(t *testing.T) {
	s := New()
	x := s.ChannelsSuggestions()
	y := `{"ok":false,"error":"not_authed","status":{"ok":false},"suggestion_types_tried":null}`
	CheckResponse(t, x, y)
}

func TestChatDelete(t *testing.T) {
	s := New()
	x := s.ChatDelete(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatMeMessage(t *testing.T) {
	s := New()
	x := s.ChatMeMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatPostMessage(t *testing.T) {
	s := New()
	x := s.ChatPostMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestChatUpdate(t *testing.T) {
	s := New()
	x := s.ChatUpdate(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestConversationsArchive(t *testing.T) {
	s := New()
	x := s.ConversationsArchive("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestConversationsCreate(t *testing.T) {
	s := New()
	x := s.ConversationsCreate("channel")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}
<commit_msg>Remove conversations.create unit test due to API uncertanties<commit_after>package slackapi

import (
	"encoding/json"
	"testing"
)

func CheckResponse(t *testing.T, x interface{}, y string) {
	out, err := json.Marshal(x)
	if err != nil {
		t.Fatal("json fromat;", err)
	}
	if string(out) != y {
		t.Fatalf("invalid json response;\n- %s\n+ %s\n", y, out)
	}
}

func TestAPITest(t *testing.T) {
	s := New()
	x := s.APITest()
	y := `{"ok":true}`
	CheckResponse(t, x, y)
}

func TestAppsList(t *testing.T) {
	s := New()
	x := s.AppsList()
	y := `{"ok":false,"error":"not_authed","apps":null,"cache_ts":""}`
	CheckResponse(t, x, y)
}

func TestAuthRevoke(t *testing.T) {
	s := New()
	x := s.AuthRevoke()
	y := `{"ok":false,"error":"not_authed","revoked":false}`
	CheckResponse(t, x, y)
}

func TestAuthTest(t *testing.T) {
	s := New()
	x, err := s.AuthTest()
	if err != nil {
		t.Fatal(err)
	}
	y := `{"ok":false,"error":"not_authed","team":"","team_id":"","url":"","user":"","user_id":""}`
	CheckResponse(t, x, y)
}

func TestBotsInfo(t *testing.T) {
	s := New()
	x := s.BotsInfo("user")
	y := `{"ok":false,"error":"not_authed","bot":{"id":"","deleted":false,"name":"","icons":null}}`
	CheckResponse(t, x, y)
}

func TestChannelsID(t *testing.T) {
	s := New()
	x := s.ChannelsID("channel")
	y := `"channel"`
	CheckResponse(t, x, y)
}

func TestChannelsMyHistory(t *testing.T) {
	s := New()
	x := s.ChannelsMyHistory("channel", "1234567890")
	y := `{"Filtered":0,"Latest":"","Messages":null,"Oldest":"","Total":0,"Username":""}`
	CheckResponse(t, x, y)
}

func TestChannelsPurgeHistory(t *testing.T) {
	s := New()
	x := s.ChannelsPurgeHistory("channel", "1234567890", true)
	y := `{"Deleted":0,"NotDeleted":0,"Messages":null}`
	CheckResponse(t, x, y)
}

func TestChannelsSetRetention(t *testing.T) {
	s := New()
	x := s.ChannelsSetRetention("channel", 1)
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestChannelsSuggestions(t *testing.T) {
	s := New()
	x := s.ChannelsSuggestions()
	y := `{"ok":false,"error":"not_authed","status":{"ok":false},"suggestion_types_tried":null}`
	CheckResponse(t, x, y)
}

func TestChatDelete(t *testing.T) {
	s := New()
	x := s.ChatDelete(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatMeMessage(t *testing.T) {
	s := New()
	x := s.ChatMeMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatPostMessage(t *testing.T) {
	s := New()
	x := s.ChatPostMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestChatUpdate(t *testing.T) {
	s := New()
	x := s.ChatUpdate(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestConversationsArchive(t *testing.T) {
	s := New()
	x := s.ConversationsArchive("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}
<|endoftext|>


<commit_before>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/polaris1119/nosql"

	"github.com/polaris1119/config"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"
	"github.com/polaris1119/times"
)

const (
	WsMsgNotify = iota // 通知消息
	WsMsgOnline        // 发送在线用户数（和需要时也发历史最高）
)

const MessageQueueLen = 3

type Message struct {
	Type int         `json:"type"`
	Body interface{} `json:"body"`
}

func NewMessage(msgType int, msgBody interface{}) *Message {
	return &Message{
		Type: msgType,
		Body: msgBody,
	}
}

type UserData struct {
	// 该用户收到的消息（key为serverId）
	serverMsgQueue map[int]chan *Message
	lastAccessTime time.Time
	onlineDuartion time.Duration

	rwMutex sync.RWMutex
}

func (this *UserData) Len() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.serverMsgQueue)
}

func (this *UserData) MessageQueue(serverId int) <-chan *Message {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return this.serverMsgQueue[serverId]
}

func (this *UserData) Remove(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	delete(this.serverMsgQueue, serverId)
}

func (this *UserData) InitMessageQueue(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	this.serverMsgQueue[serverId] = make(chan *Message, MessageQueueLen)
}

func (this *UserData) SendMessage(message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	for serverId, messageQueue := range this.serverMsgQueue {
		// 有可能用户已经退出，导致 messageQueue 满，阻塞
		if len(messageQueue) < MessageQueueLen {
			messageQueue <- message
		} else {
			logger.Infoln("server_id:", serverId, "had close")

			delete(this.serverMsgQueue, serverId)
		}
	}
}

// 用于 expvar 统计信息
type LoginUser struct {
	Uid            int    `json:"uid"`
	LastAccessTime string `json:"last_access_time"`
	OnlineDuartion string `json:"online_duration"`
}
type LoginUserSlice []*LoginUser

func (self LoginUserSlice) String() string {
	b, err := json.Marshal(self)
	if err != nil {
		return "[]"
	}

	return string(b)
}

const statOnlineKey = "stat:online"

// TODO: 多机器部署，广播、在线注册用户信息没有同步
var Book = &book{users: make(map[int]*UserData), uids: make(map[int]struct{})}

type book struct {
	users map[int]*UserData
	// 登录用户
	uids    map[int]struct{}
	rwMutex sync.RWMutex
}

// 增加一个用户到book中（有可能是用户的另一个请求）
// user为UID或IP地址的int表示
func (this *book) AddUser(user, serverId int, isUid bool) *UserData {
	var userData *UserData
	var ok bool
	this.rwMutex.Lock()
	if userData, ok = this.users[user]; ok {
		this.rwMutex.Unlock()

		userData.InitMessageQueue(serverId)
		userData.onlineDuartion += time.Now().Sub(userData.lastAccessTime)
		userData.lastAccessTime = time.Now()
	} else {
		userData = &UserData{
			serverMsgQueue: map[int]chan *Message{serverId: make(chan *Message, MessageQueueLen)},
			lastAccessTime: time.Now(),
		}
		this.users[user] = userData
		if isUid {
			this.uids[user] = struct{}{}
		}

		this.rwMutex.Unlock()

		// 存入 redis
		this.newUser2Redis(user)

		logger.Infoln("user:", user, "had enter")

		length := this.Len()

		onlineInfo := map[string]int{"online": length}
		// 在线人数超过历史最高
		if length > MaxOnlineNum() {
			maxRwMu.Lock()
			maxOnlineNum = length
			onlineInfo["maxonline"] = maxOnlineNum
			maxRwMu.Unlock()
			saveMaxOnlineNum()
		}
		// 广播给其他人：有新用户进来，包括可能的新历史最高
		message := NewMessage(WsMsgOnline, onlineInfo)
		go this.BroadcastToOthersMessage(message, user)
	}

	return userData
}

// 删除用户
func (this *book) DelUser(user, serverId int, isUid bool) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()

	// 已经不存在了
	if _, ok := this.users[user]; !ok {
		if isUid {
			delete(this.uids, user)
		}

		go this.delUserFromRedis(user)

		return
	}

	// 自己只有一个页面建立 websocket 连接
	if this.users[user].Len() == 1 {
		delete(this.users, user)
		if isUid {
			delete(this.uids, user)
		}

		go this.delUserFromRedis(user)
	} else {
		this.users[user].Remove(serverId)
	}
}

// 判断用户是否还在线（user 有可能是IP）
func (this *book) UserIsOnline(user int) bool {
	this.rwMutex.RLock()
	if _, ok := this.users[user]; ok {
		this.rwMutex.RUnlock()
		return true
	}
	this.rwMutex.RUnlock()

	// 是否其他机器在线
	if this.isStoreRedis() {
		redisClient := nosql.NewRedisClient()
		defer redisClient.Close()

		exists, _ := redisClient.HEXISTS(statOnlineKey, strconv.Itoa(user))
		return exists
	}

	return false
}

// 判断注册用户是否还在线（user 有可能是IP）
func (this *book) RegUserIsOnline(uid int) bool {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if _, ok := this.uids[uid]; ok {
		return true
	}
	return false
}

// 在线用户数
func (this *book) Len() int {
	length := 0
	if this.isStoreRedis() {
		redisClient := nosql.NewRedisClient()
		defer redisClient.Close()

		length, _ = redisClient.HLEN(statOnlineKey)
	}

	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return length + len(this.users)
}

// 在线注册会员数
func (this *book) LoginLen() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.uids)
}

func (this *book) LoginUserData() LoginUserSlice {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	loginUserData := LoginUserSlice(make([]*LoginUser, 0, len(this.uids)))
	for uid := range this.uids {
		user := this.users[uid]
		loginUserData = append(loginUserData, &LoginUser{
			Uid:            uid,
			LastAccessTime: times.Format("Y-m-d H:i:s", user.lastAccessTime),
			OnlineDuartion: user.onlineDuartion.String(),
		})
	}

	return loginUserData
}

// 给某个用户发送一条消息
func (this *book) PostMessage(uid int, message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if userData, ok := this.users[uid]; ok {
		logger.Infoln("post message to", uid, message)
		go userData.SendMessage(message)
	}
}

// 给所有用户广播消息
func (this *book) BroadcastAllUsersMessage(message *Message) {
	logger.Infoln("BroadcastAllUsersMessage message", message)

	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	for uid, userData := range this.users {
		if userData.Len() == 0 {
			delete(this.users, uid)
			delete(this.uids, uid)
		}
		userData.SendMessage(message)
	}
}

// 给除了自己的其他用户广播消息
func (this *book) BroadcastToOthersMessage(message *Message, myself int) {
	logger.Infoln("BroadcastToOthersMessage message", message)

	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	for uid, userData := range this.users {
		if uid == myself {
			continue
		}

		if userData.Len() == 0 {
			delete(this.users, uid)
			delete(this.uids, uid)
		}
		userData.SendMessage(message)
	}
}

// newUser2Redis 新用户存入 redis
func (this *book) newUser2Redis(user int) {
	if !this.isStoreRedis() {
		return
	}

	redisClient := nosql.NewRedisClient()
	defer redisClient.Close()

	redisClient.HSET(statOnlineKey, strconv.Itoa(user), "")
}

func (this *book) delUserFromRedis(user int) {
	if !this.isStoreRedis() {
		return
	}

	redisClient := nosql.NewRedisClient()
	defer redisClient.Close()

	redisClient.HDEL(statOnlineKey, strconv.Itoa(user))
}

func (this *book) isStoreRedis() bool {
	onlineStore := config.ConfigFile.MustValue("stat", "online_store")
	return strings.ToLower(onlineStore) == "redis"
}

var (
	// 保存历史最大在线用户数
	maxOnlineNum int
	maxRwMu      sync.RWMutex
)

func initMaxOnlineNum() {
	maxRwMu.Lock()
	defer maxRwMu.Unlock()
	if maxOnlineNum == 0 {
		data, err := ioutil.ReadFile(getDataFile())
		if err != nil {
			logger.Errorln("read data file error:", err)
			return
		}
		maxOnlineNum = goutils.MustInt(strings.TrimSpace(string(data)))
	}
}

// 获得历史最高在线人数
func MaxOnlineNum() int {
	initMaxOnlineNum()
	maxRwMu.RLock()
	defer maxRwMu.RUnlock()
	return maxOnlineNum
}

func saveMaxOnlineNum() {
	data := []byte(strconv.Itoa(MaxOnlineNum()))
	err := ioutil.WriteFile(getDataFile(), data, 0777)
	if err != nil {
		logger.Errorln("write data file error:", err)
		return
	}
}

var dataFile string

func getDataFile() string {
	if dataFile != "" {
		return dataFile
	}
	dataFile = config.ConfigFile.MustValue("global", "data_path")
	if !filepath.IsAbs(dataFile) {
		dataFile = config.ROOT + "/" + dataFile
	}
	// 文件夹不存在，则创建
	dataPath := filepath.Dir(dataFile)
	if err := os.MkdirAll(dataPath, 0777); err != nil {
		logger.Errorln("MkdirAll error:", err)
	}
	return dataFile
}
<commit_msg>bugfix<commit_after>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/polaris1119/nosql"

	"github.com/polaris1119/config"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"
	"github.com/polaris1119/times"
)

const (
	WsMsgNotify = iota // 通知消息
	WsMsgOnline        // 发送在线用户数（和需要时也发历史最高）
)

const MessageQueueLen = 3

type Message struct {
	Type int         `json:"type"`
	Body interface{} `json:"body"`
}

func NewMessage(msgType int, msgBody interface{}) *Message {
	return &Message{
		Type: msgType,
		Body: msgBody,
	}
}

type UserData struct {
	// 该用户收到的消息（key为serverId）
	serverMsgQueue map[int]chan *Message
	lastAccessTime time.Time
	onlineDuartion time.Duration

	rwMutex sync.RWMutex
}

func (this *UserData) Len() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.serverMsgQueue)
}

func (this *UserData) MessageQueue(serverId int) <-chan *Message {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return this.serverMsgQueue[serverId]
}

func (this *UserData) Remove(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	delete(this.serverMsgQueue, serverId)
}

func (this *UserData) InitMessageQueue(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	this.serverMsgQueue[serverId] = make(chan *Message, MessageQueueLen)
}

func (this *UserData) SendMessage(message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	for serverId, messageQueue := range this.serverMsgQueue {
		// 有可能用户已经退出，导致 messageQueue 满，阻塞
		if len(messageQueue) < MessageQueueLen {
			messageQueue <- message
		} else {
			logger.Infoln("server_id:", serverId, "had close")

			delete(this.serverMsgQueue, serverId)
		}
	}
}

// 用于 expvar 统计信息
type LoginUser struct {
	Uid            int    `json:"uid"`
	LastAccessTime string `json:"last_access_time"`
	OnlineDuartion string `json:"online_duration"`
}
type LoginUserSlice []*LoginUser

func (self LoginUserSlice) String() string {
	b, err := json.Marshal(self)
	if err != nil {
		return "[]"
	}

	return string(b)
}

const statOnlineKey = "stat:online"

// TODO: 多机器部署，广播、在线注册用户信息没有同步
var Book = &book{users: make(map[int]*UserData), uids: make(map[int]struct{})}

type book struct {
	users map[int]*UserData
	// 登录用户
	uids    map[int]struct{}
	rwMutex sync.RWMutex
}

// 增加一个用户到book中（有可能是用户的另一个请求）
// user为UID或IP地址的int表示
func (this *book) AddUser(user, serverId int, isUid bool) *UserData {
	var userData *UserData
	var ok bool
	this.rwMutex.Lock()
	if userData, ok = this.users[user]; ok {
		this.rwMutex.Unlock()

		userData.InitMessageQueue(serverId)
		userData.onlineDuartion += time.Now().Sub(userData.lastAccessTime)
		userData.lastAccessTime = time.Now()
	} else {
		userData = &UserData{
			serverMsgQueue: map[int]chan *Message{serverId: make(chan *Message, MessageQueueLen)},
			lastAccessTime: time.Now(),
		}
		this.users[user] = userData
		if isUid {
			this.uids[user] = struct{}{}
		}

		this.rwMutex.Unlock()

		// 存入 redis
		this.newUser2Redis(user)

		logger.Infoln("user:", user, "had enter")

		length := this.Len()

		onlineInfo := map[string]int{"online": length}
		// 在线人数超过历史最高
		if length > MaxOnlineNum() {
			maxRwMu.Lock()
			maxOnlineNum = length
			onlineInfo["maxonline"] = maxOnlineNum
			maxRwMu.Unlock()
			saveMaxOnlineNum()
		}
		// 广播给其他人：有新用户进来，包括可能的新历史最高
		message := NewMessage(WsMsgOnline, onlineInfo)
		go this.BroadcastToOthersMessage(message, user)
	}

	return userData
}

// 删除用户
func (this *book) DelUser(user, serverId int, isUid bool) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()

	// 已经不存在了
	if _, ok := this.users[user]; !ok {
		if isUid {
			delete(this.uids, user)
		}

		go this.delUserFromRedis(user)

		return
	}

	// 自己只有一个页面建立 websocket 连接
	if this.users[user].Len() == 1 {
		delete(this.users, user)
		if isUid {
			delete(this.uids, user)
		}

		go this.delUserFromRedis(user)
	} else {
		this.users[user].Remove(serverId)
	}
}

// 判断用户是否还在线（user 有可能是IP）
func (this *book) UserIsOnline(user int) bool {
	this.rwMutex.RLock()
	if _, ok := this.users[user]; ok {
		this.rwMutex.RUnlock()
		return true
	}
	this.rwMutex.RUnlock()

	// 是否其他机器在线
	if this.isStoreRedis() {
		redisClient := nosql.NewRedisClient()
		defer redisClient.Close()

		exists, _ := redisClient.HEXISTS(statOnlineKey, strconv.Itoa(user))
		return exists
	}

	return false
}

// 判断注册用户是否还在线（user 有可能是IP）
func (this *book) RegUserIsOnline(uid int) bool {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if _, ok := this.uids[uid]; ok {
		return true
	}
	return false
}

// 在线用户数
func (this *book) Len() int {
	if this.isStoreRedis() {
		redisClient := nosql.NewRedisClient()
		defer redisClient.Close()

		length, _ := redisClient.HLEN(statOnlineKey)

		return length
	}

	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.users)
}

// 在线注册会员数
func (this *book) LoginLen() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.uids)
}

func (this *book) LoginUserData() LoginUserSlice {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	loginUserData := LoginUserSlice(make([]*LoginUser, 0, len(this.uids)))
	for uid := range this.uids {
		user := this.users[uid]
		loginUserData = append(loginUserData, &LoginUser{
			Uid:            uid,
			LastAccessTime: times.Format("Y-m-d H:i:s", user.lastAccessTime),
			OnlineDuartion: user.onlineDuartion.String(),
		})
	}

	return loginUserData
}

// 给某个用户发送一条消息
func (this *book) PostMessage(uid int, message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if userData, ok := this.users[uid]; ok {
		logger.Infoln("post message to", uid, message)
		go userData.SendMessage(message)
	}
}

// 给所有用户广播消息
func (this *book) BroadcastAllUsersMessage(message *Message) {
	logger.Infoln("BroadcastAllUsersMessage message", message)

	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	for uid, userData := range this.users {
		if userData.Len() == 0 {
			delete(this.users, uid)
			delete(this.uids, uid)
		}
		userData.SendMessage(message)
	}
}

// 给除了自己的其他用户广播消息
func (this *book) BroadcastToOthersMessage(message *Message, myself int) {
	logger.Infoln("BroadcastToOthersMessage message", message)

	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	for uid, userData := range this.users {
		if uid == myself {
			continue
		}

		if userData.Len() == 0 {
			delete(this.users, uid)
			delete(this.uids, uid)
		}
		userData.SendMessage(message)
	}
}

// newUser2Redis 新用户存入 redis
func (this *book) newUser2Redis(user int) {
	if !this.isStoreRedis() {
		return
	}

	redisClient := nosql.NewRedisClient()
	defer redisClient.Close()

	redisClient.HSET(statOnlineKey, strconv.Itoa(user), "")
}

func (this *book) delUserFromRedis(user int) {
	if !this.isStoreRedis() {
		return
	}

	redisClient := nosql.NewRedisClient()
	defer redisClient.Close()

	redisClient.HDEL(statOnlineKey, strconv.Itoa(user))
}

func (this *book) isStoreRedis() bool {
	onlineStore := config.ConfigFile.MustValue("stat", "online_store")
	return strings.ToLower(onlineStore) == "redis"
}

var (
	// 保存历史最大在线用户数
	maxOnlineNum int
	maxRwMu      sync.RWMutex
)

func initMaxOnlineNum() {
	maxRwMu.Lock()
	defer maxRwMu.Unlock()
	if maxOnlineNum == 0 {
		data, err := ioutil.ReadFile(getDataFile())
		if err != nil {
			logger.Errorln("read data file error:", err)
			return
		}
		maxOnlineNum = goutils.MustInt(strings.TrimSpace(string(data)))
	}
}

// 获得历史最高在线人数
func MaxOnlineNum() int {
	initMaxOnlineNum()
	maxRwMu.RLock()
	defer maxRwMu.RUnlock()
	return maxOnlineNum
}

func saveMaxOnlineNum() {
	data := []byte(strconv.Itoa(MaxOnlineNum()))
	err := ioutil.WriteFile(getDataFile(), data, 0777)
	if err != nil {
		logger.Errorln("write data file error:", err)
		return
	}
}

var dataFile string

func getDataFile() string {
	if dataFile != "" {
		return dataFile
	}
	dataFile = config.ConfigFile.MustValue("global", "data_path")
	if !filepath.IsAbs(dataFile) {
		dataFile = config.ROOT + "/" + dataFile
	}
	// 文件夹不存在，则创建
	dataPath := filepath.Dir(dataFile)
	if err := os.MkdirAll(dataPath, 0777); err != nil {
		logger.Errorln("MkdirAll error:", err)
	}
	return dataFile
}
<|endoftext|>


<commit_before>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package slices defines various functions useful with slices of any type.
// Unless otherwise specified, these functions all apply to the elements
// of a slice at index 0 <= i < len(s).
package slices

import "constraints"

// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[E comparable](s1, s2 []E) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i, v1 := range s1 {
		v2 := s2[i]
		if v1 != v2 {
			return false
		}
	}
	return true
}

// EqualFunc reports whether two slices are equal using a comparison
// function on each pair of elements. If the lengths are different,
// EqualFunc returns false. Otherwise, the elements are compared in
// increasing index order, and the comparison stops at the first index
// for which eq returns false.
func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i, v1 := range s1 {
		v2 := s2[i]
		if !eq(v1, v2) {
			return false
		}
	}
	return true
}

// Compare compares the elements of s1 and s2.
// The elements are compared sequentially, starting at index 0,
// until one element is not equal to the other.
// The result of comparing the first non-matching elements is returned.
// If both slices are equal until one of them ends, the shorter slice is
// considered less than the longer one.
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
// Comparisons involving floating point NaNs are ignored.
func Compare[E constraints.Ordered](s1, s2 []E) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		switch {
		case v1 < v2:
			return -1
		case v1 > v2:
			return +1
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// CompareFunc is like Compare but uses a comparison function
// on each pair of elements. The elements are compared in increasing
// index order, and the comparisons stop after the first time cmp
// returns non-zero.
// The result is the first non-zero result of cmp; if cmp always
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
// and +1 if len(s1) > len(s2).
func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		if c := cmp(v1, v2); c != 0 {
			return c
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func Index[E comparable](s []E, v E) int {
	for i, vs := range s {
		if v == vs {
			return i
		}
	}
	return -1
}

// IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do.
func IndexFunc[E any](s []E, f func(E) bool) int {
	for i, v := range s {
		if f(v) {
			return i
		}
	}
	return -1
}

// Contains reports whether v is present in s.
func Contains[E comparable](s []E, v E) bool {
	return Index(s, v) >= 0
}

// Insert inserts the values v... into s at index i,
// returning the modified slice.
// In the returned slice r, r[i] == v[0].
// Insert panics if i is out of range.
// This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
	tot := len(s) + len(v)
	if tot <= cap(s) {
		s2 := s[:tot]
		copy(s2[i+len(v):], s[i:])
		copy(s2[i:], v)
		return s2
	}
	s2 := make(S, tot)
	copy(s2, s[:i])
	copy(s2[i:], v)
	copy(s2[i+len(v):], s[i:])
	return s2
}

// Delete removes the elements s[i:j] from s, returning the modified slice.
// Delete panics if s[i:j] is not a valid slice of s.
// Delete modifies the contents of the slice s; it does not create a new slice.
// Delete is O(len(s)-(j-i)), so if many items must be deleted, it is better to
// make a single call deleting them all together than to delete one at a time.
func Delete[S ~[]E, E any](s S, i, j int) S {
	return append(s[:i], s[j:]...)
}

// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
	// Preserve nil in case it matters.
	if s == nil {
		return nil
	}
	return append(S([]E{}), s...)
}

// Compact replaces consecutive runs of equal elements with a single copy.
// This is like the uniq command found on Unix.
// Compact modifies the contents of the slice s; it does not create a new slice.
func Compact[S ~[]E, E comparable](s S) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if v != last {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// CompactFunc is like Compact but uses a comparison function.
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if !eq(v, last) {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// Grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After Grow(n), at least n elements can be appended
// to the slice without another allocation. If n is negative or too large to
// allocate the memory, Grow panics.
func Grow[S ~[]E, E any](s S, n int) S {
	return append(s, make(S, n)...)[:len(s)]
}

// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
func Clip[S ~[]E, E any](s S) S {
	return s[:len(s):len(s)]
}
<commit_msg>slices: improve the performance of Equal<commit_after>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package slices defines various functions useful with slices of any type.
// Unless otherwise specified, these functions all apply to the elements
// of a slice at index 0 <= i < len(s).
package slices

import "constraints"

// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[E comparable](s1, s2 []E) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i := range s1 {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

// EqualFunc reports whether two slices are equal using a comparison
// function on each pair of elements. If the lengths are different,
// EqualFunc returns false. Otherwise, the elements are compared in
// increasing index order, and the comparison stops at the first index
// for which eq returns false.
func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i, v1 := range s1 {
		v2 := s2[i]
		if !eq(v1, v2) {
			return false
		}
	}
	return true
}

// Compare compares the elements of s1 and s2.
// The elements are compared sequentially, starting at index 0,
// until one element is not equal to the other.
// The result of comparing the first non-matching elements is returned.
// If both slices are equal until one of them ends, the shorter slice is
// considered less than the longer one.
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
// Comparisons involving floating point NaNs are ignored.
func Compare[E constraints.Ordered](s1, s2 []E) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		switch {
		case v1 < v2:
			return -1
		case v1 > v2:
			return +1
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// CompareFunc is like Compare but uses a comparison function
// on each pair of elements. The elements are compared in increasing
// index order, and the comparisons stop after the first time cmp
// returns non-zero.
// The result is the first non-zero result of cmp; if cmp always
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
// and +1 if len(s1) > len(s2).
func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		if c := cmp(v1, v2); c != 0 {
			return c
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func Index[E comparable](s []E, v E) int {
	for i, vs := range s {
		if v == vs {
			return i
		}
	}
	return -1
}

// IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do.
func IndexFunc[E any](s []E, f func(E) bool) int {
	for i, v := range s {
		if f(v) {
			return i
		}
	}
	return -1
}

// Contains reports whether v is present in s.
func Contains[E comparable](s []E, v E) bool {
	return Index(s, v) >= 0
}

// Insert inserts the values v... into s at index i,
// returning the modified slice.
// In the returned slice r, r[i] == v[0].
// Insert panics if i is out of range.
// This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
	tot := len(s) + len(v)
	if tot <= cap(s) {
		s2 := s[:tot]
		copy(s2[i+len(v):], s[i:])
		copy(s2[i:], v)
		return s2
	}
	s2 := make(S, tot)
	copy(s2, s[:i])
	copy(s2[i:], v)
	copy(s2[i+len(v):], s[i:])
	return s2
}

// Delete removes the elements s[i:j] from s, returning the modified slice.
// Delete panics if s[i:j] is not a valid slice of s.
// Delete modifies the contents of the slice s; it does not create a new slice.
// Delete is O(len(s)-(j-i)), so if many items must be deleted, it is better to
// make a single call deleting them all together than to delete one at a time.
func Delete[S ~[]E, E any](s S, i, j int) S {
	return append(s[:i], s[j:]...)
}

// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
	// Preserve nil in case it matters.
	if s == nil {
		return nil
	}
	return append(S([]E{}), s...)
}

// Compact replaces consecutive runs of equal elements with a single copy.
// This is like the uniq command found on Unix.
// Compact modifies the contents of the slice s; it does not create a new slice.
func Compact[S ~[]E, E comparable](s S) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if v != last {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// CompactFunc is like Compact but uses a comparison function.
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if !eq(v, last) {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// Grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After Grow(n), at least n elements can be appended
// to the slice without another allocation. If n is negative or too large to
// allocate the memory, Grow panics.
func Grow[S ~[]E, E any](s S, n int) S {
	return append(s, make(S, n)...)[:len(s)]
}

// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
func Clip[S ~[]E, E any](s S) S {
	return s[:len(s):len(s)]
}
<|endoftext|>


<commit_before>package lfs

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

const Version = "0.5.1"

var (
	LargeSizeThreshold = 5 * 1024 * 1024
	TempDir            = filepath.Join(os.TempDir(), "git-lfs")
	UserAgent          string
	LocalWorkingDir    string
	LocalGitDir        string
	LocalMediaDir      string
	LocalLogDir        string
	checkedTempDir     string
)

func TempFile(prefix string) (*os.File, error) {
	if checkedTempDir != TempDir {
		if err := os.MkdirAll(TempDir, 0774); err != nil {
			return nil, err
		}
		checkedTempDir = TempDir
	}

	return ioutil.TempFile(TempDir, prefix)
}

func ResetTempDir() error {
	checkedTempDir = ""
	return os.RemoveAll(TempDir)
}

func LocalMediaPath(sha string) (string, error) {
	path := filepath.Join(LocalMediaDir, sha[0:2], sha[2:4])
	if err := os.MkdirAll(path, 0744); err != nil {
		return "", fmt.Errorf("Error trying to create local media directory in '%s': %s", path, err)
	}

	return filepath.Join(path, sha), nil
}

func Environ() []string {
	osEnviron := os.Environ()
	env := make([]string, 6, len(osEnviron)+6)
	env[0] = fmt.Sprintf("LocalWorkingDir=%s", LocalWorkingDir)
	env[1] = fmt.Sprintf("LocalGitDir=%s", LocalGitDir)
	env[2] = fmt.Sprintf("LocalMediaDir=%s", LocalMediaDir)
	env[3] = fmt.Sprintf("TempDir=%s", TempDir)
	env[4] = fmt.Sprintf("ConcurrentTransfers=%d", Config.ConcurrentTransfers())
	env[5] = fmt.Sprintf("BatchTransfer=%v", Config.BatchTransfer())

	for _, e := range osEnviron {
		if !strings.Contains(e, "GIT_") {
			continue
		}
		env = append(env, e)
	}

	return env
}

func InRepo() bool {
	return LocalWorkingDir != ""
}

func init() {
	var err error

	tracerx.DefaultKey = "GIT"
	tracerx.Prefix = "trace git-lfs: "

	LocalWorkingDir, LocalGitDir, err = resolveGitDir()
	if err == nil {
		LocalMediaDir = filepath.Join(LocalGitDir, "lfs", "objects")
		LocalLogDir = filepath.Join(LocalMediaDir, "logs")
		TempDir = filepath.Join(LocalGitDir, "lfs", "tmp")

		if err := os.MkdirAll(LocalMediaDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create objects directory in '%s': %s", LocalMediaDir, err))
		}

		if err := os.MkdirAll(LocalLogDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create log directory in '%s': %s", LocalLogDir, err))
		}

		if err := os.MkdirAll(TempDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create temp directory in '%s': %s", TempDir, err))
		}

	}

	gitVersion, err := git.Config.Version()
	if err != nil {
		gitVersion = "unknown"
	}

	UserAgent = fmt.Sprintf("git-lfs/%s (GitHub; %s %s; git %s; go %s)", Version,
		runtime.GOOS,
		runtime.GOARCH,
		strings.Replace(gitVersion, "git version ", "", 1),
		strings.Replace(runtime.Version(), "go", "", 1))
}

func resolveGitDir() (string, string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", "", err
	}

	return recursiveResolveGitDir(wd)
}

func recursiveResolveGitDir(dir string) (string, string, error) {
	var cleanDir = filepath.Clean(dir)
	if cleanDir[len(cleanDir)-1] == os.PathSeparator {
		return "", "", fmt.Errorf("Git repository not found")
	}

	if filepath.Base(dir) == gitExt {
		return filepath.Dir(dir), dir, nil
	}

	gitDir := filepath.Join(dir, gitExt)
	info, err := os.Stat(gitDir)
	if err != nil {
		return recursiveResolveGitDir(filepath.Dir(dir))
	}

	if !info.IsDir() {
		return processDotGitFile(gitDir)
	}

	return dir, gitDir, nil
}

func processDotGitFile(file string) (string, string, error) {
	f, err := os.Open(file)
	if err != nil {
		return "", "", err
	}
	defer f.Close()

	data := make([]byte, 512)
	n, err := f.Read(data)
	if err != nil {
		return "", "", err
	}

	contents := string(data[0:n])
	wd := filepath.Dir(file)

	if strings.HasPrefix(contents, gitPtrPrefix) {
		dir := strings.TrimSpace(strings.Split(contents, gitPtrPrefix)[1])

		if filepath.IsAbs(dir) {
			// The .git file contains an absolute path.
			return wd, dir, nil
		}

		// The .git file contains a relative path.
		// Create an absolute path based on the directory the .git file is located in.
		absDir := filepath.Join(filepath.Dir(file), dir)

		return wd, absDir, nil
	}

	return wd, "", nil
}

const (
	gitExt       = ".git"
	gitPtrPrefix = "gitdir: "
)
<commit_msg>アーア アアアア アーアー<commit_after>package lfs

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

const Version = "0.5.1"

var (
	LargeSizeThreshold = 5 * 1024 * 1024
	TempDir            = filepath.Join(os.TempDir(), "git-lfs")
	UserAgent          string
	LocalWorkingDir    string
	LocalGitDir        string
	LocalMediaDir      string
	LocalLogDir        string
	checkedTempDir     string
)

func TempFile(prefix string) (*os.File, error) {
	if checkedTempDir != TempDir {
		if err := os.MkdirAll(TempDir, 0774); err != nil {
			return nil, err
		}
		checkedTempDir = TempDir
	}

	return ioutil.TempFile(TempDir, prefix)
}

func ResetTempDir() error {
	checkedTempDir = ""
	return os.RemoveAll(TempDir)
}

func LocalMediaPath(sha string) (string, error) {
	path := filepath.Join(LocalMediaDir, sha[0:2], sha[2:4])
	if err := os.MkdirAll(path, 0744); err != nil {
		return "", fmt.Errorf("Error trying to create local media directory in '%s': %s", path, err)
	}

	return filepath.Join(path, sha), nil
}

func Environ() []string {
	osEnviron := os.Environ()
	env := make([]string, 6, len(osEnviron)+6)
	env[0] = fmt.Sprintf("LocalWorkingDir=%s", LocalWorkingDir)
	env[1] = fmt.Sprintf("LocalGitDir=%s", LocalGitDir)
	env[2] = fmt.Sprintf("LocalMediaDir=%s", LocalMediaDir)
	env[3] = fmt.Sprintf("TempDir=%s", TempDir)
	env[4] = fmt.Sprintf("ConcurrentTransfers=%d", Config.ConcurrentTransfers())
	env[5] = fmt.Sprintf("BatchTransfer=%v", Config.BatchTransfer())

	for _, e := range osEnviron {
		if !strings.Contains(e, "GIT_") {
			continue
		}
		env = append(env, e)
	}

	return env
}

func InRepo() bool {
	return LocalWorkingDir != ""
}

func init() {
	var err error

	tracerx.DefaultKey = "GIT"
	tracerx.Prefix = "trace git-lfs: "

	LocalWorkingDir, LocalGitDir, err = resolveGitDir()
	if err == nil {
		LocalMediaDir = filepath.Join(LocalGitDir, "lfs", "objects")
		LocalLogDir = filepath.Join(LocalMediaDir, "logs")
		TempDir = filepath.Join(LocalGitDir, "lfs", "tmp")

		if err := os.MkdirAll(LocalMediaDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create objects directory in '%s': %s", LocalMediaDir, err))
		}

		if err := os.MkdirAll(LocalLogDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create log directory in '%s': %s", LocalLogDir, err))
		}

		if err := os.MkdirAll(TempDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create temp directory in '%s': %s", TempDir, err))
		}

	}

	gitVersion, err := git.Config.Version()
	if err != nil {
		gitVersion = "unknown"
	}

	UserAgent = fmt.Sprintf("git-lfs/%s (GitHub; %s %s; git %s; go %s)", Version,
		runtime.GOOS,
		runtime.GOARCH,
		strings.Replace(gitVersion, "git version ", "", 1),
		strings.Replace(runtime.Version(), "go", "", 1))
}

func resolveGitDir() (string, string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", "", err
	}

	return recursiveResolveGitDir(wd)
}

func recursiveResolveGitDir(dir string) (string, string, error) {
	var cleanDir = filepath.Clean(dir)
	if cleanDir[len(cleanDir)-1] == os.PathSeparator {
		return "", "", fmt.Errorf("Git repository not found")
	}

	if filepath.Base(dir) == gitExt {
		return filepath.Dir(dir), dir, nil
	}

	gitDir := filepath.Join(dir, gitExt)
	info, err := os.Stat(gitDir)
	if err != nil {
		return recursiveResolveGitDir(filepath.Dir(dir))
	}

	if !info.IsDir() {
		return processDotGitFile(gitDir)
	}

	return dir, gitDir, nil
}

func processDotGitFile(file string) (string, string, error) {
	f, err := os.Open(file)
	if err != nil {
		return "", "", err
	}
	defer f.Close()

	data := make([]byte, 512)
	n, err := f.Read(data)
	if err != nil {
		return "", "", err
	}

	contents := string(data[0:n])
	wd := filepath.Dir(file)

	if !strings.HasPrefix(contents, gitPtrPrefix) {
		// The `.git` file has no entry telling us about gitdir.
		return wd, "", nil
	}

	dir := strings.TrimSpace(strings.Split(contents, gitPtrPrefix)[1])

	if filepath.IsAbs(dir) {
		// The .git file contains an absolute path.
		return wd, dir, nil
	}

	// The .git file contains a relative path.
	// Create an absolute path based on the directory the .git file is located in.
	absDir := filepath.Join(filepath.Dir(file), dir)

	return wd, absDir, nil
}

const (
	gitExt       = ".git"
	gitPtrPrefix = "gitdir: "
)
<|endoftext|>