From 8c1ad569245fdbe296c7be55788f3c6158361856 Mon Sep 17 00:00:00 2001 From: rick olson Date: Fri, 5 Jan 2018 15:01:29 -0700 Subject: [PATCH] lfs,git: send refspec in batch api calls from smudge filter --- git/refs.go | 101 +++++++++++++++++++++++++++++++++++++ lfs/gitfilter.go | 5 ++ lfs/gitfilter_smudge.go | 5 +- test/test-fetch-refspec.sh | 88 ++++++++++++++++++++++++++++++++ test/test-happy-path.sh | 56 ++++++++++++++++++++ 5 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 git/refs.go create mode 100755 test/test-fetch-refspec.sh diff --git a/git/refs.go b/git/refs.go new file mode 100644 index 00000000..60212e56 --- /dev/null +++ b/git/refs.go @@ -0,0 +1,101 @@ +package git + +import ( + "fmt" + + "github.com/rubyist/tracerx" +) + +type RefUpdate struct { + git Env + remote string + left *Ref + right *Ref +} + +func NewRefUpdate(g Env, remote string, l, r *Ref) *RefUpdate { + return &RefUpdate{ + git: g, + remote: remote, + left: l, + right: r, + } +} + +func (u *RefUpdate) Left() *Ref { + return u.left +} + +func (u *RefUpdate) LeftCommitish() string { + return refCommitish(u.Left()) +} + +func (u *RefUpdate) Right() *Ref { + if u.right == nil { + u.right = defaultRemoteRef(u.git, u.remote, u.Left()) + } + return u.right +} + +// defaultRemoteRef returns the remote ref receiving a push based on the current +// repository config and local ref being pushed. +// +// See push.default rules in https://git-scm.com/docs/git-config +func defaultRemoteRef(g Env, remote string, left *Ref) *Ref { + pushMode, _ := g.Get("push.default") + tracerx.Printf("DEBUG %q pushmode = %q", remote, pushMode) + switch pushMode { + case "", "simple": + brRemote, _ := g.Get(fmt.Sprintf("branch.%s.remote", left.Name)) + if brRemote == remote { + // in centralized workflow, work like 'upstream' with an added safety to + // refuse to push if the upstream branch’s name is different from the + // local one. + return trackingRef(g, left) + } + + // When pushing to a remote that is different from the remote you normally + // pull from, work as current. + return left + case "upstream", "tracking": + // push the current branch back to the branch whose changes are usually + // integrated into the current branch + return trackingRef(g, left) + case "current": + // push the current branch to update a branch with the same name on the + // receiving end. + return left + default: + tracerx.Printf("WARNING: %q push mode not supported", pushMode) + return left + } +} + +func trackingRef(g Env, left *Ref) *Ref { + if merge, ok := g.Get(fmt.Sprintf("branch.%s.merge", left.Name)); ok { + tracerx.Printf("DEBUG %q branch merge %q", left.Name, merge) + return ParseRef(merge, "") + } + tracerx.Printf("DEBUG %q branch merge default %+v", left.Name, left) + return left +} + +func (u *RefUpdate) RightCommitish() string { + return refCommitish(u.Right()) +} + +func refCommitish(r *Ref) string { + if len(r.Sha) > 0 { + return r.Sha + } + return r.Name +} + +// copy of env +type Env interface { + Get(key string) (val string, ok bool) + GetAll(key string) (vals []string) + Bool(key string, def bool) (val bool) + Int(key string, def int) (val int) + All() map[string][]string +} diff --git a/lfs/gitfilter.go b/lfs/gitfilter.go index 0f6ffbcd..d3926aae 100644 --- a/lfs/gitfilter.go +++ b/lfs/gitfilter.go @@ -3,6 +3,7 @@ package lfs import ( "github.com/git-lfs/git-lfs/config" "github.com/git-lfs/git-lfs/fs" + "github.com/git-lfs/git-lfs/git" ) // GitFilter provides clean and smudge capabilities @@ -19,3 +20,7 @@ func NewGitFilter(cfg *config.Configuration) *GitFilter { func (f *GitFilter) ObjectPath(oid string) (string, error) { return f.fs.ObjectPath(oid) } + +func (f *GitFilter) RemoteRef() *git.Ref { + return git.NewRefUpdate(f.cfg.Git, f.cfg.PushRemote(), f.cfg.CurrentRef(), nil).Right() +} diff --git a/lfs/gitfilter_smudge.go b/lfs/gitfilter_smudge.go index 7eaa24e7..0d1c0d1b 100644 --- a/lfs/gitfilter_smudge.go +++ b/lfs/gitfilter_smudge.go @@ -80,7 +80,10 @@ func (f *GitFilter) downloadFile(writer io.Writer, ptr *Pointer, workingfile, me // Either way, forward it into the *tq.TransferQueue so that updates are // sent over correctly. - q := tq.NewTransferQueue(tq.Download, manifest, f.cfg.Remote(), tq.WithProgressCallback(cb)) + q := tq.NewTransferQueue(tq.Download, manifest, f.cfg.Remote(), + tq.WithProgressCallback(cb), + tq.RemoteRef(f.RemoteRef()), + ) q.Add(filepath.Base(workingfile), mediafile, ptr.Oid, ptr.Size) q.Wait() diff --git a/test/test-fetch-refspec.sh b/test/test-fetch-refspec.sh new file mode 100755 index 00000000..38529a12 --- /dev/null +++ b/test/test-fetch-refspec.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +. "test/testlib.sh" + +begin_test "fetch with good ref" +( + set -e + + reponame="fetch-master-branch-required" + setup_remote_repo "$reponame" + clone_repo "$reponame" "$reponame" + + git lfs track "*.dat" + echo "a" > a.dat + git add .gitattributes a.dat + git commit -m "add a.dat" + + git push origin master + + # $ echo "a" | shasum -a 256 + oid="87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7" + assert_local_object "$oid" 2 + assert_server_object "$reponame" "$oid" "refs/heads/master" + + rm -rf .git/lfs/objects + git lfs fetch --all + assert_local_object "$oid" 2 +) +end_test + +begin_test "fetch with tracked ref" +( + set -e + + reponame="fetch-tracked-branch-required" + setup_remote_repo "$reponame" + clone_repo "$reponame" "$reponame" + + git lfs track "*.dat" + echo "a" > a.dat + git add .gitattributes a.dat + git commit -m "add a.dat" + + git push origin master:tracked + + # $ echo "a" | shasum -a 256 + oid="87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7" + assert_local_object "$oid" 2 + assert_server_object "$reponame" "$oid" "refs/heads/tracked" + + rm -rf .git/lfs/objects + git config push.default upstream + git config branch.master.merge refs/heads/tracked + git lfs fetch --all + assert_local_object "$oid" 2 +) +end_test + +begin_test "fetch with bad ref" +( + set -e + + reponame="fetch-other-branch-required" + setup_remote_repo "$reponame" + clone_repo "$reponame" "$reponame" + + git lfs track "*.dat" + echo "a" > a.dat + git add .gitattributes a.dat + git commit -m "add a.dat" + + git push origin master:other + + # $ echo "a" | shasum -a 256 + oid="87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7" + assert_local_object "$oid" 2 + assert_server_object "$reponame" "$oid" "refs/heads/other" + + rm -rf .git/lfs/objects + GIT_CURL_VERBOSE=1 git lfs fetch --all 2>&1 | tee fetch.log + if [ "0" -eq "${PIPESTATUS[0]}" ]; then + echo >&2 "fatal: expected 'git lfs fetch' to fail" + exit 1 + fi + + grep 'Expected ref "refs/heads/other", got "refs/heads/master"' fetch.log +) +end_test diff --git a/test/test-happy-path.sh b/test/test-happy-path.sh index 84adf85b..93d2bd5e 100755 --- a/test/test-happy-path.sh +++ b/test/test-happy-path.sh @@ -93,6 +93,62 @@ begin_test "happy path on non-origin remote" ) end_test +begin_test "happy path on good ref" +( + set -e + + reponame="happy-path-master-branch-required" + setup_remote_repo "$reponame" + clone_repo "$reponame" "$reponame" + + git lfs track "*.dat" + echo "a" > a.dat + git add .gitattributes a.dat + git commit -m "add a.dat" + + git push origin master + + # $ echo "a" | shasum -a 256 + oid="87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7" + assert_local_object "$oid" 2 + assert_server_object "$reponame" "$oid" "refs/heads/master" + + clone_repo "$reponame" "$reponame-clone" + assert_local_object "$oid" 2 +) +end_test + +begin_test "happy path on tracked ref" +( + set -e + + reponame="happy-path-tracked-branch-required" + setup_remote_repo "$reponame" + clone_repo "$reponame" "$reponame" + + git lfs track "*.dat" + echo "a" > a.dat + git add .gitattributes a.dat + git commit -m "add a.dat" + + git push origin master:tracked + + # $ echo "a" | shasum -a 256 + oid="87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7" + assert_local_object "$oid" 2 + assert_server_object "$reponame" "$oid" "refs/heads/tracked" + + git lfs clone "$GITSERVER/$reponame" --exclude "*" + + git config credential.helper lfstest + git config push.default upstream + git config branch.master.merge refs/heads/tracked + + git checkout + assert_local_object "$oid" 2 +) +end_test + begin_test "clears local temp objects" ( set -e