commit 5f8a88c62b7ec149bd4e3f5d8c91499c67b19600 from: Stefan Sperling date: Sat Aug 03 22:00:37 2019 UTC make 'got commit' work with changed stanges commit - f0b75401029052c2613a1e5dc3882976b2ab4da9 commit + 5f8a88c62b7ec149bd4e3f5d8c91499c67b19600 blob - 0f55d723f8ac61a963a08b5f6e01cce9a7b96232 blob + 2e5c680e8a66dccd6ad8ff1fcd0fab01cf3b70a5 --- lib/worktree.c +++ lib/worktree.c @@ -2852,6 +2852,7 @@ struct collect_commitables_arg { struct got_pathlist_head *commitable_paths; struct got_repository *repo; struct got_worktree *worktree; + int have_staged_files; }; static const struct got_error * @@ -2867,12 +2868,20 @@ collect_commitables(void *arg, unsigned char status, char *parent_path = NULL, *path = NULL; struct stat sb; - if (status == GOT_STATUS_CONFLICT) - return got_error(GOT_ERR_COMMIT_CONFLICT); + if (a->have_staged_files) { + if (staged_status != GOT_STATUS_MODIFY && + staged_status != GOT_STATUS_ADD && + staged_status != GOT_STATUS_DELETE) + return NULL; + } else { + if (status == GOT_STATUS_CONFLICT) + return got_error(GOT_ERR_COMMIT_CONFLICT); - if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_ADD && - status != GOT_STATUS_DELETE) - return NULL; + if (status != GOT_STATUS_MODIFY && + status != GOT_STATUS_ADD && + status != GOT_STATUS_DELETE) + return NULL; + } if (asprintf(&path, "/%s", relpath) == -1) { err = got_error_from_errno("asprintf"); @@ -2899,7 +2908,7 @@ collect_commitables(void *arg, unsigned char status, err = got_error_from_errno("asprintf"); goto done; } - if (status == GOT_STATUS_DELETE) { + if (status == GOT_STATUS_DELETE || staged_status == GOT_STATUS_DELETE) { sb.st_mode = GOT_DEFAULT_FILE_MODE; } else { if (lstat(ct->ondisk_path, &sb) != 0) { @@ -2917,8 +2926,10 @@ collect_commitables(void *arg, unsigned char status, } ct->status = status; + ct->staged_status = staged_status; ct->blob_id = NULL; /* will be filled in when blob gets created */ - if (ct->status != GOT_STATUS_ADD) { + if (ct->status != GOT_STATUS_ADD && + ct->staged_status != GOT_STATUS_ADD) { ct->base_blob_id = got_object_id_dup(blob_id); if (ct->base_blob_id == NULL) { err = got_error_from_errno("got_object_id_dup"); @@ -2930,7 +2941,6 @@ collect_commitables(void *arg, unsigned char status, goto done; } } - ct->staged_status = staged_status; if (ct->staged_status == GOT_STATUS_ADD || ct->staged_status == GOT_STATUS_MODIFY) { ct->staged_blob_id = got_object_id_dup(staged_blob_id); @@ -3026,7 +3036,10 @@ alloc_modified_blob_tree_entry(struct got_tree_entry * (*new_te)->mode = get_ct_file_mode(ct); free((*new_te)->id); - (*new_te)->id = got_object_id_dup(ct->blob_id); + if (ct->staged_status == GOT_STATUS_MODIFY) + (*new_te)->id = got_object_id_dup(ct->staged_blob_id); + else + (*new_te)->id = got_object_id_dup(ct->blob_id); if ((*new_te)->id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; @@ -3065,7 +3078,10 @@ alloc_added_blob_tree_entry(struct got_tree_entry **ne (*new_te)->mode = get_ct_file_mode(ct); - (*new_te)->id = got_object_id_dup(ct->blob_id); + if (ct->staged_status == GOT_STATUS_ADD) + (*new_te)->id = got_object_id_dup(ct->staged_blob_id); + else + (*new_te)->id = got_object_id_dup(ct->blob_id); if ((*new_te)->id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; @@ -3098,9 +3114,17 @@ report_ct_status(struct got_commitable *ct, got_worktree_status_cb status_cb, void *status_arg) { const char *ct_path = ct->path; + unsigned char status; + while (ct_path[0] == '/') ct_path++; - return (*status_cb)(status_arg, ct->status, GOT_STATUS_NO_CHANGE, + + if (ct->staged_status != GOT_STATUS_NO_CHANGE) + status = ct->staged_status; + else + status = ct->status; + + return (*status_cb)(status_arg, status, GOT_STATUS_NO_CHANGE, ct_path, ct->blob_id, NULL, NULL); } @@ -3146,9 +3170,15 @@ match_deleted_or_modified_ct(struct got_commitable **c char *ct_name = NULL; int path_matches; - if (ct->status != GOT_STATUS_MODIFY && - ct->status != GOT_STATUS_DELETE) - continue; + if (ct->staged_status == GOT_STATUS_NO_CHANGE) { + if (ct->status != GOT_STATUS_MODIFY && + ct->status != GOT_STATUS_DELETE) + continue; + } else { + if (ct->staged_status != GOT_STATUS_MODIFY && + ct->staged_status != GOT_STATUS_DELETE) + continue; + } if (got_object_id_cmp(ct->base_blob_id, te->id) != 0) continue; @@ -3235,7 +3265,8 @@ write_tree(struct got_object_id **new_tree_id, struct got_commitable *ct = pe->data; char *child_path = NULL, *slash; - if (ct->status != GOT_STATUS_ADD || + if ((ct->status != GOT_STATUS_ADD && + ct->staged_status != GOT_STATUS_ADD) || (ct->flags & GOT_COMMITABLE_ADDED)) continue; @@ -3312,7 +3343,8 @@ write_tree(struct got_object_id **new_tree_id, path_base_tree, commitable_paths); if (ct) { /* NB: Deleted entries get dropped here. */ - if (ct->status == GOT_STATUS_MODIFY) { + if (ct->status == GOT_STATUS_MODIFY || + ct->staged_status == GOT_STATUS_MODIFY) { err = alloc_modified_blob_tree_entry( &new_te, te, ct); if (err) @@ -3363,9 +3395,17 @@ update_fileindex_after_commit(struct got_pathlist_head ie = got_fileindex_entry_get(fileindex, pe->path, pe->path_len); if (ie) { - if (ct->status == GOT_STATUS_DELETE) { + if (ct->status == GOT_STATUS_DELETE || + ct->staged_status == GOT_STATUS_DELETE) { got_fileindex_entry_remove(fileindex, ie); got_fileindex_entry_free(ie); + } else if (ct->staged_status == GOT_STATUS_ADD || + ct->staged_status == GOT_STATUS_MODIFY) { + got_fileindex_entry_stage_set(ie, + GOT_FILEIDX_STAGE_NONE); + err = got_fileindex_entry_update(ie, + ct->ondisk_path, ct->staged_blob_id->sha1, + new_base_commit_id->sha1, 1); } else err = got_fileindex_entry_update(ie, ct->ondisk_path, ct->blob_id->sha1, @@ -3387,14 +3427,15 @@ update_fileindex_after_commit(struct got_pathlist_head static const struct got_error * 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, + unsigned char staged_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; - if (status != GOT_STATUS_ADD) { + if (status != GOT_STATUS_ADD && staged_status != GOT_STATUS_ADD) { /* Trivial case: base commit == head commit */ if (got_object_id_cmp(base_commit_id, head_commit_id) == 0) return NULL; @@ -3473,6 +3514,11 @@ commit_worktree(struct got_object_id **new_commit_id, struct got_commitable *ct = pe->data; char *ondisk_path; + /* Blobs for staged files already exist. */ + if (ct->staged_status == GOT_STATUS_ADD || + ct->staged_status == GOT_STATUS_MODIFY) + continue; + if (ct->status != GOT_STATUS_ADD && ct->status != GOT_STATUS_MODIFY) continue; @@ -3591,6 +3637,27 @@ check_staged_file(void *arg, struct got_fileindex_entr return NULL; } +static const struct got_error * +check_non_staged_files(struct got_fileindex *fileindex, + struct got_pathlist_head *paths) +{ + struct got_pathlist_entry *pe; + struct got_fileindex_entry *ie; + + TAILQ_FOREACH(pe, paths, entry) { + if (pe->path[0] == '\0') + continue; + ie = got_fileindex_entry_get(fileindex, pe->path, pe->path_len); + if (ie == NULL) + return got_error_path(pe->path, GOT_ERR_BAD_PATH); + if (got_fileindex_entry_stage_get(ie) == GOT_FILEIDX_STAGE_NONE) + return got_error_path(pe->path, + GOT_ERR_FILE_NOT_STAGED); + } + + return NULL; +} + const struct got_error * got_worktree_commit(struct got_object_id **new_commit_id, struct got_worktree *worktree, struct got_pathlist_head *paths, @@ -3629,9 +3696,20 @@ got_worktree_commit(struct got_object_id **new_commit_ if (err) goto done; + err = got_fileindex_for_each_entry_safe(fileindex, check_staged_file, + &have_staged_files); + if (err && err->code != GOT_ERR_CANCELLED) + goto done; + if (have_staged_files) { + err = check_non_staged_files(fileindex, paths); + if (err) + goto done; + } + cc_arg.commitable_paths = &commitable_paths; cc_arg.worktree = worktree; cc_arg.repo = repo; + cc_arg.have_staged_files = have_staged_files; TAILQ_FOREACH(pe, paths, entry) { err = worktree_status(worktree, pe->path, fileindex, repo, collect_commitables, &cc_arg, NULL, NULL); @@ -3650,11 +3728,6 @@ got_worktree_commit(struct got_object_id **new_commit_ goto done; } - err = got_fileindex_for_each_entry_safe(fileindex, check_staged_file, - &have_staged_files); - if (err && err->code != GOT_ERR_CANCELLED) - goto done; - TAILQ_FOREACH(pe, &commitable_paths, entry) { struct got_commitable *ct = pe->data; const char *ct_path = ct->in_repo_path; @@ -3662,15 +3735,10 @@ got_worktree_commit(struct got_object_id **new_commit_ while (ct_path[0] == '/') ct_path++; err = check_out_of_date(ct_path, ct->status, - ct->base_blob_id, ct->base_commit_id, head_commit_id, - repo, GOT_ERR_COMMIT_OUT_OF_DATE); + ct->staged_status, ct->base_blob_id, ct->base_commit_id, + head_commit_id, repo, GOT_ERR_COMMIT_OUT_OF_DATE); if (err) - goto done; - if (have_staged_files && - ct->staged_status == GOT_STATUS_NO_CHANGE) { - err = got_error_path(ct_path, GOT_ERR_FILE_NOT_STAGED); goto done; - } } @@ -4177,6 +4245,7 @@ rebase_commit(struct got_object_id **new_commit_id, cc_arg.commitable_paths = &commitable_paths; cc_arg.worktree = worktree; cc_arg.repo = repo; + cc_arg.have_staged_files = 0; /* * If possible get the status of individual files directly to * avoid crawling the entire work tree once per rebased commit. @@ -5017,8 +5086,9 @@ stage_check_out_of_date(const char *relpath, const cha 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); + err = check_out_of_date(p, status, GOT_STATUS_NO_CHANGE, + blob_idp, base_commit_idp, head_commit_id, repo, + GOT_ERR_STAGE_OUT_OF_DATE); done: free(in_repo_path); return err; blob - 61e10ca408100bac2afcaedb9e1365b607e0a48b blob + 775dfc61717436aa450f0024d75b41d31e3158d6 --- regress/cmdline/stage.sh +++ regress/cmdline/stage.sh @@ -928,14 +928,121 @@ function test_stage_commit_non_staged { ret="$?" if [ "$ret" != "0" ]; then diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + +function test_stage_commit { + local testroot=`test_init stage_commit` + local first_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 file" > $testroot/wt/alpha + (cd $testroot/wt && got rm beta > /dev/null) + echo "new file" > $testroot/wt/foo + (cd $testroot/wt && got add foo > /dev/null) + echo "modified file" > $testroot/wt/alpha + (cd $testroot/wt && got stage alpha beta foo > /dev/null) + + echo "modified file again" > $testroot/wt/alpha + echo "new file changed" > $testroot/wt/foo + echo "non-staged change" > $testroot/wt/gamma/delta + echo "non-staged new file" > $testroot/wt/epsilon/new + (cd $testroot/wt && got add epsilon/new > /dev/null) + (cd $testroot/wt && got rm epsilon/zeta > /dev/null) + + (cd $testroot/wt && got stage -l alpha) | cut -d' ' -f 1 \ + > $testroot/blob_id_alpha + (cd $testroot/wt && got stage -l foo) | cut -d' ' -f 1 \ + > $testroot/blob_id_foo + + (cd $testroot/wt && got commit -m "staged changes" \ + > $testroot/stdout) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got commit command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + local head_commit=`git_show_head $testroot/repo` + echo "A foo" > $testroot/stdout.expected + echo "M alpha" >> $testroot/stdout.expected + echo "D beta" >> $testroot/stdout.expected + echo "Created commit $head_commit" >> $testroot/stdout.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 + + got diff -r $testroot/repo $first_commit $head_commit \ + > $testroot/stdout + + echo "diff $first_commit $head_commit" \ + > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i -c $first_commit | \ + grep 'alpha$' | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + cat $testroot/blob_id_alpha >> $testroot/stdout.expected + echo '--- alpha' >> $testroot/stdout.expected + echo '+++ alpha' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-alpha' >> $testroot/stdout.expected + echo '+modified file' >> $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i -c $first_commit \ + | grep 'beta$' | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo 'blob + /dev/null' >> $testroot/stdout.expected + echo '--- beta' >> $testroot/stdout.expected + echo '+++ /dev/null' >> $testroot/stdout.expected + echo '@@ -1 +0,0 @@' >> $testroot/stdout.expected + echo '-beta' >> $testroot/stdout.expected + echo 'blob - /dev/null' >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + cat $testroot/blob_id_foo >> $testroot/stdout.expected + echo '--- /dev/null' >> $testroot/stdout.expected + echo '+++ foo' >> $testroot/stdout.expected + echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected + echo '+new file' >> $testroot/stdout.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 + + echo 'A epsilon/new' > $testroot/stdout.expected + echo 'D epsilon/zeta' >> $testroot/stdout.expected + echo 'M gamma/delta' >> $testroot/stdout.expected + + (cd $testroot/wt && got status > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi test_done "$testroot" "$ret" } @@ -952,3 +1059,4 @@ run_test test_stage_histedit run_test test_stage_rebase run_test test_stage_update run_test test_stage_commit_non_staged +run_test test_stage_commit