int diff_merge (SeafCommit *merge, GList **results) { SeafCommit *parent1, *parent2; struct tree_desc t[3]; struct unpack_trees_options opts; struct index_state istate; g_assert (*results == NULL); g_assert (merge->parent_id != NULL && merge->second_parent_id != NULL); parent1 = seaf_commit_manager_get_commit (seaf->commit_mgr, merge->parent_id); if (!parent1) { seaf_warning ("failed to find commit %s.\n", merge->parent_id); return -1; } parent2 = seaf_commit_manager_get_commit (seaf->commit_mgr, merge->second_parent_id); if (!parent2) { seaf_warning ("failed to find commit %s.\n", merge->second_parent_id); seaf_commit_unref (parent1); return -1; } fill_tree_descriptor(&t[0], merge->root_id); fill_tree_descriptor(&t[1], parent1->root_id); fill_tree_descriptor(&t[2], parent2->root_id); seaf_commit_unref (parent1); seaf_commit_unref (parent2); /* Empty index */ memset(&istate, 0, sizeof(istate)); memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; opts.index_only = 1; opts.merge = 1; opts.fn = threeway_diff; opts.unpack_data = results; opts.src_index = &istate; opts.dst_index = NULL; if (unpack_trees(3, t, &opts) < 0) { seaf_warning ("failed to unpack trees.\n"); return -1; } if (*results != NULL) diff_resolve_renames (results); tree_desc_free (&t[0]); tree_desc_free (&t[1]); tree_desc_free (&t[2]); return 0; }
/** * Get the user who last changed a file. * @head: head commit to start the search. * @path: path of the file. */ char * get_last_changer_of_file (const char *head, const char *path) { char commit_id[41]; SeafCommit *commit = NULL; char *file_id = NULL; int changed; char *ret = NULL; GError *error = NULL; memcpy (commit_id, head, 41); while (1) { commit = seaf_commit_manager_get_commit (seaf->commit_mgr, commit_id); if (!commit) break; /* We hit the initial commit. */ if (!commit->parent_id) break; file_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr, commit->root_id, path, NULL, &error); if (error) { g_clear_error (&error); break; } /* We expect commit to have this file. */ if (!file_id) break; changed = diff_parents_with_path (commit, path, file_id, commit_id, &error); if (error) { g_clear_error (&error); break; } if (changed) { ret = g_strdup (commit->creator_name); break; } else { /* If this commit doesn't change the file, commit_id will be set * to the parent commit to traverse. */ g_free (file_id); seaf_commit_unref (commit); } } g_free (file_id); if (commit) seaf_commit_unref (commit); return ret; }
/* * 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); }
void wt_status_collect_changes_index (struct index_state *index, GList **results, SeafRepo *repo) { SeafFSManager *fs_mgr; SeafCommit *head; int pos = 0; DiffEntry *de; fs_mgr = repo->manager->seaf->fs_mgr; head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id); if (!head) { seaf_warning ("Failed to get commit %s.\n", repo->head->commit_id); return; } mark_all_ce_unused (index); /* if repo is initial, we don't need to check index changes */ if (strncmp(EMPTY_SHA1, head->root_id, 40) != 0) { SeafDir *root; /* call diff_index to get status */ root = seaf_fs_manager_get_seafdir (fs_mgr, repo->id, repo->version, head->root_id); if (!root) { seaf_warning ("Failed to get root %s.\n", head->root_id); seaf_commit_unref (head); return; } if (diff_index(repo->id, repo->version, index, root, results) < 0) g_warning("diff index failed\n"); seaf_dir_free (root); seaf_commit_unref (head); return; } seaf_commit_unref (head); while (1) { struct cache_entry *ce = next_cache_entry(index, &pos); if (!ce || ce_stage(ce)) break; ce->ce_flags |= CE_UNPACKED; de = diff_entry_new (DIFF_TYPE_INDEX, DIFF_STATUS_ADDED, ce->sha1, ce->name); *results = g_list_prepend (*results, de); } }
static GList * get_independent_commits (GList *commits) { SeafCommit **rslt; GList *list, *result; int cnt, i, j; SeafCommit *c; g_debug ("Get independent commits.\n"); cnt = g_list_length (commits); rslt = calloc(cnt, sizeof(*rslt)); for (list = commits, i = 0; list; list = list->next) rslt[i++] = list->data; g_list_free (commits); for (i = 0; i < cnt - 1; i++) { for (j = i+1; j < cnt; j++) { if (!rslt[i] || !rslt[j]) continue; result = merge_bases_many(rslt[i], 1, &rslt[j]); for (list = result; list; list = list->next) { c = list->data; /* If two commits have fast-forward relationship, * drop the older one. */ if (strcmp (rslt[i]->commit_id, c->commit_id) == 0) { seaf_commit_unref (rslt[i]); rslt[i] = NULL; } if (strcmp (rslt[j]->commit_id, c->commit_id) == 0) { seaf_commit_unref (rslt[j]); rslt[j] = NULL; } seaf_commit_unref (c); } } } /* Surviving ones in rslt[] are the independent results */ result = NULL; for (i = 0; i < cnt; i++) { if (rslt[i]) result = g_list_insert_sorted_with_data (result, rslt[i], compare_commit_by_time, NULL); } free(rslt); return result; }
static void * merge_job (void *data) { MergeAux *aux = data; CloneTask *task = aux->task; SeafRepo *repo = aux->repo; SeafBranch *local = NULL; SeafCommit *head = NULL; /* If we haven't indexed files in the worktree, index them now. */ if (task->root_id[0] == 0) { if (seaf_repo_index_worktree_files (task->repo_id, task->worktree, task->passwd, task->root_id) < 0) return aux; } local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local"); if (!local) { aux->success = FALSE; goto out; } head = seaf_commit_manager_get_commit (seaf->commit_mgr, local->commit_id); if (!head) { aux->success = FALSE; goto out; } if (check_fast_forward (head, task->root_id)) { seaf_debug ("[clone mgr] Fast forward.\n"); if (fast_forward_checkout (repo, head, task) < 0) goto out; } else { if (real_merge (repo, head, task) < 0) goto out; /* Commit the result of merge. */ GError *error = NULL; /* XXX: the commit code assumes repo->head is set. */ repo->head = local; seaf_repo_index_commit (repo, "", &error); if (error) { seaf_warning ("Failed to commit after merge.\n"); goto out; } repo->head = NULL; } /* Set repo head to mark checkout done. */ seaf_repo_set_head (repo, local); aux->success = TRUE; out: seaf_branch_unref (local); seaf_commit_unref (head); return aux; }
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); }
static int test_get_fs_size() { gint64 size; const char *commit_id; SeafCommit *commit; SeafRepo *repo; printf ("\n=== test get fs size\n"); repo = create_repo ("get_fs_size"); commit_id = first_commit (repo); commit = seaf_commit_manager_get_commit (seaf->commit_mgr, commit_id); if (!commit) { fprintf (stderr, "Failed to get commit\n"); return -1; } size = seaf_fs_manager_get_fs_size (seaf->fs_mgr, commit->root_id); printf ("size is %"G_GINT64_FORMAT"\n", size); seaf_commit_unref (commit); printf ("\n=== get fs size succeeded.\n"); return 0; }
static void fill_in_repo_info (GList *shared_repos) { SeafileSharedRepo *srepo; GList *ptr; SeafRepo *repo = NULL; SeafCommit *commit = NULL; for (ptr = shared_repos; ptr; ptr = ptr->next) { srepo = ptr->data; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, seafile_shared_repo_get_repo_id(srepo)); if (!repo) continue; commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->head->commit_id); if (!commit) { seaf_repo_unref (repo); continue; } g_object_set (srepo, "repo_name", repo->name, "repo_desc", repo->desc, "encrypted", repo->encrypted, "last_modified", commit->ctime, NULL); seaf_repo_unref (repo); seaf_commit_unref (commit); } }
static void commit_write_cb (OSAsyncResult *res, void *data) { CcnetProcessor *processor = data; TransferTask *task = ((SeafileGetcommitV3Proc *)processor)->tx_task; SeafCommit *commit; if (!res->success) { seaf_warning ("Failed to write commit %.8s.\n", res->obj_id); transfer_task_set_error (task, TASK_ERR_DOWNLOAD_COMMIT); ccnet_processor_send_update (processor, SC_SHUTDOWN, SS_SHUTDOWN, NULL, 0); ccnet_processor_done (processor, FALSE); return; } commit = seaf_commit_from_data (res->obj_id, res->data, res->len); if (!commit) { seaf_warning ("[getcommit] Bad commit object received.\n"); transfer_task_set_error (task, TASK_ERR_DOWNLOAD_COMMIT); ccnet_processor_send_update (processor, SC_BAD_OBJECT, SS_BAD_OBJECT, NULL, 0); ccnet_processor_done (processor, FALSE); return; } if (strcmp (commit->root_id, EMPTY_SHA1) != 0) object_list_insert (task->fs_roots, commit->root_id); seaf_commit_unref (commit); ccnet_processor_done (processor, TRUE); }
static int get_file_modifier_mtime_v1 (const char *repo_id, const char *store_id, int version, const char *head, const char *path, char **modifier, gint64 *mtime) { SeafCommit *commit = NULL; SeafDir *dir = NULL; SeafDirent *dent = NULL; int ret = 0; commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, version, head); if (!commit) { seaf_warning ("Failed to get commit %s.\n", head); return -1; } char *parent = g_path_get_dirname (path); if (strcmp(parent, ".") == 0) { g_free (parent); parent = g_strdup(""); } char *filename = g_path_get_basename (path); dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr, store_id, version, commit->root_id, parent, NULL); if (!dir) { seaf_warning ("dir %s doesn't exist in repo %s.\n", parent, repo_id); ret = -1; goto out; } GList *p; for (p = dir->entries; p; p = p->next) { SeafDirent *d = p->data; if (strcmp (d->name, filename) == 0) { dent = d; break; } } if (!dent) { goto out; } *modifier = g_strdup(dent->modifier); *mtime = dent->mtime; out: g_free (parent); g_free (filename); seaf_commit_unref (commit); seaf_dir_free (dir); return ret; }
/* * Merge "one" with commits in "twos". * The ancestors returned may not be ancestors for all the input commits. * They are common ancestors for one and some commits in twos array. */ static GList * merge_bases_many (SeafCommit *one, int n, SeafCommit **twos) { GHashTable *commit_hash; GList *result = NULL; SeafCommit *commit; int i; MergeTraverseData data; gboolean res; for (i = 0; i < n; i++) { if (one == twos[i]) return g_list_append (result, one); } /* First construct a hash table of all commit ids rooted at one. */ commit_hash = commit_tree_to_hash (one); if (!commit_hash) { g_warning ("Failed to load commit hash.\n"); return NULL; } data.commit_hash = commit_hash; data.result = NULL; for (i = 0; i < n; i++) { res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr, twos[i]->repo_id, twos[i]->version, twos[i]->commit_id, get_merge_bases, &data, FALSE); if (!res) goto fail; } g_hash_table_destroy (commit_hash); result = data.result; if (!result || !result->next) return result; /* There are more than one. Try to find out independent ones. */ result = get_independent_commits (result); return result; fail: result = data.result; while (result) { commit = result->data; seaf_commit_unref (commit); result = g_list_delete_link (result, result); } g_hash_table_destroy (commit_hash); return NULL; }
/* * check and recover repo, for corrupted file or folder set it empty */ static void check_and_recover_repo (SeafRepo *repo, gboolean reset, gboolean repair) { FsckData fsck_data; SeafCommit *rep_commit; seaf_message ("Checking file system integrity of repo %s(%.8s)...\n", repo->name, repo->id); rep_commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id); if (!rep_commit) { seaf_warning ("Failed to load commit %s of repo %s\n", repo->head->commit_id, repo->id); return; } memset (&fsck_data, 0, sizeof(fsck_data)); fsck_data.repair = repair; fsck_data.repo = repo; fsck_data.existing_blocks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); char *root_id = fsck_check_dir_recursive (rep_commit->root_id, "/", &fsck_data); g_hash_table_destroy (fsck_data.existing_blocks); if (root_id == NULL) { seaf_commit_unref (rep_commit); return; } if (repair) { if (strcmp (root_id, rep_commit->root_id) != 0) { // some fs objects corrupted for the head commit, // create new head commit using the new root_id reset_commit_to_repair (repo, rep_commit, root_id); } else if (reset) { // for reset commit but fs objects not corrupted, also create a repaired commit reset_commit_to_repair (repo, rep_commit, rep_commit->root_id); } } g_free (root_id); seaf_commit_unref (rep_commit); }
void seaf_repo_manager_cleanup_virtual_repos (SeafRepoManager *mgr, const char *origin_repo_id) { SeafRepo *repo = NULL; SeafCommit *head = NULL; GList *vinfo_list = NULL, *ptr; SeafVirtRepo *vinfo; SeafDir *dir; GError *error = NULL; repo = seaf_repo_manager_get_repo (mgr, origin_repo_id); if (!repo) { seaf_warning ("Failed to get repo %.10s.\n", origin_repo_id); goto out; } head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id); if (!head) { seaf_warning ("Failed to get commit %s:%.8s.\n", repo->id, repo->head->commit_id); goto out; } vinfo_list = seaf_repo_manager_get_virtual_info_by_origin (mgr, origin_repo_id); for (ptr = vinfo_list; ptr; ptr = ptr->next) { vinfo = ptr->data; dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr, repo->store_id, repo->version, head->root_id, vinfo->path, &error); if (error) { if (error->code == SEAF_ERR_PATH_NO_EXIST) { handle_missing_virtual_repo (mgr, repo, head, vinfo); } g_clear_error (&error); } else seaf_dir_free (dir); seaf_virtual_repo_info_free (vinfo); } out: seaf_repo_unref (repo); seaf_commit_unref (head); g_list_free (vinfo_list); }
void export_repo_files (const char *repo_id, const char *init_path, GHashTable *enc_repos) { SeafCommit *commit = get_available_commit (repo_id); if (!commit) { return; } if (commit->encrypted) { g_hash_table_insert (enc_repos, g_strdup (repo_id), g_strdup (commit->repo_name)); seaf_commit_unref (commit); return; } seaf_message ("Start to export files for repo %.8s(%s).\n", repo_id, commit->repo_name); char *dir_name = g_strdup_printf ("%.8s_%s_%s", repo_id, commit->repo_name, commit->creator_name); char * export_path = g_build_filename (init_path, dir_name, NULL); g_free (dir_name); if (g_mkdir (export_path, 0777) < 0) { seaf_warning ("Failed to create export dir %s: %s, export failed.\n", export_path, strerror (errno)); g_free (export_path); seaf_commit_unref (commit); return; } export_repo_files_recursive (repo_id, commit->root_id, export_path); seaf_message ("Finish exporting files for repo %.8s.\n\n", repo_id); g_free (export_path); seaf_commit_unref (commit); }
static int readdir_repo(SeafileSession *seaf, const char *user, const char *repo_id, const char *repo_path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info) { SeafRepo *repo = NULL; SeafBranch *branch; SeafCommit *commit = NULL; SeafDir *dir = NULL; GList *l; int ret = 0; repo = seaf_repo_manager_get_repo(seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to get repo %s.\n", repo_id); ret = -ENOENT; goto out; } branch = repo->head; commit = seaf_commit_manager_get_commit(seaf->commit_mgr, repo->id, repo->version, branch->commit_id); if (!commit) { seaf_warning ("Failed to get commit %.8s.\n", branch->commit_id); ret = -ENOENT; goto out; } dir = seaf_fs_manager_get_seafdir_by_path(seaf->fs_mgr, repo->store_id, repo->version, commit->root_id, repo_path, NULL); if (!dir) { seaf_warning ("Path %s doesn't exist in repo %s.\n", repo_path, repo_id); ret = -ENOENT; goto out; } for (l = dir->entries; l; l = l->next) { SeafDirent *seaf_dent = (SeafDirent *) l->data; /* FIXME: maybe we need to return stbuf */ filler(buf, seaf_dent->name, NULL, 0); } out: seaf_repo_unref (repo); seaf_commit_unref (commit); seaf_dir_free (dir); return ret; }
/* * Get the list of new blocks that would be checked out after * we merge with a branch headed by @remote. * * This function should be called before downloading any block * if the repo is set to not preserving history. In this case, * we don't want to download any block that will not be checked * out to the worktree (i.e. data from any historical commits). * * Return 0 if successfully calculate the block list, -1 otherwise. * If there is no new block to download, *@bl will be set to NULL; * otherwise it's set to the block list. */ int merge_get_new_block_list (SeafRepo *repo, SeafCommit *remote, BlockList **bl) { SeafCommit *common = NULL; SeafCommit *head; int ret = 0; head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->head->commit_id); if (!head) { g_warning ("current branch corrupted.\n"); return -1; } common = get_merge_base (head, remote); if (!common) { g_warning ("Cannot find common ancestor\n"); ret = -1; goto free_head; } if (strcmp(common->commit_id, remote->commit_id) == 0) { /* We are already up to date. No new block. */ *bl = NULL; } else if (strcmp(common->commit_id, head->commit_id) == 0) { /* Fast forward. */ ret = get_new_blocks_ff (repo, head, remote, bl); } else { /* Not up-to-date and ff, we need a real merge. */ ret = get_new_blocks_merge (repo, head, remote, common, bl); } seaf_commit_unref (common); free_head: seaf_commit_unref (head); return ret; }
VCCompareResult vc_compare_commits (const char *repo_id, int version, const char *c1, const char *c2) { SeafCommit *commit1, *commit2, *ca; VCCompareResult ret; /* Treat the same as up-to-date. */ if (strcmp (c1, c2) == 0) return VC_UP_TO_DATE; commit1 = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, version, c1); if (!commit1) return VC_INDEPENDENT; commit2 = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, version, c2); if (!commit2) { seaf_commit_unref (commit1); return VC_INDEPENDENT; } ca = get_merge_base (commit1, commit2); if (!ca) ret = VC_INDEPENDENT; else if (strcmp(ca->commit_id, commit1->commit_id) == 0) ret = VC_UP_TO_DATE; else if (strcmp(ca->commit_id, commit2->commit_id) == 0) ret = VC_FAST_FORWARD; else ret = VC_INDEPENDENT; if (ca) seaf_commit_unref (ca); seaf_commit_unref (commit1); seaf_commit_unref (commit2); return ret; }
static gboolean collect_repos (SeafDBRow *row, void *data) { GList **p_repos = data; const char *repo_id; const char *email; const char *permission; SeafRepo *repo = NULL; SeafCommit *commit = NULL; SeafileSharedRepo *srepo; repo_id = seaf_db_row_get_column_text (row, 0); repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) goto out; email = seaf_db_row_get_column_text (row, 1); permission = seaf_db_row_get_column_text (row, 2); commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->head->commit_id); if (!commit) goto out; srepo = g_object_new (SEAFILE_TYPE_SHARED_REPO, "share_type", "personal", "repo_id", repo_id, "repo_name", repo->name, "repo_desc", repo->desc, "encrypted", repo->encrypted, "user", email, "permission", permission, "last_modified", commit->ctime, NULL); *p_repos = g_list_prepend (*p_repos, srepo); out: seaf_repo_unref (repo); seaf_commit_unref (commit); return TRUE; }
static gboolean collect_repos (SeafDBRow *row, void *data) { GList **p_repos = data; const char *repo_id; const char *vrepo_id; const char *email; const char *permission; const char *commit_id; SeafileSharedRepo *srepo; SeafCommit *commit; repo_id = seaf_db_row_get_column_text (row, 0); vrepo_id = seaf_db_row_get_column_text (row, 1); email = seaf_db_row_get_column_text (row, 2); permission = seaf_db_row_get_column_text (row, 3); commit_id = seaf_db_row_get_column_text (row, 4); commit = seaf_commit_manager_get_commit (seaf->commit_mgr, commit_id); if (!commit) return TRUE; char *email_l = g_ascii_strdown (email, -1); srepo = g_object_new (SEAFILE_TYPE_SHARED_REPO, "share_type", "personal", "repo_id", repo_id, "user", email_l, "permission", permission, "repo_name", commit->repo_name, "repo_desc", commit->repo_desc, "encrypted", commit->encrypted, "last_modified", commit->ctime, "is_virtual", (vrepo_id != NULL), NULL); g_free (email_l); seaf_commit_unref (commit); *p_repos = g_list_prepend (*p_repos, srepo); return TRUE; }
static void load_repo_commit (SeafRepoManager *manager, SeafRepo *repo, SeafBranch *branch) { SeafCommit *commit; commit = seaf_commit_manager_get_commit (manager->seaf->commit_mgr, branch->commit_id); if (!commit) { g_warning ("Commit %s is missing\n", branch->commit_id); repo->is_corrupted = TRUE; return; } set_head_common (repo, branch); seaf_repo_from_commit (repo, commit); seaf_commit_unref (commit); }
GList * seaf_repo_get_commits (SeafRepo *repo) { GList *branches; GList *ptr; SeafBranch *branch; GList *commits = NULL; branches = seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo->id); if (branches == NULL) { g_warning ("Failed to get branch list of repo %s.\n", repo->id); return NULL; } for (ptr = branches; ptr != NULL; ptr = ptr->next) { branch = ptr->data; gboolean res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr, repo->id, repo->version, branch->commit_id, collect_commit, &commits, FALSE); if (!res) { for (ptr = commits; ptr != NULL; ptr = ptr->next) seaf_commit_unref ((SeafCommit *)(ptr->data)); g_list_free (commits); goto out; } } commits = g_list_reverse (commits); out: for (ptr = branches; ptr != NULL; ptr = ptr->next) { seaf_branch_unref ((SeafBranch *)ptr->data); } return commits; }
static int test_commit() { SeafRepo *repo; SeafCommit *commit; const char *commit_id; printf ("\n=== test commit\n"); repo = create_repo ("test-commit"); commit_id = first_commit (repo); printf ("*** print index\n"); if (print_index (repo) < 0) return -1; commit = seaf_commit_manager_get_commit (seaf->commit_mgr, commit_id); if (!commit) { fprintf (stderr, "Failed to get commit\n"); return -1; } if (traverse_dir (commit->root_id) < 0) return -1; /* status */ char *status = seaf_repo_status (repo); printf ("Status\n%s", status); g_assert (strcmp(status, "") == 0); g_free (status); seaf_commit_unref (commit); printf ("\n=== test commit succeeded.\n"); return 0; }
static void receive_commit (CcnetProcessor *processor, char *content, int clen) { ObjectPack *pack = (ObjectPack *)content; TransferTask *task = ((SeafileGetcommitV2Proc *)processor)->tx_task; SeafCommit *commit; if (clen < sizeof(ObjectPack)) { g_warning ("[getcommit] invalid object id.\n"); goto bad; } seaf_debug ("[getcommit] recv commit object %.8s\n", pack->id); if (save_commit (pack, clen) < 0) { goto bad; } commit = seaf_commit_manager_get_commit (seaf->commit_mgr, pack->id); if (!commit) goto bad; if (strcmp (commit->root_id, EMPTY_SHA1) != 0) object_list_insert (task->fs_roots, commit->root_id); seaf_commit_unref (commit); return; bad: g_warning ("[getcommit] Bad commit object received.\n"); transfer_task_set_error (((SeafileGetcommitV2Proc *)processor)->tx_task, TASK_ERR_DOWNLOAD_COMMIT); ccnet_processor_send_update (processor, SC_BAD_OBJECT, SS_BAD_OBJECT, NULL, 0); ccnet_processor_done (processor, FALSE); }
/* * If the missing virtual repo is renamed, update database entry; * otherwise delete the virtual repo. */ static void handle_missing_virtual_repo (SeafRepoManager *mgr, SeafRepo *repo, SeafCommit *head, SeafVirtRepo *vinfo) { SeafCommit *parent = NULL; char *old_dir_id = NULL; GList *diff_res = NULL, *ptr; DiffEntry *de; parent = seaf_commit_manager_get_commit (seaf->commit_mgr, head->repo_id, head->version, head->parent_id); if (!parent) { seaf_warning ("Failed to find commit %s:%s.\n", head->repo_id, head->parent_id); return; } int rc = diff_commits (parent, head, &diff_res, TRUE); if (rc < 0) { seaf_warning ("Failed to diff commit %s to %s.\n", parent->commit_id, head->commit_id); seaf_commit_unref (parent); return; } char *path = vinfo->path, *sub_path, *p, *par_path; gboolean is_renamed = FALSE; p = &path[strlen(path)]; par_path = g_strdup(path); sub_path = NULL; while (1) { GError *error = NULL; old_dir_id = seaf_fs_manager_get_seafdir_id_by_path (seaf->fs_mgr, repo->store_id, repo->version, parent->root_id, par_path, &error); if (!old_dir_id) { if (error && error->code == SEAF_ERR_PATH_NO_EXIST) { seaf_warning ("Failed to find %s under commit %s in repo %s.\n", par_path, parent->commit_id, repo->store_id); seaf_debug ("Delete virtual repo %.10s.\n", vinfo->repo_id); seaf_repo_manager_del_virtual_repo (mgr, vinfo->repo_id); g_clear_error (&error); } goto out; } char de_id[41]; char *new_path; for (ptr = diff_res; ptr; ptr = ptr->next) { de = ptr->data; if (de->status == DIFF_STATUS_DIR_RENAMED) { rawdata_to_hex (de->sha1, de_id, 20); if (strcmp (de_id, old_dir_id) == 0) { if (sub_path != NULL) new_path = g_strconcat ("/", de->new_name, "/", sub_path, NULL); else new_path = g_strconcat ("/", de->new_name, NULL); seaf_debug ("Updating path of virtual repo %s to %s.\n", vinfo->repo_id, new_path); set_virtual_repo_base_commit_path (vinfo->repo_id, head->commit_id, new_path); is_renamed = TRUE; break; } } } g_free (old_dir_id); if (is_renamed) break; while (--p != path && *p != '/'); if (p == path) break; g_free (par_path); g_free (sub_path); par_path = g_strndup (path, p - path); sub_path = g_strdup (p + 1); } if (!is_renamed) { seaf_debug ("Delete virtual repo %.10s.\n", vinfo->repo_id); seaf_repo_manager_del_virtual_repo (mgr, vinfo->repo_id); } out: g_free (par_path); g_free (sub_path); for (ptr = diff_res; ptr; ptr = ptr->next) diff_entry_free ((DiffEntry *)ptr->data); g_list_free (diff_res); seaf_commit_unref (parent); }
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 void* compute_repo_size (void *vjob) { RepoSizeJob *job = vjob; Scheduler *sched = job->sched; SeafRepo *repo = NULL; SeafCommit *head = NULL; char *cached_head_id = NULL; BlockList *bl; char *block_id; BlockMetadata *bmd; guint64 size = 0; repo = seaf_repo_manager_get_repo (sched->seaf->repo_mgr, job->repo_id); if (!repo) { g_warning ("[scheduler] failed to get repo %s.\n", job->repo_id); return vjob; } cached_head_id = get_cached_head_id (sched->seaf->db, job->repo_id); if (g_strcmp0 (cached_head_id, repo->head->commit_id) == 0) goto out; head = seaf_commit_manager_get_commit (sched->seaf->commit_mgr, repo->head->commit_id); if (!head) { g_warning ("[scheduler] failed to get head commit %s.\n", repo->head->commit_id); goto out; } /* Load block list first so that we don't need to count duplicate blocks. * We only calculate the size of the head commit. */ bl = block_list_new (); if (seaf_fs_manager_populate_blocklist (seaf->fs_mgr, head->root_id, bl) < 0) { block_list_free (bl); goto out; } int i; for (i = 0; i < bl->n_blocks; ++i) { block_id = g_ptr_array_index (bl->block_ids, i); bmd = seaf_block_manager_stat_block (sched->seaf->block_mgr, block_id); if (bmd) { size += bmd->size; g_free (bmd); } } block_list_free (bl); if (set_repo_size (sched->seaf->db, job->repo_id, repo->head->commit_id, size) < 0) g_warning ("[scheduler] failed to store repo size %s.\n", job->repo_id); out: seaf_repo_unref (repo); seaf_commit_unref (head); g_free (cached_head_id); return vjob; }
static void *merge_virtual_repo (void *vtask) { MergeTask *task = vtask; SeafRepoManager *mgr = seaf->repo_mgr; char *repo_id = task->repo_id; SeafVirtRepo *vinfo; SeafRepo *repo = NULL, *orig_repo = NULL; SeafCommit *head = NULL, *orig_head = NULL, *base = NULL; char *root = NULL, *orig_root = NULL, *base_root = NULL; char new_base_commit[41] = {0}; int ret = 0; /* repos */ repo = seaf_repo_manager_get_repo (mgr, repo_id); if (!repo) { seaf_warning ("Failed to get virt repo %.10s.\n", repo_id); ret = -1; goto out; } vinfo = repo->virtual_info; orig_repo = seaf_repo_manager_get_repo (mgr, vinfo->origin_repo_id); if (!orig_repo) { seaf_warning ("Failed to get orig repo %.10s.\n", vinfo->origin_repo_id); ret = -1; goto out; } /* commits */ head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version, repo->head->commit_id); if (!head) { seaf_warning ("Failed to get commit %s:%.8s.\n", repo->id, repo->head->commit_id); ret = -1; goto out; } orig_head = seaf_commit_manager_get_commit (seaf->commit_mgr, orig_repo->id, orig_repo->version, orig_repo->head->commit_id); if (!orig_head) { seaf_warning ("Failed to get commit %s:%.8s.\n", orig_repo->id, orig_repo->head->commit_id); ret = -1; goto out; } base = seaf_commit_manager_get_commit (seaf->commit_mgr, orig_repo->id, orig_repo->version, vinfo->base_commit); if (!base) { seaf_warning ("Failed to get commit %s:%.8s.\n", orig_repo->id, vinfo->base_commit); ret = -1; goto out; } /* fs roots */ root = head->root_id; base_root = seaf_fs_manager_get_seafdir_id_by_path (seaf->fs_mgr, orig_repo->store_id, orig_repo->version, base->root_id, vinfo->path, NULL); if (!base_root) { seaf_warning ("Cannot find seafdir for repo %.10s path %s.\n", vinfo->origin_repo_id, vinfo->path); ret = -1; goto out; } orig_root = seaf_fs_manager_get_seafdir_id_by_path (seaf->fs_mgr, orig_repo->store_id, orig_repo->version, orig_head->root_id, vinfo->path, NULL); if (!orig_root) { seaf_warning ("Cannot find seafdir for repo %.10s path %s.\n", vinfo->origin_repo_id, vinfo->path); ret = -1; goto out; } if (strcmp (root, orig_root) == 0) { /* Nothing to merge. */ seaf_debug ("Nothing to merge.\n"); } else if (strcmp (base_root, root) == 0) { /* Origin changed, virtual repo not changed. */ seaf_debug ("Origin changed, virtual repo not changed.\n"); ret = seaf_repo_manager_update_dir (mgr, repo_id, "/", orig_root, orig_head->creator_name, head->commit_id, NULL, NULL); if (ret < 0) { seaf_warning ("Failed to update root of virtual repo %.10s.\n", repo_id); goto out; } set_virtual_repo_base_commit_path (repo->id, orig_repo->head->commit_id, vinfo->path); } else if (strcmp (base_root, orig_root) == 0) { /* Origin not changed, virutal repo changed. */ seaf_debug ("Origin not changed, virutal repo changed.\n"); ret = seaf_repo_manager_update_dir (mgr, vinfo->origin_repo_id, vinfo->path, root, head->creator_name, orig_head->commit_id, new_base_commit, NULL); if (ret < 0) { seaf_warning ("Failed to update origin repo %.10s path %s.\n", vinfo->origin_repo_id, vinfo->path); goto out; } set_virtual_repo_base_commit_path (repo->id, new_base_commit, vinfo->path); /* Since origin repo is updated, we have to merge it with other * virtual repos if necessary. But we don't need to merge with * the current virtual repo again. */ seaf_repo_manager_cleanup_virtual_repos (mgr, vinfo->origin_repo_id); seaf_repo_manager_merge_virtual_repo (mgr, vinfo->origin_repo_id, repo_id); } else { /* Both origin and virtual repo are changed. */ seaf_debug ("Both origin and virtual repo are changed.\n"); MergeOptions opt; const char *roots[3]; memset (&opt, 0, sizeof(opt)); opt.n_ways = 3; memcpy (opt.remote_repo_id, repo_id, 36); memcpy (opt.remote_head, head->commit_id, 40); opt.do_merge = TRUE; roots[0] = base_root; /* base */ roots[1] = orig_root; /* head */ roots[2] = root; /* remote */ /* Merge virtual into origin */ if (seaf_merge_trees (orig_repo->store_id, orig_repo->version, 3, roots, &opt) < 0) { seaf_warning ("Failed to merge virtual repo %.10s.\n", repo_id); ret = -1; goto out; } seaf_debug ("Number of dirs visted in merge: %d.\n", opt.visit_dirs); /* Update virtual repo root. */ ret = seaf_repo_manager_update_dir (mgr, repo_id, "/", opt.merged_tree_root, orig_head->creator_name, head->commit_id, NULL, NULL); if (ret < 0) { seaf_warning ("Failed to update root of virtual repo %.10s.\n", repo_id); goto out; } /* Update origin repo path. */ ret = seaf_repo_manager_update_dir (mgr, vinfo->origin_repo_id, vinfo->path, opt.merged_tree_root, head->creator_name, orig_head->commit_id, new_base_commit, NULL); if (ret < 0) { seaf_warning ("Failed to update origin repo %.10s path %s.\n", vinfo->origin_repo_id, vinfo->path); goto out; } set_virtual_repo_base_commit_path (repo->id, new_base_commit, vinfo->path); seaf_repo_manager_cleanup_virtual_repos (mgr, vinfo->origin_repo_id); seaf_repo_manager_merge_virtual_repo (mgr, vinfo->origin_repo_id, repo_id); } out: seaf_repo_unref (repo); seaf_repo_unref (orig_repo); seaf_commit_unref (head); seaf_commit_unref (orig_head); seaf_commit_unref (base); g_free (base_root); g_free (orig_root); return vtask; }
static int do_create_virtual_repo (SeafRepoManager *mgr, SeafRepo *origin_repo, const char *repo_id, const char *repo_name, const char *repo_desc, const char *root_id, const char *user, const char *passwd, GError **error) { SeafRepo *repo = NULL; SeafCommit *commit = NULL; SeafBranch *master = NULL; int ret = 0; repo = seaf_repo_new (repo_id, repo_name, repo_desc); repo->no_local_history = TRUE; if (passwd != NULL && passwd[0] != '\0') { repo->encrypted = TRUE; repo->enc_version = origin_repo->enc_version; seafile_generate_magic (repo->enc_version, repo_id, passwd, repo->magic); if (repo->enc_version == 2) memcpy (repo->random_key, origin_repo->random_key, 96); } /* Virtual repos share fs and block store with origin repo and * have the same version as the origin. */ repo->version = origin_repo->version; memcpy (repo->store_id, origin_repo->id, 36); commit = seaf_commit_new (NULL, repo->id, root_id, /* root id */ user, /* creator */ EMPTY_SHA1, /* creator id */ repo_desc, /* description */ 0); /* ctime */ seaf_repo_to_commit (repo, commit); if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0) { seaf_warning ("Failed to add commit.\n"); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to add commit"); ret = -1; goto out; } master = seaf_branch_new ("master", repo->id, commit->commit_id); if (seaf_branch_manager_add_branch (seaf->branch_mgr, master) < 0) { seaf_warning ("Failed to add branch.\n"); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to add branch"); ret = -1; goto out; } if (seaf_repo_set_head (repo, master) < 0) { seaf_warning ("Failed to set repo head.\n"); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to set repo head."); ret = -1; goto out; } if (seaf_repo_manager_add_repo (mgr, repo) < 0) { seaf_warning ("Failed to add repo.\n"); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to add repo."); ret = -1; goto out; } out: if (repo) seaf_repo_unref (repo); if (commit) seaf_commit_unref (commit); if (master) seaf_branch_unref (master); return ret; }
char * seaf_repo_manager_create_virtual_repo (SeafRepoManager *mgr, const char *origin_repo_id, const char *path, const char *repo_name, const char *repo_desc, const char *owner, const char *passwd, GError **error) { SeafRepo *origin_repo = NULL; SeafCommit *origin_head = NULL; char *repo_id = NULL; char *dir_id = NULL; char *orig_owner = NULL; if (seaf_repo_manager_is_virtual_repo (mgr, origin_repo_id)) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Cannot create sub-library from a sub-library"); return NULL; } repo_id = get_existing_virtual_repo (mgr, origin_repo_id, path); if (repo_id) { return repo_id; } origin_repo = seaf_repo_manager_get_repo (mgr, origin_repo_id); if (!origin_repo) { seaf_warning ("Failed to get origin repo %.10s\n", origin_repo_id); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Origin library not exists"); return NULL; } if (origin_repo->encrypted) { if (origin_repo->enc_version < 2) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Library encryption version must be higher than 2"); seaf_repo_unref (origin_repo); return NULL; } if (!passwd || passwd[0] == 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Password is not set"); seaf_repo_unref (origin_repo); return NULL; } if (seafile_verify_repo_passwd (origin_repo_id, passwd, origin_repo->magic, origin_repo->enc_version) < 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Incorrect password"); seaf_repo_unref (origin_repo); return NULL; } } origin_head = seaf_commit_manager_get_commit (seaf->commit_mgr, origin_repo->id, origin_repo->version, origin_repo->head->commit_id); if (!origin_head) { seaf_warning ("Failed to get head commit %.8s of repo %s.\n", origin_repo->head->commit_id, origin_repo->id); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Bad origin repo head"); goto error; } dir_id = seaf_fs_manager_get_seafdir_id_by_path (seaf->fs_mgr, origin_repo->store_id, origin_repo->version, origin_head->root_id, path, NULL); if (!dir_id) { seaf_warning ("Path %s doesn't exist or is not a dir in repo %.10s.\n", path, origin_repo_id); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Bad path"); goto error; } repo_id = gen_uuid(); /* Save virtual repo info before actually create the repo. */ if (save_virtual_repo_info (mgr, repo_id, origin_repo_id, path, origin_head->commit_id) < 0) { seaf_warning ("Failed to save virtual repo info for %.10s:%s", origin_repo_id, path); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Internal error"); goto error; } orig_owner = seaf_repo_manager_get_repo_owner (mgr, origin_repo_id); if (do_create_virtual_repo (mgr, origin_repo, repo_id, repo_name, repo_desc, dir_id, orig_owner, passwd, error) < 0) goto error; if (seaf_repo_manager_set_repo_owner (mgr, repo_id, orig_owner) < 0) { seaf_warning ("Failed to set repo owner for %.10s.\n", repo_id); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to set repo owner."); goto error; } /* The size of virtual repo is non-zero at the beginning. */ update_repo_size (repo_id); seaf_repo_unref (origin_repo); seaf_commit_unref (origin_head); g_free (dir_id); g_free (orig_owner); return repo_id; error: seaf_repo_unref (origin_repo); seaf_commit_unref (origin_head); g_free (repo_id); g_free (dir_id); g_free (orig_owner); return NULL; }