aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-reflog.txt14
-rw-r--r--Documentation/git-stash.txt13
-rw-r--r--builtin-reflog.c102
-rwxr-xr-xgit-stash.sh36
-rw-r--r--refs.c4
-rw-r--r--refs.h6
-rwxr-xr-xt/t1410-reflog.sh27
-rwxr-xr-xt/t3903-stash.sh50
8 files changed, 245 insertions, 7 deletions
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index f9bba36c2..047e3ce14 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -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.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 48e6f5a3f..8dc35d493 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -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
----------
diff --git a/builtin-reflog.c b/builtin-reflog.c
index ab53c8cb7..280e24e15 100644
--- a/builtin-reflog.c
+++ b/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);
}
diff --git a/git-stash.sh b/git-stash.sh
index b00f88816..c2b68205a 100755
--- a/git-stash.sh
+++ b/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
diff --git a/refs.c b/refs.c
index c979fb1d9..1b0050eee 100644
--- a/refs.c
+++ b/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;
diff --git a/refs.h b/refs.h
index 9cd16f829..06abee152 100644
--- a/refs.h
+++ b/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);
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index f959aae84..24476bede 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -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/ .*//") &&
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 9a9a250d2..aa282e1bc 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -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