0

I've been using objecitive-git and libgit2 to try to implement pull functionality. As git pull is just a 'porcelain' command and is made up of a git fetch followed by a git merge origin/master then that would be how I implemented it.

The fetch is performed using the method in objective-git from the fetch branch on github.

[remote fetchWithCredentialProvider:nil error:&error progress:nil];

The code below is what is done after the fetch (which I know succeeds):

// Get the local branch
GTBranch *localBranch = [repo localBranchesWithError:nil][0];
// Get the remote branch
GTBranch *remoteBranch = [repo remoteBranchesWithError:nil][0];

// Get the local & remote commit
GTCommit *localCommit = [localBranch targetCommitAndReturnError:nil];
GTCommit *remoteCommit = [remoteBranch targetCommitAndReturnError:nil];

// Get the trees of both
GTTree *localTree = localCommit.tree;
GTTree *remoteTree = remoteCommit.tree;

// Get OIDs of both commits too
GTOID *localOID = localCommit.OID;
GTOID *remoteOID = remoteCommit.OID;

// Find a merge base to act as the ancestor between these two commits
GTCommit *ancestor = [repo mergeBaseBetweenFirstOID:localOID secondOID:remoteOID error:&error];
if (error) {
    NSLog(@"Error finding merge base: %@", error);
}
// Get the ancestors tree
GTTree *ancestorTree = ancestor.tree;

// Merge into the local tree
GTIndex *mergedIndex = [localTree merge:remoteTree ancestor:ancestorTree error:&error];
if (error) {
    NSLog(@"Error mergeing: %@", error);
}

// Write the merge to disk and store the new tree
GTTree *newTree = [mergedIndex writeTreeToRepository:repo error:&error];
if (error) {
    NSLog(@"Error writing merge index to disk: %@", error);
}

After the mergedIndex which starts out in memory has been written as a tree to disk (writeTreeToRepository uses git_index_write_tree_to) there is no change in the git repos status. I'm assuming I'm missing a last step to make the new tree HEAD or merge it with HEAD or something similar but I'm not sure exactly what.

Any help would be much obliged.

Joshua
  • 15,200
  • 21
  • 100
  • 172

1 Answers1

2

Once you have the tree you want to use for the merge commit, you need to create the merge commit, which you can create with createCommitWithTree in GTRepository the same way as you'd create any other, but with both ancestors as parents. That function also allows you to ask the library to update a particular branch if you expect the repository to be in a quiet state.

Carlos Martín Nieto
  • 5,207
  • 1
  • 15
  • 16
  • Thanks, I tried this using the localCommit and remoteCommit as parents but the thing with this is that it leaves me one commit ahead of the remote which a `git pull` doesn't do. Does `git pull` do something different when you pull and it does a fast-forward? – Joshua Mar 23 '14 at 12:20
  • For a fast-forward, yes. That has nothing to do with merging, it just sets the current branch to be at whatever commit the other branch has. – Carlos Martín Nieto Mar 23 '14 at 12:53
  • Sorry I likely should have phrased my question differently to specify the exact situation I was working with. Is it possible to do what you describe using objective-git or libgit2? – Joshua Mar 23 '14 at 13:01
  • Setting a reference to a particular id? Yeah, though the reference apis, you can see an example at https://github.com/libgit2/objective-git/blob/master/ObjectiveGitTests/GTReferenceSpec.m – Carlos Martín Nieto Mar 23 '14 at 14:05
  • Thanks. I've used `referenceByUpdatingTarget:error:` to correctly make the local branch point to the remote commit thus making git think that the local branch is up-to-date with origin/master however this doesn't actually update the files changed in the commit. – Joshua Mar 23 '14 at 14:39
  • Setting a reference and putting files on the working tree are different things. There's the checkout API for that. – Carlos Martín Nieto Mar 23 '14 at 15:07
  • Checking out the remote commit before updating the local branch reference doesn't update the file modified in the commit, thus leaving a detached HEAD with a modified file (when really this is just the original file before the remote commit). – Joshua Mar 23 '14 at 15:48
  • It seems that the file that is updated in the commit isn't updated because it is 'dirty'. – Joshua Mar 23 '14 at 16:16
  • If you have uncommitted changes then you need to decide how to resolve that situation, get rid of the changes, commit and merge, bring the question up to the user... – Carlos Martín Nieto Mar 23 '14 at 16:53
  • The thing is there aren't any uncommitted changes before the checkout, this is the `git status` after the fetch: http://cl.ly/UaFm, the when attempting checkout the notification block is called with the reason being that the file is dirty. Then the checkout fails `Error Domain=GTGitErrorDomain Code=-7 "Failed to checkout tree."` – Joshua Mar 23 '14 at 17:01