From: Junio C Hamano Date: Tue, 14 Feb 2006 07:44:41 +0000 (-0800) Subject: Merge branch 'master' X-Git-Tag: v1.3.0-rc1~54^2~74 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=65520c8e50ec227bc72e4196de03f785f850e5ad;hp=6a9b87972f27edfe53da4ce016adf4c0cd42f5e6;p=git.git Merge branch 'master' * master: Merge some proposed fixes s/SHELL/SHELL_PATH/ in Makefile bisect: remove BISECT_NAMES after done. Documentation: git-ls-files asciidocco. Documentation: git-commit in 1.2.X series defaults to --include. Merge branch 'pb/bisect' --- diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index fe534129..fe8ac1b0 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -8,7 +8,8 @@ git-ls-files - Information about files in the index/working directory SYNOPSIS -------- -'git-ls-files' [-z] [-t] +[verse] +'git-ls-files' [-z] [-t] [-v] (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\* (-[c|d|o|i|s|u|k|m])\* [-x |--exclude=] @@ -82,6 +83,10 @@ OPTIONS K:: to be killed ?:: other +-v:: + Similar to `-t`, but use lowercase letters for files + that are marked as 'always matching index'. + --full-name:: When run from a subdirectory, the command usually outputs paths relative to the current directory. This diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 00537d89..8c58685e 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-update-index.txt b/Documentation/git-update-index.txt index c74311da..0a1b0ad5 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -8,11 +8,14 @@ git-update-index - Modifies the index or directory cache SYNOPSIS -------- +[verse] 'git-update-index' [--add] [--remove | --force-remove] [--replace] [--refresh [-q] [--unmerged] [--ignore-missing]] [--cacheinfo ]\* [--chmod=(+|-)x] + [--assume-unchanged | --no-assume-unchanged] + [--really-refresh] [--info-only] [--index-info] [-z] [--stdin] [--verbose] @@ -65,6 +68,18 @@ OPTIONS --chmod=(+|-)x:: Set the execute permissions on the updated files. +--assume-unchanged, --no-assume-unchanged:: + When these flags are specified, the object name recorded + for the paths are not updated. Instead, these options + sets and unsets the "assume unchanged" bit for the + paths. When the "assume unchanged" bit is on, git stops + checking the working tree files for possible + modifications, so you need to manually unset the bit to + tell git when you change the working tree file. This is + sometimes helpful when working with a big project on a + filesystem that has very slow lstat(2) system call + (e.g. cifs). + --info-only:: Do not create objects in the object database for all arguments that follow this flag; just insert @@ -193,6 +208,37 @@ $ git ls-files -s ------------ +Using "assume unchanged" bit +---------------------------- + +Many operations in git depend on your filesystem to have an +efficient `lstat(2)` implementation, so that `st_mtime` +information for working tree files can be cheaply checked to see +if the file contents have changed from the version recorded in +the index file. Unfortunately, some filesystems have +inefficient `lstat(2)`. If your filesystem is one of them, you +can set "assume unchanged" bit to paths you have not changed to +cause git not to do this check. Note that setting this bit on a +path does not mean git will check the contents of the file to +see if it has changed -- it makes git to omit any checking and +assume it has *not* changed. When you make changes to working +tree files, you have to explicitly tell git about it by dropping +"assume unchanged" bit, either before or after you modify them. + +In order to set "assume unchanged" bit, use `--assume-unchanged` +option. To unset, use `--no-assume-unchanged`. + +The command looks at `core.ignorestat` configuration variable. When +this is true, paths updated with `git-update-index paths...` and +paths updated with other git commands that update both index and +working tree (e.g. `git-apply --index`, `git-checkout-index -u`, +and `git-read-tree -u`) are automatically marked as "assume +unchanged". Note that "assume unchanged" bit is *not* set if +`git-update-index --refresh` finds the working tree file matches +the index (use `git-update-index --really-refresh` if you want +to mark them as "assume unchanged"). + + Examples -------- To update and refresh only the files already checked out: @@ -201,6 +247,35 @@ To update and refresh only the files already checked out: $ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh ---------------- +On an inefficient filesystem with `core.ignorestat` set: + +------------ +$ git update-index --really-refresh <1> +$ git update-index --no-assume-unchanged foo.c <2> +$ git diff --name-only <3> +$ edit foo.c +$ git diff --name-only <4> +M foo.c +$ git update-index foo.c <5> +$ git diff --name-only <6> +$ edit foo.c +$ git diff --name-only <7> +$ git update-index --no-assume-unchanged foo.c <8> +$ git diff --name-only <9> +M foo.c + +<1> forces lstat(2) to set "assume unchanged" bits for paths + that match index. +<2> mark the path to be edited. +<3> this does lstat(2) and finds index matches the path. +<4> this does lstat(2) and finds index does not match the path. +<5> registering the new version to index sets "assume unchanged" bit. +<6> and it is assumed unchanged. +<7> even after you edit it. +<8> you can tell about the change after the fact. +<9> now it checks with lstat(2) and finds it has been changed. +------------ + Configuration ------------- @@ -213,6 +288,9 @@ in the index and the file mode on the filesystem if they differ only on executable bit. On such an unfortunate filesystem, you may need to use `git-update-index --chmod=`. +The command looks at `core.ignorestat` configuration variable. See +'Using "assume unchanged" bit' section above. + See Also -------- diff --git a/apply.c b/apply.c index 2ad47fbb..35ae48ee 100644 --- a/apply.c +++ b/apply.c @@ -1309,7 +1309,7 @@ static int check_patch(struct patch *patch) return -1; } - changed = ce_match_stat(active_cache[pos], &st); + changed = ce_match_stat(active_cache[pos], &st, 1); if (changed) return error("%s: does not match index", old_name); diff --git a/cache.h b/cache.h index c255421a..d7f5bdef 100644 --- a/cache.h +++ b/cache.h @@ -91,6 +91,7 @@ struct cache_entry { #define CE_NAMEMASK (0x0fff) #define CE_STAGEMASK (0x3000) #define CE_UPDATE (0x4000) +#define CE_VALID (0x8000) #define CE_STAGESHIFT 12 #define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT)) @@ -144,8 +145,8 @@ extern int add_cache_entry(struct cache_entry *ce, int option); extern int remove_cache_entry_at(int pos); extern int remove_file_from_cache(const char *path); extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); -extern int ce_match_stat(struct cache_entry *ce, struct stat *st); -extern int ce_modified(struct cache_entry *ce, struct stat *st); +extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int); +extern int ce_modified(struct cache_entry *ce, struct stat *st, int); extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type); extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object); @@ -161,6 +162,7 @@ extern int commit_index_file(struct cache_file *); extern void rollback_index_file(struct cache_file *); extern int trust_executable_bit; +extern int assume_unchanged; extern int only_use_symrefs; extern int diff_rename_limit_default; extern int shared_repository; diff --git a/checkout-index.c b/checkout-index.c index 53dd8cba..957b4a86 100644 --- a/checkout-index.c +++ b/checkout-index.c @@ -116,6 +116,7 @@ int main(int argc, char **argv) int all = 0; prefix = setup_git_directory(); + git_config(git_default_config); prefix_length = prefix ? strlen(prefix) : 0; if (read_cache() < 0) { diff --git a/config.c b/config.c index 8355224b..7dbdce19 100644 --- a/config.c +++ b/config.c @@ -222,6 +222,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.ignorestat")) { + assume_unchanged = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "core.symrefsonly")) { only_use_symrefs = git_config_bool(var, value); return 0; diff --git a/diff-files.c b/diff-files.c index 7db5ce64..574294f3 100644 --- a/diff-files.c +++ b/diff-files.c @@ -191,7 +191,7 @@ int main(int argc, const char **argv) show_file('-', ce); continue; } - changed = ce_match_stat(ce, &st); + changed = ce_match_stat(ce, &st, 0); if (!changed && !diff_options.find_copies_harder) continue; oldmode = ntohl(ce->ce_mode); diff --git a/diff-index.c b/diff-index.c index f8a102ec..12a9418d 100644 --- a/diff-index.c +++ b/diff-index.c @@ -33,7 +33,7 @@ static int get_stat_data(struct cache_entry *ce, } return -1; } - changed = ce_match_stat(ce, &st); + changed = ce_match_stat(ce, &st, 0); if (changed) { mode = create_ce_mode(st.st_mode); if (!trust_executable_bit && diff --git a/diff.c b/diff.c index 890bdaa6..804c08c2 100644 --- a/diff.c +++ b/diff.c @@ -311,7 +311,7 @@ static int work_tree_matches(const char *name, const unsigned char *sha1) ce = active_cache[pos]; if ((lstat(name, &st) < 0) || !S_ISREG(st.st_mode) || /* careful! */ - ce_match_stat(ce, &st) || + ce_match_stat(ce, &st, 0) || memcmp(sha1, ce->sha1, 20)) return 0; /* we return 1 only when we can stat, it is a regular file, diff --git a/entry.c b/entry.c index 6c47c3a3..8fb99bc8 100644 --- a/entry.c +++ b/entry.c @@ -123,7 +123,7 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state) strcpy(path + len, ce->name); if (!lstat(path, &st)) { - unsigned changed = ce_match_stat(ce, &st); + unsigned changed = ce_match_stat(ce, &st, 1); if (!changed) return 0; if (!state->force) { diff --git a/environment.c b/environment.c index 0596fc64..251e53ca 100644 --- a/environment.c +++ b/environment.c @@ -12,6 +12,7 @@ char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; int trust_executable_bit = 1; +int assume_unchanged = 0; int only_use_symrefs = 0; int repository_format_version = 0; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; diff --git a/git-rebase.sh b/git-rebase.sh index 16d43598..f84160d3 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 3f1b3ca7..13b85ddd 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/ls-files.c b/ls-files.c index 7024cf18..5539fd85 100644 --- a/ls-files.c +++ b/ls-files.c @@ -20,6 +20,7 @@ static int show_unmerged = 0; static int show_modified = 0; static int show_killed = 0; static int show_other_directories = 0; +static int show_valid_bit = 0; static int line_terminator = '\n'; static int prefix_len = 0, prefix_offset = 0; @@ -447,6 +448,23 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) if (pathspec && !match(pathspec, ce->name, len)) return; + if (tag && *tag && show_valid_bit && + (ce->ce_flags & htons(CE_VALID))) { + static char alttag[4]; + memcpy(alttag, tag, 3); + if (isalpha(tag[0])) + alttag[0] = tolower(tag[0]); + else if (tag[0] == '?') + alttag[0] = '!'; + else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + tag = alttag; + } + if (!show_stage) { fputs(tag, stdout); write_name_quoted("", 0, ce->name + offset, @@ -523,7 +541,7 @@ static void show_files(void) err = lstat(ce->name, &st); if (show_deleted && err) show_ce_entry(tag_removed, ce); - if (show_modified && ce_modified(ce, &st)) + if (show_modified && ce_modified(ce, &st, 0)) show_ce_entry(tag_modified, ce); } } @@ -596,7 +614,7 @@ static void verify_pathspec(void) } static const char ls_files_usage[] = - "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* " + "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " "[ --ignored ] [--exclude=] [--exclude-from=] " "[ --exclude-per-directory= ] [--full-name] [--] []*"; @@ -621,13 +639,15 @@ int main(int argc, const char **argv) line_terminator = 0; continue; } - if (!strcmp(arg, "-t")) { + if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) { tag_cached = "H "; tag_unmerged = "M "; tag_removed = "R "; tag_modified = "C "; tag_other = "? "; tag_killed = "K "; + if (arg[1] == 'v') + show_valid_bit = 1; continue; } if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) { diff --git a/read-cache.c b/read-cache.c index c5474d49..f97f92d9 100644 --- a/read-cache.c +++ b/read-cache.c @@ -27,6 +27,9 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) ce->ce_uid = htonl(st->st_uid); ce->ce_gid = htonl(st->st_gid); ce->ce_size = htonl(st->st_size); + + if (assume_unchanged) + ce->ce_flags |= htons(CE_VALID); } static int ce_compare_data(struct cache_entry *ce, struct stat *st) @@ -146,9 +149,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) return changed; } -int ce_match_stat(struct cache_entry *ce, struct stat *st) +int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) { - unsigned int changed = ce_match_stat_basic(ce, st); + unsigned int changed; + + /* + * If it's marked as always valid in the index, it's + * valid whatever the checked-out copy says. + */ + if (!ignore_valid && (ce->ce_flags & htons(CE_VALID))) + return 0; + + changed = ce_match_stat_basic(ce, st); /* * Within 1 second of this sequence: @@ -164,7 +176,7 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st) * effectively mean we can make at most one commit per second, * which is not acceptable. Instead, we check cache entries * whose mtime are the same as the index file timestamp more - * careful than others. + * carefully than others. */ if (!changed && index_file_timestamp && @@ -174,10 +186,10 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st) return changed; } -int ce_modified(struct cache_entry *ce, struct stat *st) +int ce_modified(struct cache_entry *ce, struct stat *st, int really) { int changed, changed_fs; - changed = ce_match_stat(ce, st); + changed = ce_match_stat(ce, st, really); if (!changed) return 0; /* @@ -233,6 +245,11 @@ int cache_name_compare(const char *name1, int flags1, const char *name2, int fla return -1; if (len1 > len2) return 1; + + /* Compare stages */ + flags1 &= CE_STAGEMASK; + flags2 &= CE_STAGEMASK; + if (flags1 < flags2) return -1; if (flags1 > flags2) @@ -430,6 +447,7 @@ int add_cache_entry(struct cache_entry *ce, int option) int ok_to_add = option & ADD_CACHE_OK_TO_ADD; int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE; int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; + pos = cache_name_pos(ce->name, ntohs(ce->ce_flags)); /* existing match? Just replace it. */ diff --git a/read-tree.c b/read-tree.c index 5580f15b..52f06e31 100644 --- a/read-tree.c +++ b/read-tree.c @@ -349,7 +349,7 @@ static void verify_uptodate(struct cache_entry *ce) return; if (!lstat(ce->name, &st)) { - unsigned changed = ce_match_stat(ce, &st); + unsigned changed = ce_match_stat(ce, &st, 1); if (!changed) return; errno = 0; diff --git a/templates/hooks--pre-rebase b/templates/hooks--pre-rebase new file mode 100644 index 00000000..981c454c --- /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". diff --git a/update-index.c b/update-index.c index afec98dd..ce1db38d 100644 --- a/update-index.c +++ b/update-index.c @@ -23,6 +23,10 @@ static int quiet; /* --refresh needing update is not error */ static int info_only; static int force_remove; static int verbose; +static int mark_valid_only = 0; +#define MARK_VALID 1 +#define UNMARK_VALID 2 + /* Three functions to allow overloaded pointer return; see linux/err.h */ static inline void *ERR_PTR(long error) @@ -53,6 +57,25 @@ static void report(const char *fmt, ...) va_end(vp); } +static int mark_valid(const char *path) +{ + int namelen = strlen(path); + int pos = cache_name_pos(path, namelen); + if (0 <= pos) { + switch (mark_valid_only) { + case MARK_VALID: + active_cache[pos]->ce_flags |= htons(CE_VALID); + break; + case UNMARK_VALID: + active_cache[pos]->ce_flags &= ~htons(CE_VALID); + break; + } + active_cache_changed = 1; + return 0; + } + return -1; +} + static int add_file_to_cache(const char *path) { int size, namelen, option, status; @@ -94,6 +117,7 @@ static int add_file_to_cache(const char *path) ce = xmalloc(size); memset(ce, 0, size); memcpy(ce->name, path, namelen); + ce->ce_flags = htons(namelen); fill_stat_cache_info(ce, &st); ce->ce_mode = create_ce_mode(st.st_mode); @@ -105,7 +129,6 @@ static int add_file_to_cache(const char *path) if (0 <= pos) ce->ce_mode = active_cache[pos]->ce_mode; } - ce->ce_flags = htons(namelen); if (index_path(ce->sha1, path, &st, !info_only)) return -1; @@ -128,7 +151,7 @@ static int add_file_to_cache(const char *path) * For example, you'd want to do this after doing a "git-read-tree", * to link up the stat cache details with the proper files. */ -static struct cache_entry *refresh_entry(struct cache_entry *ce) +static struct cache_entry *refresh_entry(struct cache_entry *ce, int really) { struct stat st; struct cache_entry *updated; @@ -137,21 +160,36 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce) if (lstat(ce->name, &st) < 0) return ERR_PTR(-errno); - changed = ce_match_stat(ce, &st); - if (!changed) - return NULL; + changed = ce_match_stat(ce, &st, really); + if (!changed) { + if (really && assume_unchanged && + !(ce->ce_flags & htons(CE_VALID))) + ; /* mark this one VALID again */ + else + return NULL; + } - if (ce_modified(ce, &st)) + if (ce_modified(ce, &st, really)) return ERR_PTR(-EINVAL); size = ce_size(ce); updated = xmalloc(size); memcpy(updated, ce, size); fill_stat_cache_info(updated, &st); + + /* In this case, if really is not set, we should leave + * CE_VALID bit alone. Otherwise, paths marked with + * --no-assume-unchanged (i.e. things to be edited) will + * reacquire CE_VALID bit automatically, which is not + * really what we want. + */ + if (!really && assume_unchanged && !(ce->ce_flags & htons(CE_VALID))) + updated->ce_flags &= ~htons(CE_VALID); + return updated; } -static int refresh_cache(void) +static int refresh_cache(int really) { int i; int has_errors = 0; @@ -171,12 +209,19 @@ static int refresh_cache(void) continue; } - new = refresh_entry(ce); + new = refresh_entry(ce, really); if (!new) continue; if (IS_ERR(new)) { if (not_new && PTR_ERR(new) == -ENOENT) continue; + if (really && PTR_ERR(new) == -EINVAL) { + /* If we are doing --really-refresh that + * means the index is not valid anymore. + */ + ce->ce_flags &= ~htons(CE_VALID); + active_cache_changed = 1; + } if (quiet) continue; printf("%s: needs update\n", ce->name); @@ -274,6 +319,8 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(len, stage); ce->ce_mode = create_ce_mode(mode); + if (assume_unchanged) + ce->ce_flags |= htons(CE_VALID); option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; if (add_cache_entry(ce, option)) @@ -317,6 +364,12 @@ static void update_one(const char *path, const char *prefix, int prefix_length) fprintf(stderr, "Ignoring path %s\n", path); return; } + if (mark_valid_only) { + if (mark_valid(p)) + die("Unable to mark file %s", path); + return; + } + if (force_remove) { if (remove_file_from_cache(p)) die("git-update-index: unable to remove %s", path); @@ -467,7 +520,11 @@ int main(int argc, const char **argv) continue; } if (!strcmp(path, "--refresh")) { - has_errors |= refresh_cache(); + has_errors |= refresh_cache(0); + continue; + } + if (!strcmp(path, "--really-refresh")) { + has_errors |= refresh_cache(1); continue; } if (!strcmp(path, "--cacheinfo")) { @@ -493,6 +550,14 @@ int main(int argc, const char **argv) die("git-update-index: %s cannot chmod %s", path, argv[i]); continue; } + if (!strcmp(path, "--assume-unchanged")) { + mark_valid_only = MARK_VALID; + continue; + } + if (!strcmp(path, "--no-assume-unchanged")) { + mark_valid_only = UNMARK_VALID; + continue; + } if (!strcmp(path, "--info-only")) { info_only = 1; continue; diff --git a/write-tree.c b/write-tree.c index f866059f..addb5de3 100644 --- a/write-tree.c +++ b/write-tree.c @@ -111,7 +111,7 @@ int main(int argc, char **argv) funny = 0; for (i = 0; i < entries; i++) { struct cache_entry *ce = active_cache[i]; - if (ntohs(ce->ce_flags) & ~CE_NAMEMASK) { + if (ce_stage(ce)) { if (10 < ++funny) { fprintf(stderr, "...\n"); break;