Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git2go - pull && merge

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.

like image 974
Sthe Avatar asked Oct 14 '15 10:10

Sthe


2 Answers

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.

like image 141
sschuberth Avatar answered Nov 15 '22 04:11

sschuberth


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

like image 32
Sthe Avatar answered Nov 15 '22 03:11

Sthe