From 1265f041dd3e6c7fb2313af65757f0b90f623dd2 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 9 May 2023 14:03:59 -0400 Subject: [PATCH 1/5] feat: add server-side git commands Add server-side git commands for handling git services. This includes git-upload-pack, git-upload-archive, and git-receive-pack services. Also add git-update-server-info helper. Signed-off-by: Ayman Bagabas --- server.go | 122 +++++++++++++++++++++++++++++++++++++++++++++++++ server_test.go | 27 +++++++++++ 2 files changed, 149 insertions(+) create mode 100755 server.go create mode 100755 server_test.go diff --git a/server.go b/server.go new file mode 100755 index 00000000..727c34c1 --- /dev/null +++ b/server.go @@ -0,0 +1,122 @@ +package git + +import "time" + +// UpdateServerInfoOptions represents the available UpdateServerInfo() options. +type UpdateServerInfoOptions struct { + // Force indicates to overwrite the existing server info. + Force bool + // Timeout represents the maximum time in duration that the command is allowed to run. + // + // Deprecated: Use CommandOptions.Timeout instead. + Timeout time.Duration + // The additional options to be passed to the underlying git. + CommandOptions +} + +// UpdateServerInfo updates the server info in the repository. +func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) ([]byte, error) { + var opt UpdateServerInfoOptions + if len(opts) > 0 { + opt = opts[0] + } + cmd := NewCommand("update-server-info").AddOptions(opt.CommandOptions) + if opt.Force { + cmd.AddArgs("--force") + } + cmd.AddArgs(".") + return cmd.RunInDirWithTimeout(opt.Timeout, path) +} + +// ReceivePackOptions represents the available options for ReceivePack(). +type ReceivePackOptions struct { + // Quiet is true for not printing anything to stdout. + Quiet bool + // Timeout represents the maximum time in duration that the command is allowed to run. + // + // Deprecated: Use CommandOptions.Timeout instead. + Timeout time.Duration + // HttpBackendInfoRefs indicates generating the info/refs in the + // http-backend. This is used for smart http. + HttpBackendInfoRefs bool + // The additional options to be passed to the underlying git. + CommandOptions +} + +// RecevePack receives the packfile from the client. +func ReceivePack(path string, opts ...ReceivePackOptions) ([]byte, error) { + var opt ReceivePackOptions + if len(opts) > 0 { + opt = opts[0] + } + cmd := NewCommand("receive-pack").AddOptions(opt.CommandOptions) + if opt.Quiet { + cmd.AddArgs("--quiet") + } + if opt.HttpBackendInfoRefs { + cmd.AddArgs("--http-backend-info-refs") + } + cmd.AddArgs(".") + return cmd.RunInDirWithTimeout(opt.Timeout, path) +} + +// UploadPackOptions represents the available options for UploadPack(). +type UploadPackOptions struct { + // Quit after a single request/response exchange. + StatelessRPC bool + // Do not try /.git/ if is no Git directory. + Strict bool + // Interrupt transfer after seconds of inactivity. + // Note: this is different from CommandOptions.Timeout which is the maximum + // time in duration that the command is allowed to run. + Timeout time.Duration + // HttpBackendInfoRefs indicates generating the info/refs in the + // http-backend. This is used for smart http. + HttpBackendInfoRefs bool + // The additional options to be passed to the underlying git. + CommandOptions +} + +// UploadPack uploads the packfile to the client. +func UploadPack(path string, opts ...UploadPackOptions) ([]byte, error) { + var opt UploadPackOptions + if len(opts) > 0 { + opt = opts[0] + } + cmd := NewCommand("upload-pack").AddOptions(opt.CommandOptions) + if opt.StatelessRPC { + cmd.AddArgs("--stateless-rpc") + } + if opt.Strict { + cmd.AddArgs("--strict") + } + if opt.Timeout > 0 { + cmd.AddArgs("--timeout", opt.Timeout.String()) + } + if opt.HttpBackendInfoRefs { + cmd.AddArgs("--http-backend-info-refs") + } + cmd.AddArgs(".") + return cmd.RunInDirWithTimeout(opt.Timeout, path) +} + +// UploadArchiveOptions represents the available options for UploadArchive(). +type UploadArchiveOptions struct { + // Timeout represents the maximum time in duration that the command is allowed to run. + // + // Deprecated: Use CommandOptions.Timeout instead. + Timeout time.Duration + // The additional options to be passed to the underlying git. + CommandOptions +} + +// UploadArchive uploads the archive to the client. +func UploadArchive(path string, opts ...UploadArchiveOptions) ([]byte, error) { + var opt UploadArchiveOptions + if len(opts) > 0 { + opt = opts[0] + } + cmd := NewCommand("upload-archive").AddOptions(opt.CommandOptions) + cmd.AddArgs(".") + return cmd.RunInDirWithTimeout(opt.Timeout, path) +} diff --git a/server_test.go b/server_test.go new file mode 100755 index 00000000..96f669bb --- /dev/null +++ b/server_test.go @@ -0,0 +1,27 @@ +package git + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpdateServerInfo(t *testing.T) { + tests := []struct { + path string + }{ + { + path: repoPath, + }, + } + for _, test := range tests { + t.Run("update-server-info", func(t *testing.T) { + _, err := UpdateServerInfo(test.path, UpdateServerInfoOptions{Force: true}) + assert.NoError(t, err) + _, err = os.Stat(filepath.Join(test.path, "info", "refs")) + assert.NoError(t, err) + }) + } +} From 6cf29dac30ce3d7e0f39e87cfa4aea2d4f2379ec Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Thu, 10 Aug 2023 18:49:34 -0400 Subject: [PATCH 2/5] Minor tidy up --- repo_reference.go | 2 +- server.go | 77 +++++++++++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/repo_reference.go b/repo_reference.go index 92a08d27..faf74b4e 100644 --- a/repo_reference.go +++ b/repo_reference.go @@ -244,7 +244,7 @@ func (r *Repository) Branches() ([]string, error) { // DeleteBranchOptions contains optional arguments for deleting a branch. // -// // Docs: https://git-scm.com/docs/git-branch +// Docs: https://git-scm.com/docs/git-branch type DeleteBranchOptions struct { // Indicates whether to force delete the branch. Force bool diff --git a/server.go b/server.go index 727c34c1..5fdde7d1 100755 --- a/server.go +++ b/server.go @@ -1,20 +1,27 @@ +// Copyright 2023 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package git import "time" -// UpdateServerInfoOptions represents the available UpdateServerInfo() options. +// UpdateServerInfoOptions contains optional arguments for updating auxiliary +// info file on the server side. +// +// Docs: https://git-scm.com/docs/git-update-server-info type UpdateServerInfoOptions struct { - // Force indicates to overwrite the existing server info. + // Indicates whether to overwrite the existing server info. Force bool - // Timeout represents the maximum time in duration that the command is allowed to run. - // - // Deprecated: Use CommandOptions.Timeout instead. + // The timeout duration before giving up for each shell command execution. The + // default timeout duration will be used when not supplied. Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } -// UpdateServerInfo updates the server info in the repository. +// UpdateServerInfo updates the auxiliary info file on the server side for the +// repository in given path. func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) ([]byte, error) { var opt UpdateServerInfoOptions if len(opts) > 0 { @@ -28,22 +35,23 @@ func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) ([]byte, err return cmd.RunInDirWithTimeout(opt.Timeout, path) } -// ReceivePackOptions represents the available options for ReceivePack(). +// ReceivePackOptions contains optional arguments for receiving the info pushed +// to the repository. +// +// Docs: https://git-scm.com/docs/git-receive-pack type ReceivePackOptions struct { - // Quiet is true for not printing anything to stdout. + // Indicates whether to suppress the log output. Quiet bool - // Timeout represents the maximum time in duration that the command is allowed to run. - // - // Deprecated: Use CommandOptions.Timeout instead. + // Indicates whether to generate the "info/refs" used by the "git http-backend". + HTTPBackendInfoRefs bool + // The timeout duration before giving up for each shell command execution. The + // default timeout duration will be used when not supplied. Timeout time.Duration - // HttpBackendInfoRefs indicates generating the info/refs in the - // http-backend. This is used for smart http. - HttpBackendInfoRefs bool // The additional options to be passed to the underlying git. CommandOptions } -// RecevePack receives the packfile from the client. +// ReceivePack receives what is pushed into the repository in given path. func ReceivePack(path string, opts ...ReceivePackOptions) ([]byte, error) { var opt ReceivePackOptions if len(opts) > 0 { @@ -53,31 +61,33 @@ func ReceivePack(path string, opts ...ReceivePackOptions) ([]byte, error) { if opt.Quiet { cmd.AddArgs("--quiet") } - if opt.HttpBackendInfoRefs { + if opt.HTTPBackendInfoRefs { cmd.AddArgs("--http-backend-info-refs") } cmd.AddArgs(".") return cmd.RunInDirWithTimeout(opt.Timeout, path) } -// UploadPackOptions represents the available options for UploadPack(). +// UploadPackOptions contains optional arguments for sending the packfile to the +// client. +// +// Docs: https://git-scm.com/docs/git-upload-pack type UploadPackOptions struct { - // Quit after a single request/response exchange. + // Indicates whether to quit after a single request/response exchange. StatelessRPC bool - // Do not try /.git/ if is no Git directory. + // Indicates whether to not try "/.git/" if "" is not a + // Git directory. Strict bool - // Interrupt transfer after seconds of inactivity. - // Note: this is different from CommandOptions.Timeout which is the maximum - // time in duration that the command is allowed to run. + // Indicates whether to generate the "info/refs" used by the "git http-backend". + HTTPBackendInfoRefs bool + // The timeout duration before giving up for each shell command execution. The + // default timeout duration will be used when not supplied. Timeout time.Duration - // HttpBackendInfoRefs indicates generating the info/refs in the - // http-backend. This is used for smart http. - HttpBackendInfoRefs bool // The additional options to be passed to the underlying git. CommandOptions } -// UploadPack uploads the packfile to the client. +// UploadPack sends the packfile to the client for the repository in given path. func UploadPack(path string, opts ...UploadPackOptions) ([]byte, error) { var opt UploadPackOptions if len(opts) > 0 { @@ -93,24 +103,27 @@ func UploadPack(path string, opts ...UploadPackOptions) ([]byte, error) { if opt.Timeout > 0 { cmd.AddArgs("--timeout", opt.Timeout.String()) } - if opt.HttpBackendInfoRefs { + if opt.HTTPBackendInfoRefs { cmd.AddArgs("--http-backend-info-refs") } cmd.AddArgs(".") return cmd.RunInDirWithTimeout(opt.Timeout, path) } -// UploadArchiveOptions represents the available options for UploadArchive(). +// UploadArchiveOptions contains optional arguments for sending the archive to +// the client. +// +// Docs: https://git-scm.com/docs/git-upload-archive type UploadArchiveOptions struct { - // Timeout represents the maximum time in duration that the command is allowed to run. - // - // Deprecated: Use CommandOptions.Timeout instead. + // The timeout duration before giving up for each shell command execution. The + // default timeout duration will be used when not supplied. Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } -// UploadArchive uploads the archive to the client. +// UploadArchive sends the archive to the client for the repository in given +// path. func UploadArchive(path string, opts ...UploadArchiveOptions) ([]byte, error) { var opt UploadArchiveOptions if len(opts) > 0 { From 3676e4f5046c1b265263d7d22311cfcb3bd255cb Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Thu, 10 Aug 2023 19:04:01 -0400 Subject: [PATCH 3/5] Fix TestUpdateServerInfo --- server.go | 6 +++--- server_test.go | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index 5fdde7d1..7df74250 100755 --- a/server.go +++ b/server.go @@ -22,7 +22,7 @@ type UpdateServerInfoOptions struct { // UpdateServerInfo updates the auxiliary info file on the server side for the // repository in given path. -func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) ([]byte, error) { +func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) error { var opt UpdateServerInfoOptions if len(opts) > 0 { opt = opts[0] @@ -31,8 +31,8 @@ func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) ([]byte, err if opt.Force { cmd.AddArgs("--force") } - cmd.AddArgs(".") - return cmd.RunInDirWithTimeout(opt.Timeout, path) + _, err := cmd.RunInDirWithTimeout(opt.Timeout, path) + return err } // ReceivePackOptions contains optional arguments for receiving the info pushed diff --git a/server_test.go b/server_test.go index 96f669bb..305f935f 100755 --- a/server_test.go +++ b/server_test.go @@ -1,3 +1,7 @@ +// Copyright 2023 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package git import ( @@ -6,6 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestUpdateServerInfo(t *testing.T) { @@ -18,10 +23,11 @@ func TestUpdateServerInfo(t *testing.T) { } for _, test := range tests { t.Run("update-server-info", func(t *testing.T) { - _, err := UpdateServerInfo(test.path, UpdateServerInfoOptions{Force: true}) - assert.NoError(t, err) - _, err = os.Stat(filepath.Join(test.path, "info", "refs")) - assert.NoError(t, err) + err := os.RemoveAll(filepath.Join(test.path, "info")) + require.NoError(t, err) + err = UpdateServerInfo(test.path, UpdateServerInfoOptions{Force: true}) + require.NoError(t, err) + assert.True(t, isFile(filepath.Join(test.path, "info", "refs"))) }) } } From 9145420aa2f738a75c245a54427c3e0aa6437d89 Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Thu, 10 Aug 2023 21:10:37 -0400 Subject: [PATCH 4/5] Add more tests --- server.go | 4 +++- server_test.go | 40 +++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/server.go b/server.go index 7df74250..9ff703b0 100755 --- a/server.go +++ b/server.go @@ -4,7 +4,9 @@ package git -import "time" +import ( + "time" +) // UpdateServerInfoOptions contains optional arguments for updating auxiliary // info file on the server side. diff --git a/server_test.go b/server_test.go index 305f935f..0df7789a 100755 --- a/server_test.go +++ b/server_test.go @@ -14,20 +14,30 @@ import ( ) func TestUpdateServerInfo(t *testing.T) { - tests := []struct { - path string - }{ - { - path: repoPath, + err := os.RemoveAll(filepath.Join(repoPath, "info")) + require.NoError(t, err) + err = UpdateServerInfo(repoPath, UpdateServerInfoOptions{Force: true}) + require.NoError(t, err) + assert.True(t, isFile(filepath.Join(repoPath, "info", "refs"))) +} + +func TestReceivePack(t *testing.T) { + got, err := ReceivePack(repoPath, ReceivePackOptions{HTTPBackendInfoRefs: true}) + require.NoError(t, err) + const contains = "report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta object-format=sha1 agent=git/" + assert.Contains(t, string(got), contains) +} + +func TestUploadPack(t *testing.T) { + got, err := UploadPack(repoPath, + UploadPackOptions{ + StatelessRPC: true, + Strict: true, + HTTPBackendInfoRefs: true, }, - } - for _, test := range tests { - t.Run("update-server-info", func(t *testing.T) { - err := os.RemoveAll(filepath.Join(test.path, "info")) - require.NoError(t, err) - err = UpdateServerInfo(test.path, UpdateServerInfoOptions{Force: true}) - require.NoError(t, err) - assert.True(t, isFile(filepath.Join(test.path, "info", "refs"))) - }) - } + ) + require.NoError(t, err) + const contains = "multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master object-format=sha1 agent=git/" + assert.Contains(t, string(got), contains) } + From 43fd7c3c9e89de58851d0c29ac02ba7764fed241 Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Thu, 10 Aug 2023 21:34:50 -0400 Subject: [PATCH 5/5] Remove `UploadArchive` --- server.go | 24 ------------------------ server_test.go | 1 - 2 files changed, 25 deletions(-) diff --git a/server.go b/server.go index 9ff703b0..0c2d275e 100755 --- a/server.go +++ b/server.go @@ -111,27 +111,3 @@ func UploadPack(path string, opts ...UploadPackOptions) ([]byte, error) { cmd.AddArgs(".") return cmd.RunInDirWithTimeout(opt.Timeout, path) } - -// UploadArchiveOptions contains optional arguments for sending the archive to -// the client. -// -// Docs: https://git-scm.com/docs/git-upload-archive -type UploadArchiveOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration - // The additional options to be passed to the underlying git. - CommandOptions -} - -// UploadArchive sends the archive to the client for the repository in given -// path. -func UploadArchive(path string, opts ...UploadArchiveOptions) ([]byte, error) { - var opt UploadArchiveOptions - if len(opts) > 0 { - opt = opts[0] - } - cmd := NewCommand("upload-archive").AddOptions(opt.CommandOptions) - cmd.AddArgs(".") - return cmd.RunInDirWithTimeout(opt.Timeout, path) -} diff --git a/server_test.go b/server_test.go index 0df7789a..b8d95dc2 100755 --- a/server_test.go +++ b/server_test.go @@ -40,4 +40,3 @@ func TestUploadPack(t *testing.T) { const contains = "multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master object-format=sha1 agent=git/" assert.Contains(t, string(got), contains) } -