mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-29 19:39:52 -05:00
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:
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user