Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Cai <jcai@gitlab.com>2020-03-12 01:07:54 +0300
committerJohn Cai <jcai@gitlab.com>2020-03-12 01:13:24 +0300
commitb3265379ddde9ef6887d8aaac6cb4c183f49de3a (patch)
tree2e0c027529eeb119a5c7a2741e047a6ad3fad92d
parentadd7ccf31a1943d3c3030f83c18362609ca2c177 (diff)
POC repository transactionsjc-repo-tx
-rw-r--r--internal/git/transaction.go164
-rw-r--r--internal/git/transaction_test.go49
2 files changed, 213 insertions, 0 deletions
diff --git a/internal/git/transaction.go b/internal/git/transaction.go
new file mode 100644
index 000000000..c98b96403
--- /dev/null
+++ b/internal/git/transaction.go
@@ -0,0 +1,164 @@
+package git
+
+import (
+ "bytes"
+ "context"
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os/exec"
+
+ "gitlab.com/gitlab-org/gitaly/internal/helper"
+
+ "gitlab.com/gitlab-org/gitaly/internal/git/repository"
+ "gitlab.com/gitlab-org/gitaly/internal/helper/text"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+)
+
+/*
+func init() {
+ gob.Register(gitalypb.Repository{})
+}
+
+*/
+
+type Transaction struct {
+ Args []string
+ repo *gitalypb.Repository
+}
+
+func NewTransaction(repo *gitalypb.Repository, args []string) *Transaction {
+ return &Transaction{
+ Args: args,
+ repo: repo,
+ }
+}
+
+func GetCurrentTransactionID(ctx context.Context, repo repository.GitRepo) (string, error) {
+ getTxID, err := SafeStdinCmd(ctx, repo, nil, SubCmd{
+ Name: "rev-parse",
+ Args: []string{"refs/tx/transaction"},
+ })
+ if err != nil {
+ return "", err
+ }
+
+ txID, err := ioutil.ReadAll(getTxID)
+ if err != nil {
+ return "", err
+ }
+
+ if err = getTxID.Wait(); err != nil {
+ return "", err
+ }
+
+ return text.ChompBytes(txID), nil
+}
+
+func GetCurrentTransaction(repo *gitalypb.Repository, transactionID string) (*Transaction, error) {
+ repoPath, err := helper.GetRepoPath(repo)
+ if err != nil {
+ return nil, err
+ }
+
+ var stdout bytes.Buffer
+ c := exec.Command("git", "-C", repoPath, "cat-file", "-p", transactionID)
+ c.Stdout = &stdout
+
+ if err := c.Run(); err != nil {
+ fmt.Printf("\n\nwhoa %v\n\n", err)
+ return nil, err
+ }
+
+ var transaction Transaction
+ if err := gob.NewDecoder(&stdout).Decode(&transaction); err != nil {
+ return nil, err
+ }
+
+ transaction.repo = repo
+
+ return &transaction, nil
+}
+
+func (t *Transaction) Begin(ctx context.Context) error {
+ // TODO: issue here of creating blobs that may never get used
+ // we don't want to clutter git's object database with these, even though they should get cleaned up during
+ // gc
+ repoPath, err := helper.GetRepoPath(t.repo)
+ if err != nil {
+ return err
+ }
+
+ r, w := io.Pipe()
+
+ var stdout, stderr bytes.Buffer
+
+ c := exec.Command("git", "-C", repoPath, "hash-object", "-w", "--stdin")
+ c.Stdout = &stdout
+ c.Stderr = &stderr
+ c.Stdin = r
+
+ if err := c.Start(); err != nil {
+ return err
+ }
+
+ if err = gob.NewEncoder(w).Encode(t); err != nil {
+ return err
+ }
+ w.Close()
+
+ if err := c.Wait(); err != nil {
+ return err
+ }
+ r.Close()
+
+ blobID := text.ChompBytes(stdout.Bytes())
+
+ lockCmd, err := SafeCmd(ctx, t.repo, nil, SubCmd{
+ Name: "update-ref",
+ Args: []string{"refs/tx/transaction", blobID, "0000000000000000000000000000000000000000"},
+ })
+ if err != nil {
+ return err
+ }
+
+ if err = lockCmd.Wait(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (t *Transaction) Commit(ctx context.Context, transactionID string) error {
+ name := t.Args[0]
+ var args []string
+
+ if len(t.Args) > 1 {
+ args = t.Args[1:]
+ }
+
+ txID, err := GetCurrentTransactionID(ctx, t.repo)
+ if err != nil {
+ return err
+ }
+
+ if txID != transactionID {
+ return errors.New("you are not allowed to commit this transaction")
+ }
+
+ c := exec.Command(name, args...)
+ if err := c.Run(); err != nil {
+ return err
+ }
+
+ repoPath, err := helper.GetRepoPath(t.repo)
+ if err != nil {
+ return err
+ }
+
+ c = exec.Command("git", "-C", repoPath, "update-ref", "-d", "refs/tx/transaction")
+
+ return c.Run()
+}
diff --git a/internal/git/transaction_test.go b/internal/git/transaction_test.go
new file mode 100644
index 000000000..792c1b00c
--- /dev/null
+++ b/internal/git/transaction_test.go
@@ -0,0 +1,49 @@
+package git
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/helper/text"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+)
+
+func TestTransaction(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ repo, testRepoPath, cleanup := testhelper.NewTestRepo(t)
+ defer cleanup()
+
+ refName, err := text.RandomHex(10)
+ require.NoError(t, err)
+
+ require.NoError(t, err)
+
+ transaction := NewTransaction(repo, []string{"git", "-C", testRepoPath, "update-ref", fmt.Sprintf("refs/heads/%s", refName), "master"})
+ require.NoError(t, transaction.Begin(ctx))
+
+ transactionID, err := GetCurrentTransactionID(ctx, repo)
+ require.NoError(t, err)
+
+ tx, err := GetCurrentTransaction(repo, transactionID)
+ require.NoError(t, err)
+ require.Equal(t, transaction, tx)
+
+ // try to create a second transaction
+ refName, err = text.RandomHex(10)
+ require.NoError(t, err)
+
+ anotherTransaction := NewTransaction(repo, []string{"git", "-C", testRepoPath, "update-ref", fmt.Sprintf("refs/heads/%s", refName), "master"})
+ require.Error(t, anotherTransaction.Begin(ctx))
+
+ // try to commit with an incorrect transaction id
+ require.Error(t, tx.Commit(ctx, "67d4350058a6f76a8a3d4133aaaaf96bf8a5698b"))
+
+ // commit the transaction
+ require.NoError(t, tx.Commit(ctx, transactionID))
+
+ // now another transaction can begin
+ require.NoError(t, anotherTransaction.Begin(ctx))
+}