commit - a5e55564f58b7d68b6e454d83114e367ad1520e3
commit + 1a36436d4d4024fada20498cad3039950c4b2da2
blob - 2d03e99096c1ed0b16f8b8a9ee19c81516038504
blob + acc38bf248e0148b45afacb9d4c7f2ae7db08b60
--- lib/worktree.c
+++ lib/worktree.c
struct got_object_id *head_commit_id)
{
const struct got_error *err = NULL;
- struct got_object_id *id_in_head;
+ struct got_object_id *id_in_head = NULL, *id = NULL;
+ struct got_commit_object *commit = NULL;
+ char *path = NULL;
+ const char *ct_path = ct->in_repo_path;
+
+ while (ct_path[0] == '/')
+ ct_path++;
/*
- * Require that modified/deleted files are based on the branch head.
- * This requirement could be relaxed to force less update operations
- * on users but, for now, we want to play it safe and see how it goes.
+ * Ensure that no modifications were made to files *and their parents*
+ * in commits between the file's base commit and the branch head.
*
- * If this check is relaxed, it must still ensure that no modifications
- * were made to files *and their parents* in commits between the file's
- * base commit and the branch head. Otherwise, tree conflicts will not
- * be detected reliably.
+ * Checking the parents is important for detecting conflicting tree
+ * configurations (files or parent folders might have been moved,
+ * deleted, added again, etc.). Such changes need to be merged with
+ * local changes before a commit can occur.
+ *
+ * The implication is that the file's (parent) entry in the root
+ * directory must have the same ID in all relevant commits.
*/
if (ct->status != GOT_STATUS_ADD) {
- if (got_object_id_cmp(ct->base_commit_id, head_commit_id) != 0)
- return got_error(GOT_ERR_COMMIT_OUT_OF_DATE);
- return NULL;
- }
+ struct got_object_qid *pid;
+ char *slash;
+ struct got_object_id *root_entry_id = NULL;
- /* Require that added files don't exist in the branch head. */
- err = got_object_id_by_path(&id_in_head, repo, head_commit_id,
- ct->in_repo_path);
- if (err && err->code != GOT_ERR_NO_TREE_ENTRY)
- return err;
- if (id_in_head) {
- free(id_in_head);
- return got_error(GOT_ERR_COMMIT_OUT_OF_DATE);
- }
+ /* Trivial case: base commit == head commit */
+ if (got_object_id_cmp(ct->base_commit_id, head_commit_id) == 0)
+ return NULL;
+ /* Compute the path to the root directory's entry. */
+ path = strdup(ct_path);
+ if (path == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+ slash = strchr(path, '/');
+ if (slash)
+ *slash = '\0';
+
+ err = got_object_open_as_commit(&commit, repo, head_commit_id);
+ if (err)
+ goto done;
+
+ err = got_object_id_by_path(&root_entry_id, repo,
+ head_commit_id, path);
+ if (err)
+ goto done;
+
+ pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit));
+ while (pid) {
+ struct got_commit_object *pcommit;
+
+ err = got_object_id_by_path(&id, repo, pid->id, path);
+ if (err) {
+ if (err->code != GOT_ERR_NO_TREE_ENTRY)
+ goto done;
+ err = NULL;
+ break;
+ }
+
+ err = got_object_id_by_path(&id, repo, pid->id, path);
+ if (err)
+ goto done;
+
+ if (got_object_id_cmp(id, root_entry_id) != 0) {
+ err = got_error(GOT_ERR_COMMIT_OUT_OF_DATE);
+ break;
+ }
+
+ if (got_object_id_cmp(pid->id, ct->base_commit_id) == 0)
+ break; /* all relevant commits scanned */
+
+ err = got_object_open_as_commit(&pcommit, repo,
+ pid->id);
+ if (err)
+ goto done;
+
+ got_object_commit_close(commit);
+ commit = pcommit;
+ pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(
+ commit));
+ }
+ } else {
+ /* Require that added files don't exist in the branch head. */
+ err = got_object_id_by_path(&id_in_head, repo, head_commit_id,
+ ct_path);
+ if (err && err->code != GOT_ERR_NO_TREE_ENTRY)
+ goto done;
+ err = id_in_head ? got_error(GOT_ERR_COMMIT_OUT_OF_DATE) : NULL;
+ }
+done:
+ if (commit)
+ got_object_commit_close(commit);
free(id_in_head);
- return NULL;
+ free(id);
+ free(path);
+ return err;
}
const struct got_error *
blob - abae3cb76ee35856a4531d70975bd861a55dea64
blob + 085d355fc26b777baecf7ba30412ab70430e6bfe
--- regress/cmdline/commit.sh
+++ regress/cmdline/commit.sh
echo "modified alpha" > $testroot/wt/alpha
echo "modified zeta" > $testroot/wt/epsilon/zeta
- (cd $testroot/wt && got commit -m 'test commit_subdir' epsilon/zeta \
+ (cd $testroot/wt && got commit -m 'changed zeta' epsilon/zeta \
> $testroot/stdout)
local head_rev=`git_show_head $testroot/repo`
fi
test_done "$testroot" "$ret"
}
+
+function test_commit_single_file_multiple {
+ local testroot=`test_init commit_single_file_multiple`
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ for i in 1 2 3 4; do
+ echo "modified alpha" >> $testroot/wt/alpha
+
+ (cd $testroot/wt && \
+ got commit -m "changed alpha" > $testroot/stdout)
+
+ local head_rev=`git_show_head $testroot/repo`
+ echo "M alpha" > $testroot/stdout.expected
+ echo "Created commit $head_rev" >> $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
+ done
+
+ test_done "$testroot" "0"
+}
+
run_test test_commit_basic
run_test test_commit_new_subdir
run_test test_commit_subdir
run_test test_commit_out_of_date
run_test test_commit_added_subdirs
run_test test_commit_rejects_conflicted_file
+run_test test_commit_single_file_multiple
blob - 1df093a055d1733c21aa7c17bfa1d067351715d6
blob + 07a2d33972a8a0313ab84aa837a7724d06cc13bc
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
function test_update_bumps_base_commit_id {
local testroot=`test_init update_bumps_base_commit_id`
+ echo "psi" > $testroot/repo/epsilon/psi
+ (cd $testroot/repo && git add .)
+ git_commit $testroot/repo -m "adding another file"
+
got checkout $testroot/repo $testroot/wt > /dev/null
ret="$?"
if [ "$ret" != "0" ]; then
return 1
fi
- echo "modified alpha" > $testroot/wt/alpha
- (cd $testroot/wt && got commit -m "changed alpha" > $testroot/stdout)
+ echo "modified psi" > $testroot/wt/epsilon/psi
+ (cd $testroot/wt && got commit -m "changed psi" > $testroot/stdout)
local head_rev=`git_show_head $testroot/repo`
- echo "M alpha" > $testroot/stdout.expected
+ echo "M epsilon/psi" > $testroot/stdout.expected
echo "Created commit $head_rev" >> $testroot/stdout.expected
cmp -s $testroot/stdout.expected $testroot/stdout
ret="$?"
return 1
fi
- echo "modified beta" > $testroot/wt/beta
- (cd $testroot/wt && got commit -m "changed beta" > $testroot/stdout \
+ echo "modified zeta" > $testroot/wt/epsilon/zeta
+ (cd $testroot/wt && got commit -m "changed zeta" > $testroot/stdout \
2> $testroot/stderr)
echo -n "" > $testroot/stdout.expected
return 1
fi
- # XXX At present, got requires users to run 'update' after 'commit'.
(cd $testroot/wt && got update > $testroot/stdout)
echo "Already up-to-date" > $testroot/stdout.expected
return 1
fi
- (cd $testroot/wt && got commit -m "changed beta" > $testroot/stdout)
+ (cd $testroot/wt && got commit -m "changed zeta" > $testroot/stdout)
local head_rev=`git_show_head $testroot/repo`
- echo "M beta" > $testroot/stdout.expected
+ echo "M epsilon/zeta" > $testroot/stdout.expected
echo "Created commit $head_rev" >> $testroot/stdout.expected
cmp -s $testroot/stdout.expected $testroot/stdout
ret="$?"