static void reset_commit_to_repair (SeafRepo *repo, SeafCommit *parent, char *new_root_id) { SeafCommit *new_commit = NULL; new_commit = seaf_commit_new (NULL, repo->id, new_root_id, parent->creator_name, parent->creator_id, "Repaired by system", 0); if (!new_commit) { seaf_warning ("Out of memory, stop to run fsck for repo %.8s.\n", repo->id); return; } new_commit->parent_id = g_strdup (parent->commit_id); seaf_repo_to_commit (repo, new_commit); new_commit->repaired = TRUE; seaf_message ("Update repo %.8s status to commit %.8s.\n", repo->id, new_commit->commit_id); seaf_branch_set_commit (repo->head, new_commit->commit_id); if (seaf_branch_manager_add_branch (seaf->branch_mgr, repo->head) < 0) { seaf_warning ("Update head of repo %.8s to commit %.8s failed, " "recover failed.\n", repo->id, new_commit->commit_id); } else { seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit); } seaf_commit_unref (new_commit); }
/* * Check whether the current head of @repo is consistent (including fs and block), * if not, find and reset its head to the last consistent commit. * Note that this procedure will not work with a corrupted commit object. */ static void check_and_reset_consistent_state (SeafRepo *repo) { FsckRes res; SeafCommit *rep_commit; SeafCommit *new_commit; seaf_message ("Checking file system integrity of repo %s(%.8s)...\n", repo->name, repo->id); memset (&res, 0, sizeof(res)); res.repo = repo; res.existing_blocks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id, check_fs_integrity, &res, TRUE); g_hash_table_destroy (res.existing_blocks); if (!res.consistent_head) { recover_corrupted_repo_head (repo->id); return; } /* If the current head is not consistent, reset it. */ if (strcmp (res.consistent_head, repo->head->commit_id) != 0) { rep_commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, res.consistent_head); if (rep_commit) { new_commit = cre_commit_from_parent (repo->id, rep_commit); if (new_commit == NULL) { seaf_warning ("Failed to update branch head.\n"); } else { seaf_message ("Resetting head of repo %.8s to commit %.8s.\n", repo->id, new_commit->commit_id); seaf_branch_set_commit (repo->head, new_commit->commit_id); if (seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head) < 0) { seaf_warning ("Failed to update branch head.\n"); } else { seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit); } seaf_commit_unref (new_commit); } seaf_commit_unref (rep_commit); } else { seaf_warning ("Failed to update branch head.\n"); } } g_free (res.consistent_head); }
static int recover_corrupted_repo_head (char *repo_id) { GList *commit_list = NULL; GList *temp_list = NULL; SeafCommit *temp_commit = NULL; SeafBranch *branch = NULL; SeafRepo *repo = NULL; SeafVirtRepo *vinfo = NULL; FsckRes res; int rc = -1; seaf_message ("Recovering corrupt head commit for repo %.8s.\n", repo_id); seaf_obj_store_foreach_obj (seaf->commit_mgr->obj_store, repo_id, 1, fsck_get_repo_commit, &commit_list); if (commit_list == NULL) return rc; commit_list = g_list_sort (commit_list, compare_commit_by_ctime); memset (&res, 0, sizeof(res)); res.existing_blocks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (temp_list = commit_list; temp_list; temp_list = temp_list->next) { temp_commit = temp_list->data; branch = seaf_branch_new ("master", repo_id, temp_commit->commit_id); if (branch == NULL) { continue; } repo = seaf_repo_new (repo_id, NULL, NULL); if (repo == NULL) { seaf_branch_unref (branch); continue; } repo->head = branch; seaf_repo_from_commit (repo, temp_commit); vinfo = seaf_repo_manager_get_virtual_repo_info (seaf->repo_mgr, repo_id); if (vinfo) { repo->is_virtual = TRUE; memcpy (repo->store_id, vinfo->origin_repo_id, 36); } else { repo->is_virtual = FALSE; memcpy (repo->store_id, repo->id, 36); } seaf_virtual_repo_info_free (vinfo); res.repo = repo; rc = seaf_fs_manager_traverse_tree (seaf->fs_mgr, repo->store_id, repo->version, temp_commit->root_id, fs_callback, &res, FALSE); if (rc < 0) { seaf_repo_unref (repo); } else { break; } } if (rc < 0) { seaf_warning ("Failed to fix head commit of repo %.8s.\n", repo_id); } else { // create new head commit, and set it's parent commit as latest avaliable commit temp_commit = cre_commit_from_parent (repo_id, temp_commit); if (temp_commit) { seaf_branch_set_commit (repo->head, temp_commit->commit_id); // in case of branch col miss, using add_branch instead of update_branch if (seaf_branch_manager_add_branch (seaf->branch_mgr, repo->head) < 0) { seaf_warning ("Failed to fix head commit of repo %.8s.\n", repo_id); rc = -1; } else { seaf_commit_manager_add_commit (seaf->commit_mgr, temp_commit); seaf_message ("Head commit of repo %.8s has been fixed to commit %.8s.\n", repo_id, temp_commit->commit_id); } seaf_commit_unref (temp_commit); } else { seaf_warning ("Failed to fix head commit of repo %.8s.\n", repo_id); rc = -1; } } g_hash_table_destroy (res.existing_blocks); seaf_repo_unref (repo); for (temp_list = commit_list; temp_list; temp_list = temp_list->next) { temp_commit = temp_list->data; seaf_commit_unref (temp_commit); } g_list_free (commit_list); return rc; }
static int fast_forward_or_merge (const char *repo_id, SeafCommit *base, SeafCommit *new_commit) { #define MAX_RETRY_COUNT 3 SeafRepo *repo = NULL; SeafCommit *current_head = NULL, *merged_commit = NULL; int retry_cnt = 0; int ret = 0; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Repo %s doesn't exist.\n", repo_id); ret = -1; goto out; } retry: current_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id); if (!current_head) { seaf_warning ("Failed to find head commit of %s.\n", repo_id); ret = -1; goto out; } /* Merge if base and head are not the same. */ if (strcmp (base->commit_id, current_head->commit_id) != 0) { MergeOptions opt; const char *roots[3]; char *desc = NULL; memset (&opt, 0, sizeof(opt)); opt.n_ways = 3; memcpy (opt.remote_repo_id, repo_id, 36); memcpy (opt.remote_head, new_commit->commit_id, 40); opt.do_merge = TRUE; roots[0] = base->root_id; /* base */ roots[1] = current_head->root_id; /* head */ roots[2] = new_commit->root_id; /* remote */ if (seaf_merge_trees (repo->store_id, repo->version, 3, roots, &opt) < 0) { seaf_warning ("Failed to merge.\n"); ret = -1; goto out; } if (!opt.conflict) desc = g_strdup("Auto merge by system"); else { desc = gen_merge_description (repo, opt.merged_tree_root, current_head->root_id, new_commit->root_id); if (!desc) desc = g_strdup("Auto merge by system"); } merged_commit = seaf_commit_new(NULL, repo->id, opt.merged_tree_root, new_commit->creator_name, EMPTY_SHA1, desc, 0); g_free (desc); merged_commit->parent_id = g_strdup (current_head->commit_id); merged_commit->second_parent_id = g_strdup (new_commit->commit_id); merged_commit->new_merge = TRUE; if (opt.conflict) merged_commit->conflict = TRUE; seaf_repo_to_commit (repo, merged_commit); if (seaf_commit_manager_add_commit (seaf->commit_mgr, merged_commit) < 0) { seaf_warning ("Failed to add commit.\n"); ret = -1; goto out; } } else { seaf_commit_ref (new_commit); merged_commit = new_commit; } seaf_branch_set_commit(repo->head, merged_commit->commit_id); if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr, repo->head, current_head->commit_id) < 0) { seaf_repo_unref (repo); repo = NULL; seaf_commit_unref (current_head); current_head = NULL; seaf_commit_unref (merged_commit); merged_commit = NULL; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Repo %s doesn't exist.\n", repo_id); ret = -1; goto out; } if (++retry_cnt <= MAX_RETRY_COUNT) { seaf_message ("Concurrent branch update, retry.\n"); /* Sleep random time between 100 and 1000 millisecs. */ USLEEP (g_random_int_range(1, 11) * 100 * 1000); goto retry; } else { seaf_warning ("Stop retrying.\n"); ret = -1; goto out; } } out: seaf_commit_unref (current_head); seaf_commit_unref (merged_commit); seaf_repo_unref (repo); return ret; }
static void * update_repo (void *vprocessor) { CcnetProcessor *processor = vprocessor; USE_PRIV; char *repo_id, *branch_name, *new_head; SeafRepo *repo = NULL; SeafBranch *branch = NULL; SeafCommit *commit = NULL; char old_commit_id[41]; repo_id = priv->repo_id; branch_name = priv->branch_name; new_head = priv->new_head; /* Since this is the last step of upload procedure, commit should exist. */ commit = seaf_commit_manager_get_commit (seaf->commit_mgr, new_head); if (!commit) { priv->rsp_code = g_strdup (SC_BAD_COMMIT); priv->rsp_msg = g_strdup (SS_BAD_COMMIT); goto out; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { /* repo is deleted on server */ priv->rsp_code = g_strdup (SC_BAD_REPO); priv->rsp_msg = g_strdup (SC_BAD_REPO); goto out; } if (seaf_quota_manager_check_quota (seaf->quota_mgr, repo_id) < 0) { priv->rsp_code = g_strdup(SC_QUOTA_FULL); priv->rsp_msg = g_strdup(SS_QUOTA_FULL); goto out; } branch = seaf_branch_manager_get_branch (seaf->branch_mgr, repo_id, branch_name); if (!branch) { priv->rsp_code = g_strdup (SC_BAD_BRANCH); priv->rsp_msg = g_strdup (SS_BAD_BRANCH); goto out; } /* If branch exists, check fast forward. */ if (strcmp (new_head, branch->commit_id) != 0 && !is_fast_forward (new_head, branch->commit_id)) { g_warning ("Upload is not fast forward. Refusing.\n"); seaf_repo_unref (repo); seaf_commit_unref (commit); seaf_branch_unref (branch); priv->rsp_code = g_strdup (SC_NOT_FF); priv->rsp_msg = g_strdup (SS_NOT_FF); return vprocessor; } /* Update branch. In case of concurrent update, we must ensure atomicity. */ memcpy (old_commit_id, branch->commit_id, 41); seaf_branch_set_commit (branch, commit->commit_id); if (seaf_branch_manager_test_and_update_branch (seaf->branch_mgr, branch, old_commit_id) < 0) { g_warning ("Upload is not fast forward, concurrent update.\n"); priv->rsp_code = g_strdup (SC_NOT_FF); priv->rsp_msg = g_strdup (SS_NOT_FF); goto out; } out: if (repo) seaf_repo_unref (repo); if (commit) seaf_commit_unref (commit); if (branch) seaf_branch_unref (branch); if (!priv->rsp_code) { priv->rsp_code = g_strdup (SC_OK); priv->rsp_msg = g_strdup (SS_OK); } return vprocessor; }
static void enable_sync_repo (const char *repo_id) { SeafRepo *repo = NULL; SeafCommit *parent_commit = NULL; SeafCommit *new_commit = NULL; gboolean exists; if (!is_uuid_valid (repo_id)) { seaf_warning ("Invalid repo id %s.\n", repo_id); return; } exists = seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id); if (!exists) { seaf_warning ("Repo %.8s doesn't exist.\n", repo_id); return; } repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) return; if (!repo->repaired) { seaf_repo_unref (repo); return; } seaf_message ("Enabling sync repo %s.\n", repo_id); parent_commit = seaf_commit_manager_get_commit_compatible (seaf->commit_mgr, repo_id, repo->head->commit_id); if (!parent_commit) { seaf_warning ("Commit %s:%s is missing\n", repo_id, repo->head->commit_id); goto out; } new_commit = seaf_commit_new (NULL, repo_id, parent_commit->root_id, parent_commit->creator_name, parent_commit->creator_id, "Enable sync repo", 0); if (!new_commit) { seaf_warning ("Out of memory when create commit.\n"); goto out; } new_commit->parent_id = g_strdup (parent_commit->commit_id); seaf_repo_to_commit (repo, new_commit); new_commit->repaired = FALSE; if (seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit) < 0) { seaf_warning ("Failed to save commit %.8s for repo %.8s.\n", new_commit->commit_id, repo_id); goto out; } seaf_branch_set_commit (repo->head, new_commit->commit_id); if (seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head) < 0) { seaf_warning ("Failed to update head commit %.8s to repo %.8s.\n", new_commit->commit_id, repo_id); } else { seaf_message ("Enable sync repo %.8s success.\n", repo_id); } out: if (parent_commit) seaf_commit_unref (parent_commit); if (new_commit) seaf_commit_unref (new_commit); if (repo) seaf_repo_unref (repo); }
static int do_real_merge (SeafRepo *repo, SeafBranch *head_branch, SeafCommit *head, SeafBranch *remote_branch, SeafCommit *remote, SeafCommit *common, gboolean recover_merge, char **error) { struct merge_options opts; char index_path[SEAF_PATH_MAX]; struct index_state istate; char *root_id = NULL; SeafCommit *merged; int ret = 0, clean; memset (&istate, 0, sizeof(istate)); snprintf (index_path, SEAF_PATH_MAX, "%s/%s", repo->manager->index_dir, repo->id); if (read_index_from (&istate, index_path) < 0) { g_warning ("Failed to load index.\n"); *error = g_strdup ("Internal error.\n"); return -1; } init_merge_options (&opts); opts.index = &istate; opts.worktree = repo->worktree; opts.ancestor = "common ancestor"; opts.branch1 = seaf->session->base.user_name; opts.branch2 = remote->creator_name; opts.remote_head = remote->commit_id; opts.recover_merge = recover_merge; if (repo->encrypted) { opts.crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv); } ret = merge_recursive (&opts, head->root_id, remote->root_id, common->root_id, &clean, &root_id); if (ret < 0) goto out; if (update_index (&istate, index_path) < 0) { *error = g_strdup ("Internal error.\n"); ret = -1; goto out; } if (clean) { merged = seaf_commit_new (NULL, repo->id, root_id, repo->email ? repo->email : seaf->session->base.user_name, seaf->session->base.id, "Auto merge by seafile system", 0); merged->parent_id = g_strdup(head->commit_id); merged->second_parent_id = g_strdup(remote->commit_id); seaf_repo_to_commit (repo, merged); if (seaf_commit_manager_add_commit (seaf->commit_mgr, merged) < 0) { seaf_commit_unref (merged); *error = g_strdup ("Internal error.\n"); ret = -1; goto out; } seaf_branch_set_commit (head_branch, merged->commit_id); seaf_branch_manager_update_branch (seaf->branch_mgr, head_branch); g_debug ("Auto merged.\n"); seaf_commit_unref (merged); } else { ret = -1; g_debug ("Auto merge failed.\n"); } out: if (root_id) g_free (root_id); g_free (opts.crypt); clear_merge_options (&opts); discard_index (&istate); return ret; }
int merge_branches (SeafRepo *repo, SeafBranch *remote_branch, char **error, gboolean *real_merge) { SeafCommit *common = NULL; SeafCommit *head, *remote; int ret = 0; SeafRepoMergeInfo minfo; g_assert (repo && remote_branch && error); *real_merge = FALSE; memset (&minfo, 0, sizeof(minfo)); if (seaf_repo_manager_get_merge_info (repo->manager, repo->id, &minfo) < 0) { g_warning ("Failed to get merge status of repo %s.\n", repo->id); return -1; } head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->head->commit_id); if (!head) { *error = g_strdup("Internal error: current branch corrupted.\n"); return -1; } remote = seaf_commit_manager_get_commit (seaf->commit_mgr, remote_branch->commit_id); if (!remote) { *error = g_strdup("Invalid remote branch.\n"); ret = -1; goto free_head; } /* Are we going to recover from the last interrupted merge? */ if (minfo.in_merge) { /* We don't need to recover 2 cases, since the last merge was actually finished. * - "master" and "local" are the same; * - index is unmerged. * * The first case is a clean merge; the second case is unclean merge. */ if (strcmp (head->commit_id, remote->commit_id) == 0 || seaf_repo_is_index_unmerged (repo)) { seaf_repo_manager_clear_merge (repo->manager, repo->id); goto free_head; } } /* We use the same logic for normal merge and recover. */ /* Set in_merge state. */ seaf_repo_manager_set_merge (repo->manager, repo->id, remote_branch->commit_id); common = get_merge_base (head, remote); if (!common) { g_warning ("Cannot find common ancestor\n"); *error = g_strdup ("Cannot find common ancestor\n"); ret = -1; goto free_remote; } /* printf ("common commit id is %s.\n", common->commit_id); */ if (strcmp(common->commit_id, remote->commit_id) == 0) { /* We are already up to date. */ g_debug ("Already up to date.\n"); } else if (strcmp(common->commit_id, head->commit_id) == 0) { /* Fast forward. */ if (seaf_repo_checkout_commit (repo, remote, minfo.in_merge, error) < 0) { ret = -1; goto out; } seaf_branch_set_commit (repo->head, remote->commit_id); seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head); /* Repo info on the client is in memory. */ g_free (repo->name); repo->name = g_strdup(remote->repo_name); g_free (repo->desc); repo->desc = g_strdup(remote->repo_desc); g_debug ("Fast forward.\n"); } else { /* Not up-to-date and ff, we need a real merge. */ *real_merge = TRUE; ret = do_real_merge (repo, repo->head, head, remote_branch, remote, common, minfo.in_merge, error); } out: /* Clear in_merge state, no matter clean or not. */ seaf_repo_manager_clear_merge (repo->manager, repo->id); seaf_commit_unref (common); free_remote: seaf_commit_unref (remote); free_head: seaf_commit_unref (head); return ret; }