Merge fixes up to 1.2.1
authorJunio C Hamano <junkio@cox.net>
Thu, 16 Feb 2006 03:39:21 +0000 (19:39 -0800)
committerJunio C Hamano <junkio@cox.net>
Thu, 16 Feb 2006 03:39:21 +0000 (19:39 -0800)
13 files changed:
Documentation/git-commit.txt
Documentation/git-send-email.txt
Documentation/git-svnimport.txt
cache.h
git-bisect.sh
git-commit.sh
git-rebase.sh
git-send-email.perl
git-svnimport.perl
ls-files.c
pack-check.c
sha1_file.c
templates/hooks--pre-rebase [new file with mode: 0644]

index 5b1b4d3..214ed23 100644 (file)
@@ -85,27 +85,12 @@ OPTIONS
 <file>...::
        Files to be committed.  The meaning of these is
        different between `--include` and `--only`.  Without
-       either, it defaults `--include` semantics.
+       either, it defaults `--only` semantics.
 
 If you make a commit and then found a mistake immediately after
 that, you can recover from it with gitlink:git-reset[1].
 
 
-WARNING
--------
-
-The 1.2.0 and its maintenance series 1.2.X will keep the
-traditional `--include` semantics as the default when neither
-`--only` nor `--include` is specified and `paths...` are given.
-This *will* change during the development towards 1.3.0 in the
-'master' branch of `git.git` repository.  If you are using this
-command in your scripts, and you depend on the traditional
-`--include` semantics, please update them to explicitly ask for
-`--include` semantics.  Also if you are used to making partial
-commit using `--include` semantics, please train your fingers to
-say `git commit --include paths...` (or `git commit -i paths...`).
-
-
 Discussion
 ----------
 
@@ -121,7 +106,7 @@ even the command is invoked from a subdirectory.
 That is, update the specified paths to the index and then commit
 the whole tree.
 
-`git commit --only paths...` largely bypasses the index file and
+`git commit paths...` largely bypasses the index file and
 commits only the changes made to the specified paths.  It has
 however several safety valves to prevent confusion.
 
index 00537d8..8c58685 100644 (file)
@@ -24,6 +24,9 @@ OPTIONS
 -------
 The options available are:
 
+--cc::
+       Specify a starting "Cc:" value for each email.
+
 --chain-reply-to, --no-chain-reply-to::
        If this is set, each email will be sent as a reply to the previous
        email sent.  If disabled with "--no-chain-reply-to", all emails after
@@ -48,6 +51,9 @@ The options available are:
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
+--no-signed-off-by-cc::
+       Do not add emails foudn in Signed-off-by: lines to the cc list.
+
 --quiet::
        Make git-send-email less verbose.  One line per email should be
        all that is output.
@@ -61,6 +67,10 @@ The options available are:
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
+--suppress-from::
+       Do not add the From: address to the cc: list, if it shows up in a From:
+       line.
+
 --to::
        Specify the primary recipient of the emails generated.
        Generally, this will be the upstream maintainer of the
index 63e28b8..5c543d5 100644 (file)
@@ -61,6 +61,10 @@ When importing incrementally, you might need to edit the .git/svn2git file.
        the git repository. Use this option if you want to import into a
        different branch.
 
+-r::
+       Prepend 'rX: ' to commit messages, where X is the imported
+       subversion revision.
+
 -m::
        Attempt to detect merges based on the commit message. This option
        will enable default regexes that try to capture the name source
diff --git a/cache.h b/cache.h
index c255421..b5db01f 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -322,7 +322,7 @@ extern int num_packed_objects(const struct packed_git *p);
 extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
 extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
 extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);
-extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, int *, unsigned char *);
+extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
 
 /* Dumb servers support */
 extern int update_server_info(int);
index 0750253..03df143 100755 (executable)
@@ -49,9 +49,16 @@ bisect_start() {
        die "Bad HEAD - I need a symbolic ref"
        case "$head" in
        refs/heads/bisect*)
-               git checkout master || exit
+               if [ -s "$GIT_DIR/head-name" ]; then
+                   branch=`cat "$GIT_DIR/head-name"`
+               else
+                   branch=master
+               fi
+               git checkout $branch || exit
                ;;
        refs/heads/*)
+               [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
+               echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
                ;;
        *)
                die "Bad HEAD - strange symbolic ref"
@@ -159,7 +166,11 @@ bisect_visualize() {
 
 bisect_reset() {
        case "$#" in
-       0) branch=master ;;
+       0) if [ -s "$GIT_DIR/head-name" ]; then
+              branch=`cat "$GIT_DIR/head-name"`
+          else
+              branch=master
+          fi ;;
        1) test -f "$GIT_DIR/refs/heads/$1" || {
               echo >&2 "$1 does not seem to be a valid branch"
               exit 1
@@ -170,7 +181,7 @@ bisect_reset() {
        esac
        git checkout "$branch" &&
        rm -fr "$GIT_DIR/refs/bisect"
-       rm -f "$GIT_DIR/refs/heads/bisect"
+       rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
        rm -f "$GIT_DIR/BISECT_LOG"
        rm -f "$GIT_DIR/BISECT_NAMES"
 }
index 59551d9..f7ee1aa 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Linus Torvalds
 # Copyright (c) 2006 Junio C Hamano
 
-USAGE='[-a] [-i] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [--author <author>] [<path>...]'
+USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [--author <author>] [[-i | -o] <path>...]'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
 
@@ -180,6 +180,7 @@ verify=t
 verbose=
 signoff=
 force_author=
+only_include_assumed=
 while case "$#" in 0) break;; esac
 do
   case "$1" in
@@ -340,15 +341,8 @@ case "$#,$also$only" in
 0,)
   ;;
 *,)
-  echo >&2 "assuming --include paths..."
-  also=t
-  # Later when switch the defaults, we will replace them with these:
-  # echo >&2 "assuming --only paths..."
-  # also=
-
-  # If we are going to launch an editor, the message won't be
-  # shown without this...
-  test -z "$log_given$status_only" && sleep 1
+  only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+  also=
   ;;
 esac
 unset only
@@ -383,6 +377,8 @@ t,)
        ;;
 ,t)
        save_index &&
+       git-ls-files --error-unmatch -- "$@" >/dev/null || exit
+
        git-diff-files --name-only -z -- "$@"  |
        (
                cd "$TOP"
@@ -411,7 +407,7 @@ t,)
                refuse_partial "Different in index and the last commit:
 $dirty_in_index"
            fi
-           commit_only=`git-ls-files -- "$@"`
+           commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
 
            # Build the temporary index and update the real index
            # the same way.
@@ -572,7 +568,10 @@ else
        PARENTS=""
 fi
 
-run_status >>"$GIT_DIR"/COMMIT_EDITMSG
+{
+       test -z "$only_include_assumed" || echo "$only_include_assumed"
+       run_status
+} >>"$GIT_DIR"/COMMIT_EDITMSG
 if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
 then
        rm -f "$GIT_DIR/COMMIT_EDITMSG"
index 16d4359..f84160d 100755 (executable)
@@ -36,6 +36,15 @@ other=$(git-rev-parse --verify "$1^0") || usage
 # Make sure the branch to rebase is valid.
 head=$(git-rev-parse --verify "${2-HEAD}^0") || exit
 
+# If a hook exists, give it a chance to interrupt
+if test -x "$GIT_DIR/hooks/pre-rebase"
+then
+       "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
+               echo >&2 "The pre-rebase hook refused to rebase."
+               exit 1
+       }
+fi
+
 # If the branch to rebase is given, first switch to it.
 case "$#" in
 2)
index 3f1b3ca..13b85dd 100755 (executable)
@@ -31,10 +31,10 @@ sub cleanup_compose_files();
 my $compose_filename = ".msg.$$";
 
 # Variables we fill in automatically, or via prompting:
-my (@to,@cc,$initial_reply_to,$initial_subject,@files,$from,$compose);
+my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose);
 
 # Behavior modification variables
-my ($chain_reply_to, $smtp_server, $quiet) = (1, "localhost", 0);
+my ($chain_reply_to, $smtp_server, $quiet, $suppress_from, $no_signed_off_cc) = (1, "localhost", 0, 0, 0);
 
 # Example reply to:
 #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
@@ -48,10 +48,13 @@ my $rc = GetOptions("from=s" => \$from,
                     "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
                    "to=s" => \@to,
+                   "cc=s" => \@initial_cc,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
+                   "suppress-from" => \$suppress_from,
+                   "no-signed-off-cc" => \$no_signed_off_cc,
         );
 
 # Now, let's fill any that aren't set in with defaults:
@@ -197,6 +200,9 @@ Options:
 
    --to           Specify the primary "To:" line of the email.
 
+   --cc           Specify an initial "Cc:" list for the entire series
+                  of emails.
+
    --compose      Use \$EDITOR to edit an introductory message for the
                   patch series.
 
@@ -212,13 +218,19 @@ Options:
                   email sent, rather than to the first email sent.
                   Defaults to on.
 
+   --no-signed-off-cc Suppress the automatic addition of email addresses
+                 that appear in a Signed-off-by: line, to the cc: list.
+                Note: Using this option is not recommended.
+
    --smtp-server  If set, specifies the outgoing SMTP server to use.
                   Defaults to localhost.
 
+  --suppress-from Supress sending emails to yourself if your address
+                  appears in a From: line.
+
    --quiet     Make git-send-email less verbose.  One line per email should be
                all that is output.
 
-
 Error: Please specify a file or a directory on the command line.
 EOT
        exit(1);
@@ -290,7 +302,7 @@ $subject = $initial_subject;
 foreach my $t (@files) {
        open(F,"<",$t) or die "can't open file $t";
 
-       @cc = ();
+       @cc = @initial_cc;
        my $found_mbox = 0;
        my $header_done = 0;
        $message = "";
@@ -304,6 +316,7 @@ foreach my $t (@files) {
                                        $subject = $1;
 
                                } elsif (/^(Cc|From):\s+(.*)$/) {
+                                       next if ($2 eq $from && $suppress_from);
                                        printf("(mbox) Adding cc: %s from line '%s'\n",
                                                $2, $_) unless $quiet;
                                        push @cc, $2;
@@ -332,7 +345,7 @@ foreach my $t (@files) {
                        }
                } else {
                        $message .=  $_;
-                       if (/^Signed-off-by: (.*)$/i) {
+                       if (/^Signed-off-by: (.*)$/i && !$no_signed_off_cc) {
                                my $c = $1;
                                chomp $c;
                                push @cc, $c;
index f17d5a2..c536d70 100755 (executable)
@@ -30,19 +30,19 @@ die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_s,$opt_l,$opt_d,$opt_D);
+our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_r,$opt_s,$opt_l,$opt_d,$opt_D);
 
 sub usage() {
        print STDERR <<END;
 Usage: ${\basename $0}     # fetch/update GIT from SVN
        [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
        [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
-       [-d|-D] [-i] [-u] [-s start_chg] [-m] [-M regex] [SVN_URL]
+       [-d|-D] [-i] [-u] [-r] [-s start_chg] [-m] [-M regex] [SVN_URL]
 END
        exit(1);
 }
 
-getopts("b:C:dDhil:mM:o:s:t:T:uv") or usage();
+getopts("b:C:dDhil:mM:o:rs:t:T:uv") or usage();
 usage if $opt_h;
 
 my $tag_name = $opt_t || "tags";
@@ -650,6 +650,7 @@ sub commit {
                $pr->reader();
 
                $message =~ s/[\s\n]+\z//;
+               $message = "r$revision: $message" if $opt_r;
 
                print $pw "$message\n"
                        or die "Error writing to git-commit-tree: $!\n";
index 7024cf1..df93cf2 100644 (file)
@@ -25,6 +25,8 @@ static int line_terminator = '\n';
 static int prefix_len = 0, prefix_offset = 0;
 static const char *prefix = NULL;
 static const char **pathspec = NULL;
+static int error_unmatch = 0;
+static char *ps_matched = NULL;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -325,7 +327,8 @@ static int cmp_name(const void *p1, const void *p2)
  * Match a pathspec against a filename. The first "len" characters
  * are the common prefix
  */
-static int match(const char **spec, const char *filename, int len)
+static int match(const char **spec, char *ps_matched,
+                const char *filename, int len)
 {
        const char *m;
 
@@ -333,17 +336,24 @@ static int match(const char **spec, const char *filename, int len)
                int matchlen = strlen(m + len);
 
                if (!matchlen)
-                       return 1;
+                       goto matched;
                if (!strncmp(m + len, filename + len, matchlen)) {
                        if (m[len + matchlen - 1] == '/')
-                               return 1;
+                               goto matched;
                        switch (filename[len + matchlen]) {
                        case '/': case '\0':
-                               return 1;
+                               goto matched;
                        }
                }
                if (!fnmatch(m + len, filename + len, 0))
-                       return 1;
+                       goto matched;
+               if (ps_matched)
+                       ps_matched++;
+               continue;
+       matched:
+               if (ps_matched)
+                       *ps_matched = 1;
+               return 1;
        }
        return 0;
 }
@@ -356,7 +366,7 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
        if (len >= ent->len)
                die("git-ls-files: internal error - directory entry not superset of prefix");
 
-       if (pathspec && !match(pathspec, ent->name, len))
+       if (pathspec && !match(pathspec, ps_matched, ent->name, len))
                return;
 
        fputs(tag, stdout);
@@ -444,7 +454,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        if (len >= ce_namelen(ce))
                die("git-ls-files: internal error - cache entry not superset of prefix");
 
-       if (pathspec && !match(pathspec, ce->name, len))
+       if (pathspec && !match(pathspec, ps_matched, ce->name, len))
                return;
 
        if (!show_stage) {
@@ -699,6 +709,10 @@ int main(int argc, const char **argv)
                        prefix_offset = 0;
                        continue;
                }
+               if (!strcmp(arg, "--error-unmatch")) {
+                       error_unmatch = 1;
+                       continue;
+               }
                if (*arg == '-')
                        usage(ls_files_usage);
                break;
@@ -710,6 +724,14 @@ int main(int argc, const char **argv)
        if (pathspec)
                verify_pathspec();
 
+       /* Treat unmatching pathspec elements as errors */
+       if (pathspec && error_unmatch) {
+               int num;
+               for (num = 0; pathspec[num]; num++)
+                       ;
+               ps_matched = xcalloc(1, num);
+       }
+
        if (show_ignored && !exc_given) {
                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
                        argv[0]);
@@ -725,5 +747,20 @@ int main(int argc, const char **argv)
        if (prefix)
                prune_cache();
        show_files();
+
+       if (ps_matched) {
+               /* We need to make sure all pathspec matched otherwise
+                * it is an error.
+                */
+               int num, errors = 0;
+               for (num = 0; pathspec[num]; num++) {
+                       if (ps_matched[num])
+                               continue;
+                       error("pathspec '%s' did not match any.",
+                             pathspec[num] + prefix_offset);
+               }
+               return errors ? 1 : 0;
+       }
+
        return 0;
 }
index 67a7ecd..eca32b6 100644 (file)
@@ -84,7 +84,7 @@ static void show_pack_info(struct packed_git *p)
                char type[20];
                unsigned long size;
                unsigned long store_size;
-               int delta_chain_length;
+               unsigned int delta_chain_length;
 
                if (nth_packed_object_sha1(p, i, sha1))
                        die("internal error pack-check nth-packed-object");
@@ -98,7 +98,7 @@ static void show_pack_info(struct packed_git *p)
                if (!delta_chain_length)
                        printf("%-6s %lu %u\n", type, size, e.offset);
                else
-                       printf("%-6s %lu %u %d %s\n", type, size, e.offset,
+                       printf("%-6s %lu %u %u %s\n", type, size, e.offset,
                               delta_chain_length, sha1_to_hex(base_sha1));
        }
 
index 3d11a9b..64cf245 100644 (file)
@@ -830,7 +830,7 @@ void packed_object_info_detail(struct pack_entry *e,
                               char *type,
                               unsigned long *size,
                               unsigned long *store_size,
-                              int *delta_chain_length,
+                              unsigned int *delta_chain_length,
                               unsigned char *base_sha1)
 {
        struct packed_git *p = e->p;
@@ -844,7 +844,7 @@ void packed_object_info_detail(struct pack_entry *e,
        if (kind != OBJ_DELTA)
                *delta_chain_length = 0;
        else {
-               int chain_length = 0;
+               unsigned int chain_length = 0;
                memcpy(base_sha1, pack, 20);
                do {
                        struct pack_entry base_ent;
diff --git a/templates/hooks--pre-rebase b/templates/hooks--pre-rebase
new file mode 100644 (file)
index 0000000..981c454
--- /dev/null
@@ -0,0 +1,150 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+       topic="refs/heads/$2"
+else
+       topic=`git symbolic-ref HEAD`
+fi
+
+case "$basebranch,$topic" in
+master,refs/heads/??/*)
+       ;;
+*)
+       exit 0 ;# we do not interrupt others.
+       ;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master.  Is it OK to rebase it?
+
+# Is topic fully merged to master?
+not_in_master=`git-rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+       echo >&2 "$topic is fully merged to master; better remove it."
+       exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next?  If so you should not be rebasing it.
+only_next_1=`git-rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git-rev-list ^master           ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+       not_in_topic=`git-rev-list "^$topic" master`
+       if test -z "$not_in_topic"
+       then
+               echo >&2 "$topic is already up-to-date with master"
+               exit 1 ;# we could allow it, but there is no point.
+       else
+               exit 0
+       fi
+else
+       not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"`
+       perl -e '
+               my $topic = $ARGV[0];
+               my $msg = "* $topic has commits already merged to public branch:\n";
+               my (%not_in_next) = map {
+                       /^([0-9a-f]+) /;
+                       ($1 => 1);
+               } split(/\n/, $ARGV[1]);
+               for my $elem (map {
+                               /^([0-9a-f]+) (.*)$/;
+                               [$1 => $2];
+                       } split(/\n/, $ARGV[2])) {
+                       if (!exists $not_in_next{$elem->[0]}) {
+                               if ($msg) {
+                                       print STDERR $msg;
+                                       undef $msg;
+                               }
+                               print STDERR " $elem->[1]\n";
+                       }
+               }
+       ' "$topic" "$not_in_next" "$not_in_master"
+       exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+   merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+   it is deleted.  If you need to build on top of it to correct
+   earlier mistakes, a new topic branch is created by forking at
+   the tip of the "master".  This is not strictly necessary, but
+   it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+   branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next".  Young
+    topic branches can have stupid mistakes you would rather
+    clean up before publishing, and things that have not been
+    merged into other branches can be easily rebased without
+    affecting other people.  But once it is published, you would
+    not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+    Then you can delete it.  More importantly, you should not
+    build on top of it -- other people may already want to
+    change things related to the topic as patches against your
+    "master", so if you need further changes, it is better to
+    fork the topic (perhaps with the same name) afresh from the
+    tip of "master".
+
+Let's look at this example:
+
+                  o---o---o---o---o---o---o---o---o---o "next"
+                 /       /           /           /
+                /   a---a---b A     /           /
+               /   /               /           /
+              /   /   c---c---c---c B         /
+             /   /   /             \         /
+            /   /   /   b---b C     \       /
+           /   /   /   /             \     /
+    ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished.  It has been fully merged up to "master" and "next",
+   and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+       git-rev-list ^master ^topic next
+       git-rev-list ^master        next
+
+       if these match, topic has not merged in next at all.
+
+To compute (2):
+
+       git-rev-list master..topic
+
+       if this is empty, it is fully merged to "master".