Skip to content

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

Description

@MatejLach

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?

Activity

  1. ljluestc commented on May 22, 2025

    @ljluestc
    
    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")
    }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions