Merge branch 'lt/tree-2' into next
authorJunio C Hamano <junkio@cox.net>
Fri, 2 Jun 2006 22:24:07 +0000 (15:24 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 2 Jun 2006 22:24:07 +0000 (15:24 -0700)
* lt/tree-2:
  fetch.c: do not call process_tree() from process_tree().

57 files changed:
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/everyday.txt
Documentation/git-branch.txt
Documentation/git-checkout.txt
Documentation/git-read-tree.txt
Documentation/git-rev-parse.txt
Documentation/git-update-ref.txt
Documentation/git-write-tree.txt
Documentation/repository-layout.txt
Documentation/technical/pack-format.txt
Documentation/tutorial-2.txt
Documentation/tutorial.txt
builtin-log.c
builtin-read-tree.c
cache-tree.c
cache-tree.h
cache.h
config.c
contrib/git-svn/git-svn.perl
contrib/git-svn/t/t0001-contrib-git-svn-props.sh
environment.c
exec_cmd.c
fetch-pack.c
fetch.c
fetch.h
git-am.sh
git-applypatch.sh
git-branch.sh
git-checkout.sh
git-clean.sh
git-commit.sh
git-reset.sh
git-send-email.perl
git-svnimport.perl
gitk
http-fetch.c
http.c
local-fetch.c
log-tree.c
mailsplit.c
refs.c
refs.h
revision.h
sha1_name.c
ssh-fetch.c
t/t0000-basic.sh
t/t1400-update-ref.sh [new file with mode: 0755]
t/t2101-update-index-reupdate.sh
t/t3200-branch.sh
t/t3300-funny-names.sh
t/t4012-diff-binary.sh
t/t5500-fetch-pack.sh
t/t6000lib.sh
t/t9001-send-email.sh [new file with mode: 0755]
update-ref.c
write-tree.c

index d1a4bec..e178ee2 100644 (file)
@@ -70,6 +70,14 @@ core.preferSymlinkRefs::
        This is sometimes needed to work with old scripts that
        expect HEAD to be a symbolic link.
 
+core.logAllRefUpdates::
+       If true, `git-update-ref` will append a line to
+       "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
+       of the update.  If the file does not exist it will be
+       created automatically.  This information can be used to
+       determine what commit was the tip of a branch "2 days ago".
+       This value is false by default (no logging).
+
 core.repositoryFormatVersion::
        Internal variable identifying the repository format and layout
        version.
index d1360ec..5a831ad 100644 (file)
@@ -1,5 +1,5 @@
-A short git tutorial
-====================
+A git core tutorial for developers
+==================================
 
 Introduction
 ------------
index 4b56370..2ad2d61 100644 (file)
@@ -66,7 +66,7 @@ $ git prune <4>
 <1> running without "--full" is usually cheap and assures the
 repository health reasonably well.
 <2> check how many loose objects there are and how much
-diskspace is wasted by not repacking.
+disk space is wasted by not repacking.
 <3> without "-a" repacks incrementally.  repacking every 4-5MB
 of loose objects accumulation may be a good rule of thumb.
 <4> after repack, prune removes the duplicate loose objects.
@@ -86,7 +86,7 @@ Individual Developer (Standalone)[[Individual Developer (Standalone)]]
 ----------------------------------------------------------------------
 
 A standalone individual developer does not exchange patches with
-other poeple, and works alone in a single repository, using the
+other people, and works alone in a single repository, using the
 following commands.
 
   * gitlink:git-show-branch[1] to see where you are.
@@ -370,7 +370,7 @@ Examples
 Run git-daemon to serve /pub/scm from inetd.::
 +
 ------------
-$ grep git /etc/inet.conf
+$ grep git /etc/inetd.conf
 git    stream  tcp     nowait  nobody \
   /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm
 ------------
index 0b37e2b..d43ef1d 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git-branch' [-r]
-'git-branch' [-f] <branchname> [<start-point>]
+'git-branch' [-l] [-f] <branchname> [<start-point>]
 'git-branch' (-d | -D) <branchname>...
 
 DESCRIPTION
@@ -23,7 +23,8 @@ If no <start-point> is given, the branch will be created with a head
 equal to that of the currently checked out branch.
 
 With a `-d` or `-D` option, `<branchname>` will be deleted.  You may
-specify more than one branch for deletion.
+specify more than one branch for deletion.  If the branch currently
+has a ref log then the ref log will also be deleted.
 
 
 OPTIONS
@@ -34,6 +35,11 @@ OPTIONS
 -D::
        Delete a branch irrespective of its index status.
 
+-l::
+       Create the branch's ref log.  This activates recording of
+       all changes to made the branch ref, enabling use of date
+       based sha1 expressions such as "<branchname>@{yesterday}".
+
 -f::
        Force the creation of a new branch even if it means deleting
        a branch that already exists with the same name.
index d82efc0..fbdbadc 100644 (file)
@@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch
 SYNOPSIS
 --------
 [verse]
-'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>]
+'git-checkout' [-f] [-b <new_branch> [-l]] [-m] [<branch>]
 'git-checkout' [-m] [<branch>] <paths>...
 
 DESCRIPTION
@@ -40,6 +40,11 @@ OPTIONS
        by gitlink:git-check-ref-format[1].  Some of these checks
        may restrict the characters allowed in a branch name.
 
+-l::
+       Create the new branch's ref log.  This activates recording of
+       all changes to made the branch ref, enabling use of date
+       based sha1 expressions such as "<branchname>@{yesterday}".
+
 -m::
        If you have local modifications to one or more files that
        are different between the current branch and the branch to
index 844cfda..1f21d95 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -63,6 +63,15 @@ OPTIONS
 * when both sides adds a path identically.  The resolution
   is to add that path.
 
+--prefix=<prefix>/::
+       Keep the current index contents, and read the contents
+       of named tree-ish under directory at `<prefix>`.  The
+       original index file cannot have anything at the path
+       `<prefix>` itself, and have nothing in `<prefix>/`
+       directory.  Note that the `<prefix>/` value must end
+       with a slash.
+
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
index ab896fc..b894694 100644 (file)
@@ -124,6 +124,13 @@ syntax.
   happen to have both heads/master and tags/master, you can
   explicitly say 'heads/master' to tell git which one you mean.
 
+* A suffix '@' followed by a date specification enclosed in a brace
+  pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
+  second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
+  of the ref at a prior point in time.  This suffix may only be
+  used immediately following a ref name and the ref must have an
+  existing log ($GIT_DIR/logs/<ref>).
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
index 475237f..e062030 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' [-m <reason>] <ref> <newvalue> [<oldvalue>]
 
 DESCRIPTION
 -----------
@@ -49,6 +49,32 @@ for reading but not for writing (so we'll never write through a
 ref symlink to some other tree, if you have copied a whole
 archive by creating a symlink tree).
 
+Logging Updates
+---------------
+If config parameter "core.logAllRefUpdates" is true or the file
+"$GIT_DIR/logs/<ref>" exists then `git-update-ref` will append
+a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
+symbolic refs before creating the log name) describing the change
+in ref value.  Log lines are formatted as:
+
+    . oldsha1 SP newsha1 SP committer LF
++
+Where "oldsha1" is the 40 character hexadecimal value previously
+stored in <ref>, "newsha1" is the 40 character hexadecimal value of
+<newvalue> and "committer" is the committer's name, email address
+and date in the standard GIT committer ident format.
+
+Optionally with -m:
+
+    . oldsha1 SP newsha1 SP committer TAB message LF
++
+Where all fields are as described above and "message" is the
+value supplied to the -m option.
+
+An update will fail (without changing <ref>) if the current user is
+unable to create a new log file, append to the existing log file
+or does not have committer information available.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>.
index 77e12cb..c85fa89 100644 (file)
@@ -8,7 +8,7 @@ git-write-tree - Creates a tree object from the current index
 
 SYNOPSIS
 --------
-'git-write-tree' [--missing-ok]
+'git-write-tree' [--missing-ok] [--prefix=<prefix>/]
 
 DESCRIPTION
 -----------
@@ -30,6 +30,12 @@ OPTIONS
        directory exist in the object database.  This option disables this
        check.
 
+--prefix=<prefix>/::
+       Writes a tree object that represents a subdirectory
+       `<prefix>`.  This can be used to write the tree object
+       for a subproject that is in the named subdirectory.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 98fbe7d..b52dfdc 100644 (file)
@@ -128,3 +128,14 @@ remotes::
        Stores shorthands to be used to give URL and default
        refnames to interact with remote repository to `git
        fetch`, `git pull` and `git push` commands.
+
+logs::
+       Records of changes made to refs are stored in this
+       directory.  See the documentation on git-update-ref
+       for more information.
+
+logs/refs/heads/`name`::
+       Records all changes made to the branch tip named `name`.
+
+logs/refs/tags/`name`::
+       Records all changes made to the tag named `name`.
index ed2decc..0e1ffb2 100644 (file)
@@ -5,8 +5,13 @@ GIT pack format
 
    - The header appears at the beginning and consists of the following:
 
-     4-byte signature
-     4-byte version number (network byte order)
+     4-byte signature:
+         The signature is: {'P', 'A', 'C', 'K'}
+
+     4-byte version number (network byte order):
+         GIT currently accepts version number 2 or 3 but
+         generates version 2 only.
+
      4-byte number of objects contained in the pack (network byte order)
 
      Observation: we cannot have more than 4G versions ;-) and
@@ -41,7 +46,7 @@ GIT pack format
     8-byte integers to go beyond 4G objects per pack, but it is
     not strictly necessary.
 
-  - The header is followed by sorted 28-byte entries, one entry
+  - The header is followed by sorted 24-byte entries, one entry
     per object in the pack.  Each entry is:
 
     4-byte network byte order integer, recording where the
index 08d3453..9c9500c 100644 (file)
@@ -377,7 +377,7 @@ At this point you should know everything necessary to read the man
 pages for any of the git commands; one good place to start would be
 with the commands mentioned in link:everyday.html[Everyday git].  You
 should be able to find any unknown jargon in the
-link:glossary.html[Glosssay].
+link:glossary.html[Glossary].
 
 The link:cvs-migration.html[CVS migration] document explains how to
 import a CVS repository into git, and shows how to use git in a
index 79781ad..039a859 100644 (file)
@@ -429,16 +429,24 @@ $ gitk --since="2 weeks ago" drivers/
 -------------------------------------
 
 allows you to browse any commits from the last 2 weeks of commits
-that modified files under the "drivers" directory.
+that modified files under the "drivers" directory.  (Note: you can
+adjust gitk's fonts by holding down the control key while pressing
+"-" or "+".)
 
 Finally, most commands that take filenames will optionally allow you
 to precede any filename by a commit, to specify a particular version
-fo the file:
+of the file:
 
 -------------------------------------
 $ git diff v2.5:Makefile HEAD:Makefile.in
 -------------------------------------
 
+You can also use "git cat-file -p" to see any such file:
+
+-------------------------------------
+$ git cat-file -p v2.5:Makefile
+-------------------------------------
+
 Next Steps
 ----------
 
index db1912a..6612f4c 100644 (file)
@@ -85,6 +85,23 @@ static int istitlechar(char c)
                (c >= '0' && c <= '9') || c == '.' || c == '_';
 }
 
+static char *extra_headers = NULL;
+static int extra_headers_size = 0;
+
+static int git_format_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "format.headers")) {
+               int len = strlen(value);
+               extra_headers_size += len + 1;
+               extra_headers = realloc(extra_headers, extra_headers_size);
+               extra_headers[extra_headers_size - len - 1] = 0;
+               strcat(extra_headers, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+
 static FILE *realstdout = NULL;
 static char *output_directory = NULL;
 
@@ -150,6 +167,7 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
        int numbered = 0;
        int start_number = -1;
        int keep_subject = 0;
+       char *add_signoff = NULL;
 
        init_revisions(&rev);
        rev.commit_format = CMIT_FMT_EMAIL;
@@ -161,6 +179,9 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
        rev.ignore_merges = 1;
        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 
+       git_config(git_format_config);
+       rev.extra_headers = extra_headers;
+
        /*
         * Parse the arguments before setup_revisions(), or something
         * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is
@@ -179,11 +200,13 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
                        if (i == argc)
                                die("Need a number for --start-number");
                        start_number = strtol(argv[i], NULL, 10);
-               } else if (!strcmp(argv[i], "-k") ||
+               }
+               else if (!strcmp(argv[i], "-k") ||
                                !strcmp(argv[i], "--keep-subject")) {
                        keep_subject = 1;
                        rev.total = -1;
-               } else if (!strcmp(argv[i], "-o")) {
+               }
+               else if (!strcmp(argv[i], "-o")) {
                        if (argc < 3)
                                die ("Which directory?");
                        if (mkdir(argv[i + 1], 0777) < 0 && errno != EEXIST)
@@ -192,6 +215,16 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
                        output_directory = strdup(argv[i + 1]);
                        i++;
                }
+               else if (!strcmp(argv[i], "--signoff") ||
+                        !strcmp(argv[i], "-s")) {
+                       const char *committer = git_committer_info(1);
+                       const char *endpos = strchr(committer, '>');
+                       if (!endpos)
+                               die("bogos committer info %s\n", committer);
+                       add_signoff = xmalloc(endpos - committer + 2);
+                       memcpy(add_signoff, committer, endpos - committer + 1);
+                       add_signoff[endpos - committer + 1] = 0;
+               }
                else if (!strcmp(argv[i], "--attach"))
                        rev.mime_boundary = git_version_string;
                else if (!strncmp(argv[i], "--attach=", 9))
@@ -230,6 +263,7 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
        total = nr;
        if (numbered)
                rev.total = total + start_number - 1;
+       rev.add_signoff = add_signoff;
        while (0 <= --nr) {
                int shown;
                commit = list[nr];
index 10afd46..0c6ba3d 100644 (file)
@@ -24,6 +24,7 @@ static int trivial_merges_only = 0;
 static int aggressive = 0;
 static int verbose_update = 0;
 static volatile int progress_update = 0;
+static const char *prefix = NULL;
 
 static int head_idx = -1;
 static int merge_size = 0;
@@ -411,7 +412,8 @@ static int unpack_trees(merge_fn_t fn)
                        posns[i] = create_tree_entry_list((struct tree *) posn->item);
                        posn = posn->next;
                }
-               if (unpack_trees_rec(posns, len, "", fn, &indpos))
+               if (unpack_trees_rec(posns, len, prefix ? prefix : "",
+                                    fn, &indpos))
                        return -1;
        }
 
@@ -761,6 +763,28 @@ static int twoway_merge(struct cache_entry **src)
 }
 
 /*
+ * Bind merge.
+ *
+ * Keep the index entries at stage0, collapse stage1 but make sure
+ * stage0 does not have anything there.
+ */
+static int bind_merge(struct cache_entry **src)
+{
+       struct cache_entry *old = src[0];
+       struct cache_entry *a = src[1];
+
+       if (merge_size != 1)
+               return error("Cannot do a bind merge of %d trees\n",
+                            merge_size);
+       if (a && old)
+               die("Entry '%s' overlaps.  Cannot bind.", a->name);
+       if (!a)
+               return keep_entry(old);
+       else
+               return merged_entry(a, NULL);
+}
+
+/*
  * One-way merge.
  *
  * The rule is:
@@ -775,8 +799,10 @@ static int oneway_merge(struct cache_entry **src)
                return error("Cannot do a oneway merge of %d trees",
                             merge_size);
 
-       if (!a)
+       if (!a) {
+               invalidate_ce_path(old);
                return deleted_entry(old, old);
+       }
        if (old && same(old, a)) {
                if (reset) {
                        struct stat st;
@@ -849,7 +875,7 @@ static void prime_cache_tree(void)
 
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])";
 
 static struct cache_file cache_file;
 
@@ -894,9 +920,24 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
                        continue;
                }
 
+               /* "--prefix=<subdirectory>/" means keep the current index
+                *  entries and put the entries from the tree under the
+                * given subdirectory.
+                */
+               if (!strncmp(arg, "--prefix=", 9)) {
+                       if (stage || merge || prefix)
+                               usage(read_tree_usage);
+                       prefix = arg + 9;
+                       merge = 1;
+                       stage = 1;
+                       if (read_cache_unmerged())
+                               die("you need to resolve your current index first");
+                       continue;
+               }
+
                /* This differs from "-m" in that we'll silently ignore unmerged entries */
                if (!strcmp(arg, "--reset")) {
-                       if (stage || merge)
+                       if (stage || merge || prefix)
                                usage(read_tree_usage);
                        reset = 1;
                        merge = 1;
@@ -917,7 +958,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
 
                /* "-m" stands for "merge", meaning we start in stage 1 */
                if (!strcmp(arg, "-m")) {
-                       if (stage || merge)
+                       if (stage || merge || prefix)
                                usage(read_tree_usage);
                        if (read_cache_unmerged())
                                die("you need to resolve your current index first");
@@ -939,12 +980,31 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
        if ((update||index_only) && !merge)
                usage(read_tree_usage);
 
+       if (prefix) {
+               int pfxlen = strlen(prefix);
+               int pos;
+               if (prefix[pfxlen-1] != '/')
+                       die("prefix must end with /");
+               if (stage != 2)
+                       die("binding merge takes only one tree");
+               pos = cache_name_pos(prefix, pfxlen);
+               if (0 <= pos)
+                       die("corrupt index file");
+               pos = -pos-1;
+               if (pos < active_nr &&
+                   !strncmp(active_cache[pos]->name, prefix, pfxlen))
+                       die("subdirectory '%s' already exists.", prefix);
+               pos = cache_name_pos(prefix, pfxlen-1);
+               if (0 <= pos)
+                       die("file '%.*s' already exists.", pfxlen-1, prefix);
+       }
+
        if (merge) {
                if (stage < 2)
                        die("just how do you expect me to merge %d trees?", stage-1);
                switch (stage - 1) {
                case 1:
-                       fn = oneway_merge;
+                       fn = prefix ? bind_merge : oneway_merge;
                        break;
                case 2:
                        fn = twoway_merge;
index a880c97..d9f7e1e 100644 (file)
@@ -529,3 +529,29 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
                return NULL; /* not the whole tree */
        return read_one(&buffer, &size);
 }
+
+struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
+{
+       while (*path) {
+               const char *slash;
+               struct cache_tree_sub *sub;
+
+               slash = strchr(path, '/');
+               if (!slash)
+                       slash = path + strlen(path);
+               /* between path and slash is the name of the
+                * subtree to look for.
+                */
+               sub = find_subtree(it, path, slash - path, 0);
+               if (!sub)
+                       return NULL;
+               it = sub->cache_tree;
+               if (slash)
+                       while (*slash && *slash == '/')
+                               slash++;
+               if (!slash || !*slash)
+                       return it; /* prefix ended with slashes */
+               path = slash;
+       }
+       return it;
+}
index 72c6480..119407e 100644 (file)
@@ -28,4 +28,6 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
 int cache_tree_fully_valid(struct cache_tree *);
 int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
 
+struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
+
 #endif
diff --git a/cache.h b/cache.h
index f11d5e7..d530af9 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -179,6 +179,7 @@ extern void rollback_index_file(struct cache_file *);
 extern int trust_executable_bit;
 extern int assume_unchanged;
 extern int prefer_symlink_refs;
+extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int diff_rename_limit_default;
 extern int shared_repository;
index 0248c6d..2ae6153 100644 (file)
--- a/config.c
+++ b/config.c
@@ -269,6 +269,11 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.logallrefupdates")) {
+               log_all_ref_updates = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "core.warnambiguousrefs")) {
                warn_ambiguous_refs = git_config_bool(var, value);
                return 0;
index b3e0684..aac8779 100755 (executable)
@@ -567,7 +567,6 @@ sub precommit_check {
 sub svn_checkout_tree {
        my ($svn_rev, $treeish) = @_;
        my $from = file_to_s("$REV_DIR/$svn_rev");
-       assert_svn_wc_clean($svn_rev);
        assert_tree($from);
        print "diff-tree $from $treeish\n";
        my $pid = open my $diff_fh, '-|';
index 6fa7889..23a5a2a 100644 (file)
@@ -20,9 +20,10 @@ a_empty_cr=
 a_empty_crlf=
 
 cd import
-       cat >> kw.c <<''
+       cat >> kw.c <<\EOF
 /* Make it look like somebody copied a file from CVS into SVN: */
 /* $Id: kw.c,v 1.1.1.1 1994/03/06 00:00:00 eric Exp $ */
+EOF
 
        printf "Hello\r\nWorld\r\n" > crlf
        a_crlf=`git-hash-object -w crlf`
index 444c99e..2e79eab 100644 (file)
@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];
 int trust_executable_bit = 1;
 int assume_unchanged = 0;
 int prefer_symlink_refs = 0;
+int log_all_ref_updates = 0;
 int warn_ambiguous_refs = 1;
 int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
index 44bb2f2..c1539d1 100644 (file)
@@ -21,7 +21,7 @@ const char *git_exec_path(void)
                return current_exec_path;
 
        env = getenv("GIT_EXEC_PATH");
-       if (env) {
+       if (env && *env) {
                return env;
        }
 
@@ -32,22 +32,25 @@ const char *git_exec_path(void)
 int execv_git_cmd(const char **argv)
 {
        char git_command[PATH_MAX + 1];
-       int len,  i;
+       int i;
        const char *paths[] = { current_exec_path,
                                getenv("GIT_EXEC_PATH"),
                                builtin_exec_path };
 
        for (i = 0; i < ARRAY_SIZE(paths); ++i) {
+               size_t len;
+               int rc;
                const char *exec_dir = paths[i];
                const char *tmp;
 
-               if (!exec_dir) continue;
+               if (!exec_dir || !*exec_dir) continue;
 
                if (*exec_dir != '/') {
                        if (!getcwd(git_command, sizeof(git_command))) {
                                fprintf(stderr, "git: cannot determine "
-                                       "current directory\n");
-                               exit(1);
+                                       "current directory: %s\n",
+                                       strerror(errno));
+                               break;
                        }
                        len = strlen(git_command);
 
@@ -57,17 +60,28 @@ int execv_git_cmd(const char **argv)
                                while (*exec_dir == '/')
                                        exec_dir++;
                        }
-                       snprintf(git_command + len, sizeof(git_command) - len,
-                                "/%s", exec_dir);
+
+                       rc = snprintf(git_command + len,
+                                     sizeof(git_command) - len, "/%s",
+                                     exec_dir);
+                       if (rc < 0 || rc >= sizeof(git_command) - len) {
+                               fprintf(stderr, "git: command name given "
+                                       "is too long.\n");
+                               break;
+                       }
                } else {
+                       if (strlen(exec_dir) + 1 > sizeof(git_command)) {
+                               fprintf(stderr, "git: command name given "
+                                       "is too long.\n");
+                               break;
+                       }
                        strcpy(git_command, exec_dir);
                }
 
                len = strlen(git_command);
-               len += snprintf(git_command + len, sizeof(git_command) - len,
-                               "/git-%s", argv[0]);
-
-               if (sizeof(git_command) <= len) {
+               rc = snprintf(git_command + len, sizeof(git_command) - len,
+                             "/git-%s", argv[0]);
+               if (rc < 0 || rc >= sizeof(git_command) - len) {
                        fprintf(stderr,
                                "git: command name given is too long.\n");
                        break;
index 8daa93d..8371348 100644 (file)
@@ -18,6 +18,12 @@ static const char *exec = "git-upload-pack";
 #define SEEN           (1U << 3)
 #define POPPED         (1U << 4)
 
+/*
+ * After sending this many "have"s if we do not get any new ACK , we
+ * give up traversing our history.
+ */
+#define MAX_IN_VAIN 256
+
 static struct commit_list *rev_list = NULL;
 static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0;
 
@@ -134,6 +140,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        int fetching;
        int count = 0, flushes = 0, retval;
        const unsigned char *sha1;
+       unsigned in_vain = 0;
+       int got_continue = 0;
 
        for_each_ref(rev_list_insert_ref);
 
@@ -172,6 +180,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
                if (verbose)
                        fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
+               in_vain++;
                if (!(31 & ++count)) {
                        int ack;
 
@@ -200,9 +209,16 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                                                lookup_commit(result_sha1);
                                        mark_common(commit, 0, 1);
                                        retval = 0;
+                                       in_vain = 0;
+                                       got_continue = 1;
                                }
                        } while (ack);
                        flushes--;
+                       if (got_continue && MAX_IN_VAIN < in_vain) {
+                               if (verbose)
+                                       fprintf(stderr, "giving up\n");
+                               break; /* give up */
+                       }
                }
        }
 done:
diff --git a/fetch.c b/fetch.c
index 107504b..e040ef9 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -9,6 +9,7 @@
 #include "refs.h"
 
 const char *write_ref = NULL;
+const char *write_ref_log_details = NULL;
 
 int get_tree = 0;
 int get_history = 0;
@@ -215,23 +216,51 @@ static int mark_complete(const char *path, const unsigned char *sha1)
 
 int pull(char *target)
 {
+       struct ref_lock *lock = NULL;
        unsigned char sha1[20];
+       char *msg;
+       int ret;
 
        save_commit_buffer = 0;
        track_object_refs = 0;
+       if (write_ref) {
+               lock = lock_ref_sha1(write_ref, NULL, 0);
+               if (!lock) {
+                       error("Can't lock ref %s", write_ref);
+                       return -1;
+               }
+       }
 
        if (!get_recover)
                for_each_ref(mark_complete);
 
-       if (interpret_target(target, sha1))
-               return error("Could not interpret %s as something to pull",
-                            target);
-       if (process(lookup_unknown_object(sha1)))
+       if (interpret_target(target, sha1)) {
+               error("Could not interpret %s as something to pull", target);
+               if (lock)
+                       unlock_ref(lock);
                return -1;
-       if (loop())
+       }
+       if (process(lookup_unknown_object(sha1))) {
+               if (lock)
+                       unlock_ref(lock);
                return -1;
-       
-       if (write_ref)
-               write_ref_sha1_unlocked(write_ref, sha1);
+       }
+       if (loop()) {
+               if (lock)
+                       unlock_ref(lock);
+               return -1;
+       }
+
+       if (write_ref) {
+               if (write_ref_log_details) {
+                       msg = xmalloc(strlen(write_ref_log_details) + 12);
+                       sprintf(msg, "fetch from %s", write_ref_log_details);
+               } else
+                       msg = NULL;
+               ret = write_ref_sha1(lock, sha1, msg ? msg : "fetch (unknown)");
+               if (msg)
+                       free(msg);
+               return ret;
+       }
        return 0;
 }
diff --git a/fetch.h b/fetch.h
index 001a6b8..841bb1a 100644 (file)
--- a/fetch.h
+++ b/fetch.h
@@ -25,6 +25,9 @@ extern int fetch_ref(char *ref, unsigned char *sha1);
 /* If set, the ref filename to write the target value to. */
 extern const char *write_ref;
 
+/* If set additional text will appear in the ref log. */
+extern const char *write_ref_log_details;
+
 /* Set to fetch the target tree. */
 extern int get_tree;
 
index 97ec2d0..4232e27 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -413,7 +413,7 @@ do
        parent=$(git-rev-parse --verify HEAD) &&
        commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
        echo Committed: $commit &&
-       git-update-ref HEAD $commit $parent ||
+       git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
        stop_here $this
 
        if test -x "$GIT_DIR"/hooks/post-applypatch
index 12cab1e..e4b0947 100755 (executable)
@@ -204,7 +204,7 @@ echo Wrote tree $tree
 parent=$(git-rev-parse --verify HEAD) &&
 commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
 echo Committed: $commit
-git-update-ref HEAD $commit $parent || exit
+git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit
 
 if test -x "$GIT_DIR"/hooks/post-applypatch
 then
index 134e68c..e0501ec 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
+USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
 LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
 If one argument, create a new branch <branchname> based off of current HEAD.
 If two arguments, create a new branch <branchname> based off of <start-point>.'
@@ -42,6 +42,7 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'."
            esac
            ;;
        esac
+       rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
        rm -f "$GIT_DIR/refs/heads/$branch_name"
        echo "Deleted branch $branch_name."
     done
@@ -55,6 +56,7 @@ ls_remote_branches () {
 }
 
 force=
+create_log=
 while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
 do
        case "$1" in
@@ -69,6 +71,9 @@ do
        -f)
                force="$1"
                ;;
+       -l)
+               create_log="yes"
+               ;;
        --)
                shift
                break
@@ -117,4 +122,9 @@ then
                die "cannot force-update the current branch."
        fi
 fi
-git update-ref "refs/heads/$branchname" $rev
+if test "$create_log" = 'yes'
+then
+       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
+       touch "$GIT_DIR/logs/refs/heads/$branchname"
+fi
+git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
index a11c939..564117f 100755 (executable)
@@ -5,10 +5,13 @@ SUBDIRECTORY_OK=Sometimes
 . git-sh-setup
 
 old=$(git-rev-parse HEAD)
+old_name=HEAD
 new=
+new_name=
 force=
 branch=
 newbranch=
+newbranch_log=
 merge=
 while [ "$#" != "0" ]; do
     arg="$1"
@@ -24,6 +27,9 @@ while [ "$#" != "0" ]; do
                git-check-ref-format "heads/$newbranch" ||
                        die "git checkout: we do not like '$newbranch' as a branch name."
                ;;
+       "-l")
+               newbranch_log=1
+               ;;
        "-f")
                force=1
                ;;
@@ -44,6 +50,7 @@ while [ "$#" != "0" ]; do
                                exit 1
                        fi
                        new="$rev"
+                       new_name="$arg^0"
                        if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
                                branch="$arg"
                        fi
@@ -51,9 +58,11 @@ while [ "$#" != "0" ]; do
                then
                        # checking out selected paths from a tree-ish.
                        new="$rev"
+                       new_name="$arg^{tree}"
                        branch=
                else
                        new=
+                       new_name=
                        branch=
                        set x "$arg" "$@"
                        shift
@@ -114,7 +123,7 @@ then
        cd "$cdup"
 fi
 
-[ -z "$new" ] && new=$old
+[ -z "$new" ] && new=$old && new_name="$old_name"
 
 # If we don't have an old branch that we're switching to,
 # and we don't have a new branch name for the target we
@@ -187,9 +196,11 @@ fi
 #
 if [ "$?" -eq 0 ]; then
        if [ "$newbranch" ]; then
-               leading=`expr "refs/heads/$newbranch" : '\(.*\)/'` &&
-               mkdir -p "$GIT_DIR/$leading" &&
-               echo $new >"$GIT_DIR/refs/heads/$newbranch" || exit
+               if [ "$newbranch_log" ]; then
+                       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
+                       touch "$GIT_DIR/logs/refs/heads/$newbranch"
+               fi
+               git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
                branch="$newbranch"
        fi
        [ "$branch" ] &&
index bb56264..3834323 100755 (executable)
@@ -19,8 +19,8 @@ ignored=
 ignoredonly=
 cleandir=
 quiet=
-rmf="rm -f"
-rmrf="rm -rf"
+rmf="rm -f --"
+rmrf="rm -rf --"
 rm_refuse="echo Not removing"
 echo1="echo"
 
index 1983d45..6dd04fd 100755 (executable)
@@ -260,20 +260,41 @@ do
   -m|--m|--me|--mes|--mess|--messa|--messag|--message)
       case "$#" in 1) usage ;; esac
       shift
-      log_given=t$log_given
-      log_message="$1"
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message="$1"
+      else
+          log_message="$log_message
+
+$1"
+      fi
       no_edit=t
       shift
       ;;
   -m*)
-      log_given=t$log_given
-      log_message=`expr "$1" : '-m\(.*\)'`
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message=`expr "$1" : '-m\(.*\)'`
+      else
+          log_message="$log_message
+
+`expr "$1" : '-m\(.*\)'`"
+      fi
       no_edit=t
       shift
       ;;
   --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
-      log_given=t$log_given
-      log_message=`expr "$1" : '-[^=]*=\(.*\)'`
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message=`expr "$1" : '-[^=]*=\(.*\)'`
+      else
+          log_message="$log_message
+
+`expr "$1" : '-[^=]*=\(.*\)'`"
+      fi
       no_edit=t
       shift
       ;;
@@ -378,7 +399,9 @@ esac
 
 case "$log_given" in
 tt*)
-  die "Only one of -c/-C/-F/-m can be used." ;;
+  die "Only one of -c/-C/-F can be used." ;;
+*tm*|*mt*)
+  die "Option -m cannot be combined with -c/-C/-F." ;;
 esac
 
 case "$#,$also,$only,$amend" in
@@ -690,7 +713,8 @@ then
                rm -f "$TMP_INDEX"
        fi &&
        commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
-       git-update-ref HEAD $commit $current &&
+       rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
+       git-update-ref -m "commit: $rlogm" HEAD $commit $current &&
        rm -f -- "$GIT_DIR/MERGE_HEAD" &&
        if test -f "$NEXT_INDEX"
        then
index 0ee3e3e..296f3b7 100755 (executable)
@@ -48,7 +48,7 @@ then
 else
        rm -f "$GIT_DIR/ORIG_HEAD"
 fi
-git-update-ref HEAD "$rev"
+git-update-ref -m "reset $reset_type $@" HEAD "$rev"
 
 case "$reset_type" in
 --hard )
index 312a4ea..ed1d89b 100755 (executable)
@@ -21,7 +21,6 @@ use warnings;
 use Term::ReadLine;
 use Getopt::Long;
 use Data::Dumper;
-use Net::SMTP;
 
 # most mail servers generate the Date: header, but not all...
 $ENV{LC_ALL} = 'C';
@@ -37,7 +36,8 @@ sub cleanup_compose_files();
 my $compose_filename = ".msg.$$";
 
 # Variables we fill in automatically, or via prompting:
-my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
+my (@to,@cc,@initial_cc,@bcclist,
+       $initial_reply_to,$initial_subject,@files,$from,$compose,$time);
 
 # Behavior modification variables
 my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
@@ -56,6 +56,7 @@ my $rc = GetOptions("from=s" => \$from,
                    "subject=s" => \$initial_subject,
                    "to=s" => \@to,
                    "cc=s" => \@initial_cc,
+                   "bcc=s" => \@bcclist,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
                    "compose" => \$compose,
@@ -160,6 +161,7 @@ sub expand_aliases {
 
 @to = expand_aliases(@to);
 @initial_cc = expand_aliases(@initial_cc);
+@bcclist = expand_aliases(@bcclist);
 
 if (!defined $initial_subject && $compose) {
        do {
@@ -269,6 +271,9 @@ Options:
    --cc           Specify an initial "Cc:" list for the entire series
                   of emails.
 
+   --bcc          Specify a list of email addresses that should be Bcc:
+                 on all the emails.
+
    --compose      Use \$EDITOR to edit an introductory message for the
                   patch series.
 
@@ -303,7 +308,7 @@ EOT
 }
 
 # Variables we set as part of the loop over files
-our ($message_id, $cc, %mail, $subject, $reply_to, $message);
+our ($message_id, $cc, %mail, $subject, $reply_to, $references, $message);
 
 sub extract_valid_address {
        my $address = shift;
@@ -316,7 +321,11 @@ sub extract_valid_address {
        } else {
                # less robust/correct than the monster regexp in Email::Valid,
                # but still does a 99% job, and one less dependency
-               return ($address =~ /([^\"<>\s]+@[^<>\s]+)/);
+               my $cleaned_address;
+               if ($address =~ /([^\"<>\s]+@[^<>\s]+)/) {
+                       $cleaned_address = $1;
+               }
+               return $cleaned_address;
        }
 }
 
@@ -348,7 +357,7 @@ sub send_message
 {
        my @recipients = unique_email_list(@to);
        my $to = join (",\n\t", @recipients);
-       @recipients = unique_email_list(@recipients,@cc);
+       @recipients = unique_email_list(@recipients,@cc,@bcclist);
        my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
        my $gitversion = '@@GIT_VERSION@@';
        if ($gitversion =~ m/..GIT_VERSION../) {
@@ -367,17 +376,24 @@ Date: $date
 Message-Id: $message_id
 X-Mailer: git-send-email $gitversion
 ";
-       $header .= "In-Reply-To: $reply_to\n" if $reply_to;
+       if ($reply_to) {
+
+               $header .= "In-Reply-To: $reply_to\n";
+               $header .= "References: $references\n";
+       }
 
        if ($smtp_server =~ m#^/#) {
                my $pid = open my $sm, '|-';
                defined $pid or die $!;
                if (!$pid) {
-                       exec($smtp_server,'-i',@recipients) or die $!;
+                       exec($smtp_server,'-i',
+                            map { scalar extract_valid_address($_) }
+                            @recipients) or die $!;
                }
                print $sm "$header\n$message";
                close $sm or die $?;
        } else {
+               require Net::SMTP;
                $smtp ||= Net::SMTP->new( $smtp_server );
                $smtp->mail( $from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
@@ -406,6 +422,7 @@ X-Mailer: git-send-email $gitversion
 }
 
 $reply_to = $initial_reply_to;
+$references = $initial_reply_to || '';
 make_message_id();
 $subject = $initial_subject;
 
@@ -482,6 +499,11 @@ foreach my $t (@files) {
        # set up for the next message
        if ($chain_reply_to || length($reply_to) == 0) {
                $reply_to = $message_id;
+               if (length $references > 0) {
+                       $references .= " $message_id";
+               } else {
+                       $references = "$message_id";
+               }
        }
        make_message_id();
 }
index 61f559f..38ac732 100755 (executable)
@@ -63,10 +63,17 @@ my $svn_dir = $ARGV[1];
 
 our @mergerx = ();
 if ($opt_m) {
-       @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+       my $branch_esc = quotemeta ($branch_name);
+       my $trunk_esc  = quotemeta ($trunk_name);
+       @mergerx =
+       (
+               qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+               qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+               qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i
+       );
 }
 if ($opt_M) {
-       push (@mergerx, qr/$opt_M/);
+       unshift (@mergerx, qr/$opt_M/);
 }
 
 # Absolutize filename now, since we will have chdir'ed by the time we
diff --git a/gitk b/gitk
index 4aa57c0..101cf9b 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -34,10 +34,10 @@ proc start_rev_list {view} {
        set order "--date-order"
     }
     if {[catch {
-       set fd [open [concat | git-rev-list --header $order \
+       set fd [open [concat | git rev-list --header $order \
                          --parents --boundary --default HEAD $args] r]
     } err]} {
-       puts stderr "Error executing git-rev-list: $err"
+       puts stderr "Error executing git rev-list: $err"
        exit 1
     }
     set commfd($view) $fd
@@ -94,10 +94,10 @@ proc getcommitlines {fd view}  {
            }
            if {[string range $err 0 4] == "usage"} {
                set err "Gitk: error reading commits$fv:\
-                       bad arguments to git-rev-list."
+                       bad arguments to git rev-list."
                if {$viewname($view) eq "Command line"} {
                    append err \
-                       "  (Note: arguments to gitk are passed to git-rev-list\
+                       "  (Note: arguments to gitk are passed to git rev-list\
                         to allow selection of commits to be displayed.)"
                }
            } else {
@@ -148,7 +148,7 @@ proc getcommitlines {fd view}  {
            if {[string length $shortcmit] > 80} {
                set shortcmit "[string range $shortcmit 0 80]..."
            }
-           error_popup "Can't parse git-rev-list output: {$shortcmit}"
+           error_popup "Can't parse git rev-list output: {$shortcmit}"
            exit 1
        }
        set id [lindex $ids 0]
@@ -217,7 +217,7 @@ proc doupdate {} {
 }
 
 proc readcommit {id} {
-    if {[catch {set contents [exec git-cat-file commit $id]}]} return
+    if {[catch {set contents [exec git cat-file commit $id]}]} return
     parsecommit $id $contents 0
 }
 
@@ -276,8 +276,8 @@ proc parsecommit {id contents listed} {
        set headline $comment
     }
     if {!$listed} {
-       # git-rev-list indents the comment by 4 spaces;
-       # if we got this via git-cat-file, add the indentation
+       # git rev-list indents the comment by 4 spaces;
+       # if we got this via git cat-file, add the indentation
        set newcomment {}
        foreach line [split $comment "\n"] {
            append newcomment "    "
@@ -337,14 +337,14 @@ proc readrefs {} {
            set type {}
            set tag {}
            catch {
-               set commit [exec git-rev-parse "$id^0"]
+               set commit [exec git rev-parse "$id^0"]
                if {"$commit" != "$id"} {
                    set tagids($name) $commit
                    lappend idtags($commit) $name
                }
            }           
            catch {
-               set tagcontents($name) [exec git-cat-file tag "$id"]
+               set tagcontents($name) [exec git cat-file tag "$id"]
            }
        } elseif { $type == "heads" } {
            set headids($name) $id
@@ -357,21 +357,21 @@ proc readrefs {} {
     close $refd
 }
 
-proc show_error {w msg} {
+proc show_error {w top msg} {
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text OK -command "destroy $w"
+    button $w.ok -text OK -command "destroy $top"
     pack $w.ok -side bottom -fill x
-    bind $w <Visibility> "grab $w; focus $w"
-    bind $w <Key-Return> "destroy $w"
-    tkwait window $w
+    bind $top <Visibility> "grab $top; focus $top"
+    bind $top <Key-Return> "destroy $top"
+    tkwait window $top
 }
 
 proc error_popup msg {
     set w .error
     toplevel $w
     wm transient $w .
-    show_error $w $msg
+    show_error $w $w $msg
 }
 
 proc makewindow {} {
@@ -380,7 +380,7 @@ proc makewindow {} {
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
     global maincursor textcursor curtextcursor
-    global rowctxmenu mergemax
+    global rowctxmenu mergemax wrapcomment
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
@@ -527,6 +527,7 @@ proc makewindow {} {
     pack $ctext -side left -fill both -expand 1
     .ctop.cdet add .ctop.cdet.left
 
+    $ctext tag conf comment -wrap $wrapcomment
     $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
     $ctext tag conf hunksep -fore blue
     $ctext tag conf d0 -fore red
@@ -696,7 +697,7 @@ proc savestuff {w} {
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth
     global viewname viewfiles viewargs viewperm nextviewnum
-    global cmitmode
+    global cmitmode wrapcomment
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -709,6 +710,7 @@ proc savestuff {w} {
        puts $f [list set maxgraphpct $maxgraphpct]
        puts $f [list set maxwidth $maxwidth]
        puts $f [list set cmitmode $cmitmode]
+       puts $f [list set wrapcomment $wrapcomment]
        puts $f "set geometry(width) [winfo width .ctop]"
        puts $f "set geometry(height) [winfo height .ctop]"
        puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
@@ -1285,7 +1287,7 @@ proc vieweditor {top n title} {
     checkbutton $top.perm -text "Remember this view" -variable newviewperm($n)
     grid $top.perm - -pady 5 -sticky w
     message $top.al -aspect 1000 -font $uifont \
-       -text "Commits to include (arguments to git-rev-list):"
+       -text "Commits to include (arguments to git rev-list):"
     grid $top.al - -sticky w -pady 5
     entry $top.args -width 50 -textvariable newviewargs($n) \
        -background white
@@ -2939,7 +2941,7 @@ proc findpatches {} {
     }
 
     if {[catch {
-       set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
+       set f [open [list | git diff-tree --stdin -s -r -S$findstring \
                         << $inputids] r]
     } err]} {
        error_popup "Error starting search process: $err"
@@ -2971,7 +2973,7 @@ proc readfindproc {} {
        return
     }
     if {![regexp {^[0-9a-f]{40}} $line id]} {
-       error_popup "Can't parse git-diff-tree output: $line"
+       error_popup "Can't parse git diff-tree output: $line"
        stopfindproc
        return
     }
@@ -3036,10 +3038,10 @@ proc findfiles {} {
        if {$l == $findstartline} break
     }
 
-    # start off a git-diff-tree process if needed
+    # start off a git diff-tree process if needed
     if {$diffsneeded ne {}} {
        if {[catch {
-           set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
+           set df [open [list | git diff-tree -r --stdin << $diffsneeded] r]
        } err ]} {
            error_popup "Error starting search process: $err"
            return
@@ -3069,7 +3071,7 @@ proc readfilediffs {df} {
            if {[catch {close $df} err]} {
                stopfindproc
                bell
-               error_popup "Error in git-diff-tree: $err"
+               error_popup "Error in git diff-tree: $err"
            } elseif {[info exists findid]} {
                set id $findid
                stopfindproc
@@ -3096,7 +3098,7 @@ proc donefilediff {} {
     if {[info exists fdiffid]} {
        while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffid
               && $fdiffpos < [llength $fdiffsneeded]} {
-           # git-diff-tree doesn't output anything for a commit
+           # git diff-tree doesn't output anything for a commit
            # which doesn't change anything
            set nullid [lindex $fdiffsneeded $fdiffpos]
            set treediffs($nullid) {}
@@ -3213,8 +3215,11 @@ proc selcanvline {w x y} {
 
 proc commit_descriptor {p} {
     global commitinfo
+    if {![info exists commitinfo($p)]} {
+       getcommit $p
+    }
     set l "..."
-    if {[info exists commitinfo($p)]} {
+    if {[llength $commitinfo($p)] > 1} {
        set l [lindex $commitinfo($p) 0]
     }
     return "$p ($l)"
@@ -3222,11 +3227,11 @@ proc commit_descriptor {p} {
 
 # append some text to the ctext widget, and make any SHA1 ID
 # that we know about be a clickable link.
-proc appendwithlinks {text} {
+proc appendwithlinks {text tags} {
     global ctext commitrow linknum curview
 
     set start [$ctext index "end - 1c"]
-    $ctext insert end $text
+    $ctext insert end $text $tags
     $ctext insert end "\n"
     set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
     foreach l $links {
@@ -3354,7 +3359,7 @@ proc selectline {l isnew} {
        $ctext insert end "\n"
     }
  
-    set comment {}
+    set headers {}
     set olds [lindex $parentlist $l]
     if {[llength $olds] > 1} {
        set np 0
@@ -3365,23 +3370,22 @@ proc selectline {l isnew} {
                set tag m$np
            }
            $ctext insert end "Parent: " $tag
-           appendwithlinks [commit_descriptor $p]
+           appendwithlinks [commit_descriptor $p] {}
            incr np
        }
     } else {
        foreach p $olds {
-           append comment "Parent: [commit_descriptor $p]\n"
+           append headers "Parent: [commit_descriptor $p]\n"
        }
     }
 
     foreach c [lindex $childlist $l] {
-       append comment "Child:  [commit_descriptor $c]\n"
+       append headers "Child:  [commit_descriptor $c]\n"
     }
-    append comment "\n"
-    append comment [lindex $info 5]
 
     # make anything that looks like a SHA1 ID be a clickable link
-    appendwithlinks $comment
+    appendwithlinks $headers {}
+    appendwithlinks [lindex $info 5] {comment}
 
     $ctext tag delete Comments
     $ctext tag remove found 1.0 end
@@ -3426,6 +3430,7 @@ proc selnextpage {dir} {
        set lpp 1
     }
     allcanvs yview scroll [expr {$dir * $lpp}] units
+    drawvisible
     if {![info exists selectedline]} return
     set l [expr {$selectedline + $dir * $lpp}]
     if {$l < 0} {
@@ -3521,7 +3526,7 @@ proc gettree {id} {
     catch {unset diffmergeid}
     if {![info exists treefilelist($id)]} {
        if {![info exists treepending]} {
-           if {[catch {set gtf [open [concat | git-ls-tree -r $id] r]}]} {
+           if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} {
                return
            }
            set treepending $id
@@ -3569,7 +3574,7 @@ proc showfile {f} {
        return
     }
     set blob [lindex $treeidlist($diffids) $i]
-    if {[catch {set bf [open [concat | git-cat-file blob $blob] r]} err]} {
+    if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
        puts "oops, error reading blob $blob: $err"
        return
     }
@@ -3611,7 +3616,7 @@ proc mergediff {id l} {
     set diffids $id
     # this doesn't seem to actually affect anything...
     set env(GIT_DIFF_OPTS) $diffopts
-    set cmd [concat | git-diff-tree --no-commit-id --cc $id]
+    set cmd [concat | git diff-tree --no-commit-id --cc $id]
     if {[catch {set mdf [open $cmd r]} err]} {
        error_popup "Error getting merge diffs: $err"
        return
@@ -3723,7 +3728,7 @@ proc gettreediffs {ids} {
     set treepending $ids
     set treediff {}
     if {[catch \
-        {set gdtf [open [concat | git-diff-tree --no-commit-id -r $ids] r]} \
+        {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \
        ]} return
     fconfigure $gdtf -blocking 0
     fileevent $gdtf readable [list gettreediffline $gdtf $ids]
@@ -3759,7 +3764,7 @@ proc getblobdiffs {ids} {
     global nextupdate diffinhdr treediffs
 
     set env(GIT_DIFF_OPTS) $diffopts
-    set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
+    set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids]
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
        return
@@ -4296,7 +4301,7 @@ proc mkpatchgo {} {
     set oldid [$patchtop.fromsha1 get]
     set newid [$patchtop.tosha1 get]
     set fname [$patchtop.fname get]
-    if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
+    if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} {
        error_popup "Error creating patch: $err"
     }
     catch {destroy $patchtop}
@@ -4504,7 +4509,7 @@ proc showtag {tag isnew} {
     } else {
        set text "Tag: $tag\nId:  $tagids($tag)"
     }
-    appendwithlinks $text
+    appendwithlinks $text {}
     $ctext conf -state disabled
     init_flist {}
 }
@@ -4863,11 +4868,11 @@ proc tcl_encoding {enc} {
 # defaults...
 set datemode 0
 set diffopts "-U 5 -p"
-set wrcomcmd "git-diff-tree --stdin -p --pretty"
+set wrcomcmd "git diff-tree --stdin -p --pretty"
 
 set gitencoding {}
 catch {
-    set gitencoding [exec git-repo-config --get i18n.commitencoding]
+    set gitencoding [exec git repo-config --get i18n.commitencoding]
 }
 if {$gitencoding == ""} {
     set gitencoding "utf-8"
@@ -4890,6 +4895,7 @@ set downarrowlen 7
 set mingaplen 30
 set flistmode "flat"
 set cmitmode "patch"
+set wrapcomment "none"
 
 set colors {green red blue magenta darkgrey brown orange}
 
@@ -4911,7 +4917,7 @@ foreach arg $argv {
 # check that we can find a .git directory somewhere...
 set gitdir [gitdir]
 if {![file isdirectory $gitdir]} {
-    show_error . "Cannot find the git directory \"$gitdir\"."
+    show_error {} . "Cannot find the git directory \"$gitdir\"."
     exit 1
 }
 
@@ -4922,7 +4928,7 @@ if {$i >= 0} {
     set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
 } elseif {$revtreeargs ne {}} {
     if {[catch {
-       set f [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
+       set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
        set cmdline_files [split $f "\n"]
        set n [llength $cmdline_files]
        set revtreeargs [lrange $revtreeargs 0 end-$n]
@@ -4931,9 +4937,9 @@ if {$i >= 0} {
        # so look for "fatal:".
        set i [string first "fatal:" $err]
        if {$i > 0} {
-           set err [string range [expr {$i + 6}] end]
+           set err [string range $err [expr {$i + 6}] end]
        }
-       show_error . "Bad arguments to gitk:\n$err"
+       show_error {} . "Bad arguments to gitk:\n$err"
        exit 1
     }
 }
index 861644b..661c909 100644 (file)
@@ -1223,6 +1223,7 @@ int main(int argc, char **argv)
        int rc = 0;
 
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -1249,6 +1250,7 @@ int main(int argc, char **argv)
        }
        commit_id = argv[arg];
        url = argv[arg + 1];
+       write_ref_log_details = url;
 
        http_init();
 
@@ -1269,10 +1271,10 @@ int main(int argc, char **argv)
        if (pull(commit_id))
                rc = 1;
 
-       curl_slist_free_all(no_pragma_header);
-
        http_cleanup();
 
+       curl_slist_free_all(no_pragma_header);
+
        if (corrupt_object_found) {
                fprintf(stderr,
 "Some loose object were found to be corrupt, but they might be just\n"
diff --git a/http.c b/http.c
index 0cb42a8..146cf7b 100644 (file)
--- a/http.c
+++ b/http.c
@@ -25,7 +25,6 @@ long curl_low_speed_limit = -1;
 long curl_low_speed_time = -1;
 
 struct curl_slist *pragma_header;
-struct curl_slist *no_range_header;
 
 struct active_request_slot *active_queue_head = NULL;
 
@@ -208,7 +207,6 @@ void http_init(void)
        curl_global_init(CURL_GLOBAL_ALL);
 
        pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
-       no_range_header = curl_slist_append(no_range_header, "Range:");
 
 #ifdef USE_CURL_MULTI
        {
@@ -344,9 +342,14 @@ struct active_request_slot *get_active_slot(void)
        slot->finished = NULL;
        slot->callback_data = NULL;
        slot->callback_func = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
-       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
 
        return slot;
 }
index fa9e697..ffa4887 100644 (file)
@@ -208,6 +208,7 @@ int main(int argc, char **argv)
        int arg = 1;
 
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't')
@@ -239,6 +240,7 @@ int main(int argc, char **argv)
                usage(local_pull_usage);
        commit_id = argv[arg];
        path = argv[arg + 1];
+       write_ref_log_details = path;
 
        if (pull(commit_id))
                return 1;
index 58b0163..ebb49f2 100644 (file)
@@ -12,6 +12,37 @@ static void show_parents(struct commit *commit, int abbrev)
        }
 }
 
+static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
+{
+       int signoff_len = strlen(signoff);
+       static const char signed_off_by[] = "Signed-off-by: ";
+       char *cp = buf;
+
+       /* Do we have enough space to add it? */
+       if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 2)
+               return at;
+
+       /* First see if we already have the sign-off by the signer */
+       while (1) {
+               cp = strstr(cp, signed_off_by);
+               if (!cp)
+                       break;
+               cp += strlen(signed_off_by);
+               if ((cp + signoff_len < buf + at) &&
+                   !strncmp(cp, signoff, signoff_len) &&
+                   isspace(cp[signoff_len]))
+                       return at; /* we already have him */
+       }
+
+       strcpy(buf + at, signed_off_by);
+       at += strlen(signed_off_by);
+       strcpy(buf + at, signoff);
+       at += signoff_len;
+       buf[at++] = '\n';
+       buf[at] = 0;
+       return at;
+}
+
 void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
 {
        static char this_header[16384];
@@ -20,7 +51,7 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
        int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
        const char *extra;
        int len;
-       char *subject = NULL, *after_subject = NULL;
+       const char *subject = NULL, *extra_headers = opt->extra_headers;
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
@@ -69,6 +100,7 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
                        static char subject_buffer[1024];
                        static char buffer[1024];
                        snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+                                "%s"
                                 "MIME-Version: 1.0\n"
                                 "Content-Type: multipart/mixed;\n"
                                 " boundary=\"%s%s\"\n"
@@ -79,9 +111,10 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
                                 "Content-Type: text/plain; "
                                 "charset=UTF-8; format=fixed\n"
                                 "Content-Transfer-Encoding: 8bit\n\n",
+                                extra_headers ? extra_headers : "",
                                 mime_boundary_leader, opt->mime_boundary,
                                 mime_boundary_leader, opt->mime_boundary);
-                       after_subject = subject_buffer;
+                       extra_headers = subject_buffer;
 
                        snprintf(buffer, sizeof(buffer) - 1,
                                 "--%s%s\n"
@@ -110,7 +143,11 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
        /*
         * And then the pretty-printed message itself
         */
-       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, after_subject);
+       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, extra_headers);
+
+       if (opt->add_signoff)
+               len = append_signoff(this_header, sizeof(this_header), len,
+                                    opt->add_signoff);
        printf("%s%s%s", this_header, extra, sep);
 }
 
index c529e2d..70a569c 100644 (file)
@@ -162,7 +162,7 @@ int main(int argc, const char **argv)
 
        while (*argp) {
                const char *file = *argp++;
-               FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "rt");
+               FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
                int file_done = 0;
 
                if ( !f )
diff --git a/refs.c b/refs.c
index 0f3491f..eeb1196 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -142,6 +142,8 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                        namelen = strlen(de->d_name);
                        if (namelen > 255)
                                continue;
+                       if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock"))
+                               continue;
                        memcpy(path + baselen, de->d_name, namelen+1);
                        if (stat(git_path("%s", path), &st) < 0)
                                continue;
@@ -198,26 +200,6 @@ int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
        return do_for_each_ref("refs/remotes", fn, 13);
 }
 
-static char *ref_file_name(const char *ref)
-{
-       char *base = get_refs_directory();
-       int baselen = strlen(base);
-       int reflen = strlen(ref);
-       char *ret = xmalloc(baselen + 2 + reflen);
-       sprintf(ret, "%s/%s", base, ref);
-       return ret;
-}
-
-static char *ref_lock_file_name(const char *ref)
-{
-       char *base = get_refs_directory();
-       int baselen = strlen(base);
-       int reflen = strlen(ref);
-       char *ret = xmalloc(baselen + 7 + reflen);
-       sprintf(ret, "%s/%s.lock", base, ref);
-       return ret;
-}
-
 int get_ref_sha1(const char *ref, unsigned char *sha1)
 {
        if (check_ref_format(ref))
@@ -225,94 +207,6 @@ int get_ref_sha1(const char *ref, unsigned char *sha1)
        return read_ref(git_path("refs/%s", ref), sha1);
 }
 
-static int lock_ref_file(const char *filename, const char *lock_filename,
-                        const unsigned char *old_sha1)
-{
-       int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
-       unsigned char current_sha1[20];
-       int retval;
-       if (fd < 0) {
-               return error("Couldn't open lock file for %s: %s",
-                            filename, strerror(errno));
-       }
-       retval = read_ref(filename, current_sha1);
-       if (old_sha1) {
-               if (retval) {
-                       close(fd);
-                       unlink(lock_filename);
-                       return error("Could not read the current value of %s",
-                                    filename);
-               }
-               if (memcmp(current_sha1, old_sha1, 20)) {
-                       close(fd);
-                       unlink(lock_filename);
-                       error("The current value of %s is %s",
-                             filename, sha1_to_hex(current_sha1));
-                       return error("Expected %s",
-                                    sha1_to_hex(old_sha1));
-               }
-       } else {
-               if (!retval) {
-                       close(fd);
-                       unlink(lock_filename);
-                       return error("Unexpectedly found a value of %s for %s",
-                                    sha1_to_hex(current_sha1), filename);
-               }
-       }
-       return fd;
-}
-
-int lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
-{
-       char *filename;
-       char *lock_filename;
-       int retval;
-       if (check_ref_format(ref))
-               return -1;
-       filename = ref_file_name(ref);
-       lock_filename = ref_lock_file_name(ref);
-       retval = lock_ref_file(filename, lock_filename, old_sha1);
-       free(filename);
-       free(lock_filename);
-       return retval;
-}
-
-static int write_ref_file(const char *filename,
-                         const char *lock_filename, int fd,
-                         const unsigned char *sha1)
-{
-       char *hex = sha1_to_hex(sha1);
-       char term = '\n';
-       if (write(fd, hex, 40) < 40 ||
-           write(fd, &term, 1) < 1) {
-               error("Couldn't write %s", filename);
-               close(fd);
-               return -1;
-       }
-       close(fd);
-       rename(lock_filename, filename);
-       return 0;
-}
-
-int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
-{
-       char *filename;
-       char *lock_filename;
-       int retval;
-       if (fd < 0)
-               return -1;
-       if (check_ref_format(ref))
-               return -1;
-       filename = ref_file_name(ref);
-       lock_filename = ref_lock_file_name(ref);
-       if (safe_create_leading_directories(filename))
-               die("unable to create leading directory for %s", filename);
-       retval = write_ref_file(filename, lock_filename, fd, sha1);
-       free(filename);
-       free(lock_filename);
-       return retval;
-}
-
 /*
  * Make sure "ref" is something reasonable to have under ".git/refs/";
  * We do not like it if:
@@ -365,25 +259,255 @@ int check_ref_format(const char *ref)
        }
 }
 
-int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
+static struct ref_lock* verify_lock(struct ref_lock *lock,
+       const unsigned char *old_sha1, int mustexist)
+{
+       char buf[40];
+       int nr, fd = open(lock->ref_file, O_RDONLY);
+       if (fd < 0 && (mustexist || errno != ENOENT)) {
+               error("Can't verify ref %s", lock->ref_file);
+               unlock_ref(lock);
+               return NULL;
+       }
+       nr = read(fd, buf, 40);
+       close(fd);
+       if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
+               error("Can't verify ref %s", lock->ref_file);
+               unlock_ref(lock);
+               return NULL;
+       }
+       if (memcmp(lock->old_sha1, old_sha1, 20)) {
+               error("Ref %s is at %s but expected %s", lock->ref_file,
+                       sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
+               unlock_ref(lock);
+               return NULL;
+       }
+       return lock;
+}
+
+static struct ref_lock* lock_ref_sha1_basic(const char *path,
+       int plen,
+       const unsigned char *old_sha1, int mustexist)
+{
+       struct ref_lock *lock;
+       struct stat st;
+
+       lock = xcalloc(1, sizeof(struct ref_lock));
+       lock->lock_fd = -1;
+
+       plen = strlen(path) - plen;
+       path = resolve_ref(path, lock->old_sha1, mustexist);
+       if (!path) {
+               unlock_ref(lock);
+               return NULL;
+       }
+
+       lock->ref_file = strdup(path);
+       lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
+       lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
+       lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+
+       if (safe_create_leading_directories(lock->lock_file))
+               die("unable to create directory for %s", lock->lock_file);
+       lock->lock_fd = open(lock->lock_file,
+               O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (lock->lock_fd < 0) {
+               error("Couldn't open lock file %s: %s",
+                       lock->lock_file, strerror(errno));
+               unlock_ref(lock);
+               return NULL;
+       }
+
+       return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+}
+
+struct ref_lock* lock_ref_sha1(const char *ref,
+       const unsigned char *old_sha1, int mustexist)
 {
-       char *filename;
-       char *lock_filename;
-       int fd;
-       int retval;
        if (check_ref_format(ref))
+               return NULL;
+       return lock_ref_sha1_basic(git_path("refs/%s", ref),
+               5 + strlen(ref), old_sha1, mustexist);
+}
+
+struct ref_lock* lock_any_ref_for_update(const char *ref,
+       const unsigned char *old_sha1, int mustexist)
+{
+       return lock_ref_sha1_basic(git_path("%s", ref),
+               strlen(ref), old_sha1, mustexist);
+}
+
+void unlock_ref (struct ref_lock *lock)
+{
+       if (lock->lock_fd >= 0) {
+               close(lock->lock_fd);
+               unlink(lock->lock_file);
+       }
+       if (lock->ref_file)
+               free(lock->ref_file);
+       if (lock->lock_file)
+               free(lock->lock_file);
+       if (lock->log_file)
+               free(lock->log_file);
+       free(lock);
+}
+
+static int log_ref_write(struct ref_lock *lock,
+       const unsigned char *sha1, const char *msg)
+{
+       int logfd, written, oflags = O_APPEND | O_WRONLY;
+       unsigned maxlen, len;
+       char *logrec;
+       const char *comitter;
+
+       if (log_all_ref_updates) {
+               if (safe_create_leading_directories(lock->log_file) < 0)
+                       return error("unable to create directory for %s",
+                               lock->log_file);
+               oflags |= O_CREAT;
+       }
+
+       logfd = open(lock->log_file, oflags, 0666);
+       if (logfd < 0) {
+               if (!log_all_ref_updates && errno == ENOENT)
+                       return 0;
+               return error("Unable to append to %s: %s",
+                       lock->log_file, strerror(errno));
+       }
+
+       setup_ident();
+       comitter = git_committer_info(1);
+       if (msg) {
+               maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
+               logrec = xmalloc(maxlen);
+               len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
+                       sha1_to_hex(lock->old_sha1),
+                       sha1_to_hex(sha1),
+                       comitter,
+                       msg);
+       } else {
+               maxlen = strlen(comitter) + 2*40 + 4;
+               logrec = xmalloc(maxlen);
+               len = snprintf(logrec, maxlen, "%s %s %s\n",
+                       sha1_to_hex(lock->old_sha1),
+                       sha1_to_hex(sha1),
+                       comitter);
+       }
+       written = len <= maxlen ? write(logfd, logrec, len) : -1;
+       free(logrec);
+       close(logfd);
+       if (written != len)
+               return error("Unable to append to %s", lock->log_file);
+       return 0;
+}
+
+int write_ref_sha1(struct ref_lock *lock,
+       const unsigned char *sha1, const char *logmsg)
+{
+       static char term = '\n';
+
+       if (!lock)
                return -1;
-       filename = ref_file_name(ref);
-       lock_filename = ref_lock_file_name(ref);
-       if (safe_create_leading_directories(filename))
-               die("unable to create leading directory for %s", filename);
-       fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
-       if (fd < 0) {
-               error("Writing %s", lock_filename);
-               perror("Open");
+       if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
+               unlock_ref(lock);
+               return 0;
        }
-       retval = write_ref_file(filename, lock_filename, fd, sha1);
-       free(filename);
-       free(lock_filename);
-       return retval;
+       if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+           write(lock->lock_fd, &term, 1) != 1
+               || close(lock->lock_fd) < 0) {
+               error("Couldn't write %s", lock->lock_file);
+               unlock_ref(lock);
+               return -1;
+       }
+       if (log_ref_write(lock, sha1, logmsg) < 0) {
+               unlock_ref(lock);
+               return -1;
+       }
+       if (rename(lock->lock_file, lock->ref_file) < 0) {
+               error("Couldn't set %s", lock->ref_file);
+               unlock_ref(lock);
+               return -1;
+       }
+       lock->lock_fd = -1;
+       unlock_ref(lock);
+       return 0;
+}
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+       const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
+       char *tz_c;
+       int logfd, tz;
+       struct stat st;
+       unsigned long date;
+       unsigned char logged_sha1[20];
+
+       logfile = git_path("logs/%s", ref);
+       logfd = open(logfile, O_RDONLY, 0);
+       if (logfd < 0)
+               die("Unable to read log %s: %s", logfile, strerror(errno));
+       fstat(logfd, &st);
+       if (!st.st_size)
+               die("Log %s is empty.", logfile);
+       logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       close(logfd);
+
+       lastrec = NULL;
+       rec = logend = logdata + st.st_size;
+       while (logdata < rec) {
+               if (logdata < rec && *(rec-1) == '\n')
+                       rec--;
+               lastgt = NULL;
+               while (logdata < rec && *(rec-1) != '\n') {
+                       rec--;
+                       if (*rec == '>')
+                               lastgt = rec;
+               }
+               if (!lastgt)
+                       die("Log %s is corrupt.", logfile);
+               date = strtoul(lastgt + 1, &tz_c, 10);
+               if (date <= at_time) {
+                       if (lastrec) {
+                               if (get_sha1_hex(lastrec, logged_sha1))
+                                       die("Log %s is corrupt.", logfile);
+                               if (get_sha1_hex(rec + 41, sha1))
+                                       die("Log %s is corrupt.", logfile);
+                               if (memcmp(logged_sha1, sha1, 20)) {
+                                       tz = strtoul(tz_c, NULL, 10);
+                                       fprintf(stderr,
+                                               "warning: Log %s has gap after %s.\n",
+                                               logfile, show_rfc2822_date(date, tz));
+                               }
+                       } else if (date == at_time) {
+                               if (get_sha1_hex(rec + 41, sha1))
+                                       die("Log %s is corrupt.", logfile);
+                       } else {
+                               if (get_sha1_hex(rec + 41, logged_sha1))
+                                       die("Log %s is corrupt.", logfile);
+                               if (memcmp(logged_sha1, sha1, 20)) {
+                                       tz = strtoul(tz_c, NULL, 10);
+                                       fprintf(stderr,
+                                               "warning: Log %s unexpectedly ended on %s.\n",
+                                               logfile, show_rfc2822_date(date, tz));
+                               }
+                       }
+                       munmap((void*)logdata, st.st_size);
+                       return 0;
+               }
+               lastrec = rec;
+       }
+
+       rec = logdata;
+       while (rec < logend && *rec != '>' && *rec != '\n')
+               rec++;
+       if (rec == logend || *rec == '\n')
+               die("Log %s is corrupt.", logfile);
+       date = strtoul(rec + 1, &tz_c, 10);
+       tz = strtoul(tz_c, NULL, 10);
+       if (get_sha1_hex(logdata, sha1))
+               die("Log %s is corrupt.", logfile);
+       munmap((void*)logdata, st.st_size);
+       fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+               logfile, show_rfc2822_date(date, tz));
+       return 0;
 }
diff --git a/refs.h b/refs.h
index fa816c1..6c946ea 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -1,6 +1,15 @@
 #ifndef REFS_H
 #define REFS_H
 
+struct ref_lock {
+       char *ref_file;
+       char *lock_file;
+       char *log_file;
+       unsigned char old_sha1[20];
+       int lock_fd;
+       int force_write;
+};
+
 /*
  * Calls the specified function for each ref file until it returns nonzero,
  * and returns the value
@@ -14,16 +23,20 @@ extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *
 /** Reads the refs file specified into sha1 **/
 extern int get_ref_sha1(const char *ref, unsigned char *sha1);
 
-/** Locks ref and returns the fd to give to write_ref_sha1() if the ref
- * has the given value currently; otherwise, returns -1.
- **/
-extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
+extern struct ref_lock* lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Locks any ref (for 'HEAD' type refs). */
+extern struct ref_lock* lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Release any lock taken but not written. **/
+extern void unlock_ref (struct ref_lock *lock);
 
-/** Writes sha1 into the refs file specified, locked with the given fd. **/
-extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
+/** Writes sha1 into the ref specified by the lock. **/
+extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
-/** Writes sha1 into the refs file specified. **/
-extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
+/** Reads log for the value of ref during at_time. **/
+extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
 
 /** Returns 0 if target has the right format for a ref. **/
 extern int check_ref_format(const char *target);
index bdbdd23..7d85b0f 100644 (file)
@@ -60,6 +60,8 @@ struct rev_info {
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
+       const char      *add_signoff;
+       const char      *extra_headers;
 
        /* special limits */
        int max_count;
index dc68355..fbbde1c 100644 (file)
@@ -4,6 +4,7 @@
 #include "tree.h"
 #include "blob.h"
 #include "tree-walk.h"
+#include "refs.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -245,36 +246,61 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                "refs/remotes/%.*s/HEAD",
                NULL
        };
-       const char **p;
-       const char *warning = "warning: refname '%.*s' is ambiguous.\n";
-       char *pathname;
-       int already_found = 0;
+       static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+       const char **p, *pathname;
+       char *real_path = NULL;
+       int refs_found = 0, am;
+       unsigned long at_time = (unsigned long)-1;
        unsigned char *this_result;
        unsigned char sha1_from_ref[20];
 
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
+       /* At a given period of time? "@{2 hours ago}" */
+       for (am = 1; am < len - 1; am++) {
+               if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
+                       int date_len = len - am - 3;
+                       char *date_spec = xmalloc(date_len + 1);
+                       strncpy(date_spec, str + am + 2, date_len);
+                       date_spec[date_len] = 0;
+                       at_time = approxidate(date_spec);
+                       free(date_spec);
+                       len = am;
+                       break;
+               }
+       }
+
        /* Accept only unambiguous ref paths. */
        if (ambiguous_path(str, len))
                return -1;
 
        for (p = fmt; *p; p++) {
-               this_result = already_found ? sha1_from_ref : sha1;
-               pathname = git_path(*p, len, str);
-               if (!read_ref(pathname, this_result)) {
-                       if (warn_ambiguous_refs) {
-                               if (already_found)
-                                       fprintf(stderr, warning, len, str);
-                               already_found++;
-                       }
-                       else
-                               return 0;
+               this_result = refs_found ? sha1_from_ref : sha1;
+               pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
+               if (pathname) {
+                       if (!refs_found++)
+                               real_path = strdup(pathname);
+                       if (!warn_ambiguous_refs)
+                               break;
                }
        }
-       if (already_found)
-               return 0;
-       return -1;
+
+       if (!refs_found)
+               return -1;
+
+       if (warn_ambiguous_refs && refs_found > 1)
+               fprintf(stderr, warning, len, str);
+
+       if (at_time != (unsigned long)-1) {
+               read_ref_at(
+                       real_path + strlen(git_path(".")) - 1,
+                       at_time,
+                       sha1);
+       }
+
+       free(real_path);
+       return 0;
 }
 
 static int get_sha1_1(const char *name, int len, unsigned char *sha1);
@@ -456,7 +482,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
  */
 int get_sha1(const char *name, unsigned char *sha1)
 {
-       int ret;
+       int ret, bracket_depth;
        unsigned unused;
        int namelen = strlen(name);
        const char *cp;
@@ -502,8 +528,15 @@ int get_sha1(const char *name, unsigned char *sha1)
                }
                return -1;
        }
-       cp = strchr(name, ':');
-       if (cp) {
+       for (cp = name, bracket_depth = 0; *cp; cp++) {
+               if (*cp == '{')
+                       bracket_depth++;
+               else if (bracket_depth && *cp == '}')
+                       bracket_depth--;
+               else if (!bracket_depth && *cp == ':')
+                       break;
+       }
+       if (*cp == ':') {
                unsigned char tree_sha1[20];
                if (!get_sha1_1(name, cp-name, tree_sha1))
                        return get_tree_entry(tree_sha1, cp+1, sha1,
index 4eb9e04..e3067b8 100644 (file)
@@ -132,6 +132,7 @@ int main(int argc, char **argv)
        if (!prog) prog = "git-ssh-upload";
 
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -158,6 +159,7 @@ int main(int argc, char **argv)
        }
        commit_id = argv[arg];
        url = argv[arg + 1];
+       write_ref_log_details = url;
 
        if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
                return 1;
index cf33989..2c9bbb5 100755 (executable)
@@ -195,6 +195,20 @@ test_expect_success \
     'git-ls-tree -r output for a known tree.' \
     'diff current expected'
 
+test_expect_success \
+    'writing partial tree out with git-write-tree --prefix.' \
+    'ptree=$(git-write-tree --prefix=path3)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3'
+
+test_expect_success \
+    'writing partial tree out with git-write-tree --prefix.' \
+    'ptree=$(git-write-tree --prefix=path3/subp3)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2'
+
 ################################################################
 rm .git/index
 test_expect_success \
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
new file mode 100755 (executable)
index 0000000..df3e993
--- /dev/null
@@ -0,0 +1,213 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='Test git-update-ref and basic ref logging'
+. ./test-lib.sh
+
+Z=0000000000000000000000000000000000000000
+A=1111111111111111111111111111111111111111
+B=2222222222222222222222222222222222222222
+C=3333333333333333333333333333333333333333
+D=4444444444444444444444444444444444444444
+E=5555555555555555555555555555555555555555
+F=6666666666666666666666666666666666666666
+m=refs/heads/master
+
+test_expect_success \
+       "create $m" \
+       'git-update-ref $m $A &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "create $m" \
+       'git-update-ref $m $B $A &&
+        test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_success \
+       "create $m (by HEAD)" \
+       'git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "create $m (by HEAD)" \
+       'git-update-ref HEAD $B $A &&
+        test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_failure \
+       '(not) create HEAD with old sha1' \
+       'git-update-ref HEAD $A $B'
+test_expect_failure \
+       "(not) prior created .git/$m" \
+       'test -f .git/$m'
+rm -f .git/$m
+
+test_expect_success \
+       "create HEAD" \
+       'git-update-ref HEAD $A'
+test_expect_failure \
+       '(not) change HEAD with wrong SHA1' \
+       'git-update-ref HEAD $B $Z'
+test_expect_failure \
+       "(not) changed .git/$m" \
+       'test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+mkdir -p .git/logs/refs/heads
+touch .git/logs/refs/heads/master
+test_expect_success \
+       "create $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+        git-update-ref HEAD $A -m "Initial Creation" &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "update $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:31" \
+        git-update-ref HEAD $B $A -m "Switch" &&
+        test $B = $(cat .git/$m)'
+test_expect_success \
+       "set $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:41" \
+        git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000      Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000      Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
+EOF
+test_expect_success \
+       "verifying $m's log" \
+       'diff expect .git/logs/$m'
+rm -rf .git/$m .git/logs expect
+
+test_expect_success \
+       'enable core.logAllRefUpdates' \
+       'git-repo-config core.logAllRefUpdates true &&
+        test true = $(git-repo-config --bool --get core.logAllRefUpdates)'
+
+test_expect_success \
+       "create $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:32" \
+        git-update-ref HEAD $A -m "Initial Creation" &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "update $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:33" \
+        git-update-ref HEAD $B $A -m "Switch" &&
+        test $B = $(cat .git/$m)'
+test_expect_success \
+       "set $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:43" \
+        git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000      Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000      Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
+EOF
+test_expect_success \
+       "verifying $m's log" \
+       'diff expect .git/logs/$m'
+rm -f .git/$m .git/logs/$m expect
+
+git-update-ref $m $D
+cat >.git/logs/$m <<EOF
+$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+EOF
+
+ed="Thu, 26 May 2005 18:32:00 -0500"
+gd="Thu, 26 May 2005 18:33:00 -0500"
+ld="Thu, 26 May 2005 18:43:00 -0500"
+test_expect_success \
+       'Query "master@{May 25 2005}" (before history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+        test $C = $(cat o) &&
+        test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+       "Query master@{2005-05-25} (before history)" \
+       'rm -f o e
+        git-rev-parse --verify master@{2005-05-25} >o 2>e &&
+        test $C = $(cat o) &&
+        echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+       'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
+        test $C = $(cat o) &&
+        test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+       'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+        test $A = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+        test $B = $(cat o) &&
+        test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+        test $Z = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+        test $E = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-28}" (past end of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+        test $D = $(cat o) &&
+        test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
+
+
+rm -f .git/$m .git/logs/$m expect
+
+test_expect_success \
+    'creating initial files' \
+    'echo TEST >F &&
+     git-add F &&
+        GIT_AUTHOR_DATE="2005-05-26 23:30" \
+        GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
+        h_TEST=$(git-rev-parse --verify HEAD)
+        echo The other day this did not work. >M &&
+        echo And then Bob told me how to fix it. >>M &&
+        echo OTHER >F &&
+        GIT_AUTHOR_DATE="2005-05-26 23:41" \
+        GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
+        h_OTHER=$(git-rev-parse --verify HEAD)
+        rm -f M'
+
+cat >expect <<EOF
+$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit: add
+$h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000   commit: The other day this did not work.
+EOF
+test_expect_success \
+       'git-commit logged updates' \
+       'diff expect .git/logs/$m'
+unset h_TEST h_OTHER
+
+test_expect_success \
+       'git-cat-file blob master:F (expect OTHER)' \
+       'test OTHER = $(git-cat-file blob master:F)'
+test_expect_success \
+       'git-cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
+       'test TEST = $(git-cat-file blob "master@{2005-05-26 23:30}:F")'
+test_expect_success \
+       'git-cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
+       'test OTHER = $(git-cat-file blob "master@{2005-05-26 23:42}:F")'
+
+test_done
index 77aed8d..a78ea7f 100755 (executable)
@@ -8,15 +8,16 @@ test_description='git-update-index --again test.
 
 . ./test-lib.sh
 
+cat > expected <<\EOF
+100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
+100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
+EOF
 test_expect_success 'update-index --add' \
        'echo hello world >file1 &&
         echo goodbye people >file2 &&
         git-update-index --add file1 file2 &&
         git-ls-files -s >current &&
-        cmp current - <<\EOF
-100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
-100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
-EOF'
+        cmp current expected'
 
 test_expect_success 'update-index --again' \
        'rm -f file1 &&
@@ -29,20 +30,22 @@ test_expect_success 'update-index --again' \
                echo happy - failed as expected
        fi &&
         git-ls-files -s >current &&
-        cmp current - <<\EOF
-100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
-100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
-EOF'
+        cmp current expected'
 
+cat > expected <<\EOF
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
 test_expect_success 'update-index --remove --again' \
        'git-update-index --remove --again &&
         git-ls-files -s >current &&
-        cmp current - <<\EOF
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
-EOF'
+        cmp current expected'
 
 test_expect_success 'first commit' 'git-commit -m initial'
 
+cat > expected <<\EOF
+100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
 test_expect_success 'update-index again' \
        'mkdir -p dir1 &&
        echo hello world >dir1/file3 &&
@@ -52,11 +55,12 @@ test_expect_success 'update-index again' \
        echo happy >dir1/file3 &&
        git-update-index --again &&
        git-ls-files -s >current &&
-       cmp current - <<\EOF
-100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0      dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
-EOF'
+       cmp current expected'
 
+cat > expected <<\EOF
+100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
 test_expect_success 'update-index --update from subdir' \
        'echo not so happy >file2 &&
        cd dir1 &&
@@ -64,19 +68,17 @@ test_expect_success 'update-index --update from subdir' \
        git-update-index --again &&
        cd .. &&
        git-ls-files -s >current &&
-       cmp current - <<\EOF
-100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0      dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
-EOF'
+       cmp current expected'
 
+cat > expected <<\EOF
+100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
 test_expect_success 'update-index --update with pathspec' \
        'echo very happy >file2 &&
        cat file2 >dir1/file3 &&
        git-update-index --again dir1/ &&
        git-ls-files -s >current &&
-       cmp current - <<\EOF
-100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0      dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
-EOF'
+       cmp current expected'
 
 test_done
index c3de151..5b04efc 100755 (executable)
@@ -14,7 +14,8 @@ test_expect_success \
     'prepare an trivial repository' \
     'echo Hello > A &&
      git-update-index --add A &&
-     git-commit -m "Initial commit."'
+     git-commit -m "Initial commit." &&
+     HEAD=$(git-rev-parse --verify HEAD)'
 
 test_expect_success \
     'git branch --help should return success now.' \
@@ -32,4 +33,32 @@ test_expect_success \
     'git branch a/b/c should create a branch' \
     'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
 
+cat >expect <<EOF
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     branch: Created from HEAD
+EOF
+test_expect_success \
+    'git branch -l d/e/f should create a branch and a log' \
+       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+     git-branch -l d/e/f &&
+        test -f .git/refs/heads/d/e/f &&
+        test -f .git/logs/refs/heads/d/e/f &&
+        diff expect .git/logs/refs/heads/d/e/f'
+
+test_expect_success \
+    'git branch -d d/e/f should delete a branch and a log' \
+       'git-branch -d d/e/f &&
+        test ! -f .git/refs/heads/d/e/f &&
+        test ! -f .git/logs/refs/heads/d/e/f'
+
+cat >expect <<EOF
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     checkout: Created from master^0
+EOF
+test_expect_success \
+    'git checkout -b g/h/i -l should create a branch and a log' \
+       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+     git-checkout -b g/h/i -l master &&
+        test -f .git/refs/heads/g/h/i &&
+        test -f .git/logs/refs/heads/g/h/i &&
+        diff expect .git/logs/refs/heads/g/h/i'
+
 test_done
index 72a93da..c12270e 100755 (executable)
@@ -40,9 +40,11 @@ test_expect_success 'git-ls-files no-funny' \
 t0=`git-write-tree`
 echo "$t0" >t0
 
-echo 'just space
+cat > expected <<\EOF
+just space
 no-funny
-"tabs\t,\" (dq) and spaces"' >expected
+"tabs\t,\" (dq) and spaces"
+EOF
 test_expect_success 'git-ls-files with-funny' \
        'git-update-index --add "$p1" &&
        git-ls-files >current &&
@@ -58,14 +60,18 @@ test_expect_success 'git-ls-files -z with-funny' \
 t1=`git-write-tree`
 echo "$t1" >t1
 
-echo 'just space
+cat > expected <<\EOF
+just space
 no-funny
-"tabs\t,\" (dq) and spaces"' >expected
+"tabs\t,\" (dq) and spaces"
+EOF
 test_expect_success 'git-ls-tree with funny' \
        'git-ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
         diff -u expected current'
 
-echo 'A        "tabs\t,\" (dq) and spaces"' >expected
+cat > expected <<\EOF
+A      "tabs\t,\" (dq) and spaces"
+EOF
 test_expect_success 'git-diff-index with-funny' \
        'git-diff-index --name-status $t0 >current &&
        diff -u expected current'
@@ -84,53 +90,62 @@ test_expect_success 'git-diff-tree -z with-funny' \
        'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
        diff -u expected current'
 
-echo 'CNUM     no-funny        "tabs\t,\" (dq) and spaces"' >expected
+cat > expected <<\EOF
+CNUM   no-funny        "tabs\t,\" (dq) and spaces"
+EOF
 test_expect_success 'git-diff-tree -C with-funny' \
        'git-diff-tree -C --find-copies-harder --name-status \
                $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
        diff -u expected current'
 
-echo 'RNUM     no-funny        "tabs\t,\" (dq) and spaces"' >expected
+cat > expected <<\EOF
+RNUM   no-funny        "tabs\t,\" (dq) and spaces"
+EOF
 test_expect_success 'git-diff-tree delete with-funny' \
        'git-update-index --force-remove "$p0" &&
        git-diff-index -M --name-status \
                $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
        diff -u expected current'
 
-echo 'diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+cat > expected <<\EOF
+diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
 similarity index NUM%
 rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"' >expected
-
+rename to "tabs\t,\" (dq) and spaces"
+EOF
 test_expect_success 'git-diff-tree delete with-funny' \
        'git-diff-index -M -p $t0 |
         sed -e "s/index [0-9]*%/index NUM%/" >current &&
         diff -u expected current'
 
 chmod +x "$p1"
-echo 'diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+cat > expected <<\EOF
+diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
 old mode 100644
 new mode 100755
 similarity index NUM%
 rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"' >expected
-
+rename to "tabs\t,\" (dq) and spaces"
+EOF
 test_expect_success 'git-diff-tree delete with-funny' \
        'git-diff-index -M -p $t0 |
         sed -e "s/index [0-9]*%/index NUM%/" >current &&
         diff -u expected current'
 
-echo >expected ' "tabs\t,\" (dq) and spaces"
- 1 files changed, 0 insertions(+), 0 deletions(-)'
+cat >expected <<\EOF
+ "tabs\t,\" (dq) and spaces"
+ 1 files changed, 0 insertions(+), 0 deletions(-)
+EOF
 test_expect_success 'git-diff-tree rename with-funny applied' \
        'git-diff-index -M -p $t0 |
         git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
         diff -u expected current'
 
-echo >expected ' no-funny
+cat > expected <<\EOF
+ no-funny
  "tabs\t,\" (dq) and spaces"
- 2 files changed, 3 insertions(+), 3 deletions(-)'
-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+EOF
 test_expect_success 'git-diff-tree delete with-funny applied' \
        'git-diff-index -p $t0 |
         git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
index bdd95c0..323606c 100755 (executable)
@@ -16,25 +16,20 @@ test_expect_success 'prepare repository' \
         echo git >c &&
         cat b b >d'
 
-test_expect_success 'diff without --binary' \
-       'git-diff | git-apply --stat --summary >current &&
-        cmp current - <<\EOF
+cat > expected <<\EOF
  a |    2 +-
  b |  Bin
  c |    2 +-
  d |  Bin
  4 files changed, 2 insertions(+), 2 deletions(-)
-EOF'
+EOF
+test_expect_success 'diff without --binary' \
+       'git-diff | git-apply --stat --summary >current &&
+        cmp current expected'
 
 test_expect_success 'diff with --binary' \
        'git-diff --binary | git-apply --stat --summary >current &&
-        cmp current - <<\EOF
- a |    2 +-
- b |  Bin
- c |    2 +-
- d |  Bin
- 4 files changed, 2 insertions(+), 2 deletions(-)
-EOF'
+        cmp current expected'
 
 # apply needs to be able to skip the binary material correctly
 # in order to report the line number of a corrupt patch.
index 92f12d9..f7625a6 100755 (executable)
@@ -12,11 +12,11 @@ test_description='Testing multi_ack pack fetching
 
 # Some convenience functions
 
-function add () {
-       local name=$1
-       local text="$@"
-       local branch=${name:0:1}
-       local parents=""
+add () {
+       name=$1
+       text="$@"
+       branch=`echo $name | sed -e 's/^\(.\).*$/\1/'`
+       parents=""
 
        shift
        while test $1; do
@@ -36,13 +36,13 @@ function add () {
        eval ${branch}TIP=$commit
 }
 
-function count_objects () {
+count_objects () {
        ls .git/objects/??/* 2>>log2.txt | wc -l | tr -d " "
 }
 
-function test_expect_object_count () {
-       local message=$1
-       local count=$2
+test_expect_object_count () {
+       message=$1
+       count=$2
 
        output="$(count_objects)"
        test_expect_success \
@@ -50,18 +50,18 @@ function test_expect_object_count () {
                "test $count = $output"
 }
 
-function pull_to_client () {
-       local number=$1
-       local heads=$2
-       local count=$3
-       local no_strict_count_check=$4
+pull_to_client () {
+       number=$1
+       heads=$2
+       count=$3
+       no_strict_count_check=$4
 
        cd client
        test_expect_success "$number pull" \
                "git-fetch-pack -k -v .. $heads"
        case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
        case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
-       git-symbolic-ref HEAD refs/heads/${heads:0:1}
+       git-symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
 
        test_expect_success "fsck" 'git-fsck-objects --full > fsck.txt 2>&1'
 
index c6752af..d402621 100755 (executable)
@@ -69,7 +69,9 @@ on_committer_date()
 {
     _date=$1
     shift 1
-    GIT_COMMITTER_DATE=$_date "$@"
+    export GIT_COMMITTER_DATE="$_date"
+    "$@"
+    unset GIT_COMMITTER_DATE
 }
 
 # Execute a command and suppress any error output.
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
new file mode 100755 (executable)
index 0000000..a61da1e
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='git-send-email'
+. ./test-lib.sh
+
+PROG='git send-email'
+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 \
+    'Setup helper tool' \
+    '(echo "#!/bin/sh"
+      echo shift
+      echo for a
+      echo do
+      echo "  echo \"!\$a!\""
+      echo "done >commandline"
+      echo "cat > msgtxt"
+      ) >fake.sendmail
+     chmod +x ./fake.sendmail
+     git add fake.sendmail
+     GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
+
+test_expect_success \
+    'Extract patches and send' \
+    'git format-patch -n HEAD^1
+     git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" ./0001*txt'
+
+cat >expected <<\EOF
+!nobody@example.com!
+!author@example.com!
+EOF
+test_expect_success \
+    'Verify commandline' \
+    'diff commandline expected'
+
+test_done
index fd48742..a1e6bb9 100644 (file)
@@ -1,85 +1,56 @@
 #include "cache.h"
 #include "refs.h"
 
-static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
-
-static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
-{
-       char buf[40];
-       int fd = open(path, O_RDONLY), nr;
-       if (fd < 0)
-               return -1;
-       nr = read(fd, buf, 40);
-       close(fd);
-       if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
-               return -1;
-       return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
-}
+static const char git_update_ref_usage[] =
+"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
 
 int main(int argc, char **argv)
 {
-       char *hex;
-       const char *refname, *value, *oldval, *path;
-       char *lockpath;
-       unsigned char sha1[20], oldsha1[20], currsha1[20];
-       int fd, written;
+       const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+       struct ref_lock *lock;
+       unsigned char sha1[20], oldsha1[20];
+       int i;
 
        setup_git_directory();
        git_config(git_default_config);
-       if (argc < 3 || argc > 4)
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp("-m", argv[i])) {
+                       if (i+1 >= argc)
+                               usage(git_update_ref_usage);
+                       msg = argv[++i];
+                       if (!*msg)
+                               die("Refusing to perform update with empty message.");
+                       if (strchr(msg, '\n'))
+                               die("Refusing to perform update with \\n in message.");
+                       continue;
+               }
+               if (!refname) {
+                       refname = argv[i];
+                       continue;
+               }
+               if (!value) {
+                       value = argv[i];
+                       continue;
+               }
+               if (!oldval) {
+                       oldval = argv[i];
+                       continue;
+               }
+       }
+       if (!refname || !value)
                usage(git_update_ref_usage);
 
-       refname = argv[1];
-       value = argv[2];
-       oldval = argv[3];
        if (get_sha1(value, sha1))
                die("%s: not a valid SHA1", value);
        memset(oldsha1, 0, 20);
        if (oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
-       path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
-       if (!path)
-               die("No such ref: %s", refname);
-
-       if (oldval) {
-               if (memcmp(currsha1, oldsha1, 20))
-                       die("Ref %s is at %s but expected %s", refname, sha1_to_hex(currsha1), sha1_to_hex(oldsha1));
-               /* Nothing to do? */
-               if (!memcmp(oldsha1, sha1, 20))
-                       exit(0);
-       }
-       path = strdup(path);
-       lockpath = mkpath("%s.lock", path);
-       if (safe_create_leading_directories(lockpath) < 0)
-               die("Unable to create all of %s", lockpath);
-
-       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
-       if (fd < 0)
-               die("Unable to create %s", lockpath);
-       hex = sha1_to_hex(sha1);
-       hex[40] = '\n';
-       written = write(fd, hex, 41);
-       close(fd);
-       if (written != 41) {
-               unlink(lockpath);
-               die("Unable to write to %s", lockpath);
-       }
-
-       /*
-        * Re-read the ref after getting the lock to verify
-        */
-       if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
-               unlink(lockpath);
-               die("Ref lock failed");
-       }
-
-       /*
-        * Finally, replace the old ref with the new one
-        */
-       if (rename(lockpath, path) < 0) {
-               unlink(lockpath);
-               die("Unable to create %s", path);
-       }
+       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+       if (!lock)
+               return 1;
+       if (write_ref_sha1(lock, sha1, msg) < 0)
+               return 1;
        return 0;
 }
index 7a4f691..895e7a3 100644 (file)
@@ -8,8 +8,10 @@
 #include "cache-tree.h"
 
 static int missing_ok = 0;
+static char *prefix = NULL;
 
-static const char write_tree_usage[] = "git-write-tree [--missing-ok]";
+static const char write_tree_usage[] =
+"git-write-tree [--missing-ok] [--prefix=<prefix>/]";
 
 static struct cache_file cache_file;
 
@@ -21,13 +23,18 @@ int main(int argc, char **argv)
 
        newfd = hold_index_file_for_update(&cache_file, get_index_file());
        entries = read_cache();
-       if (argc == 2) {
-               if (!strcmp(argv[1], "--missing-ok"))
+
+       while (1 < argc) {
+               char *arg = argv[1];
+               if (!strcmp(arg, "--missing-ok"))
                        missing_ok = 1;
+               else if (!strncmp(arg, "--prefix=", 9))
+                       prefix = arg + 9;
                else
                        die(write_tree_usage);
+               argc--; argv++;
        }
-       
+
        if (argc > 2)
                die("too many options");
 
@@ -54,6 +61,12 @@ int main(int argc, char **argv)
                 * performance penalty and not a big deal.
                 */
        }
-       printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
+       if (prefix) {
+               struct cache_tree *subtree =
+                       cache_tree_find(active_cache_tree, prefix);
+               printf("%s\n", sha1_to_hex(subtree->sha1));
+       }
+       else
+               printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
        return 0;
 }