commit - ebf48fd51b015826a4c8227d238ec8533b15476c
commit + 735ef5acf12acadb028a1ec180c877c45e631f5d
blob - e1049fab7c3d44ba6648ace4bb8783554328583f
blob + 0a2e9fc90236fca9a7c4e1c9efbd07616aa5e5c6
--- include/got_error.h
+++ include/got_error.h
#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;
{ 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
}
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 *
}
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,
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,
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
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
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