git2go icon indicating copy to clipboard operation
git2go copied to clipboard

How to pass the equivalent of git push -o <string>?

Open MatejLach opened this issue 2 years ago • 1 comments

On the Git CLI, I can use git push -o <option> for example git push -o merge_request.create to supply string options with push that are then processed on the server by i.e. the post-receive hook to perform additional actions, like create a pull request automatically after the push etc. See GitLab's options as an example.

My failed attempt to recreate this with git2go :

package main

import (
	git "github.com/libgit2/git2go/v34"
	log "github.com/sirupsen/logrus"
)

func main() {
	repo, err := git.OpenRepository("/home/ml/Projects/demo")
	if err != nil {
		log.Fatal(err)
	}
	remote, err := repo.Remotes.Lookup("origin")
	if err != nil {
		remote, err = repo.Remotes.Create("origin", repo.Path())
		if err != nil {
			log.Fatal(err)
		}
	}

	// ref
	ref, err := repo.Head()
	if err != nil {
		log.Fatal(err)
	}

	// Get the name
	branch := ref.Branch()
	branchName, err := branch.Name()
	if err != nil {
		log.Fatal(err)
	}

	if err := remote.Push([]string{"refs/heads/" + branchName}, &git.PushOptions{
		RemoteCallbacks: git.RemoteCallbacks{
			CredentialsCallback: func(url string, username_from_url string, allowed_types git.CredentialType) (*git.Credential, error) {
				return git.NewCredentialUserpassPlaintext("username", "pass")
			},
		},
		Headers: []string{"merge_request.create:true"},
	}); err != nil {
		log.Fatal(err)
	}
}

Is there a way to do this?

MatejLach avatar Jun 17 '23 16:06 MatejLach


package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	git "github.com/libgit2/git2go/v34"
	log "github.com/sirupsen/logrus"
)

// createCredentialHelper creates a temporary credential helper script for git push.
func createCredentialHelper(username, password string) (string, error) {
	// Create a temporary file for the credential helper script
	tmpDir := os.TempDir()
	scriptPath := filepath.Join(tmpDir, "git-credential-helper.sh")

	// Write a simple shell script to provide username and password
	scriptContent := fmt.Sprintf(`#!/bin/sh
echo username=%s
echo password=%s
`, username, password)

	if err := os.WriteFile(scriptPath, []byte(scriptContent), 0700); err != nil {
		return "", fmt.Errorf("failed to write credential helper: %w", err)
	}

	return scriptPath, nil
}

// pushWithOptions performs a git push with the specified push options using the Git CLI.
func pushWithOptions(repoPath, branchName, remoteName string, pushOptions []string, username, password string) error {
	// Create credential helper
	credHelperPath, err := createCredentialHelper(username, password)
	if err != nil {
		return err
	}
	defer os.Remove(credHelperPath)

	// Build the git push command
	args := []string{
		"push",
		remoteName,
		"refs/heads/" + branchName,
	}
	for _, opt := range pushOptions {
		args = append(args, "-o", opt)
	}

	cmd := exec.Command("git", args...)
	cmd.Dir = repoPath
	cmd.Env = append(os.Environ(),
		fmt.Sprintf("GIT_ASKPASS=%s", credHelperPath),
		"GIT_TERMINAL_PROMPT=0", // Disable interactive prompts
	)

	// Capture output for logging
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("git push failed: %s\nOutput: %s", err, string(output))
	}

	log.Infof("git push succeeded: %s", string(output))
	return nil
}

func main() {
	// Initialize logging
	log.SetFormatter(&log.TextFormatter{})
	log.SetLevel(log.InfoLevel)

	// Open the repository
	repoPath := "/home/ml/Projects/demo"
	repo, err := git.OpenRepository(repoPath)
	if err != nil {
		log.Fatalf("Failed to open repository: %v", err)
	}
	defer repo.Free()

	// Lookup or create the remote
	remoteName := "origin"
	remote, err := repo.Remotes.Lookup(remoteName)
	if err != nil {
		remote, err = repo.Remotes.Create(remoteName, repo.Path())
		if err != nil {
			log.Fatalf("Failed to create remote: %v", err)
		}
	}
	defer remote.Free()

	// Get the current branch
	ref, err := repo.Head()
	if err != nil {
		log.Fatalf("Failed to get HEAD: %v", err)
	}
	defer ref.Free()

	branch := ref.Branch()
	branchName, err := branch.Name()
	if err != nil {
		log.Fatalf("Failed to get branch name: %v", err)
	}

	// Perform git push with push options
	pushOptions := []string{
		"merge_request.create",
		// Add more options as needed, e.g., "merge_request.target=main"
	}
	username := "username" // Replace with actual username
	password := "pass"     // Replace with actual password or token
	if err := pushWithOptions(repoPath, branchName, remoteName, pushOptions, username, password); err != nil {
		log.Fatalf("Push failed: %v", err)
	}

	log.Info("Push completed successfully")
}

ljluestc avatar May 22 '25 15:05 ljluestc