diff --git a/commands/command_push.go b/commands/command_push.go index 81eaa0c7..56142b9c 100644 --- a/commands/command_push.go +++ b/commands/command_push.go @@ -1,6 +1,7 @@ package commands import ( + "bufio" "os" "github.com/git-lfs/git-lfs/v3/errors" @@ -21,12 +22,15 @@ var ( // shares some global vars and functions with command_pre_push.go ) -// pushCommand pushes local objects to a Git LFS server. It takes two -// arguments: +// pushCommand pushes local objects to a Git LFS server. It has four forms: // -// ` ` +// ` ...` +// ` --stdin` (reads refs from stdin) +// ` --object-id ...` +// ` --object-id --stdin` (reads oids from stdin) // -// Remote must be a remote name, not a URL +// Remote must be a remote name, not a URL. With --stdin, values are newline +// separated. // // pushCommand calculates the git objects to send by comparing the range // of commits between the local and remote git servers. @@ -44,15 +48,36 @@ func pushCommand(cmd *cobra.Command, args []string) { } ctx := newUploadContext(pushDryRun) - if pushObjectIDs { - if len(args) < 2 { - Print(tr.Tr.Get("At least one object ID must be supplied with --object-id")) - return + + var argList []string + if useStdin { + if len(args) > 1 { + Print(tr.Tr.Get("Further command line arguments are ignored with --stdin")) + os.Exit(1) } - uploadsWithObjectIDs(ctx, args[1:]) + scanner := bufio.NewScanner(os.Stdin) // line-delimited + for scanner.Scan() { + line := scanner.Text() + if line != "" { + argList = append(argList, line) + } + } + if err := scanner.Err(); err != nil { + ExitWithError(errors.Wrap(err, tr.Tr.Get("Error reading from stdin:"))) + } } else { - uploadsBetweenRefAndRemote(ctx, args[1:]) + argList = args[1:] + } + + if pushObjectIDs { + if len(argList) < 1 { + Print(tr.Tr.Get("At least one object ID must be supplied with --object-id")) + os.Exit(1) + } + uploadsWithObjectIDs(ctx, argList) + } else { + uploadsBetweenRefAndRemote(ctx, argList) } } @@ -137,6 +162,7 @@ func init() { RegisterCommand("push", pushCommand, func(cmd *cobra.Command) { cmd.Flags().BoolVarP(&pushDryRun, "dry-run", "d", false, "Do everything except actually send the updates") cmd.Flags().BoolVarP(&pushObjectIDs, "object-id", "o", false, "Push LFS object ID(s)") + cmd.Flags().BoolVarP(&useStdin, "stdin", "", false, "Read object IDs or refs from stdin") cmd.Flags().BoolVarP(&pushAll, "all", "a", false, "Push all objects for the current ref to the remote.") }) } diff --git a/docs/man/git-lfs-push.adoc b/docs/man/git-lfs-push.adoc index 26eff85c..0820c029 100644 --- a/docs/man/git-lfs-push.adoc +++ b/docs/man/git-lfs-push.adoc @@ -8,7 +8,9 @@ git-lfs-push - Push queued large files to the Git LFS endpoint `git lfs push` [options] [...] + `git lfs push` [...] + +`git lfs push` [options] --stdin `git lfs push` --object-id [...] +`git lfs push` --object-id --stdin == DESCRIPTION @@ -32,6 +34,9 @@ by the local clone of the remote. `--object-id`:: This pushes only the object OIDs listed at the end of the command, separated by spaces. +`--stdin`:: + Read a list of newline-delimited refs (or object IDs when using `--object-id`) + from standard input instead of the command line. == SEE ALSO diff --git a/t/t-push.sh b/t/t-push.sh index c43225c3..5eb9c5d3 100755 --- a/t/t-push.sh +++ b/t/t-push.sh @@ -66,6 +66,17 @@ begin_test "push with given remote, configured pushRemote" ) end_test +begin_test "push via stdin with extra arguments" +( + set -e + + push_repo_setup "push-stdin-extra-args" + + echo "main" | git lfs push origin --stdin --dry-run "another-ref" \ + 2>&1 | tee push.log + grep "Further command line arguments are ignored with --stdin" push.log +) + begin_test "push" ( set -e @@ -98,6 +109,11 @@ begin_test "push" grep "push 82be50ad35070a4ef3467a0a650c52d5b637035e7ad02c36652e59d01ba282b7 => b.dat" push.log [ $(grep -c "^push " < push.log) -eq 2 ] + printf "push-b\n\n" | git lfs push --dry-run origin --stdin 2>&1 | tee push.log + grep "push 4c48d2a6991c9895bcddcf027e1e4907280bcf21975492b1afbade396d6a3340 => a.dat" push.log + grep "push 82be50ad35070a4ef3467a0a650c52d5b637035e7ad02c36652e59d01ba282b7 => b.dat" push.log + [ $(grep -c "^push " < push.log) -eq 2 ] + # simulate remote ref mkdir -p .git/refs/remotes/origin git rev-parse HEAD > .git/refs/remotes/origin/HEAD @@ -301,6 +317,13 @@ begin_test "push --all (multiple ref args)" grep "push $oid4 => file1.dat" push.log [ $(grep -c "^push " push.log) -eq 4 ] + printf "branch\ntag" | git lfs push --dry-run --all origin --stdin 2>&1 | tee push.log + grep "push $oid1 => file1.dat" push.log + grep "push $oid2 => file1.dat" push.log + grep "push $oid3 => file1.dat" push.log + grep "push $oid4 => file1.dat" push.log + [ $(grep -c "^push " push.log) -eq 4 ] + git lfs push --all origin branch tag 2>&1 | tee push.log [ $(grep -c "Uploading LFS objects: 100% (4/4)" push.log) -eq 1 ] assert_server_object "$reponame-$suffix" "$oid1" @@ -426,6 +449,47 @@ begin_test "push object id(s)" ) end_test +begin_test "push object id(s) via stdin" +( + set -e + + reponame="$(basename "$0" ".sh")" + setup_remote_repo "$reponame" + clone_repo "$reponame" repo3 + + git config "lfs.$(repo_endpoint "$GITSERVER" "$reponame").locksverify" true + + git lfs track "*.dat" + echo "push a" > a.dat + git add .gitattributes a.dat + git commit -m "add a.dat" + + echo "" | git lfs push --object-id origin --stdin --dry-run \ + 2>&1 | tee push.log + grep "At least one object ID must be supplied with --object-id" push.log + + echo "4c48d2a6991c9895bcddcf027e1e4907280bcf21975492b1afbade396d6a3340" | \ + git lfs push --object-id origin --stdin --dry-run "c0ffee" \ + 2>&1 | tee push.log + grep "Further command line arguments are ignored with --stdin" push.log + + echo "4c48d2a6991c9895bcddcf027e1e4907280bcf21975492b1afbade396d6a3340" | \ + git lfs push --object-id origin --stdin --dry-run \ + 2>&1 | tee push.log + grep "push 4c48d2a6991c9895bcddcf027e1e4907280bcf21975492b1afbade396d6a3340 =>" push.log + + echo "push b" > b.dat + git add b.dat + git commit -m "add b.dat" + + printf "4c48d2a6991c9895bcddcf027e1e4907280bcf21975492b1afbade396d6a3340\n82be50ad35070a4ef3467a0a650c52d5b637035e7ad02c36652e59d01ba282b7\n\n" | \ + git lfs push --object-id origin --stdin --dry-run \ + 2>&1 | tee push.log + grep "push 4c48d2a6991c9895bcddcf027e1e4907280bcf21975492b1afbade396d6a3340 =>" push.log + grep "push 82be50ad35070a4ef3467a0a650c52d5b637035e7ad02c36652e59d01ba282b7 =>" push.log +) +end_test + begin_test "push modified files" ( set -e