Pull (fetch & merge) with libgit2 - objective-c

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.

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.

Related

libgit2: Resolve merge conflict automatically

I'm writing a program that is using libgit2 with a repo for management of config files across multiple instances. If there is a conflicting merge I would like to automatically resolve this (by picking the latest commits), without user interaction.
After performing a git_merge I get the git_repository_index and check if git_index_has_conflicts.
When I iterate over the conflicts I am able to get the git_index_entry for the contributing parts, but here is where my basic understanding of git falls short. My approach is to resolve what git_commit that relates to the git_index_entry and check the commit time to determine which entry to pick for merged solution. At the moment I'm a bit stuck on this so any pointers on how to go about doing or if there is a better approach to this problem is much appreciated.
I need to use the libgit2 API to solve this.
Here is some simplified sample code to show my approach:
void merge(const git_a_repository* a_repo, const git_signature* a_signature)
{
//Get the FETCH_HEAD annotated commit
git_annotated_commit* annotated = nullptr;
git_object* obj = nullptr;
git_revparse_single(&obj, a_repo, "FETCH_HEAD");
git_annotated_commit_lookup(annotated, a_repo, git_object_id(obj));
git_object_free(obj);
//Omitting git_merge_analysis for simplicity
//and assuming merge condition
//Merge the FETCH_HEAD with HEAD
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE
| GIT_CHECKOUT_ALLOW_CONFLICTS;
git_merge(a_repo,(const git_annotated_commit**)(&annotated),
1, &merge_opts, &checkout_opts);
git_index* index = nullptr;
git_a_repository_index(&index, a_repo);
//Handle conflicts
if (git_index_has_conflicts(index))
{
const git_index_entry* ancestor = nullptr;
const git_index_entry* our = nullptr;
const git_index_entry* their = nullptr;
git_index_conflict_iterator* conflicts = nullptr;
int err = 0;
std::vector entriesToKeep;
git_index_conflict_iterator_new(&conflicts, index);
while ((err = git_index_conflict_next(&ancestor, &our, &their, conflicts))
== GIT_OK)
{
git_commit* ourcommit = nullptr;
git_commit* theircommit = nullptr;
//TODO: Need to find the commit belonging to git_index_entry
// to look up the comit time and use this to select
// which git_index_entry should be staged
//entriesToKeep.push_back(selectedEntry);
}
for (auto r : entriesToKeep)
{
git_index_add(index, r);
}
git_index_conflict_iterator_free(conflicts);
git_index_conflict_cleanup(index);
}
//Commit merge
git_tree* tree = nullptr;
git_oid tree_oid, commit_oid;
git_index_write_tree(&tree_oid, index);
git_tree_lookup(&tree, a_repo, &tree_oid);
git_reference* head_ref = nullptr;
git_a_repository_head(&head_ref, a_repo);
git_commit** parents = (git_commit**)(calloc(2, sizeof(git_commit*)));
git_reference_peel((git_object**)&parents[0], head_ref, GIT_OBJECT_COMMIT);
git_commit_lookup(&parents[1], a_repo, git_annotated_commit_id(annotated));
git_commit_create(&commit_oid, a_repo, git_reference_name(head_ref),
a_signature, a_signature, NULL, "Merge", tree, 2,
(const git_commit**)parents);
free(parents);
git_tree_free(tree);
git_annotated_commit_free(annotated);
git_a_repository_state_cleanup(a_repo);
}

Switch / Checkout Branch ist not working

I'm using Version 0.19
I've a remote branch named 'dev'
after cloning i want to switch to this branch.
i found some code which performs an update to the branch. but for me it doesn't work.
I also try to run a checkout after this which also doesnt work.
When viewing the git log after the code i see the changesets of the master branch. But the local branch name is the name of the given name for the created branch (e.G. "dev")
what am i doing wrong?
private static Branch SwitchBranch(Repository repo, RepositoryProperties properties)
{
string branchname = properties.Branch;
Branch result = null;
if (!string.IsNullOrWhiteSpace(properties.Branch))
{
Branch remote = null;
foreach (var branch in repo.Branches)
{
if (string.Equals(branch.Name, "origin/" + branchname))
{
remote = branch;
break;
}
}
string localBranchName = properties.Branch;
Branch localbranch = repo.CreateBranch(localBranchName);
Branch updatedBranch = repo.Branches.Update(localbranch,
b =>
{
b.TrackedBranch = remote.CanonicalName;
});
repo.Checkout(updatedBranch);
result = updatedBranch;
}
return result;
}
The xml documentation of the CreateBranch() overload you're using states "Creates a branch with the specified name. This branch will point at the commit pointed at by Repository.Head".
From your question, it looks like you'd like this branch to also point to the same Commit than the remote tracking one.
As such, I'd suggest you to change your code as follows:
Branch localbranch = repo.CreateBranch(localBranchName, remote.Tip);
Be aware that you can only create the local branch once. So you're going to get an error the second time. At least, I did.
Branch localbranch = repo.Branches.FirstOrDefault(x => !x.IsRemote && x.FriendlyName.Equals(localBranchName));
if (localbranch == null)
{
localbranch = repo.CreateBranch(localBranchName, remote.Tip);
}
Branch updatedBranch = repo.Branches.Update(localbranch,
b =>
{
b.TrackedBranch = remote.CanonicalName;
});
repo.Checkout(updatedBranch);

Un-stage file with libgit2

Using objective-git and libgit2 it has been fairly easy to stage a file ready for commit:
GTIndex *repoIndex = [self.repository indexWithError:&error];
[repoIndex removeFile:path error:&error];
if (status != GTFileStatusIgnored && status != GTFileStatusWorkingDeleted) {
// Now add the file to the index
[repoIndex addFile:path error:&error];
}
[repoIndex write:&error];
However un-staging a file is proving to be a tad more tricky. Simply removing it from the repository's index doesn't work as git then thinks the file has been deleted which makes sense. It seems what I need to do is change the index entry in the index to the one it was before it was staged.
I have tried doing the following, using diff to get the old diff delta and constructing a git_index_entry from that and inserting it:
GTIndex *repoIndex = [self.repository indexWithError:&error];
GTBranch *localBranch = [self.repository currentBranchWithError:&error];
GTCommit *localCommit = [localBranch targetCommitAndReturnError:&error];
GTDiff *indexCommitDiff = [GTDiff diffIndexFromTree:localCommit.tree inRepository:self.repository options:nil error:&error];
// Enumerate through the diff deltas until we get to the file we want to unstage
[indexCommitDiff enumerateDeltasUsingBlock:^(GTDiffDelta *delta, BOOL *stop) {
NSString *deltaPath = delta.newFile.path;
// Check if it matches the path we want to usntage
if ([deltaPath isEqualToString:path]) {
GTDiffFile *oldFile = delta.oldFile;
NSString *oldFileSHA = oldFile.OID.SHA;
git_oid oldFileOID;
int status = git_oid_fromstr(&oldFileOID, oldFileSHA.fileSystemRepresentation);
git_index_entry entry;
entry.mode = oldFile.mode;
entry.oid = oldFileOID;
entry.path = oldFile.path.fileSystemRepresentation;
[repoIndex removeFile:path error:&error];
status = git_index_add(repoIndex.git_index, &entry);
[repoIndex write:&error];
}
}];
However this leaves the git index in a corrupt state resulting in any git command logging a fatal error:
fatal: Unknown index entry format bfff0000
fatal: Unknown index entry format bfff0000
What is the correct way to un-stage a file using libgit2?
Remember that Git is snapshot-based, so whatever is in the index at commit time will be what's committed.
There is no unstage action by itself, as it's context-dependent. If the file is new, then removing it is what you do to unstage it. Otherwise, unstaging and staging are the same operation, with the difference that the blob you use in the entry is the old version of the file.
Since you want to move files back to their state in HEAD, there shouldn't be a need to use diff, but take the tree of HEAD and look up the entries you want there (though from a quick glance, I don't see objective-git wrapping git_tree_entry_bypath())
If we write out a bad version of the index, that's definitely a bug in the library, could you open an issue so we can try to reproduce it?

Objective-Git Merge

I am using Objective-Git. I cannot get the following method to work:
- (GTIndex *)merge:(GTTree *)otherTree ancestor:(GTTree *)ancestorTree error:(NSError **)error
No error is returned, but the index returned is empty, while it exists, all attributes are nil. The merge operation does not take place, I can't write out a tree as I cannot obtain the index resulting from the attempted merge.
Has anybody managed to successfully perform a merge using objective git - How? Help!
GTBranch *branch1 = branches[0];
GTCommit *commit1 = [branch1 targetCommitAndReturnError:NULL];
GTOID *oid1 = commit1.OID;
GTTree *tree1 = commit1.tree;
GTBranch *branch2 = branches[1];
GTCommit *commit2 = [branch2 targetCommitAndReturnError:NULL];
GTTree *tree2 = commit2.tree;
GTOID *oid2 = commit2.OID;
GTRepository *repo = branch1.repository;
NSError *error;
GTCommit *ancestor = [repo mergeBaseBetweenFirstOID:oid1 secondOID:oid2 error:&error];
if (error){
NSLog(#"%#", error.description);
}
GTTree *ancTree = ancestor.tree;
NSError *someError;
NSLog(#"attempting merge into ""%#"" from ""%#"" with ancestor ""%#""", commit2.message, commit1.message,ancestor.message);
GTIndex *mergedIndex = [tree2 merge:tree1 ancestor: ancTree error:&someError]; //returns index not backed by existing repo --> index_file_path = nil, all attributes of git_index are nil
if (someError){
NSLog(#"%#", someError.description);
}
NSError *theError;
GTTree *mergedtree = [mergedIndex writeTree:&theError]; //can't write out the tree as the index given back by merge: ancestor: error: does not reference a repo
if (theError){
NSLog(#"%#",theError);
}
}
}
The index that is returned by merging the trees together is not bound to the repository. Unfortunately, the operation to write the index as a tree to a specific repository (git_index_write_tree_to) is not exposed yet through Objective-Git.
You probably want to open a ticket in their issue tracker.

SQLite iphone data lost when rebooting or closing the app

I have a question... I have an application using sqlite to save some data.Everything works perfectly, meaning I can add, delete, view data. The data are persistent when the application goes into background. Now, when I remove the application from memory or reboot the iPhone, the database is corrupted and all the data are messed up !
I have a class call dbAccess where all the database actions are define(add, delete, retreive rows). At the end I have a finalize action, that finalize all the statement used and then close the database.
+ (void)finalizeStatements{
NSLog(#"Finalizing the Delete Statements");
if(deleteStmt) {
NSLog(#"Delete Statement exist... finalization");
sqlite3_finalize(deleteStmt);
deleteStmt = nil;
}
NSLog(#"Finalizing the Add Statements");
if(addStmt) {
NSLog(#"Add Statement exist... finalization");
sqlite3_finalize(addStmt);
addStmt = nil;
}
NSLog(#"Finalizing the Store Statements");
if(storeStmt) {
NSLog(#"Store Statement exist... finalization");
sqlite3_finalize(storeStmt);
storeStmt = nil;
}
NSLog(#"Finalizing the Agent Statements");
if(agentStmt) {
NSLog(#"Agent Statement exist... finalization");
sqlite3_finalize(agentStmt);
agentStmt = nil;
}
NSLog(#"Closing the Database");
if(database) {
NSLog(#"The database exist... closing it");
sqlite3_close(database);
}
}
This method is called by the application delegate when applicationDidEnterBackground and applicationWillTerminate. An openDatabase method is call when applicationDidBecomeActive.
Any ideas why the database is corrupted ?
Thanks.
First off, check out fmdb or some other proven wrappers. You can also browse the code.
Not sure if there's enough info to know why it's corrupt. But, you should get return codes from ALL sqlite3_xxx calls and log at a minimum to understand what's going on or you may just be blasting past an issue.
Also, make sure to call sqlite_errmsg which will offer more clues if the return code is not success.
In my wrapper, I do this in close. It's expected that statements are finalized bu handled if not. I have a statement cache which on clear finalizes each statement.
:
- (void)close
{
if (_sqlite3)
{
NSLog(#"closing");
[self clearStatementCache];
int rc = sqlite3_close(_sqlite3);
NSLog(#"close rc=%d", rc);
if (rc == SQLITE_BUSY)
{
NSLog(#"SQLITE_BUSY: not all statements cleanly finalized");
sqlite3_stmt *stmt;
while ((stmt = sqlite3_next_stmt(_sqlite3, 0x00)) != 0)
{
NSLog(#"finalizing stmt");
sqlite3_finalize(stmt);
}
rc = sqlite3_close(_sqlite3);
}
if (rc != SQLITE_OK)
{
NSLog(#"close not OK. rc=%d", rc);
}
_sqlite3 = NULL;
}
}