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