From 556cd7c77b778322a73fc315097a7636da5a1fe4 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Tue, 26 Aug 2014 18:06:44 -0300 Subject: [PATCH 1/2] Increase coverage of the api package --- api/handler_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/api/handler_test.go b/api/handler_test.go index c32b180..d50b69d 100644 --- a/api/handler_test.go +++ b/api/handler_test.go @@ -85,6 +85,36 @@ func (s *S) TestMaxMemoryValueDontResetMaxMemory(c *gocheck.C) { c.Assert(maxMemoryValue(), gocheck.Equals, uint(359)) } +func (s *S) TestAccessParametersShouldReturnErrorWhenInvalidJSONInput(c *gocheck.C) { + b := bufferCloser{bytes.NewBufferString(``)} + _, _, err := accessParameters(b) + c.Assert(err, gocheck.ErrorMatches, `^Could not parse json: .+$`) + b = bufferCloser{bytes.NewBufferString(`{`)} + _, _, err = accessParameters(b) + c.Assert(err, gocheck.ErrorMatches, `^Could not parse json: .+$`) + b = bufferCloser{bytes.NewBufferString(`bang`)} + _, _, err = accessParameters(b) + c.Assert(err, gocheck.ErrorMatches, `^Could not parse json: .+$`) + b = bufferCloser{bytes.NewBufferString(` `)} + _, _, err = accessParameters(b) + c.Assert(err, gocheck.ErrorMatches, `^Could not parse json: .+$`) +} + +func (s *S) TestAccessParametersShouldReturnErrorWhenNoUserListProvided(c *gocheck.C) { + b := bufferCloser{bytes.NewBufferString(`{"users": "oneuser"}`)} + _, _, err := accessParameters(b) + c.Assert(err, gocheck.ErrorMatches, `^Could not parse json: json: cannot unmarshal string into Go value of type \[\]string$`) + b = bufferCloser{bytes.NewBufferString(`{"repositories": ["barad-dur"]}`)} + _, _, err = accessParameters(b) + c.Assert(err, gocheck.ErrorMatches, `^It is need a user list$`) +} + +func (s *S) TestAccessParametersShouldReturnErrorWhenNoRepositoryListProvided(c *gocheck.C) { + b := bufferCloser{bytes.NewBufferString(`{"users": ["nazgul"]}`)} + _, _, err := accessParameters(b) + c.Assert(err, gocheck.ErrorMatches, `^It is need a repository list$`) +} + func (s *S) TestNewUser(c *gocheck.C) { b := strings.NewReader(fmt.Sprintf(`{"name": "brain", "keys": {"keyname": %q}}`, rawKey)) recorder, request := post("/user", b, c) From 3e050fb78245f646b51b791d84871108154f5dbb Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Tue, 26 Aug 2014 18:15:27 -0300 Subject: [PATCH 2/2] Support namespaces --- bin/gandalf.go | 42 +++------ bin/gandalf_test.go | 51 +++++++---- docs/source/api.rst | 31 +++++++ repository/repository.go | 13 ++- repository/repository_test.go | 156 ++++++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+), 46 deletions(-) diff --git a/bin/gandalf.go b/bin/gandalf.go index 129b807..6a30987 100644 --- a/bin/gandalf.go +++ b/bin/gandalf.go @@ -63,7 +63,7 @@ func action() string { // and gets the repository from the database based on the info // obtained by the SSH_ORIGINAL_COMMAND parse. func requestedRepository() (repository.Repository, error) { - repoName, err := requestedRepositoryName() + _, repoName, err := parseGitCommand() if err != nil { return repository.Repository{}, err } @@ -79,30 +79,19 @@ func requestedRepository() (repository.Repository, error) { return repo, nil } -func requestedRepositoryName() (string, error) { - r, err := regexp.Compile(`[\w-]+ '/?([\w-]+)\.git'`) - if err != nil { - panic(err) - } - m := r.FindStringSubmatch(os.Getenv("SSH_ORIGINAL_COMMAND")) - if len(m) < 2 { - return "", errors.New("Cannot deduce repository name from command. You are probably trying to do something nasty") - } - return m[1], nil -} - // Checks whether a command is a valid git command // The following format is allowed: -// git-([\w-]+) '([\w-]+)\.git' -func validateCmd() error { - r, err := regexp.Compile(`git-([\w-]+) '/?([\w-]+)\.git'`) +// (git-[a-z-]+) '/?([\w-+@][\w-+.@]*/)?([\w-]+)\.git' +func parseGitCommand() (command, name string, err error) { + r, err := regexp.Compile(`(git-[a-z-]+) '/?([\w-+@][\w-+.@]*/)?([\w-]+)\.git'`) if err != nil { panic(err) } - if m := r.FindStringSubmatch(os.Getenv("SSH_ORIGINAL_COMMAND")); len(m) < 3 { - return errors.New("You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.") + m := r.FindStringSubmatch(os.Getenv("SSH_ORIGINAL_COMMAND")) + if len(m) != 4 { + return "", "", errors.New("You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.") } - return nil + return m[1], m[2] + m[3], nil } // Executes the SSH_ORIGINAL_COMMAND based on the condition @@ -161,21 +150,18 @@ func formatCommand() ([]string, error) { log.Err(err.Error()) return []string{}, err } - repoName, err := requestedRepositoryName() + _, repoName, err := parseGitCommand() if err != nil { log.Err(err.Error()) return []string{}, err } repoName += ".git" cmdList := strings.Split(os.Getenv("SSH_ORIGINAL_COMMAND"), " ") - for i, c := range cmdList { - c = strings.Trim(c, "'") - c = strings.Trim(c, "/") - if c == repoName { - cmdList[i] = path.Join(p, repoName) - break - } + if len(cmdList) != 2 { + log.Err("Malformed git command") + return []string{}, fmt.Errorf("Malformed git command") } + cmdList[1] = path.Join(p, repoName) return cmdList, nil } @@ -192,7 +178,7 @@ func main() { fmt.Fprintln(os.Stderr, err.Error()) return } - err = validateCmd() + _, _, err = parseGitCommand() if err != nil { log.Err(err.Error()) fmt.Fprintln(os.Stderr, err.Error()) diff --git a/bin/gandalf_test.go b/bin/gandalf_test.go index 4fd9a63..c0a70f7 100644 --- a/bin/gandalf_test.go +++ b/bin/gandalf_test.go @@ -142,12 +142,12 @@ func (s *S) TestRequestedRepositoryShouldReturnErrorWhenCommandDoesNotPassesWhat os.Setenv("SSH_ORIGINAL_COMMAND", "rm -rf /") defer os.Setenv("SSH_ORIGINAL_COMMAND", "") _, err := requestedRepository() - c.Assert(err, gocheck.ErrorMatches, "^Cannot deduce repository name from command. You are probably trying to do something nasty$") + c.Assert(err, gocheck.ErrorMatches, "^You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.$") } func (s *S) TestRequestedRepositoryShouldReturnErrorWhenThereIsNoCommandPassedToSSH_ORIGINAL_COMMAND(c *gocheck.C) { _, err := requestedRepository() - c.Assert(err, gocheck.ErrorMatches, "^Cannot deduce repository name from command. You are probably trying to do something nasty$") + c.Assert(err, gocheck.ErrorMatches, "^You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.$") } func (s *S) TestRequestedRepositoryShouldReturnFormatedErrorWhenRepositoryDoesNotExists(c *gocheck.C) { @@ -163,48 +163,56 @@ func (s *S) TestRequestedRepositoryShouldReturnEmptyRepositoryStructOnError(c *g c.Assert(repo.Name, gocheck.Equals, "") } -func (s *S) TestRequestedRepositoryName(c *gocheck.C) { +func (s *S) TestParseGitCommand(c *gocheck.C) { os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack 'foobar.git'") defer os.Setenv("SSH_ORIGINAL_COMMAND", "") - name, err := requestedRepositoryName() + _, name, err := parseGitCommand() c.Assert(err, gocheck.IsNil) c.Assert(name, gocheck.Equals, "foobar") } -func (s *S) TestRequestedRepositoryNameWithSlash(c *gocheck.C) { +func (s *S) TestParseGitCommandWithSlash(c *gocheck.C) { os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack '/foobar.git'") defer os.Setenv("SSH_ORIGINAL_COMMAND", "") - name, err := requestedRepositoryName() + _, name, err := parseGitCommand() c.Assert(err, gocheck.IsNil) c.Assert(name, gocheck.Equals, "foobar") } -func (s *S) TestrequestedRepositoryNameShouldReturnErrorWhenTheresNoMatch(c *gocheck.C) { - os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack foobar") +func (s *S) TestParseGitCommandShouldReturnErrorWhenTheresNoMatch(c *gocheck.C) { defer os.Setenv("SSH_ORIGINAL_COMMAND", "") - name, err := requestedRepositoryName() - c.Assert(err, gocheck.ErrorMatches, "Cannot deduce repository name from command. You are probably trying to do something nasty") + os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack foobar") + _, name, err := parseGitCommand() + c.Assert(err, gocheck.ErrorMatches, "You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.") + c.Assert(name, gocheck.Equals, "") + os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack ../foobar") + _, name, err = parseGitCommand() + c.Assert(err, gocheck.ErrorMatches, "You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.") + c.Assert(name, gocheck.Equals, "") + os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack /etc") + _, name, err = parseGitCommand() + c.Assert(err, gocheck.ErrorMatches, "You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.") c.Assert(name, gocheck.Equals, "") } -func (s *S) TestValidateCmdReturnsErrorWhenSSH_ORIGINAL_COMMANDIsNotAGitCommand(c *gocheck.C) { +func (s *S) TestParseGitCommandReturnsErrorWhenSSH_ORIGINAL_COMMANDIsNotAGitCommand(c *gocheck.C) { os.Setenv("SSH_ORIGINAL_COMMAND", "rm -rf /") defer os.Setenv("SSH_ORIGINAL_COMMAND", "") - err := validateCmd() + _, _, err := parseGitCommand() c.Assert(err, gocheck.ErrorMatches, "^You've tried to execute some weird command, I'm deliberately denying you to do that, get over it.$") } -func (s *S) TestValidateCmdDoNotReturnsErrorWhenSSH_ORIGINAL_COMMANDIsAValidGitCommand(c *gocheck.C) { +func (s *S) TestParseGitCommandDoNotReturnsErrorWhenSSH_ORIGINAL_COMMANDIsAValidGitCommand(c *gocheck.C) { os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack 'my-repo.git'") defer os.Setenv("SSH_ORIGINAL_COMMAND", "") - err := validateCmd() + _, _, err := parseGitCommand() c.Assert(err, gocheck.IsNil) } -func (s *S) TestValidateCmdDoNotReturnsErrorWhenSSH_ORIGINAL_COMMANDIsAValidGitCommandWithDashInName(c *gocheck.C) { +func (s *S) TestParseGitCommandDoNotReturnsErrorWhenSSH_ORIGINAL_COMMANDIsAValidGitCommandWithDashInName(c *gocheck.C) { os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack '/my-repo.git'") defer os.Setenv("SSH_ORIGINAL_COMMAND", "") - err := validateCmd() + _, _, err := parseGitCommand() c.Assert(err, gocheck.IsNil) } @@ -270,6 +278,17 @@ func (s *S) TestFormatCommandShouldReceiveAGitCommandAndCanonizalizeTheRepositor c.Assert(cmd, gocheck.DeepEquals, []string{"git-receive-pack", expected}) } +func (s *S) TestFormatCommandShouldReceiveAGitCommandAndCanonizalizeTheRepositoryPathWithNamespace(c *gocheck.C) { + os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack 'me/myproject.git'") + defer os.Setenv("SSH_ORIGINAL_COMMAND", "") + cmd, err := formatCommand() + c.Assert(err, gocheck.IsNil) + p, err := config.GetString("git:bare:location") + c.Assert(err, gocheck.IsNil) + expected := path.Join(p, "me/myproject.git") + c.Assert(cmd, gocheck.DeepEquals, []string{"git-receive-pack", expected}) +} + func (s *S) TestFormatCommandShouldReceiveAGitCommandProjectWithDash(c *gocheck.C) { os.Setenv("SSH_ORIGINAL_COMMAND", "git-receive-pack '/myproject.git'") defer os.Setenv("SSH_ORIGINAL_COMMAND", "") diff --git a/docs/source/api.rst b/docs/source/api.rst index e63ee3c..d2a8bb4 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -340,3 +340,34 @@ Example result:: }], next: "1267b5de5943632e47cb6f8bf5b2147bc0be5cf123" } + +Namespaces +---------- + +Gandalf supports namespaces for repositories and must be informed in the name of the repository followed by a single slash and the actual name of the repository, i.e. `mynamespace/myrepository`. Examples of usage: + +* Creates a repository in a namespace: + + * Method: POST + * URI: /repository + * Format: JSON + + Example URL (http://gandalf-server omitted for clarity):: + + $ curl -XPOST /repository \ + -d '{"name": "mynamespace/myrepository", \ + "users": ["myuser"]}' + +* Returns a list of all the branches of the specified `mynamespace/myrepository`. + + * Method: GET + * URI: //repository/`:name`/branches + * Format: JSON + + Where: + + * `:name` is the name of the repository. + + Example URL (http://gandalf-server omitted for clarity):: + + $ curl /repository/mynamespace/myrepository/branches # gets list of branches diff --git a/repository/repository.go b/repository/repository.go index 9c62e41..567064a 100644 --- a/repository/repository.go +++ b/repository/repository.go @@ -19,6 +19,7 @@ import ( "mime/multipart" "os" "os/exec" + "path/filepath" "regexp" "strings" ) @@ -253,17 +254,25 @@ func (r *Repository) ReadOnlyURL() string { } // Validates a repository -// A valid repository must have: +// A valid repository MUST have: // - a name without any special chars only alphanumeric and underlines are allowed. // - at least one user in users array +// A valid repository MAY have one namespace since: +// - one slash (/) separates namespace and name +// - a namespace does not start with period +// - a namespace contains ony alphanumeric, underlines @, - and period func (r *Repository) isValid() (bool, error) { - m, e := regexp.Match(`^[\w-]+$`, []byte(r.Name)) + m, e := regexp.Match(`^([\w-+@][\w-+.@]*/)?[\w-]+$`, []byte(r.Name)) if e != nil { panic(e) } if !m { return false, errors.New("Validation Error: repository name is not valid") } + absPath, err := filepath.Abs(barePath(r.Name)) + if err != nil || !strings.HasPrefix(absPath, bare) { + return false, errors.New("Validation Error: repository name is not valid") + } if len(r.Users) == 0 { return false, errors.New("Validation Error: repository should have at least one user") } diff --git a/repository/repository_test.go b/repository/repository_test.go index f7e926d..b1fc40a 100644 --- a/repository/repository_test.go +++ b/repository/repository_test.go @@ -96,6 +96,82 @@ func (s *S) TestNewShouldCreateANewRepository(c *gocheck.C) { c.Assert(r.IsPublic, gocheck.Equals, false) } +func (s *S) TestNewIntegration(c *gocheck.C) { + configBare, err := config.GetString("git:bare:location") + c.Assert(err, gocheck.IsNil) + odlBare := bare + bare, err = ioutil.TempDir("", "gandalf_repository_test") + c.Assert(err, gocheck.IsNil) + config.Set("git:bare:location", bare) + c.Assert(err, gocheck.IsNil) + defer func() { + os.RemoveAll(bare) + config.Set("git:bare:location", configBare) + checkBare, err := config.GetString("git:bare:location") + c.Assert(err, gocheck.IsNil) + c.Assert(checkBare, gocheck.Equals, configBare) + bare = odlBare + }() + r, err := New("the-shire", []string{"bilbo"}, false) + c.Assert(err, gocheck.IsNil) + conn, err := db.Conn() + c.Assert(err, gocheck.IsNil) + defer conn.Close() + defer conn.Repository().Remove(bson.M{"_id": "the-shire"}) + barePath := barePath(r.Name) + c.Assert(barePath, gocheck.Equals, path.Join(bare, "the-shire.git")) + fstat, errStat := os.Stat(path.Join(barePath, "HEAD")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, false) + fstat, errStat = os.Stat(path.Join(barePath, "config")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, false) + fstat, errStat = os.Stat(path.Join(barePath, "objects")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, true) + fstat, errStat = os.Stat(path.Join(barePath, "refs")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, true) +} + +func (s *S) TestNewIntegrationWithNamespace(c *gocheck.C) { + configBare, err := config.GetString("git:bare:location") + c.Assert(err, gocheck.IsNil) + odlBare := bare + bare, err = ioutil.TempDir("", "gandalf_repository_test") + c.Assert(err, gocheck.IsNil) + config.Set("git:bare:location", bare) + c.Assert(err, gocheck.IsNil) + defer func() { + os.RemoveAll(bare) + config.Set("git:bare:location", configBare) + checkBare, err := config.GetString("git:bare:location") + c.Assert(err, gocheck.IsNil) + c.Assert(checkBare, gocheck.Equals, configBare) + bare = odlBare + }() + r, err := New("saruman/two-towers", []string{"frodo"}, false) + c.Assert(err, gocheck.IsNil) + conn, err := db.Conn() + c.Assert(err, gocheck.IsNil) + defer conn.Close() + defer conn.Repository().Remove(bson.M{"_id": "saruman/two-towers"}) + barePath := barePath(r.Name) + c.Assert(barePath, gocheck.Equals, path.Join(bare, "saruman/two-towers.git")) + fstat, errStat := os.Stat(path.Join(barePath, "HEAD")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, false) + fstat, errStat = os.Stat(path.Join(barePath, "config")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, false) + fstat, errStat = os.Stat(path.Join(barePath, "objects")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, true) + fstat, errStat = os.Stat(path.Join(barePath, "refs")) + c.Assert(errStat, gocheck.IsNil) + c.Assert(fstat.IsDir(), gocheck.Equals, true) +} + func (s *S) TestNewShouldRecordItOnDatabase(c *gocheck.C) { tmpdir, err := commandmocker.Add("git", "$*") c.Assert(err, gocheck.IsNil) @@ -113,6 +189,37 @@ func (s *S) TestNewShouldRecordItOnDatabase(c *gocheck.C) { c.Assert(r.IsPublic, gocheck.Equals, false) } +func (s *S) TestNewShouldCreateNamesakeRepositories(c *gocheck.C) { + tmpdir, err := commandmocker.Add("git", "$*") + c.Assert(err, gocheck.IsNil) + defer commandmocker.Remove(tmpdir) + conn, err := db.Conn() + c.Assert(err, gocheck.IsNil) + defer conn.Close() + u1 := struct { + Name string `bson:"_id"` + }{Name: "melkor"} + err = conn.User().Insert(&u1) + c.Assert(err, gocheck.IsNil) + defer conn.User().RemoveId(u1.Name) + u2 := struct { + Name string `bson:"_id"` + }{Name: "morgoth"} + err = conn.User().Insert(&u2) + c.Assert(err, gocheck.IsNil) + defer conn.User().RemoveId(u2.Name) + r1, err := New("melkor/angband", []string{"nazgul"}, false) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().Remove(bson.M{"_id": "melkor/angband"}) + c.Assert(r1.Name, gocheck.Equals, "melkor/angband") + c.Assert(r1.IsPublic, gocheck.Equals, false) + r2, err := New("morgoth/angband", []string{"nazgul"}, false) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().Remove(bson.M{"_id": "morgoth/angband"}) + c.Assert(r2.Name, gocheck.Equals, "morgoth/angband") + c.Assert(r2.IsPublic, gocheck.Equals, false) +} + func (s *S) TestNewPublicRepository(c *gocheck.C) { rfs := &fstesting.RecordingFs{FileContent: "foo"} fs.Fsystem = rfs @@ -173,6 +280,36 @@ func (s *S) TestRepositoryShoudBeInvalidWIthoutAnyUsers(c *gocheck.C) { c.Assert(got, gocheck.Equals, expected) } +func (s *S) TestRepositoryShoudBeInvalidWIthInvalidNamespace(c *gocheck.C) { + r := Repository{Name: "../repositories", Users: []string{}} + v, err := r.isValid() + c.Assert(v, gocheck.Equals, false) + c.Assert(err, gocheck.NotNil) + expected := "^Validation Error: repository name is not valid$" + c.Assert(err, gocheck.ErrorMatches, expected) + r = Repository{Name: "../../repositories", Users: []string{}} + v, err = r.isValid() + c.Assert(v, gocheck.Equals, false) + c.Assert(err, gocheck.NotNil) + expected = "^Validation Error: repository name is not valid$" + c.Assert(err, gocheck.ErrorMatches, expected) +} + +func (s *S) TestRepositoryAcceptsValidNamespaces(c *gocheck.C) { + r := Repository{Name: "_.mallory/foo_bar", Users: []string{"alice", "bob"}} + v, err := r.isValid() + c.Assert(v, gocheck.Equals, true) + c.Assert(err, gocheck.IsNil) + r = Repository{Name: "_git/foo_bar", Users: []string{"alice", "bob"}} + v, err = r.isValid() + c.Assert(v, gocheck.Equals, true) + c.Assert(err, gocheck.IsNil) + r = Repository{Name: "time-home_rc2+beta@globoi.com/foo_bar", Users: []string{"you", "me"}} + v, err = r.isValid() + c.Assert(v, gocheck.Equals, true) + c.Assert(err, gocheck.IsNil) +} + func (s *S) TestRepositoryShouldBeValidWithoutIsPublic(c *gocheck.C) { r := Repository{Name: "someName", Users: []string{"smeagol"}} v, _ := r.isValid() @@ -280,6 +417,13 @@ func (s *S) TestReadOnlyURL(c *gocheck.C) { c.Assert(remote, gocheck.Equals, fmt.Sprintf("git://%s/lol.git", host)) } +func (s *S) TestReadOnlyURLWithNamespace(c *gocheck.C) { + host, err := config.GetString("host") + c.Assert(err, gocheck.IsNil) + remote := (&Repository{Name: "olo/lol"}).ReadOnlyURL() + c.Assert(remote, gocheck.Equals, fmt.Sprintf("git://%s/olo/lol.git", host)) +} + func (s *S) TestReadOnlyURLWithSSH(c *gocheck.C) { config.Set("git:ssh:use", true) defer config.Unset("git:ssh:use") @@ -319,6 +463,18 @@ func (s *S) TestReadWriteURLWithSSH(c *gocheck.C) { c.Assert(remote, gocheck.Equals, expected) } +func (s *S) TestReadWriteURLWithNamespaceAndSSH(c *gocheck.C) { + config.Set("git:ssh:use", true) + defer config.Unset("git:ssh:use") + uid, err := config.GetString("uid") + c.Assert(err, gocheck.IsNil) + host, err := config.GetString("host") + c.Assert(err, gocheck.IsNil) + remote := (&Repository{Name: "olo/lol"}).ReadWriteURL() + expected := fmt.Sprintf("ssh://%s@%s/olo/lol.git", uid, host) + c.Assert(remote, gocheck.Equals, expected) +} + func (s *S) TestReadWriteURLWithSSHAndPort(c *gocheck.C) { config.Set("git:ssh:use", true) defer config.Unset("git:ssh:use")