dolt/{bats,go}: git dolt install finds the .git directory even if it's in a parent directory (#1715)

Closes #1701.

* dolt/go/cmd/git-dolt/utils/find_git_config_dir*.go: Add util for finding git directory in parent directory structure

* dolt/bats/git-dolt.bats: Add test for git dolt install working in subdirectories of git repos

* dolt/go/cmd/git-dolt/commands/install.go: Find the .git directory even if it's in a parent of the working directory

* dolt/bats/git-dolt.bats: Fix indentation

* dolt/go/cmd/git-dolt/utils/find_git_config_dir.go: Omit extraneous type annotation like a good Gopher

* dolt/go/cmd/git-dolt: Use filesystem root instead of home directory as terminal path for .git search

* dolt/bats/git-dolt.bats: Unskip no-longer-hanging test

* dolt/go/cmd/git-dolt/utils/find_git_config_dir.go: Add method docs

* dolt/go/cmd/git-dolt/utils/find_git_config_dir_test.go: Create all test files in a temp directory

* dolt/go/cmd/git-dolt/utils/find_git_config_dir*.go: Return an error instead of hanging when currentPath is not a descendent of terminalPath

* dolt/go/cmd/git-dolt: Gofmt

* dolt/go/cmd/git-dolt/utils/find_git_config_dir.go: Actually, if these are always absolute paths, we can simplify things a bit
This commit is contained in:
Matt Jesuele
2019-07-09 17:44:28 -07:00
committed by GitHub
parent 986f29ef0a
commit c83472fba6
5 changed files with 174 additions and 5 deletions
+23
View File
@@ -44,6 +44,29 @@ teardown() {
[[ "${lines[len-1]}" =~ "smudge = git-dolt-smudge" ]] || false
}
@test "git dolt install works in subdirectories of the git repository" {
init_git_repo
mkdir -p deeply/nested/directory
pushd deeply/nested/directory
run git dolt install
[ "$status" -eq 0 ]
popd
run cat .gitattributes
[ "${lines[0]}" = "*.git-dolt filter=git-dolt" ]
run cat .git/config
len=${#lines[@]}
[ "${lines[len-2]}" = "[filter \"git-dolt\"]" ]
[[ "${lines[len-1]}" =~ "smudge = git-dolt-smudge" ]] || false
}
@test "git dolt install fails with a helpful error when executed outside of a git repo" {
run git dolt install
[ "$status" -eq 1 ]
[[ "$output" =~ "couldn't find a .git directory" ]] || false
}
@test "git dolt link takes a remote url (and an optional revspec and destination directory), clones the repo, and outputs a pointer file" {
init_git_repo
run git dolt link $REMOTE
+13 -4
View File
@@ -3,24 +3,33 @@ package commands
import (
"fmt"
"os/exec"
"path/filepath"
"github.com/liquidata-inc/ld/dolt/go/cmd/git-dolt/utils"
)
// Install configures this git repository for use with git-dolt; specifically, it sets up the
// Install configures this git repository for use with git-dolt; specifically, it sets up the
// smudge filter that automatically clones dolt repos when git-dolt pointer files are checked out.
func Install() error {
if _, err := exec.LookPath("git-dolt-smudge"); err != nil {
return fmt.Errorf("can't find git-dolt-smudge in PATH")
}
if err := utils.AppendToFile(".gitattributes", "*.git-dolt filter=git-dolt"); err != nil {
gitPath, err := utils.FindGitConfigUnderRoot()
if err != nil {
return err
}
if err := utils.AppendToFile(".git/config", "[filter \"git-dolt\"]\n\tsmudge = git-dolt-smudge"); err != nil {
gitParentPath := filepath.Dir(gitPath)
gitAttributesPath := filepath.Join(gitParentPath, ".gitattributes")
if err := utils.AppendToFile(gitAttributesPath, "*.git-dolt filter=git-dolt"); err != nil {
return err
}
}
gitConfigPath := filepath.Join(gitPath, "config")
if err := utils.AppendToFile(gitConfigPath, "[filter \"git-dolt\"]\n\tsmudge = git-dolt-smudge"); err != nil {
return err
}
fmt.Println("Installed git-dolt smudge filter. When git-dolt pointer files are checked out in this git repository, the corresponding Dolt repositories will be automatically cloned.")
fmt.Println("\nYou should git commit the changes to .gitattributes.")
+1 -1
View File
@@ -15,7 +15,7 @@ func Link(remote string) error {
if err := doltops.Clone(remote); err != nil {
return err
}
dirname := utils.LastSegment(remote)
revision, err := utils.CurrentRevision(dirname)
if err != nil {
@@ -0,0 +1,62 @@
package utils
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// FindGitConfigUnderRoot will recurse upwards from the current working directory
// to the system root looking for a directory named .git, returning its path if found,
// and an error if not.
func FindGitConfigUnderRoot() (string, error) {
currentPath, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("error getting current directory: %v", err)
}
rootPath, err := filepath.Abs("/")
if err != nil {
return "", fmt.Errorf("error getting root directory: %v", err)
}
return FindGitConfigDir(currentPath, rootPath)
}
// FindGitConfigDir will recurse upwards from currentPath to terminalPath looking for
// a directory named .git, returning its path if found, and an error if not.
//
// Both currentPath and terminalPath are assumed to be absolute paths. An error is returned
// if currentPath is not a descendent of terminalPath.
func FindGitConfigDir(currentPath, terminalPath string) (string, error) {
if !strings.HasPrefix(currentPath, terminalPath) {
return "", fmt.Errorf("current path %s is not a descendent of terminal path %s", currentPath, terminalPath)
}
// recursive base case -- currentPath and terminalPath are the same
if currentPath == terminalPath {
return "", fmt.Errorf("recursed upwards to %s but couldn't find a .git directory", currentPath)
}
// check to see if .git exists in currentPath
fileInfo, fileErr := os.Stat(filepath.Join(currentPath, ".git"))
// .git exists and is a directory -- success!
if fileErr == nil && fileInfo.IsDir() {
return filepath.Join(currentPath, ".git"), nil
}
// something went wrong looking for .git other than it not existing -- return an error
if fileErr != nil && !os.IsNotExist(fileErr) {
return "", fmt.Errorf("error looking for the .git directory in %s: %v", currentPath, fileErr)
}
// either .git exists and is not a directory, or .git does not exist:
// go up one directory level and make the recursive call
parentPath := filepath.Dir(currentPath)
if parentPath == "." {
return "", fmt.Errorf("ran out of ancestors at %s but couldn't find a .git directory", currentPath)
}
return FindGitConfigDir(parentPath, terminalPath)
}
@@ -0,0 +1,75 @@
package utils
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFindGitConfigDir(t *testing.T) {
// Setup
tmpDir, err := ioutil.TempDir("", "git-dolt-test")
if err != nil {
t.Errorf("Error creating temp directory: %v", err)
}
defer func() {
if err := os.RemoveAll(tmpDir); err != nil {
t.Errorf("Error removing test directories: %v", err)
}
}()
nestedExamplePath := filepath.Join(tmpDir, "__deeply", "nested", "example")
topLevelExamplePath := filepath.Join(tmpDir, "__top", "level", "example")
noGitExamplePath := filepath.Join(tmpDir, "__no", "git", "example")
nestedGitPath := filepath.Join(nestedExamplePath, ".git")
topLevelGitPath := filepath.Join(tmpDir, "__top", ".git")
if err := os.MkdirAll(nestedGitPath, os.ModePerm); err != nil {
t.Errorf("Error creating test directories: %v", err)
}
if err := os.MkdirAll(topLevelExamplePath, os.ModePerm); err != nil {
t.Errorf("Error creating test directories: %v", err)
}
if err := os.MkdirAll(topLevelGitPath, os.ModePerm); err != nil {
t.Errorf("Error creating test directories: %v", err)
}
if err := os.MkdirAll(noGitExamplePath, os.ModePerm); err != nil {
t.Errorf("Error creating test directories: %v", err)
}
// Tests
type args struct {
startingPath string
terminalPath string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{"finds a deeply-nested git directory", args{nestedExamplePath, tmpDir}, nestedGitPath, false},
{"finds a top-level git directory", args{topLevelExamplePath, tmpDir}, topLevelGitPath, false},
{"returns an error when there is no git directory", args{noGitExamplePath, tmpDir}, "", true},
{"returns an error (and does not hang) when startingPath is not a descendent of terminalPath", args{noGitExamplePath, nestedExamplePath}, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FindGitConfigDir(tt.args.startingPath, tt.args.terminalPath)
if tt.wantErr {
assert.Error(t, err)
} else if err != nil {
t.Errorf("wanted %v, got error %v", tt.want, err)
}
assert.Equal(t, tt.want, got)
})
}
}