Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix: "unable to switch worktree" in gitlab
Automatically invalidate inconsistent service git worktree which werf creates in the ~/.werf/local_cache/git_worktrees.

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Feb 15, 2022
1 parent 7618324 commit 2ef0735
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 43 deletions.
141 changes: 98 additions & 43 deletions pkg/true_git/work_tree.go
Expand Up @@ -11,6 +11,8 @@ import (
"strings"
"time"

"github.com/werf/werf/pkg/util"

"github.com/werf/lockgate"

"github.com/werf/werf/pkg/werf"
Expand Down Expand Up @@ -72,59 +74,70 @@ func prepareWorkTree(ctx context.Context, repoDir, workTreeCacheDir string, comm
}

workTreeDir := filepath.Join(workTreeCacheDir, "worktree")

isWorkTreeDirExist := false
if _, err := os.Stat(workTreeDir); err == nil {
isWorkTreeDirExist = true
} else if !os.IsNotExist(err) {
return "", fmt.Errorf("error accessing %s: %s", workTreeDir, err)
}

isWorkTreeRegistered := false
if workTreeList, err := GetWorkTreeList(repoDir); err != nil {
return "", fmt.Errorf("unable to get worktree list for repo %s: %s", repoDir, err)
} else {
for _, workTreeDesc := range workTreeList {
if workTreeDesc.Path == workTreeDir {
isWorkTreeRegistered = true
}
}
}

currentCommit := ""
currentCommitPath := filepath.Join(workTreeCacheDir, "current_commit")
currentCommitPathExists := true
if _, err := os.Stat(currentCommitPath); os.IsNotExist(err) {
currentCommitPathExists = false
} else if err != nil {
return "", fmt.Errorf("unable to access %s: %s", currentCommitPath, err)
}

if isWorkTreeDirExist && !isWorkTreeRegistered {
logboek.Context(ctx).Info().LogFDetails("Removing unregistered work tree dir %s of repo %s\n", workTreeDir, repoDir)

if err := os.RemoveAll(currentCommitPath); err != nil {
return "", fmt.Errorf("unable to remove %s: %s", currentCommitPath, err)
_, err := os.Stat(workTreeDir)
switch {
case os.IsNotExist(err):
case err != nil:
return "", fmt.Errorf("unable to access %q: %s", workTreeDir, err)
default:
isWorkTreeRegistered := false
if workTreeList, err := GetWorkTreeList(repoDir); err != nil {
return "", fmt.Errorf("unable to get worktree list for repo %s: %s", repoDir, err)
} else {
for _, workTreeDesc := range workTreeList {
if filepath.ToSlash(workTreeDesc.Path) == filepath.ToSlash(workTreeDir) {
isWorkTreeRegistered = true
}
}
}
if !isWorkTreeRegistered {
logboek.Context(ctx).Default().LogFDetails("Detected unregistered work tree dir %q of repo %s\n", workTreeDir, repoDir)
}
currentCommitPathExists = false

if err := os.RemoveAll(workTreeDir); err != nil {
return "", fmt.Errorf("unable to remove invalidated work tree dir %s: %s", workTreeDir, err)
isWorkTreeConsistent, err := verifyWorkTreeConsistency(ctx, repoDir, workTreeDir)
if err != nil {
return "", fmt.Errorf("unable to verify work tree %q consistency: %s", workTreeDir, err)
}
if !isWorkTreeConsistent {
logboek.Context(ctx).Default().LogFDetails("Detected inconsistent work tree dir %q of repo %s\n", workTreeDir, repoDir)
}
isWorkTreeDirExist = false
} else if isWorkTreeDirExist && currentCommitPathExists {
if data, err := ioutil.ReadFile(currentCommitPath); err == nil {
currentCommit = strings.TrimSpace(string(data))

if currentCommit == commit {
return workTreeDir, nil
if !isWorkTreeRegistered || !isWorkTreeConsistent {
logboek.Context(ctx).Default().LogF("Removing invalidated work tree dir %q of repo %s\n", workTreeDir, repoDir)

if err := os.RemoveAll(currentCommitPath); err != nil {
return "", fmt.Errorf("unable to remove %s: %s", currentCommitPath, err)
}

if err := os.RemoveAll(workTreeDir); err != nil {
return "", fmt.Errorf("unable to remove invalidated work tree dir %s: %s", workTreeDir, err)
}
} else {
return "", fmt.Errorf("error reading %s: %s", currentCommitPath, err)
}
currentCommitPathExists := true
if _, err := os.Stat(currentCommitPath); os.IsNotExist(err) {
currentCommitPathExists = false
} else if err != nil {
return "", fmt.Errorf("unable to access %s: %s", currentCommitPath, err)
}

if currentCommitPathExists {
if data, err := ioutil.ReadFile(currentCommitPath); err == nil {
currentCommit = strings.TrimSpace(string(data))

if err := os.RemoveAll(currentCommitPath); err != nil {
return "", fmt.Errorf("unable to remove %s: %s", currentCommitPath, err)
if currentCommit == commit {
return workTreeDir, nil
}
} else {
return "", fmt.Errorf("error reading %s: %s", currentCommitPath, err)
}

if err := os.RemoveAll(currentCommitPath); err != nil {
return "", fmt.Errorf("unable to remove %s: %s", currentCommitPath, err)
}
}
}
}

Expand All @@ -149,6 +162,48 @@ func prepareWorkTree(ctx context.Context, repoDir, workTreeCacheDir string, comm
return workTreeDir, nil
}

func verifyWorkTreeConsistency(ctx context.Context, repoDir, workTreeDir string) (bool, error) {
resolvedGitDir, err := resolveDotGitFile(ctx, filepath.Join(workTreeDir, ".git"))
if err != nil {
return false, fmt.Errorf("unable to resolve dot-git file %q: %s", filepath.Join(workTreeDir, ".git"), err)
}

if !util.IsSubpathOfBasePath(repoDir, resolvedGitDir) {
return false, nil
}

_, err = os.Stat(resolvedGitDir)
switch {
case os.IsNotExist(err):
return false, nil
case err != nil:
return false, fmt.Errorf("error accessing resolved dot git dir %q: %s", resolvedGitDir, err)
}

return true, nil
}

func resolveDotGitFile(ctx context.Context, dotGitPath string) (string, error) {
data, err := os.ReadFile(dotGitPath)
if err != nil {
return "", fmt.Errorf("error reading %q: %s", dotGitPath, err)
}

lines := util.SplitLines(string(data))
if len(lines) == 0 {
goto InvalidDotGit
}

if !strings.HasPrefix(lines[0], "gitdir: ") {
goto InvalidDotGit
}

return strings.TrimSpace(strings.TrimPrefix(lines[0], "gitdir: ")), nil

InvalidDotGit:
return "", fmt.Errorf("invalid file format: expected gitdir record")
}

func debugWorktreeSwitch() bool {
return os.Getenv("WERF_TRUE_GIT_DEBUG_WORKTREE_SWITCH") == "1"
}
Expand Down
26 changes: 26 additions & 0 deletions pkg/util/file.go
Expand Up @@ -3,6 +3,7 @@ package util
import (
"os"
"strings"
"reflect"
)

// FileExists returns true if path exists
Expand Down Expand Up @@ -39,3 +40,28 @@ func isNotExistError(err error) bool {
func IsNotADirectoryError(err error) bool {
return strings.HasSuffix(err.Error(), "not a directory")
}

func IsSubpathOfBasePath(basePath, path string) bool {
basePathParts := SplitFilepath(basePath)
pathParts := SplitFilepath(path)

if len(basePathParts) > len(pathParts) {
return false
}

if reflect.DeepEqual(basePathParts, pathParts) {
return false
}

for ind := range basePathParts {
if basePathParts[ind] == "" {
continue
}

if basePathParts[ind] != pathParts[ind] {
return false
}
}

return true
}
16 changes: 16 additions & 0 deletions pkg/util/lines.go
@@ -0,0 +1,16 @@
package util

import (
"bufio"
"strings"
)

func SplitLines(s string) []string {
var lines []string
sc := bufio.NewScanner(strings.NewReader(s))
sc.Split(bufio.ScanLines)
for sc.Scan() {
lines = append(lines, sc.Text())
}
return lines
}

0 comments on commit 2ef0735

Please sign in to comment.