Skip to content

Duplicate repo entries from mixed path separators on Windows #650

@cderv

Description

@cderv

roborev repo list shows duplicate entries for the same physical repo when the path was registered with different separator styles:

NAME        PATH                                                  REVIEWS
quarto-cli  C:/Users/chris/Documents/DEV_R/quarto-cli             198
quarto-cli  C:\Users\chris\Documents\DEV_R\quarto-cli             332
quarto-web  C:/Users/chris/Documents/DEV_R/quarto-web             174
quarto-web  C:\Users\chris\Documents\DEV_R\quarto-web             58

Same physical directories, tracked as separate repos. The backslash entries were created before #426 added filepath.ToSlash normalization to GetOrCreateRepo. New registrations (from post-commit hooks in Git Bash) now store forward-slash paths, but the old backslash entries remain in the database. The UNIQUE constraint on root_path treats C:/foo and C:\foo as distinct rows.

This splits review history. roborev summary and roborev insights only see one entry's data — roborev insights analyzed 50 reviews from the forward-slash entry, missing 332 reviews from the backslash entry.

repo merge can't fully work around this

FindRepo tries GetRepoByPath first, which normalizes with filepath.ToSlash. Both a backslash path and a forward-slash path resolve to the same forward-slash entry — the backslash entry is unreachable by path:

# Both arguments normalize to the forward-slash entry:
$ roborev repo merge "C:\Users\chris\Documents\DEV_R\quarto-cli" "C:/Users/chris/Documents/DEV_R/quarto-cli"
# → "source and target are the same repo"

# Workaround: name resolves via GetRepoByName (no path normalization):
$ roborev repo merge quarto-cli "C:/Users/chris/Documents/DEV_R/quarto-cli"

The name-based workaround only works because GetRepoByName happens to return the backslash entry first. If both entries had different names, or if the query order changed, there'd be no way to select the backslash entry for merging.

Code references

Normalization added in #426 for new registrations, but no migration for existing entries:

absPath = filepath.ToSlash(absPath)

Same normalization in GetRepoByPath makes backslash entries unreachable by path lookup:

absPath = filepath.ToSlash(absPath)

FindRepo tries path first, falls back to name — the only way to reach a backslash entry:

func (db *DB) FindRepo(identifier string) (*Repo, error) {
// Try by path first
absPath, _ := filepath.Abs(identifier)
repo, err := db.GetRepoByPath(absPath)
if err == nil {
return repo, nil
}
if err != sql.ErrNoRows {
return nil, err
}
// Try by name
repo, err = db.GetRepoByName(identifier)
if err != nil {
return nil, err
}
return repo, nil
}

Environment

  • roborev v0.51.0, Windows 11
  • Git Bash (MSYS2) for post-commit hooks + Claude Code
  • PowerShell 7 for some manual commands

Happy to help test any fix on Windows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions