src-d / go-git

Project has been moved to: https://github.com/go-git/go-git
https://github.com/go-git/go-git
Apache License 2.0
4.91k stars 541 forks source link

How can i push an empty branch? #1251

Open rnag opened 4 years ago

rnag commented 4 years ago

I want to create an empty branch but not connected to master (like orphan branch), where the first commit is unrelated to commits in master branch.

These are the git commands (in shell) that I basically want to be able to do:

git symbolic-ref HEAD refs/heads/my-orphan-branch
git rm --cached -r .
git clean -f -d
echo "testing" > filename.txt
git add filename.txt
git commit -m "example go-git commit"
git push -u origin my-orphan-branch

I tried something like the following to achieve this:

branch := "my-orphan-branch"

symRef := plumbing.NewSymbolicReference("refs/heads/" + branch, plumbing.NewRemoteHEADReferenceName("origin"))
err := r.Storer.SetReference(symRef)
CheckIfError(err)

// Switching to new branch
err = w.Checkout(&git.CheckoutOptions{
    Hash: symRef.Hash(),
    Branch: symRef.Name(),
    Create:  false,
    Keep:   true,
})
CheckIfError(err)

// Create sample file
filename := filepath.Join(directory, "filename.txt")
err := ioutil.WriteFile(filename, []byte("testing"), 0644)
CheckIfError(err)

// Add some files, etc.
w, err := r.Worktree()
CheckIfError(err)
_, err = w.Add("filename.txt")
CheckIfError(err)

// commit the new file
commit, err := w.Commit("example go-git commit", &git.CommitOptions{
    Author: &object.Signature{
        Name:  "John Doe",
        Email: "john@doe.org",
        When:  time.Now(),
    },
})
CheckIfError(err)

// push to remote
err = r.Push(&git.PushOptions{
    RemoteName: "origin",
    Auth: auth,
})
CheckIfError(err)

But when I see the new branch on github, it includes the history of commits that are in master branch.. but I want the first commit to be the one that i have just pushed.

Any help on how to do this with go-git?

rnag commented 4 years ago

Just a heads up, but after a while I realized you can easily check this behavior without needing to push to remote by just going to the branch and doing a git status -s. In an actual orphan branch, all files already present in the commit history of master branch should show up with an Added (A) status. However, this is not the case after I run the code I had above. I am also able to verify with this:

// We can verify the current status of the worktree using the method Status.
Info("git status --porcelain")
status, err = w.Status()
CheckIfError(err)
fmt.Println(status)

within the new branch, most of the files in the commit history of master seem to show up with a Modified (M) status. This implies that the new branch is not actually an orphaned branch - it seems connected to the history of master, which I can also verify with git log (it appears to share the commit history with master). I'm not sure if that's a bug with plumbing.NewSymbolicReference, but I am not able to figure out how to get the desired behavior of git symbolic-ref HEAD <new branch> using go-git.

As a side note, it would probably be much easier if there was an orphan flag when creating a new branch, but I'm not familiar enough with go-git that I would know where to begin when creating a PR for it. I am also open to any existing workarounds that can allow me to achieve the same desired behavior of creating a branch with unrelated history to master. Right now I'm resorting to a exec.Command("git", "checkout", "--orphan", branch), which I guess is not an ideal solution i should be using (but on the plus side it seems to work for now)

iyalang commented 4 years ago

Hi @RNag I had the same problem and I managed to solve it by slightly modifying your code:

branch := "test-orphan-branch"
repo, err := git.PlainOpen(gitPath + repoName)
w, err := repo.Worktree()

// here I swapped HEAD and the branch name
symRef := plumbing.NewSymbolicReference(plumbing.ReferenceName("HEAD"), plumbing.ReferenceName("refs/heads/"+branch))
err = repo.Storer.SetReference(symRef)

// printing the references to make sure it's there
// it should look like this: "ref: refs/heads/test-orphan-branch HEAD"
refs, _ := repo.Storer.IterReferences()
refs.ForEach(func(ref *plumbing.Reference) error {
    fmt.Println(ref)
    return nil
})

// I removed checking out

filename := filepath.Join("./git/"+repoName+"/", "filename.txt")
err = ioutil.WriteFile(filename, []byte("testing"), 0644)
_, err = w.Add("filename.txt")

_, err = w.Commit("test go-git commit", &git.CommitOptions{
    Author: &object.Signature{
        Name:  "Name",
        Email: "email@email.com",
    },
})

_, err = repo.Head()
err = repo.Push(&git.PushOptions{
    RemoteName: "origin",
    Auth: &githttp.BasicAuth{
        Username: user,
        Password: token,
    },
    RefSpecs: []config.RefSpec{config.RefSpec("refs/heads/" + branch + ":" + "refs/heads/" + branch)},
})

So I changed only two things:

  1. In plumbing.NewSymbolicReference I used "HEAD" as the first parameter and the new branch name as the second.
  2. I removed checking out since HEAD is already set to the new branch.

I checked the repository after running it and there was a branch without any parent and containing the file "filename.txt" only. Please let me know if it doesn't work. :)