commit 735ef5acf12acadb028a1ec180c877c45e631f5d from: Stefan Sperling date: Sat Aug 03 19:35:34 2019 UTC reject staging of out-of-date files commit - ebf48fd51b015826a4c8227d238ec8533b15476c commit + 735ef5acf12acadb028a1ec180c877c45e631f5d blob - e1049fab7c3d44ba6648ace4bb8783554328583f blob + 0a2e9fc90236fca9a7c4e1c9efbd07616aa5e5c6 --- include/got_error.h +++ include/got_error.h @@ -117,6 +117,7 @@ #define GOT_ERR_FILE_STAGED 101 #define GOT_ERR_STAGE_NO_CHANGE 102 #define GOT_ERR_STAGE_CONFLICT 103 +#define GOT_ERR_STAGE_OUT_OF_DATE 104 static const struct got_error { int code; @@ -236,6 +237,8 @@ static const struct got_error { { GOT_ERR_FILE_STAGED, "file is staged" }, { GOT_ERR_STAGE_NO_CHANGE, "no changes to stage" }, { GOT_ERR_STAGE_CONFLICT, "cannot stage file in conflicted status" }, + { GOT_ERR_STAGE_OUT_OF_DATE, "work tree must be updated before " + "changes can be staged" }, }; /* blob - 15369994a065683b34c7ca2682d55ed64c56881e blob + 86ead4f26c71e8102ef14a49228b4deed16c7cd7 --- lib/worktree.c +++ lib/worktree.c @@ -3366,47 +3366,59 @@ update_fileindex_after_commit(struct got_pathlist_head } return err; } + static const struct got_error * -check_ct_out_of_date(struct got_commitable *ct, struct got_repository *repo, - struct got_object_id *head_commit_id) +check_out_of_date(const char *in_repo_path, unsigned char status, + struct got_object_id *base_blob_id, struct got_object_id *base_commit_id, + struct got_object_id *head_commit_id, struct got_repository *repo, + int ood_errcode) { const struct got_error *err = NULL; struct got_object_id *id = NULL; - struct got_commit_object *commit = NULL; - const char *ct_path = ct->in_repo_path; - while (ct_path[0] == '/') - ct_path++; - - if (ct->status != GOT_STATUS_ADD) { + if (status != GOT_STATUS_ADD) { /* Trivial case: base commit == head commit */ - if (got_object_id_cmp(ct->base_commit_id, head_commit_id) == 0) + if (got_object_id_cmp(base_commit_id, head_commit_id) == 0) return NULL; /* * Ensure file content which local changes were based * on matches file content in the branch head. */ - err = got_object_id_by_path(&id, repo, head_commit_id, ct_path); + err = got_object_id_by_path(&id, repo, head_commit_id, + in_repo_path); if (err) { if (err->code != GOT_ERR_NO_TREE_ENTRY) goto done; - err = got_error(GOT_ERR_COMMIT_OUT_OF_DATE); + err = got_error(ood_errcode); goto done; - } else if (got_object_id_cmp(id, ct->base_blob_id) != 0) - err = got_error(GOT_ERR_COMMIT_OUT_OF_DATE); + } else if (got_object_id_cmp(id, base_blob_id) != 0) + err = got_error(ood_errcode); } else { /* Require that added files don't exist in the branch head. */ - err = got_object_id_by_path(&id, repo, head_commit_id, ct_path); + err = got_object_id_by_path(&id, repo, head_commit_id, + in_repo_path); if (err && err->code != GOT_ERR_NO_TREE_ENTRY) goto done; - err = id ? got_error(GOT_ERR_COMMIT_OUT_OF_DATE) : NULL; + err = id ? got_error(ood_errcode) : NULL; } done: - if (commit) - got_object_commit_close(commit); free(id); return err; +} + +static const struct got_error * +check_ct_out_of_date(struct got_commitable *ct, struct got_repository *repo, + struct got_object_id *head_commit_id) +{ + const char *ct_path = ct->in_repo_path; + + while (ct_path[0] == '/') + ct_path++; + + return check_out_of_date(ct_path, ct->status, ct->base_commit_id, + ct->base_commit_id, head_commit_id, repo, + GOT_ERR_COMMIT_OUT_OF_DATE); } const struct got_error * @@ -4928,6 +4940,55 @@ done: } static const struct got_error * +stage_check_out_of_date(const char *relpath, const char *ondisk_path, + struct got_object_id *head_commit_id, struct got_worktree *worktree, + struct got_fileindex *fileindex, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_fileindex_entry *ie; + unsigned char status; + struct stat sb; + struct got_object_id blob_id, base_commit_id; + struct got_object_id *blob_idp = NULL, *base_commit_idp = NULL; + char *in_repo_path = NULL, *p; + + ie = got_fileindex_entry_get(fileindex, relpath, strlen(relpath)); + if (ie == NULL) + return got_error_path(relpath, GOT_ERR_FILE_STATUS); + + if (get_staged_status(ie) != GOT_STATUS_NO_CHANGE) + return NULL; + + if (asprintf(&in_repo_path, "%s%s%s", worktree->path_prefix, + got_path_is_root_dir(worktree->path_prefix) ? "" : "/", + relpath) == -1) + return got_error_from_errno("asprintf"); + + if (got_fileindex_entry_has_blob(ie)) { + memcpy(blob_id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH); + blob_idp = &blob_id; + } + if (got_fileindex_entry_has_commit(ie)) { + memcpy(base_commit_id.sha1, ie->commit_sha1, + SHA1_DIGEST_LENGTH); + base_commit_idp = &base_commit_id; + } + + err = get_file_status(&status, &sb, ie, ondisk_path, repo); + if (err) + goto done; + + p = in_repo_path; + while (p[0] == '/') + p++; + err = check_out_of_date(p, status, blob_idp, base_commit_idp, + head_commit_id, repo, GOT_ERR_STAGE_OUT_OF_DATE); +done: + free(in_repo_path); + return err; +} + +static const struct got_error * stage_path(const char *relpath, const char *ondisk_path, const char *path_content, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, @@ -5001,20 +5062,43 @@ got_worktree_stage(struct got_worktree *worktree, struct got_pathlist_entry *pe; struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; + struct got_reference *head_ref = NULL; + struct got_object_id *head_commit_id = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; + err = got_ref_open(&head_ref, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (err) + goto done; + err = got_ref_resolve(&head_commit_id, repo, head_ref); + if (err) + goto done; err = open_fileindex(&fileindex, &fileindex_path, worktree); if (err) goto done; + /* Check out-of-dateness before staging anything. */ TAILQ_FOREACH(pe, paths, entry) { char *relpath; err = got_path_skip_common_ancestor(&relpath, got_worktree_get_root_path(worktree), pe->path); if (err) + goto done; + err = stage_check_out_of_date(relpath, pe->path, + head_commit_id, worktree, fileindex, repo); + free(relpath); + if (err) + goto done; + } + + TAILQ_FOREACH(pe, paths, entry) { + char *relpath; + err = got_path_skip_common_ancestor(&relpath, + got_worktree_get_root_path(worktree), pe->path); + if (err) break; err = stage_path(relpath, pe->path, (const char *)pe->data, worktree, fileindex, repo, @@ -5028,6 +5112,9 @@ got_worktree_stage(struct got_worktree *worktree, if (sync_err && err == NULL) err = sync_err; done: + if (head_ref) + got_ref_close(head_ref); + free(head_commit_id); free(fileindex_path); if (fileindex) got_fileindex_free(fileindex); blob - 27af353405089f86d0d9b8197dec61b931ccaf7e blob + 26db4abbe62fb899704e697f98df6334277086c2 --- regress/cmdline/stage.sh +++ regress/cmdline/stage.sh @@ -88,6 +88,51 @@ function test_stage_conflict { echo -n > $testroot/stdout.expected echo "got: alpha: cannot stage file in conflicted status" \ + > $testroot/stderr.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + cmp -s $testroot/stderr.expected $testroot/stderr + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stderr.expected $testroot/stderr + fi + test_done "$testroot" "$ret" +} + +function test_stage_out_of_date { + local testroot=`test_init stage_out_of_date` + local initial_commit=`git_show_head $testroot/repo` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo "modified alpha" > $testroot/wt/alpha + (cd $testroot/wt && got commit -m "modified alpha" >/dev/null) + + (cd $testroot/wt && got update -c $initial_commit > /dev/null) + + echo "modified alpha again" > $testroot/wt/alpha + (cd $testroot/wt && got stage alpha > $testroot/stdout \ + 2> $testroot/stderr) + ret="$?" + if [ "$ret" == "0" ]; then + echo "got stage command succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo -n > $testroot/stdout.expected + echo "got: work tree must be updated before changes can be staged" \ > $testroot/stderr.expected cmp -s $testroot/stdout.expected $testroot/stdout @@ -695,6 +740,7 @@ function test_stage_diff { run_test test_stage_basic run_test test_stage_conflict +run_test test_stage_out_of_date run_test test_double_stage run_test test_stage_status run_test test_stage_add_already_staged_file