diff --git a/CHANGELOG.md b/CHANGELOG.md index 5236a6d6f..34553aa21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ v0.22 + 1 * The local transport now auto-scales the number of threads to use when creating the packfile instead of sticking to one. +* Reference renaming now uses the right id for the old value. + +* The annotated version of branch creation, HEAD detaching and reset + allow for specifying the expression from the user to be put into the + reflog. + ### API additions * Parsing and retrieving a configuration value as a path is exposed @@ -33,7 +39,14 @@ v0.22 + 1 * `git_config_get_string_buf()` provides a way to safely retrieve a string from a non-snapshot configuration. -* Reference renaming now uses the right id for the old value. +* `git_annotated_commit_from_revspec()` allows to get an annotated + commit from an extended sha synatx string. + +* `git_repository_set_head_detached_from_annotated()`, + `git_branch_create_from_annotated()` and + `git_reset_from_annotated()` allow for the caller to provide an + annotated commit through which they can control what expression is + put into the reflog as the source/target. * `git_index_add_frombuffer()` can now create a blob from memory buffer and add it to the index which is attached to a repository. diff --git a/include/git2/annotated_commit.h b/include/git2/annotated_commit.h index e842d2032..7fb896a5f 100644 --- a/include/git2/annotated_commit.h +++ b/include/git2/annotated_commit.h @@ -77,6 +77,23 @@ GIT_EXTERN(int) git_annotated_commit_lookup( git_repository *repo, const git_oid *id); +/** + * Creates a `git_annotated_comit` from a revision string. + * + * See `man gitrevisions`, or + * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for + * information on the syntax accepted. + * + * @param out pointer to store the git_annotated_commit result in + * @param repo repository that contains the given commit + * @param revspec the extended sha syntax string to use to lookup the commit + * @return 0 on success or error code + */ +GIT_EXTERN(int) git_annotated_commit_from_revspec( + git_annotated_commit **out, + git_repository *repo, + const char *revspec); + /** * Gets the commit ID that the given `git_annotated_commit` refers to. * diff --git a/include/git2/branch.h b/include/git2/branch.h index 06f4d2c68..34354f4e5 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -54,6 +54,24 @@ GIT_EXTERN(int) git_branch_create( const git_commit *target, int force); +/** + * Create a new branch pointing at a target commit + * + * This behaves like `git_branch_create()` but takes an annotated + * commit, which lets you specify which extended sha syntax string was + * specified by a user, allowing for more exact reflog messages. + * + * See the documentation for `git_branch_create()`. + * + * @see git_branch_create + */ +GIT_EXTERN(int) git_branch_create_from_annotated( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_annotated_commit *commit, + int force); + /** * Delete an existing branch reference. * diff --git a/include/git2/repository.h b/include/git2/repository.h index e3ff3b375..ce56fef0f 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -629,6 +629,22 @@ GIT_EXTERN(int) git_repository_set_head_detached( git_repository* repo, const git_oid* commitish); +/** + * Make the repository HEAD directly point to the Commit. + * + * This behaves like `git_repository_set_head_detached()` but takes an + * annotated commit, which lets you specify which extended sha syntax + * string was specified by a user, allowing for more exact reflog + * messages. + * + * See the documentation for `git_repository_set_head_detached()`. + * + * @see git_repository_set_head_detached + */ +GIT_EXTERN(int) git_repository_set_head_detached_from_annotated( + git_repository *repo, + const git_annotated_commit *commitish); + /** * Detach the HEAD. * diff --git a/include/git2/reset.h b/include/git2/reset.h index 93ac0b29c..c03dbed8c 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -64,6 +64,24 @@ GIT_EXTERN(int) git_reset( git_reset_t reset_type, git_checkout_options *checkout_opts); +/** + * Sets the current head to the specified commit oid and optionally + * resets the index and working tree to match. + * + * This behaves like `git_reset()` but takes an annotated commit, + * which lets you specify which extended sha syntax string was + * specified by a user, allowing for more exact reflog messages. + * + * See the documentation for `git_reset()`. + * + * @see git_reset + */ +GIT_EXTERN(int) git_reset_from_annotated( + git_repository *repo, + git_annotated_commit *commit, + git_reset_t reset_type, + git_checkout_options *checkout_opts); + /** * Updates some entries in the index from the target commit tree. * diff --git a/src/annotated_commit.c b/src/annotated_commit.c index 0a917802a..3f2d2ed17 100644 --- a/src/annotated_commit.c +++ b/src/annotated_commit.c @@ -12,6 +12,7 @@ #include "git2/refs.h" #include "git2/repository.h" #include "git2/annotated_commit.h" +#include "git2/revparse.h" static int annotated_commit_init( git_annotated_commit **out, @@ -96,6 +97,33 @@ int git_annotated_commit_from_fetchhead( return annotated_commit_init(out, repo, id, branch_name, remote_url); } +int git_annotated_commit_from_revspec( + git_annotated_commit **out, + git_repository *repo, + const char *revspec) +{ + git_object *obj, *commit; + int error; + + assert(out && repo && revspec); + + if ((error = git_revparse_single(&obj, repo, revspec)) < 0) + return error; + + if ((error = git_object_peel(&commit, obj, GIT_OBJ_COMMIT))) { + git_object_free(obj); + return error; + } + + error = annotated_commit_init(out, repo, git_object_id(commit), revspec, NULL); + + git_object_free(obj); + git_object_free(commit); + + return error; +} + + const git_oid *git_annotated_commit_id( const git_annotated_commit *annotated_commit) { diff --git a/src/branch.c b/src/branch.c index a16d3a3f9..10be6f70c 100644 --- a/src/branch.c +++ b/src/branch.c @@ -12,6 +12,7 @@ #include "refspec.h" #include "refs.h" #include "remote.h" +#include "annotated_commit.h" #include "git2/branch.h" @@ -49,11 +50,12 @@ static int not_a_local_branch(const char *reference_name) return -1; } -int git_branch_create( +static int create_branch( git_reference **ref_out, git_repository *repository, const char *branch_name, const git_commit *commit, + const char *from, int force) { int is_head = 0; @@ -86,7 +88,7 @@ int git_branch_create( if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) goto cleanup; - if (git_buf_printf(&log_message, "branch: Created from %s", git_oid_tostr_s(git_commit_id(commit))) < 0) + if (git_buf_printf(&log_message, "branch: Created from %s", from) < 0) goto cleanup; error = git_reference_create(&branch, repository, @@ -102,6 +104,26 @@ cleanup: return error; } +int git_branch_create( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_commit *commit, + int force) +{ + return create_branch(ref_out, repository, branch_name, commit, git_oid_tostr_s(git_commit_id(commit)), force); +} + +int git_branch_create_from_annotated( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_annotated_commit *commit, + int force) +{ + return create_branch(ref_out, repository, branch_name, commit->commit, commit->ref_name, force); +} + int git_branch_delete(git_reference *branch) { int is_head; diff --git a/src/repository.c b/src/repository.c index 0cbdf086a..b1f94f0e2 100644 --- a/src/repository.c +++ b/src/repository.c @@ -26,6 +26,7 @@ #include "remote.h" #include "merge.h" #include "diff_driver.h" +#include "annotated_commit.h" #ifdef GIT_WIN32 # include "win32/w32_util.h" @@ -1961,27 +1962,28 @@ cleanup: return error; } -int git_repository_set_head_detached( - git_repository* repo, - const git_oid* commitish) +static int detach(git_repository *repo, const git_oid *id, const char *from) { int error; git_buf log_message = GIT_BUF_INIT; git_object *object = NULL, *peeled = NULL; git_reference *new_head = NULL, *current = NULL; - assert(repo && commitish); + assert(repo && id); if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) return error; - if ((error = git_object_lookup(&object, repo, commitish, GIT_OBJ_ANY)) < 0) + if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0) goto cleanup; if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) goto cleanup; - if ((error = checkout_message(&log_message, current, git_oid_tostr_s(git_object_id(peeled)))) < 0) + if (from == NULL) + from = git_oid_tostr_s(git_object_id(peeled)); + + if ((error = checkout_message(&log_message, current, from)) < 0) goto cleanup; error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message)); @@ -1995,6 +1997,22 @@ cleanup: return error; } +int git_repository_set_head_detached( + git_repository* repo, + const git_oid* commitish) +{ + return detach(repo, commitish, NULL); +} + +int git_repository_set_head_detached_from_annotated( + git_repository *repo, + const git_annotated_commit *commitish) +{ + assert(repo && commitish); + + return detach(repo, git_annotated_commit_id(commitish), commitish->ref_name); +} + int git_repository_detach_head(git_repository* repo) { git_reference *old_head = NULL, *new_head = NULL, *current = NULL; diff --git a/src/reset.c b/src/reset.c index 351ecaa2a..aaebf4198 100644 --- a/src/reset.c +++ b/src/reset.c @@ -10,6 +10,7 @@ #include "tag.h" #include "merge.h" #include "diff.h" +#include "annotated_commit.h" #include "git2/reset.h" #include "git2/checkout.h" #include "git2/merge.h" @@ -96,9 +97,10 @@ cleanup: return error; } -int git_reset( +static int reset( git_repository *repo, git_object *target, + const char *to, git_reset_t reset_type, git_checkout_options *checkout_opts) { @@ -139,7 +141,7 @@ int git_reset( goto cleanup; } - if ((error = git_buf_printf(&log_message, "reset: moving to %s", git_oid_tostr_s(git_object_id(commit)))) < 0) + if ((error = git_buf_printf(&log_message, "reset: moving to %s", to)) < 0) return error; /* move HEAD to the new target */ @@ -176,3 +178,21 @@ cleanup: return error; } + +int git_reset( + git_repository *repo, + git_object *target, + git_reset_t reset_type, + git_checkout_options *checkout_opts) +{ + return reset(repo, target, git_oid_tostr_s(git_object_id(target)), reset_type, checkout_opts); +} + +int git_reset_from_annotated( + git_repository *repo, + git_annotated_commit *commit, + git_reset_t reset_type, + git_checkout_options *checkout_opts) +{ + return reset(repo, (git_object *) commit->commit, commit->ref_name, reset_type, checkout_opts); +} diff --git a/tests/refs/branches/create.c b/tests/refs/branches/create.c index d4cf4c29f..31dec0678 100644 --- a/tests/refs/branches/create.c +++ b/tests/refs/branches/create.c @@ -97,6 +97,7 @@ void test_refs_branches_create__default_reflog_message(void) git_reflog *log; git_buf buf = GIT_BUF_INIT; const git_reflog_entry *entry; + git_annotated_commit *annotated; git_signature *sig; git_config *cfg; @@ -116,6 +117,21 @@ void test_refs_branches_create__default_reflog_message(void) cl_assert_equal_s(git_buf_cstr(&buf), git_reflog_entry_message(entry)); cl_assert_equal_s(sig->email, git_reflog_entry_committer(entry)->email); + cl_git_pass(git_reference_remove(repo, "refs/heads/" NEW_BRANCH_NAME)); + git_reference_free(branch); + git_reflog_free(log); + git_buf_clear(&buf); + + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "e90810b8df3")); + cl_git_pass(git_branch_create_from_annotated(&branch, repo, NEW_BRANCH_NAME, annotated, true)); + cl_git_pass(git_reflog_read(&log, repo, "refs/heads/" NEW_BRANCH_NAME)); + + entry = git_reflog_entry_byindex(log, 0); + cl_git_pass(git_buf_printf(&buf, "branch: Created from e90810b8df3")); + cl_assert_equal_s(git_buf_cstr(&buf), git_reflog_entry_message(entry)); + cl_assert_equal_s(sig->email, git_reflog_entry_committer(entry)->email); + + git_annotated_commit_free(annotated); git_buf_free(&buf); git_reflog_free(log); git_signature_free(sig); diff --git a/tests/repo/head.c b/tests/repo/head.c index b26d6acc7..31c228777 100644 --- a/tests/repo/head.c +++ b/tests/repo/head.c @@ -2,6 +2,7 @@ #include "refs.h" #include "repo_helpers.h" #include "posix.h" +#include "git2/annotated_commit.h" static const char *g_email = "foo@example.com"; static git_repository *repo; @@ -251,6 +252,7 @@ void test_repo_head__setting_head_updates_reflog(void) { git_object *tag; git_signature *sig; + git_annotated_commit *annotated; cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); @@ -264,6 +266,12 @@ void test_repo_head__setting_head_updates_reflog(void) test_reflog(repo, 1, NULL, "tags/test^{commit}", "foo@example.com", "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d"); test_reflog(repo, 0, "tags/test^{commit}", "refs/heads/haacked", "foo@example.com", "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"); + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "haacked~0")); + cl_git_pass(git_repository_set_head_detached_from_annotated(repo, annotated)); + + test_reflog(repo, 0, NULL, "refs/heads/haacked", "foo@example.com", "checkout: moving from haacked to haacked~0"); + + git_annotated_commit_free(annotated); git_object_free(tag); git_signature_free(sig); } diff --git a/tests/reset/hard.c b/tests/reset/hard.c index f6ca1037b..86d4be2ed 100644 --- a/tests/reset/hard.c +++ b/tests/reset/hard.c @@ -201,6 +201,7 @@ void test_reset_hard__cleans_up_merge(void) void test_reset_hard__reflog_is_correct(void) { git_buf buf = GIT_BUF_INIT; + git_annotated_commit *annotated; const char *exp_msg = "commit: Add a file which name should appear before the " "\"subdir/\" folder while being dealt with by the treewalker"; @@ -215,7 +216,7 @@ void test_reset_hard__reflog_is_correct(void) git_object_free(target); - /* Moved branch, expect default message */ + /* Moved branch, expect id in message */ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); cl_git_pass(git_buf_printf(&buf, "reset: moving to %s", git_oid_tostr_s(git_object_id(target)))); cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); @@ -223,4 +224,14 @@ void test_reset_hard__reflog_is_correct(void) reflog_check(repo, "refs/heads/master", 4, NULL, git_buf_cstr(&buf)); git_buf_free(&buf); + + /* Moved branch, expect revspec in message */ + exp_msg = "reset: moving to HEAD~^{commit}"; + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); + cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_HARD, NULL)); + reflog_check(repo, "HEAD", 5, NULL, exp_msg); + reflog_check(repo, "refs/heads/master", 5, NULL, exp_msg); + + git_annotated_commit_free(annotated); + } diff --git a/tests/reset/mixed.c b/tests/reset/mixed.c index b374902aa..97eac74e8 100644 --- a/tests/reset/mixed.c +++ b/tests/reset/mixed.c @@ -51,6 +51,7 @@ void test_reset_mixed__resetting_refreshes_the_index_to_the_commit_tree(void) void test_reset_mixed__reflog_is_correct(void) { git_buf buf = GIT_BUF_INIT; + git_annotated_commit *annotated; const char *exp_msg = "commit: Updating test data so we can test inter-hunk-context"; reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); @@ -65,13 +66,20 @@ void test_reset_mixed__reflog_is_correct(void) git_object_free(target); target = NULL; - /* Moved branch, expect default message */ + /* Moved branch, expect id in message */ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); git_buf_clear(&buf); cl_git_pass(git_buf_printf(&buf, "reset: moving to %s", git_oid_tostr_s(git_object_id(target)))); cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); reflog_check(repo, "HEAD", 10, NULL, git_buf_cstr(&buf)); reflog_check(repo, "refs/heads/master", 10, NULL, git_buf_cstr(&buf)); - git_buf_free(&buf); + + /* Moved branch, expect revspec in message */ + exp_msg = "reset: moving to HEAD~^{commit}"; + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); + cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_MIXED, NULL)); + reflog_check(repo, "HEAD", 11, NULL, exp_msg); + reflog_check(repo, "refs/heads/master", 11, NULL, exp_msg); + git_annotated_commit_free(annotated); } diff --git a/tests/reset/soft.c b/tests/reset/soft.c index a5bb13cc8..506decaed 100644 --- a/tests/reset/soft.c +++ b/tests/reset/soft.c @@ -155,28 +155,35 @@ void test_reset_soft__fails_when_index_contains_conflicts_independently_of_MERGE cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT, NULL)); } -void test_reset_soft_reflog_is_correct(void) +void test_reset_soft__reflog_is_correct(void) { - const char *exp_msg = "commit: Updating test data so we can test inter-hunk-context"; + git_annotated_commit *annotated; + const char *exp_msg = "checkout: moving from br2 to master"; + const char *master_msg = "commit: checking in"; - reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "HEAD", 7, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 2, "yoram.harmelin@gmail.com", master_msg); /* Branch not moving, no reflog entry */ cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}")); cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "HEAD", 7, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 2, "yoram.harmelin@gmail.com", master_msg); + git_object_free(target); - /* Moved branch, expect default message */ + /* Moved branch, expect id in message */ + exp_msg = "reset: moving to be3563ae3f795b2b4353bcce3a527ad0a4f7f644"; cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 10, NULL, "reset: moving"); + reflog_check(repo, "HEAD", 8, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 3, NULL, exp_msg); - /* Moved branch, expect custom message */ - cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + /* Moved branch, expect message with annotated string */ + exp_msg = "reset: moving to HEAD~^{commit}"; + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); + cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_SOFT, NULL)); reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 11, NULL, "message1"); + reflog_check(repo, "refs/heads/master", 4, NULL, exp_msg); + + git_annotated_commit_free(annotated); }