diff options
author | Junio C Hamano <gitster@pobox.com> | 2008-03-07 22:34:26 -0800 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2008-03-07 22:34:26 -0800 |
commit | d33046c1ed2182a4f73b741e47ab28b1570805c5 (patch) | |
tree | 36bb9474d82a6d8ec9a60a41065883af2dd1ce73 | |
parent | 5628a7a309e11c413138291b215a8cecedaddd24 (diff) | |
parent | b683c08082d73484596dcf1236449228d4a9eb98 (diff) | |
download | git-d33046c1ed2182a4f73b741e47ab28b1570805c5.tar.gz |
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
-rw-r--r-- | Documentation/git-reflog.txt | 14 | ||||
-rw-r--r-- | Documentation/git-stash.txt | 13 | ||||
-rw-r--r-- | builtin-reflog.c | 102 | ||||
-rwxr-xr-x | git-stash.sh | 36 | ||||
-rw-r--r-- | refs.c | 4 | ||||
-rw-r--r-- | refs.h | 6 | ||||
-rwxr-xr-x | t/t1410-reflog.sh | 27 | ||||
-rwxr-xr-x | t/t3903-stash.sh | 50 |
8 files changed, 245 insertions, 7 deletions
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index f9bba36c23..047e3ce14d 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 48e6f5a3f7..8dc35d493e 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 ab53c8cb7c..280e24e151 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 b00f888169..c2b68205a2 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 @@ -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; @@ -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 f959aae846..24476bede5 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 9a9a250d2c..aa282e1bc1 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 |