Skip to content

Instantly share code, notes, and snippets.

@sithembiso
Last active March 15, 2023 16:17
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save sithembiso/d890d3f751029a73f2c5 to your computer and use it in GitHub Desktop.
Save sithembiso/d890d3f751029a73f2c5 to your computer and use it in GitHub Desktop.
git pull using git2go
func (repo *Repo) Pull() error {
branch, err := repo.Branch()
if err != nil {
return err
}
// Get the name
name, err := branch.Name()
if err != nil {
return err
}
// Locate remote
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 remote master
remoteBranch, err := repo.References.Lookup("refs/remotes/origin/"+name)
if err != nil {
return err
}
remoteBranchID := remoteBranch.Target()
// Get annotated commit
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
}
// Get repo head
head, err := repo.Head()
if err != nil {
return err
}
if analysis & git.MergeAnalysisUpToDate != 0 {
return nil
} 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()
} 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/"+name)
if err != nil {
return err
}
// Point branch to the object
branchRef.SetTarget(remoteBranchID, "")
if _, err := head.SetTarget(remoteBranchID, ""); err != nil {
return err
}
} else {
return fmt.Errorf("Unexpected merge analysis result %d", analysis)
}
return nil
}
@amimimor
Copy link

amimimor commented Sep 12, 2018

Hi and thanks for this snippet. It really saved me a lot of time and hassle!

I think I've found a logical problem with the conditional expressions of applying AND bitwise as demonstrated here.

Problem is that in libgit2 (merge.c), an analysis value representing a valid fast-forward is the result of GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL as evident here.

Meaning is that both conditionals (analysis & git.MergeAnalysisFastForward != 0 and analysis & git.MergeAnalysisNormal != 0) are true and the entrance to either one of them is just a matter of ordering of the conditional (here normal will be always entered although FF is possible)

Also note that repo.CheckoutTree(remoteTree, nil) only does a 'dry-run' which is the default behavior when passing nil opts.
This should be changed to something like repo.CheckoutTree(tree, &git.CheckoutOpts{Strategy: git.CheckoutSafe}) to effectively checkout the remote tree into the current repo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment