diff --git a/cmd/git-remote-gitopia/gitopia.go b/cmd/git-remote-gitopia/gitopia.go index 1b36eea..87d6c90 100644 --- a/cmd/git-remote-gitopia/gitopia.go +++ b/cmd/git-remote-gitopia/gitopia.go @@ -1,27 +1,16 @@ package main import ( - "bytes" "context" "encoding/json" "fmt" - "net/http" "os" + "strconv" "strings" - clientTx "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" "github.com/cosmos/cosmos-sdk/crypto/hd" - cosmoscryptoed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - cosmoscryptosecp "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - authTx "github.com/cosmos/cosmos-sdk/x/auth/tx" - authType "github.com/cosmos/cosmos-sdk/x/auth/types" core "github.com/gitopia/git-remote-gitopia/core" gitopiaTypes "github.com/gitopia/gitopia/x/gitopia/types" "github.com/gitopia/gitopia/x/gitopia/utils" @@ -34,11 +23,10 @@ import ( ) const ( - chainID = "internal-4" AccountAddressPrefix = "gitopia" - apiURL = "34.87.90.147:9090" - objectsURL = "http://34.126.69.254:5000" - saveToArweaveURL = "http://34.126.69.254:5000/save" + apiURL = "34.126.183.252:9090" + objectsURL = "http://34.87.64.22:5000" + saveToArweaveURL = "http://34.87.64.22:5000/save" branchPrefix = "refs/heads/" tagPrefix = "refs/tags/" ) @@ -70,10 +58,10 @@ type SaveToArweavePostBody struct { } type GitopiaHandler struct { - queryClient gitopiaTypes.QueryClient - accountQueryClient authType.QueryClient - txClient tx.ServiceClient + grpcConn *grpc.ClientConn + queryClient gitopiaTypes.QueryClient + chainId string remoteUserId string remoteRepositoryName string remoteRepository gitopiaTypes.Repository @@ -82,7 +70,9 @@ type GitopiaHandler struct { } func (h *GitopiaHandler) Initialize(remote *core.Remote) error { - grpcConn, err := grpc.Dial(apiURL, + var err error + + h.grpcConn, err = grpc.Dial(apiURL, grpc.WithInsecure(), ) if err != nil { @@ -90,9 +80,15 @@ func (h *GitopiaHandler) Initialize(remote *core.Remote) error { } // defer grpcConn.Close() - h.queryClient = gitopiaTypes.NewQueryClient(grpcConn) - h.accountQueryClient = authType.NewQueryClient(grpcConn) - h.txClient = tx.NewServiceClient(grpcConn) + h.queryClient = gitopiaTypes.NewQueryClient(h.grpcConn) + serviceClient := tmservice.NewServiceClient(h.grpcConn) + + // Get chain id for signing transaction + nodeInfoRes, err := serviceClient.GetNodeInfo(context.Background(), &tmservice.GetNodeInfoRequest{}) + if err != nil { + return err + } + h.chainId = nodeInfoRes.DefaultNodeInfo.Network // Get RepositoryId res, err := h.queryClient.AddressRepository(context.Background(), &gitopiaTypes.QueryGetAddressRepositoryRequest{ @@ -100,11 +96,15 @@ func (h *GitopiaHandler) Initialize(remote *core.Remote) error { RepositoryName: h.remoteRepositoryName, }) if err != nil { - return fmt.Errorf("fatal: repository 'gitopia://%s/%s' not found. Please create it from the gitopia webapp", h.remoteUserId, h.remoteRepositoryName) + return err } h.remoteRepository = *res.Repository + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(AccountAddressPrefix, AccountPubKeyPrefix) + config.Seal() + return nil } @@ -164,18 +164,8 @@ func (h *GitopiaHandler) Fetch(remote *core.Remote, sha, ref string) error { func (h *GitopiaHandler) Push(remote *core.Remote, local string, remoteRef string) (string, error) { h.didPush = true - remoteURL := fmt.Sprintf("%v/%v.git", objectsURL, h.remoteRepository.Id) - remoteConfig := &goGitConfig.RemoteConfig{ - Name: "gitopia-objects-store", - URLs: []string{remoteURL}, - } - - _, err := remote.Repo.CreateRemote(remoteConfig) - if err != nil { - return "", err - } - defer remote.Repo.DeleteRemote("gitopia-objects-store") + // Read wallet file gitopiaWalletPath := os.Getenv("GITOPIA_WALLET") if gitopiaWalletPath == "" { return "", fmt.Errorf("fatal: GITOPIA_WALLET environment variable is not set") @@ -192,17 +182,14 @@ func (h *GitopiaHandler) Push(remote *core.Remote, local string, remoteRef strin return "", fmt.Errorf("fatal: error decoding wallet file") } - derivedPriv, err := hd.Secp256k1.Derive()(gitopiaWallet.Mnemonic, "", gitopiaWallet.HDpath) + // Generate private key + hdPath := gitopiaWallet.HDpath + strconv.Itoa(gitopiaWallet.PathIncrement) + derivedPriv, err := hd.Secp256k1.Derive()(gitopiaWallet.Mnemonic, "", hdPath) if err != nil { return "", err } privKey := hd.Secp256k1.Generate()(derivedPriv) - - config := sdk.GetConfig() - config.SetBech32PrefixForAccount(AccountAddressPrefix, AccountPubKeyPrefix) - config.Seal() - walletAddress := sdk.AccAddress(privKey.PubKey().Address()) havePushPermission, err := h.havePushPermission(walletAddress.String()) @@ -213,10 +200,55 @@ func (h *GitopiaHandler) Push(remote *core.Remote, local string, remoteRef strin return "", fmt.Errorf("fatal: you don't have write permissions to this repository") } + var msg sdk.Msg + + // Delete branch/tag + if local == "" { + if strings.HasPrefix(remoteRef, branchPrefix) { + remoteBranchName := strings.TrimPrefix(remoteRef, branchPrefix) + + // Check if it's the default branch + if remoteBranchName == h.remoteRepository.DefaultBranch { + return "", fmt.Errorf("fatal: cannot delete default branch, %v", remoteBranchName) + } + + msg = gitopiaTypes.NewMsgDeleteBranch(walletAddress.String(), h.remoteRepository.Id, remoteBranchName) + } else if strings.HasPrefix(remoteRef, tagPrefix) { + remoteTagName := strings.TrimPrefix(remoteRef, tagPrefix) + msg = gitopiaTypes.NewMsgDeleteTag(walletAddress.String(), h.remoteRepository.Id, remoteTagName) + } + + err := signAndBroadcastTx(h.grpcConn, walletAddress.String(), h.chainId, privKey, msg) + if err != nil { + return "", err + } + + return local, nil + } + + remoteURL := fmt.Sprintf("%v/%v.git", objectsURL, h.remoteRepository.Id) + remoteConfig := &goGitConfig.RemoteConfig{ + Name: "gitopia-objects-store", + URLs: []string{remoteURL}, + } + + _, err = remote.Repo.CreateRemote(remoteConfig) + if err != nil { + return "", err + } + defer remote.Repo.DeleteRemote("gitopia-objects-store") + + force := false + if strings.HasPrefix(local, "+") { + local = strings.TrimPrefix(local, "+") + force = true + } + pushOptions := &git.PushOptions{ RemoteName: "gitopia-objects-store", RefSpecs: []goGitConfig.RefSpec{goGitConfig.RefSpec(fmt.Sprintf("%s:%s", local, remoteRef))}, Progress: os.Stdout, + Force: force, } err = remote.Repo.Push(pushOptions) @@ -224,25 +256,9 @@ func (h *GitopiaHandler) Push(remote *core.Remote, local string, remoteRef strin return "", fmt.Errorf("fatal: error pushing the git objects, %v", err.Error()) } - // Update ref on gitopia - interfaceRegistry := types.NewInterfaceRegistry() - interfaceRegistry.RegisterInterface( - "cosmos.auth.v1beta1.AccountI", - (*authType.AccountI)(nil), - &authType.BaseAccount{}, - &authType.ModuleAccount{}, - ) - interfaceRegistry.RegisterInterface("cosmos.crypto.PubKey", (*cryptotypes.PubKey)(nil)) - interfaceRegistry.RegisterImplementations((*cryptotypes.PubKey)(nil), &cosmoscryptosecp.PubKey{}) - interfaceRegistry.RegisterImplementations((*cryptotypes.PubKey)(nil), &cosmoscryptoed.PubKey{}) - marshaler := codec.NewProtoCodec(interfaceRegistry) - txCfg := authTx.NewTxConfig(marshaler, authTx.DefaultSignModes) - - txBuilder := txCfg.NewTxBuilder() - var newRemoteRefSha, prevRemoteRefSha string - var msg sdk.Msg + // Update ref on gitopia if strings.HasPrefix(local, branchPrefix) { localCommitHash, err := remote.Repo.ResolveRevision(plumbing.Revision(local)) if err != nil { @@ -282,98 +298,35 @@ func (h *GitopiaHandler) Push(remote *core.Remote, local string, remoteRef strin return "", fmt.Errorf("fatal: not a valid branch/tag, %v", local) } - txBuilder.SetMsgs(msg) - txBuilder.SetGasLimit(200000) - - res, err := h.accountQueryClient.Account(context.Background(), - &authType.QueryAccountRequest{ - Address: walletAddress.String(), - }, - ) - var acc authType.AccountI - if err := interfaceRegistry.UnpackAny(res.Account, &acc); err != nil { - return "", err - } - - sigV2 := signing.SignatureV2{ - PubKey: privKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: txCfg.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: acc.GetSequence(), - } - err = txBuilder.SetSignatures(sigV2) - if err != nil { - return "", err - } - - signerData := xauthsigning.SignerData{ - ChainID: chainID, - AccountNumber: acc.GetAccountNumber(), - Sequence: acc.GetSequence(), - } - - sigV2, err = clientTx.SignWithPrivKey(txCfg.SignModeHandler().DefaultMode(), signerData, - txBuilder, privKey, txCfg, acc.GetSequence()) - if err != nil { - return "", err - } - - err = txBuilder.SetSignatures(sigV2) + err = signAndBroadcastTx(h.grpcConn, walletAddress.String(), h.chainId, privKey, msg) if err != nil { return "", err } - err = txBuilder.GetTx().ValidateBasic() - if err != nil { - fmt.Fprintf(os.Stderr, "fatal: tx validation failed: %v", err.Error()) - } - - var txBytes []byte - txBytes, err = txCfg.TxEncoder()(txBuilder.GetTx()) - if err != nil { - return "", err - } - - var grpcRes *tx.BroadcastTxResponse - grpcRes, err = h.txClient.BroadcastTx( - context.Background(), - &tx.BroadcastTxRequest{ - Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC, - TxBytes: txBytes, - }, - ) - if err != nil { - return "", err - } - - if grpcRes.TxResponse.Code != 0 { - return "", fmt.Errorf("fatal: failed to broadcast transaction, code: %v", grpcRes.TxResponse.Code) - } + _ = prevRemoteRefSha // Queue task to upload objects to arweave - saveToArweavePostBody := SaveToArweavePostBody{ - RepositoryID: h.remoteRepository.Id, - RemoteRefName: remoteRef, - NewRemoteRefSha: newRemoteRefSha, - PrevRemoteRefSha: prevRemoteRefSha, - } - - postBody, err := json.Marshal(saveToArweavePostBody) - if err != nil { - return "", fmt.Errorf("fatal: failed to serialize post data: %v", err.Error()) - } - responseBody := bytes.NewBuffer(postBody) - resp, err := http.Post(saveToArweaveURL, "application/json", responseBody) - if err != nil { - return "", fmt.Errorf("fatal: error posting saveToArweave: %v", err.Error()) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("fatal: error saving to Arweave") - } + // saveToArweavePostBody := SaveToArweavePostBody{ + // RepositoryID: h.remoteRepository.Id, + // RemoteRefName: remoteRef, + // NewRemoteRefSha: newRemoteRefSha, + // PrevRemoteRefSha: prevRemoteRefSha, + // } + + // postBody, err := json.Marshal(saveToArweavePostBody) + // if err != nil { + // return "", fmt.Errorf("fatal: failed to serialize post data: %v", err.Error()) + // } + // responseBody := bytes.NewBuffer(postBody) + // resp, err := http.Post(saveToArweaveURL, "application/json", responseBody) + // if err != nil { + // return "", fmt.Errorf("fatal: error posting saveToArweave: %v", err.Error()) + // } + // defer resp.Body.Close() + + // if resp.StatusCode != http.StatusOK { + // return "", fmt.Errorf("fatal: error saving to Arweave") + // } return local, nil } diff --git a/cmd/git-remote-gitopia/util.go b/cmd/git-remote-gitopia/util.go new file mode 100644 index 0000000..7c0a2e9 --- /dev/null +++ b/cmd/git-remote-gitopia/util.go @@ -0,0 +1,113 @@ +package main + +import ( + "context" + "fmt" + "os" + + clientTx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + cosmoscryptoed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + cosmoscryptosecp "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtype "github.com/cosmos/cosmos-sdk/x/auth/types" + + "google.golang.org/grpc" +) + +func signAndBroadcastTx(cc *grpc.ClientConn, sender string, chainId string, privKey cryptotypes.PrivKey, msg sdk.Msg) error { + accountQueryClient := authtype.NewQueryClient(cc) + txClient := tx.NewServiceClient(cc) + + interfaceRegistry := types.NewInterfaceRegistry() + interfaceRegistry.RegisterInterface( + "cosmos.auth.v1beta1.AccountI", + (*authtype.AccountI)(nil), + &authtype.BaseAccount{}, + &authtype.ModuleAccount{}, + ) + interfaceRegistry.RegisterInterface("cosmos.crypto.PubKey", (*cryptotypes.PubKey)(nil)) + interfaceRegistry.RegisterImplementations((*cryptotypes.PubKey)(nil), &cosmoscryptosecp.PubKey{}) + interfaceRegistry.RegisterImplementations((*cryptotypes.PubKey)(nil), &cosmoscryptoed.PubKey{}) + marshaler := codec.NewProtoCodec(interfaceRegistry) + txCfg := authtx.NewTxConfig(marshaler, authtx.DefaultSignModes) + + txBuilder := txCfg.NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetGasLimit(200000) + + res, err := accountQueryClient.Account(context.Background(), + &authtype.QueryAccountRequest{ + Address: sender, + }, + ) + var acc authtype.AccountI + if err := interfaceRegistry.UnpackAny(res.Account, &acc); err != nil { + return err + } + + sigV2 := signing.SignatureV2{ + PubKey: privKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: txCfg.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: acc.GetSequence(), + } + err = txBuilder.SetSignatures(sigV2) + if err != nil { + return err + } + + signerData := xauthsigning.SignerData{ + ChainID: chainId, + AccountNumber: acc.GetAccountNumber(), + Sequence: acc.GetSequence(), + } + + sigV2, err = clientTx.SignWithPrivKey(txCfg.SignModeHandler().DefaultMode(), signerData, + txBuilder, privKey, txCfg, acc.GetSequence()) + if err != nil { + return err + } + + err = txBuilder.SetSignatures(sigV2) + if err != nil { + return err + } + + err = txBuilder.GetTx().ValidateBasic() + if err != nil { + fmt.Fprintf(os.Stderr, "fatal: tx validation failed: %v", err.Error()) + } + + var txBytes []byte + txBytes, err = txCfg.TxEncoder()(txBuilder.GetTx()) + if err != nil { + return err + } + + var grpcRes *tx.BroadcastTxResponse + grpcRes, err = txClient.BroadcastTx( + context.Background(), + &tx.BroadcastTxRequest{ + Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC, + TxBytes: txBytes, + }, + ) + if err != nil { + return err + } + + if grpcRes.TxResponse.Code != 0 { + return fmt.Errorf("fatal: failed to broadcast transaction, code: %v", grpcRes.TxResponse.Code) + } + + return nil +} diff --git a/core/remote.go b/core/remote.go index b142d8c..4136f7b 100644 --- a/core/remote.go +++ b/core/remote.go @@ -76,7 +76,7 @@ func (r *Remote) Printf(format string, a ...interface{}) (n int, err error) { return fmt.Fprintf(r.writer, format, a...) } -func (r *Remote) push(src, dst string, force bool) { +func (r *Remote) push(src, dst string) { r.todo = append(r.todo, func() (string, error) { done, err := r.Handler.Push(r, src, dst) if err != nil { @@ -126,7 +126,7 @@ loop: r.Printf("\n") case strings.HasPrefix(command, "push "): refs := strings.Split(command[5:], ":") - r.push(refs[0], refs[1], false) //TODO: parse force + r.push(refs[0], refs[1]) case strings.HasPrefix(command, "fetch "): parts := strings.Split(command, " ") r.fetch(parts[1], parts[2]) diff --git a/go.mod b/go.mod index 82aaca6..87acaae 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.16 require ( github.com/cosmos/cosmos-sdk v0.42.4 // indirect - github.com/gitopia/gitopia v0.7.1-0.20210908101447-c82c98352e19 + github.com/gitopia/gitopia v0.10.0-rc.1 github.com/go-git/go-git/v5 v5.4.2 + google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect google.golang.org/grpc v1.40.0 ) diff --git a/go.sum b/go.sum index 313117a..a0dff7d 100644 --- a/go.sum +++ b/go.sum @@ -194,6 +194,10 @@ github.com/gitopia/gitopia v0.7.1-0.20210907194705-53e5c34b2fb2 h1:XR+YfEjfD6AkK github.com/gitopia/gitopia v0.7.1-0.20210907194705-53e5c34b2fb2/go.mod h1:zVFAP4WzI1bapXPNn7POT1If5uHJxTDZ4nG2CGiiu+c= github.com/gitopia/gitopia v0.7.1-0.20210908101447-c82c98352e19 h1:rpg74I7JG9XHs6SzdtugFKxGJxmpsZK+Yf1LBgrA4rg= github.com/gitopia/gitopia v0.7.1-0.20210908101447-c82c98352e19/go.mod h1:dNVXmTrslR7J20yRTu6DwfoVhgyG8zW1Oo9C5zTuLcI= +github.com/gitopia/gitopia v0.9.0 h1:yQy1xWeZI/Da6k5xh79RQhHouoCBf2MuuqdATCOIa9M= +github.com/gitopia/gitopia v0.9.0/go.mod h1:dNVXmTrslR7J20yRTu6DwfoVhgyG8zW1Oo9C5zTuLcI= +github.com/gitopia/gitopia v0.10.0-rc.1 h1:B57VcQiYO8xGVojBqF2Ui469xPwgpROlhGUZrnwtBRI= +github.com/gitopia/gitopia v0.10.0-rc.1/go.mod h1:dNVXmTrslR7J20yRTu6DwfoVhgyG8zW1Oo9C5zTuLcI= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=