Subject: [RFC/PATCH] Add builtin git-last-modified.

From: Robert Fitzsimons <[email-masked]>
Date: Mon, 1 Jan 2007 07:55:15 +0000

This is a plumbing command which can be used by gitweb.  It will scan
a list of commits and/or refs and return the time in seconds for the
youngest commit.  This value can then be used to generate and check
the Last-Modified and Is-Modified-Since HTTP headers.

Signed-off-by: Robert Fitzsimons <[email-masked]>
---

The following two commands lines are similar:

$ git for-each-ref --count=1 --sort=-committerdate '--format=%(committerdate)' refs/heads
$ git last-modified --branches

The last-modified version is a bit faster (5-10%).

I'm not 100% sure how this command and if this command should be used
by gitweb.  But I'll post the patch anyway for comments.

Robert

 .gitignore                          |    1 +
 Documentation/git-last-modified.txt |   49 ++++++++++++++++++++++++++++
 Makefile                            |    3 +-
 builtin-last-modified.c             |   61 +++++++++++++++++++++++++++++++++++
 builtin.h                           |    1 +
 git.c                               |    1 +
 t/t3810-last-modified.sh            |   57 ++++++++++++++++++++++++++++++++
 7 files changed, 172 insertions(+), 1 deletions(-)

diff --git a/.gitignore b/.gitignore
index 2904f12..ffca6c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,6 +58,7 @@ git-lost-found
 git-ls-files
 git-ls-remote
 git-ls-tree
+git-last-modified
 git-mailinfo
 git-mailsplit
 git-merge
diff --git a/Documentation/git-last-modified.txt b/Documentation/git-last-modified.txt
new file mode 100644
index 0000000..4584b5d
--- /dev/null
+++ b/Documentation/git-last-modified.txt
@@ -0,0 +1,49 @@
+git-last-modified(1)
+================
+
+NAME
+----
+git-last-modified - Get the last modified time in seconds
+
+
+SYNOPSIS
+--------
+'git-last-modified' [ \--all ]
+		    [ \--branches ]
+		    [ \--tags ]
+		    [ \--remotes ]
+		    <commmit>...
+
+DESCRIPTION
+-----------
+
+Get the last modified time in seconds for the youngest commit object
+based on the search criteria.  The time value is from the 'commit'
+header of the commit object, ignoring the timezone.  The time is
+formatted as an ASCII decimal number and is the number of seconds
+since epoch.
+
+This is a plumbing command used by gitweb to generate and check the
+Last-Modified and Is-Modified-Since HTTP headers.
+
+OPTIONS
+-------
+--all::
+	Search all refs found in `$GIT_DIR/refs`.
+
+--branches::
+	Search branch refs found in `$GIT_DIR/refs/heads`.
+
+--tags::
+	Search tag refs found in `$GIT_DIR/refs/tags`.
+
+--remotes::
+	Search remote refs found in `$GIT_DIR/refs/remotes`.
+
+<commit>...::
+	Search list of commits and refs.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Makefile b/Makefile
index fa1a022..1f169ac 100644
--- a/Makefile
+++ b/Makefile
@@ -312,7 +312,8 @@ BUILTIN_OBJS = \
 	builtin-verify-pack.o \
 	builtin-write-tree.o \
 	builtin-show-ref.o \
-	builtin-pack-refs.o
+	builtin-pack-refs.o \
+	builtin-last-modified.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 EXTLIBS = -lz
diff --git a/builtin-last-modified.c b/builtin-last-modified.c
new file mode 100644
index 0000000..38cfb80
--- /dev/null
+++ b/builtin-last-modified.c
@@ -0,0 +1,61 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "builtin.h"
+
+static int lastmod_date(const unsigned char *sha1, unsigned long *date)
+{
+	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+
+	if (!commit)
+		return 1;
+
+	if (commit->date > *date)
+		*date = commit->date;
+
+	return 0;
+}
+
+static int process_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+	return lastmod_date(sha1, (unsigned long *)cb_data);
+}
+
+int cmd_last_modified(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	unsigned char sha1[20];
+	unsigned long date = 0;
+
+	git_config(git_default_config);
+
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+
+		if (!strcmp(arg, "--all")) {
+			for_each_ref(process_ref, &date);
+			continue;
+		}
+		if (!strcmp(arg, "--branches")) {
+			for_each_branch_ref(process_ref, &date);
+			continue;
+		}
+		if (!strcmp(arg, "--tags")) {
+			for_each_tag_ref(process_ref, &date);
+			continue;
+		}
+		if (!strcmp(arg, "--remotes")) {
+			for_each_remote_ref(process_ref, &date);
+			continue;
+		}
+
+		if (get_sha1(arg, sha1))
+			die("last-modified: could not get sha1 for '%s'", arg);
+		if (lastmod_date(sha1, &date))
+			die("last-modified: bad revision '%s'", arg);
+	}
+
+	printf("%lu\n", date);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index df72d09..862cadb 100644
--- a/builtin.h
+++ b/builtin.h
@@ -75,5 +75,6 @@ extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);
+extern int cmd_last_modified(int argc, const char **argv, const char *prefix);
 
 #endif
diff --git a/git.c b/git.c
index c82ca45..e6242e6 100644
--- a/git.c
+++ b/git.c
@@ -269,6 +269,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
 		{ "verify-pack", cmd_verify_pack },
 		{ "show-ref", cmd_show_ref, RUN_SETUP },
 		{ "pack-refs", cmd_pack_refs, RUN_SETUP },
+		{ "last-modified", cmd_last_modified, RUN_SETUP },
 	};
 	int i;
 
diff --git a/t/t3810-last-modified.sh b/t/t3810-last-modified.sh
new file mode 100755
index 0000000..d85283e
--- /dev/null
+++ b/t/t3810-last-modified.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+test_description='git-last-modified'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+    for n in 1 2 3 4 5 ; do \
+        echo $n > a ; \
+        git-add a ; \
+        git-commit -m "$n" ; \
+    done ; \
+    git-tag -m "t1" t1 ; \
+    git-checkout -b o ; \
+    for n in o1 o1 o3 o4 o5 ; do \
+        echo $n > b ; \
+        git-add b ; \
+        git-commit -m "$n" ; \
+    done ; \
+    git-tag -m "t2" t2 ; \
+    git-checkout master
+'
+
+test_expect_success 'no options' '
+    test $(git-last-modified) -eq 0
+'
+
+test_expect_success 'rev HEAD' '
+    test $(git-last-modified HEAD) -gt 0 &&
+    test $(git-last-modified refs/heads/master) -gt 0 &&
+    test $(git-last-modified --all) -gt 0 &&
+    test $(git-last-modified --branches) -gt 0 &&
+    test $(git-last-modified --tags) -gt 0 &&
+    test $(git-last-modified --remotes) -eq 0
+'
+
+test_expect_success 'rev master -lt o' '
+    test $(git-last-modified master) -lt $(git-last-modified o)
+'
+
+test_expect_success 'mixed --branches HEAD t1 o' '
+    test $(git-last-modified --branches HEAD t1 o) -gt 0
+'
+
+test_expect_failure 'bad sha1' '
+    git-last-modified 000000
+'
+
+test_expect_failure 'bad rev' '
+    git-last-modified xyz
+'
+
+test_expect_failure 'bad --branches rev' '
+    git-last-modified xyz
+'
+
+test_done
-- 
1.5.0.rc0.g2708-dirty