I am trying to implement something that will give me the outcome of git pull
or git fetch && git merge
. I got this working in part, but the problem I am having is that after running the following code, the current repo is left thinking that there are local changes to be committed.
As far as I can see, I believe I am probably not creating the annotated commit from the correct HEAD or I need to make another commit? (I am not sure).
My code looks like this and I am stuck:
func (repo *Repo) Pull() error {
// Get remote
remote, err := repo.Remotes.Lookup("origin")
if err != nil {
remote, err = repo.Remotes.Create("origin", repo.Path())
if err != nil {
return err
}
}
// Get the branch
branch, err := repo.Branch()
if err != nil {
return err
}
// Get the name
branchName, err := branch.Name()
if err != nil {
return err
}
if err := remote.Fetch([]string{}, &git.FetchOptions{}, ""); err != nil {
return err
}
// Merge
remoteRef, err := repo.References.Lookup("refs/remotes/origin/" + branchName)
if err != nil {
return err
}
mergeRemoteHead, err := repo.AnnotatedCommitFromRef(remoteRef)
if err != nil {
return err
}
mergeHeads := make([]*git.AnnotatedCommit, 1)
mergeHeads[0] = mergeRemoteHead
if err = repo.Merge(mergeHeads, nil, nil); err != nil {
return err
}
return nil
}
After running it, I get the changes from remote merged and the working directory updated, but it tells me I need to make a commit.
I think I have a similar issue as the OP of this question.
That's the way libgit2's git_merge function is documented:
Any changes are staged for commit and any conflicts are written to the index. Callers should inspect the repository's index after this completes, resolve any conflicts and prepare a commit.
So you have to add code to check whether the index has conflicts. If there are none, you may commit what is staged. Otherwise you probably would want to prompt the user to resolve the conflicts.
Using input from sschuberth
's answer above, I was able to put together a working version of this function and I thought I'd share the breakthrough. As pointed out, the repo.Merge()
function in libgit2 (as Git2go in this case) doesn't do nearly as much as git pull
does. Let me explain it one step at a time (I stand corrected):
As explained here, git pull
actually does a git fetch
and then a git merge
and that is what we gonna do:
Locate the remote to retrieve changes from
remote, err := repo.Remotes.Lookup("origin")
if err != nil {
return err
}
Fetch changes from remote
if err := remote.Fetch([]string{}, nil, ""); err != nil {
return err
}
Get the corresponding remote reference
remoteBranch, err := repo.References.Lookup("refs/remotes/origin/branch_name")
if err != nil {
return err
}
You now have the changes from the remote but you need to let Git tell you how to deal with them further. So, do a merge analysis.
Perform a merge analysis
annotatedCommit, err := repo.AnnotatedCommitFromRef(remoteBranch)
if err != nil {
return err
}
// Do the merge analysis
mergeHeads := make([]*git.AnnotatedCommit, 1)
mergeHeads[0] = annotatedCommit
analysis, _, err := repo.MergeAnalysis(mergeHeads)
if err != nil {
return err
}
Now, you need to check the value of analysis
to see which status value it points to and do the merge accordingly.
Test the returned value
Be sure to do the test on the binary level, so use the bitwise operators. For example:
if analysis & git.MergeAnalysisUpToDate != 0 {
return nil
}
There is nothing to do here (in my case). Everything is up to date.
else if analysis & git.MergeAnalysisNormal != 0 {
// Just merge changes
if err := repo.Merge([]*git.AnnotatedCommit{annotatedCommit}, nil, nil); err != nil {
return err
}
// Check for conflicts
index, err := repo.Index()
if err != nil {
return err
}
if index.HasConflicts() {
return errors.New("Conflicts encountered. Please resolve them.")
}
// Make the merge commit
sig, err := repo.DefaultSignature()
if err != nil {
return err
}
// Get Write Tree
treeId, err := index.WriteTree()
if err != nil {
return err
}
tree, err := repo.LookupTree(treeId)
if err != nil {
return err
}
localCommit, err := repo.LookupCommit(head.Target())
if err != nil {
return err
}
remoteCommit, err := repo.LookupCommit(remoteBranchID)
if err != nil {
return err
}
repo.CreateCommit("HEAD", sig, sig, "", tree, localCommit, remoteCommit)
// Clean up
repo.StateCleanup()
}
In short, the code block above just performs a merge and tests for conflicts after. If any conflicts are encountered, deal with them (prompt the user perhaps). This will result in uncommitted changes, so be sure to create a commit after.
else if analysis & git.MergeAnalysisFastForward != 0 {
// Fast-forward changes
// Get remote tree
remoteTree, err := repo.LookupTree(remoteBranchID)
if err != nil {
return err
}
// Checkout
if err := repo.CheckoutTree(remoteTree, nil); err != nil {
return err
}
branchRef, err := repo.References.Lookup("refs/heads/branch_name")
if err != nil {
return err
}
// Point branch to the object
branchRef.SetTarget(remoteBranchID, "")
if _, err := head.SetTarget(remoteBranchID, ""); err != nil {
return err
}
}
On the code above, there isn't anything to merge. You just need to replay the changes from remote onto your local and updated where HEAD is pointing.
The above was sufficient for me. I hope the approach helps you too. Find the complete function on this gist
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With