diff options
-rw-r--r-- | Documentation/git-commit.txt | 19 | ||||
-rw-r--r-- | Documentation/git-send-email.txt | 10 | ||||
-rw-r--r-- | Documentation/git-svnimport.txt | 4 | ||||
-rw-r--r-- | cache.h | 2 | ||||
-rwxr-xr-x | git-bisect.sh | 17 | ||||
-rwxr-xr-x | git-commit.sh | 23 | ||||
-rwxr-xr-x | git-rebase.sh | 9 | ||||
-rwxr-xr-x | git-send-email.perl | 23 | ||||
-rwxr-xr-x | git-svnimport.perl | 7 | ||||
-rw-r--r-- | ls-files.c | 51 | ||||
-rw-r--r-- | pack-check.c | 4 | ||||
-rw-r--r-- | sha1_file.c | 4 | ||||
-rw-r--r-- | templates/hooks--pre-rebase | 150 |
13 files changed, 271 insertions, 52 deletions
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 5b1b4d3780..214ed235c5 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -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. diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 00537d8907..8c58685e28 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -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 diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index 63e28b89d5..5c543d5d1b 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -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 @@ -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); diff --git a/git-bisect.sh b/git-bisect.sh index 07502536ce..03df1433ef 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -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" } diff --git a/git-commit.sh b/git-commit.sh index 59551d99f9..f7ee1aadee 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -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" diff --git a/git-rebase.sh b/git-rebase.sh index 16d4359830..f84160d324 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -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) diff --git a/git-send-email.perl b/git-send-email.perl index 3f1b3ca788..13b85dddd1 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -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; diff --git a/git-svnimport.perl b/git-svnimport.perl index f17d5a27c8..c536d7026d 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -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"; diff --git a/ls-files.c b/ls-files.c index 7024cf18e1..df93cf2263 100644 --- a/ls-files.c +++ b/ls-files.c @@ -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; } diff --git a/pack-check.c b/pack-check.c index 67a7ecdf16..eca32b6cab 100644 --- a/pack-check.c +++ b/pack-check.c @@ -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)); } diff --git a/sha1_file.c b/sha1_file.c index 3d11a9bfdc..64cf245418 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -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 index 0000000000..981c454cda --- /dev/null +++ b/templates/hooks--pre-rebase @@ -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". |