Code

Merge branch 'jc/parse-date-raw'
authorJunio C Hamano <gitster@pobox.com>
Fri, 10 Feb 2012 22:08:12 +0000 (14:08 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 10 Feb 2012 22:08:12 +0000 (14:08 -0800)
* jc/parse-date-raw:
  parse_date(): '@' prefix forces git-timestamp
  parse_date(): allow ancient git-timestamp

1  2 
builtin/commit.c
date.c
git-sh-setup.sh
t/t3400-rebase.sh

diff --combined builtin/commit.c
index 470b4a4e8c704b995124ebd08bcab225a62601e8,bcb0db2db53de93887a5d91465a910bb0e4b9a24..eae5a29aeb4248b972f97632026993c25d5e03da
@@@ -26,7 -26,6 +26,7 @@@
  #include "unpack-trees.h"
  #include "quote.h"
  #include "submodule.h"
 +#include "gpg-interface.h"
  
  static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@@ -82,13 -81,10 +82,13 @@@ static const char *template_file
  static const char *author_message, *author_message_buffer;
  static char *edit_message, *use_message;
  static char *fixup_message, *squash_message;
 -static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
 +static int all, also, interactive, patch_interactive, only, amend, signoff;
 +static int edit_flag = -1; /* unspecified */
  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
  static int no_post_rewrite, allow_empty_message;
  static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
 +static char *sign_commit;
 +
  /*
   * The default commit message cleanup mode will remove the lines
   * beginning with # (shell comments) and leading and trailing
@@@ -107,7 -103,7 +107,7 @@@ static enum commit_whence whence
  static int use_editor = 1, include_status = 1;
  static int show_ignored_in_status;
  static const char *only_include_assumed;
 -static struct strbuf message;
 +static struct strbuf message = STRBUF_INIT;
  
  static int null_termination;
  static enum {
@@@ -142,14 -138,12 +142,14 @@@ static struct option builtin_commit_opt
        OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
        OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
        OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
 -      OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
 +      OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
 -      OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
 +      OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
        OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
 +      { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
 +        "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
        /* end commit message options */
  
        OPT_GROUP("Commit contents options"),
@@@ -196,16 -190,16 +196,16 @@@ static void determine_whence(struct wt_
  
  static const char *whence_s(void)
  {
 -      char *s = "";
 +      const char *s = "";
  
        switch (whence) {
        case FROM_COMMIT:
                break;
        case FROM_MERGE:
 -              s = "merge";
 +              s = _("merge");
                break;
        case FROM_CHERRY_PICK:
 -              s = "cherry-pick";
 +              s = _("cherry-pick");
                break;
        }
  
@@@ -261,9 -255,8 +261,9 @@@ static int list_paths(struct string_lis
        m = xcalloc(1, i);
  
        if (with_tree) {
 -              const char *max_prefix = pathspec_prefix(prefix, pattern);
 -              overlay_tree_on_cache(with_tree, max_prefix);
 +              char *max_prefix = common_prefix(pattern);
 +              overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix);
 +              free(max_prefix);
        }
  
        for (i = 0; i < active_nr; i++) {
@@@ -400,7 -393,6 +400,7 @@@ static char *prepare_index(int argc, co
                fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
                refresh_cache_or_die(refresh_flags);
 +              update_main_cache_tree(1);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
 +                      update_main_cache_tree(1);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
@@@ -543,6 -534,7 +543,7 @@@ static void determine_author_info(struc
  
        if (author_message) {
                const char *a, *lb, *rb, *eol;
+               size_t len;
  
                a = strstr(author_message_buffer, "\nauthor ");
                if (!a)
                                         (a + strlen("\nauthor "))));
                email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
                date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> ")));
+               len = eol - (rb + strlen("> "));
+               date = xmalloc(len + 2);
+               *date = '@';
+               memcpy(date + 1, rb + strlen("> "), len);
+               date[len + 1] = '\0';
        }
  
        if (force_author) {
@@@ -870,7 -867,10 +876,7 @@@ static int prepare_to_commit(const cha
         */
        discard_cache();
        read_cache_from(index_file);
 -      if (!active_cache_tree)
 -              active_cache_tree = cache_tree();
 -      if (cache_tree_update(active_cache_tree,
 -                            active_cache, active_nr, 0, 0) < 0) {
 +      if (update_main_cache_tree(0)) {
                error(_("Error building trees"));
                return 0;
        }
@@@ -1025,8 -1025,8 +1031,8 @@@ static int parse_and_validate_options(i
  
        if (logfile || message.len || use_message || fixup_message)
                use_editor = 0;
 -      if (edit_flag)
 -              use_editor = 1;
 +      if (0 <= edit_flag)
 +              use_editor = edit_flag;
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
  
@@@ -1264,7 -1264,7 +1270,7 @@@ static void print_summary(const char *p
        struct commit *commit;
        struct strbuf format = STRBUF_INIT;
        unsigned char junk_sha1[20];
 -      const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
 +      const char *head;
        struct pretty_print_context pctx = {0};
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
 +      head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
        printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
  static int git_commit_config(const char *k, const char *v, void *cb)
  {
        struct wt_status *s = cb;
 +      int status;
  
        if (!strcmp(k, "commit.template"))
                return git_config_pathname(&template_file, k, v);
                return 0;
        }
  
 +      status = git_gpg_config(k, v, NULL);
 +      if (status)
 +              return status;
        return git_status_config(k, v, s);
  }
  
@@@ -1392,7 -1387,6 +1398,7 @@@ int cmd_commit(int argc, const char **a
        int allow_fast_forward = 1;
        struct wt_status s;
        struct commit *current_head = NULL;
 +      struct commit_extra_header *extra = NULL;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
        if (get_sha1("HEAD", sha1))
                current_head = NULL;
        else {
 -              current_head = lookup_commit(sha1);
 +              current_head = lookup_commit_or_die(sha1, "HEAD");
                if (!current_head || parse_commit(current_head))
                        die(_("could not parse HEAD commit"));
        }
                        die_errno(_("could not open '%s' for reading"),
                                  git_path("MERGE_HEAD"));
                while (strbuf_getline(&m, fp, '\n') != EOF) {
 -                      unsigned char sha1[20];
 -                      if (get_sha1_hex(m.buf, sha1) < 0)
 +                      struct commit *parent;
 +
 +                      parent = get_merge_parent(m.buf);
 +                      if (!parent)
                                die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
 -                      pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
 +                      pptr = &commit_list_insert(parent, pptr)->next;
                }
                fclose(fp);
                strbuf_release(&m);
                exit(1);
        }
  
 -      if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
 -                      author_ident.buf)) {
 +      if (amend) {
 +              const char *exclude_gpgsig[2] = { "gpgsig", NULL };
 +              extra = read_commit_extra_headers(current_head, exclude_gpgsig);
 +      } else {
 +              struct commit_extra_header **tail = &extra;
 +              append_merge_tag_headers(parents, &tail);
 +      }
 +
 +      if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
 +                               author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
        strbuf_release(&author_ident);
 +      free_commit_extra_headers(extra);
  
        ref_lock = lock_any_ref_for_update("HEAD",
                                           !current_head
        }
  
        unlink(git_path("CHERRY_PICK_HEAD"));
 +      unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
diff --combined date.c
index 353e0a5e53f13c36549e65452931f254de3423c4,bf8e088e6aec6278ec4be054732e517ce3abaafc..a5055ca09dc1fafce2b9434c4fda02ad4f8e117f
--- 1/date.c
--- 2/date.c
+++ b/date.c
@@@ -552,35 -552,23 +552,35 @@@ static int match_digit(const char *date
  static int match_tz(const char *date, int *offp)
  {
        char *end;
 -      int offset = strtoul(date+1, &end, 10);
 -      int min, hour;
 -      int n = end - date - 1;
 +      int hour = strtoul(date + 1, &end, 10);
 +      int n = end - (date + 1);
 +      int min = 0;
  
 -      min = offset % 100;
 -      hour = offset / 100;
 +      if (n == 4) {
 +              /* hhmm */
 +              min = hour % 100;
 +              hour = hour / 100;
 +      } else if (n != 2) {
 +              min = 99; /* random crap */
 +      } else if (*end == ':') {
 +              /* hh:mm? */
 +              min = strtoul(end + 1, &end, 10);
 +              if (end - (date + 1) != 5)
 +                      min = 99; /* random crap */
 +      } /* otherwise we parsed "hh" */
  
        /*
 -       * Don't accept any random crap.. At least 3 digits, and
 -       * a valid minute. We might want to check that the minutes
 -       * are divisible by 30 or something too.
 +       * Don't accept any random crap. Even though some places have
 +       * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
 +       * UTC+14), there is something wrong if hour part is much
 +       * larger than that. We might also want to check that the
 +       * minutes are divisible by 15 or something too. (Offset of
 +       * Kathmandu, Nepal is UTC+5:45)
         */
 -      if (min < 60 && n > 2) {
 -              offset = hour*60+min;
 +      if (min < 60 && hour < 24) {
 +              int offset = hour * 60 + min;
                if (*date == '-')
                        offset = -offset;
 -
                *offp = offset;
        }
        return end - date;
@@@ -597,6 -585,33 +597,33 @@@ static int date_string(unsigned long da
        return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
  }
  
+ /*
+  * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+  * only when it appears not as part of any other string.
+  */
+ static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset)
+ {
+       char *end;
+       unsigned long stamp;
+       int ofs;
+       if (*date < '0' || '9' <= *date)
+               return -1;
+       stamp = strtoul(date, &end, 10);
+       if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+               return -1;
+       date = end + 2;
+       ofs = strtol(date, &end, 10);
+       if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+               return -1;
+       ofs = (ofs / 100) * 60 + (ofs % 100);
+       if (date[-1] == '-')
+               ofs = -ofs;
+       *timestamp = stamp;
+       *offset = ofs;
+       return 0;
+ }
  /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
     (i.e. English) day/month names, and it doesn't work correctly with %z. */
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
        *offset = -1;
        tm_gmt = 0;
  
+       if (*date == '@' &&
+           !match_object_header_date(date + 1, timestamp, offset))
+               return 0; /* success */
        for (;;) {
                int match = 0;
                unsigned char c = *date;
diff --combined git-sh-setup.sh
index 1fba6c2de0b78ddb5aa1495c9c519c26b48eebc2,015fe6e336314c4994b68b966d41b5c09f97d956..5d8e4e6c89f0471567a7aa1f07b054d8ac502e15
@@@ -90,7 -90,7 +90,7 @@@ $LONG_USAGE
        fi
  
        case "$1" in
 -              -h|--h|--he|--hel|--help)
 +              -h)
                echo "$LONG_USAGE"
                exit
        esac
@@@ -200,7 -200,7 +200,7 @@@ get_author_ident_from_commit () 
                s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
  
                g
-               s/^author [^<]* <[^>]*> \(.*\)$/\1/
+               s/^author [^<]* <[^>]*> \(.*\)$/@\1/
                s/.*/GIT_AUTHOR_DATE='\''&'\''/p
  
                q
diff --combined t/t3400-rebase.sh
index c3555332366d687d04bfbc031b1be808f3caa802,e26e14dd535d13ca1da1590461b07c0953d8dcd6..e647272a01f3ac89e1d08e6b1ffa36a3a543e655
@@@ -172,8 -172,8 +172,8 @@@ test_expect_success 'fail when upstrea
  
  test_expect_success 'default to @{upstream} when upstream arg is missing' '
        git checkout -b default topic &&
 -      git config branch.default.remote .
 -      git config branch.default.merge refs/heads/master
 +      git config branch.default.remote . &&
 +      git config branch.default.merge refs/heads/master &&
        git rebase &&
        test "$(git rev-parse default~1)" = "$(git rev-parse master)"
  '
@@@ -218,4 -218,27 +218,27 @@@ test_expect_success 'rebase -m can cop
        test "a note" = "$(git notes show HEAD)"
  '
  
+ test_expect_success 'rebase commit with an ancient timestamp' '
+       git reset --hard &&
+       >old.one && git add old.one && test_tick &&
+       git commit --date="@12345 +0400" -m "Old one" &&
+       >old.two && git add old.two && test_tick &&
+       git commit --date="@23456 +0500" -m "Old two" &&
+       >old.three && git add old.three && test_tick &&
+       git commit --date="@34567 +0600" -m "Old three" &&
+       git cat-file commit HEAD^^ >actual &&
+       grep "author .* 12345 +0400$" actual &&
+       git cat-file commit HEAD^ >actual &&
+       grep "author .* 23456 +0500$" actual &&
+       git cat-file commit HEAD >actual &&
+       grep "author .* 34567 +0600$" actual &&
+       git rebase --onto HEAD^^ HEAD^ &&
+       git cat-file commit HEAD >actual &&
+       grep "author .* 34567 +0600$" actual
+ '
  test_done