Merge branch 'sp/checkout'
authorJunio C Hamano <junkio@cox.net>
Tue, 7 Mar 2006 04:58:17 +0000 (20:58 -0800)
committerJunio C Hamano <junkio@cox.net>
Tue, 7 Mar 2006 04:58:17 +0000 (20:58 -0800)
* sp/checkout:
  Add --temp and --stage=all options to checkout-index.

33 files changed:
.gitignore
Documentation/Makefile
Documentation/asciidoc.conf
Documentation/git-add.txt
Documentation/git-archimport.txt
Documentation/git-cvsserver.txt
Documentation/git-fetch-pack.txt
Documentation/git-pack-redundant.txt
Documentation/git-rm.txt
Documentation/git-shell.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-update-ref.txt
Documentation/git-var.txt
blame.c
contrib/emacs/.gitignore [new file with mode: 0644]
contrib/emacs/Makefile [new file with mode: 0644]
exec_cmd.c
exec_cmd.h
git-annotate.perl
git-format-patch.sh
git.c
receive-pack.c
revision.c
run-command.c
run-command.h
send-pack.c
shell.c
t/.gitignore [new file with mode: 0644]
t/annotate-tests.sh [new file with mode: 0644]
t/t8001-annotate.sh
t/t8002-blame.sh [new file with mode: 0755]
upload-pack.c

index 5be239a..abbc509 100644 (file)
@@ -130,3 +130,4 @@ libgit.a
 *.o
 *.py[co]
 config.mak
+git-blame
index a3bca86..f4cbf7e 100644 (file)
@@ -1,4 +1,7 @@
-MAN1_TXT=$(wildcard git-*.txt) gitk.txt
+MAN1_TXT= \
+       $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
+               $(wildcard git-*.txt)) \
+       gitk.txt
 MAN7_TXT=git.txt
 
 DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN7_TXT))
@@ -11,6 +14,7 @@ ARTICLES += howto-index
 ARTICLES += repository-layout
 ARTICLES += hooks
 ARTICLES += everyday
+ARTICLES += git-tools
 # with their own formatting rules.
 SP_ARTICLES = glossary howto/revert-branch-rebase
 
index fa0877d..7ce7151 100644 (file)
@@ -18,6 +18,16 @@ ifdef::backend-docbook[]
 {0#</citerefentry>}
 endif::backend-docbook[]
 
+ifdef::backend-docbook[]
+# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
+[listingblock]
+<example><title>{title}</title>
+<literallayout>
+|
+</literallayout>
+{title#}</example>
+endif::backend-docbook[]
+
 ifdef::backend-xhtml11[]
 [gitlink-inlinemacro]
 <a href="{target}.html">{target}{0?({0})}</a>
index 7e29383..5b7c354 100644 (file)
@@ -65,6 +65,9 @@ git-add git-*.sh::
        (i.e. you are listing the files explicitly), it does not
        add `subdir/git-foo.sh` to the index.
 
+See Also
+--------
+gitlink:git-rm[1]
 
 Author
 ------
index 023d3ae..5a13187 100644 (file)
@@ -9,7 +9,7 @@ git-archimport - Import an Arch repository into git
 SYNOPSIS
 --------
 [verse]
-`git-archimport` [-h] [-v] [-o] [-a] [-f] [-T] [-D depth] [-t tempdir]
+'git-archimport' [-h] [-v] [-o] [-a] [-f] [-T] [-D depth] [-t tempdir]
                <archive/branch> [ <archive/branch> ]
 
 DESCRIPTION
index 19c9c51..4dc13c3 100644 (file)
@@ -5,14 +5,12 @@ NAME
 ----
 git-cvsserver - A CVS server emulator for git
 
-
 SYNOPSIS
 --------
 [verse]
 export CVS_SERVER=git-cvsserver
 'cvs' -d :ext:user@server/path/repo.git co <HEAD_name>
 
-
 DESCRIPTION
 -----------
 
@@ -27,48 +25,85 @@ plugin. Most functionality works fine with both of these clients.
 
 LIMITATIONS
 -----------
-Currently gitcvs only works over ssh connections.
 
+Currently cvsserver works over SSH connections for read/write clients, and
+over pserver for anonymous CVS access.
+
+CVS clients cannot tag, branch or perform GIT merges.
 
 INSTALLATION
 ------------
-1. Put server.pl somewhere useful on the same machine that is hosting your git repos
+
+1. If you are going to offer anonymous CVS access via pserver, add a line in
+   /etc/inetd.conf like
+
+   cvspserver stream tcp nowait nobody git-cvsserver pserver
+
+   Note: In some cases, you need to pass the 'pserver' argument twice for
+   git-cvsserver to see it. So the line would look like
+
+   cvspserver stream tcp nowait nobody git-cvsserver pserver pserver
+
+   No special setup is needed for SSH access, other than having GIT tools
+   in the PATH. If you have clients that do not accept the CVS_SERVER
+   env variable, you can rename git-cvsserver to cvs.
 
 2. For each repo that you want accessible from CVS you need to edit config in
    the repo and add the following section.
 
    [gitcvs]
         enabled=1
+        # optional for debugging
         logfile=/path/to/logfile
 
-   n.b. you need to ensure each user that is going to invoke server.pl has
-   write access to the log file.
+   Note: you need to ensure each user that is going to invoke git-cvsserver has
+   write access to the log file and to the git repository. When offering anon
+   access via pserver, this means that the nobody user should have write access
+   to at least the sqlite database at the root of the repository.
+
+3. On the client machine you need to set the following variables.
+   CVSROOT should be set as per normal, but the directory should point at the
+   appropriate git repo. For example:
+
+   For SSH access, CVS_SERVER should be set to git-cvsserver
+
+   Example:
 
-5. On each client machine you need to set the following variables.
-     CVSROOT should be set as per normal, but the directory should point at the
-             appropriate git repo.
-     CVS_SERVER should be set to the server.pl script that has been put on the
-                remote machine.
+     export CVSROOT=:ext:user@server:/var/git/project.git
+     export CVS_SERVER=git-cvsserver
 
-6. Clients should now be able to check out modules (where modules are the names
-   of branches in git).
-     $ cvs co -d mylocaldir master
+4. For SSH clients that will make commits, make sure their .bashrc file
+   sets the GIT_AUTHOR and GIT_COMMITTER variables.
+
+5. Clients should now be able to check out the project. Use the CVS 'module'
+   name to indicate what GIT 'head' you want to check out. Example:
+
+     cvs co -d project-master master
 
 Eclipse CVS Client Notes
 ------------------------
 
 To get a checkout with the Eclipse CVS client:
 
-1. Create a new project from CVS checkout, giving it repository and module
-2. Context Menu->Team->Share Project...
-3. Enter the repository and module information again and click Finish
-4. The Synchronize view appears. Untick  "launch commit wizard" to avoid
-committing the .project file, and select HEAD as the tag to synchronize to.
-Update all incoming changes.
-
-Note that most versions of Eclipse ignore CVS_SERVER (which you can set in
-the Preferences->Team->CVS->ExtConnection pane), so you may have to
-rename, alias or symlink git-cvsserver to 'cvs' on the server.
+1. Select "Create a new project -> From CVS checkout"
+2. Create a new location. See the notes below for details on how to choose the
+   right protocol.
+3. Browse the 'modules' available. It will give you a list of the heads in
+   the repository. You will not be able to browse the tree from there. Only
+   the heads.
+4. Pick 'HEAD' when it asks what branch/tag to check out. Untick the
+   "launch commit wizard" to avoid committing the .project file.
+
+Protocol notes: If you are using anonymous acces via pserver, just select that.
+Those using SSH access should choose the 'ext' protocol, and configure 'ext'
+access on the Preferences->Team->CVS->ExtConnection pane. Set CVS_SERVER to
+'git-cvsserver'. Not that password support is not good when using 'ext',
+you will definitely want to have SSH keys setup.
+
+Alternatively, you can just use the non-standard extssh protocol that Eclipse
+offer. In that case CVS_SERVER is ignored, and you will have to replace
+the cvs utility on the server with git-cvsserver or manipulate your .bashrc
+so that calling 'cvs' effectively calls git-cvsserver.
 
 Clients known to work
 ---------------------
@@ -106,7 +141,7 @@ Authors: Martyn Smith    <martyn@catalyst.net.nz>
 
 Documentation
 --------------
-Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz>Matthias Urlichs <smurf@smurf.noris.de>.
+Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz> Matthias Urlichs <smurf@smurf.noris.de>.
 
 GIT
 ---
index b507e9b..913b75b 100644 (file)
@@ -8,7 +8,7 @@ git-fetch-pack - Receive missing objects from another repository.
 
 SYNOPSIS
 --------
-git-fetch-pack [-q] [-k] [--exec=<git-upload-pack>] [<host>:]<directory> [<refs>...]
+'git-fetch-pack' [-q] [-k] [--exec=<git-upload-pack>] [<host>:]<directory> [<refs>...]
 
 DESCRIPTION
 -----------
index 2f4cc46..a81cb97 100644 (file)
@@ -8,7 +8,7 @@ git-pack-redundant - Program used to find redundant pack files.
 
 SYNOPSIS
 --------
-'git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >'
+'git-pack-redundant' [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >
 
 DESCRIPTION
 -----------
index 401bfb2..d8a5afa 100644 (file)
@@ -74,6 +74,9 @@ git-rm -f git-*.sh::
        shell expand the asterisk (i.e. you are listing the files
        explicitly), it does not remove `subdir/git-foo.sh`.
 
+See Also
+--------
+gitlink:git-add[1]
 
 Author
 ------
index 3f4d804..cc4266d 100644 (file)
@@ -8,7 +8,7 @@ git-shell - Restricted login shell for GIT over SSH only
 
 SYNOPSIS
 --------
-'git-shell -c <command> <argument>'
+'git-shell' -c <command> <argument>
 
 DESCRIPTION
 -----------
index 65ca77f..bf8db8b 100644 (file)
@@ -8,7 +8,7 @@ git-shortlog - Summarize 'git log' output.
 
 SYNOPSIS
 --------
-'git-log --pretty=short | git shortlog'
+git-log --pretty=short | 'git-shortlog'
 
 DESCRIPTION
 -----------
index 7b1a9c9..e474cd0 100644 (file)
@@ -8,9 +8,9 @@ git-show-branch - Show branches and their commits.
 SYNOPSIS
 --------
 [verse]
-git-show-branch [--all] [--heads] [--tags] [--topo-order] [--current]
-       [--more=<n> | --list | --independent | --merge-base]
-       [--no-name | --sha1-name] [<rev> | <glob>]...
+'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
+               [--more=<n> | --list | --independent | --merge-base]
+               [--no-name | --sha1-name] [<rev> | <glob>]...
 
 DESCRIPTION
 -----------
index 69715aa..475237f 100644 (file)
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-`git-update-ref` <ref> <newvalue> [<oldvalue>]
+'git-update-ref' <ref> <newvalue> [<oldvalue>]
 
 DESCRIPTION
 -----------
index c22d34f..90cb157 100644 (file)
@@ -8,7 +8,7 @@ git-var - Print the git users identity
 
 SYNOPSIS
 --------
-git-var [ -l | <variable> ]
+'git-var' [ -l | <variable> ]
 
 DESCRIPTION
 -----------
diff --git a/blame.c b/blame.c
index 7308c36..90338af 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -5,6 +5,7 @@
 #include <assert.h>
 #include <time.h>
 #include <sys/time.h>
+#include <math.h>
 
 #include "cache.h"
 #include "refs.h"
 
 #define DEBUG 0
 
-struct commit **blame_lines;
-int num_blame_lines;
+static const char blame_usage[] = "[-c] [-l] [--] file [commit]\n"
+       "  -c, --compability Use the same output mode as git-annotate (Default: off)\n"
+       "  -l, --long        Show long commit SHA1 (Default: off)\n"
+       "  -h, --help        This message";
+
+static struct commit **blame_lines;
+static int num_blame_lines;
+static char* blame_contents;
+static int blame_len;
 
 struct util_info {
        int *line_map;
@@ -84,7 +92,7 @@ static struct patch *get_patch(struct commit *commit, struct commit *other)
                die("write failed: %s", strerror(errno));
        close(fd);
 
-       sprintf(diff_cmd, "diff -u0 %s %s", tmp_path1, tmp_path2);
+       sprintf(diff_cmd, "diff -0 %s %s", tmp_path1, tmp_path2);
        fin = popen(diff_cmd, "r");
        if (!fin)
                die("popen failed: %s", strerror(errno));
@@ -226,6 +234,7 @@ static void print_patch(struct patch *p)
        }
 }
 
+#if DEBUG
 /* For debugging only */
 static void print_map(struct commit *cmit, struct commit *other)
 {
@@ -259,6 +268,7 @@ static void print_map(struct commit *cmit, struct commit *other)
                printf("\n");
        }
 }
+#endif
 
 // p is a patch from commit to other.
 static void fill_line_map(struct commit *commit, struct commit *other,
@@ -388,9 +398,8 @@ static void init_first_commit(struct commit* commit, const char* filename)
        alloc_line_map(commit);
 
        util = commit->object.util;
-       num_blame_lines = util->num_lines;
 
-       for (i = 0; i < num_blame_lines; i++)
+       for (i = 0; i < util->num_lines; i++)
                util->line_map[i] = i;
 }
 
@@ -412,6 +421,9 @@ static void process_commits(struct rev_info *rev, const char *path,
        util = commit->object.util;
        num_blame_lines = util->num_lines;
        blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
+       blame_contents = util->buf;
+       blame_len = util->size;
+
        for (i = 0; i < num_blame_lines; i++)
                blame_lines[i] = NULL;
 
@@ -499,32 +511,137 @@ static void process_commits(struct rev_info *rev, const char *path,
        } while ((commit = get_revision(rev)) != NULL);
 }
 
+struct commit_info
+{
+       char* author;
+       char* author_mail;
+       unsigned long author_time;
+       char* author_tz;
+};
+
+static void get_commit_info(struct commit* commit, struct commit_info* ret)
+{
+       int len;
+       char* tmp;
+       static char author_buf[1024];
+
+       tmp = strstr(commit->buffer, "\nauthor ") + 8;
+       len = index(tmp, '\n') - tmp;
+       ret->author = author_buf;
+       memcpy(ret->author, tmp, len);
+
+       tmp = ret->author;
+       tmp += len;
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_tz = tmp+1;
+
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_time = strtoul(tmp, NULL, 10);
+
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_mail = tmp + 1;
+
+       *tmp = 0;
+}
+
+static const char* format_time(unsigned long time, const char* tz_str)
+{
+       static char time_buf[128];
+       time_t t = time;
+       int minutes, tz;
+       struct tm *tm;
+
+       tz = atoi(tz_str);
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       tm = gmtime(&t);
+
+       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
+       strcat(time_buf, tz_str);
+       return time_buf;
+}
+
 int main(int argc, const char **argv)
 {
        int i;
        struct commit *initial = NULL;
        unsigned char sha1[20];
-       const char* filename;
+
+       const char *filename = NULL, *commit = NULL;
+       char filename_buf[256];
+       int sha1_len = 8;
+       int compability = 0;
+       int options = 1;
+
        int num_args;
        const char* args[10];
        struct rev_info rev;
 
-       setup_git_directory();
+       struct commit_info ci;
+       const char *buf;
+       int max_digits;
 
-       if (argc != 3)
-               die("Usage: blame commit-ish file");
+       const char* prefix = setup_git_directory();
 
+       for(i = 1; i < argc; i++) {
+               if(options) {
+                       if(!strcmp(argv[i], "-h") ||
+                          !strcmp(argv[i], "--help"))
+                               usage(blame_usage);
+                       else if(!strcmp(argv[i], "-l") ||
+                               !strcmp(argv[i], "--long")) {
+                               sha1_len = 40;
+                               continue;
+                       } else if(!strcmp(argv[i], "-c") ||
+                                 !strcmp(argv[i], "--compability")) {
+                               compability = 1;
+                               continue;
+                       } else if(!strcmp(argv[i], "--")) {
+                               options = 0;
+                               continue;
+                       } else if(argv[i][0] == '-')
+                               usage(blame_usage);
+                       else
+                               options = 0;
+               }
 
-       filename = argv[2];
+               if(!options) {
+                       if(!filename)
+                               filename = argv[i];
+                       else if(!commit)
+                               commit = argv[i];
+                       else
+                               usage(blame_usage);
+               }
+       }
+
+       if(!filename)
+               usage(blame_usage);
+       if(!commit)
+               commit = "HEAD";
+
+       if(prefix)
+               sprintf(filename_buf, "%s%s", prefix, filename);
+       else
+               strcpy(filename_buf, filename);
+       filename = filename_buf;
 
        {
-               struct commit* commit;
-               if (get_sha1(argv[1], sha1))
-                       die("get_sha1 failed");
-               commit = lookup_commit_reference(sha1);
+               struct commit* c;
+               if (get_sha1(commit, sha1))
+                       die("get_sha1 failed, commit '%s' not found", commit);
+               c = lookup_commit_reference(sha1);
 
-               if (fill_util_info(commit, filename)) {
-                       printf("%s not found in %s\n", filename, argv[1]);
+               if (fill_util_info(c, filename)) {
+                       printf("%s not found in %s\n", filename, commit);
                        return 1;
                }
        }
@@ -533,7 +650,7 @@ int main(int argc, const char **argv)
        args[num_args++] = NULL;
        args[num_args++] = "--topo-order";
        args[num_args++] = "--remove-empty";
-       args[num_args++] = argv[1];
+       args[num_args++] = commit;
        args[num_args++] = "--";
        args[num_args++] = filename;
        args[num_args] = NULL;
@@ -542,13 +659,35 @@ int main(int argc, const char **argv)
        prepare_revision_walk(&rev);
        process_commits(&rev, filename, &initial);
 
+       buf = blame_contents;
+       for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
+               i *= 10;
+
        for (i = 0; i < num_blame_lines; i++) {
                struct commit *c = blame_lines[i];
                if (!c)
                        c = initial;
 
-               printf("%d %.8s\n", i, sha1_to_hex(c->object.sha1));
-// printf("%d %s\n", i, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+               get_commit_info(c, &ci);
+               fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
+               if(compability)
+                       printf("\t(%10s\t%10s\t%d)", ci.author,
+                              format_time(ci.author_time, ci.author_tz), i+1);
+               else
+                       printf(" (%-15.15s %10s %*d) ", ci.author,
+                              format_time(ci.author_time, ci.author_tz),
+                              max_digits, i+1);
+
+               if(i == num_blame_lines - 1) {
+                       fwrite(buf, blame_len - (buf - blame_contents),
+                              1, stdout);
+                       if(blame_contents[blame_len-1] != '\n')
+                               putc('\n', stdout);
+               } else {
+                       char* next_buf = index(buf, '\n') + 1;
+                       fwrite(buf, next_buf - buf, 1, stdout);
+                       buf = next_buf;
+               }
        }
 
        if (DEBUG) {
diff --git a/contrib/emacs/.gitignore b/contrib/emacs/.gitignore
new file mode 100644 (file)
index 0000000..c531d98
--- /dev/null
@@ -0,0 +1 @@
+*.elc
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
new file mode 100644 (file)
index 0000000..d3619db
--- /dev/null
@@ -0,0 +1,20 @@
+## Build and install stuff
+
+EMACS = emacs
+
+ELC = git.elc vc-git.elc
+INSTALL = install
+INSTALL_ELC = $(INSTALL) -m 644
+prefix = $(HOME)
+emacsdir = $(prefix)/share/emacs/site-lisp
+
+all: $(ELC)
+
+install: all
+       $(INSTALL) -d $(emacsdir)
+       $(INSTALL_ELC) $(ELC) $(emacsdir)
+
+%.elc: %.el
+       $(EMACS) --batch --eval '(byte-compile-file "$<")'
+
+clean:; rm -f $(ELC)
index b5e59a9..96cc212 100644 (file)
@@ -29,10 +29,9 @@ const char *git_exec_path(void)
 }
 
 
-int execv_git_cmd(char **argv)
+int execv_git_cmd(const char **argv)
 {
        char git_command[PATH_MAX + 1];
-       char *tmp;
        int len, err, i;
        const char *paths[] = { current_exec_path,
                                getenv("GIT_EXEC_PATH"),
@@ -40,6 +39,8 @@ int execv_git_cmd(char **argv)
 
        for (i = 0; i < sizeof(paths)/sizeof(paths[0]); ++i) {
                const char *exec_dir = paths[i];
+               const char *tmp;
+
                if (!exec_dir) continue;
 
                if (*exec_dir != '/') {
@@ -82,7 +83,7 @@ int execv_git_cmd(char **argv)
                argv[0] = git_command;
 
                /* execve() can only ever return if it fails */
-               execve(git_command, argv, environ);
+               execve(git_command, (char **)argv, environ);
 
                err = errno;
 
@@ -93,11 +94,11 @@ int execv_git_cmd(char **argv)
 }
 
 
-int execl_git_cmd(char *cmd,...)
+int execl_git_cmd(const char *cmd,...)
 {
        int argc;
-       char *argv[MAX_ARGS + 1];
-       char *arg;
+       const char *argv[MAX_ARGS + 1];
+       const char *arg;
        va_list param;
 
        va_start(param, cmd);
index 5150ee2..989621f 100644 (file)
@@ -3,8 +3,8 @@
 
 extern void git_set_exec_path(const char *exec_path);
 extern const char* git_exec_path(void);
-extern int execv_git_cmd(char **argv); /* NULL terminated */
-extern int execl_git_cmd(char *cmd, ...);
+extern int execv_git_cmd(const char **argv); /* NULL terminated */
+extern int execl_git_cmd(const char *cmd, ...);
 
 
 #endif /* __GIT_EXEC_CMD_H_ */
index d93ee19..feea0a2 100755 (executable)
@@ -99,7 +99,7 @@ while (my $bound = pop @stack) {
        }
 }
 push @revqueue, $head;
-init_claim( defined $starting_rev ? $starting_rev : 'dirty');
+init_claim( defined $starting_rev ? $head : 'dirty');
 unless (defined $starting_rev) {
        my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename)
                or die "Failed to call git diff to check for dirty state: $!";
@@ -345,6 +345,7 @@ sub git_cat_file {
        return () unless defined $rev && defined $filename;
 
        my $blob = git_ls_tree($rev, $filename);
+       die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob;
 
        my $catfile = open_pipe("git","cat-file", "blob", $blob)
                or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!;
@@ -367,12 +368,13 @@ sub git_ls_tree {
 
        my ($mode, $type, $blob, $tfilename);
        while(<$lstree>) {
+               chomp;
                ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
                last if ($tfilename eq $filename);
        }
        close($lstree);
 
-       return $blob if $filename eq $filename;
+       return $blob if ($tfilename eq $filename);
        die "git-ls-tree failed to find blob for $filename";
 
 }
@@ -418,7 +420,13 @@ sub format_date {
                return $_[0];
        }
        my ($timestamp, $timezone) = split(' ', $_[0]);
-       return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp));
+       my $minutes = abs($timezone);
+       $minutes = int($minutes / 100) * 60 + ($minutes % 100);
+       if ($timezone < 0) {
+           $minutes = -$minutes;
+       }
+       my $t = $timestamp + $minutes * 60;
+       return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t));
 }
 
 # Copied from git-send-email.perl - We need a Git.pm module..
index 2bd2639..2ebf7e8 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--diff-options] <his> [<mine>]'
+USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--diff-options] [--attach] <his> [<mine>]'
 LONG_USAGE='Prepare each commit with its patch since <mine> head forked from
 <his> head, one file per patch formatted to resemble UNIX mailbox
 format, for e-mail submission or use with git-am.
@@ -18,7 +18,9 @@ is ignored if --stdout is specified.
 
 When -n is specified, instead of "[PATCH] Subject", the first
 line is formatted as "[PATCH N/M] Subject", unless you have only
-one patch.'
+one patch.
+
+When --attach is specified, patches are attached, not inlined.'
 
 . git-sh-setup
 
@@ -40,6 +42,8 @@ do
     -d|--d|--da|--dat|--date|\
     -m|--m|--mb|--mbo|--mbox) # now noop
     ;;
+    --at|--att|--atta|--attac|--attach)
+    attach=t ;;
     -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
     --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
     keep_subject=t ;;
@@ -149,6 +153,12 @@ do
 done >$series
 
 me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
+headers=`git-repo-config --get format.headers`
+case "$attach" in
+"") ;;
+*)
+       mimemagic="050802040500080604070107"
+esac
 
 case "$outdir" in
 */) ;;
@@ -173,7 +183,7 @@ titleScript='
 
 process_one () {
        perl -w -e '
-my ($keep_subject, $num, $signoff, $commsg) = @ARGV;
+my ($keep_subject, $num, $signoff, $headers, $mimemagic, $commsg) = @ARGV;
 my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen,
     $last_was_signoff);
 
@@ -224,7 +234,20 @@ while (<FH>) {
            s/^\[PATCH[^]]*\]\s*//;
            s/^/[PATCH$num] /;
        }
+       if ($headers) {
+           print "$headers\n";
+       }
         print "Subject: $_";
+       if ($mimemagic) {
+           print "MIME-Version: 1.0\n";
+           print "Content-Type: multipart/mixed;\n";
+           print " boundary=\"------------$mimemagic\"\n";
+           print "\n";
+           print "This is a multi-part message in MIME format.\n";
+           print "--------------$mimemagic\n";
+           print "Content-Type: text/plain; charset=UTF-8; format=fixed\n";
+           print "Content-Transfer-Encoding: 8bit\n";
+       }
        $done_subject = 1;
        next;
     }
@@ -250,14 +273,33 @@ if (!$signoff_seen && $signoff ne "") {
 }
 print "\n---\n\n";
 close FH or die "close $commsg pipe";
-' "$keep_subject" "$num" "$signoff" $commsg
+' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg
 
        git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
        echo
+       case "$mimemagic" in
+       '');;
+       *)
+               echo "--------------$mimemagic"
+               echo "Content-Type: text/x-patch;"
+               echo " name=\"$commit.diff\""
+               echo "Content-Transfer-Encoding: 8bit"
+               echo "Content-Disposition: inline;"
+               echo " filename=\"$commit.diff\""
+               echo
+       esac
        git-diff-tree -p $diff_opts "$commit"
-       echo "-- "
-       echo "@@GIT_VERSION@@"
-
+       case "$mimemagic" in
+       '')
+               echo "-- "
+               echo "@@GIT_VERSION@@"
+               ;;
+       *)
+               echo
+               echo "--------------$mimemagic--"
+               echo
+               ;;
+       esac
        echo
 }
 
diff --git a/git.c b/git.c
index a547dbd..164d3e9 100644 (file)
--- a/git.c
+++ b/git.c
@@ -216,33 +216,33 @@ static void prepend_to_path(const char *dir, int len)
        setenv("PATH", path, 1);
 }
 
-static void show_man_page(char *git_cmd)
+static void show_man_page(const char *git_cmd)
 {
-       char *page;
+       const char *page;
 
        if (!strncmp(git_cmd, "git", 3))
                page = git_cmd;
        else {
                int page_len = strlen(git_cmd) + 4;
-
-               page = malloc(page_len + 1);
-               strcpy(page, "git-");
-               strcpy(page + 4, git_cmd);
-               page[page_len] = 0;
+               char *p = malloc(page_len + 1);
+               strcpy(p, "git-");
+               strcpy(p + 4, git_cmd);
+               p[page_len] = 0;
+               page = p;
        }
 
        execlp("man", "man", page, NULL);
 }
 
-static int cmd_version(int argc, char **argv, char **envp)
+static int cmd_version(int argc, const char **argv, char **envp)
 {
        printf("git version %s\n", GIT_VERSION);
        return 0;
 }
 
-static int cmd_help(int argc, char **argv, char **envp)
+static int cmd_help(int argc, const char **argv, char **envp)
 {
-       char *help_cmd = argv[1];
+       const char *help_cmd = argv[1];
        if (!help_cmd)
                cmd_usage(git_exec_path(), NULL);
        show_man_page(help_cmd);
@@ -251,7 +251,7 @@ static int cmd_help(int argc, char **argv, char **envp)
 
 #define LOGSIZE (65536)
 
-static int cmd_log(int argc, char **argv, char **envp)
+static int cmd_log(int argc, const char **argv, char **envp)
 {
        struct rev_info rev;
        struct commit *commit;
@@ -263,7 +263,7 @@ static int cmd_log(int argc, char **argv, char **envp)
 
        argc = setup_revisions(argc, argv, &rev, "HEAD");
        while (1 < argc) {
-               char *arg = argv[1];
+               const char *arg = argv[1];
                if (!strncmp(arg, "--pretty", 8)) {
                        commit_format = get_commit_format(arg + 8);
                        if (commit_format == CMIT_FMT_ONELINE)
@@ -325,12 +325,12 @@ static int cmd_log(int argc, char **argv, char **envp)
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
-static void handle_internal_command(int argc, char **argv, char **envp)
+static void handle_internal_command(int argc, const char **argv, char **envp)
 {
        const char *cmd = argv[0];
        static struct cmd_struct {
                const char *cmd;
-               int (*fn)(int, char **, char **);
+               int (*fn)(int, const char **, char **);
        } commands[] = {
                { "version", cmd_version },
                { "help", cmd_help },
@@ -346,9 +346,9 @@ static void handle_internal_command(int argc, char **argv, char **envp)
        }
 }
 
-int main(int argc, char **argv, char **envp)
+int main(int argc, const char **argv, char **envp)
 {
-       char *cmd = argv[0];
+       const char *cmd = argv[0];
        char *slash = strrchr(cmd, '/');
        char git_command[PATH_MAX + 1];
        const char *exec_path = NULL;
index 2a3db16..93929b5 100644 (file)
@@ -6,7 +6,7 @@
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
-static char *unpacker[] = { "unpack-objects", NULL };
+static const char *unpacker[] = { "unpack-objects", NULL };
 
 static int report_status = 0;
 
@@ -177,7 +177,7 @@ static void run_update_post_hook(struct command *cmd)
 {
        struct command *cmd_p;
        int argc;
-       char **argv;
+       const char **argv;
 
        if (access(update_post_hook, X_OK) < 0)
                return;
@@ -190,10 +190,12 @@ static void run_update_post_hook(struct command *cmd)
        argv[0] = update_post_hook;
 
        for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+               char *p;
                if (cmd_p->error_string)
                        continue;
-               argv[argc] = xmalloc(strlen(cmd_p->ref_name) + 1);
-               strcpy(argv[argc], cmd_p->ref_name);
+               p = xmalloc(strlen(cmd_p->ref_name) + 1);
+               strcpy(p, cmd_p->ref_name);
+               argv[argc] = p;
                argc++;
        }
        argv[argc] = NULL;
index a3df810..2a33637 100644 (file)
@@ -684,13 +684,11 @@ static void rewrite_parents(struct commit *commit)
 struct commit *get_revision(struct rev_info *revs)
 {
        struct commit_list *list = revs->commits;
-       struct commit *commit;
 
        if (!list)
                return NULL;
 
        /* Check the max_count ... */
-       commit = list->item;
        switch (revs->max_count) {
        case -1:
                break;
@@ -701,22 +699,28 @@ struct commit *get_revision(struct rev_info *revs)
        }
 
        do {
-               commit = pop_most_recent_commit(&revs->commits, SEEN);
+               struct commit *commit = revs->commits->item;
+
                if (commit->object.flags & (UNINTERESTING|SHOWN))
-                       continue;
+                       goto next;
                if (revs->min_age != -1 && (commit->date > revs->min_age))
-                       continue;
+                       goto next;
                if (revs->max_age != -1 && (commit->date < revs->max_age))
                        return NULL;
                if (revs->no_merges && commit->parents && commit->parents->next)
-                       continue;
+                       goto next;
                if (revs->paths && revs->dense) {
                        if (!(commit->object.flags & TREECHANGE))
-                               continue;
+                               goto next;
                        rewrite_parents(commit);
                }
+               /* More to go? */
+               if (revs->max_count)
+                       pop_most_recent_commit(&revs->commits, SEEN);
                commit->object.flags |= SHOWN;
                return commit;
+next:
+               pop_most_recent_commit(&revs->commits, SEEN);
        } while (revs->commits);
        return NULL;
 }
index b3d287e..ca67ee9 100644 (file)
@@ -3,7 +3,7 @@
 #include <sys/wait.h>
 #include "exec_cmd.h"
 
-int run_command_v_opt(int argc, char **argv, int flags)
+int run_command_v_opt(int argc, const char **argv, int flags)
 {
        pid_t pid = fork();
 
@@ -47,7 +47,7 @@ int run_command_v_opt(int argc, char **argv, int flags)
        }
 }
 
-int run_command_v(int argc, char **argv)
+int run_command_v(int argc, const char **argv)
 {
        return run_command_v_opt(argc, argv, 0);
 }
@@ -55,7 +55,7 @@ int run_command_v(int argc, char **argv)
 int run_command(const char *cmd, ...)
 {
        int argc;
-       char *argv[MAX_RUN_COMMAND_ARGS];
+       const char *argv[MAX_RUN_COMMAND_ARGS];
        const char *arg;
        va_list param;
 
index ef3ee05..70b477a 100644 (file)
@@ -13,8 +13,8 @@ enum {
 
 #define RUN_COMMAND_NO_STDIO 1
 #define RUN_GIT_CMD         2  /*If this is to be git sub-command */
-int run_command_v_opt(int argc, char **argv, int opt);
-int run_command_v(int argc, char **argv);
+int run_command_v_opt(int argc, const char **argv, int opt);
+int run_command_v(int argc, const char **argv);
 int run_command(const char *cmd, ...);
 
 #endif
index f558386..c8ffc8d 100644 (file)
@@ -27,7 +27,7 @@ static int is_zero_sha1(const unsigned char *sha1)
 
 static void exec_pack_objects(void)
 {
-       static char *args[] = {
+       static const char *args[] = {
                "pack-objects",
                "--stdout",
                NULL
@@ -39,7 +39,7 @@ static void exec_pack_objects(void)
 static void exec_rev_list(struct ref *refs)
 {
        struct ref *ref;
-       static char *args[1000];
+       static const char *args[1000];
        int i = 0, j;
 
        args[i++] = "rev-list"; /* 0 */
diff --git a/shell.c b/shell.c
index fc0c73c..8c08cf0 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -15,7 +15,7 @@ static int do_generic_cmd(const char *me, char *arg)
        my_argv[1] = arg;
        my_argv[2] = NULL;
 
-       return execv_git_cmd((char**) my_argv);
+       return execv_git_cmd(my_argv);
 }
 
 static struct commands {
diff --git a/t/.gitignore b/t/.gitignore
new file mode 100644 (file)
index 0000000..fad67c0
--- /dev/null
@@ -0,0 +1 @@
+trash
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
new file mode 100644 (file)
index 0000000..9c5a15a
--- /dev/null
@@ -0,0 +1,121 @@
+# This file isn't used as a test script directly, instead it is
+# sourced from t8001-annotate.sh and t8001-blame.sh.
+
+check_count () {
+       head=
+       case "$1" in -h) head="$2"; shift; shift ;; esac
+       $PROG file $head | perl -e '
+               my %expect = (@ARGV);
+               my %count = ();
+               while (<STDIN>) {
+                       if (/^[0-9a-f]+\t\(([^\t]+)\t/) {
+                               my $author = $1;
+                               for ($author) { s/^\s*//; s/\s*$//; }
+                               if (exists $expect{$author}) {
+                                       $count{$author}++;
+                               }
+                       }
+               }
+               my $bad = 0;
+               while (my ($author, $count) = each %count) {
+                       my $ok;
+                       if ($expect{$author} != $count) {
+                               $bad = 1;
+                               $ok = "bad";
+                       }
+                       else {
+                               $ok = "good";
+                       }
+                       print STDERR "Author $author (expected $expect{$author}, attributed $count) $ok\n";
+               }
+               exit($bad);
+       ' "$@"
+}
+
+test_expect_success \
+    'prepare reference tree' \
+    'echo "1A quick brown fox jumps over the" >file &&
+     echo "lazy dog" >>file &&
+     git add file
+     GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+    'check all lines blamed on A' \
+    'check_count A 2'
+
+test_expect_success \
+    'Setup new lines blamed on B' \
+    'echo "2A quick brown fox jumps over the" >>file &&
+     echo "lazy dog" >> file &&
+     GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+
+test_expect_success \
+    'Two lines blamed on A, two on B' \
+    'check_count A 2 B 2'
+
+test_expect_success \
+    'merge-setup part 1' \
+    'git checkout -b branch1 master &&
+     echo "3A slow green fox jumps into the" >> file &&
+     echo "well." >> file &&
+     GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+
+test_expect_success \
+    'Two lines blamed on A, two on B, two on B1' \
+    'check_count A 2 B 2 B1 2'
+
+test_expect_success \
+    'merge-setup part 2' \
+    'git checkout -b branch2 master &&
+     sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
+     mv file.new file &&
+     GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+
+test_expect_success \
+    'Two lines blamed on A, one on B, one on B2' \
+    'check_count A 2 B 1 B2 1'
+
+test_expect_success \
+    'merge-setup part 3' \
+    'git pull . branch1'
+
+test_expect_success \
+    'Two lines blamed on A, one on B, two on B1, one on B2' \
+    'check_count A 2 B 1 B1 2 B2 1'
+
+test_expect_success \
+    'Annotating an old revision works' \
+    'check_count -h master A 2 B 2'
+
+test_expect_success \
+    'Annotating an old revision works' \
+    'check_count -h master^ A 2'
+
+test_expect_success \
+    'merge-setup part 4' \
+    'echo "evil merge." >>file &&
+     EDITOR=: git commit -a --amend'
+
+test_expect_success \
+    'Two lines blamed on A, one on B, two on B1, one on B2, one on A U Thor' \
+    'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1'
+
+test_expect_success \
+    'an incomplete line added' \
+    'echo "incomplete" | tr -d "\\012" >>file &&
+    GIT_AUTHOR_NAME="C" git commit -a -m "Incomplete"'
+
+test_expect_success \
+    'With incomplete lines.' \
+    'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1 C 1'
+
+test_expect_success \
+    'some edit' \
+    'mv file file1 &&
+     sed -e 1d -e "5s/3A/99/" file1 >file &&
+     rm -f file1 &&
+    GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
+
+test_expect_success \
+    'some edit' \
+    'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1'
index 172908a..2496397 100755 (executable)
@@ -3,88 +3,7 @@
 test_description='git-annotate'
 . ./test-lib.sh
 
-test_expect_success \
-    'prepare reference tree' \
-    'echo "1A quick brown fox jumps over the" >file &&
-     echo "lazy dog" >>file &&
-     git add file
-     GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
-
-test_expect_success \
-    'check all lines blamed on A' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
-
-test_expect_success \
-    'Setup new lines blamed on B' \
-    'echo "2A quick brown fox jumps over the" >>file &&
-     echo "lazy dog" >> file &&
-     GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
-
-test_expect_success \
-    'Two lines blamed on A' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
-
-test_expect_success \
-    'Two lines blamed on B' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "B") == 2 ]'
-
-test_expect_success \
-    'merge-setup part 1' \
-    'git checkout -b branch1 master &&
-     echo "3A slow green fox jumps into the" >> file &&
-     echo "well." >> file &&
-     GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
-
-test_expect_success \
-    'Two lines blamed on A' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
-
-test_expect_success \
-    'Two lines blamed on B' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 2 ]'
-
-test_expect_success \
-    'Two lines blamed on B1' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
-
-test_expect_success \
-    'merge-setup part 2' \
-    'git checkout -b branch2 master &&
-     sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
-     mv file.new file &&
-     GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
-
-test_expect_success \
-    'Two lines blamed on A' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
-
-test_expect_success \
-    'One line blamed on B' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
-
-test_expect_success \
-    'One line blamed on B2' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
-
-
-test_expect_success \
-    'merge-setup part 3' \
-    'git pull . branch1'
-
-test_expect_success \
-    'Two lines blamed on A' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
-
-test_expect_success \
-    'One line blamed on B' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
-
-test_expect_success \
-    'Two lines blamed on B1' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
-
-test_expect_success \
-    'One line blamed on B2' \
-    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+PROG='git annotate'
+. ../annotate-tests.sh
 
 test_done
diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh
new file mode 100755 (executable)
index 0000000..9777393
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='git-blame'
+. ./test-lib.sh
+
+PROG='git blame -c'
+. ../annotate-tests.sh
+
+test_done
index 635abb3..47560c9 100644 (file)
@@ -46,7 +46,7 @@ static void create_pack_file(void)
        if (!pid) {
                int i;
                int args;
-               char **argv;
+               const char **argv;
                char *buf;
                char **p;
 
@@ -56,9 +56,9 @@ static void create_pack_file(void)
                }
                else
                        args = nr_has + nr_needs + 5;
-               argv = xmalloc(args * sizeof(char *));
+               p = xmalloc(args * sizeof(char *));
+               argv = (const char **) p;
                buf = xmalloc(args * 45);
-               p = argv;
 
                dup2(fd[1], 1);
                close(0);