Merge branch 'np/verbose-push'
authorJunio C Hamano <junkio@cox.net>
Sun, 5 Nov 2006 01:22:14 +0000 (17:22 -0800)
committerJunio C Hamano <junkio@cox.net>
Sun, 5 Nov 2006 01:22:14 +0000 (17:22 -0800)
* np/verbose-push:
make git-push a bit more verbose

14 files changed:
Documentation/git-clone.txt
Documentation/git-diff-index.txt
Documentation/git-repo-config.txt
builtin-apply.c
builtin-branch.c
builtin-for-each-ref.c
builtin-pack-refs.c
builtin-push.c
builtin-repo-config.c
config.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
gitweb/gitweb.perl
sha1_file.c
index f973c64313699713100ba63822d80fd86979ff78..86060472ad88088bcb59df5eb9acce097f68cb83 100644 (file)
@@ -75,6 +75,7 @@ OPTIONS
        this option is used, neither the `origin` branch nor the
        default `remotes/origin` file is created.
 
+--origin <name>::
 -o <name>::
        Instead of using the branch name 'origin' to keep track
        of the upstream repository, use <name> instead.  Note
index 9cd43f105bd78778359ded7c6b3300502d642ca9..2df581c2c9e598c676fcb95c35c91209d15562ca 100644 (file)
@@ -54,7 +54,7 @@ If '--cached' is specified, it allows you to ask:
 
 For example, let's say that you have worked on your working directory, updated
 some files in the index and are ready to commit. You want to see exactly
-*what* you are going to commit is without having to write a new tree
+*what* you are going to commit, without having to write a new tree
 object and compare it that way, and to do that, you just do
 
        git-diff-index --cached HEAD
@@ -68,7 +68,7 @@ matches my working directory. But doing a "git-diff-index" does:
   -100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        commit.c
   +100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        git-commit.c
 
-You can trivially see that the above is a rename.
+You can see easily that the above is a rename.
 
 In fact, "git-diff-index --cached" *should* always be entirely equivalent to
 actually doing a "git-write-tree" and comparing that. Except this one is much
index 8a1ab61e943aba92aed6734c949b196850914a3a..8199615dde6630124c155bb4a296603dc8829c83 100644 (file)
@@ -3,19 +3,19 @@ git-repo-config(1)
 
 NAME
 ----
-git-repo-config - Get and set options in .git/config
+git-repo-config - Get and set repository or global options.
 
 
 SYNOPSIS
 --------
 [verse]
-'git-repo-config' [type] name [value [value_regex]]
-'git-repo-config' [type] --replace-all name [value [value_regex]]
-'git-repo-config' [type] --get name [value_regex]
-'git-repo-config' [type] --get-all name [value_regex]
-'git-repo-config' [type] --unset name [value_regex]
-'git-repo-config' [type] --unset-all name [value_regex]
-'git-repo-config' -l | --list
+'git-repo-config' [--global] [type] name [value [value_regex]]
+'git-repo-config' [--global] [type] --replace-all name [value [value_regex]]
+'git-repo-config' [--global] [type] --get name [value_regex]
+'git-repo-config' [--global] [type] --get-all name [value_regex]
+'git-repo-config' [--global] [type] --unset name [value_regex]
+'git-repo-config' [--global] [type] --unset-all name [value_regex]
+'git-repo-config' [--global] -l | --list
 
 DESCRIPTION
 -----------
@@ -41,8 +41,9 @@ This command will fail if:
 . Can not write to .git/config,
 . no section was provided,
 . the section or key is invalid,
-. you try to unset an option which does not exist, or
-. you try to unset/set an option for which multiple lines match.
+. you try to unset an option which does not exist,
+. you try to unset/set an option for which multiple lines match, or
+. you use --global option without $HOME being properly set.
 
 
 OPTIONS
@@ -64,14 +65,17 @@ OPTIONS
 --get-regexp::
        Like --get-all, but interprets the name as a regular expression.
 
+--global::
+       Use global ~/.gitconfig file rather than the repository .git/config.
+
 --unset::
-       Remove the line matching the key from .git/config.
+       Remove the line matching the key from config file.
 
 --unset-all::
-       Remove all matching lines from .git/config.
+       Remove all matching lines from config file.
 
 -l, --list::
-       List all variables set in .git/config.
+       List all variables set in config file.
 
 
 ENVIRONMENT
@@ -79,6 +83,7 @@ ENVIRONMENT
 
 GIT_CONFIG::
        Take the configuration from the given file instead of .git/config.
+       Using the "--global" option forces this to ~/.gitconfig.
 
 GIT_CONFIG_LOCAL::
        Currently the same as $GIT_CONFIG; when Git will support global
index 11397f5504f98ccff47a90b228abc71b30327fb9..db7cdce1d37e2984f4d2663bb86eaec7f33f5789 100644 (file)
@@ -1043,10 +1043,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                 * then not having oldlines means the patch is creation,
                 * and not having newlines means the patch is deletion.
                 */
-               if (patch->is_new < 0 && !oldlines)
+               if (patch->is_new < 0 && !oldlines) {
                        patch->is_new = 1;
-               if (patch->is_delete < 0 && !newlines)
+                       patch->old_name = NULL;
+               }
+               if (patch->is_delete < 0 && !newlines) {
                        patch->is_delete = 1;
+                       patch->new_name = NULL;
+               }
        }
 
        if (0 < patch->is_new && oldlines)
index e028a5390fae60674eb39b2fcf2cea9943bff920..368b68ec91f3d72d1f5e1d1dea503334207f864d 100644 (file)
@@ -103,6 +103,7 @@ static int ref_cmp(const void *r1, const void *r2)
 static void print_ref_list(int remote_only)
 {
        int i;
+       char c;
 
        if (remote_only)
                for_each_remote_ref(append_ref, NULL);
@@ -112,10 +113,11 @@ static void print_ref_list(int remote_only)
        qsort(ref_list, ref_index, sizeof(char *), ref_cmp);
 
        for (i = 0; i < ref_index; i++) {
+               c = ' ';
                if (!strcmp(ref_list[i], head))
-                       printf("* %s\n", ref_list[i]);
-               else
-                       printf("  %s\n", ref_list[i]);
+                       c = '*';
+
+               printf("%c %s\n", c, ref_list[i]);
        }
 }
 
index 93d3d7eef06315c598952761a7f2755a2f95be2c..173bf38735315a1c299f1f2e3db76a7795a2305c 100644 (file)
@@ -59,6 +59,8 @@ static struct {
        { "taggername" },
        { "taggeremail" },
        { "taggerdate", FIELD_TIME },
+       { "creator" },
+       { "creatordate", FIELD_TIME },
        { "subject" },
        { "body" },
        { "contents" },
@@ -401,6 +403,29 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
                else if (!strcmp(name + wholen, "date"))
                        grab_date(wholine, v);
        }
+
+       /* For a tag or a commit object, if "creator" or "creatordate" is
+        * requested, do something special.
+        */
+       if (strcmp(who, "tagger") && strcmp(who, "committer"))
+               return; /* "author" for commit object is not wanted */
+       if (!wholine)
+               wholine = find_wholine(who, wholen, buf, sz);
+       if (!wholine)
+               return;
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+
+               if (!strcmp(name, "creatordate"))
+                       grab_date(wholine, v);
+               else if (!strcmp(name, "creator"))
+                       v->s = copy_line(wholine);
+       }
 }
 
 static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
index 1087657674840820662cba686f525a69a68e689c..042d2718f904303600a57cfe8b5de83f5c0d34db 100644 (file)
@@ -12,6 +12,7 @@ struct ref_to_prune {
 
 struct pack_refs_cb_data {
        int prune;
+       int all;
        struct ref_to_prune *ref_to_prune;
        FILE *refs_file;
 };
@@ -29,6 +30,8 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
 {
        struct pack_refs_cb_data *cb = cb_data;
 
+       if (!cb->all && strncmp(path, "refs/tags/", 10))
+               return 0;
        /* Do not pack the symbolic refs */
        if (!(flags & REF_ISSYMREF))
                fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
@@ -68,7 +71,6 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
 {
        int fd, i;
        struct pack_refs_cb_data cbdata;
-       int (*iterate_ref)(each_ref_fn, void *) = for_each_tag_ref;
 
        memset(&cbdata, 0, sizeof(cbdata));
 
@@ -79,7 +81,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp(arg, "--all")) {
-                       iterate_ref = for_each_ref;
+                       cbdata.all = 1;
                        continue;
                }
                /* perhaps other parameters later... */
@@ -93,7 +95,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
        if (!cbdata.refs_file)
                die("unable to create ref-pack file structure (%s)",
                    strerror(errno));
-       iterate_ref(handle_one_ref, &cbdata);
+       for_each_ref(handle_one_ref, &cbdata);
        fflush(cbdata.refs_file);
        fsync(fd);
        fclose(cbdata.refs_file);
index 5f7eccf14b272fc108bcd49aece911ebde460b4f..d23974e708cb744f75efbb9f1acbf37606e92b05 100644 (file)
@@ -10,7 +10,7 @@
 
 static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]";
 
-static int all, tags, force, thin = 1;
+static int all, tags, force, thin = 1, verbose;
 static const char *execute;
 
 #define BUF_SIZE (2084)
@@ -248,6 +248,8 @@ static int do_push(const char *repo)
                while (dest_refspec_nr--)
                        argv[dest_argc++] = *dest_refspec++;
                argv[dest_argc] = NULL;
+               if (verbose)
+                       fprintf(stderr, "Pushing to %s\n", dest);
                err = run_command_v(argc, argv);
                if (!err)
                        continue;
@@ -281,6 +283,14 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        i++;
                        break;
                }
+               if (!strcmp(arg, "-v")) {
+                       verbose=1;
+                       continue;
+               }
+               if (!strncmp(arg, "--repo=", 7)) {
+                       repo = arg+7;
+                       continue;
+               }
                if (!strcmp(arg, "--all")) {
                        all = 1;
                        continue;
index f60cee1dc54497d8ba00069a56e14b431f6a0852..7b6e5725ae6d33350a9648e57139b646ab49696e 100644 (file)
@@ -3,7 +3,7 @@
 #include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
 
 static char *key;
 static regex_t *key_regexp;
@@ -139,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
                        type = T_BOOL;
                else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
                        return git_config(show_all_config);
-               else
+               else if (!strcmp(argv[1], "--global")) {
+                       char *home = getenv("HOME");
+                       if (home) {
+                               char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+                               setenv("GIT_CONFIG", user_config, 1);
+                               free(user_config);
+                       } else {
+                               die("$HOME not set");
+                       }
+               } else
                        break;
                argc--;
                argv++;
index e8f0caf7cf674ff44b23e0182420988e9672cc69..3cae3901aa9b50e8d04a8ce2633a1a1ef8ed0f6c 100644 (file)
--- a/config.c
+++ b/config.c
@@ -103,6 +103,11 @@ static char *parse_value(void)
        }
 }
 
+static inline int iskeychar(int c)
+{
+       return isalnum(c) || c == '-';
+}
+
 static int get_value(config_fn_t fn, char *name, unsigned int len)
 {
        int c;
@@ -113,7 +118,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
                c = get_next_char();
                if (c == EOF)
                        break;
-               if (!isalnum(c))
+               if (!iskeychar(c))
                        break;
                name[len++] = tolower(c);
                if (len >= MAXNAME)
@@ -181,7 +186,7 @@ static int get_base_var(char *name)
                        return baselen;
                if (isspace(c))
                        return get_extended_base_var(name, baselen, c);
-               if (!isalnum(c) && c != '.')
+               if (!iskeychar(c) && c != '.')
                        return -1;
                if (baselen > MAXNAME / 2)
                        return -1;
@@ -573,7 +578,7 @@ int git_config_set_multivar(const char* key, const char* value,
                        dot = 1;
                /* Leave the extended basename untouched.. */
                if (!dot || i > store.baselen) {
-                       if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+                       if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
                                fprintf(stderr, "invalid key: %s\n", key);
                                free(store.key);
                                ret = 1;
index b074f4fe5779da8907e112c8d5af1ea60f120be5..a3fbb9032c1849382f82a2d7f9591cbea6e0e645 100755 (executable)
@@ -271,6 +271,13 @@ _git_push ()
        esac
 }
 
+_git_reset ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local opt="--mixed --hard --soft"
+       COMPREPLY=($(compgen -W "$opt $(__git_refs .)" -- "$cur"))
+}
+
 _git_show ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -304,6 +311,7 @@ _git ()
                ls-tree)     _git_ls_tree ;;
                pull)        _git_pull ;;
                push)        _git_push ;;
+               reset)       _git_reset ;;
                show)        _git_show ;;
                show-branch) _git_log ;;
                whatchanged) _git_log ;;
@@ -332,6 +340,7 @@ complete -o default -o nospace -F _git_ls_tree git-ls-tree
 complete -o default            -F _git_merge_base git-merge-base
 complete -o default -o nospace -F _git_pull git-pull
 complete -o default -o nospace -F _git_push git-push
+complete -o default            -F _git_reset git-reset
 complete -o default            -F _git_show git-show
 complete -o default -o nospace -F _git_log git-whatchanged
 
@@ -339,6 +348,7 @@ complete -o default -o nospace -F _git_log git-whatchanged
 # when the user has tab-completed the executable name and consequently
 # included the '.exe' suffix.
 #
+complete -o default            -F _git_branch git-branch.exe
 complete -o default -o nospace -F _git_cat_file git-cat-file.exe
 complete -o default -o nospace -F _git_diff git-diff.exe
 complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
index 5354cd67b3dfa05d0018c03f15d0dcaf4e3df4a7..972c402ea0e79405ef7123dcdf748eccf9e6f3f7 100644 (file)
@@ -589,6 +589,7 @@ and returns the process output as a string."
                           (let ((commit (git-commit-tree buffer tree head)))
                             (git-update-ref "HEAD" commit head)
                             (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+                            (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
                             (with-current-buffer buffer (erase-buffer))
                             (git-set-files-state files 'uptodate)
                             (when (file-directory-p ".git/rr-cache")
@@ -670,6 +671,32 @@ and returns the process output as a string."
   (unless git-status (error "Not in git-status buffer."))
   (ewoc-goto-prev git-status n))
 
+(defun git-next-unmerged-file (&optional n)
+  "Move the selection down N unmerged files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((last (ewoc-locate git-status))
+         (node (ewoc-next git-status last)))
+    (while (and node (> n 0))
+      (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+        (setq n (1- n))
+        (setq last node))
+      (setq node (ewoc-next git-status node)))
+    (ewoc-goto-node git-status last)))
+
+(defun git-prev-unmerged-file (&optional n)
+  "Move the selection up N unmerged files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((last (ewoc-locate git-status))
+         (node (ewoc-prev git-status last)))
+    (while (and node (> n 0))
+      (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+        (setq n (1- n))
+        (setq last node))
+      (setq node (ewoc-prev git-status node)))
+    (ewoc-goto-node git-status last)))
+
 (defun git-add-file ()
   "Add marked file(s) to the index cache."
   (interactive)
@@ -862,7 +889,7 @@ and returns the process output as a string."
           'face 'git-header-face)
          (propertize git-log-msg-separator 'face 'git-separator-face)
          "\n")
-        (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+        (cond ((file-readable-p ".git/MERGE_MSG")
                (insert-file-contents ".git/MERGE_MSG"))
               (sign-off
                (insert (format "\n\nSigned-off-by: %s <%s>\n"
@@ -873,7 +900,8 @@ and returns the process output as a string."
               (2 font-lock-function-name-face))
              (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
               (1 font-lock-comment-face)))))
-      (log-edit #'git-do-commit nil #'git-log-edit-files buffer))))
+      (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
+      (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
 
 (defun git-find-file ()
   "Visit the current file in its own buffer."
@@ -884,6 +912,15 @@ and returns the process output as a string."
     (when (eq 'unmerged (git-fileinfo->state info))
       (smerge-mode))))
 
+(defun git-find-file-other-window ()
+  "Visit the current file in its own buffer in another window."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (find-file-other-window (git-fileinfo->name info))
+    (when (eq 'unmerged (git-fileinfo->state info))
+      (smerge-mode))))
+
 (defun git-find-file-imerge ()
   "Visit the current file in interactive merge mode."
   (interactive)
@@ -967,7 +1004,10 @@ and returns the process output as a string."
     (define-key map "m"   'git-mark-file)
     (define-key map "M"   'git-mark-all)
     (define-key map "n"   'git-next-file)
+    (define-key map "N"   'git-next-unmerged-file)
+    (define-key map "o"   'git-find-file-other-window)
     (define-key map "p"   'git-prev-file)
+    (define-key map "P"   'git-prev-unmerged-file)
     (define-key map "q"   'git-status-quit)
     (define-key map "r"   'git-remove-file)
     (define-key map "R"   'git-resolve-file)
index bf5f8299442ffe3ad8c4de6629af79667a67e5e2..3c6fd7ca47551eb808a40ca2cc7ed1690f19cc06 100755 (executable)
        'pathinfo' => {
                'override' => 0,
                'default' => [0]},
+
+       # Make gitweb consider projects in project root subdirectories
+       # to be forks of existing projects. Given project $projname.git,
+       # projects matching $projname/*.git will not be shown in the main
+       # projects list, instead a '+' mark will be added to $projname
+       # there and a 'forks' view will be enabled for the project, listing
+       # all the forks. This feature is supported only if project list
+       # is taken from a directory, not file.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'forks'}{'default'} = [1];
+       # Project specific override is not supported.
+       'forks' => {
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_check_feature {
@@ -405,6 +420,7 @@ sub evaluate_path_info {
        "commitdiff" => \&git_commitdiff,
        "commitdiff_plain" => \&git_commitdiff_plain,
        "commit" => \&git_commit,
+       "forks" => \&git_forks,
        "heads" => \&git_heads,
        "history" => \&git_history,
        "log" => \&git_log,
@@ -554,12 +570,17 @@ sub esc_url {
 }
 
 # replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html {
+sub esc_html ($;%) {
        my $str = shift;
+       my %opts = @_;
+
        $str = to_utf8($str);
        $str = escapeHTML($str);
        $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
        $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1)
+       if ($opts{'-nbsp'}) {
+               $str =~ s/ /&nbsp;/g;
+       }
        return $str;
 }
 
@@ -784,7 +805,7 @@ sub format_diff_line {
                $diff_class = " incomplete";
        }
        $line = untabify($line);
-       return "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
+       return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
 }
 
 ## ----------------------------------------------------------------------
@@ -892,13 +913,21 @@ sub git_get_project_url_list {
 }
 
 sub git_get_projects_list {
+       my ($filter) = @_;
        my @list;
 
+       $filter ||= '';
+       $filter =~ s/\.git$//;
+
        if (-d $projects_list) {
                # search in directory
-               my $dir = $projects_list;
+               my $dir = $projects_list . ($filter ? "/$filter" : '');
+               # remove the trailing "/"
+               $dir =~ s!/+$!!;
                my $pfxlen = length("$dir");
 
+               my $check_forks = gitweb_check_feature('forks');
+
                File::Find::find({
                        follow_fast => 1, # follow symbolic links
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
@@ -910,8 +939,10 @@ sub git_get_projects_list {
 
                                my $subdir = substr($File::Find::name, $pfxlen + 1);
                                # we check related file in $projectroot
-                               if (check_export_ok("$projectroot/$subdir")) {
-                                       push @list, { path => $subdir };
+                               if ($check_forks and $subdir =~ m#/.#) {
+                                       $File::Find::prune = 1;
+                               } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
+                                       push @list, { path => ($filter ? "$filter/" : '') . $subdir };
                                        $File::Find::prune = 1;
                                }
                        },
@@ -1294,47 +1325,88 @@ ($;%)
 ## ......................................................................
 ## parse to array of hashes functions
 
-sub git_get_refs_list {
-       my $type = shift || "";
-       my %refs;
-       my @reflist;
+sub git_get_heads_list {
+       my $limit = shift;
+       my @headslist;
 
-       my @refs;
-       open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+       open my $fd, '-|', git_cmd(), 'for-each-ref',
+               ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+               '--format=%(objectname) %(refname) %(subject)%00%(committer)',
+               'refs/heads'
                or return;
        while (my $line = <$fd>) {
-               chomp $line;
-               if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) {
-                       if (defined $refs{$1}) {
-                               push @{$refs{$1}}, $2;
-                       } else {
-                               $refs{$1} = [ $2 ];
-                       }
+               my %ref_item;
 
-                       if (! $4) { # unpeeled, direct reference
-                               push @refs, { hash => $1, name => $3 }; # without type
-                       } elsif ($3 eq $refs[-1]{'name'}) {
-                               # most likely a tag is followed by its peeled
-                               # (deref) one, and when that happens we know the
-                               # previous one was of type 'tag'.
-                               $refs[-1]{'type'} = "tag";
-                       }
+               chomp $line;
+               my ($refinfo, $committerinfo) = split(/\0/, $line);
+               my ($hash, $name, $title) = split(' ', $refinfo, 3);
+               my ($committer, $epoch, $tz) =
+                       ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
+               $name =~ s!^refs/heads/!!;
+
+               $ref_item{'name'}  = $name;
+               $ref_item{'id'}    = $hash;
+               $ref_item{'title'} = $title || '(no commit message)';
+               $ref_item{'epoch'} = $epoch;
+               if ($epoch) {
+                       $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+               } else {
+                       $ref_item{'age'} = "unknown";
                }
+
+               push @headslist, \%ref_item;
        }
        close $fd;
 
-       foreach my $ref (@refs) {
-               my $ref_file = $ref->{'name'};
-               my $ref_id   = $ref->{'hash'};
+       return wantarray ? @headslist : \@headslist;
+}
+
+sub git_get_tags_list {
+       my $limit = shift;
+       my @tagslist;
+
+       open my $fd, '-|', git_cmd(), 'for-each-ref',
+               ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
+               '--format=%(objectname) %(objecttype) %(refname) '.
+               '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
+               'refs/tags'
+               or return;
+       while (my $line = <$fd>) {
+               my %ref_item;
+
+               chomp $line;
+               my ($refinfo, $creatorinfo) = split(/\0/, $line);
+               my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
+               my ($creator, $epoch, $tz) =
+                       ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
+               $name =~ s!^refs/tags/!!;
+
+               $ref_item{'type'} = $type;
+               $ref_item{'id'} = $id;
+               $ref_item{'name'} = $name;
+               if ($type eq "tag") {
+                       $ref_item{'subject'} = $title;
+                       $ref_item{'reftype'} = $reftype;
+                       $ref_item{'refid'}   = $refid;
+               } else {
+                       $ref_item{'reftype'} = $type;
+                       $ref_item{'refid'}   = $id;
+               }
 
-               my $type = $ref->{'type'} || git_get_type($ref_id) || next;
-               my %ref_item = parse_ref($ref_file, $ref_id, $type);
+               if ($type eq "tag" || $type eq "commit") {
+                       $ref_item{'epoch'} = $epoch;
+                       if ($epoch) {
+                               $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+                       } else {
+                               $ref_item{'age'} = "unknown";
+                       }
+               }
 
-               push @reflist, \%ref_item;
+               push @tagslist, \%ref_item;
        }
-       # sort refs by age
-       @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
-       return (\@reflist, \%refs);
+       close $fd;
+
+       return wantarray ? @tagslist : \@tagslist;
 }
 
 ## ----------------------------------------------------------------------
@@ -1945,19 +2017,19 @@ sub git_difftree_body {
                        print "</td>\n";
                        print "<td>$mode_chnge</td>\n";
                        print "<td class=\"link\">";
-                       if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
-                               if ($action eq 'commitdiff') {
-                                       # link to patch
-                                       $patchno++;
-                                       print $cgi->a({-href => "#patch$patchno"}, "patch");
-                               } else {
-                                       print $cgi->a({-href => href(action=>"blobdiff",
-                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                    hash_base=>$hash, hash_parent_base=>$parent,
-                                                                    file_name=>$diff{'file'})},
-                                                     "diff");
-                               }
-                               print " | ";
+                       if ($action eq 'commitdiff') {
+                               # link to patch
+                               $patchno++;
+                               print $cgi->a({-href => "#patch$patchno"}, "patch") .
+                                     " | ";
+                       } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+                               # "commit" view and modified file (not onlu mode changed)
+                               print $cgi->a({-href => href(action=>"blobdiff",
+                                                            hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                            hash_base=>$hash, hash_parent_base=>$parent,
+                                                            file_name=>$diff{'file'})},
+                                             "diff") .
+                                     " | ";
                        }
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
                                                     hash_base=>$hash, file_name=>$diff{'file'})},
@@ -1988,19 +2060,19 @@ sub git_difftree_body {
                                      -class => "list"}, esc_html($diff{'from_file'})) .
                              " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
                              "<td class=\"link\">";
-                       if ($diff{'to_id'} ne $diff{'from_id'}) {
-                               if ($action eq 'commitdiff') {
-                                       # link to patch
-                                       $patchno++;
-                                       print $cgi->a({-href => "#patch$patchno"}, "patch");
-                               } else {
-                                       print $cgi->a({-href => href(action=>"blobdiff",
-                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                    hash_base=>$hash, hash_parent_base=>$parent,
-                                                                    file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
-                                                     "diff");
-                               }
-                               print " | ";
+                       if ($action eq 'commitdiff') {
+                               # link to patch
+                               $patchno++;
+                               print $cgi->a({-href => "#patch$patchno"}, "patch") .
+                                     " | ";
+                       } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+                               # "commit" view and modified file (not only pure rename or copy)
+                               print $cgi->a({-href => href(action=>"blobdiff",
+                                                            hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                            hash_base=>$hash, hash_parent_base=>$parent,
+                                                            file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+                                             "diff") .
+                                     " | ";
                        }
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
                                                     hash_base=>$parent, file_name=>$diff{'from_file'})},
@@ -2051,13 +2123,6 @@ sub git_patchset_body {
                        }
                        $patch_idx++;
 
-                       # for now, no extended header, hence we skip empty patches
-                       # companion to  next LINE if $in_header;
-                       if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change
-                               $in_header = 1;
-                               next LINE;
-                       }
-
                        if ($diffinfo->{'status'} eq "A") { # added
                                print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" .
                                      $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
@@ -2144,6 +2209,124 @@ sub git_patchset_body {
 
 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
+sub git_project_list_body {
+       my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+
+       my $check_forks = gitweb_check_feature('forks');
+
+       my @projects;
+       foreach my $pr (@$projlist) {
+               my (@aa) = git_get_last_activity($pr->{'path'});
+               unless (@aa) {
+                       next;
+               }
+               ($pr->{'age'}, $pr->{'age_string'}) = @aa;
+               if (!defined $pr->{'descr'}) {
+                       my $descr = git_get_project_description($pr->{'path'}) || "";
+                       $pr->{'descr'} = chop_str($descr, 25, 5);
+               }
+               if (!defined $pr->{'owner'}) {
+                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+               }
+               if ($check_forks) {
+                       my $pname = $pr->{'path'};
+                       $pname =~ s/\.git$//;
+                       $pr->{'forks'} = -d "$projectroot/$pname";
+               }
+               push @projects, $pr;
+       }
+
+       $order ||= "project";
+       $from = 0 unless defined $from;
+       $to = $#projects if (!defined $to || $#projects < $to);
+
+       print "<table class=\"project_list\">\n";
+       unless ($no_header) {
+               print "<tr>\n";
+               if ($check_forks) {
+                       print "<th></th>\n";
+               }
+               if ($order eq "project") {
+                       @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+                       print "<th>Project</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'project'),
+                                      -class => "header"}, "Project") .
+                             "</th>\n";
+               }
+               if ($order eq "descr") {
+                       @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+                       print "<th>Description</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'descr'),
+                                      -class => "header"}, "Description") .
+                             "</th>\n";
+               }
+               if ($order eq "owner") {
+                       @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+                       print "<th>Owner</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'owner'),
+                                      -class => "header"}, "Owner") .
+                             "</th>\n";
+               }
+               if ($order eq "age") {
+                       @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
+                       print "<th>Last Change</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'age'),
+                                      -class => "header"}, "Last Change") .
+                             "</th>\n";
+               }
+               print "<th></th>\n" .
+                     "</tr>\n";
+       }
+       my $alternate = 1;
+       for (my $i = $from; $i <= $to; $i++) {
+               my $pr = $projects[$i];
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+               if ($check_forks) {
+                       print "<td>";
+                       if ($pr->{'forks'}) {
+                               print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+                       }
+                       print "</td>\n";
+               }
+               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+                                       -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+                     "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+                     "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
+               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+                     $pr->{'age_string'} . "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
+                     $cgi->a({-href => '/git-browser/by-commit.html?r='.$pr->{'path'}}, "graphiclog") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
+                     ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
+                     "</td>\n" .
+                     "</tr>\n";
+       }
+       if (defined $extra) {
+               print "<tr>\n";
+               if ($check_forks) {
+                       print "<td></td>\n";
+               }
+               print "<td colspan=\"5\">$extra</td>\n" .
+                     "</tr>\n";
+       }
+       print "</table>\n";
+}
+
 sub git_shortlog_body {
        # uses global variable $project
        my ($revlist, $from, $to, $refs, $extra) = @_;
@@ -2264,8 +2447,7 @@ sub git_tags_body {
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $taglist->[$i];
                my %tag = %$entry;
-               my $comment_lines = $tag{'comment'};
-               my $comment = shift @$comment_lines;
+               my $comment = $tag{'subject'};
                my $comment_short;
                if (defined $comment) {
                        $comment_short = chop_str($comment, 30, 5);
@@ -2298,7 +2480,7 @@ sub git_tags_body {
                      $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
                if ($tag{'reftype'} eq "commit") {
                        print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") .
-                             " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log");
+                             " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log");
                } elsif ($tag{'reftype'} eq "blob") {
                        print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
                }
@@ -2323,23 +2505,23 @@ sub git_heads_body {
        my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $headlist->[$i];
-               my %tag = %$entry;
-               my $curr = $tag{'id'} eq $head;
+               my %ref = %$entry;
+               my $curr = $ref{'id'} eq $head;
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               print "<td><i>$tag{'age'}</i></td>\n" .
-                     ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") .
-                     $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}),
-                              -class => "list name"},esc_html($tag{'name'})) .
+               print "<td><i>$ref{'age'}</i></td>\n" .
+                     ($curr ? "<td class=\"current_head\">" : "<td>") .
+                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}),
+                              -class => "list name"},esc_html($ref{'name'})) .
                      "</td>\n" .
                      "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " .
-                     $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " .
-                     $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") .
+                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " .
+                     $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") .
                      "</td>\n" .
                      "</tr>";
        }
@@ -2362,25 +2544,9 @@ sub git_project_list {
        }
 
        my @list = git_get_projects_list();
-       my @projects;
        if (!@list) {
                die_error(undef, "No projects found");
        }
-       foreach my $pr (@list) {
-               my (@aa) = git_get_last_activity($pr->{'path'});
-               unless (@aa) {
-                       next;
-               }
-               ($pr->{'age'}, $pr->{'age_string'}) = @aa;
-               if (!defined $pr->{'descr'}) {
-                       my $descr = git_get_project_description($pr->{'path'}) || "";
-                       $pr->{'descr'} = chop_str($descr, 25, 5);
-               }
-               if (!defined $pr->{'owner'}) {
-                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
-               }
-               push @projects, $pr;
-       }
 
        git_header_html();
        if (-f $home_text) {
@@ -2390,75 +2556,30 @@ sub git_project_list {
                close $fd;
                print "</div>\n";
        }
-       print "<table class=\"project_list\">\n" .
-             "<tr>\n";
-       $order ||= "project";
-       if ($order eq "project") {
-               @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
-               print "<th>Project</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'project'),
-                              -class => "header"}, "Project") .
-                     "</th>\n";
-       }
-       if ($order eq "descr") {
-               @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
-               print "<th>Description</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'descr'),
-                              -class => "header"}, "Description") .
-                     "</th>\n";
-       }
-       if ($order eq "owner") {
-               @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
-               print "<th>Owner</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'owner'),
-                              -class => "header"}, "Owner") .
-                     "</th>\n";
-       }
-       if ($order eq "age") {
-               @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
-               print "<th>Last Change</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'age'),
-                              -class => "header"}, "Last Change") .
-                     "</th>\n";
+       git_project_list_body(\@list, $order);
+       git_footer_html();
+}
+
+sub git_forks {
+       my $order = $cgi->param('o');
+       if (defined $order && $order !~ m/project|descr|owner|age/) {
+               die_error(undef, "Unknown order parameter");
        }
-       print "<th></th>\n" .
-             "</tr>\n";
-       my $alternate = 1;
-       foreach my $pr (@projects) {
-               if ($alternate) {
-                       print "<tr class=\"dark\">\n";
-               } else {
-                       print "<tr class=\"light\">\n";
-               }
-               $alternate ^= 1;
-               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-                                       -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-                     "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
-                     "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
-               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
-                     $pr->{'age_string'} . "</td>\n" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
-                     "</td>\n" .
-                     "</tr>\n";
+
+       my @list = git_get_projects_list($project);
+       if (!@list) {
+               die_error(undef, "No forks found");
        }
-       print "</table>\n";
+
+       git_header_html();
+       git_print_page_nav('','');
+       git_print_header_div('summary', "$project forks");
+       git_project_list_body(\@list, $order);
        git_footer_html();
 }
 
 sub git_project_index {
-       my @projects = git_get_projects_list();
+       my @projects = git_get_projects_list($project);
 
        print $cgi->header(
                -type => 'text/plain',
@@ -2489,17 +2610,12 @@ sub git_summary {
 
        my $owner = git_get_project_owner($project);
 
-       my ($reflist, $refs) = git_get_refs_list();
-
-       my @taglist;
-       my @headlist;
-       foreach my $ref (@$reflist) {
-               if ($ref->{'name'} =~ s!^heads/!!) {
-                       push @headlist, $ref;
-               } else {
-                       $ref->{'name'} =~ s!^tags/!!;
-                       push @taglist, $ref;
-               }
+       my $refs = git_get_references();
+       my @taglist  = git_get_tags_list(15);
+       my @headlist = git_get_heads_list(15);
+       my @forklist;
+       if (gitweb_check_feature('forks')) {
+               @forklist = git_get_projects_list($project);
        }
 
        git_header_html();
@@ -2551,6 +2667,13 @@ sub git_summary {
                               $cgi->a({-href => href(action=>"heads")}, "..."));
        }
 
+       if (@forklist) {
+               git_print_header_div('forks');
+               git_project_list_body(\@forklist, undef, 0, 15,
+                                     $cgi->a({-href => href(action=>"forks")}, "..."),
+                                     'noheader');
+       }
+
        git_footer_html();
 }
 
@@ -2792,9 +2915,9 @@ sub git_tags {
        git_print_page_nav('','', $head,undef,$head);
        git_print_header_div('summary', $project);
 
-       my ($taglist) = git_get_refs_list("tags");
-       if (@$taglist) {
-               git_tags_body($taglist);
+       my @tagslist = git_get_tags_list();
+       if (@tagslist) {
+               git_tags_body(\@tagslist);
        }
        git_footer_html();
 }
@@ -2805,9 +2928,9 @@ sub git_heads {
        git_print_page_nav('','', $head,undef,$head);
        git_print_header_div('summary', $project);
 
-       my ($headlist) = git_get_refs_list("heads");
-       if (@$headlist) {
-               git_heads_body($headlist, $head);
+       my @headslist = git_get_heads_list();
+       if (@headslist) {
+               git_heads_body(\@headslist, $head);
        }
        git_footer_html();
 }
@@ -2920,7 +3043,7 @@ sub git_blob {
                $nr++;
                $line = untabify($line);
                printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                      $nr, $nr, $nr, esc_html($line);
+                      $nr, $nr, $nr, esc_html($line, -nbsp=>1);
        }
        close $fd
                or print "Reading blob failed.\n";
index 570706919aeae6ba6d5c12df3babf110be6d76a9..4e5ee1054f6a4f5d1f50c8f93a3f45d6a6b1c72a 100644 (file)
@@ -1399,8 +1399,7 @@ static int link_temp_to_file(const char *tmpfile, const char *filename)
        dir = strrchr(filename, '/');
        if (dir) {
                *dir = 0;
-               mkdir(filename, 0777);
-               if (adjust_shared_perm(filename)) {
+               if (!mkdir(filename, 0777) && adjust_shared_perm(filename)) {
                        *dir = '/';
                        return -2;
                }