Re: [PATCH 4/8] builtin/reflog: implement subcommand to write new entries

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 25/07/22 01:20PM, Patrick Steinhardt wrote:
> While we provide a couple of subcommands in git-reflog(1) to remove
> reflog entries, we don't provide any to write new entries. Obviously
> this is not an operation that really would be needed for many use cases
> out there, or otherwise people would have complained that such a command
> does not exist yet. But the introduction of the "reftable" backend
> changes the picture a bit, as it is now basically impossible to manually
> append a reflog entry if one wanted to do so due to the binary format.
> 
> Plug this gap by introducing a simple "write" subcommand. For now, all
> this command does is to append a single new reflog entry with the given
> object IDs and message to the reflog. More specifically, it is not yet
> possible to:
> 
>   - Write multiple reflog entries at once.
> 
>   - Insert reflog entries at arbitrary indices.
> 
>   - Specify the date of the reflog entry.
> 
>   - Insert reflog entries that refer to nonexistent objects.
> 
> If required, those features can be added at a future point in time. For
> now though, the new command aims to fulfill the most basic use cases
> while being as strict as possible when it comes to verifying parameters.
> 
> Signed-off-by: Patrick Steinhardt <ps@xxxxxx>
> ---
>  Documentation/git-reflog.adoc |  1 +
>  builtin/reflog.c              | 65 ++++++++++++++++++++++++++++++++++
>  t/meson.build                 |  1 +
>  t/t1421-reflog-write.sh       | 81 +++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 148 insertions(+)
> 
> diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc
> index 6ae13e772b8..798dbc0a00a 100644
> --- a/Documentation/git-reflog.adoc
> +++ b/Documentation/git-reflog.adoc
> @@ -12,6 +12,7 @@ SYNOPSIS
>  git reflog [show] [<log-options>] [<ref>]
>  git reflog list
>  git reflog exists <ref>
> +git reflog write <ref> <old-oid> <new-oid> <message>

The other subcommands each have an entry in the description. Do we want
to also add something for the "write" subcommand?

Also, if we want to be consistent, I noticed the order of the
subcommands listed in the description was not changed either. 

>  git reflog delete [--rewrite] [--updateref]
>  	[--dry-run | -n] [--verbose] <ref>@{<specifier>}...
>  git reflog drop [--all [--single-worktree] | <refs>...]
> diff --git a/builtin/reflog.c b/builtin/reflog.c
> index b00b3f9edc9..d0374295620 100644
> --- a/builtin/reflog.c
> +++ b/builtin/reflog.c
> @@ -3,6 +3,8 @@
>  #include "builtin.h"
>  #include "config.h"
>  #include "gettext.h"
> +#include "hex.h"
> +#include "odb.h"
>  #include "revision.h"
>  #include "reachable.h"
>  #include "wildmatch.h"
> @@ -20,6 +22,9 @@
>  #define BUILTIN_REFLOG_EXISTS_USAGE \
>  	N_("git reflog exists <ref>")
>  
> +#define BUILTIN_REFLOG_WRITE_USAGE \
> +	N_("git reflog write <ref> <old-oid> <new-oid> <message>")
> +
>  #define BUILTIN_REFLOG_DELETE_USAGE \
>  	N_("git reflog delete [--rewrite] [--updateref]\n" \
>  	   "                  [--dry-run | -n] [--verbose] <ref>@{<specifier>}...")
> @@ -47,6 +52,11 @@ static const char *const reflog_exists_usage[] = {
>  	NULL,
>  };
>  
> +static const char *const reflog_write_usage[] = {
> +	BUILTIN_REFLOG_WRITE_USAGE,
> +	NULL,
> +};
> +
>  static const char *const reflog_delete_usage[] = {
>  	BUILTIN_REFLOG_DELETE_USAGE,
>  	NULL
> @@ -66,6 +76,7 @@ static const char *const reflog_usage[] = {
>  	BUILTIN_REFLOG_SHOW_USAGE,
>  	BUILTIN_REFLOG_LIST_USAGE,
>  	BUILTIN_REFLOG_EXISTS_USAGE,
> +	BUILTIN_REFLOG_WRITE_USAGE,
>  	BUILTIN_REFLOG_DELETE_USAGE,
>  	BUILTIN_REFLOG_DROP_USAGE,
>  	BUILTIN_REFLOG_EXPIRE_USAGE,
> @@ -392,6 +403,59 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix,
>  	return ret;
>  }
>  
> +static int cmd_reflog_write(int argc, const char **argv, const char *prefix,
> +			    struct repository *repo)
> +{
> +	const struct option options[] = {
> +		OPT_END()
> +	};
> +	struct object_id old_oid, new_oid;
> +	struct strbuf err = STRBUF_INIT;
> +	struct ref_transaction *tx;
> +	const char *ref, *message;
> +	int ret;
> +
> +	argc = parse_options(argc, argv, prefix, options, reflog_drop_usage, 0);
> +	if (argc != 4)
> +		usage_with_options(reflog_write_usage, options);
> +
> +	ref = argv[0];
> +	if (check_refname_format(ref, REFNAME_ALLOW_ONELEVEL))
> +		die(_("invalid reference name: %s"), ref);
> +
> +	ret = get_oid_hex_algop(argv[1], &old_oid, repo->hash_algo);
> +	if (ret)
> +		die(_("invalid old object ID: '%s'"), argv[1]);
> +	if (!is_null_oid(&old_oid) && !odb_has_object(repo->objects, &old_oid, 0))
> +		die(_("old object '%s' does not exist"), argv[1]);
> +
> +	ret = get_oid_hex_algop(argv[2], &new_oid, repo->hash_algo);
> +	if (ret)
> +		die(_("invalid new object ID: '%s'"), argv[2]);
> +	if (!is_null_oid(&new_oid) && !odb_has_object(repo->objects, &new_oid, 0))
> +		die(_("new object '%s' does not exist"), argv[2]);

Ok so we validate the reference name and the old/new obects names to
make sure they are sane.

> +
> +	message = argv[3];
> +
> +	tx = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err);
> +	if (!tx)
> +		die(_("cannot start transaction: %s"), err.buf);
> +
> +	ret = ref_transaction_update_reflog(tx, ref, &new_oid, &old_oid,
> +					    git_committer_info(0),
> +					    message, 0, &err);
> +	if (ret)
> +		die(_("cannot queue reflog update: %s"), err.buf);
> +
> +	ret = ref_transaction_commit(tx, &err);
> +	if (ret)
> +		die(_("cannot commit reflog update: %s"), err.buf);

And here we write the reflog entry. Looks good

> +
> +	ref_transaction_free(tx);
> +	strbuf_release(&err);
> +	return 0;
> +}
> +
>  /*
>   * main "reflog"
>   */
> @@ -405,6 +469,7 @@ int cmd_reflog(int argc,
>  		OPT_SUBCOMMAND("show", &fn, cmd_reflog_show),
>  		OPT_SUBCOMMAND("list", &fn, cmd_reflog_list),
>  		OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists),
> +		OPT_SUBCOMMAND("write", &fn, cmd_reflog_write),
>  		OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
>  		OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop),
>  		OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
> diff --git a/t/meson.build b/t/meson.build
> index 1af289425d4..d68f5e24dbe 100644
> --- a/t/meson.build
> +++ b/t/meson.build
> @@ -219,6 +219,7 @@ integration_tests = [
>    't1418-reflog-exists.sh',
>    't1419-exclude-refs.sh',
>    't1420-lost-found.sh',
> +  't1421-reflog-write.sh',
>    't1430-bad-ref-name.sh',
>    't1450-fsck.sh',
>    't1451-fsck-buffer.sh',
> diff --git a/t/t1421-reflog-write.sh b/t/t1421-reflog-write.sh
> new file mode 100755
> index 00000000000..e284f42178f
> --- /dev/null
> +++ b/t/t1421-reflog-write.sh
> @@ -0,0 +1,81 @@
> +#!/bin/sh
> +
> +test_description='Manually write reflog entries'
> +
> +. ./test-lib.sh
> +
> +SIGNATURE="C O Mitter <committer@xxxxxxxxxxx> 1112911993 -0700"
> +
> +test_reflog_matches () {
> +	repo="$1" &&
> +	refname="$2" &&
> +	cat >actual &&
> +	test-tool -C "$repo" ref-store main for-each-reflog-ent "$refname" >expected &&
> +	test_cmp expected actual
> +}
> +
> +test_expect_success 'invalid number of arguments' '
> +	test_when_finished "rm -rf repo" &&
> +	git init repo &&
> +	(
> +		cd repo &&
> +		for args in "" "1" "1 2" "1 2 3" "1 2 3 4 5"
> +		do
> +			test_must_fail git reflog write $args 2>err &&
> +			test_grep "usage: git reflog write" err || return 1
> +		done
> +	)
> +'
> +
> +test_expect_success 'invalid refname' '
> +	test_when_finished "rm -rf repo" &&
> +	git init repo &&
> +	(
> +		cd repo &&
> +		test_must_fail git reflog write "refs/heads/ invalid" $ZERO_OID $ZERO_OID first 2>err &&
> +		test_grep "invalid reference name: " err
> +	)
> +'
> +
> +test_expect_success 'nonexistent old object ID' '
> +	test_when_finished "rm -rf repo" &&
> +	git init repo &&
> +	(
> +		cd repo &&
> +		test_must_fail git reflog write refs/heads/something $(test_oid deadbeef) $ZERO_OID first 2>err &&
> +		test_grep "old object .* does not exist" err
> +	)
> +'
> +
> +test_expect_success 'nonexistent new object ID' '
> +	test_when_finished "rm -rf repo" &&
> +	git init repo &&
> +	(
> +		cd repo &&
> +		test_must_fail git reflog write refs/heads/something $ZERO_OID $(test_oid deadbeef) first 2>err &&
> +		test_grep "new object .* does not exist" err
> +	)
> +'
> +
> +test_expect_success 'simple writes' '
> +	test_when_finished "rm -rf repo" &&
> +	git init repo &&
> +	(
> +		cd repo &&
> +		test_commit initial &&
> +		COMMIT_OID=$(git rev-parse HEAD) &&
> +
> +		git reflog write refs/heads/something $ZERO_OID $COMMIT_OID first &&
> +		test_reflog_matches . refs/heads/something <<-EOF &&
> +		$ZERO_OID $COMMIT_OID $SIGNATURE	first
> +		EOF
> +
> +		git reflog write refs/heads/something $COMMIT_OID $COMMIT_OID second &&
> +		test_reflog_matches . refs/heads/something <<-EOF
> +		$ZERO_OID $COMMIT_OID $SIGNATURE	first
> +		$COMMIT_OID $COMMIT_OID $SIGNATURE	second
> +		EOF
> +	)
> +'
> +
> +test_done
> 
> -- 
> 2.50.1.465.gcb3da1c9e6.dirty
> 
> 




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux