Simplify inner loop: just fetch $ref
Old way:
- ls-remote $ref $ref^{} and parse
- compare to current
- if changed, fetch
- update worktree
New way:
- fetch $ref
- compare to current
- if change, update worktree
This commit is contained in:
parent
ad92ba62c4
commit
083d8dda85
93
main.go
93
main.go
|
|
@ -836,10 +836,7 @@ func main() {
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isHash, err := git.IsKnownHash(ctx, git.ref); err != nil {
|
if hash == git.ref {
|
||||||
log.Error(err, "can't tell if ref is a git hash, exiting", "ref", git.ref)
|
|
||||||
os.Exit(1)
|
|
||||||
} else if isHash {
|
|
||||||
log.V(0).Info("ref appears to be a git hash, no further sync needed", "ref", git.ref)
|
log.V(0).Info("ref appears to be a git hash, no further sync needed", "ref", git.ref)
|
||||||
log.DeleteErrorFile()
|
log.DeleteErrorFile()
|
||||||
sleepForever()
|
sleepForever()
|
||||||
|
|
@ -1493,49 +1490,6 @@ func (m multiError) Error() string {
|
||||||
return strings.Join(strs, "; ")
|
return strings.Join(strs, "; ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// remoteHashForRef returns the upstream hash for a given ref.
|
|
||||||
func (git *repoSync) remoteHashForRef(ctx context.Context, ref string) (string, error) {
|
|
||||||
// Fetch both the bare and dereferenced ref. git sorts the results and
|
|
||||||
// prints the dereferenced result, if present, after the bare result, so we
|
|
||||||
// always want the last result it produces.
|
|
||||||
output, _, err := git.Run(ctx, git.root, "ls-remote", "-q", git.repo, ref, ref+"^{}")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
line := lastNonEmptyLine(output)
|
|
||||||
parts := strings.Split(line, "\t") // guaranteed to have at least 1 element
|
|
||||||
return parts[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lastNonEmptyLine(text string) string {
|
|
||||||
lines := strings.Split(text, "\n") // guaranteed to have at least 1 element
|
|
||||||
for i := len(lines) - 1; i >= 0; i-- {
|
|
||||||
line := strings.TrimSpace(lines[i])
|
|
||||||
if line != "" {
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsKnownHash returns true if ref is the hash of a commit which is known to this
|
|
||||||
// repo. In the event that ref is an abbreviated hash (e.g. "abcd" which
|
|
||||||
// resolves to "abcdef1234567890"), this will return true by prefix-matching.
|
|
||||||
// If ref is ambiguous, it will consider whatever result git returns. If ref
|
|
||||||
// is not a hash or is not known to this repo, even if it appears to be a hash,
|
|
||||||
// this will return false.
|
|
||||||
func (git *repoSync) IsKnownHash(ctx context.Context, ref string) (bool, error) {
|
|
||||||
stdout, stderr, err := git.Run(ctx, git.root, "rev-parse", ref+"^{commit}")
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(stderr, "unknown revision") {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
line := lastNonEmptyLine(stdout)
|
|
||||||
return strings.HasPrefix(line, ref), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// worktree represents a git worktree (which may or may not exist on disk).
|
// worktree represents a git worktree (which may or may not exist on disk).
|
||||||
type worktree absPath
|
type worktree absPath
|
||||||
|
|
||||||
|
|
@ -1592,26 +1546,6 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Figure out what hash the remote resolves to.
|
|
||||||
remoteHash, err := git.remoteHashForRef(ctx, git.ref)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we couldn't find a remote commit, it might have been a hash literal.
|
|
||||||
if remoteHash == "" {
|
|
||||||
// If git thinks it tastes like a hash, we just use that and if it
|
|
||||||
// is wrong, we will fail later.
|
|
||||||
output, _, err := git.Run(ctx, git.root, "rev-parse", git.ref)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
result := strings.Trim(output, "\n")
|
|
||||||
if result == git.ref {
|
|
||||||
remoteHash = git.ref
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find out what we currently have synced, if anything.
|
// Find out what we currently have synced, if anything.
|
||||||
var currentWorktree worktree
|
var currentWorktree worktree
|
||||||
if wt, err := git.currentWorktree(); err != nil {
|
if wt, err := git.currentWorktree(); err != nil {
|
||||||
|
|
@ -1622,6 +1556,22 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con
|
||||||
currentHash := currentWorktree.Hash()
|
currentHash := currentWorktree.Hash()
|
||||||
git.log.V(3).Info("current state", "hash", currentHash, "worktree", currentWorktree)
|
git.log.V(3).Info("current state", "hash", currentHash, "worktree", currentWorktree)
|
||||||
|
|
||||||
|
// This should be very fast if we already have the hash we need. Parameters
|
||||||
|
// like depth are set at fetch time.
|
||||||
|
if err := git.fetch(ctx, git.ref); err != nil {
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out what we got. The ^{} syntax "peels" annotated tags to
|
||||||
|
// their underlying commit hashes, but has no effect if we fetched a
|
||||||
|
// branch, plain tag, or hash.
|
||||||
|
remoteHash := ""
|
||||||
|
if output, _, err := git.Run(ctx, git.root, "rev-parse", "FETCH_HEAD^{}"); err != nil {
|
||||||
|
return false, "", err
|
||||||
|
} else {
|
||||||
|
remoteHash = strings.Trim(output, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
if currentHash == remoteHash {
|
if currentHash == remoteHash {
|
||||||
// We seem to have the right hash already. Let's be sure it's good.
|
// We seem to have the right hash already. Let's be sure it's good.
|
||||||
git.log.V(3).Info("current hash is same as remote", "hash", currentHash)
|
git.log.V(3).Info("current hash is same as remote", "hash", currentHash)
|
||||||
|
|
@ -1643,17 +1593,12 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con
|
||||||
// are set properly. This is cheap when we already have the target hash.
|
// are set properly. This is cheap when we already have the target hash.
|
||||||
if changed || git.syncCount == 0 {
|
if changed || git.syncCount == 0 {
|
||||||
git.log.V(0).Info("update required", "ref", git.ref, "local", currentHash, "remote", remoteHash, "syncCount", git.syncCount)
|
git.log.V(0).Info("update required", "ref", git.ref, "local", currentHash, "remote", remoteHash, "syncCount", git.syncCount)
|
||||||
|
|
||||||
// Parameters like depth are set at fetch time.
|
|
||||||
if err := git.fetch(ctx, remoteHash); err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
metricFetchCount.Inc()
|
metricFetchCount.Inc()
|
||||||
|
|
||||||
// Reset the repo (note: not the worktree - that happens later) to the new
|
// Reset the repo (note: not the worktree - that happens later) to the new
|
||||||
// ref. This makes subsequent fetches much less expensive. It uses --soft
|
// ref. This makes subsequent fetches much less expensive. It uses --soft
|
||||||
// so no files are checked out.
|
// so no files are checked out.
|
||||||
if _, _, err := git.Run(ctx, git.root, "reset", "--soft", "FETCH_HEAD"); err != nil {
|
if _, _, err := git.Run(ctx, git.root, "reset", "--soft", remoteHash); err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1715,7 +1660,7 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con
|
||||||
|
|
||||||
// fetch retrieves the specified ref from the upstream repo.
|
// fetch retrieves the specified ref from the upstream repo.
|
||||||
func (git *repoSync) fetch(ctx context.Context, ref string) error {
|
func (git *repoSync) fetch(ctx context.Context, ref string) error {
|
||||||
git.log.V(1).Info("fetching", "ref", ref, "repo", git.repo)
|
git.log.V(2).Info("fetching", "ref", ref, "repo", git.repo)
|
||||||
|
|
||||||
// Fetch the ref and do some cleanup, setting or un-setting the repo's
|
// Fetch the ref and do some cleanup, setting or un-setting the repo's
|
||||||
// shallow flag as appropriate.
|
// shallow flag as appropriate.
|
||||||
|
|
|
||||||
|
|
@ -3021,7 +3021,7 @@ function e2e::export_error() {
|
||||||
--error-file="error.json"
|
--error-file="error.json"
|
||||||
assert_file_absent "$ROOT/link"
|
assert_file_absent "$ROOT/link"
|
||||||
assert_file_absent "$ROOT/link/file"
|
assert_file_absent "$ROOT/link/file"
|
||||||
assert_file_contains "$ROOT/error.json" "unknown revision"
|
assert_file_contains "$ROOT/error.json" "couldn't find remote ref"
|
||||||
|
|
||||||
# the error.json file should be removed if sync succeeds.
|
# the error.json file should be removed if sync succeeds.
|
||||||
GIT_SYNC \
|
GIT_SYNC \
|
||||||
|
|
@ -3049,7 +3049,7 @@ function e2e::export_error_abs_path() {
|
||||||
--error-file="$ROOT/dir/error.json"
|
--error-file="$ROOT/dir/error.json"
|
||||||
assert_file_absent "$ROOT/link"
|
assert_file_absent "$ROOT/link"
|
||||||
assert_file_absent "$ROOT/link/file"
|
assert_file_absent "$ROOT/link/file"
|
||||||
assert_file_contains "$ROOT/dir/error.json" "unknown revision"
|
assert_file_contains "$ROOT/dir/error.json" "couldn't find remote ref"
|
||||||
}
|
}
|
||||||
|
|
||||||
##############################################
|
##############################################
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,14 @@ commit (by SHA) it needs. This transfers less data and closes the race
|
||||||
condition where a symbolic name can change after `git ls-remote` but before
|
condition where a symbolic name can change after `git ls-remote` but before
|
||||||
`git fetch`.
|
`git fetch`.
|
||||||
|
|
||||||
|
### The v4.2+ loop
|
||||||
|
|
||||||
|
The v4.2 loop refines the v4 loop even further. Instead of using ls-remote to
|
||||||
|
see what the upstream has and then fetching it, git-sync sill just fetch it by
|
||||||
|
ref. If the local sync already has the corresponding hash, nothing more will
|
||||||
|
be synced. If it did not have that hash before, then it does now and can
|
||||||
|
update the worktree.
|
||||||
|
|
||||||
## Flags
|
## Flags
|
||||||
|
|
||||||
The flag syntax parsing has changed in v4. git-sync v3 accept flags in Go's
|
The flag syntax parsing has changed in v4. git-sync v3 accept flags in Go's
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue