Merge branch 'bc/reflog-fix' into js/reflog-delete
authorJunio C Hamano <gitster@pobox.com>
Sat, 23 Feb 2008 06:54:37 +0000 (22:54 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sat, 23 Feb 2008 06:54:37 +0000 (22:54 -0800)
* bc/reflog-fix: (1490 commits)
  builtin-reflog.c: don't install new reflog on write failure
  hash: fix lookup_hash semantics
  gitweb: Better chopping in commit search results
  builtin-tag.c: remove cruft
  git-merge-index documentation: clarify synopsis
  send-email: fix In-Reply-To regression
  git-reset --hard and git-read-tree --reset: fix read_cache_unmerged()
  Teach git-grep --name-only as synonym for -l
  diff: fix java funcname pattern for solaris
  t3404: use configured shell instead of /bin/sh
  git_config_*: don't assume we are parsing a config file
  prefix_path: use is_absolute_path() instead of *orig == '/'
  git-clean: handle errors if removing files fails
  Clarified the meaning of git-add -u in the documentation
  git-clone.sh: properly configure remote even if remote's head is dangling
  git.el: Set process-environment instead of invoking env
  Documentation/git-stash: document options for git stash list
  send-email: squelch warning due to comparing undefined $_ to ""
  cvsexportcommit: be graceful when "cvs status" reorders the arguments
  Rename git-core rpm to just git and rename the meta-pacakge to git-all.
  ...

Conflicts:

Documentation/git-reflog.txt
t/t1410-reflog.sh

1  2 
Documentation/git-reflog.txt
builtin-reflog.c
t/t1410-reflog.sh

@@@ -19,9 -19,7 +19,9 @@@ depending on the subcommand
  git reflog expire [--dry-run] [--stale-fix] [--verbose]
        [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...
  
- git reflog [show] [log-options]
 +git reflog delete ref@\{specifier\}...
 +
+ git reflog [show] [log-options] [<ref>]
  
  Reflog is a mechanism to record when the tip of branches are
  updated.  This command is to manage the information recorded in it.
@@@ -30,17 -28,21 +30,24 @@@ The subcommand "expire" is used to prun
  Entries older than `expire` time, or entries older than
  `expire-unreachable` time and are not reachable from the current
  tip, are removed from the reflog.  This is typically not used
- directly by the end users -- instead, see gitlink:git-gc[1].
+ directly by the end users -- instead, see linkgit:git-gc[1].
  
  The subcommand "show" (which is also the default, in the absence of any
  subcommands) will take all the normal log options, and show the log of
- `HEAD`, which will cover all recent actions, including branch switches.
- It is basically an alias for 'git log -g --abbrev-commit
- --pretty=oneline', see gitlink:git-log[1].
+ the reference provided in the command-line (or `HEAD`, by default).
+ The reflog will cover all recent actions (HEAD reflog records branch switching
+ as well).  It is an alias for 'git log -g --abbrev-commit --pretty=oneline';
+ see linkgit:git-log[1].
+ The reflog is useful in various git commands, to specify the old value
+ of a reference. For example, `HEAD@\{2\}` means "where HEAD used to be
+ 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
  -------
@@@ -86,4 -88,4 +93,4 @@@ Documentation by Junio C Hamano and th
  
  GIT
  ---
- Part of the gitlink:git[7] suite
+ Part of the linkgit:git[7] suite
diff --combined builtin-reflog.c
@@@ -25,7 -25,6 +25,7 @@@ struct cmd_reflog_expire_cb 
        int verbose;
        unsigned long expire_total;
        unsigned long expire_unreachable;
 +      int recno;
  };
  
  struct expire_reflog_cb {
        struct cmd_reflog_expire_cb *cmd;
  };
  
+ struct collected_reflog {
+       unsigned char sha1[20];
+       char reflog[FLEX_ARRAY];
+ };
+ struct collect_reflog_cb {
+       struct collected_reflog **e;
+       int alloc;
+       int nr;
+ };
  #define INCOMPLETE    (1u<<10)
  #define STUDYING      (1u<<11)
  
@@@ -221,9 -230,6 +231,9 @@@ static int expire_reflog_ent(unsigned c
                        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;
@@@ -270,10 -276,11 +280,11 @@@ static int expire_reflog(const char *re
        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
   finish:
        if (cb.newlog) {
-               if (fclose(cb.newlog))
+               if (fclose(cb.newlog)) {
                        status |= error("%s: %s", strerror(errno),
                                        newlog_path);
-               if (rename(newlog_path, log_file)) {
+                       unlink(newlog_path);
+               } else if (rename(newlog_path, log_file)) {
                        status |= error("cannot rename %s to %s",
                                        newlog_path, log_file);
                        unlink(newlog_path);
        return status;
  }
  
+ static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+ {
+       struct collected_reflog *e;
+       struct collect_reflog_cb *cb = cb_data;
+       size_t namelen = strlen(ref);
+       e = xmalloc(sizeof(*e) + namelen + 1);
+       hashcpy(e->sha1, sha1);
+       memcpy(e->reflog, ref, namelen + 1);
+       ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
+       cb->e[cb->nr++] = e;
+       return 0;
+ }
  static int reflog_expire_config(const char *var, const char *value)
  {
-       if (!strcmp(var, "gc.reflogexpire"))
+       if (!strcmp(var, "gc.reflogexpire")) {
+               if (!value)
+                       config_error_nonbool(var);
                default_reflog_expire = approxidate(value);
-       else if (!strcmp(var, "gc.reflogexpireunreachable"))
+               return 0;
+       }
+       if (!strcmp(var, "gc.reflogexpireunreachable")) {
+               if (!value)
+                       config_error_nonbool(var);
                default_reflog_expire_unreachable = approxidate(value);
-       else
-               return git_default_config(var, value);
-       return 0;
+               return 0;
+       }
+       return git_default_config(var, value);
  }
  
  static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                        putchar('\n');
        }
  
-       if (do_all)
-               status |= for_each_reflog(expire_reflog, &cb);
+       if (do_all) {
+               struct collect_reflog_cb collected;
+               int i;
+               memset(&collected, 0, sizeof(collected));
+               for_each_reflog(collect_reflog, &collected);
+               for (i = 0; i < collected.nr; i++) {
+                       struct collected_reflog *e = collected.e[i];
+                       status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
+                       free(e);
+               }
+               free(collected.e);
+       }
        while (i < argc) {
                const char *ref = argv[i++];
                unsigned char sha1[20];
        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;
 +
 +      if (argc < 2)
 +              return error("Nothing to delete?");
 +
 +      memset(&cb, 0, sizeof(cb));
 +
 +      for (i = 1; 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"
   */
@@@ -438,9 -425,6 +481,9 @@@ int cmd_reflog(int argc, const char **a
        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 --combined t/t1410-reflog.sh
@@@ -175,30 -175,22 +175,49 @@@ 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/ .*//") &&
+       BLOB=$(echo aleph | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE &&
+       git reset --hard &&
+       git prune --expire=1.hour.ago &&
+       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE &&
+       test-chmtime -86500 $BLOB_FILE &&
+       git prune --expire 1.day &&
+       test $before = $(git count-objects | sed "s/ .*//") &&
+       ! test -f $BLOB_FILE
  '
  
  test_done