//TODO: Use a real error? assert(core); assert(core->repository); /* TODO: FIX THIS */ const char * local_branch_name = core->local_branch_name; const char * remote_branch_name = core->remote_branch_name; assert(local_branch_name); assert(remote_branch_name); git_repository * repo = core->repository; int ok; git_reference_name_to_id(&local_oid, repo, local_branch_name); git_reference_name_to_id(&remote_oid, repo, remote_branch_name); ok = git_merge_base(&base_oid, repo, &local_oid, &remote_oid); commit_oid_to_tree(&local_tree, repo, &local_oid); commit_oid_to_tree(&remote_tree, repo, &remote_oid); commit_oid_to_tree(&base_tree, repo, &base_oid); return agb_merge__create_iterator(local_tree, remote_tree, base_tree, agb_merge_iterator_options_NONE); } const git_oid * agb_merge_iterator_tree_id( const AGBMergeIterator * it, enum AGBMergeIndex i) { return git_tree_id(it->trees[i]); } const char * agb_merge_entry_name( const AGBMergeEntry * entry) { return entry->name;
static int try_to_update(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, const char *remote_url, const char *branch, enum remote_transport rt) { git_oid base; const git_oid *local_id, *remote_id; int ret = 0; if (verbose) fprintf(stderr, "git storage: try to update\n"); git_storage_update_progress(false, "try to update"); if (!git_reference_cmp(local, remote)) return 0; // Dirty modified state in the working tree? We're not going // to update either way if (git_status_foreach(repo, check_clean, NULL)) { if (is_subsurface_cloud) goto cloud_data_error; else return report_error("local cached copy is dirty, skipping update"); } local_id = git_reference_target(local); remote_id = git_reference_target(remote); if (!local_id || !remote_id) { if (is_subsurface_cloud) goto cloud_data_error; else return report_error("Unable to get local or remote SHA1"); } if (git_merge_base(&base, repo, local_id, remote_id)) { if (is_subsurface_cloud) goto cloud_data_error; else return report_error("Unable to find common commit of local and remote branches"); } /* Is the remote strictly newer? Use it */ if (git_oid_equal(&base, local_id)) { git_storage_update_progress(false, "fast forward to remote"); return reset_to_remote(repo, local, remote_id); } /* Is the local repo the more recent one? See if we can update upstream */ if (git_oid_equal(&base, remote_id)) { if (verbose) fprintf(stderr, "local is newer than remote, update remote\n"); git_storage_update_progress(false, "git_update_remote, local was newer"); return update_remote(repo, origin, local, remote, rt); } /* Merging a bare repository always needs user action */ if (git_repository_is_bare(repo)) { if (is_subsurface_cloud) goto cloud_data_error; else return report_error("Local and remote have diverged, merge of bare branch needed"); } /* Merging will definitely need the head branch too */ if (git_branch_is_head(local) != 1) { if (is_subsurface_cloud) goto cloud_data_error; else return report_error("Local and remote do not match, local branch not HEAD - cannot update"); } /* Ok, let's try to merge these */ git_storage_update_progress(false, "try to merge"); ret = try_to_git_merge(repo, &local, remote, &base, local_id, remote_id); if (ret == 0) return update_remote(repo, origin, local, remote, rt); else return ret; cloud_data_error: // since we are working with Subsurface cloud storage we want to make the user interaction // as painless as possible. So if something went wrong with the local cache, tell the user // about it an move it away return cleanup_local_cache(remote_url, branch); }
/** * Updates the master branch with the latest changes, including local * changes and changes from the remote repository. */ int sync_master(git_repository *repo, int *files_changed, int *need_push) { int e = 0; git_oid master_id = {{0}}; git_oid remote_id = {{0}}; git_oid base_id = {{0}}; int master_dirty = 0; int remote_dirty = 0; int local_dirty = 0; // Find the relevant commit objects: git_check(sync_lookup_soft(&master_id, repo, SYNC_REF_MASTER)); git_check(sync_lookup_soft(&remote_id, repo, SYNC_REF_REMOTE)); if (!git_oid_iszero(&remote_id) && !git_oid_iszero(&master_id)) { e = git_merge_base(&base_id, repo, &master_id, &remote_id); if (e < 0 && e != GIT_ENOTFOUND) { goto exit; } } // Figure out what needs syncing: master_dirty = git_oid_cmp(&master_id, &base_id); remote_dirty = git_oid_cmp(&remote_id, &base_id); git_check(sync_local_dirty(&local_dirty, repo, &master_id)); if (remote_dirty) { if (master_dirty || local_dirty) { // 3-way merge: git_oid local_tree; git_oid base_tree; git_oid remote_tree; if (local_dirty) { git_check(sync_workdir_tree(&local_tree, repo)); } else { git_check(sync_get_tree(&local_tree, repo, &master_id)); } git_check(sync_get_tree(&remote_tree, repo, &remote_id)); git_check(sync_get_tree(&base_tree, repo, &base_id)); // Do merge: git_oid merged_tree; git_check(sync_merge_trees(&merged_tree, repo, &base_tree, &remote_tree, &local_tree)); // Commit to master: char const *message = local_dirty ? "merge local changes" : "merge"; if (git_oid_iszero(&master_id)) { const git_oid *parents[] = {&remote_id}; git_check(sync_commit_master(repo, message, &merged_tree, 1, parents)); } else { const git_oid *parents[] = {&master_id, &remote_id}; git_check(sync_commit_master(repo, message, &merged_tree, 2, parents)); } } else { // Fast-forward to remote: git_check(sync_fast_forward(repo, SYNC_REF_MASTER, &remote_id)); } if (!git_repository_is_bare(repo)) { git_check(sync_checkout(repo, SYNC_REF_MASTER)); } } else if (local_dirty) { // Commit local changes: git_oid local_tree; git_check(sync_workdir_tree(&local_tree, repo)); if (git_oid_iszero(&master_id)) { const git_oid *parents[] = {NULL}; git_check(sync_commit_master(repo, "first commit", &local_tree, 0, parents)); } else { const git_oid *parents[] = {&master_id}; git_check(sync_commit_master(repo, "commit local changes", &local_tree, 1, parents)); } } // Report the outcome: *files_changed = !!remote_dirty; *need_push = local_dirty || master_dirty; exit: return e; }