Merge branch 'js/reflog-delete'
* js/reflog-delete: t3903-stash.sh: Add tests for new stash commands drop and pop git-reflog.txt: Document new commands --updateref and --rewrite t3903-stash.sh: Add missing '&&' to body of testcase git-stash: add new 'pop' subcommand git-stash: add new 'drop' subcommand git-reflog: add option --updateref to write the last reflog sha1 into the ref refs.c: make close_ref() and commit_ref() non-static git-reflog: add option --rewrite to update reflog entries while expiring reflog-delete: parse standard reflog options builtin-reflog.c: fix typo that accesses an unset variable Teach "git reflog" a subcommand to delete single entries
This commit is contained in:
commit
d33046c1ed
@ -19,6 +19,8 @@ depending on the subcommand:
|
||||
git reflog expire [--dry-run] [--stale-fix] [--verbose]
|
||||
[--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...
|
||||
|
||||
git reflog delete ref@\{specifier\}...
|
||||
|
||||
git reflog [show] [log-options] [<ref>]
|
||||
|
||||
Reflog is a mechanism to record when the tip of branches are
|
||||
@ -43,6 +45,9 @@ two moves ago", `master@\{one.week.ago\}` means "where master used to
|
||||
point to one week ago", and so on. See linkgit:git-rev-parse[1] for
|
||||
more details.
|
||||
|
||||
To delete single entries from the reflog, use the subcommand "delete"
|
||||
and specify the _exact_ entry (e.g. ``git reflog delete master@\{2\}'').
|
||||
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
@ -75,6 +80,15 @@ them.
|
||||
--all::
|
||||
Instead of listing <refs> explicitly, prune all refs.
|
||||
|
||||
--updateref::
|
||||
Update the ref with the sha1 of the top reflog entry (i.e.
|
||||
<ref>@\{0\}) after expiring or deleting.
|
||||
|
||||
--rewrite::
|
||||
While expiring or deleting, adjust each reflog entry to ensure
|
||||
that the `old` sha1 field points to the `new` sha1 field of the
|
||||
previous entry.
|
||||
|
||||
--verbose::
|
||||
Print extra information on screen.
|
||||
|
||||
|
@ -8,7 +8,7 @@ git-stash - Stash the changes in a dirty working directory away
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git-stash' (list | show [<stash>] | apply [<stash>] | clear)
|
||||
'git-stash' (list | show [<stash>] | apply [<stash>] | clear | drop [<stash>] | pop [<stash>])
|
||||
'git-stash' [save [<message>]]
|
||||
|
||||
DESCRIPTION
|
||||
@ -85,6 +85,17 @@ clear::
|
||||
Remove all the stashed states. Note that those states will then
|
||||
be subject to pruning, and may be difficult or impossible to recover.
|
||||
|
||||
drop [<stash>]::
|
||||
|
||||
Remove a single stashed state from the stash list. When no `<stash>`
|
||||
is given, it removes the latest one. i.e. `stash@\{0}`
|
||||
|
||||
pop [<stash>]::
|
||||
|
||||
Remove a single stashed state from the stash list and apply on top
|
||||
of the current working tree state. When no `<stash>` is given,
|
||||
`stash@\{0}` is assumed. See also `apply`.
|
||||
|
||||
|
||||
DISCUSSION
|
||||
----------
|
||||
|
102
builtin-reflog.c
102
builtin-reflog.c
@ -14,6 +14,8 @@
|
||||
|
||||
static const char reflog_expire_usage[] =
|
||||
"git-reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
|
||||
static const char reflog_delete_usage[] =
|
||||
"git-reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
|
||||
|
||||
static unsigned long default_reflog_expire;
|
||||
static unsigned long default_reflog_expire_unreachable;
|
||||
@ -22,9 +24,12 @@ struct cmd_reflog_expire_cb {
|
||||
struct rev_info revs;
|
||||
int dry_run;
|
||||
int stalefix;
|
||||
int rewrite;
|
||||
int updateref;
|
||||
int verbose;
|
||||
unsigned long expire_total;
|
||||
unsigned long expire_unreachable;
|
||||
int recno;
|
||||
};
|
||||
|
||||
struct expire_reflog_cb {
|
||||
@ -32,6 +37,7 @@ struct expire_reflog_cb {
|
||||
const char *ref;
|
||||
struct commit *ref_commit;
|
||||
struct cmd_reflog_expire_cb *cmd;
|
||||
unsigned char last_kept_sha1[20];
|
||||
};
|
||||
|
||||
struct collected_reflog {
|
||||
@ -213,6 +219,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
|
||||
if (timestamp < cb->cmd->expire_total)
|
||||
goto prune;
|
||||
|
||||
if (cb->cmd->rewrite)
|
||||
osha1 = cb->last_kept_sha1;
|
||||
|
||||
old = new = NULL;
|
||||
if (cb->cmd->stalefix &&
|
||||
(!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
|
||||
@ -230,6 +239,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
|
||||
goto prune;
|
||||
}
|
||||
|
||||
if (cb->cmd->recno && --(cb->cmd->recno) == 0)
|
||||
goto prune;
|
||||
|
||||
if (cb->newlog) {
|
||||
char sign = (tz < 0) ? '-' : '+';
|
||||
int zone = (tz < 0) ? (-tz) : tz;
|
||||
@ -237,6 +249,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
|
||||
sha1_to_hex(osha1), sha1_to_hex(nsha1),
|
||||
email, timestamp, sign, zone,
|
||||
message);
|
||||
hashcpy(cb->last_kept_sha1, nsha1);
|
||||
}
|
||||
if (cb->cmd->verbose)
|
||||
printf("keep %s", message);
|
||||
@ -280,10 +293,20 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
|
||||
status |= error("%s: %s", strerror(errno),
|
||||
newlog_path);
|
||||
unlink(newlog_path);
|
||||
} else if (cmd->updateref &&
|
||||
(write_in_full(lock->lock_fd,
|
||||
sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
|
||||
write_in_full(lock->lock_fd, "\n", 1) != 1 ||
|
||||
close_ref(lock) < 0)) {
|
||||
status |= error("Couldn't write %s",
|
||||
lock->lk->filename);
|
||||
unlink(newlog_path);
|
||||
} else if (rename(newlog_path, log_file)) {
|
||||
status |= error("cannot rename %s to %s",
|
||||
newlog_path, log_file);
|
||||
unlink(newlog_path);
|
||||
} else if (cmd->updateref && commit_ref(lock)) {
|
||||
status |= error("Couldn't set %s", lock->ref_name);
|
||||
}
|
||||
}
|
||||
free(newlog_path);
|
||||
@ -358,6 +381,10 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
||||
cb.expire_unreachable = approxidate(arg + 21);
|
||||
else if (!strcmp(arg, "--stale-fix"))
|
||||
cb.stalefix = 1;
|
||||
else if (!strcmp(arg, "--rewrite"))
|
||||
cb.rewrite = 1;
|
||||
else if (!strcmp(arg, "--updateref"))
|
||||
cb.updateref = 1;
|
||||
else if (!strcmp(arg, "--all"))
|
||||
do_all = 1;
|
||||
else if (!strcmp(arg, "--verbose"))
|
||||
@ -406,6 +433,78 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
||||
return status;
|
||||
}
|
||||
|
||||
static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
|
||||
const char *email, unsigned long timestamp, int tz,
|
||||
const char *message, void *cb_data)
|
||||
{
|
||||
struct cmd_reflog_expire_cb *cb = cb_data;
|
||||
if (!cb->expire_total || timestamp < cb->expire_total)
|
||||
cb->recno++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct cmd_reflog_expire_cb cb;
|
||||
int i, status = 0;
|
||||
|
||||
memset(&cb, 0, sizeof(cb));
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
|
||||
cb.dry_run = 1;
|
||||
else if (!strcmp(arg, "--rewrite"))
|
||||
cb.rewrite = 1;
|
||||
else if (!strcmp(arg, "--updateref"))
|
||||
cb.updateref = 1;
|
||||
else if (!strcmp(arg, "--verbose"))
|
||||
cb.verbose = 1;
|
||||
else if (!strcmp(arg, "--")) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
else if (arg[0] == '-')
|
||||
usage(reflog_delete_usage);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (argc - i < 1)
|
||||
return error("Nothing to delete?");
|
||||
|
||||
for ( ; i < argc; i++) {
|
||||
const char *spec = strstr(argv[i], "@{");
|
||||
unsigned char sha1[20];
|
||||
char *ep, *ref;
|
||||
int recno;
|
||||
|
||||
if (!spec) {
|
||||
status |= error("Not a reflog: %s", argv[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!dwim_ref(argv[i], spec - argv[i], sha1, &ref)) {
|
||||
status |= error("%s points nowhere!", argv[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
recno = strtoul(spec + 2, &ep, 10);
|
||||
if (*ep == '}') {
|
||||
cb.recno = -recno;
|
||||
for_each_reflog_ent(ref, count_reflog_ent, &cb);
|
||||
} else {
|
||||
cb.expire_total = approxidate(spec + 2);
|
||||
for_each_reflog_ent(ref, count_reflog_ent, &cb);
|
||||
cb.expire_total = 0;
|
||||
}
|
||||
|
||||
status |= expire_reflog(ref, sha1, 0, &cb);
|
||||
free(ref);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* main "reflog"
|
||||
*/
|
||||
@ -425,6 +524,9 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
|
||||
if (!strcmp(argv[1], "expire"))
|
||||
return cmd_reflog_expire(argc - 1, argv + 1, prefix);
|
||||
|
||||
if (!strcmp(argv[1], "delete"))
|
||||
return cmd_reflog_delete(argc - 1, argv + 1, prefix);
|
||||
|
||||
/* Not a recognized reflog command..*/
|
||||
usage(reflog_usage);
|
||||
}
|
||||
|
36
git-stash.sh
36
git-stash.sh
@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) 2007, Nanako Shiraishi
|
||||
|
||||
USAGE='[ | save | list | show | apply | clear | create ]'
|
||||
USAGE='[ | save | list | show | apply | clear | drop | pop | create ]'
|
||||
|
||||
SUBDIRECTORY_OK=Yes
|
||||
OPTIONS_SPEC=
|
||||
@ -196,6 +196,28 @@ apply_stash () {
|
||||
fi
|
||||
}
|
||||
|
||||
drop_stash () {
|
||||
have_stash || die 'No stash entries to drop'
|
||||
|
||||
if test $# = 0
|
||||
then
|
||||
set x "$ref_stash@{0}"
|
||||
shift
|
||||
fi
|
||||
# Verify supplied argument looks like a stash entry
|
||||
s=$(git rev-parse --revs-only --no-flags "$@") &&
|
||||
git rev-parse --verify "$s:" > /dev/null 2>&1 &&
|
||||
git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
|
||||
git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
|
||||
die "$*: not a valid stashed state"
|
||||
|
||||
git reflog delete --updateref --rewrite "$@" &&
|
||||
echo "Dropped $* ($s)" || die "$*: Could not drop stash entry"
|
||||
|
||||
# clear_stash if we just dropped the last stash entry
|
||||
git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
|
||||
}
|
||||
|
||||
# Main command set
|
||||
case "$1" in
|
||||
list)
|
||||
@ -230,6 +252,18 @@ create)
|
||||
fi
|
||||
create_stash "$*" && echo "$w_commit"
|
||||
;;
|
||||
drop)
|
||||
shift
|
||||
drop_stash "$@"
|
||||
;;
|
||||
pop)
|
||||
shift
|
||||
if apply_stash "$@"
|
||||
then
|
||||
test -z "$unstash_index" || shift
|
||||
drop_stash "$@"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if test $# -eq 0
|
||||
then
|
||||
|
4
refs.c
4
refs.c
@ -1033,7 +1033,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int close_ref(struct ref_lock *lock)
|
||||
int close_ref(struct ref_lock *lock)
|
||||
{
|
||||
if (close_lock_file(lock->lk))
|
||||
return -1;
|
||||
@ -1041,7 +1041,7 @@ static int close_ref(struct ref_lock *lock)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int commit_ref(struct ref_lock *lock)
|
||||
int commit_ref(struct ref_lock *lock)
|
||||
{
|
||||
if (commit_lock_file(lock->lk))
|
||||
return -1;
|
||||
|
6
refs.h
6
refs.h
@ -33,6 +33,12 @@ extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_
|
||||
#define REF_NODEREF 0x01
|
||||
extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags);
|
||||
|
||||
/** Close the file descriptor owned by a lock and return the status */
|
||||
extern int close_ref(struct ref_lock *lock);
|
||||
|
||||
/** Close and commit the ref locked by the lock */
|
||||
extern int commit_ref(struct ref_lock *lock);
|
||||
|
||||
/** Release any lock taken but not written. **/
|
||||
extern void unlock_ref(struct ref_lock *lock);
|
||||
|
||||
|
@ -175,6 +175,33 @@ test_expect_success 'recover and check' '
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'delete' '
|
||||
echo 1 > C &&
|
||||
test_tick &&
|
||||
git commit -m rat C &&
|
||||
|
||||
echo 2 > C &&
|
||||
test_tick &&
|
||||
git commit -m ox C &&
|
||||
|
||||
echo 3 > C &&
|
||||
test_tick &&
|
||||
git commit -m tiger C &&
|
||||
|
||||
test 5 = $(git reflog | wc -l) &&
|
||||
|
||||
git reflog delete master@{1} &&
|
||||
git reflog show master > output &&
|
||||
test 4 = $(wc -l < output) &&
|
||||
! grep ox < output &&
|
||||
|
||||
git reflog delete master@{07.04.2005.15:15:00.-0700} &&
|
||||
git reflog show master > output &&
|
||||
test 3 = $(wc -l < output) &&
|
||||
! grep dragon < output
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'prune --expire' '
|
||||
|
||||
before=$(git count-objects | sed "s/ .*//") &&
|
||||
|
@ -40,8 +40,8 @@ test_expect_success 'parents of stash' '
|
||||
test_expect_success 'apply needs clean working directory' '
|
||||
echo 4 > other-file &&
|
||||
git add other-file &&
|
||||
echo 5 > other-file
|
||||
! git stash apply
|
||||
echo 5 > other-file &&
|
||||
test_must_fail git stash apply
|
||||
'
|
||||
|
||||
test_expect_success 'apply stashed changes' '
|
||||
@ -70,7 +70,51 @@ test_expect_success 'unstashing in a subdirectory' '
|
||||
git reset --hard HEAD &&
|
||||
mkdir subdir &&
|
||||
cd subdir &&
|
||||
git stash apply
|
||||
git stash apply &&
|
||||
cd ..
|
||||
'
|
||||
|
||||
test_expect_success 'drop top stash' '
|
||||
git reset --hard &&
|
||||
git stash list > stashlist1 &&
|
||||
echo 7 > file &&
|
||||
git stash &&
|
||||
git stash drop &&
|
||||
git stash list > stashlist2 &&
|
||||
diff stashlist1 stashlist2 &&
|
||||
git stash apply &&
|
||||
test 3 = $(cat file) &&
|
||||
test 1 = $(git show :file) &&
|
||||
test 1 = $(git show HEAD:file)
|
||||
'
|
||||
|
||||
test_expect_success 'drop middle stash' '
|
||||
git reset --hard &&
|
||||
echo 8 > file &&
|
||||
git stash &&
|
||||
echo 9 > file &&
|
||||
git stash &&
|
||||
git stash drop stash@{1} &&
|
||||
test 2 = $(git stash list | wc -l) &&
|
||||
git stash apply &&
|
||||
test 9 = $(cat file) &&
|
||||
test 1 = $(git show :file) &&
|
||||
test 1 = $(git show HEAD:file) &&
|
||||
git reset --hard &&
|
||||
git stash drop &&
|
||||
git stash apply &&
|
||||
test 3 = $(cat file) &&
|
||||
test 1 = $(git show :file) &&
|
||||
test 1 = $(git show HEAD:file)
|
||||
'
|
||||
|
||||
test_expect_success 'stash pop' '
|
||||
git reset --hard &&
|
||||
git stash pop &&
|
||||
test 3 = $(cat file) &&
|
||||
test 1 = $(git show :file) &&
|
||||
test 1 = $(git show HEAD:file) &&
|
||||
test 0 = $(git stash list | wc -l)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user