From acd2a45b83e50c0f33b01ee74df241f1adfdff39 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 11 Feb 2009 02:28:03 -0800 Subject: [PATCH 001/384] Refuse updating the current branch in a non-bare repository via push This makes git-push refuse pushing into a non-bare repository to update the current branch by default. To help people who are used to be able to do this (and later "reset --hard" it in some other way), an error message is issued when this refusal is triggered, instructing how to resurrect the old behaviour. Hosting sites that do not give the users direct access to customize their repositories (e.g. repo.or.cz, gitorious, github etc.) may further want to explicitly set the configuration variable to "refuse" for their customers' repositories. Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 40 ++++++++++++++++--------------------- t/t5400-send-pack.sh | 3 ++- t/t5401-update-hooks.sh | 1 + t/t5405-send-pack-rewind.sh | 1 + t/t5516-fetch-push.sh | 1 + t/t5517-push-mirror.sh | 3 ++- t/t5522-pull-symlink.sh | 20 ++++++++++++------- t/t5701-clone-local.sh | 4 +++- 8 files changed, 40 insertions(+), 33 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 6ec1d056e6..b8b69dde48 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -218,33 +218,27 @@ static int is_ref_checked_out(const char *ref) return !strcmp(head_name, ref); } -static char *warn_unconfigured_deny_msg[] = { - "Updating the currently checked out branch may cause confusion,", - "as the index and work tree do not reflect changes that are in HEAD.", - "As a result, you may see the changes you just pushed into it", - "reverted when you run 'git diff' over there, and you may want", - "to run 'git reset --hard' before starting to work to recover.", +static char *refuse_unconfigured_deny_msg[] = { + "By default, updating the current branch in a non-bare repository", + "is denied, because it will make the index and work tree inconsistent", + "with what you pushed, and will require 'git reset --hard' to match", + "the work tree to HEAD.", "", "You can set 'receive.denyCurrentBranch' configuration variable to", - "'refuse' in the remote repository to forbid pushing into its", - "current branch." + "'ignore' or 'warn' in the remote repository to allow pushing into", + "its current branch; however, this is not recommended unless you", + "arranged to update its work tree to match what you pushed in some", + "other way.", "", - "To allow pushing into the current branch, you can set it to 'ignore';", - "but this is not recommended unless you arranged to update its work", - "tree to match what you pushed in some other way.", - "", - "To squelch this message, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse updating the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message and still keep the default behaviour, set", + "'receive.denyCurrentBranch' configuration variable to 'refuse'." }; -static void warn_unconfigured_deny(void) +static void refuse_unconfigured_deny(void) { int i; - for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++) - warning("%s", warn_unconfigured_deny_msg[i]); + for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++) + error("%s", refuse_unconfigured_deny_msg[i]); } static char *warn_unconfigured_deny_delete_current_msg[] = { @@ -290,14 +284,14 @@ static const char *update(struct command *cmd) switch (deny_current_branch) { case DENY_IGNORE: break; - case DENY_UNCONFIGURED: case DENY_WARN: warning("updating the current branch"); - if (deny_current_branch == DENY_UNCONFIGURED) - warn_unconfigured_deny(); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: error("refusing to update checked out branch: %s", name); + if (deny_current_branch == DENY_UNCONFIGURED) + refuse_unconfigured_deny(); return "branch is currently checked out"; } } diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index f2d5581b12..8463332cb8 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -32,7 +32,7 @@ test_expect_success setup ' done && git update-ref HEAD "$commit" && git clone ./. victim && - ( cd victim && git log ) && + ( cd victim && git config receive.denyCurrentBranch warn && git log ) && git update-ref HEAD "$zero" && parent=$zero && i=0 && @@ -129,6 +129,7 @@ rewound_push_setup() { cd parent && git init && echo one >file && git add file && git commit -m one && + git config receive.denyCurrentBranch warn && echo two >file && git commit -a -m two ) && git clone parent child && diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 64f66c94f3..325714e529 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -18,6 +18,7 @@ test_expect_success setup ' git update-ref refs/heads/master $commit0 && git update-ref refs/heads/tofail $commit1 && git clone ./. victim && + GIT_DIR=victim/.git git config receive.denyCurrentBranch warn && GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 && git update-ref refs/heads/master $commit1 && git update-ref refs/heads/tofail $commit0 diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh index cb9aacc7bc..4bda18a662 100755 --- a/t/t5405-send-pack-rewind.sh +++ b/t/t5405-send-pack-rewind.sh @@ -8,6 +8,7 @@ test_expect_success setup ' >file1 && git add file1 && test_tick && git commit -m Initial && + git config receive.denyCurrentBranch warn && mkdir another && ( cd another && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2d2633f3f8..6529d97dc0 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -12,6 +12,7 @@ mk_empty () { ( cd testrepo && git init && + git config receive.denyCurrentBranch warn && mv .git/hooks .git/hooks-disabled ) } diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index ea49dedbf8..e2ad260508 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -19,7 +19,8 @@ mk_repo_pair () { mkdir mirror && ( cd mirror && - git init + git init && + git config receive.denyCurrentBranch warn ) && mkdir master && ( diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh index 86bbd7d024..7206817ca1 100755 --- a/t/t5522-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -20,13 +20,19 @@ fi # # The working directory is subdir-link. -mkdir subdir -echo file >subdir/file -git add subdir/file -git commit -q -m file -git clone -q . clone-repo -ln -s clone-repo/subdir/ subdir-link - +test_expect_success setup ' + mkdir subdir && + echo file >subdir/file && + git add subdir/file && + git commit -q -m file && + git clone -q . clone-repo && + ln -s clone-repo/subdir/ subdir-link && + ( + cd clone-repo && + git config receive.denyCurrentBranch warn + ) && + git config receive.denyCurrentBranch warn +' # Demonstrate that things work if we just avoid the symlink # diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index 19b5c0d552..8b4c356cd2 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -119,7 +119,9 @@ test_expect_success 'bundle clone with nonexistent HEAD' ' test_expect_success 'clone empty repository' ' cd "$D" && mkdir empty && - (cd empty && git init) && + (cd empty && + git init && + git config receive.denyCurrentBranch warn) && git clone empty empty-clone && test_tick && (cd empty-clone From 375881fa6a43e21ab922b20b2061f9868ef18644 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 9 Feb 2009 00:19:46 -0800 Subject: [PATCH 002/384] Refuse deleting the current branch via push This makes git-push refuse deleting the current branch by default. Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 30 ++++++++++++------------------ t/t5400-send-pack.sh | 9 ++------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b8b69dde48..db12b813ac 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -241,30 +241,24 @@ static void refuse_unconfigured_deny(void) error("%s", refuse_unconfigured_deny_msg[i]); } -static char *warn_unconfigured_deny_delete_current_msg[] = { - "Deleting the current branch can cause confusion by making the next", - "'git clone' not check out any file.", +static char *refuse_unconfigured_deny_delete_current_msg[] = { + "By default, deleting the current branch is denied, because the next", + "'git clone' won't result in any file checked out, causing confusion.", "", "You can set 'receive.denyDeleteCurrent' configuration variable to", - "'refuse' in the remote repository to disallow deleting the current", - "branch.", + "'warn' or 'ignore' in the remote repository to allow deleting the", + "current branch, with or without a warning message.", "", - "You can set it to 'ignore' to allow such a delete without a warning.", - "", - "To make this warning message less loud, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse deleting the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message, you can set it to 'refuse'." }; -static void warn_unconfigured_deny_delete_current(void) +static void refuse_unconfigured_deny_delete_current(void) { int i; for (i = 0; - i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg); + i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg); i++) - warning("%s", warn_unconfigured_deny_delete_current_msg[i]); + error("%s", refuse_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@ -313,12 +307,12 @@ static const char *update(struct command *cmd) case DENY_IGNORE: break; case DENY_WARN: - case DENY_UNCONFIGURED: - if (deny_delete_current == DENY_UNCONFIGURED) - warn_unconfigured_deny_delete_current(); warning("deleting the current branch"); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: + if (deny_delete_current == DENY_UNCONFIGURED) + refuse_unconfigured_deny_delete_current(); error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; } diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 8463332cb8..c718253673 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -191,16 +191,11 @@ test_expect_success 'pushing wildcard refspecs respects forcing' ' test "$parent_head" = "$child_head" ' -test_expect_success 'warn pushing to delete current branch' ' +test_expect_success 'deny pushing to delete current branch' ' rewound_push_setup && ( cd child && - git send-pack ../parent :refs/heads/master 2>errs - ) && - grep "warning: to refuse deleting" child/errs && - ( - cd parent && - test_must_fail git rev-parse --verify master + test_must_fail git send-pack ../parent :refs/heads/master 2>errs ) ' From f245194f9a13d5108c3a59fd4ab1770ae9fd5b65 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 May 2009 12:45:29 -0700 Subject: [PATCH 003/384] diff: change semantics of "ignore whitespace" options Traditionally, the --ignore-whitespace* options have merely meant to tell the diff output routine that some class of differences are not worth showing in the textual diff output, so that the end user has easier time to review the remaining (presumably more meaningful) changes. These options never affected the outcome of the command, given as the exit status when the --exit-code option was in effect (either directly or indirectly). When you have only whitespace changes, however, you might expect git diff -b --exit-code to report that there is _no_ change with zero exit status. Change the semantics of --ignore-whitespace* options to mean more than "omit showing the difference in text". The exit status, when --exit-code is in effect, is computed by checking if we found any differences at the path level, while diff frontends feed filepairs to the diffcore engine. When "ignore whitespace" options are in effect, we defer this determination until the very end of diffcore transformation. We simply do not know until the textual diff is generated, which comes very late in the pipeline. When --quiet is in effect, various diff frontends optimize by breaking out early from the loop that enumerates the filepairs, when we find the first path level difference; when --ignore-whitespace* is used the above change automatically disables this optimization. Signed-off-by: Junio C Hamano --- diff.c | 34 +++++++++++++++++-- diff.h | 1 + t/t4037-whitespace-status.sh | 63 ++++++++++++++++++++++++++++++++++++ tree-diff.c | 3 +- 4 files changed, 97 insertions(+), 4 deletions(-) create mode 100755 t/t4037-whitespace-status.sh diff --git a/diff.c b/diff.c index cd35e0c2d7..467925d931 100644 --- a/diff.c +++ b/diff.c @@ -2378,6 +2378,20 @@ int diff_setup_done(struct diff_options *options) if (count > 1) die("--name-only, --name-status, --check and -s are mutually exclusive"); + /* + * Most of the time we can say "there are changes" + * only by checking if there are changed paths, but + * --ignore-whitespace* options force us to look + * inside contets. + */ + + if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL)) + DIFF_OPT_SET(options, DIFF_FROM_CONTENTS); + else + DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS); + if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) options->detect_rename = DIFF_DETECT_COPY; @@ -3330,6 +3344,18 @@ free_queue: q->nr = q->alloc = 0; if (options->close_file) fclose(options->file); + + /* + * Report the contents level differences with HAS_CHANGES; + * diff_addremove/diff_change does not set the bit when + * DIFF_FROM_CONTENTS is in effect (e.g. with -w). + */ + if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + if (options->found_changes) + DIFF_OPT_SET(options, HAS_CHANGES); + else + DIFF_OPT_CLR(options, HAS_CHANGES); + } } static void diffcore_apply_filter(const char *filter) @@ -3466,7 +3492,7 @@ void diffcore_std(struct diff_options *options) diff_resolve_rename_copy(); diffcore_apply_filter(options->filter); - if (diff_queued_diff.nr) + if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) DIFF_OPT_SET(options, HAS_CHANGES); else DIFF_OPT_CLR(options, HAS_CHANGES); @@ -3526,7 +3552,8 @@ void diff_addremove(struct diff_options *options, fill_filespec(two, sha1, mode); diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_change(struct diff_options *options, @@ -3558,7 +3585,8 @@ void diff_change(struct diff_options *options, fill_filespec(two, new_sha1, new_mode); diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_unmerge(struct diff_options *options, diff --git a/diff.h b/diff.h index 6616877ee5..538e4f0d8f 100644 --- a/diff.h +++ b/diff.h @@ -66,6 +66,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19) #define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20) #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) +#define DIFF_OPT_DIFF_FROM_CONTENTS (1 << 22) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) #define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) diff --git a/t/t4037-whitespace-status.sh b/t/t4037-whitespace-status.sh new file mode 100755 index 0000000000..a30b03bcf2 --- /dev/null +++ b/t/t4037-whitespace-status.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +test_description='diff --exit-code with whitespace' +. ./test-lib.sh + +test_expect_success setup ' + mkdir a b && + echo >c && + echo >a/d && + echo >b/e && + git add . && + test_tick && + git commit -m initial && + echo " " >a/d && + test_tick && + git commit -a -m second && + echo " " >a/d && + echo " " >b/e && + git add a/d +' + +test_expect_success 'diff-tree --exit-code' ' + test_must_fail git diff --exit-code HEAD^ HEAD && + test_must_fail git diff-tree --exit-code HEAD^ HEAD +' + +test_expect_success 'diff-tree -b --exit-code' ' + git diff -b --exit-code HEAD^ HEAD && + git diff-tree -b -p --exit-code HEAD^ HEAD && + git diff-tree -b --exit-code HEAD^ HEAD +' + +test_expect_success 'diff-index --cached --exit-code' ' + test_must_fail git diff --cached --exit-code HEAD && + test_must_fail git diff-index --cached --exit-code HEAD +' + +test_expect_success 'diff-index -b -p --cached --exit-code' ' + git diff -b --cached --exit-code HEAD && + git diff-index -b -p --cached --exit-code HEAD +' + +test_expect_success 'diff-index --exit-code' ' + test_must_fail git diff --exit-code HEAD && + test_must_fail git diff-index --exit-code HEAD +' + +test_expect_success 'diff-index -b -p --exit-code' ' + git diff -b --exit-code HEAD && + git diff-index -b -p --exit-code HEAD +' + +test_expect_success 'diff-files --exit-code' ' + test_must_fail git diff --exit-code && + test_must_fail git diff-files --exit-code +' + +test_expect_success 'diff-files -b -p --exit-code' ' + git diff -b --exit-code && + git diff-files -b -p --exit-code +' + +test_done diff --git a/tree-diff.c b/tree-diff.c index 0459e54d3d..7c526d33f4 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -286,7 +286,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru int baselen = strlen(base); for (;;) { - if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES)) + if (DIFF_OPT_TST(opt, QUIET) && + DIFF_OPT_TST(opt, HAS_CHANGES)) break; if (opt->nr_paths) { skip_uninteresting(t1, base, baselen, opt); From 90b1994170900514a1ce7a3345e25cb7216915cc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 May 2009 01:15:35 -0700 Subject: [PATCH 004/384] diff: Rename QUIET internal option to QUICK The option "QUIET" primarily meant "find if we have _any_ difference as quick as possible and report", which means we often do not even have to look at blobs if we know the trees are different by looking at the higher level (e.g. "diff-tree A B"). As a side effect, because there is no point showing one change that we happened to have found first, it also enables NO_OUTPUT and EXIT_WITH_STATUS options, making the end result look quiet. Rename the internal option to QUICK to reflect this better; it also makes grepping the source tree much easier, as there are other kinds of QUIET option everywhere. Signed-off-by: Junio C Hamano --- builtin-log.c | 2 +- builtin-rev-list.c | 2 +- diff-lib.c | 4 ++-- diff.c | 4 ++-- diff.h | 2 +- revision.c | 2 +- tree-diff.c | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/builtin-log.c b/builtin-log.c index 0c2fa0ae2d..7903e5a78f 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -537,7 +537,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev) get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); - if (!DIFF_OPT_TST(&rev->diffopt, QUIET)) + if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); if (freopen(filename.buf, "w", stdout) == NULL) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12e0b..69753dc206 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -320,7 +320,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) memset(&info, 0, sizeof(info)); info.revs = &revs; - quiet = DIFF_OPT_TST(&revs.diffopt, QUIET); + quiet = DIFF_OPT_TST(&revs.diffopt, QUICK); for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde74..b7813af614 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -73,7 +73,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) struct cache_entry *ce = active_cache[i]; int changed; - if (DIFF_OPT_TST(&revs->diffopt, QUIET) && + if (DIFF_OPT_TST(&revs->diffopt, QUICK) && DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; @@ -523,7 +523,7 @@ int index_differs_from(const char *def, int diff_flags) init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, def); - DIFF_OPT_SET(&rev.diffopt, QUIET); + DIFF_OPT_SET(&rev.diffopt, QUICK); DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); rev.diffopt.flags |= diff_flags; run_diff_index(&rev, 1); diff --git a/diff.c b/diff.c index 467925d931..91d6ea21a9 100644 --- a/diff.c +++ b/diff.c @@ -2452,7 +2452,7 @@ int diff_setup_done(struct diff_options *options) * to have found. It does not make sense not to return with * exit code in such a case either. */ - if (DIFF_OPT_TST(options, QUIET)) { + if (DIFF_OPT_TST(options, QUICK)) { options->output_format = DIFF_FORMAT_NO_OUTPUT; DIFF_OPT_SET(options, EXIT_WITH_STATUS); } @@ -2643,7 +2643,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--exit-code")) DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) - DIFF_OPT_SET(options, QUIET); + DIFF_OPT_SET(options, QUICK); else if (!strcmp(arg, "--ext-diff")) DIFF_OPT_SET(options, ALLOW_EXTERNAL); else if (!strcmp(arg, "--no-ext-diff")) diff --git a/diff.h b/diff.h index 538e4f0d8f..a7e7ccbd42 100644 --- a/diff.h +++ b/diff.h @@ -55,7 +55,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_COLOR_DIFF (1 << 8) #define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9) #define DIFF_OPT_HAS_CHANGES (1 << 10) -#define DIFF_OPT_QUIET (1 << 11) +#define DIFF_OPT_QUICK (1 << 11) #define DIFF_OPT_NO_INDEX (1 << 12) #define DIFF_OPT_ALLOW_EXTERNAL (1 << 13) #define DIFF_OPT_EXIT_WITH_STATUS (1 << 14) diff --git a/revision.c b/revision.c index 9f5dac5f1d..b8afc7c2b5 100644 --- a/revision.c +++ b/revision.c @@ -791,7 +791,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->ignore_merges = 1; revs->simplify_history = 1; DIFF_OPT_SET(&revs->pruning, RECURSIVE); - DIFF_OPT_SET(&revs->pruning, QUIET); + DIFF_OPT_SET(&revs->pruning, QUICK); revs->pruning.add_remove = file_add_remove; revs->pruning.change = file_change; revs->lifo = 1; diff --git a/tree-diff.c b/tree-diff.c index 7c526d33f4..7d745b4406 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -286,7 +286,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru int baselen = strlen(base); for (;;) { - if (DIFF_OPT_TST(opt, QUIET) && + if (DIFF_OPT_TST(opt, QUICK) && DIFF_OPT_TST(opt, HAS_CHANGES)) break; if (opt->nr_paths) { From 76e2f7ce323c7ddb3a7c29e56407da6a69a4fa53 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 7 Aug 2009 23:31:57 -0700 Subject: [PATCH 005/384] git stat: the beginning of "status that is not a dry-run of commit" Tentatively add "git stat" as a new command. This is not "preview of commit with the same arguments"; the path parameters are not paths to be added to the pristine index (aka "--only" option), but are taken as pathspecs to limit the output. Later in 1.7.0 release, it will take over "git status". Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-commit.c | 75 +++++++++++++++++++++++++++++++++++++++++------- builtin.h | 1 + git.c | 1 + wt-status.c | 10 ++++--- wt-status.h | 1 + 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index daf4296706..39dd334385 100644 --- a/Makefile +++ b/Makefile @@ -378,6 +378,7 @@ BUILT_INS += git-init$X BUILT_INS += git-merge-subtree$X BUILT_INS += git-peek-remote$X BUILT_INS += git-repo-config$X +BUILT_INS += git-stat$X BUILT_INS += git-show$X BUILT_INS += git-stage$X BUILT_INS += git-status$X diff --git a/builtin-commit.c b/builtin-commit.c index 200ffdaad4..5e23ef1f0a 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -24,6 +24,7 @@ #include "string-list.h" #include "rerere.h" #include "unpack-trees.h" +#include "quote.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] ...", @@ -35,6 +36,11 @@ static const char * const builtin_status_usage[] = { NULL }; +static const char * const builtin_stat_usage[] = { + "git stat [options]", + NULL +}; + static unsigned char head_sha1[20], merge_head_sha1[20]; static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; @@ -346,6 +352,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, struct wt_status *s) { + unsigned char sha1[20]; + if (s->relative_paths) s->prefix = prefix; @@ -357,7 +365,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->index_file = index_file; s->fp = fp; s->nowarn = nowarn; + s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; + wt_status_collect(s); wt_status_print(s); return s->commitable; @@ -691,6 +701,21 @@ static const char *find_author_by_nickname(const char *name) die("No existing author found with '%s'", name); } + +static void handle_untracked_files_arg(struct wt_status *s) +{ + if (!untracked_files_arg) + ; /* default already initialized */ + else if (!strcmp(untracked_files_arg, "no")) + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "normal")) + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "all")) + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + else + die("Invalid untracked files mode '%s'", untracked_files_arg); +} + static int parse_and_validate_options(int argc, const char *argv[], const char * const usage[], const char *prefix, @@ -794,16 +819,7 @@ static int parse_and_validate_options(int argc, const char *argv[], else die("Invalid cleanup mode %s", cleanup_arg); - if (!untracked_files_arg) - ; /* default already initialized */ - else if (!strcmp(untracked_files_arg, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - die("Invalid untracked files mode '%s'", untracked_files_arg); + handle_untracked_files_arg(s); if (all && argc > 0) die("Paths with -a does not make sense."); @@ -886,6 +902,45 @@ static int git_status_config(const char *k, const char *v, void *cb) return git_diff_ui_config(k, v, NULL); } +int cmd_stat(int argc, const char **argv, const char *prefix) +{ + struct wt_status s; + unsigned char sha1[20]; + static struct option builtin_stat_options[] = { + OPT__VERBOSE(&verbose), + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, + "mode", + "show untracked files, optional modes: all, normal, no. (Default: all)", + PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_END(), + }; + + wt_status_prepare(&s); + git_config(git_status_config, &s); + argc = parse_options(argc, argv, prefix, + builtin_stat_options, + builtin_stat_usage, 0); + handle_untracked_files_arg(&s); + + if (*argv) + s.pathspec = get_pathspec(prefix, argv); + + read_cache(); + refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); + s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; + wt_status_collect(&s); + + s.verbose = verbose; + if (s.relative_paths) + s.prefix = prefix; + if (s.use_color == -1) + s.use_color = git_use_color_default; + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + wt_status_print(&s); + return 0; +} + int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; diff --git a/builtin.h b/builtin.h index 20427d2963..eeaf0b6cf5 100644 --- a/builtin.h +++ b/builtin.h @@ -95,6 +95,7 @@ extern int cmd_send_pack(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_stat(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 807d875ae0..de7fcf6dfb 100644 --- a/git.c +++ b/git.c @@ -350,6 +350,7 @@ static void handle_internal_command(int argc, const char **argv) { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "stat", cmd_stat, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, diff --git a/wt-status.c b/wt-status.c index 63598ce40c..249227c382 100644 --- a/wt-status.c +++ b/wt-status.c @@ -269,6 +269,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; + rev.prune_data = s->pathspec; run_diff_files(&rev, 0); } @@ -285,6 +286,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; rev.diffopt.break_opt = 0; + rev.prune_data = s->pathspec; run_diff_index(&rev, 1); } @@ -297,6 +299,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s) struct wt_status_change_data *d; struct cache_entry *ce = active_cache[i]; + if (!ce_path_match(ce, s->pathspec)) + continue; it = string_list_insert(ce->name, &s->change); d = it->util; if (!d) { @@ -330,6 +334,8 @@ static void wt_status_collect_untracked(struct wt_status *s) struct dir_entry *ent = dir.entries[i]; if (!cache_name_is_other(ent->name, ent->len)) continue; + if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + continue; s->workdir_untracked = 1; string_list_insert(ent->name, &s->untracked); } @@ -533,10 +539,8 @@ static void wt_status_print_tracking(struct wt_status *s) void wt_status_print(struct wt_status *s) { - unsigned char sha1[20]; const char *branch_color = color(WT_STATUS_HEADER, s); - s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; if (s->branch) { const char *on_what = "On branch "; const char *branch_name = s->branch; @@ -553,8 +557,6 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } - wt_status_collect(s); - if (s->is_initial) { color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit"); diff --git a/wt-status.h b/wt-status.h index a0e75177be..09fd9f1091 100644 --- a/wt-status.h +++ b/wt-status.h @@ -31,6 +31,7 @@ struct wt_status { int is_initial; char *branch; const char *reference; + const char **pathspec; int verbose; int amend; int nowarn; From 173e6c8852be3c543689f8613ddf3fdafd9ca7b9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 4 Aug 2009 23:55:22 -0700 Subject: [PATCH 006/384] git stat -s: short status output Give -s(hort) option to "git stat" that shows the status of paths in a more concise way. XY PATH1 -> PATH2 format to be more machine readable than output from "git status", which is about previewing of "git commit" with the same arguments. PATH1 is the path in the HEAD, and " -> PATH2" part is shown only when PATH1 corresponds to a different path in the index/worktree. For unmerged entries, X shows the status of stage #2 (i.e. ours) and Y shows the status of stage #3 (i.e. theirs). For entries that do not have conflicts, X shows the status of the index, and Y shows the status of the work tree. For untracked paths, XY are "??". X Y Meaning ------------------------------------------------- [MD] not updated M [ MD] updated in index A [ MD] added to index D [ MD] deleted from index R [ MD] renamed in index C [ MD] copied in index [MARC] index and work tree matches [ MARC] M work tree changed since index [ MARC] D deleted in work tree D D unmerged, both deleted A U unmerged, added by us U D unmerged, deleted by them U A unmerged, added by them D U unmerged, deleted by us A A unmerged, both added U U unmerged, both modified ? ? untracked When given -z option, the records are terminated by NUL characters for better machine readability. Because the traditional long format is designed for human consumption, NUL termination does not make sense. For this reason, -z option implies -s (short output). Signed-off-by: Junio C Hamano --- builtin-commit.c | 114 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 8 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 5e23ef1f0a..1a360cb37c 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -902,12 +902,87 @@ static int git_status_config(const char *k, const char *v, void *cb) return git_diff_ui_config(k, v, NULL); } +#define quote_path quote_path_relative + +static void short_unmerged(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + const char *how = "??"; + + switch (d->stagemask) { + case 1: how = "DD"; break; /* both deleted */ + case 2: how = "AU"; break; /* added by us */ + case 3: how = "UD"; break; /* deleted by them */ + case 4: how = "UA"; break; /* added by them */ + case 5: how = "DU"; break; /* deleted by us */ + case 6: how = "AA"; break; /* both added */ + case 7: how = "UU"; break; /* both modified */ + } + printf("%s ", how); + if (null_termination) { + fprintf(stdout, "%s%c", it->string, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf("%s\n", one); + strbuf_release(&onebuf); + } +} + +static void short_status(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + + printf("%c%c ", + !d->index_status ? ' ' : d->index_status, + !d->worktree_status ? ' ' : d->worktree_status); + if (null_termination) { + fprintf(stdout, "%s%c", it->string, 0); + if (d->head_path) + fprintf(stdout, "%s%c", d->head_path, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + if (d->head_path) { + one = quote_path(d->head_path, -1, &onebuf, s->prefix); + printf("%s -> ", one); + strbuf_release(&onebuf); + } + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf("%s\n", one); + strbuf_release(&onebuf); + } +} + +static void short_untracked(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + if (null_termination) { + fprintf(stdout, "?? %s%c", it->string, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf("?? %s\n", one); + strbuf_release(&onebuf); + } +} + int cmd_stat(int argc, const char **argv, const char *prefix) { struct wt_status s; + static int null_termination, shortstatus; + int i; unsigned char sha1[20]; static struct option builtin_stat_options[] = { OPT__VERBOSE(&verbose), + OPT_BOOLEAN('s', "short", &shortstatus, + "show status concicely"), + OPT_BOOLEAN('z', "null", &null_termination, + "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", @@ -915,6 +990,9 @@ int cmd_stat(int argc, const char **argv, const char *prefix) OPT_END(), }; + if (null_termination) + shortstatus = 1; + wt_status_prepare(&s); git_config(git_status_config, &s); argc = parse_options(argc, argv, prefix, @@ -930,14 +1008,34 @@ int cmd_stat(int argc, const char **argv, const char *prefix) s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; wt_status_collect(&s); - s.verbose = verbose; - if (s.relative_paths) - s.prefix = prefix; - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - wt_status_print(&s); + if (shortstatus) { + for (i = 0; i < s.change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + + it = &(s.change.items[i]); + d = it->util; + if (d->stagemask) + short_unmerged(null_termination, it, &s); + else + short_status(null_termination, it, &s); + } + for (i = 0; i < s.untracked.nr; i++) { + struct string_list_item *it; + + it = &(s.untracked.items[i]); + short_untracked(null_termination, it, &s); + } + } else { + s.verbose = verbose; + if (s.relative_paths) + s.prefix = prefix; + if (s.use_color == -1) + s.use_color = git_use_color_default; + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + wt_status_print(&s); + } return 0; } From 9e4b7ab652561e1807702fe5288e04b8873fc437 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 15 Aug 2009 02:27:39 -0700 Subject: [PATCH 007/384] git status: not "commit --dry-run" anymore This removes tentative "git stat" and make it take over "git status". There are some tests that expect "git status" to exit with non-zero status when there is something staged. Some tests expect "git status path..." to show the status for a partial commit. For these, replace "git status" with "git commit --dry-run". For the ones that do not attempt a dry-run of a partial commit that check the output from the command, check the output from "git status" as well, as they should be identical. Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 77 +++++++++++++++++++++++++++++++----- Makefile | 1 - builtin-commit.c | 29 ++------------ builtin.h | 1 - git.c | 1 - t/t6040-tracking-info.sh | 2 +- t/t7060-wtstatus.sh | 8 ++-- t/t7506-status-submodule.sh | 6 +-- t/t7508-status.sh | 12 +++--- 9 files changed, 88 insertions(+), 49 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 84f60f3407..b5939d6b58 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -8,7 +8,7 @@ git-status - Show the working tree status SYNOPSIS -------- -'git status' ... +'git status' [...] [--] [...] DESCRIPTION ----------- @@ -20,25 +20,85 @@ are what you _would_ commit by running `git commit`; the second and third are what you _could_ commit by running 'git-add' before running `git commit`. -The command takes the same set of options as 'git-commit'; it -shows what would be committed if the same options are given to -'git-commit'. +OPTIONS +------- -If there is no path that is different between the index file and -the current HEAD commit (i.e., there is nothing to commit by running -`git commit`), the command exits with non-zero status. +-s:: +--short:: + Give the output in the short-format. + +-u[]:: +--untracked-files[=]:: + Show untracked files (Default: 'all'). ++ +The mode parameter is optional, and is used to specify +the handling of untracked files. The possible options are: ++ +-- + - 'no' - Show no untracked files + - 'normal' - Shows untracked files and directories + - 'all' - Also shows individual files in untracked directories. +-- ++ +See linkgit:git-config[1] for configuration variable +used to change the default for when the option is not +specified. + +-z:: + Terminate entries with NUL, instead of LF. This implies `-s` + (short status) output format. OUTPUT ------ The output from this command is designed to be used as a commit template comment, and all the output lines are prefixed with '#'. +The default, long format, is designed to be human readable, +verbose and descriptive. They are subject to change in any time. The paths mentioned in the output, unlike many other git commands, are made relative to the current directory if you are working in a subdirectory (this is on purpose, to help cutting and pasting). See the status.relativePaths config option below. +In short-format, the status of each path is shown as + + XY PATH1 -> PATH2 + +where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is +shown only when `PATH1` corresponds to a different path in the +index/worktree (i.e. renamed). + +For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y` +shows the status of stage #3 (i.e. theirs). + +For entries that do not have conflicts, `X` shows the status of the index, +and `Y` shows the status of the work tree. For untracked paths, `XY` are +`??`. + + X Y Meaning + ------------------------------------------------- + [MD] not updated + M [ MD] updated in index + A [ MD] added to index + D [ MD] deleted from index + R [ MD] renamed in index + C [ MD] copied in index + [MARC] index and work tree matches + [ MARC] M work tree changed since index + [ MARC] D deleted in work tree + ------------------------------------------------- + D D unmerged, both deleted + A U unmerged, added by us + U D unmerged, deleted by them + U A unmerged, added by them + D U unmerged, deleted by us + A A unmerged, both added + U U unmerged, both modified + ------------------------------------------------- + ? ? untracked + ------------------------------------------------- + CONFIGURATION ------------- @@ -63,8 +123,7 @@ linkgit:gitignore[5] Author ------ -Written by Linus Torvalds and -Junio C Hamano . +Written by Junio C Hamano . Documentation -------------- diff --git a/Makefile b/Makefile index 39dd334385..daf4296706 100644 --- a/Makefile +++ b/Makefile @@ -378,7 +378,6 @@ BUILT_INS += git-init$X BUILT_INS += git-merge-subtree$X BUILT_INS += git-peek-remote$X BUILT_INS += git-repo-config$X -BUILT_INS += git-stat$X BUILT_INS += git-show$X BUILT_INS += git-stage$X BUILT_INS += git-status$X diff --git a/builtin-commit.c b/builtin-commit.c index 1a360cb37c..6cb0e40484 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -36,11 +36,6 @@ static const char * const builtin_status_usage[] = { NULL }; -static const char * const builtin_stat_usage[] = { - "git stat [options]", - NULL -}; - static unsigned char head_sha1[20], merge_head_sha1[20]; static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; @@ -971,13 +966,13 @@ static void short_untracked(int null_termination, struct string_list_item *it, } } -int cmd_stat(int argc, const char **argv, const char *prefix) +int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; static int null_termination, shortstatus; int i; unsigned char sha1[20]; - static struct option builtin_stat_options[] = { + static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), OPT_BOOLEAN('s', "short", &shortstatus, "show status concicely"), @@ -996,8 +991,8 @@ int cmd_stat(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); git_config(git_status_config, &s); argc = parse_options(argc, argv, prefix, - builtin_stat_options, - builtin_stat_usage, 0); + builtin_status_options, + builtin_status_usage, 0); handle_untracked_files_arg(&s); if (*argv) @@ -1039,22 +1034,6 @@ int cmd_stat(int argc, const char **argv, const char *prefix) return 0; } -int cmd_status(int argc, const char **argv, const char *prefix) -{ - struct wt_status s; - - wt_status_prepare(&s); - git_config(git_status_config, &s); - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - argc = parse_and_validate_options(argc, argv, builtin_status_usage, - prefix, &s); - return dry_run_commit(argc, argv, prefix, &s); -} - static void print_summary(const char *prefix, const unsigned char *sha1) { struct rev_info rev; diff --git a/builtin.h b/builtin.h index eeaf0b6cf5..20427d2963 100644 --- a/builtin.h +++ b/builtin.h @@ -95,7 +95,6 @@ extern int cmd_send_pack(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); -extern int cmd_stat(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index de7fcf6dfb..807d875ae0 100644 --- a/git.c +++ b/git.c @@ -350,7 +350,6 @@ static void handle_internal_command(int argc, const char **argv) { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, - { "stat", cmd_stat, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 00e1de9627..664b0f8052 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -69,7 +69,7 @@ test_expect_success 'status' ' cd test && git checkout b1 >/dev/null && # reports nothing to commit - test_must_fail git status + test_must_fail git commit --dry-run ) >actual && grep "have 1 and 1 different" actual ' diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 1044aa6549..7b5db8066f 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -50,9 +50,11 @@ test_expect_success 'M/D conflict does not segfault' ' git rm foo && git commit -m delete && test_must_fail git merge master && - test_must_fail git status > ../actual - ) && - test_cmp expect actual + test_must_fail git commit --dry-run >../actual && + test_cmp ../expect ../actual && + git status >../actual && + test_cmp ../expect ../actual + ) ' test_done diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index d9a08aac56..3ca17abad1 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -19,8 +19,8 @@ test_expect_success 'status clean' ' git status | grep "nothing to commit" ' -test_expect_success 'status -a clean' ' - git status -a | +test_expect_success 'commit --dry-run -a clean' ' + git commit --dry-run -a | grep "nothing to commit" ' test_expect_success 'rm submodule contents' ' @@ -31,7 +31,7 @@ test_expect_success 'status clean (empty submodule dir)' ' grep "nothing to commit" ' test_expect_success 'status -a clean (empty submodule dir)' ' - git status -a | + git commit --dry-run -a | grep "nothing to commit" ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 93f875f500..1173dbb36e 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -248,8 +248,8 @@ cat <expect # output # untracked EOF -test_expect_success 'status of partial commit excluding new file in index' ' - git status dir1/modified >output && +test_expect_success 'dry-run of partial commit excluding new file in index' ' + git commit --dry-run dir1/modified >output && test_cmp expect output ' @@ -358,7 +358,9 @@ EOF test_expect_success 'status submodule summary (clean submodule)' ' git commit -m "commit submodule" && git config status.submodulesummary 10 && - test_must_fail git status >output && + test_must_fail git commit --dry-run >output && + test_cmp expect output && + git status >output && test_cmp expect output ' @@ -391,9 +393,9 @@ cat >expect <output && + git commit --dry-run --amend >output && test_cmp expect output ' From 41fe87fa49cbeec8f05c3e51d6ffd9c57f6c754c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 22 Aug 2009 12:48:48 -0700 Subject: [PATCH 008/384] send-email: make --no-chain-reply-to the default In http://article.gmane.org/gmane.comp.version-control.git/109790 I threatened to announce a change to the default threading style used by send-email to no-chain-reply-to (i.e. the second and subsequent messages will all be replies to the first one), unless nobody objected, in 1.6.3. Nobody objected, as far as I can dig the list archive. But when nothing happened in 1.6.3 nor 1.6.4, nobody from the camp who complained loudly that led to the message did not complain either. So I am guessing that after all nobody cares about this. But 1.7.0 is a good time to change this, and as I said in the message, I personally think it is a good change, so here it is. Signed-off-by: Junio C Hamano --- Documentation/git-send-email.txt | 6 +++--- git-send-email.perl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 767cf4d4bd..626c2dccc1 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -84,7 +84,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'. --in-reply-to=:: Specify the contents of the first In-Reply-To header. Subsequent emails will refer to the previous email - instead of this if --chain-reply-to is set (the default) + instead of this if --chain-reply-to is set. Only necessary if --compose is also set. If --compose is not set, this will be prompted for. @@ -171,8 +171,8 @@ Automating email sent. If disabled with "--no-chain-reply-to", all emails after the first will be sent as replies to the first email sent. When using this, it is recommended that the first file given be an overview of the - entire patch series. Default is the value of the 'sendemail.chainreplyto' - configuration value; if that is unspecified, default to --chain-reply-to. + entire patch series. Disabled by default, but the 'sendemail.chainreplyto' + configuration variable can be used to enable it. --identity=:: A configuration identity. When given, causes values in the diff --git a/git-send-email.perl b/git-send-email.perl index 0700d80afc..c1d093033d 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -71,7 +71,7 @@ git send-email [options] --suppress-cc * author, self, sob, cc, cccmd, body, bodycc, all. --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. --[no-]suppress-from * Send to self. Default off. - --[no-]chain-reply-to * Chain In-Reply-To: fields. Default on. + --[no-]chain-reply-to * Chain In-Reply-To: fields. Default off. --[no-]thread * Use In-Reply-To: field. Default on. Administering: @@ -188,7 +188,7 @@ my (@suppress_cc); my %config_bool_settings = ( "thread" => [\$thread, 1], - "chainreplyto" => [\$chain_reply_to, 1], + "chainreplyto" => [\$chain_reply_to, undef], "suppressfrom" => [\$suppress_from, undef], "signedoffbycc" => [\$signed_off_by_cc, undef], "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated From 83b327ba4ec6d29fd59e343b734f642d266aeafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:55 +0700 Subject: [PATCH 009/384] update-index: refactor mark_valid() in preparation for new options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-update-index.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/builtin-update-index.c b/builtin-update-index.c index 92beaaf4b3..f1b6c8e88e 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -24,8 +24,8 @@ static int info_only; static int force_remove; static int verbose; static int mark_valid_only; -#define MARK_VALID 1 -#define UNMARK_VALID 2 +#define MARK_FLAG 1 +#define UNMARK_FLAG 2 static void report(const char *fmt, ...) { @@ -40,19 +40,15 @@ static void report(const char *fmt, ...) va_end(vp); } -static int mark_valid(const char *path) +static int mark_ce_flags(const char *path, int flag, int mark) { 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 |= CE_VALID; - break; - case UNMARK_VALID: - active_cache[pos]->ce_flags &= ~CE_VALID; - break; - } + if (mark) + active_cache[pos]->ce_flags |= flag; + else + active_cache[pos]->ce_flags &= ~flag; cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; return 0; @@ -276,7 +272,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) goto free_return; } if (mark_valid_only) { - if (mark_valid(p)) + if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG)) die("Unable to mark file %s", path); goto free_return; } @@ -647,11 +643,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(path, "--assume-unchanged")) { - mark_valid_only = MARK_VALID; + mark_valid_only = MARK_FLAG; continue; } if (!strcmp(path, "--no-assume-unchanged")) { - mark_valid_only = UNMARK_VALID; + mark_valid_only = UNMARK_FLAG; continue; } if (!strcmp(path, "--info-only")) { From dbd57f99680eac33626d5058459efd7f118f5170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:56 +0700 Subject: [PATCH 010/384] Add test-index-version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 06aaaa0bf70fe37d198893f4e25fa73b6516f8a9 may step index format version up and down, depends on whether extended flags present in the index. This adds a test to check for index format version. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + test-index-version.c | 14 ++++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 test-index-version.c diff --git a/.gitignore b/.gitignore index 41c0b20a76..e3a864c0ad 100644 --- a/.gitignore +++ b/.gitignore @@ -153,6 +153,7 @@ test-date test-delta test-dump-cache-tree test-genrandom +test-index-version test-match-trees test-parse-options test-path-utils diff --git a/Makefile b/Makefile index daf4296706..3c5b890223 100644 --- a/Makefile +++ b/Makefile @@ -1580,6 +1580,7 @@ TEST_PROGRAMS += test-parse-options$X TEST_PROGRAMS += test-path-utils$X TEST_PROGRAMS += test-sha1$X TEST_PROGRAMS += test-sigchain$X +TEST_PROGRAMS += test-index-version$X all:: $(TEST_PROGRAMS) diff --git a/test-index-version.c b/test-index-version.c new file mode 100644 index 0000000000..bfaad9e09e --- /dev/null +++ b/test-index-version.c @@ -0,0 +1,14 @@ +#include "cache.h" + +int main(int argc, const char **argv) +{ + struct cache_header hdr; + int version; + + memset(&hdr,0,sizeof(hdr)); + if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr)) + return 0; + version = ntohl(hdr.hdr_version); + printf("%d\n", version); + return 0; +} From 44a3691362dc71241a5d68d90b07642c46992e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:57 +0700 Subject: [PATCH 011/384] Introduce "skip-worktree" bit in index, teach Git to get/set this bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detail about this bit is in Documentation/git-update-index.txt. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-ls-files.txt | 1 + Documentation/git-update-index.txt | 29 ++++++++++++++ builtin-ls-files.c | 5 ++- builtin-update-index.c | 16 +++++++- cache.h | 4 +- t/t2104-update-index-skip-worktree.sh | 57 +++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100755 t/t2104-update-index-skip-worktree.sh diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 021066e95d..6f9d880aa4 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -107,6 +107,7 @@ OPTIONS Identify the file status with the following tags (followed by a space) at the start of each line: H:: cached + S:: skip-worktree M:: unmerged R:: removed/deleted C:: modified/changed diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 25e0bbea86..a10f355b7c 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -15,6 +15,7 @@ SYNOPSIS [--cacheinfo ]\* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] + [--skip-worktree | --no-skip-worktree] [--ignore-submodules] [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] @@ -99,6 +100,13 @@ in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually. +--skip-worktree:: +--no-skip-worktree:: + When one of these flags is specified, the object name recorded + for the paths are not updated. Instead, these options + set and unset the "skip-worktree" bit for the paths. See + section "Skip-worktree bit" below for more information. + -g:: --again:: Runs 'git-update-index' itself on the paths whose index @@ -304,6 +312,27 @@ M foo.c <9> now it checks with lstat(2) and finds it has been changed. +Skip-worktree bit +----------------- + +Skip-worktree bit can be defined in one (long) sentence: When reading +an entry, if it is marked as skip-worktree, then Git pretends its +working directory version is up to date and read the index version +instead. + +To elaborate, "reading" means checking for file existence, reading +file attributes or file content. The working directory version may be +present or absent. If present, its content may match against the index +version or not. Writing is not affected by this bit, content safety +is still first priority. Note that Git _can_ update working directory +file, that is marked skip-worktree, if it is safe to do so (i.e. +working directory version matches index version) + +Although this bit looks similar to assume-unchanged bit, its goal is +different from assume-unchanged bit's. Skip-worktree also takes +precedence over assume-unchanged bit when both are set. + + Configuration ------------- diff --git a/builtin-ls-files.c b/builtin-ls-files.c index f473220502..c1afbad453 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -37,6 +37,7 @@ static const char *tag_removed = ""; static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; +static const char *tag_skip_worktree = ""; static void show_dir_entry(const char *tag, struct dir_entry *ent) { @@ -178,7 +179,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); + show_ce_entry(ce_stage(ce) ? tag_unmerged : + (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); } } if (show_deleted | show_modified) { @@ -490,6 +492,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) tag_modified = "C "; tag_other = "? "; tag_killed = "K "; + tag_skip_worktree = "S "; } if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) require_work_tree = 1; diff --git a/builtin-update-index.c b/builtin-update-index.c index f1b6c8e88e..5e97d09497 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -24,6 +24,7 @@ static int info_only; static int force_remove; static int verbose; static int mark_valid_only; +static int mark_skip_worktree_only; #define MARK_FLAG 1 #define UNMARK_FLAG 2 @@ -276,6 +277,11 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to mark file %s", path); goto free_return; } + if (mark_skip_worktree_only) { + if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG)) + die("Unable to mark file %s", path); + goto free_return; + } if (force_remove) { if (remove_file_from_cache(p)) @@ -384,7 +390,7 @@ static void read_index_info(int line_termination) } static const char update_index_usage[] = -"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] ..."; +"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] ..."; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -650,6 +656,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) mark_valid_only = UNMARK_FLAG; continue; } + if (!strcmp(path, "--no-skip-worktree")) { + mark_skip_worktree_only = UNMARK_FLAG; + continue; + } + if (!strcmp(path, "--skip-worktree")) { + mark_skip_worktree_only = MARK_FLAG; + continue; + } if (!strcmp(path, "--info-only")) { info_only = 1; continue; diff --git a/cache.h b/cache.h index 9222774e6c..f266246caf 100644 --- a/cache.h +++ b/cache.h @@ -181,10 +181,11 @@ struct cache_entry { * Extended on-disk flags */ #define CE_INTENT_TO_ADD 0x20000000 +#define CE_SKIP_WORKTREE 0x40000000 /* CE_EXTENDED2 is for future extension */ #define CE_EXTENDED2 0x80000000 -#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD) +#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE) /* * Safeguard to avoid saving wrong flags: @@ -233,6 +234,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce) ondisk_cache_entry_size(ce_namelen(ce))) #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) +#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE) #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh new file mode 100755 index 0000000000..1d0879be06 --- /dev/null +++ b/t/t2104-update-index-skip-worktree.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <expect.skip < Date: Thu, 20 Aug 2009 20:46:58 +0700 Subject: [PATCH 012/384] Teach Git to respect skip-worktree bit (reading part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grep: turn on --cached for files that is marked skip-worktree ls-files: do not check for deleted file that is marked skip-worktree update-index: ignore update request if it's skip-worktree, while still allows removing diff*: skip worktree version Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-commit.c | 5 + builtin-grep.c | 2 +- builtin-ls-files.c | 2 + builtin-update-index.c | 38 ++++--- diff-lib.c | 5 +- diff.c | 2 +- read-cache.c | 8 +- t/t7011-skip-worktree-reading.sh | 163 +++++++++++++++++++++++++++++++ 8 files changed, 199 insertions(+), 26 deletions(-) create mode 100755 t/t7011-skip-worktree-reading.sh diff --git a/builtin-commit.c b/builtin-commit.c index 4bcce06fbf..a0b1fd35cb 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -180,6 +180,11 @@ static void add_remove_files(struct string_list *list) for (i = 0; i < list->nr; i++) { struct stat st; struct string_list_item *p = &(list->items[i]); + int pos = index_name_pos(&the_index, p->string, strlen(p->string)); + struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; + + if (ce && ce_skip_worktree(ce)) + continue; if (!lstat(p->string, &st)) { if (add_to_cache(p->string, &st, 0)) diff --git a/builtin-grep.c b/builtin-grep.c index ad0e0a5385..813fe9778a 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -517,7 +517,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, * are identical, even if worktree file has been modified, so use * cache version instead */ - if (cached || (ce->ce_flags & CE_VALID)) { + if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { if (ce_stage(ce)) continue; hit |= grep_sha1(opt, ce->sha1, ce->name, 0); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index c1afbad453..ad7e44784f 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -194,6 +194,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; + if (ce_skip_worktree(ce)) + continue; err = lstat(ce->name, &st); if (show_deleted && err) show_ce_entry(tag_removed, ce); diff --git a/builtin-update-index.c b/builtin-update-index.c index 5e97d09497..97b9ea61f7 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -172,29 +172,29 @@ static int process_directory(const char *path, int len, struct stat *st) return error("%s: is a directory - add files inside instead", path); } -/* - * Process a regular file - */ -static int process_file(const char *path, int len, struct stat *st) -{ - int pos = cache_name_pos(path, len); - struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; - - if (ce && S_ISGITLINK(ce->ce_mode)) - return error("%s is already a gitlink, not replacing", path); - - return add_one_path(ce, path, len, st); -} - static int process_path(const char *path) { - int len; + int pos, len; struct stat st; + struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) return error("'%s' is beyond a symbolic link", path); + pos = cache_name_pos(path, len); + ce = pos < 0 ? NULL : active_cache[pos]; + if (ce && ce_skip_worktree(ce)) { + /* + * working directory version is assumed "good" + * so updating it does not make sense. + * On the other hand, removing it from index should work + */ + if (allow_remove && remove_file_from_cache(path)) + return error("%s: cannot remove from the index", path); + return 0; + } + /* * First things first: get the stat information, to decide * what to do about the pathname! @@ -205,7 +205,13 @@ static int process_path(const char *path) if (S_ISDIR(st.st_mode)) return process_directory(path, len, &st); - return process_file(path, len, &st); + /* + * Process a regular file + */ + if (ce && S_ISGITLINK(ce->ce_mode)) + return error("%s is already a gitlink, not replacing", path); + + return add_one_path(ce, path, len, &st); } static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, diff --git a/diff-lib.c b/diff-lib.c index 22da66ef14..b0b379d9d2 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -159,7 +159,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } - if (ce_uptodate(ce)) + if (ce_uptodate(ce) || ce_skip_worktree(ce)) continue; /* If CE_VALID is set, don't look at workdir for file removal */ @@ -339,7 +339,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, int match_missing, cached; /* if the entry is not checked out, don't examine work tree */ - cached = o->index_only || (idx && (idx->ce_flags & CE_VALID)); + cached = o->index_only || + (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx))); /* * Backward compatibility wart - "diff-index -m" does * not mean "do not ignore merges", but "match_missing". diff --git a/diff.c b/diff.c index cd35e0c2d7..3970df4afc 100644 --- a/diff.c +++ b/diff.c @@ -1805,7 +1805,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * If ce is marked as "assume unchanged", there is no * guarantee that work tree matches what we are looking for. */ - if (ce->ce_flags & CE_VALID) + if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) return 0; /* diff --git a/read-cache.c b/read-cache.c index 4e3e272ee4..5ee7d9da9c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -265,7 +265,7 @@ int ie_match_stat(const struct index_state *istate, * 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 & CE_VALID)) + if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) return 0; /* @@ -1004,11 +1004,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, if (ce_uptodate(ce)) return ce; - /* - * CE_VALID means the user promised us that the change to - * the work tree does not matter and told us not to worry. - */ - if (!ignore_valid && (ce->ce_flags & CE_VALID)) { + if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) { ce_mark_uptodate(ce); return ce; } diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh new file mode 100755 index 0000000000..e996928de2 --- /dev/null +++ b/t/t7011-skip-worktree-reading.sh @@ -0,0 +1,163 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <expect.skip < expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +test_expect_success 'setup' ' + test_commit init && + mkdir sub && + touch ./1 ./2 sub/1 sub/2 && + git add 1 2 sub/1 sub/2 && + git update-index --skip-worktree 1 sub/1 && + git ls-files -t > result && + test_cmp expect.skip result +' + +test_expect_success 'update-index' ' + setup_absent && + git update-index 1 && + test_absent +' + +test_expect_success 'update-index' ' + setup_dirty && + git update-index 1 && + test_dirty +' + +test_expect_success 'update-index --remove' ' + setup_absent && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + test ! -f 1 +' + +test_expect_success 'update-index --remove' ' + setup_dirty && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + echo dirty > expected && + test_cmp expected 1 +' + +test_expect_success 'ls-files --delete' ' + setup_absent && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --delete' ' + setup_dirty && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --modified' ' + setup_absent && + test -z "$(git ls-files -m)" +' + +test_expect_success 'ls-files --modified' ' + setup_dirty && + test -z "$(git ls-files -m)" +' + +test_expect_success 'grep with skip-worktree file' ' + git update-index --no-skip-worktree 1 && + echo test > 1 && + git update-index 1 && + git update-index --skip-worktree 1 && + rm 1 && + test "$(git grep --no-ext-grep test)" = "1:test" +' + +echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected +test_expect_success 'diff-index does not examine skip-worktree absent entries' ' + setup_absent && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-index does not examine skip-worktree dirty entries' ' + setup_dirty && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-files does not examine skip-worktree absent entries' ' + setup_absent && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'diff-files does not examine skip-worktree dirty entries' ' + setup_dirty && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'git-rm succeeds on skip-worktree absent entries' ' + setup_absent && + git rm 1 +' + +test_expect_failure 'commit on skip-worktree absent entries' ' + git reset && + setup_absent && + test_must_fail git commit -m null 1 +' + +test_expect_failure 'commit on skip-worktree dirty entries' ' + git reset && + setup_dirty && + test_must_fail git commit -m null 1 +' + +test_done From 52030836943c0d28d8be4202762ede28e921dc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:59 +0700 Subject: [PATCH 013/384] Teach Git to respect skip-worktree bit (writing part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This part is mainly to remove CE_VALID shortcuts (and as a consequence, ce_uptodate() shortcuts as it may be turned on by CE_VALID) in writing code path if skip-worktree is used. Various tests are added to avoid future breakages. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t7012-skip-worktree-writing.sh | 146 +++++++++++++++++++++++++++++++ unpack-trees.c | 4 +- 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100755 t/t7012-skip-worktree-writing.sh diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh new file mode 100755 index 0000000000..8d8b1c0e25 --- /dev/null +++ b/t/t7012-skip-worktree-writing.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='test worktree writing operations when skip-worktree is used' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + echo modified >> init.t && + touch added && + git add init.t added && + git commit -m "modified and added" && + git tag top +' + +test_expect_success 'read-tree updates worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + rm init.t && + git read-tree -m -u HEAD^ && + echo init > expected && + test_cmp expected init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + echo dirty >> init.t && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty init.t && + test "$(git ls-files -t init.t)" = "S init.t" && + git update-index --no-skip-worktree init.t +' + +test_expect_success 'read-tree removes worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree added && + rm added && + git read-tree -m -u HEAD^ && + test ! -f added +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree added && + echo dirty >> added && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty added && + test "$(git ls-files -t added)" = "S added" && + git update-index --no-skip-worktree added +' + +NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +ZERO_SHA0=0000000000000000000000000000000000000000 +setup_absent() { + test -f 1 && rm 1 + git update-index --remove 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_absent() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +cat >expected < result && + test_cmp expected result +' + +test_expect_success 'git-add ignores worktree content' ' + setup_absent && + git add 1 && + test_absent +' + +test_expect_success 'git-add ignores worktree content' ' + setup_dirty && + git add 1 && + test_dirty +' + +test_expect_success 'git-rm fails if worktree is dirty' ' + setup_dirty && + test_must_fail git rm 1 && + test_dirty +' + +cat >expected < result && + test_cmp expected result +' + +test_expect_success 'git-clean, dirty case' ' + setup_dirty && + git clean -n > result && + test_cmp expected result +' + +test_expect_failure 'git-apply adds file' false +test_expect_failure 'git-apply updates file' false +test_expect_failure 'git-apply removes file' false +test_expect_failure 'git-mv to skip-worktree' false +test_expect_failure 'git-mv from skip-worktree' false +test_expect_failure 'git-checkout' false + +test_done diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1616..3eda263590 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -450,7 +450,7 @@ static int verify_uptodate(struct cache_entry *ce, { struct stat st; - if (o->index_only || o->reset || ce_uptodate(ce)) + if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce)))) return 0; if (!lstat(ce->name, &st)) { @@ -1004,7 +1004,7 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o) if (old && same(old, a)) { int update = 0; - if (o->reset && !ce_uptodate(old)) { + if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) { struct stat st; if (lstat(old->name, &st) || ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID)) From b5041c5f3b9ea70ce7aa9711af6ed6f2d02909b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:00 +0700 Subject: [PATCH 014/384] Avoid writing to buffer in add_excludes_from_file_1() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the next patch, the buffer that is being used within add_excludes_from_file_1() comes from another function and does not have extra space to put \n at the end. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dir.c b/dir.c index e05b850acf..1170d64675 100644 --- a/dir.c +++ b/dir.c @@ -229,10 +229,9 @@ static int add_excludes_from_file_1(const char *fname, if (buf_p) *buf_p = buf; - buf[size++] = '\n'; entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') { + for (i = 0; i <= size; i++) { + if (i == size || buf[i] == '\n') { if (entry != buf + i && entry[0] != '#') { buf[i - (i && buf[i-1] == '\r')] = 0; add_exclude(entry, base, baselen, which); From c28b3d6e7b0471a02f81324a90b26effae0f4bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:01 +0700 Subject: [PATCH 015/384] Read .gitignore from index if it is skip-worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds index as a prerequisite for directory listing (with exclude). At the moment directory listing is used by "git clean", "git add", "git ls-files" and "git status"/"git commit" and unpack_trees()-related commands. These commands have been checked/modified to populate index before doing directory listing. add_excludes_from_file() does not enable this feature, because it is used to read .git/info/exclude and some explicit files specified by "git ls-files". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- .../technical/api-directory-listing.txt | 3 + builtin-clean.c | 4 +- builtin-ls-files.c | 4 +- dir.c | 65 +++++++++++++------ t/t3001-ls-files-others-exclude.sh | 22 +++++++ t/t7300-clean.sh | 19 ++++++ 6 files changed, 95 insertions(+), 22 deletions(-) diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index 5bbd18f020..add6f435b5 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -58,6 +58,9 @@ The result of the enumeration is left in these fields:: Calling sequence ---------------- +Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE +marked. If you to exclude files, make sure you have loaded index first. + * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0, sizeof(dir))`. diff --git a/builtin-clean.c b/builtin-clean.c index 2d8c735d48..e424b77e6b 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -71,11 +71,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (read_cache() < 0) + die("index file corrupt"); + if (!ignored) setup_standard_excludes(&dir); pathspec = get_pathspec(prefix, argv); - read_cache(); fill_directory(&dir, pathspec); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ad7e44784f..2e47242b9d 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -485,6 +485,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) prefix_offset = strlen(prefix); git_config(git_default_config, NULL); + if (read_cache() < 0) + die("index file corrupt"); + argc = parse_options(argc, argv, prefix, builtin_ls_files_options, ls_files_usage, 0); if (show_tag || show_valid_bit) { @@ -513,7 +516,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv); /* be nice with submodule paths ending in a slash */ - read_cache(); if (pathspec) strip_trailing_slash_from_submodules(); diff --git a/dir.c b/dir.c index 1170d64675..e8e5b7917d 100644 --- a/dir.c +++ b/dir.c @@ -200,11 +200,35 @@ void add_exclude(const char *string, const char *base, which->excludes[which->nr++] = x; } +static void *read_skip_worktree_file_from_index(const char *path, size_t *size) +{ + int pos, len; + unsigned long sz; + enum object_type type; + void *data; + struct index_state *istate = &the_index; + + len = strlen(path); + pos = index_name_pos(istate, path, len); + if (pos < 0) + return NULL; + if (!ce_skip_worktree(istate->cache[pos])) + return NULL; + data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return NULL; + } + *size = xsize_t(sz); + return data; +} + static int add_excludes_from_file_1(const char *fname, const char *base, int baselen, char **buf_p, - struct exclude_list *which) + struct exclude_list *which, + int check_index) { struct stat st; int fd, i; @@ -212,20 +236,26 @@ static int add_excludes_from_file_1(const char *fname, char *buf, *entry; fd = open(fname, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0) - goto err; - size = xsize_t(st.st_size); - if (size == 0) { + if (fd < 0 || fstat(fd, &st) < 0) { + if (0 <= fd) + close(fd); + if (!check_index || + (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL) + return -1; + } + else { + size = xsize_t(st.st_size); + if (size == 0) { + close(fd); + return 0; + } + buf = xmalloc(size); + if (read_in_full(fd, buf, size) != size) { + close(fd); + return -1; + } close(fd); - return 0; } - buf = xmalloc(size+1); - if (read_in_full(fd, buf, size) != size) - { - free(buf); - goto err; - } - close(fd); if (buf_p) *buf_p = buf; @@ -240,17 +270,12 @@ static int add_excludes_from_file_1(const char *fname, } } return 0; - - err: - if (0 <= fd) - close(fd); - return -1; } void add_excludes_from_file(struct dir_struct *dir, const char *fname) { if (add_excludes_from_file_1(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE]) < 0) + &dir->exclude_list[EXC_FILE], 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -301,7 +326,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); add_excludes_from_file_1(dir->basebuf, dir->basebuf, stk->baselen, - &stk->filebuf, el); + &stk->filebuf, el, 1); dir->exclude_stack = stk; current = stk->baselen; } diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index c65bca8388..132c4765cb 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -64,6 +64,8 @@ two/*.4 echo '!*.2 !*.8' >one/two/.gitignore +allignores='.gitignore one/.gitignore one/two/.gitignore' + test_expect_success \ 'git ls-files --others with various exclude options.' \ 'git ls-files --others \ @@ -85,6 +87,26 @@ test_expect_success \ >output && test_cmp expect output' +test_expect_success 'setup skip-worktree gitignore' ' + git add $allignores && + git update-index --skip-worktree $allignores && + rm $allignores +' + +test_expect_success \ + 'git ls-files --others with various exclude options.' \ + 'git ls-files --others \ + --exclude=\*.6 \ + --exclude-per-directory=.gitignore \ + --exclude-from=.git/ignore \ + >output && + test_cmp expect output' + +test_expect_success 'restore gitignore' ' + git checkout $allignores && + rm .git/index +' + cat > excludes-file <<\EOF *.[1-8] e* diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 929d5d4d3b..8073d02be5 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -22,6 +22,25 @@ test_expect_success 'setup' ' ' +test_expect_success 'git clean with skip-worktree .gitignore' ' + git update-index --skip-worktree .gitignore && + rm .gitignore && + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git clean && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so && + git update-index --no-skip-worktree .gitignore && + git checkout .gitignore +' + test_expect_success 'git clean' ' mkdir -p build docs && From 32f54ca31747c47f56abb7ae87a6da0f5b8d97c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:02 +0700 Subject: [PATCH 016/384] unpack-trees(): carry skip-worktree bit over in merged_entry() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this code path, we would remove "old" and replace it with "merge". "old" may have skip-worktree bit, so re-add it to "merge". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unpack-trees.c b/unpack-trees.c index 3eda263590..dc6d74a16b 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -680,6 +680,8 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, } else { if (verify_uptodate(old, o)) return -1; + if (ce_skip_worktree(old)) + update |= CE_SKIP_WORKTREE; invalidate_ce_path(old, o); } } From c84de70781674a35b9bfd20aa5bc8c47582615df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:03 +0700 Subject: [PATCH 017/384] excluded_1(): support exclude files in index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Index does not really have "directories", attempts to match "foo/" against index will fail unless someone tries to reconstruct directories from a list of file. Observing that dtype in this function can never be NULL (otherwise it would segfault), dtype NULL will be used to say "hey.. you are matching against index" and behave properly. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dir.c b/dir.c index e8e5b7917d..7735cea631 100644 --- a/dir.c +++ b/dir.c @@ -349,6 +349,12 @@ static int excluded_1(const char *pathname, int to_exclude = x->to_exclude; if (x->flags & EXC_FLAG_MUSTBEDIR) { + if (!dtype) { + if (!prefixcmp(pathname, exclude)) + return to_exclude; + else + continue; + } if (*dtype == DT_UNKNOWN) *dtype = get_dtype(NULL, pathname, pathlen); if (*dtype != DT_DIR) From cb097534230a6bd7438e19ce6dff719697cbf983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:04 +0700 Subject: [PATCH 018/384] dir.c: export excluded_1() and add_excludes_from_file_1() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions are used to handle .gitignore. They are now exported so that sparse checkout can reuse. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 32 ++++++++++++++++---------------- dir.h | 4 ++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/dir.c b/dir.c index 7735cea631..6b1c47822f 100644 --- a/dir.c +++ b/dir.c @@ -223,12 +223,12 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size) return data; } -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - char **buf_p, - struct exclude_list *which, - int check_index) +int add_excludes_from_file_to_list(const char *fname, + const char *base, + int baselen, + char **buf_p, + struct exclude_list *which, + int check_index) { struct stat st; int fd, i; @@ -274,8 +274,8 @@ static int add_excludes_from_file_1(const char *fname, void add_excludes_from_file(struct dir_struct *dir, const char *fname) { - if (add_excludes_from_file_1(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE], 0) < 0) + if (add_excludes_from_file_to_list(fname, "", 0, NULL, + &dir->exclude_list[EXC_FILE], 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -324,9 +324,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) memcpy(dir->basebuf + current, base + current, stk->baselen - current); strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); - add_excludes_from_file_1(dir->basebuf, - dir->basebuf, stk->baselen, - &stk->filebuf, el, 1); + add_excludes_from_file_to_list(dir->basebuf, + dir->basebuf, stk->baselen, + &stk->filebuf, el, 1); dir->exclude_stack = stk; current = stk->baselen; } @@ -336,9 +336,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) /* Scan the list and let the last match determine the fate. * Return 1 for exclude, 0 for include and -1 for undecided. */ -static int excluded_1(const char *pathname, - int pathlen, const char *basename, int *dtype, - struct exclude_list *el) +int excluded_from_list(const char *pathname, + int pathlen, const char *basename, int *dtype, + struct exclude_list *el) { int i; @@ -412,8 +412,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) prep_exclude(dir, pathname, basename-pathname); for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, basename, - dtype_p, &dir->exclude_list[st])) { + switch (excluded_from_list(pathname, pathlen, basename, + dtype_p, &dir->exclude_list[st])) { case 0: return 0; case 1: diff --git a/dir.h b/dir.h index a6314464f9..472e11e659 100644 --- a/dir.h +++ b/dir.h @@ -69,7 +69,11 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen, extern int fill_directory(struct dir_struct *dir, const char **pathspec); extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec); +extern int excluded_from_list(const char *pathname, int pathlen, const char *basename, + int *dtype, struct exclude_list *el); extern int excluded(struct dir_struct *, const char *, int *); +extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, + char **buf_p, struct exclude_list *which, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); From ed5336a7541e19b267de53afc8d15cffdbde8286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:05 +0700 Subject: [PATCH 019/384] Introduce "sparse checkout" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With skip-worktree bit, you can manually set it to unwanted files, then remove them: you would have the so-called sparse checkout. The disadvantages are: - Porcelain tools are not aware of this. Everytime you do an operation that may update working directory, skip-worktree may be cleared out. You have to set them again. - You still have to remove skip-worktree'd files manually, which is boring and ineffective. These will be addressed in the following patches. This patch gives an idea what is "sparse checkout" in Documentation/git-read-tree.txt. This file is chosen instead of git-checkout.txt because it is quite technical and user-unfriendly. I'd expect git-checkout.txt to have something when Porcelain support is done. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-read-tree.txt | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 4a932b08c6..8b3971685a 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -360,6 +360,50 @@ middle of doing, and when your working tree is ready (i.e. you have finished your work-in-progress), attempt the merge again. +Sparse checkout +--------------- + +"Sparse checkout" allows to sparsely populate working directory. +It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell +Git whether a file on working directory is worth looking at. + +"git read-tree" and other merge-based commands ("git merge", "git +checkout"...) can help maintaining skip-worktree bitmap and working +directory update. `$GIT_DIR/info/sparse-checkout` is used to +define the skip-worktree reference bitmap. When "git read-tree" needs +to update working directory, it will reset skip-worktree bit in index +based on this file, which uses the same syntax as .gitignore files. +If an entry matches a pattern in this file, skip-worktree will be +set on that entry. Otherwise, skip-worktree will be unset. + +Then it compares the new skip-worktree value with the previous one. If +skip-worktree turns from unset to set, it will add the corresponding +file back. If it turns from set to unset, that file will be removed. + +While `$GIT_DIR/info/sparse-checkout` is usually used to specify what +files are in. You can also specify what files are _not_ in, using +negate patterns. For example, to remove file "unwanted": + +---------------- +* +!unwanted +---------------- + +Another tricky thing is fully repopulating working directory when you +no longer want sparse checkout. You cannot just disable "sparse +checkout" because skip-worktree are still in the index and you working +directory is still sparsely populated. You should re-populate working +directory with the `$GIT_DIR/info/sparse-checkout` file content as +follows: + +---------------- +* +---------------- + +Then you can disable sparse checkout. Sparse checkout support in "git +read-tree" and similar commands is disabled by default. + + SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; From e663db2f446b84bfe39b4dcb3d26b33826d52c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:06 +0700 Subject: [PATCH 020/384] unpack-trees(): add CE_WT_REMOVE to remove on worktree alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CE_REMOVE now removes both worktree and index versions. Sparse checkout must be able to remove worktree version while keep the index intact when checkout area is narrowed. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- cache.h | 3 +++ unpack-trees.c | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cache.h b/cache.h index f266246caf..d3f81a1c7d 100644 --- a/cache.h +++ b/cache.h @@ -177,6 +177,9 @@ struct cache_entry { #define CE_HASHED (0x100000) #define CE_UNHASHED (0x200000) +/* Only remove in work directory, not index */ +#define CE_WT_REMOVE (0x400000) + /* * Extended on-disk flags */ diff --git a/unpack-trees.c b/unpack-trees.c index dc6d74a16b..6a51a69b2a 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -78,7 +78,7 @@ static int check_updates(struct unpack_trees_options *o) if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { struct cache_entry *ce = index->cache[cnt]; - if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) + if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE)) total++; } @@ -92,6 +92,13 @@ static int check_updates(struct unpack_trees_options *o) for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; + if (ce->ce_flags & CE_WT_REMOVE) { + display_progress(progress, ++cnt); + if (o->update) + unlink_entry(ce); + continue; + } + if (ce->ce_flags & CE_REMOVE) { display_progress(progress, ++cnt); if (o->update) From 35a5aa79d040e2121b6f6bbb17b173f4644699e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:07 +0700 Subject: [PATCH 021/384] unpack-trees.c: generalize verify_* functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 6a51a69b2a..8eb4b7095c 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -452,8 +452,9 @@ static int same(struct cache_entry *a, struct cache_entry *b) * When a CE gets turned into an unmerged entry, we * want it to be up-to-date */ -static int verify_uptodate(struct cache_entry *ce, - struct unpack_trees_options *o) +static int verify_uptodate_1(struct cache_entry *ce, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; @@ -478,7 +479,13 @@ static int verify_uptodate(struct cache_entry *ce, if (errno == ENOENT) return 0; return o->gently ? -1 : - error(ERRORMSG(o, not_uptodate_file), ce->name); + error(error_msg, ce->name); +} + +static int verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) @@ -586,8 +593,9 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. */ -static int verify_absent(struct cache_entry *ce, const char *action, - struct unpack_trees_options *o) +static int verify_absent_1(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; @@ -667,6 +675,11 @@ static int verify_absent(struct cache_entry *ce, const char *action, } return 0; } +static int verify_absent(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); +} static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) From 08aefc9e47eb2eaf231352223c939d69b0fb3759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:08 +0700 Subject: [PATCH 022/384] unpack-trees(): "enable" sparse checkout and load $GIT_DIR/info/sparse-checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces core.sparseCheckout, which will control whether sparse checkout support is enabled in unpack_trees() It also loads sparse-checkout file that will be used in the next patch. I split it out so the next patch will be shorter, easier to read. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ Documentation/git-read-tree.txt | 4 +++- cache.h | 1 + config.c | 5 +++++ environment.c | 1 + unpack-trees.c | 36 +++++++++++++++++++++++++++------ unpack-trees.h | 4 ++++ 7 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 7791c32bc3..5825c914fb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -439,6 +439,10 @@ On some file system/operating system combinations, this is unreliable. Set this config setting to 'rename' there; However, This will remove the check that makes sure that existing object files will not get overwritten. +core.sparseCheckout:: + Enable "sparse checkout" feature. See section "Sparse checkout" in + linkgit:git-read-tree[1] for more information. + add.ignore-errors:: Tells 'git-add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 8b3971685a..fc3f08b81c 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -401,7 +401,9 @@ follows: ---------------- Then you can disable sparse checkout. Sparse checkout support in "git -read-tree" and similar commands is disabled by default. +read-tree" and similar commands is disabled by default. You need to +turn `core.sparseCheckout` on in order to have sparse checkout +support. SEE ALSO diff --git a/cache.h b/cache.h index d3f81a1c7d..2de00f8120 100644 --- a/cache.h +++ b/cache.h @@ -526,6 +526,7 @@ extern size_t delta_base_cache_limit; extern int auto_crlf; extern int fsync_object_files; extern int core_preload_index; +extern int core_apply_sparse_checkout; enum safe_crlf { SAFE_CRLF_FALSE = 0, diff --git a/config.c b/config.c index e87edeab0c..abd762ebfa 100644 --- a/config.c +++ b/config.c @@ -503,6 +503,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.sparsecheckout")) { + core_apply_sparse_checkout = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/environment.c b/environment.c index 8f5eaa7dd8..020422c034 100644 --- a/environment.c +++ b/environment.c @@ -48,6 +48,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING; #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; int grafts_replace_parents = 1; +int core_apply_sparse_checkout; /* Parallel index stat data preload? */ int core_preload_index = 0; diff --git a/unpack-trees.c b/unpack-trees.c index 8eb4b7095c..44f8fdf808 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -378,6 +378,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options { int ret; static struct cache_entry *dfc; + struct exclude_list el; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); @@ -387,6 +388,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options state.quiet = 1; state.refresh_cache = 1; + memset(&el, 0, sizeof(el)); + if (!core_apply_sparse_checkout || !o->update) + o->skip_sparse_checkout = 1; + if (!o->skip_sparse_checkout) { + if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0) + o->skip_sparse_checkout = 1; + else + o->el = ⪙ + } + memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; if (o->src_index) { @@ -407,26 +418,39 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; - if (traverse_trees(len, t, &info) < 0) - return unpack_failed(o, NULL); + if (traverse_trees(len, t, &info) < 0) { + ret = unpack_failed(o, NULL); + goto done; + } } /* Any left-over entries in the index? */ if (o->merge) { while (o->pos < o->src_index->cache_nr) { struct cache_entry *ce = o->src_index->cache[o->pos]; - if (unpack_index_entry(ce, o) < 0) - return unpack_failed(o, NULL); + if (unpack_index_entry(ce, o) < 0) { + ret = unpack_failed(o, NULL); + goto done; + } } } - if (o->trivial_merges_only && o->nontrivial_merge) - return unpack_failed(o, "Merge requires file-level merging"); + if (o->trivial_merges_only && o->nontrivial_merge) { + ret = unpack_failed(o, "Merge requires file-level merging"); + goto done; + } o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) *o->dst_index = o->result; + +done: + for (i = 0;i < el.nr;i++) + free(el.excludes[i]); + if (el.excludes) + free(el.excludes); + return ret; } diff --git a/unpack-trees.h b/unpack-trees.h index d19df44f40..5c9e98a666 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -4,6 +4,7 @@ #define MAX_UNPACK_TREES 8 struct unpack_trees_options; +struct exclude_list; typedef int (*merge_fn_t)(struct cache_entry **src, struct unpack_trees_options *options); @@ -28,6 +29,7 @@ struct unpack_trees_options { skip_unmerged, initial_checkout, diff_index_cached, + skip_sparse_checkout, gently; const char *prefix; int pos; @@ -44,6 +46,8 @@ struct unpack_trees_options { struct index_state *dst_index; struct index_state *src_index; struct index_state result; + + struct exclude_list *el; /* for internal use */ }; extern int unpack_trees(unsigned n, struct tree_desc *t, From e800ec9d72a0fafbc323c41e90f7757062c2680e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:09 +0700 Subject: [PATCH 023/384] unpack_trees(): apply $GIT_DIR/info/sparse-checkout to the final index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++- unpack-trees.h | 2 ++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/unpack-trees.c b/unpack-trees.c index 44f8fdf808..2d8ecb73b1 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -32,6 +32,12 @@ static struct unpack_trees_error_msgs unpack_plumbing_errors = { /* bind_overlap */ "Entry '%s' overlaps with '%s'. Cannot bind.", + + /* sparse_not_uptodate_file */ + "Entry '%s' not uptodate. Cannot update sparse checkout.", + + /* would_lose_orphaned */ + "Working tree file '%s' would be %s by sparse checkout update.", }; #define ERRORMSG(o,fld) \ @@ -125,6 +131,57 @@ static int check_updates(struct unpack_trees_options *o) return errs != 0; } +static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o); +static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o); + +static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o) +{ + const char *basename; + + if (ce_stage(ce)) + return 0; + + basename = strrchr(ce->name, '/'); + basename = basename ? basename+1 : ce->name; + return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0; +} + +static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o) +{ + int was_skip_worktree = ce_skip_worktree(ce); + + if (will_have_skip_worktree(ce, o)) + ce->ce_flags |= CE_SKIP_WORKTREE; + else + ce->ce_flags &= ~CE_SKIP_WORKTREE; + + /* + * We only care about files getting into the checkout area + * If merge strategies want to remove some, go ahead, this + * flag will be removed eventually in unpack_trees() if it's + * outside checkout area. + */ + if (ce->ce_flags & CE_REMOVE) + return 0; + + if (!was_skip_worktree && ce_skip_worktree(ce)) { + /* + * If CE_UPDATE is set, verify_uptodate() must be called already + * also stat info may have lost after merged_entry() so calling + * verify_uptodate() again may fail + */ + if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o)) + return -1; + ce->ce_flags |= CE_WT_REMOVE; + } + if (was_skip_worktree && !ce_skip_worktree(ce)) { + if (verify_absent_sparse(ce, "overwritten", o)) + return -1; + ce->ce_flags |= CE_UPDATE; + } + return 0; +} + static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o) { int ret = o->fn(src, o); @@ -376,7 +433,7 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message) */ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { - int ret; + int i, ret; static struct cache_entry *dfc; struct exclude_list el; @@ -440,6 +497,17 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options goto done; } + if (!o->skip_sparse_checkout) { + for (i = 0;i < o->result.cache_nr;i++) { + struct cache_entry *ce = o->result.cache[i]; + + if (apply_sparse_checkout(ce, o)) { + ret = -1; + goto done; + } + } + } + o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) @@ -512,6 +580,12 @@ static int verify_uptodate(struct cache_entry *ce, return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } +static int verify_uptodate_sparse(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file)); +} + static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) { if (ce) @@ -705,6 +779,12 @@ static int verify_absent(struct cache_entry *ce, const char *action, return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); } +static int verify_absent_sparse(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned)); +} + static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) { diff --git a/unpack-trees.h b/unpack-trees.h index 5c9e98a666..95ff36c824 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -15,6 +15,8 @@ struct unpack_trees_error_msgs { const char *not_uptodate_dir; const char *would_lose_untracked; const char *bind_overlap; + const char *sparse_not_uptodate_file; + const char *would_lose_orphaned; }; struct unpack_trees_options { From f1f523eae99020d58ad6e7a1ef8187569e15c270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:10 +0700 Subject: [PATCH 024/384] unpack-trees(): ignore worktree check outside checkout area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verify_absent() and verify_uptodate() are used to ensure worktree is safe to be updated, then CE_REMOVE or CE_UPDATE will be set. Finally check_updates() bases on CE_REMOVE, CE_UPDATE and the recently added CE_WT_REMOVE to update working directory accordingly. The entries that are checked may eventually be left out of checkout area (done later in apply_sparse_checkout()). We don't want to update outside checkout area. This patch teaches Git to assume "good", skip these checks when it's sure those entries will be outside checkout area, and clear CE_REMOVE|CE_UPDATE that could be set due to this assumption. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/unpack-trees.c b/unpack-trees.c index 2d8ecb73b1..72743b34df 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -505,6 +505,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options ret = -1; goto done; } + /* + * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout + * area as a result of ce_skip_worktree() shortcuts in + * verify_absent() and verify_uptodate(). Clear them. + */ + if (ce_skip_worktree(ce)) + ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); + } } @@ -577,6 +585,8 @@ static int verify_uptodate_1(struct cache_entry *ce, static int verify_uptodate(struct cache_entry *ce, struct unpack_trees_options *o) { + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } @@ -776,6 +786,8 @@ static int verify_absent_1(struct cache_entry *ce, const char *action, static int verify_absent(struct cache_entry *ce, const char *action, struct unpack_trees_options *o) { + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); } From a5d07d0f5c838acb2c9d69a19907c50f72848b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:11 +0700 Subject: [PATCH 025/384] read-tree: add --no-sparse-checkout to disable sparse checkout support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-read-tree.txt | 6 +++++- builtin-read-tree.c | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index fc3f08b81c..ea7b0b2f5c 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] - [--index-output=] + [--index-output=] [--no-sparse-checkout] [ []] @@ -110,6 +110,10 @@ OPTIONS directories the index file and index output file are located in. +--no-sparse-checkout:: + Disable sparse checkout support even if `core.sparseCheckout` + is true. + :: The id of the tree object(s) to be read/merged. diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 9c2d634d6d..f5acb1aa93 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -31,7 +31,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] [--index-output=] [ []]", + "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] [--no-sparse-checkout] [--index-output=] [ []]", NULL }; @@ -98,6 +98,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) PARSE_OPT_NONEG, exclude_per_directory_cb }, OPT_SET_INT('i', NULL, &opts.index_only, "don't check the working tree after merging", 1), + OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, + "skip applying sparse checkout filter", 1), OPT_END() }; From d6b38f61c8125423abdf2b9c10f2187c5fecd80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:12 +0700 Subject: [PATCH 026/384] Add tests for sparse checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t1009-read-tree-sparse-checkout.sh | 154 +++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100755 t/t1009-read-tree-sparse-checkout.sh diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh new file mode 100755 index 0000000000..2192f5abc7 --- /dev/null +++ b/t/t1009-read-tree-sparse-checkout.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +test_description='sparse checkout tests' + +. ./test-lib.sh + +cat >expected <> init.t && + mkdir sub && + touch sub/added && + git add init.t sub/added && + git commit -m "modified and added" && + git tag top && + git rm sub/added && + git commit -m removed && + git tag removed && + git checkout top && + git ls-files --stage > result && + test_cmp expected result +' + +cat >expected.swt < result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result +' + +test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' + echo > .git/info/sparse-checkout + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' + git config core.sparsecheckout true && + echo > .git/info/sparse-checkout && + git read-tree --no-sparse-checkout -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files --stage > result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test ! -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + echo sub >> .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test ! -f sub/added +' + +test_expect_success 'read-tree updates worktree, absent case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + git read-tree -m -u HEAD^ && + test ! -f init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > init.t && + git read-tree -m -u HEAD^ && + grep -q dirty init.t && + rm init.t +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > added && + git read-tree -m -u HEAD^ && + grep -q dirty added +' + +test_expect_success 'read-tree adds to worktree, absent case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + git read-tree -u -m HEAD^ && + test ! -f sub/added +' + +test_expect_success 'read-tree adds to worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + mkdir sub && + echo dirty > sub/added && + git read-tree -u -m HEAD^ && + grep -q dirty sub/added +' + +test_done From 9e1afb16753d583a696c988d33f45655f81e8f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:13 +0700 Subject: [PATCH 027/384] sparse checkout: inhibit empty worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The way sparse checkout works, users may empty their worktree completely, because of non-matching sparse-checkout spec, or empty spec. I believe this is not desired. This patch makes Git refuse to produce such worktree. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t1009-read-tree-sparse-checkout.sh | 10 +++------- unpack-trees.c | 7 +++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh index 2192f5abc7..62246dbf95 100755 --- a/t/t1009-read-tree-sparse-checkout.sh +++ b/t/t1009-read-tree-sparse-checkout.sh @@ -55,20 +55,16 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse- test -f sub/added ' -cat >expected.swt < .git/info/sparse-checkout && - git read-tree -m -u HEAD && + test_must_fail git read-tree -m -u HEAD && git ls-files --stage > result && test_cmp expected result && git ls-files -t > result && test_cmp expected.swt result && - test ! -f init.t && - test ! -f sub/added + test -f init.t && + test -f sub/added ' cat >expected.swt <skip_sparse_checkout) { + int empty_worktree = 1; for (i = 0;i < o->result.cache_nr;i++) { struct cache_entry *ce = o->result.cache[i]; @@ -512,8 +513,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options */ if (ce_skip_worktree(ce)) ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); + else + empty_worktree = 0; } + if (o->result.cache_nr && empty_worktree) { + ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory"); + goto done; + } } o->src_index = NULL; From 0e098b6d79fbcab763874f2b6fde5aa82144d150 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Thu, 27 Aug 2009 09:38:27 +0200 Subject: [PATCH 028/384] Make test case number unique Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- t/{t4037-whitespace-status.sh => t4040-whitespace-status.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename t/{t4037-whitespace-status.sh => t4040-whitespace-status.sh} (100%) diff --git a/t/t4037-whitespace-status.sh b/t/t4040-whitespace-status.sh similarity index 100% rename from t/t4037-whitespace-status.sh rename to t/t4040-whitespace-status.sh From 97bf2a08095197f43b20b3bc1124552ae24d71bf Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sun, 30 Aug 2009 22:27:02 +0200 Subject: [PATCH 029/384] diff.c: fix typoes in comments Should be squashed when we reroll 'next' into the main commit. --- diff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff.c b/diff.c index 91d6ea21a9..24bd3fce3f 100644 --- a/diff.c +++ b/diff.c @@ -2382,7 +2382,7 @@ int diff_setup_done(struct diff_options *options) * Most of the time we can say "there are changes" * only by checking if there are changed paths, but * --ignore-whitespace* options force us to look - * inside contets. + * inside contents. */ if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || @@ -3346,7 +3346,7 @@ free_queue: fclose(options->file); /* - * Report the contents level differences with HAS_CHANGES; + * Report the content-level differences with HAS_CHANGES; * diff_addremove/diff_change does not set the bit when * DIFF_FROM_CONTENTS is in effect (e.g. with -w). */ From 9b4fe22990dd3fab9494927935c371b072ee7c8f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:50:26 -0400 Subject: [PATCH 030/384] status: typo fix in usage Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-commit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index 6cb0e40484..812470e63d 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -975,7 +975,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), OPT_BOOLEAN('s', "short", &shortstatus, - "show status concicely"), + "show status concisely"), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, From 01d8ba187d6e8a6bfa908fbef16a36d1186981dd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:53:48 -0400 Subject: [PATCH 031/384] status: refactor short-mode printing to its own function We want to be able to call it from multiple places. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-commit.c | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 812470e63d..5b42179fe7 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -966,11 +966,32 @@ static void short_untracked(int null_termination, struct string_list_item *it, } } +static void short_print(struct wt_status *s, int null_termination) +{ + int i; + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + + it = &(s->change.items[i]); + d = it->util; + if (d->stagemask) + short_unmerged(null_termination, it, s); + else + short_status(null_termination, it, s); + } + for (i = 0; i < s->untracked.nr; i++) { + struct string_list_item *it; + + it = &(s->untracked.items[i]); + short_untracked(null_termination, it, s); + } +} + int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; static int null_termination, shortstatus; - int i; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), @@ -1003,25 +1024,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; wt_status_collect(&s); - if (shortstatus) { - for (i = 0; i < s.change.nr; i++) { - struct wt_status_change_data *d; - struct string_list_item *it; - - it = &(s.change.items[i]); - d = it->util; - if (d->stagemask) - short_unmerged(null_termination, it, &s); - else - short_status(null_termination, it, &s); - } - for (i = 0; i < s.untracked.nr; i++) { - struct string_list_item *it; - - it = &(s.untracked.items[i]); - short_untracked(null_termination, it, &s); - } - } else { + if (shortstatus) + short_print(&s, null_termination); + else { s.verbose = verbose; if (s.relative_paths) s.prefix = prefix; From dd2be243d62260d4c825c22fdd2f61a7da12de22 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:54:14 -0400 Subject: [PATCH 032/384] status: refactor format option parsing This makes it possible to have more than two formats. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-commit.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 5b42179fe7..aa4a358799 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -991,12 +991,16 @@ static void short_print(struct wt_status *s, int null_termination) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; - static int null_termination, shortstatus; + static int null_termination; + static enum { + STATUS_FORMAT_LONG, + STATUS_FORMAT_SHORT, + } status_format = STATUS_FORMAT_LONG; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), - OPT_BOOLEAN('s', "short", &shortstatus, - "show status concisely"), + OPT_SET_INT('s', "short", &status_format, + "show status concisely", STATUS_FORMAT_SHORT), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, @@ -1006,8 +1010,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT_END(), }; - if (null_termination) - shortstatus = 1; + if (null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_SHORT; wt_status_prepare(&s); git_config(git_status_config, &s); @@ -1024,9 +1028,11 @@ int cmd_status(int argc, const char **argv, const char *prefix) s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; wt_status_collect(&s); - if (shortstatus) + switch (status_format) { + case STATUS_FORMAT_SHORT: short_print(&s, null_termination); - else { + break; + case STATUS_FORMAT_LONG: s.verbose = verbose; if (s.relative_paths) s.prefix = prefix; @@ -1035,6 +1041,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; wt_status_print(&s); + break; } return 0; } From 6f15787181a163e158c6fee1d79085b97692ac2f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:55:37 -0400 Subject: [PATCH 033/384] status: add --porcelain output format The "short" format was added to "git status" recently to provide a less verbose way of looking at the same information. This has two practical uses: 1. Users who want a more dense display of the information. 2. Scripts which want to parse the information and need a stable, easy-to-parse interface. For now, the "--short" format covers both of those uses. However, as time goes on, users of (1) may want additional format tweaks, or for "git status" to change its behavior based on configuration variables. Those wishes will be at odds with (2), which wants to stability for scripts. This patch introduces a separate --porcelain option early to avoid problems later on. Right now the --short and --porcelain outputs are identical. However, as time goes on, we will have the freedom to customize --short for human consumption while keeping --porcelain stable. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 9 +++++++-- builtin-commit.c | 9 ++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index b5939d6b58..e9363d9978 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -27,6 +27,11 @@ OPTIONS --short:: Give the output in the short-format. +--porcelain:: + Give the output in a stable, easy-to-parse format for scripts. + Currently this is identical to --short output, but is guaranteed + not to change in the future, making it safe for scripts. + -u[]:: --untracked-files[=]:: Show untracked files (Default: 'all'). @@ -45,8 +50,8 @@ used to change the default for when the option is not specified. -z:: - Terminate entries with NUL, instead of LF. This implies `-s` - (short status) output format. + Terminate entries with NUL, instead of LF. This implies + the `--porcelain` output format if no other format is given. OUTPUT diff --git a/builtin-commit.c b/builtin-commit.c index aa4a358799..ffdee31bbf 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -995,12 +995,16 @@ int cmd_status(int argc, const char **argv, const char *prefix) static enum { STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, + STATUS_FORMAT_PORCELAIN, } status_format = STATUS_FORMAT_LONG; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), + OPT_SET_INT(0, "porcelain", &status_format, + "show porcelain output format", + STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, @@ -1011,7 +1015,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) }; if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_SHORT; + status_format = STATUS_FORMAT_PORCELAIN; wt_status_prepare(&s); git_config(git_status_config, &s); @@ -1032,6 +1036,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) case STATUS_FORMAT_SHORT: short_print(&s, null_termination); break; + case STATUS_FORMAT_PORCELAIN: + short_print(&s, null_termination); + break; case STATUS_FORMAT_LONG: s.verbose = verbose; if (s.relative_paths) From 7c9f7038e923e6eb135b27c6fca9a010b034bc27 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:59:56 -0400 Subject: [PATCH 034/384] commit: support alternate status formats The status command recently grew "short" and "porcelain" options for alternate output formats. Since status is no longer "commit --dry-run", these formats are inaccessible to people who do want to see a dry-run in a parseable form. This patch makes those formats available to "git commit", implying the "dry-run" option when they are used. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 14 +++++++++++++ builtin-commit.c | 39 +++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 64f94cfe12..c45fbe4f97 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -75,6 +75,20 @@ OPTIONS and paths that are untracked, similar to the one that is given in the commit log editor. +--short:: + When doing a dry-run, give the output in the short-format. See + linkgit:git-status[1] for details. Implies `--dry-run`. + +--porcelain:: + When doing a dry-run, give the output in a porcelain-ready + format. See linkgit:git-status[1] for details. Implies + `--dry-run`. + +-z:: + When showing `short` or `porcelain` status output, terminate + entries in the status output with NUL, instead of LF. If no + format is given, implies the `--porcelain` output format. + -F :: --file=:: Take the commit message from the given file. Use '-' to diff --git a/builtin-commit.c b/builtin-commit.c index ffdee31bbf..f2fd0a4580 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -72,6 +72,15 @@ static int use_editor = 1, initial_commit, in_merge; static const char *only_include_assumed; static struct strbuf message; +static int null_termination; +static enum { + STATUS_FORMAT_LONG, + STATUS_FORMAT_SHORT, + STATUS_FORMAT_PORCELAIN, +} status_format = STATUS_FORMAT_LONG; + +static void short_print(struct wt_status *s, int null_termination); + static int opt_parse_m(const struct option *opt, const char *arg, int unset) { struct strbuf *buf = opt->value; @@ -105,6 +114,12 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), + OPT_SET_INT(0, "short", &status_format, "show status concisely", + STATUS_FORMAT_SHORT), + OPT_SET_INT(0, "porcelain", &status_format, + "show porcelain output format", STATUS_FORMAT_PORCELAIN), + OPT_BOOLEAN('z', "null", &null_termination, + "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), @@ -363,7 +378,18 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; wt_status_collect(s); - wt_status_print(s); + + switch (status_format) { + case STATUS_FORMAT_SHORT: + short_print(s, null_termination); + break; + case STATUS_FORMAT_PORCELAIN: + short_print(s, null_termination); + break; + case STATUS_FORMAT_LONG: + wt_status_print(s); + break; + } return s->commitable; } @@ -821,6 +847,11 @@ static int parse_and_validate_options(int argc, const char *argv[], else if (interactive && argc > 0) die("Paths with --interactive does not make sense."); + if (null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_PORCELAIN; + if (status_format != STATUS_FORMAT_LONG) + dry_run = 1; + return argc; } @@ -991,12 +1022,6 @@ static void short_print(struct wt_status *s, int null_termination) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; - static int null_termination; - static enum { - STATUS_FORMAT_LONG, - STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, - } status_format = STATUS_FORMAT_LONG; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), From 46b77a6b487fceeeb297a9473631939aefd7e6fd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:52:18 -0400 Subject: [PATCH 035/384] docs: note that status configuration affects only long format The short format does not respect any of the usual status.* configuration. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index e9363d9978..58d35fb3c2 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -114,13 +114,13 @@ compatibility) and `color.status.` configuration variables to colorize its output. If the config variable `status.relativePaths` is set to false, then all -paths shown are relative to the repository root, not to the current -directory. +paths shown in the long format are relative to the repository root, not +to the current directory. If `status.submodulesummary` is set to a non zero number or true (identical -to -1 or an unlimited number), the submodule summary will be enabled and a -summary of commits for modified submodules will be shown (see --summary-limit -option of linkgit:git-submodule[1]). +to -1 or an unlimited number), the submodule summary will be enabled for +the long format and a summary of commits for modified submodules will be +shown (see --summary-limit option of linkgit:git-submodule[1]). SEE ALSO -------- From 619a644d6daef56d70aeca85514e2d281eb483a5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 12:34:56 -0700 Subject: [PATCH 036/384] "checkout A...B" switches to the merge base between A and B When flipping commits around on topic branches, I often end up doing this sequence: * Run "log --oneline next..jc/frotz" to find out the first commit on 'jc/frotz' branch not yet merged to 'next'; * Run "checkout $that_commit^" to detach HEAD to the parent of it; * Rebuild the series on top of that commit; and * "show-branch jc/frotz HEAD" and "diff jc/frotz HEAD" to verify. Introduce a new syntax to "git checkout" to name the commit to switch to, to make the first two steps easier. When the branch to switch to is specified as A...B (you can omit either A or B but not both, and HEAD is used instead of the omitted side), the merge base between these two commits are computed, and if there is one unique one, we detach the HEAD at that commit. With this, I can say "checkout next...jc/frotz". Signed-off-by: Junio C Hamano --- builtin-checkout.c | 7 +++++-- cache.h | 1 + sha1_name.c | 42 ++++++++++++++++++++++++++++++++++++++++ t/t2012-checkout-last.sh | 27 +++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index da04eed391..fe7c8584ca 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -689,7 +689,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * case 3: git checkout [] * * With no paths, if is a commit, that is to - * switch to the branch or detach HEAD at it. + * switch to the branch or detach HEAD at it. As a special case, + * if is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. * * With no paths, if is _not_ a commit, no -t nor -b * was given, and there is a tracking branch whose name is @@ -715,7 +718,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "-")) arg = "@{-1}"; - if (get_sha1(arg, rev)) { + if (get_sha1_mb(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); if (!patch_mode && diff --git a/cache.h b/cache.h index 71a731dbc9..f8df15a060 100644 --- a/cache.h +++ b/cache.h @@ -703,6 +703,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int * extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); extern int interpret_branch_name(const char *str, struct strbuf *); +extern int get_sha1_mb(const char *str, unsigned char *sha1); extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); extern const char *ref_rev_parse_rules[]; diff --git a/sha1_name.c b/sha1_name.c index 44bb62d270..d5a240cf07 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -794,6 +794,48 @@ release_return: return retval; } +int get_sha1_mb(const char *name, unsigned char *sha1) +{ + struct commit *one, *two; + struct commit_list *mbs; + unsigned char sha1_tmp[20]; + const char *dots; + int st; + + dots = strstr(name, "..."); + if (!dots) + return get_sha1(name, sha1); + if (dots == name) + st = get_sha1("HEAD", sha1_tmp); + else { + struct strbuf sb; + strbuf_init(&sb, dots - name); + strbuf_add(&sb, name, dots - name); + st = get_sha1(sb.buf, sha1_tmp); + strbuf_release(&sb); + } + if (st) + return st; + one = lookup_commit_reference_gently(sha1_tmp, 0); + if (!one) + return -1; + + if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp)) + return -1; + two = lookup_commit_reference_gently(sha1_tmp, 0); + if (!two) + return -1; + mbs = get_merge_bases(one, two, 1); + if (!mbs || mbs->next) + st = -1; + else { + st = 0; + hashcpy(sha1, mbs->item->object.sha1); + } + free_commit_list(mbs); + return st; +} + /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh index 87b30a268c..b44de9dc62 100755 --- a/t/t2012-checkout-last.sh +++ b/t/t2012-checkout-last.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='checkout can switch to last branch' +test_description='checkout can switch to last branch and merge base' . ./test-lib.sh @@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' ' test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13" ' +test_expect_success 'merge base test setup' ' + git checkout -b another other && + echo "hello again" >>world && + git add world && + git commit -m third +' + +test_expect_success 'another...master' ' + git checkout another && + git checkout another...master && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + +test_expect_success '...master' ' + git checkout another && + git checkout ...master && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + +test_expect_success 'master...' ' + git checkout another && + git checkout master... && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + test_done From f2a37151d4624906e34a9bcafb2ad79d0e8cb7ec Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:21 +0100 Subject: [PATCH 037/384] Fix memory leak in helper method for disconnect Since some cases may need to disconnect from the helper and reconnect, wrap the function that just disconnects in a function that also frees transport->data. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- transport-helper.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/transport-helper.c b/transport-helper.c index f57e84c676..e24fcbb17d 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -67,6 +67,13 @@ static int disconnect_helper(struct transport *transport) return 0; } +static int release_helper(struct transport *transport) +{ + disconnect_helper(transport); + free(transport->data); + return 0; +} + static int fetch_with_fetch(struct transport *transport, int nr_heads, const struct ref **to_fetch) { @@ -163,6 +170,6 @@ int transport_helper_init(struct transport *transport, const char *name) transport->data = data; transport->get_refs_list = get_refs_list; transport->fetch = fetch; - transport->disconnect = disconnect_helper; + transport->disconnect = release_helper; return 0; } From fb0cc87ec0f1e9c13b27ea2c89127991d44ed3d3 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:22 +0100 Subject: [PATCH 038/384] Allow programs to not depend on remotes having urls For fetch and ls-remote, which use the first url of a remote, have transport_get() determine this by passing a remote and passing NULL for the url. For push, which uses every url of a remote, use each url in turn if there are any, and use NULL if there are none. This will allow the transport code to do something different if the location is not specified with a url. Also, have the message for a fetch say "foreign" if there is no url. Signed-off-by: Daniel Barkalow Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- builtin-fetch.c | 7 +++-- builtin-ls-remote.c | 2 +- builtin-push.c | 68 ++++++++++++++++++++++++++++----------------- transport.c | 3 ++ 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/builtin-fetch.c b/builtin-fetch.c index a35a6f8cb8..013a6ba1b9 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -309,7 +309,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, if (!fp) return error("cannot open %s: %s\n", filename, strerror(errno)); - url = transport_anonymize_url(raw_url); + if (raw_url) + url = transport_anonymize_url(raw_url); + else + url = xstrdup("foreign"); for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; @@ -704,7 +707,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (!remote) die("Where do you want to fetch from today?"); - transport = transport_get(remote, remote->url[0]); + transport = transport_get(remote, NULL); if (verbosity >= 2) transport->verbose = 1; if (verbosity < 0) diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c index 78a88f7476..d625df2f4e 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -89,7 +89,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) remote = nongit ? NULL : remote_get(dest); if (remote && !remote->url_nr) die("remote %s has no configured URL", dest); - transport = transport_get(remote, remote ? remote->url[0] : dest); + transport = transport_get(remote, NULL); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); diff --git a/builtin-push.c b/builtin-push.c index b5cd2cdad0..9846c638a6 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -88,6 +88,36 @@ static void setup_default_push_refspecs(void) } } +static int push_with_options(struct transport *transport, int flags) +{ + int err; + int nonfastforward; + if (receivepack) + transport_set_option(transport, + TRANS_OPT_RECEIVEPACK, receivepack); + if (thin) + transport_set_option(transport, TRANS_OPT_THIN, "yes"); + + if (flags & TRANSPORT_PUSH_VERBOSE) + fprintf(stderr, "Pushing to %s\n", transport->url); + err = transport_push(transport, refspec_nr, refspec, flags, + &nonfastforward); + err |= transport_disconnect(transport); + + if (!err) + return 0; + + error("failed to push some refs to '%s'", transport->url); + + if (nonfastforward && advice_push_nonfastforward) { + printf("To prevent you from losing history, non-fast-forward updates were rejected\n" + "Merge the remote changes before pushing again. See the 'non-fast-forward'\n" + "section of 'git push --help' for details.\n"); + } + + return 1; +} + static int do_push(const char *repo, int flags) { int i, errs; @@ -136,33 +166,19 @@ static int do_push(const char *repo, int flags) url = remote->url; url_nr = remote->url_nr; } - for (i = 0; i < url_nr; i++) { - struct transport *transport = - transport_get(remote, url[i]); - int err; - int nonfastforward; - if (receivepack) - transport_set_option(transport, - TRANS_OPT_RECEIVEPACK, receivepack); - if (thin) - transport_set_option(transport, TRANS_OPT_THIN, "yes"); - - if (flags & TRANSPORT_PUSH_VERBOSE) - fprintf(stderr, "Pushing to %s\n", url[i]); - err = transport_push(transport, refspec_nr, refspec, flags, - &nonfastforward); - err |= transport_disconnect(transport); - - if (!err) - continue; - - error("failed to push some refs to '%s'", url[i]); - if (nonfastforward && advice_push_nonfastforward) { - printf("To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes before pushing again. See the 'non-fast forward'\n" - "section of 'git push --help' for details.\n"); + if (url_nr) { + for (i = 0; i < url_nr; i++) { + struct transport *transport = + transport_get(remote, url[i]); + if (push_with_options(transport, flags)) + errs++; } - errs++; + } else { + struct transport *transport = + transport_get(remote, NULL); + + if (push_with_options(transport, flags)) + errs++; } return !!errs; } diff --git a/transport.c b/transport.c index 644a30a0b2..9daa68609f 100644 --- a/transport.c +++ b/transport.c @@ -813,6 +813,9 @@ struct transport *transport_get(struct remote *remote, const char *url) struct transport *ret = xcalloc(1, sizeof(*ret)); ret->remote = remote; + + if (!url && remote && remote->url) + url = remote->url[0]; ret->url = url; if (!prefixcmp(url, "rsync:")) { From 0a4da29dd806bca41cc615961d034b5a5fc30ff7 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:23 +0100 Subject: [PATCH 039/384] Use a function to determine whether a remote is valid Currently, it only checks url, but it will allow other things in the future. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- remote.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/remote.c b/remote.c index 73d33f2584..15c9cec60b 100644 --- a/remote.c +++ b/remote.c @@ -52,6 +52,11 @@ static struct rewrites rewrites_push; #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; +static int valid_remote(const struct remote *remote) +{ + return !!remote->url; +} + static const char *alias_url(const char *url, struct rewrites *r) { int i, j; @@ -688,14 +693,14 @@ struct remote *remote_get(const char *name) ret = make_remote(name, 0); if (valid_remote_nick(name)) { - if (!ret->url) + if (!valid_remote(ret)) read_remotes_file(ret); - if (!ret->url) + if (!valid_remote(ret)) read_branches_file(ret); } - if (name_given && !ret->url) + if (name_given && !valid_remote(ret)) add_url_alias(ret, name); - if (!ret->url) + if (!valid_remote(ret)) return NULL; ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec); ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec); From 3714831189b32591ffe33c08e209a9a61c25a2f6 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:24 +0100 Subject: [PATCH 040/384] Allow fetch to modify refs This allows the transport to use the null sha1 for a ref reported to be present in the remote repository to indicate that a ref exists but its actual value is presently unknown and will be set if the objects are fetched. Also adds documentation to the API to specify exactly what the methods should do and how they should interpret arguments. Signed-off-by: Daniel Barkalow Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- builtin-clone.c | 3 ++- transport-helper.c | 4 ++-- transport.c | 13 +++++++------ transport.h | 41 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/builtin-clone.c b/builtin-clone.c index 5762a6f9d8..32f22e100e 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -360,9 +360,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; int dest_exists; - const struct ref *refs, *remote_head, *mapped_refs; + const struct ref *refs, *remote_head; const struct ref *remote_head_points_at; const struct ref *our_head_points_at; + struct ref *mapped_refs; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; diff --git a/transport-helper.c b/transport-helper.c index e24fcbb17d..53d8f08ee9 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -75,7 +75,7 @@ static int release_helper(struct transport *transport) } static int fetch_with_fetch(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct child_process *helper = get_helper(transport); FILE *file = xfdopen(helper->out, "r"); @@ -99,7 +99,7 @@ static int fetch_with_fetch(struct transport *transport, } static int fetch(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct helper_data *data = transport->data; int i, count; diff --git a/transport.c b/transport.c index 9daa68609f..5ae8db6335 100644 --- a/transport.c +++ b/transport.c @@ -204,7 +204,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) } static int fetch_objs_via_rsync(struct transport *transport, - int nr_objs, const struct ref **to_fetch) + int nr_objs, struct ref **to_fetch) { struct strbuf buf = STRBUF_INIT; struct child_process rsync; @@ -408,7 +408,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus } static int fetch_refs_from_bundle(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct bundle_transport_data *data = transport->data; return unbundle(&data->header, data->fd); @@ -486,7 +486,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus } static int fetch_refs_via_pack(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct git_transport_data *data = transport->data; char **heads = xmalloc(nr_heads * sizeof(*heads)); @@ -926,16 +926,17 @@ const struct ref *transport_get_remote_refs(struct transport *transport) return transport->remote_refs; } -int transport_fetch_refs(struct transport *transport, const struct ref *refs) +int transport_fetch_refs(struct transport *transport, struct ref *refs) { int rc; int nr_heads = 0, nr_alloc = 0, nr_refs = 0; - const struct ref **heads = NULL; - const struct ref *rm; + struct ref **heads = NULL; + struct ref *rm; for (rm = refs; rm; rm = rm->next) { nr_refs++; if (rm->peer_ref && + !is_null_sha1(rm->old_sha1) && !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1)) continue; ALLOC_GROW(heads, nr_heads + 1, nr_alloc); diff --git a/transport.h b/transport.h index c14da6f1e5..503db11701 100644 --- a/transport.h +++ b/transport.h @@ -18,11 +18,48 @@ struct transport { int (*set_option)(struct transport *connection, const char *name, const char *value); + /** + * Returns a list of the remote side's refs. In order to allow + * the transport to try to share connections, for_push is a + * hint as to whether the ultimate operation is a push or a fetch. + * + * If the transport is able to determine the remote hash for + * the ref without a huge amount of effort, it should store it + * in the ref's old_sha1 field; otherwise it should be all 0. + **/ struct ref *(*get_refs_list)(struct transport *transport, int for_push); - int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs); + + /** + * Fetch the objects for the given refs. Note that this gets + * an array, and should ignore the list structure. + * + * If the transport did not get hashes for refs in + * get_refs_list(), it should set the old_sha1 fields in the + * provided refs now. + **/ + int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); + + /** + * Push the objects and refs. Send the necessary objects, and + * then, for any refs where peer_ref is set and + * peer_ref->new_sha1 is different from old_sha1, tell the + * remote side to update each ref in the list from old_sha1 to + * peer_ref->new_sha1. + * + * Where possible, set the status for each ref appropriately. + * + * The transport must modify new_sha1 in the ref to the new + * value if the remote accepted the change. Note that this + * could be a different value from peer_ref->new_sha1 if the + * process involved generating new commits. + **/ int (*push_refs)(struct transport *transport, struct ref *refs, int flags); int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags); + /** get_refs_list(), fetch(), and push_refs() can keep + * resources (such as a connection) reserved for futher + * use. disconnect() releases these resources. + **/ int (*disconnect)(struct transport *connection); char *pack_lockfile; signed verbose : 2; @@ -74,7 +111,7 @@ int transport_push(struct transport *connection, const struct ref *transport_get_remote_refs(struct transport *transport); -int transport_fetch_refs(struct transport *transport, const struct ref *refs); +int transport_fetch_refs(struct transport *transport, struct ref *refs); void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); char *transport_anonymize_url(const char *url); From c578f51d52fe75e71d75566c2c216af989b65e6e Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:25 +0100 Subject: [PATCH 041/384] Add a config option for remotes to specify a foreign vcs If this is set, the url is not required, and the transport always uses a helper named "git-remote-". It is a separate configuration option in order to allow a sensible configuration for foreign systems which either have no meaningful urls for repositories or which require urls that do not specify the system used by the repository at that location. However, this only affects how the name of the helper is determined, not anything about the interaction with the helper, and the contruction is such that, if the foreign scm does happen to use a co-named url method, a url with that method may be used directly. Signed-off-by: Daniel Barkalow Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ remote.c | 4 +++- remote.h | 2 ++ transport.c | 5 +++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index d1e2120e15..0d9d369ca7 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1408,6 +1408,10 @@ remote..tagopt:: Setting this value to \--no-tags disables automatic tag following when fetching from remote +remote..vcs:: + Setting this to a value will cause git to interact with + the remote with the git-remote- helper. + remotes.:: The list of remotes which are fetched by "git remote update ". See linkgit:git-remote[1]. diff --git a/remote.c b/remote.c index 15c9cec60b..09bb79c22c 100644 --- a/remote.c +++ b/remote.c @@ -54,7 +54,7 @@ static char buffer[BUF_SIZE]; static int valid_remote(const struct remote *remote) { - return !!remote->url; + return (!!remote->url) || (!!remote->foreign_vcs); } static const char *alias_url(const char *url, struct rewrites *r) @@ -444,6 +444,8 @@ static int handle_config(const char *key, const char *value, void *cb) } else if (!strcmp(subkey, ".proxy")) { return git_config_string((const char **)&remote->http_proxy, key, value); + } else if (!strcmp(subkey, ".vcs")) { + return git_config_string(&remote->foreign_vcs, key, value); } return 0; } diff --git a/remote.h b/remote.h index 5db842087d..ac0ce2ff9c 100644 --- a/remote.h +++ b/remote.h @@ -11,6 +11,8 @@ struct remote { const char *name; int origin; + const char *foreign_vcs; + const char **url; int url_nr; int url_alloc; diff --git a/transport.c b/transport.c index 5ae8db6335..13bab4e2a1 100644 --- a/transport.c +++ b/transport.c @@ -818,6 +818,11 @@ struct transport *transport_get(struct remote *remote, const char *url) url = remote->url[0]; ret->url = url; + if (remote && remote->foreign_vcs) { + transport_helper_init(ret, remote->foreign_vcs); + return ret; + } + if (!prefixcmp(url, "rsync:")) { ret->get_refs_list = get_refs_via_rsync; ret->fetch = fetch_objs_via_rsync; From 87422439d100f020cadb63b5da8495e5fbfb8fa3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 18 Nov 2009 02:42:26 +0100 Subject: [PATCH 042/384] Allow specifying the remote helper in the url The common case for remote helpers will be to import some repository which can be specified by a single URL. Support this use case by allowing users to say: git clone hg::https://soc.googlecode.com/hg/ soc Signed-off-by: Johannes Schindelin Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- transport.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/transport.c b/transport.c index 13bab4e2a1..5d814b50e4 100644 --- a/transport.c +++ b/transport.c @@ -818,6 +818,16 @@ struct transport *transport_get(struct remote *remote, const char *url) url = remote->url[0]; ret->url = url; + /* maybe it is a foreign URL? */ + if (url) { + const char *p = url; + + while (isalnum(*p)) + p++; + if (!prefixcmp(p, "::")) + remote->foreign_vcs = xstrndup(url, p - url); + } + if (remote && remote->foreign_vcs) { transport_helper_init(ret, remote->foreign_vcs); return ret; From e65e91ed4af5ed9c5c810a2cd77b8648a0287e66 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:27 +0100 Subject: [PATCH 043/384] Add support for "import" helper command This command, supported if the "import" capability is advertized, allows a helper to support fetching by outputting a git-fast-import stream. If both "fetch" and "import" are advertized, git itself will use "fetch" (although other users may use "import" in this case). Signed-off-by: Daniel Barkalow Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- Documentation/git-remote-helpers.txt | 10 ++++++ transport-helper.c | 52 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 173ee232f2..e9aa67e757 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -43,6 +43,13 @@ Commands are given by the caller on the helper's standard input, one per line. + Supported if the helper has the "fetch" capability. +'import' :: + Produces a fast-import stream which imports the current value + of the named ref. It may additionally import other refs as + needed to construct the history efficiently. ++ +Supported if the helper has the "import" capability. + If a fatal error occurs, the program writes the error message to stderr and exits. The caller should expect that a suitable error message has been printed if the child closes the connection without @@ -57,6 +64,9 @@ CAPABILITIES 'fetch':: This helper supports the 'fetch' command. +'import':: + This helper supports the 'import' command. + REF LIST ATTRIBUTES ------------------- diff --git a/transport-helper.c b/transport-helper.c index 53d8f08ee9..82caaaead6 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -11,6 +11,7 @@ struct helper_data const char *name; struct child_process *helper; unsigned fetch : 1; + unsigned import : 1; }; static struct child_process *get_helper(struct transport *transport) @@ -48,6 +49,8 @@ static struct child_process *get_helper(struct transport *transport) break; if (!strcmp(buf.buf, "fetch")) data->fetch = 1; + if (!strcmp(buf.buf, "import")) + data->import = 1; } return data->helper; } @@ -98,6 +101,52 @@ static int fetch_with_fetch(struct transport *transport, return 0; } +static int get_importer(struct transport *transport, struct child_process *fastimport) +{ + struct child_process *helper = get_helper(transport); + memset(fastimport, 0, sizeof(*fastimport)); + fastimport->in = helper->out; + fastimport->argv = xcalloc(5, sizeof(*fastimport->argv)); + fastimport->argv[0] = "fast-import"; + fastimport->argv[1] = "--quiet"; + + fastimport->git_cmd = 1; + return start_command(fastimport); +} + +static int fetch_with_import(struct transport *transport, + int nr_heads, struct ref **to_fetch) +{ + struct child_process fastimport; + struct child_process *helper = get_helper(transport); + int i; + struct ref *posn; + struct strbuf buf = STRBUF_INIT; + + if (get_importer(transport, &fastimport)) + die("Couldn't run fast-import"); + + for (i = 0; i < nr_heads; i++) { + posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + + strbuf_addf(&buf, "import %s\n", posn->name); + write_in_full(helper->in, buf.buf, buf.len); + strbuf_reset(&buf); + } + disconnect_helper(transport); + finish_command(&fastimport); + + for (i = 0; i < nr_heads; i++) { + posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + read_ref(posn->name, posn->old_sha1); + } + return 0; +} + static int fetch(struct transport *transport, int nr_heads, struct ref **to_fetch) { @@ -115,6 +164,9 @@ static int fetch(struct transport *transport, if (data->fetch) return fetch_with_fetch(transport, nr_heads, to_fetch); + if (data->import) + return fetch_with_import(transport, nr_heads, to_fetch); + return -1; } From 72ff894308d4f6eb9f081167377857f7a3268bca Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:28 +0100 Subject: [PATCH 044/384] Allow helper to map private ref names into normal names This allows a helper to say that, when it handles "import refs/heads/topic", the script it outputs will actually write to refs/svn/origin/branches/topic; therefore, transport-helper should read it from the latter location after git-fast-import completes. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- Documentation/git-remote-helpers.txt | 16 ++++++++++++- remote.c | 27 ++++++++++++++++++++++ remote.h | 5 ++++ transport-helper.c | 34 +++++++++++++++++++++++++++- 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index e9aa67e757..d6c5268d38 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -46,7 +46,11 @@ Supported if the helper has the "fetch" capability. 'import' :: Produces a fast-import stream which imports the current value of the named ref. It may additionally import other refs as - needed to construct the history efficiently. + needed to construct the history efficiently. The script writes + to a helper-specific private namespace. The value of the named + ref should be written to a location in this namespace derived + by applying the refspecs from the "refspec" capability to the + name of the ref. + Supported if the helper has the "import" capability. @@ -67,6 +71,16 @@ CAPABILITIES 'import':: This helper supports the 'import' command. +'refspec' 'spec':: + When using the import command, expect the source ref to have + been written to the destination ref. The earliest applicable + refspec takes precedence. For example + "refs/heads/*:refs/svn/origin/branches/*" means that, after an + "import refs/heads/name", the script has written to + refs/svn/origin/branches/name. If this capability is used at + all, it must cover all refs reported by the list command; if + it is not used, it is effectively "*:*" + REF LIST ATTRIBUTES ------------------- diff --git a/remote.c b/remote.c index 09bb79c22c..1f7870d107 100644 --- a/remote.c +++ b/remote.c @@ -673,6 +673,16 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) return parse_refspec_internal(nr_refspec, refspec, 0, 0); } +void free_refspec(int nr_refspec, struct refspec *refspec) +{ + int i; + for (i = 0; i < nr_refspec; i++) { + free(refspec[i].src); + free(refspec[i].dst); + } + free(refspec); +} + static int valid_remote_nick(const char *name) { if (!name[0] || is_dot_or_dotdot(name)) @@ -811,6 +821,23 @@ static int match_name_with_pattern(const char *key, const char *name, return ret; } +char *apply_refspecs(struct refspec *refspecs, int nr_refspec, + const char *name) +{ + int i; + char *ret = NULL; + for (i = 0; i < nr_refspec; i++) { + struct refspec *refspec = refspecs + i; + if (refspec->pattern) { + if (match_name_with_pattern(refspec->src, name, + refspec->dst, &ret)) + return ret; + } else if (!strcmp(refspec->src, name)) + return strdup(refspec->dst); + } + return NULL; +} + int remote_find_tracking(struct remote *remote, struct refspec *refspec) { int find_src = refspec->src == NULL; diff --git a/remote.h b/remote.h index ac0ce2ff9c..cdc3b5b159 100644 --- a/remote.h +++ b/remote.h @@ -91,6 +91,11 @@ void ref_remove_duplicates(struct ref *ref_map); int valid_fetch_refspec(const char *refspec); struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec); +void free_refspec(int nr_refspec, struct refspec *refspec); + +char *apply_refspecs(struct refspec *refspecs, int nr_refspec, + const char *name); + int match_refs(struct ref *src, struct ref **dst, int nr_refspec, const char **refspec, int all); diff --git a/transport-helper.c b/transport-helper.c index 82caaaead6..da8185a981 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -5,6 +5,7 @@ #include "commit.h" #include "diff.h" #include "revision.h" +#include "remote.h" struct helper_data { @@ -12,6 +13,9 @@ struct helper_data struct child_process *helper; unsigned fetch : 1; unsigned import : 1; + /* These go from remote name (as in "list") to private name */ + struct refspec *refspecs; + int refspec_nr; }; static struct child_process *get_helper(struct transport *transport) @@ -20,6 +24,9 @@ static struct child_process *get_helper(struct transport *transport) struct strbuf buf = STRBUF_INIT; struct child_process *helper; FILE *file; + const char **refspecs = NULL; + int refspec_nr = 0; + int refspec_alloc = 0; if (data->helper) return data->helper; @@ -51,6 +58,21 @@ static struct child_process *get_helper(struct transport *transport) data->fetch = 1; if (!strcmp(buf.buf, "import")) data->import = 1; + if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) { + ALLOC_GROW(refspecs, + refspec_nr + 1, + refspec_alloc); + refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec ")); + } + } + if (refspecs) { + int i; + data->refspec_nr = refspec_nr; + data->refspecs = parse_fetch_refspec(refspec_nr, refspecs); + for (i = 0; i < refspec_nr; i++) { + free((char *)refspecs[i]); + } + free(refspecs); } return data->helper; } @@ -72,6 +94,9 @@ static int disconnect_helper(struct transport *transport) static int release_helper(struct transport *transport) { + struct helper_data *data = transport->data; + free_refspec(data->refspec_nr, data->refspecs); + data->refspecs = NULL; disconnect_helper(transport); free(transport->data); return 0; @@ -119,6 +144,7 @@ static int fetch_with_import(struct transport *transport, { struct child_process fastimport; struct child_process *helper = get_helper(transport); + struct helper_data *data = transport->data; int i; struct ref *posn; struct strbuf buf = STRBUF_INIT; @@ -139,10 +165,16 @@ static int fetch_with_import(struct transport *transport, finish_command(&fastimport); for (i = 0; i < nr_heads; i++) { + char *private; posn = to_fetch[i]; if (posn->status & REF_STATUS_UPTODATE) continue; - read_ref(posn->name, posn->old_sha1); + if (data->refspecs) + private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name); + else + private = strdup(posn->name); + read_ref(private, posn->old_sha1); + free(private); } return 0; } From b962dbdc806510a4572a5574245d825a414231ab Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Wed, 18 Nov 2009 02:42:29 +0100 Subject: [PATCH 045/384] Fix various memory leaks in transport-helper.c Found with: valgrind --tool=memcheck --leak-check=full --show-reachable=yes Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- transport-helper.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transport-helper.c b/transport-helper.c index da8185a981..628a5ca210 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -74,6 +74,7 @@ static struct child_process *get_helper(struct transport *transport) } free(refspecs); } + strbuf_release(&buf); return data->helper; } @@ -163,6 +164,8 @@ static int fetch_with_import(struct transport *transport, } disconnect_helper(transport); finish_command(&fastimport); + free(fastimport.argv); + fastimport.argv = NULL; for (i = 0; i < nr_heads; i++) { char *private; @@ -176,6 +179,7 @@ static int fetch_with_import(struct transport *transport, read_ref(private, posn->old_sha1); free(private); } + strbuf_release(&buf); return 0; } From f8ec916731ef8d81eefc5db61e3405dade65d821 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Wed, 18 Nov 2009 02:42:30 +0100 Subject: [PATCH 046/384] Allow helpers to report in "list" command that the ref is unchanged Helpers may use a line like "? name unchanged" to specify that there is nothing new at that name, without any git-specific code to determine the correct response. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- Documentation/git-remote-helpers.txt | 4 +++- transport-helper.c | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index d6c5268d38..f4b6a5aea3 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -84,7 +84,9 @@ CAPABILITIES REF LIST ATTRIBUTES ------------------- -None are defined yet, but the caller must accept any which are supplied. +'unchanged':: + This ref is unchanged since the last import or fetch, although + the helper cannot necessarily determine what value that produced. Documentation ------------- diff --git a/transport-helper.c b/transport-helper.c index 628a5ca210..c87530e87d 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -206,6 +206,22 @@ static int fetch(struct transport *transport, return -1; } +static int has_attribute(const char *attrs, const char *attr) { + int len; + if (!attrs) + return 0; + + len = strlen(attr); + for (;;) { + const char *space = strchrnul(attrs, ' '); + if (len == space - attrs && !strncmp(attrs, attr, len)) + return 1; + if (!*space) + return 0; + attrs = space + 1; + } +} + static struct ref *get_refs_list(struct transport *transport, int for_push) { struct child_process *helper; @@ -240,6 +256,12 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) (*tail)->symref = xstrdup(buf.buf + 1); else if (buf.buf[0] != '?') get_sha1_hex(buf.buf, (*tail)->old_sha1); + if (eon) { + if (has_attribute(eon + 1, "unchanged")) { + (*tail)->status |= REF_STATUS_UPTODATE; + read_ref((*tail)->name, (*tail)->old_sha1); + } + } tail = &((*tail)->next); } strbuf_release(&buf); From d4e1b47a922576e6af98cd84913471a6d3096ac0 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Wed, 18 Nov 2009 02:42:31 +0100 Subject: [PATCH 047/384] Basic build infrastructure for Python scripts This patch adds basic boilerplate support (based on corresponding Perl sections) for enabling the building and installation Python scripts. There are currently no Python scripts being built, and when Python scripts are added in future patches, their building and installation can be disabled by defining NO_PYTHON. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Makefile | 13 +++++++++++++ configure.ac | 3 +++ t/test-lib.sh | 1 + 3 files changed, 17 insertions(+) diff --git a/Makefile b/Makefile index 268aede566..b27a7d603b 100644 --- a/Makefile +++ b/Makefile @@ -164,6 +164,8 @@ all:: # # Define NO_PERL if you do not want Perl scripts or libraries at all. # +# Define NO_PYTHON if you do not want Python scripts or libraries at all. +# # Define NO_TCLTK if you do not want Tcl/Tk GUI. # # The TCL_PATH variable governs the location of the Tcl interpreter @@ -308,6 +310,7 @@ LIB_H = LIB_OBJS = PROGRAMS = SCRIPT_PERL = +SCRIPT_PYTHON = SCRIPT_SH = TEST_PROGRAMS = @@ -345,6 +348,7 @@ SCRIPT_PERL += git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ + $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-instaweb # Empty... @@ -398,8 +402,12 @@ endif ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif +ifndef PYTHON_PATH + PYTHON_PATH = /usr/bin/python +endif export PERL_PATH +export PYTHON_PATH LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a @@ -1308,6 +1316,10 @@ ifeq ($(PERL_PATH),) NO_PERL=NoThanks endif +ifeq ($(PYTHON_PATH),) +NO_PYTHON=NoThanks +endif + QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = @@ -1355,6 +1367,7 @@ prefix_SQ = $(subst ','\'',$(prefix)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) LIBS = $(GITLIBS) $(EXTLIBS) diff --git a/configure.ac b/configure.ac index b09b8e446f..84b6cf48a4 100644 --- a/configure.ac +++ b/configure.ac @@ -233,6 +233,9 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # +# Define PYTHON_PATH to provide path to Python. +GIT_ARG_SET_PATH(python) +# # Define ZLIB_PATH to provide path to zlib. GIT_ARG_SET_PATH(zlib) # diff --git a/t/test-lib.sh b/t/test-lib.sh index f2ca536472..0b991dbd5d 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -729,6 +729,7 @@ case $(uname -s) in esac test -z "$NO_PERL" && test_set_prereq PERL +test -z "$NO_PYTHON" && test_set_prereq PYTHON # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS From 3e97c7c6af2901cec63bf35fcd43ae3472e24af8 Mon Sep 17 00:00:00 2001 From: Greg Bacon Date: Thu, 19 Nov 2009 15:12:24 -0600 Subject: [PATCH 048/384] No diff -b/-w output for all-whitespace changes Change git-diff's whitespace-ignoring modes to generate output only if a non-empty patch results, which git-apply rejects. Update the tests to look for the new behavior. Signed-off-by: Greg Bacon Signed-off-by: Junio C Hamano --- diff.c | 35 +++++++++++++++++++++++++++-------- t/t4015-diff-whitespace.sh | 14 ++++++++++++-- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/diff.c b/diff.c index 0d7f5ea4a8..108de23611 100644 --- a/diff.c +++ b/diff.c @@ -189,6 +189,7 @@ struct emit_callback { struct diff_words_data *diff_words; int *found_changesp; FILE *file; + struct strbuf *header; }; static int count_lines(const char *data, int size) @@ -755,6 +756,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); + if (ecbdata->header) { + fprintf(ecbdata->file, "%s", ecbdata->header->buf); + strbuf_reset(ecbdata->header); + ecbdata->header = NULL; + } *(ecbdata->found_changesp) = 1; if (ecbdata->label_path[0]) { @@ -1561,6 +1567,7 @@ static void builtin_diff(const char *name_a, const char *reset = diff_get_color_opt(o, DIFF_RESET); const char *a_prefix, *b_prefix; const char *textconv_one = NULL, *textconv_two = NULL; + struct strbuf header = STRBUF_INIT; if (DIFF_OPT_TST(o, SUBMODULE_LOG) && (!one->mode || S_ISGITLINK(one->mode)) && @@ -1595,25 +1602,26 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); + strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else if (lbl[1][0] == '/') { - fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset); + strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else { if (one->mode != two->mode) { - fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset); - fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); + strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); } if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + /* * we do not run diff between different kind * of objects. @@ -1623,6 +1631,8 @@ static void builtin_diff(const char *name_a, if (complete_rewrite && (textconv_one || !diff_filespec_is_binary(one)) && (textconv_two || !diff_filespec_is_binary(two))) { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); emit_rewrite_diff(name_a, name_b, one, two, textconv_one, textconv_two, o); o->found_changes = 1; @@ -1640,6 +1650,8 @@ static void builtin_diff(const char *name_a, if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) goto free_ab_and_return; + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) emit_binary_diff(o->file, &mf1, &mf2); else @@ -1656,6 +1668,11 @@ static void builtin_diff(const char *name_a, struct emit_callback ecbdata; const struct userdiff_funcname *pe; + if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); + } + if (textconv_one) { size_t size; mf1.ptr = run_textconv(textconv_one, one, &size); @@ -1685,6 +1702,7 @@ static void builtin_diff(const char *name_a, if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; + ecbdata.header = header.len ? &header : NULL; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; @@ -1729,6 +1747,7 @@ static void builtin_diff(const char *name_a, } free_ab_and_return: + strbuf_release(&header); diff_free_filespec_data(one); diff_free_filespec_data(two); free(a_one); diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 8dd147d78f..90f3342373 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -93,8 +93,6 @@ git diff > out test_expect_success 'another test, without options' 'test_cmp expect out' cat << EOF > expect -diff --git a/x b/x -index d99af23..8b32fb5 100644 EOF git diff -w > out test_expect_success 'another test, with -w' 'test_cmp expect out' @@ -386,6 +384,18 @@ test_expect_success 'checkdiff allows new blank lines' ' git diff --check ' +cat <expect +EOF +test_expect_success 'whitespace-only changes not reported' ' + git reset --hard && + echo >x "hello world" && + git add x && + git commit -m "hello 1" && + echo >x "hello world" && + git diff -b >actual && + test_cmp expect actual +' + test_expect_success 'combined diff with autocrlf conversion' ' git reset --hard && From 61dfa1bb6710690e53fa20bc3ddd1f5fbe8c1d22 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Nov 2009 03:02:44 -0800 Subject: [PATCH 049/384] "rebase --onto A...B" replays history on the merge base between A and B This is in spirit similar to "checkout A...B". To re-queue a new set of patches for a series that the original author prepared to apply on 'next' on the same base as before, you would do something like this: $ git checkout next^0 $ git am -s rerolled-series.mbox $ git rebase --onto next...jh/notes next The first two commands recreates commits to be rebased as the original author intended (i.e. applies directly on top of 'next'), and the rebase command replays that history on top of the same commit the series being replaced was built on (which is typically much older than the tip of 'next'). Signed-off-by: Junio C Hamano --- git-rebase.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/git-rebase.sh b/git-rebase.sh index 6ec155cf03..6503113a84 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -34,6 +34,8 @@ set_reflog_action rebase require_work_tree cd_to_toplevel +LF=' +' OK_TO_SKIP_PRE_REBASE= RESOLVEMSG=" When you have resolved this problem run \"git rebase --continue\". @@ -417,7 +419,22 @@ fi # Make sure the branch to rebase onto is valid. onto_name=${newbase-"$upstream_name"} -onto=$(git rev-parse --verify "${onto_name}^0") || exit +if left=$(expr "$onto_name" : '\(.*\)\.\.\.') && + right=$(expr "$onto_name" : '\.\.\.\(.*\)$') && + : ${left:=HEAD} ${right:=HEAD} && + onto=$(git merge-base "$left" "$right") +then + case "$onto" in + ?*"$LF"?*) + die "$onto_name: there are more than one merge bases" + ;; + '') + die "$onto_name: there is no merge base" + ;; + esac +else + onto=$(git rev-parse --verify "${onto_name}^0") || exit +fi # If a hook exists, give it a chance to interrupt run_pre_rebase_hook "$upstream_arg" "$@" From 2fe40b63001844f9a884ac9cb49cabd5e183b62f Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Wed, 18 Nov 2009 02:42:32 +0100 Subject: [PATCH 050/384] Add Python support library for remote helpers This patch introduces parts of a Python package called "git_remote_helpers" containing the building blocks for remote helpers written in Python. No actual remote helpers are part of this patch, this patch only includes the common basics needed to start writing such helpers. The patch includes the necessary Makefile additions to build and install the git_remote_helpers Python package along with the rest of Git. This patch is based on Johan Herland's git_remote_cvs patch and has been improved by the following contributions: - David Aguilar: Lots of Python coding style fixes - David Aguilar: DESTDIR support in Makefile Cc: David Aguilar Cc: Johan Herland Signed-off-by: Sverre Rabbelier Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Makefile | 38 ++ git_remote_helpers/.gitignore | 2 + git_remote_helpers/Makefile | 35 ++ git_remote_helpers/__init__.py | 16 + git_remote_helpers/git/__init__.py | 0 git_remote_helpers/git/git.py | 678 +++++++++++++++++++++++++++++ git_remote_helpers/setup.py | 17 + git_remote_helpers/util.py | 194 +++++++++ t/test-lib.sh | 9 + 9 files changed, 989 insertions(+) create mode 100644 git_remote_helpers/.gitignore create mode 100644 git_remote_helpers/Makefile create mode 100644 git_remote_helpers/__init__.py create mode 100644 git_remote_helpers/git/__init__.py create mode 100644 git_remote_helpers/git/git.py create mode 100644 git_remote_helpers/setup.py create mode 100644 git_remote_helpers/util.py diff --git a/Makefile b/Makefile index b27a7d603b..a95ffda23a 100644 --- a/Makefile +++ b/Makefile @@ -1398,6 +1398,9 @@ ifndef NO_TCLTK endif ifndef NO_PERL $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all +endif +ifndef NO_PYTHON + $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all endif $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) @@ -1517,6 +1520,35 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh mv $@+ $@ endif # NO_PERL +ifndef NO_PYTHON +$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS +$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py + $(QUIET_GEN)$(RM) $@ $@+ && \ + INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \ + --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \ + instlibdir` && \ + sed -e '1{' \ + -e ' s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ + -e '}' \ + -e 's|^import sys.*|&; \\\ + import os; \\\ + sys.path[0] = os.environ.has_key("GITPYTHONLIB") and \\\ + os.environ["GITPYTHONLIB"] or \\\ + "@@INSTLIBDIR@@"|' \ + -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ + $@.py >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ +else # NO_PYTHON +$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh + $(QUIET_GEN)$(RM) $@ $@+ && \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \ + unimplemented.sh >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ +endif # NO_PYTHON + configure: configure.ac $(QUIET_GEN)$(RM) $@ $<+ && \ sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ @@ -1741,6 +1773,9 @@ install: all ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install endif +ifndef NO_PYTHON + $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install +endif ifndef NO_TCLTK $(MAKE) -C gitk-git install $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install @@ -1854,6 +1889,9 @@ clean: ifndef NO_PERL $(RM) gitweb/gitweb.cgi $(MAKE) -C perl clean +endif +ifndef NO_PYTHON + $(MAKE) -C git_remote_helpers clean endif $(MAKE) -C templates/ clean $(MAKE) -C t/ clean diff --git a/git_remote_helpers/.gitignore b/git_remote_helpers/.gitignore new file mode 100644 index 0000000000..2247d5f95a --- /dev/null +++ b/git_remote_helpers/.gitignore @@ -0,0 +1,2 @@ +/build +/dist diff --git a/git_remote_helpers/Makefile b/git_remote_helpers/Makefile new file mode 100644 index 0000000000..c62dfd0f4d --- /dev/null +++ b/git_remote_helpers/Makefile @@ -0,0 +1,35 @@ +# +# Makefile for the git_remote_helpers python support modules +# +pysetupfile:=setup.py + +# Shell quote (do not use $(call) to accommodate ancient setups); +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) + +ifndef PYTHON_PATH + PYTHON_PATH = /usr/bin/python +endif +ifndef prefix + prefix = $(HOME) +endif +ifndef V + QUIET = @ + QUIETSETUP = --quiet +endif + +PYLIBDIR=$(shell $(PYTHON_PATH) -c \ + "import sys; \ + print 'lib/python%i.%i/site-packages' % sys.version_info[:2]") + +all: $(pysetupfile) + $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build + +install: $(pysetupfile) + $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix) + +instlibdir: $(pysetupfile) + @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)" + +clean: + $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a + $(RM) *.pyo *.pyc diff --git a/git_remote_helpers/__init__.py b/git_remote_helpers/__init__.py new file mode 100644 index 0000000000..00f69cbeda --- /dev/null +++ b/git_remote_helpers/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +"""Support library package for git remote helpers. + +Git remote helpers are helper commands that interfaces with a non-git +repository to provide automatic import of non-git history into a Git +repository. + +This package provides the support library needed by these helpers.. +The following modules are included: + +- git.git - Interaction with Git repositories + +- util - General utility functionality use by the other modules in + this package, and also used directly by the helpers. +""" diff --git a/git_remote_helpers/git/__init__.py b/git_remote_helpers/git/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py new file mode 100644 index 0000000000..a383e6c08d --- /dev/null +++ b/git_remote_helpers/git/git.py @@ -0,0 +1,678 @@ +#!/usr/bin/env python + +"""Functionality for interacting with Git repositories. + +This module provides classes for interfacing with a Git repository. +""" + +import os +import re +import time +from binascii import hexlify +from cStringIO import StringIO +import unittest + +from git_remote_helpers.util import debug, error, die, start_command, run_command + + +def get_git_dir (): + """Return the path to the GIT_DIR for this repo.""" + args = ("git", "rev-parse", "--git-dir") + exit_code, output, errors = run_command(args) + if exit_code: + die("Failed to retrieve git dir") + assert not errors + return output.strip() + + +def parse_git_config (): + """Return a dict containing the parsed version of 'git config -l'.""" + exit_code, output, errors = run_command(("git", "config", "-z", "-l")) + if exit_code: + die("Failed to retrieve git configuration") + assert not errors + return dict([e.split('\n', 1) for e in output.split("\0") if e]) + + +def git_config_bool (value): + """Convert the given git config string value to True or False. + + Raise ValueError if the given string was not recognized as a + boolean value. + + """ + norm_value = str(value).strip().lower() + if norm_value in ("true", "1", "yes", "on", ""): + return True + if norm_value in ("false", "0", "no", "off", "none"): + return False + raise ValueError("Failed to parse '%s' into a boolean value" % (value)) + + +def valid_git_ref (ref_name): + """Return True iff the given ref name is a valid git ref name.""" + # The following is a reimplementation of the git check-ref-format + # command. The rules were derived from the git check-ref-format(1) + # manual page. This code should be replaced by a call to + # check_ref_format() in the git library, when such is available. + if ref_name.endswith('/') or \ + ref_name.startswith('.') or \ + ref_name.count('/.') or \ + ref_name.count('..') or \ + ref_name.endswith('.lock'): + return False + for c in ref_name: + if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[": + return False + return True + + +class GitObjectFetcher(object): + + """Provide parsed access to 'git cat-file --batch'. + + This provides a read-only interface to the Git object database. + + """ + + def __init__ (self): + """Initiate a 'git cat-file --batch' session.""" + self.queue = [] # List of object names to be submitted + self.in_transit = None # Object name currently in transit + + # 'git cat-file --batch' produces binary output which is likely + # to be corrupted by the default "rU"-mode pipe opened by + # start_command. (Mode == "rU" does universal new-line + # conversion, which mangles carriage returns.) Therefore, we + # open an explicitly binary-safe pipe for transferring the + # output from 'git cat-file --batch'. + pipe_r_fd, pipe_w_fd = os.pipe() + pipe_r = os.fdopen(pipe_r_fd, "rb") + pipe_w = os.fdopen(pipe_w_fd, "wb") + self.proc = start_command(("git", "cat-file", "--batch"), + stdout = pipe_w) + self.f = pipe_r + + def __del__ (self): + """Verify completed communication with 'git cat-file --batch'.""" + assert not self.queue + assert self.in_transit is None + self.proc.stdin.close() + assert self.proc.wait() == 0 # Zero exit code + assert self.f.read() == "" # No remaining output + + def _submit_next_object (self): + """Submit queue items to the 'git cat-file --batch' process. + + If there are items in the queue, and there is currently no item + currently in 'transit', then pop the first item off the queue, + and submit it. + + """ + if self.queue and self.in_transit is None: + self.in_transit = self.queue.pop(0) + print >> self.proc.stdin, self.in_transit[0] + + def push (self, obj, callback): + """Push the given object name onto the queue. + + The given callback function will at some point in the future + be called exactly once with the following arguments: + - self - this GitObjectFetcher instance + - obj - the object name provided to push() + - sha1 - the SHA1 of the object, if 'None' obj is missing + - t - the type of the object (tag/commit/tree/blob) + - size - the size of the object in bytes + - data - the object contents + + """ + self.queue.append((obj, callback)) + self._submit_next_object() # (Re)start queue processing + + def process_next_entry (self): + """Read the next entry off the queue and invoke callback.""" + obj, cb = self.in_transit + self.in_transit = None + header = self.f.readline() + if header == "%s missing\n" % (obj): + cb(self, obj, None, None, None, None) + return + sha1, t, size = header.split(" ") + assert len(sha1) == 40 + assert t in ("tag", "commit", "tree", "blob") + assert size.endswith("\n") + size = int(size.strip()) + data = self.f.read(size) + assert self.f.read(1) == "\n" + cb(self, obj, sha1, t, size, data) + self._submit_next_object() + + def process (self): + """Process the current queue until empty.""" + while self.in_transit is not None: + self.process_next_entry() + + # High-level convenience methods: + + def get_sha1 (self, objspec): + """Return the SHA1 of the object specified by 'objspec'. + + Return None if 'objspec' does not specify an existing object. + + """ + class _ObjHandler(object): + """Helper class for getting the returned SHA1.""" + def __init__ (self, parser): + self.parser = parser + self.sha1 = None + + def __call__ (self, parser, obj, sha1, t, size, data): + # FIXME: Many unused arguments. Could this be cheaper? + assert parser == self.parser + self.sha1 = sha1 + + handler = _ObjHandler(self) + self.push(objspec, handler) + self.process() + return handler.sha1 + + def open_obj (self, objspec): + """Return a file object wrapping the contents of a named object. + + The caller is responsible for calling .close() on the returned + file object. + + Raise KeyError if 'objspec' does not exist in the repo. + + """ + class _ObjHandler(object): + """Helper class for parsing the returned git object.""" + def __init__ (self, parser): + """Set up helper.""" + self.parser = parser + self.contents = StringIO() + self.err = None + + def __call__ (self, parser, obj, sha1, t, size, data): + """Git object callback (see GitObjectFetcher documentation).""" + assert parser == self.parser + if not sha1: # Missing object + self.err = "Missing object '%s'" % obj + else: + assert size == len(data) + self.contents.write(data) + + handler = _ObjHandler(self) + self.push(objspec, handler) + self.process() + if handler.err: + raise KeyError(handler.err) + handler.contents.seek(0) + return handler.contents + + def walk_tree (self, tree_objspec, callback, prefix = ""): + """Recursively walk the given Git tree object. + + Recursively walk all subtrees of the given tree object, and + invoke the given callback passing three arguments: + (path, mode, data) with the path, permission bits, and contents + of all the blobs found in the entire tree structure. + + """ + class _ObjHandler(object): + """Helper class for walking a git tree structure.""" + def __init__ (self, parser, cb, path, mode = None): + """Set up helper.""" + self.parser = parser + self.cb = cb + self.path = path + self.mode = mode + self.err = None + + def parse_tree (self, treedata): + """Parse tree object data, yield tree entries. + + Each tree entry is a 3-tuple (mode, sha1, path) + + self.path is prepended to all paths yielded + from this method. + + """ + while treedata: + mode = int(treedata[:6], 10) + # Turn 100xxx into xxx + if mode > 100000: + mode -= 100000 + assert treedata[6] == " " + i = treedata.find("\0", 7) + assert i > 0 + path = treedata[7:i] + sha1 = hexlify(treedata[i + 1: i + 21]) + yield (mode, sha1, self.path + path) + treedata = treedata[i + 21:] + + def __call__ (self, parser, obj, sha1, t, size, data): + """Git object callback (see GitObjectFetcher documentation).""" + assert parser == self.parser + if not sha1: # Missing object + self.err = "Missing object '%s'" % (obj) + return + assert size == len(data) + if t == "tree": + if self.path: + self.path += "/" + # Recurse into all blobs and subtrees + for m, s, p in self.parse_tree(data): + parser.push(s, + self.__class__(self.parser, self.cb, p, m)) + elif t == "blob": + self.cb(self.path, self.mode, data) + else: + raise ValueError("Unknown object type '%s'" % (t)) + + self.push(tree_objspec, _ObjHandler(self, callback, prefix)) + self.process() + + +class GitRefMap(object): + + """Map Git ref names to the Git object names they currently point to. + + Behaves like a dictionary of Git ref names -> Git object names. + + """ + + def __init__ (self, obj_fetcher): + """Create a new Git ref -> object map.""" + self.obj_fetcher = obj_fetcher + self._cache = {} # dict: refname -> objname + + def _load (self, ref): + """Retrieve the object currently bound to the given ref. + + The name of the object pointed to by the given ref is stored + into this mapping, and also returned. + + """ + if ref not in self._cache: + self._cache[ref] = self.obj_fetcher.get_sha1(ref) + return self._cache[ref] + + def __contains__ (self, refname): + """Return True if the given refname is present in this cache.""" + return bool(self._load(refname)) + + def __getitem__ (self, refname): + """Return the git object name pointed to by the given refname.""" + commit = self._load(refname) + if commit is None: + raise KeyError("Unknown ref '%s'" % (refname)) + return commit + + def get (self, refname, default = None): + """Return the git object name pointed to by the given refname.""" + commit = self._load(refname) + if commit is None: + return default + return commit + + +class GitFICommit(object): + + """Encapsulate the data in a Git fast-import commit command.""" + + SHA1RE = re.compile(r'^[0-9a-f]{40}$') + + @classmethod + def parse_mode (cls, mode): + """Verify the given git file mode, and return it as a string.""" + assert mode in (644, 755, 100644, 100755, 120000) + return "%i" % (mode) + + @classmethod + def parse_objname (cls, objname): + """Return the given object name (or mark number) as a string.""" + if isinstance(objname, int): # Object name is a mark number + assert objname > 0 + return ":%i" % (objname) + + # No existence check is done, only checks for valid format + assert cls.SHA1RE.match(objname) # Object name is valid SHA1 + return objname + + @classmethod + def quote_path (cls, path): + """Return a quoted version of the given path.""" + path = path.replace("\\", "\\\\") + path = path.replace("\n", "\\n") + path = path.replace('"', '\\"') + return '"%s"' % (path) + + @classmethod + def parse_path (cls, path): + """Verify that the given path is valid, and quote it, if needed.""" + assert not isinstance(path, int) # Cannot be a mark number + + # These checks verify the rules on the fast-import man page + assert not path.count("//") + assert not path.endswith("/") + assert not path.startswith("/") + assert not path.count("/./") + assert not path.count("/../") + assert not path.endswith("/.") + assert not path.endswith("/..") + assert not path.startswith("./") + assert not path.startswith("../") + + if path.count('"') + path.count('\n') + path.count('\\'): + return cls.quote_path(path) + return path + + def __init__ (self, name, email, timestamp, timezone, message): + """Create a new Git fast-import commit, with the given metadata.""" + self.name = name + self.email = email + self.timestamp = timestamp + self.timezone = timezone + self.message = message + self.pathops = [] # List of path operations in this commit + + def modify (self, mode, blobname, path): + """Add a file modification to this Git fast-import commit.""" + self.pathops.append(("M", + self.parse_mode(mode), + self.parse_objname(blobname), + self.parse_path(path))) + + def delete (self, path): + """Add a file deletion to this Git fast-import commit.""" + self.pathops.append(("D", self.parse_path(path))) + + def copy (self, path, newpath): + """Add a file copy to this Git fast-import commit.""" + self.pathops.append(("C", + self.parse_path(path), + self.parse_path(newpath))) + + def rename (self, path, newpath): + """Add a file rename to this Git fast-import commit.""" + self.pathops.append(("R", + self.parse_path(path), + self.parse_path(newpath))) + + def note (self, blobname, commit): + """Add a note object to this Git fast-import commit.""" + self.pathops.append(("N", + self.parse_objname(blobname), + self.parse_objname(commit))) + + def deleteall (self): + """Delete all files in this Git fast-import commit.""" + self.pathops.append("deleteall") + + +class TestGitFICommit(unittest.TestCase): + + """GitFICommit selftests.""" + + def test_basic (self): + """GitFICommit basic selftests.""" + + def expect_fail (method, data): + """Verify that the method(data) raises an AssertionError.""" + try: + method(data) + except AssertionError: + return + raise AssertionError("Failed test for invalid data '%s(%s)'" % + (method.__name__, repr(data))) + + def test_parse_mode (self): + """GitFICommit.parse_mode() selftests.""" + self.assertEqual(GitFICommit.parse_mode(644), "644") + self.assertEqual(GitFICommit.parse_mode(755), "755") + self.assertEqual(GitFICommit.parse_mode(100644), "100644") + self.assertEqual(GitFICommit.parse_mode(100755), "100755") + self.assertEqual(GitFICommit.parse_mode(120000), "120000") + self.assertRaises(AssertionError, GitFICommit.parse_mode, 0) + self.assertRaises(AssertionError, GitFICommit.parse_mode, 123) + self.assertRaises(AssertionError, GitFICommit.parse_mode, 600) + self.assertRaises(AssertionError, GitFICommit.parse_mode, "644") + self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc") + + def test_parse_objname (self): + """GitFICommit.parse_objname() selftests.""" + self.assertEqual(GitFICommit.parse_objname(1), ":1") + self.assertRaises(AssertionError, GitFICommit.parse_objname, 0) + self.assertRaises(AssertionError, GitFICommit.parse_objname, -1) + self.assertEqual(GitFICommit.parse_objname("0123456789" * 4), + "0123456789" * 4) + self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4), + "2468abcdef" * 4) + self.assertRaises(AssertionError, GitFICommit.parse_objname, + "abcdefghij" * 4) + + def test_parse_path (self): + """GitFICommit.parse_path() selftests.""" + self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar") + self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"), + '"path/with\\n and \\" in it"') + self.assertRaises(AssertionError, GitFICommit.parse_path, 1) + self.assertRaises(AssertionError, GitFICommit.parse_path, 0) + self.assertRaises(AssertionError, GitFICommit.parse_path, -1) + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo//bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/") + self.assertRaises(AssertionError, GitFICommit.parse_path, "/foo/bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/./bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/../bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/.") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/..") + self.assertRaises(AssertionError, GitFICommit.parse_path, "./foo/bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "../foo/bar") + + +class GitFastImport(object): + + """Encapsulate communication with git fast-import.""" + + def __init__ (self, f, obj_fetcher, last_mark = 0): + """Set up self to communicate with a fast-import process through f.""" + self.f = f # File object where fast-import stream is written + self.obj_fetcher = obj_fetcher # GitObjectFetcher instance + self.next_mark = last_mark + 1 # Next mark number + self.refs = set() # Keep track of the refnames we've seen + + def comment (self, s): + """Write the given comment in the fast-import stream.""" + assert "\n" not in s, "Malformed comment: '%s'" % (s) + self.f.write("# %s\n" % (s)) + + def commit (self, ref, commitdata): + """Make a commit on the given ref, with the given GitFICommit. + + Return the mark number identifying this commit. + + """ + self.f.write("""\ +commit %(ref)s +mark :%(mark)i +committer %(name)s <%(email)s> %(timestamp)i %(timezone)s +data %(msgLength)i +%(msg)s +""" % { + 'ref': ref, + 'mark': self.next_mark, + 'name': commitdata.name, + 'email': commitdata.email, + 'timestamp': commitdata.timestamp, + 'timezone': commitdata.timezone, + 'msgLength': len(commitdata.message), + 'msg': commitdata.message, +}) + + if ref not in self.refs: + self.refs.add(ref) + parent = ref + "^0" + if self.obj_fetcher.get_sha1(parent): + self.f.write("from %s\n" % (parent)) + + for op in commitdata.pathops: + self.f.write(" ".join(op)) + self.f.write("\n") + self.f.write("\n") + retval = self.next_mark + self.next_mark += 1 + return retval + + def blob (self, data): + """Import the given blob. + + Return the mark number identifying this blob. + + """ + self.f.write("blob\nmark :%i\ndata %i\n%s\n" % + (self.next_mark, len(data), data)) + retval = self.next_mark + self.next_mark += 1 + return retval + + def reset (self, ref, objname): + """Reset the given ref to point at the given Git object.""" + self.f.write("reset %s\nfrom %s\n\n" % + (ref, GitFICommit.parse_objname(objname))) + if ref not in self.refs: + self.refs.add(ref) + + +class GitNotes(object): + + """Encapsulate access to Git notes. + + Simulates a dictionary of object name (SHA1) -> Git note mappings. + + """ + + def __init__ (self, notes_ref, obj_fetcher): + """Create a new Git notes interface, bound to the given notes ref.""" + self.notes_ref = notes_ref + self.obj_fetcher = obj_fetcher # Used to get objects from repo + self.imports = [] # list: (objname, note data blob name) tuples + + def __del__ (self): + """Verify that self.commit_notes() was called before destruction.""" + if self.imports: + error("Missing call to self.commit_notes().") + error("%i notes are not committed!", len(self.imports)) + + def _load (self, objname): + """Return the note data associated with the given git object. + + The note data is returned in string form. If no note is found + for the given object, None is returned. + + """ + try: + f = self.obj_fetcher.open_obj("%s:%s" % (self.notes_ref, objname)) + ret = f.read() + f.close() + except KeyError: + ret = None + return ret + + def __getitem__ (self, objname): + """Return the note contents associated with the given object. + + Raise KeyError if given object has no associated note. + + """ + blobdata = self._load(objname) + if blobdata is None: + raise KeyError("Object '%s' has no note" % (objname)) + return blobdata + + def get (self, objname, default = None): + """Return the note contents associated with the given object. + + Return given default if given object has no associated note. + + """ + blobdata = self._load(objname) + if blobdata is None: + return default + return blobdata + + def import_note (self, objname, data, gfi): + """Tell git fast-import to store data as a note for objname. + + This method uses the given GitFastImport object to create a + blob containing the given note data. Also an entry mapping the + given object name to the created blob is stored until + commit_notes() is called. + + Note that this method only works if it is later followed by a + call to self.commit_notes() (which produces the note commit + that refers to the blob produced here). + + """ + if not data.endswith("\n"): + data += "\n" + gfi.comment("Importing note for object %s" % (objname)) + mark = gfi.blob(data) + self.imports.append((objname, mark)) + + def commit_notes (self, gfi, author, message): + """Produce a git fast-import note commit for the imported notes. + + This method uses the given GitFastImport object to create a + commit on the notes ref, introducing the notes previously + submitted to import_note(). + + """ + if not self.imports: + return + commitdata = GitFICommit(author[0], author[1], + time.time(), "0000", message) + for objname, blobname in self.imports: + assert isinstance(objname, int) and objname > 0 + assert isinstance(blobname, int) and blobname > 0 + commitdata.note(blobname, objname) + gfi.commit(self.notes_ref, commitdata) + self.imports = [] + + +class GitCachedNotes(GitNotes): + + """Encapsulate access to Git notes (cached version). + + Only use this class if no caching is done at a higher level. + + Simulates a dictionary of object name (SHA1) -> Git note mappings. + + """ + + def __init__ (self, notes_ref, obj_fetcher): + """Set up a caching wrapper around GitNotes.""" + GitNotes.__init__(self, notes_ref, obj_fetcher) + self._cache = {} # Cache: object name -> note data + + def __del__ (self): + """Verify that GitNotes' destructor is called.""" + GitNotes.__del__(self) + + def _load (self, objname): + """Extend GitNotes._load() with a local objname -> note cache.""" + if objname not in self._cache: + self._cache[objname] = GitNotes._load(self, objname) + return self._cache[objname] + + def import_note (self, objname, data, gfi): + """Extend GitNotes.import_note() with a local objname -> note cache.""" + if not data.endswith("\n"): + data += "\n" + assert objname not in self._cache + self._cache[objname] = data + GitNotes.import_note(self, objname, data, gfi) + + +if __name__ == '__main__': + unittest.main() diff --git a/git_remote_helpers/setup.py b/git_remote_helpers/setup.py new file mode 100644 index 0000000000..4d434b65cb --- /dev/null +++ b/git_remote_helpers/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +"""Distutils build/install script for the git_remote_helpers package.""" + +from distutils.core import setup + +setup( + name = 'git_remote_helpers', + version = '0.1.0', + description = 'Git remote helper program for non-git repositories', + license = 'GPLv2', + author = 'The Git Community', + author_email = 'git@vger.kernel.org', + url = 'http://www.git-scm.com/', + package_dir = {'git_remote_helpers': ''}, + packages = ['git_remote_helpers', 'git_remote_helpers.git'], +) diff --git a/git_remote_helpers/util.py b/git_remote_helpers/util.py new file mode 100644 index 0000000000..dce83e6066 --- /dev/null +++ b/git_remote_helpers/util.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python + +"""Misc. useful functionality used by the rest of this package. + +This module provides common functionality used by the other modules in +this package. + +""" + +import sys +import os +import subprocess + + +# Whether or not to show debug messages +DEBUG = False + +def notify(msg, *args): + """Print a message to stderr.""" + print >> sys.stderr, msg % args + +def debug (msg, *args): + """Print a debug message to stderr when DEBUG is enabled.""" + if DEBUG: + print >> sys.stderr, msg % args + +def error (msg, *args): + """Print an error message to stderr.""" + print >> sys.stderr, "ERROR:", msg % args + +def warn(msg, *args): + """Print a warning message to stderr.""" + print >> sys.stderr, "warning:", msg % args + +def die (msg, *args): + """Print as error message to stderr and exit the program.""" + error(msg, *args) + sys.exit(1) + + +class ProgressIndicator(object): + + """Simple progress indicator. + + Displayed as a spinning character by default, but can be customized + by passing custom messages that overrides the spinning character. + + """ + + States = ("|", "/", "-", "\\") + + def __init__ (self, prefix = "", f = sys.stdout): + """Create a new ProgressIndicator, bound to the given file object.""" + self.n = 0 # Simple progress counter + self.f = f # Progress is written to this file object + self.prev_len = 0 # Length of previous msg (to be overwritten) + self.prefix = prefix # Prefix prepended to each progress message + self.prefix_lens = [] # Stack of prefix string lengths + + def pushprefix (self, prefix): + """Append the given prefix onto the prefix stack.""" + self.prefix_lens.append(len(self.prefix)) + self.prefix += prefix + + def popprefix (self): + """Remove the last prefix from the prefix stack.""" + prev_len = self.prefix_lens.pop() + self.prefix = self.prefix[:prev_len] + + def __call__ (self, msg = None, lf = False): + """Indicate progress, possibly with a custom message.""" + if msg is None: + msg = self.States[self.n % len(self.States)] + msg = self.prefix + msg + print >> self.f, "\r%-*s" % (self.prev_len, msg), + self.prev_len = len(msg.expandtabs()) + if lf: + print >> self.f + self.prev_len = 0 + self.n += 1 + + def finish (self, msg = "done", noprefix = False): + """Finalize progress indication with the given message.""" + if noprefix: + self.prefix = "" + self(msg, True) + + +def start_command (args, cwd = None, shell = False, add_env = None, + stdin = subprocess.PIPE, stdout = subprocess.PIPE, + stderr = subprocess.PIPE): + """Start the given command, and return a subprocess object. + + This provides a simpler interface to the subprocess module. + + """ + env = None + if add_env is not None: + env = os.environ.copy() + env.update(add_env) + return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout, + stderr = stderr, cwd = cwd, shell = shell, + env = env, universal_newlines = True) + + +def run_command (args, cwd = None, shell = False, add_env = None, + flag_error = True): + """Run the given command to completion, and return its results. + + This provides a simpler interface to the subprocess module. + + The results are formatted as a 3-tuple: (exit_code, output, errors) + + If flag_error is enabled, Error messages will be produced if the + subprocess terminated with a non-zero exit code and/or stderr + output. + + The other arguments are passed on to start_command(). + + """ + process = start_command(args, cwd, shell, add_env) + (output, errors) = process.communicate() + exit_code = process.returncode + if flag_error and errors: + error("'%s' returned errors:\n---\n%s---", " ".join(args), errors) + if flag_error and exit_code: + error("'%s' returned exit code %i", " ".join(args), exit_code) + return (exit_code, output, errors) + + +def file_reader_method (missing_ok = False): + """Decorator for simplifying reading of files. + + If missing_ok is True, a failure to open a file for reading will + not raise the usual IOError, but instead the wrapped method will be + called with f == None. The method must in this case properly + handle f == None. + + """ + def _wrap (method): + """Teach given method to handle both filenames and file objects. + + The given method must take a file object as its second argument + (the first argument being 'self', of course). This decorator + will take a filename given as the second argument and promote + it to a file object. + + """ + def _wrapped_method (self, filename, *args, **kwargs): + if isinstance(filename, file): + f = filename + else: + try: + f = open(filename, 'r') + except IOError: + if missing_ok: + f = None + else: + raise + try: + return method(self, f, *args, **kwargs) + finally: + if not isinstance(filename, file) and f: + f.close() + return _wrapped_method + return _wrap + + +def file_writer_method (method): + """Decorator for simplifying writing of files. + + Enables the given method to handle both filenames and file objects. + + The given method must take a file object as its second argument + (the first argument being 'self', of course). This decorator will + take a filename given as the second argument and promote it to a + file object. + + """ + def _new_method (self, filename, *args, **kwargs): + if isinstance(filename, file): + f = filename + else: + # Make sure the containing directory exists + parent_dir = os.path.dirname(filename) + if not os.path.isdir(parent_dir): + os.makedirs(parent_dir) + f = open(filename, 'w') + try: + return method(self, f, *args, **kwargs) + finally: + if not isinstance(filename, file): + f.close() + return _new_method diff --git a/t/test-lib.sh b/t/test-lib.sh index 0b991dbd5d..6ed5b28be5 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -638,6 +638,15 @@ test -d ../templates/blt || { error "You haven't built things yet, have you?" } +if test -z "$GIT_TEST_INSTALLED" +then + GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib" + export GITPYTHONLIB + test -d ../git_remote_helpers/build || { + error "You haven't built git_remote_helpers yet, have you?" + } +fi + if ! test -x ../test-chmtime; then echo >&2 'You need to build test-chmtime:' echo >&2 'Run "make test-chmtime" in the source (toplevel) directory' From bbbe508d771f6a4c65fea43343597ecfa1eb540e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 25 Nov 2009 17:46:16 -0500 Subject: [PATCH 051/384] tests: rename duplicate t1009 We should avoid duplicate test numbers, since things like GIT_SKIP_TESTS consider something like t1009.5 to be unambiguous. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- ...tree-sparse-checkout.sh => t1011-read-tree-sparse-checkout.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename t/{t1009-read-tree-sparse-checkout.sh => t1011-read-tree-sparse-checkout.sh} (100%) diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh similarity index 100% rename from t/t1009-read-tree-sparse-checkout.sh rename to t/t1011-read-tree-sparse-checkout.sh From 482a6c106132bea454bf839f458c014f84ddbd99 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Thu, 26 Nov 2009 16:24:38 +0100 Subject: [PATCH 052/384] status -s: respect the status.relativePaths option Otherwise, 'status' and 'status -s' in a subdir would produce different names. This change is all the more important because status.relativePaths is on by default. Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 4 ++-- builtin-commit.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 58d35fb3c2..b3dfa42cc0 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -114,8 +114,8 @@ compatibility) and `color.status.` configuration variables to colorize its output. If the config variable `status.relativePaths` is set to false, then all -paths shown in the long format are relative to the repository root, not -to the current directory. +paths shown are relative to the repository root, not to the current +directory. If `status.submodulesummary` is set to a non zero number or true (identical to -1 or an unlimited number), the submodule summary will be enabled for diff --git a/builtin-commit.c b/builtin-commit.c index f2fd0a4580..f49b598cbd 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -1059,6 +1059,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) switch (status_format) { case STATUS_FORMAT_SHORT: + if (s.relative_paths) + s.prefix = prefix; short_print(&s, null_termination); break; case STATUS_FORMAT_PORCELAIN: From 14ed05ddd6a8cdd3cb792e2c991207d1640943d1 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Fri, 27 Nov 2009 22:29:30 +0100 Subject: [PATCH 053/384] t7508-status.sh: Add tests for status -s The new short status has been completely untested so far. Introduce tests by duplicating all tests which are present for the long format. Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano --- t/t7508-status.sh | 166 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 1173dbb36e..99a74bda6f 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -68,6 +68,24 @@ test_expect_success 'status (2)' ' ' +cat > expect << \EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF + +test_expect_success 'status -s (2)' ' + + git status -s > output && + test_cmp expect output + +' + cat >expect <expect << EOF + M dir1/modified +A dir2/added +EOF +test_expect_success 'status -s -uno' ' + git config --unset status.showuntrackedfiles + git status -s -uno >output && + test_cmp expect output +' + +test_expect_success 'status -s (status.showUntrackedFiles no)' ' + git config status.showuntrackedfiles no + git status -s >output && + test_cmp expect output +' + cat >expect <expect <output && + test_cmp expect output +' + +test_expect_success 'status -s (status.showUntrackedFiles normal)' ' + git config status.showuntrackedfiles normal + git status -s >output && + test_cmp expect output +' + cat >expect <expect <output && + test_cmp expect output +' +test_expect_success 'status -s (status.showUntrackedFiles all)' ' + git config status.showuntrackedfiles all + git status -s >output && + rm -rf dir3 && + git config --unset status.showuntrackedfiles && + test_cmp expect output +' + cat > expect << \EOF # On branch master # Changes to be committed: @@ -200,6 +280,23 @@ test_expect_success 'status with relative paths' ' ' +cat > expect << \EOF + M modified +A ../dir2/added +?? untracked +?? ../dir2/modified +?? ../dir2/untracked +?? ../expect +?? ../output +?? ../untracked +EOF +test_expect_success 'status -s with relative paths' ' + + (cd dir1 && git status -s) > output && + test_cmp expect output + +' + cat > expect << \EOF # On branch master # Changes to be committed: @@ -232,6 +329,24 @@ test_expect_success 'status without relative paths' ' ' +cat > expect << \EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF + +test_expect_success 'status -s without relative paths' ' + + (cd dir1 && git status -s) > output && + test_cmp expect output + +' + cat <expect # On branch master # Changes to be committed: @@ -298,6 +413,28 @@ test_expect_success 'status --untracked-files=all does not show submodule' ' test_cmp expect output ' +cat >expect <output && + test_cmp expect output +' + +# we expect the same as the previous test +test_expect_success 'status -s --untracked-files=all does not show submodule' ' + git status -s --untracked-files=all >output && + test_cmp expect output +' + head=$(cd sm && git rev-parse --short=7 --verify HEAD) cat >expect <expect <output && + test_cmp expect output +' cat >expect <expect <output && + test_cmp expect output +' + cat >expect < Date: Fri, 27 Nov 2009 23:42:26 +0800 Subject: [PATCH 054/384] http: maintain curl sessions Allow curl sessions to be kept alive (ie. not ended with curl_easy_cleanup()) even after the request is completed, the number of which is determined by the configuration setting http.minSessions. Add a count for curl sessions, and update it, across slots, when starting and ending curl sessions. Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- Documentation/config.txt | 6 ++++++ http.c | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index a8e0876a2a..b77d66da25 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1132,6 +1132,12 @@ http.maxRequests:: How many HTTP requests to launch in parallel. Can be overridden by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5. +http.minSessions:: + The number of curl sessions (counted across slots) to be kept across + requests. They will not be ended with curl_easy_cleanup() until + http_cleanup() is invoked. If USE_CURL_MULTI is not defined, this + value will be capped at 1. Defaults to 1. + http.postBuffer:: Maximum size in bytes of the buffer used by smart HTTP transports when POSTing data to the remote system. diff --git a/http.c b/http.c index ed6414a2aa..fb0a97b3f9 100644 --- a/http.c +++ b/http.c @@ -7,6 +7,8 @@ int active_requests; int http_is_verbose; size_t http_post_buffer = 16 * LARGE_PACKET_MAX; +static int min_curl_sessions = 1; +static int curl_session_count; #ifdef USE_CURL_MULTI static int max_requests = -1; static CURLM *curlm; @@ -152,6 +154,14 @@ static int http_options(const char *var, const char *value, void *cb) ssl_cert_password_required = 1; return 0; } + if (!strcmp("http.minsessions", var)) { + min_curl_sessions = git_config_int(var, value); +#ifndef USE_CURL_MULTI + if (min_curl_sessions > 1) + min_curl_sessions = 1; +#endif + return 0; + } #ifdef USE_CURL_MULTI if (!strcmp("http.maxrequests", var)) { max_requests = git_config_int(var, value); @@ -372,6 +382,7 @@ void http_init(struct remote *remote) if (curl_ssl_verify == -1) curl_ssl_verify = 1; + curl_session_count = 0; #ifdef USE_CURL_MULTI if (max_requests < 1) max_requests = DEFAULT_MAX_REQUESTS; @@ -480,6 +491,7 @@ struct active_request_slot *get_active_slot(void) #else slot->curl = curl_easy_duphandle(curl_default); #endif + curl_session_count++; } active_requests++; @@ -558,9 +570,11 @@ void fill_active_slots(void) } while (slot != NULL) { - if (!slot->in_use && slot->curl != NULL) { + if (!slot->in_use && slot->curl != NULL + && curl_session_count > min_curl_sessions) { curl_easy_cleanup(slot->curl); slot->curl = NULL; + curl_session_count--; } slot = slot->next; } @@ -633,12 +647,13 @@ static void closedown_active_slot(struct active_request_slot *slot) void release_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); - if (slot->curl) { + if (slot->curl && curl_session_count > min_curl_sessions) { #ifdef USE_CURL_MULTI curl_multi_remove_handle(curlm, slot->curl); #endif curl_easy_cleanup(slot->curl); slot->curl = NULL; + curl_session_count--; } #ifdef USE_CURL_MULTI fill_active_slots(); From b8ac923010484908d8426cb8ded5ad7e8c21a7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Fri, 27 Nov 2009 23:43:08 +0800 Subject: [PATCH 055/384] Add an option for using any HTTP authentication scheme, not only basic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds the configuration option http.authAny (overridable with the environment variable GIT_HTTP_AUTH_ANY), for instructing curl to allow any HTTP authentication scheme, not only basic (which sends the password in plaintext). When this is enabled, curl has to do double requests most of the time, in order to discover which HTTP authentication method to use, which lowers the performance slightly. Therefore this isn't enabled by default. One example of another authentication scheme to use is digest, which doesn't send the password in plaintext, but uses a challenge-response mechanism instead. Using digest authentication in practice requires at least curl 7.18.1, due to bugs in the digest handling in earlier versions of curl. Signed-off-by: Martin Storsjö Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- Documentation/config.txt | 7 +++++++ http.c | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index b77d66da25..a54ede350f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1158,6 +1158,13 @@ http.noEPSV:: support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV' environment variable. Default is false (curl will use EPSV). +http.authAny:: + Allow any HTTP authentication method, not only basic. Enabling + this lowers the performance slightly, by having to do requests + without any authentication to discover the authentication method + to use. Can be overridden by the 'GIT_HTTP_AUTH_ANY' + environment variable. Default is false. + i18n.commitEncoding:: Character encoding the commit messages are stored in; git itself does not care per se, but this information is necessary e.g. when diff --git a/http.c b/http.c index fb0a97b3f9..aeb69b3f29 100644 --- a/http.c +++ b/http.c @@ -7,6 +7,10 @@ int active_requests; int http_is_verbose; size_t http_post_buffer = 16 * LARGE_PACKET_MAX; +#if LIBCURL_VERSION_NUM >= 0x070a06 +#define LIBCURL_CAN_HANDLE_AUTH_ANY +#endif + static int min_curl_sessions = 1; static int curl_session_count; #ifdef USE_CURL_MULTI @@ -36,6 +40,9 @@ static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; static const char *curl_http_proxy; static char *user_name, *user_pass; +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY +static int curl_http_auth_any = 0; +#endif #if LIBCURL_VERSION_NUM >= 0x071700 /* Use CURLOPT_KEYPASSWD as is */ @@ -190,6 +197,12 @@ static int http_options(const char *var, const char *value, void *cb) http_post_buffer = LARGE_PACKET_MAX; return 0; } +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY + if (!strcmp("http.authany", var)) { + curl_http_auth_any = git_config_bool(var, value); + return 0; + } +#endif /* Fall back on the default ones */ return git_default_config(var, value, cb); @@ -240,6 +253,10 @@ static CURL *get_curl_handle(void) #if LIBCURL_VERSION_NUM >= 0x070907 curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); #endif +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY + if (curl_http_auth_any) + curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY); +#endif init_curl_http_auth(result); @@ -391,6 +408,11 @@ void http_init(struct remote *remote) if (getenv("GIT_CURL_FTP_NO_EPSV")) curl_ftp_no_epsv = 1; +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY + if (getenv("GIT_HTTP_AUTH_ANY")) + curl_http_auth_any = 1; +#endif + if (remote && remote->url && remote->url[0]) { http_auth_init(remote->url[0]); if (!ssl_cert_password_required && From 73eb40eeaaebc5ebae283c06286b96b4aea00143 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Jun 2008 00:17:27 -0700 Subject: [PATCH 056/384] git-merge-file --ours, --theirs Sometimes people want their conflicting merges autoresolved by favouring upstream changes. The standard answer they are given is to run "git diff --name-only | xargs git checkout MERGE_HEAD --" in such a case. This is to accept automerge results for the paths that are fully resolved automatically, while taking their version of the file in full for paths that have conflicts. This is problematic on two counts. One is that this is not exactly what these people want. It discards all changes they did on their branch for any paths that conflicted. They usually want to salvage as much automerge result as possible in a conflicted file, and want to take the upstream change only in the conflicted part. This patch teaches two new modes of operation to the lowest-lever merge machinery, xdl_merge(). Instead of leaving the conflicted lines from both sides enclosed in <<<, ===, and >>> markers, the conflicts are resolved favouring our side or their side of changes. A larger problem is that this tends to encourage a bad workflow by allowing people to record such a mixed up half-merged result as a full commit without auditing. This commit does not tackle this issue at all. In git, we usually give long enough rope to users with strange wishes as long as the risky features are not enabled by default, and this is such a risky feature. Signed-off-by: Avery Pennarun Signed-off-by: Junio C Hamano --- Documentation/git-merge-file.txt | 12 ++++++++++-- builtin-merge-file.c | 15 ++++++++++----- xdiff/xdiff.h | 8 +++++++- xdiff/xmerge.c | 9 +++++++-- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt index 303537357b..b9d22764a6 100644 --- a/Documentation/git-merge-file.txt +++ b/Documentation/git-merge-file.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git merge-file' [-L [-L [-L ]]] - [-p|--stdout] [-q|--quiet] + [--ours|--theirs] [-p|--stdout] [-q|--quiet] + DESCRIPTION @@ -34,7 +35,9 @@ normally outputs a warning and brackets the conflict with lines containing >>>>>>> B If there are conflicts, the user should edit the result and delete one of -the alternatives. +the alternatives. When `--ours` or `--theirs` option is in effect, however, +these conflicts are resolved favouring lines from `` or +lines from `` respectively. The exit value of this program is negative on error, and the number of conflicts otherwise. If the merge was clean, the exit value is 0. @@ -62,6 +65,11 @@ OPTIONS -q:: Quiet; do not warn about conflicts. +--ours:: +--theirs:: + Instead of leaving conflicts in the file, resolve conflicts + favouring our (or their) side of the lines. + EXAMPLES -------- diff --git a/builtin-merge-file.c b/builtin-merge-file.c index afd2ea7a73..1efc4e09bc 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -27,13 +27,18 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) mmbuffer_t result = {NULL, 0}; xpparam_t xpp = {XDF_NEED_MINIMAL}; int ret = 0, i = 0, to_stdout = 0; - int merge_level = XDL_MERGE_ZEALOUS_ALNUM; - int merge_style = 0, quiet = 0; + int level = XDL_MERGE_ZEALOUS_ALNUM; + int style = 0, quiet = 0; + int favor = 0; int nongit; struct option options[] = { OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), - OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3), + OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3), + OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version", + XDL_MERGE_FAVOR_OURS), + OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version", + XDL_MERGE_FAVOR_THEIRS), OPT__QUIET(&quiet), OPT_CALLBACK('L', NULL, names, "name", "set labels for file1/orig_file/file2", &label_cb), @@ -45,7 +50,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) /* Read the configuration file */ git_config(git_xmerge_config, NULL); if (0 <= git_xmerge_style) - merge_style = git_xmerge_style; + style = git_xmerge_style; } argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0); @@ -68,7 +73,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) } ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xpp, merge_level | merge_style, &result); + &xpp, XDL_MERGE_FLAGS(level, style, favor), &result); for (i = 0; i < 3; i++) free(mmfs[i].ptr); diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 4da052a3ff..8a0efed313 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -58,6 +58,12 @@ extern "C" { #define XDL_MERGE_ZEALOUS_ALNUM 3 #define XDL_MERGE_LEVEL_MASK 0x0f +/* merge favor modes */ +#define XDL_MERGE_FAVOR_OURS 1 +#define XDL_MERGE_FAVOR_THEIRS 2 +#define XDL_MERGE_FAVOR(flags) (((flags)>>4) & 3) +#define XDL_MERGE_FLAGS(level, style, favor) ((level)|(style)|((favor)<<4)) + /* merge output styles */ #define XDL_MERGE_DIFF3 0x8000 #define XDL_MERGE_STYLE_MASK 0x8000 @@ -110,7 +116,7 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, mmfile_t *mf2, const char *name2, - xpparam_t const *xpp, int level, mmbuffer_t *result); + xpparam_t const *xpp, int flags, mmbuffer_t *result); #ifdef __cplusplus } diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 1cb65a9516..b2ddc75376 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -214,11 +214,15 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, + int favor, xdmerge_t *m, char *dest, int style) { int size, i; for (size = i = 0; m; m = m->next) { + if (favor && !m->mode) + m->mode = favor; + if (m->mode == 0) size = fill_conflict_hunk(xe1, name1, xe2, name2, size, i, style, m, dest); @@ -391,6 +395,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, int i0, i1, i2, chg0, chg1, chg2; int level = flags & XDL_MERGE_LEVEL_MASK; int style = flags & XDL_MERGE_STYLE_MASK; + int favor = XDL_MERGE_FAVOR(flags); if (style == XDL_MERGE_DIFF3) { /* @@ -523,14 +528,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, /* output */ if (result) { int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, - changes, NULL, style); + favor, changes, NULL, style); result->ptr = xdl_malloc(size); if (!result->ptr) { xdl_cleanup_merge(changes); return -1; } result->size = size; - xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, + xdl_fill_merge_buffer(xe1, name1, xe2, name2, favor, changes, result->ptr, style); } return xdl_cleanup_merge(changes); From 6c81a9908206799ccbc9a17bde17f1d766a8eef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 1 Dec 2009 12:33:39 +0200 Subject: [PATCH 057/384] Allow curl to rewind the RPC read buffer When using multi-pass authentication methods, the curl library may need to rewind the read buffers used for providing data to HTTP POST, if data has been output before a 401 error is received. This is needed only when the first request (when the multi-pass authentication method isn't initialized and hasn't received its challenge yet) for a certain curl session is a chunked HTTP POST. As long as the current rpc read buffer is the first one, we're able to rewind without need for additional buffering. The curl library currently starts sending data without waiting for a response to the Expect: 100-continue header, due to a bug in curl that exists up to curl version 7.19.7. If the HTTP server doesn't handle Expect: 100-continue headers properly (e.g. Lighttpd), the library has to start sending data without knowing if the request will be successfully authenticated. In this case, this rewinding solution is not sufficient - the whole request will be sent before the 401 error is received. Signed-off-by: Martin Storsjo Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- remote-curl.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/remote-curl.c b/remote-curl.c index a331bae6c8..28b2a316d3 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -290,6 +290,7 @@ struct rpc_state { int out; struct strbuf result; unsigned gzip_request : 1; + unsigned initial_buffer : 1; }; static size_t rpc_out(void *ptr, size_t eltsize, @@ -300,6 +301,7 @@ static size_t rpc_out(void *ptr, size_t eltsize, size_t avail = rpc->len - rpc->pos; if (!avail) { + rpc->initial_buffer = 0; avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); if (!avail) return 0; @@ -314,6 +316,29 @@ static size_t rpc_out(void *ptr, size_t eltsize, return avail; } +#ifndef NO_CURL_IOCTL +curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) +{ + struct rpc_state *rpc = clientp; + + switch (cmd) { + case CURLIOCMD_NOP: + return CURLIOE_OK; + + case CURLIOCMD_RESTARTREAD: + if (rpc->initial_buffer) { + rpc->pos = 0; + return CURLIOE_OK; + } + fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n"); + return CURLIOE_FAILRESTART; + + default: + return CURLIOE_UNKNOWNCMD; + } +} +#endif + static size_t rpc_in(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) { @@ -370,8 +395,13 @@ static int post_rpc(struct rpc_state *rpc) */ headers = curl_slist_append(headers, "Expect: 100-continue"); headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); + rpc->initial_buffer = 1; curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc); +#endif if (options.verbosity > 1) { fprintf(stderr, "POST %s (chunked)\n", rpc->service_name); fflush(stderr); From 5d2dcc423ec689ff4a6bcb83b00d5b43657b0c88 Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Wed, 2 Dec 2009 23:28:40 +0200 Subject: [PATCH 058/384] General --quiet improvements 'git reset' is missing --quiet, and 'git gc' is not using OPT__QUIET. Let's fix that. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano --- Documentation/git-reset.txt | 1 + builtin-gc.c | 2 +- builtin-reset.c | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 2d27e405a3..9df6de2e7d 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -62,6 +62,7 @@ This means that `git reset -p` is the opposite of `git add -p` (see linkgit:git-add[1]). -q:: +--quiet:: Be quiet, only report errors. :: diff --git a/builtin-gc.c b/builtin-gc.c index 093517e390..c304638b78 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -180,12 +180,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) char buf[80]; struct option builtin_gc_options[] = { + OPT__QUIET(&quiet), { OPTION_STRING, 0, "prune", &prune_expire, "date", "prune unreferenced objects", PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), - OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"), OPT_END() }; diff --git a/builtin-reset.c b/builtin-reset.c index 73e60223db..25b38cee18 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -202,6 +202,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) struct commit *commit; char *reflog_action, msg[1024]; const struct option options[] = { + OPT__QUIET(&quiet), OPT_SET_INT(0, "mixed", &reset_type, "reset HEAD and index", MIXED), OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), @@ -209,8 +210,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", HARD), OPT_SET_INT(0, "merge", &reset_type, "reset HEAD, index and working tree", MERGE), - OPT_BOOLEAN('q', NULL, &quiet, - "disable showing new HEAD in hard reset and progress message"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; From ea925196f1bd3acaabaeaf384cc694337db97b2b Mon Sep 17 00:00:00 2001 From: Matthew Ogilvie Date: Wed, 2 Dec 2009 22:14:05 -0700 Subject: [PATCH 059/384] build dashless "bin-wrappers" directory similar to installed bindir The new bin-wrappers directory contains wrapper scripts for executables that will be installed into the standard bindir. It explicitly does not contain most dashed-commands. The scripts automatically set environment variables to run out of the source tree, not the installed directory. This will allow running the test suite without dashed commands in the PATH. It also provides a simplified way to test run custom built git executables without installing them first. bin-wrappers also contains wrappers for some test suite support executables, where the test suite will soon make use of them. Signed-off-by: Matthew Ogilvie Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 49 ++++++++++++++++++++++++++++++++++++------------- wrap-for-bin.sh | 15 +++++++++++++++ 3 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 wrap-for-bin.sh diff --git a/.gitignore b/.gitignore index ac02a580da..5d32289664 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /GIT-CFLAGS /GIT-GUI-VARS /GIT-VERSION-FILE +/bin-wrappers/ /git /git-add /git-add--interactive diff --git a/Makefile b/Makefile index 4dba10e7f0..b981364536 100644 --- a/Makefile +++ b/Makefile @@ -416,6 +416,15 @@ ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) # what 'all' will build but not install in gitexecdir OTHER_PROGRAMS = git$X +# what test wrappers are needed and 'install' will install, in bindir +BINDIR_PROGRAMS_NEED_X += git +BINDIR_PROGRAMS_NEED_X += git-upload-pack +BINDIR_PROGRAMS_NEED_X += git-receive-pack +BINDIR_PROGRAMS_NEED_X += git-upload-archive +BINDIR_PROGRAMS_NEED_X += git-shell + +BINDIR_PROGRAMS_NO_X += git-cvsserver + # Set paths to tools early so that they can be used for version tests. ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -1690,19 +1699,30 @@ endif ### Testing rules -TEST_PROGRAMS += test-chmtime$X -TEST_PROGRAMS += test-ctype$X -TEST_PROGRAMS += test-date$X -TEST_PROGRAMS += test-delta$X -TEST_PROGRAMS += test-dump-cache-tree$X -TEST_PROGRAMS += test-genrandom$X -TEST_PROGRAMS += test-match-trees$X -TEST_PROGRAMS += test-parse-options$X -TEST_PROGRAMS += test-path-utils$X -TEST_PROGRAMS += test-sha1$X -TEST_PROGRAMS += test-sigchain$X +TEST_PROGRAMS_NEED_X += test-chmtime +TEST_PROGRAMS_NEED_X += test-ctype +TEST_PROGRAMS_NEED_X += test-date +TEST_PROGRAMS_NEED_X += test-delta +TEST_PROGRAMS_NEED_X += test-dump-cache-tree +TEST_PROGRAMS_NEED_X += test-genrandom +TEST_PROGRAMS_NEED_X += test-match-trees +TEST_PROGRAMS_NEED_X += test-parse-options +TEST_PROGRAMS_NEED_X += test-path-utils +TEST_PROGRAMS_NEED_X += test-sha1 +TEST_PROGRAMS_NEED_X += test-sigchain -all:: $(TEST_PROGRAMS) +TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) + +test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) + +all:: $(TEST_PROGRAMS) $(test_bindir_programs) + +bin-wrappers/%: wrap-for-bin.sh + @mkdir -p bin-wrappers + $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@BUILD_DIR@@|$(shell pwd)|' \ + -e 's|@@PROG@@|$(@F)|' < $< > $@ && \ + chmod +x $@ # GNU make supports exporting all variables by "export" without parameters. # However, the environment gets quite big, and some programs have problems @@ -1763,11 +1783,13 @@ endif gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir)) export gitexec_instdir +install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X) + install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' - $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)' + $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install @@ -1878,6 +1900,7 @@ clean: $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) + $(RM) -r bin-wrappers $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh new file mode 100644 index 0000000000..c5075c9c61 --- /dev/null +++ b/wrap-for-bin.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# wrap-for-bin.sh: Template for git executable wrapper scripts +# to run test suite against sandbox, but with only bindir-installed +# executables in PATH. The Makefile copies this into various +# files in bin-wrappers, substituting +# @@BUILD_DIR@@ and @@PROG@@. + +GIT_EXEC_PATH='@@BUILD_DIR@@' +GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt' +GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib' +PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH" +export GIT_EXEC_PATH GIT_TEMPLATE_DIR GITPERLLIB PATH + +exec "${GIT_EXEC_PATH}/@@PROG@@" "$@" From e4597aae6590cbd6c868c0406ac5521221c4f465 Mon Sep 17 00:00:00 2001 From: Matthew Ogilvie Date: Wed, 2 Dec 2009 22:14:06 -0700 Subject: [PATCH 060/384] run test suite without dashed git-commands in PATH Only put bin-wrappers in the PATH (not GIT_EXEC_PATH), to emulate the default installed user environment, and ensure all the programs run correctly in such an environment. This is now the default, although it can be overridden with a --with-dashes test option when running tests. Signed-off-by: Matthew Ogilvie Signed-off-by: Junio C Hamano --- t/README | 9 +++++++++ t/test-lib.sh | 33 +++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/t/README b/t/README index 4e1d7dd183..dcd3ebb5f2 100644 --- a/t/README +++ b/t/README @@ -75,6 +75,15 @@ appropriately before running "make". As the names depend on the tests' file names, it is safe to run the tests with this option in parallel. +--with-dashes:: + By default tests are run without dashed forms of + commands (like git-commit) in the PATH (it only uses + wrappers from ../bin-wrappers). Use this option to include + the build directory (..) in the PATH, which contains all + the dashed forms of commands. This option is currently + implied by other options like --valgrind and + GIT_TEST_INSTALLED. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various diff --git a/t/test-lib.sh b/t/test-lib.sh index ec3336aba5..85377c8fc3 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -105,6 +105,8 @@ do verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) quiet=t; shift ;; + --with-dashes) + with_dashes=t; shift ;; --no-color) color=; shift ;; --no-python) @@ -551,19 +553,8 @@ test_done () { # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in 'trash directory' subdirectory. TEST_DIRECTORY=$(pwd) -if test -z "$valgrind" +if test -n "$valgrind" then - if test -z "$GIT_TEST_INSTALLED" - then - PATH=$TEST_DIRECTORY/..:$PATH - GIT_EXEC_PATH=$TEST_DIRECTORY/.. - else - GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || - error "Cannot run git from $GIT_TEST_INSTALLED." - PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH - GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} - fi -else make_symlink () { test -h "$2" && test "$1" = "$(readlink "$2")" || { @@ -625,6 +616,24 @@ else PATH=$GIT_VALGRIND/bin:$PATH GIT_EXEC_PATH=$GIT_VALGRIND/bin export GIT_VALGRIND +elif test -n "$GIT_TEST_INSTALLED" ; then + GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || + error "Cannot run git from $GIT_TEST_INSTALLED." + PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH + GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} +else # normal case, use ../bin-wrappers only unless $with_dashes: + git_bin_dir="$TEST_DIRECTORY/../bin-wrappers" + if ! test -x "$git_bin_dir/git" ; then + if test -z "$with_dashes" ; then + say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH" + fi + with_dashes=t + fi + PATH="$git_bin_dir:$PATH" + GIT_EXEC_PATH=$TEST_DIRECTORY/.. + if test -n "$with_dashes" ; then + PATH="$TEST_DIRECTORY/..:$PATH" + fi fi GIT_TEMPLATE_DIR=$(pwd)/../templates/blt unset GIT_CONFIG From 904580122b5ac46a9a38e20b9cc032951d9e7982 Mon Sep 17 00:00:00 2001 From: Matthew Ogilvie Date: Wed, 2 Dec 2009 22:14:07 -0700 Subject: [PATCH 061/384] INSTALL: document a simpler way to run uninstalled builds The new scripts automatically saved in the bin-wrappers directory allow you to run a build when you have neither installed git nor tweaked environment variables. Mention this in INSTALL, along with the slight performance issue of doing so. This can be especially handy for manually testing network-invoked git (from ssh, web servers, or similar), but it is also handy with a plain command prompt. Signed-off-by: Matthew Ogilvie Signed-off-by: Junio C Hamano --- INSTALL | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/INSTALL b/INSTALL index be504c95e1..61086ab120 100644 --- a/INSTALL +++ b/INSTALL @@ -38,13 +38,17 @@ Issues of note: Interactive Tools package still can install "git", but you can build it with --disable-transition option to avoid this. - - You can use git after building but without installing if you - wanted to. Various git commands need to find other git - commands and scripts to do their work, so you would need to - arrange a few environment variables to tell them that their - friends will be found in your built source area instead of at - their standard installation area. Something like this works - for me: + - You can use git after building but without installing if you want + to test drive it. Simply run git found in bin-wrappers directory + in the build directory, or prepend that directory to your $PATH. + This however is less efficient than running an installed git, as + you always need an extra fork+exec to run any git subcommand. + + It is still possible to use git without installing by setting a few + environment variables, which was the way this was done + traditionally. But using git found in bin-wrappers directory in + the build directory is far simpler. As a historical reference, the + old way went like this: GIT_EXEC_PATH=`pwd` PATH=`pwd`:$PATH From 02b47cd77e4af40da95a74c90846965a2ea6999b Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 2 Dec 2009 23:16:18 +0100 Subject: [PATCH 062/384] builtin-commit: add --date option This is like --author: allow a user to specify a given date without using the GIT_AUTHOR_DATE environment variable. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 5 ++++- builtin-commit.c | 6 +++++- t/t7501-commit.sh | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index d227cec9ba..c37c21e4e3 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git commit' [-a | --interactive] [-s] [-v] [-u] [--amend] [--dry-run] [(-c | -C) ] [-F | -m ] [--reset-author] [--allow-empty] [--no-verify] [-e] [--author=] - [--cleanup=] [--] [[-i | -o ]...] + [--date=] [--cleanup=] [--] [[-i | -o ]...] DESCRIPTION ----------- @@ -85,6 +85,9 @@ OPTIONS an existing commit that matches the given string and its author name is used. +--date=:: + Override the author date used in the commit. + -m :: --message=:: Use the given as the commit message. diff --git a/builtin-commit.c b/builtin-commit.c index e93a647c59..9a1264aaf8 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -52,7 +52,7 @@ static char *edit_message, *use_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; -static char *untracked_files_arg; +static char *untracked_files_arg, *force_date; /* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing @@ -90,6 +90,7 @@ static struct option builtin_commit_options[] = { OPT_FILENAME('F', "file", &logfile, "read log from file"), OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), + OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"), OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), @@ -410,6 +411,9 @@ static void determine_author_info(void) email = xstrndup(lb + 2, rb - (lb + 2)); } + if (force_date) + date = force_date; + author_name = name; author_email = email; author_date = date; diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index a603f6d21a..a529701241 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -211,6 +211,21 @@ test_expect_success 'amend commit to fix author' ' ' +test_expect_success 'amend commit to fix date' ' + + test_tick && + newtick=$GIT_AUTHOR_DATE && + git reset --hard && + git cat-file -p HEAD | + sed -e "s/author.*/author $author $newtick/" \ + -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \ + expected && + git commit --amend --date="$newtick" && + git cat-file -p HEAD > current && + test_cmp expected current + +' + test_expect_success 'sign off (1)' ' echo 1 >positive && From 788070a261ecc3a37a7e0ed9301ecec4a333586d Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Thu, 3 Dec 2009 00:49:19 +0100 Subject: [PATCH 063/384] Document date formats accepted by parse_date() Signed-off-by: Junio C Hamano --- Documentation/date-formats.txt | 26 ++++++++++++++++++++++++++ Documentation/git-commit-tree.txt | 1 + Documentation/git-commit.txt | 2 ++ 3 files changed, 29 insertions(+) create mode 100644 Documentation/date-formats.txt diff --git a/Documentation/date-formats.txt b/Documentation/date-formats.txt new file mode 100644 index 0000000000..c000f08a9d --- /dev/null +++ b/Documentation/date-formats.txt @@ -0,0 +1,26 @@ +DATE FORMATS +------------ + +The GIT_AUTHOR_DATE, GIT_COMMITTER_DATE environment variables +ifdef::git-commit[] +and the `--date` option +endif::git-commit[] +support the following date formats: + +Git internal format:: + It is ` `, where `` is the number of seconds since the UNIX epoch. + `` is a positive or negative offset from UTC. + For example CET (which is 2 hours ahead UTC) is `+0200`. + +RFC 2822:: + The standard email format as described by RFC 2822, for example + `Thu, 07 Apr 2005 22:13:13 +0200`. + +ISO 8601:: + Time and date specified by the ISO 8601 standard, for example + `2005-04-07T22:13:13`. The parser accepts a space instead of the + `T` character as well. ++ +NOTE: In addition, the date part is accepted in the following formats: +`YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`. diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index b8834baced..4fec5d5e38 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -73,6 +73,7 @@ A commit comment is read from stdin. If a changelog entry is not provided via "<" redirection, 'git-commit-tree' will just wait for one to be entered and terminated with ^D. +include::date-formats.txt[] Diagnostics ----------- diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index c37c21e4e3..de3435d397 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -220,6 +220,8 @@ specified. these files are also staged for the next commit on top of what have been staged before. +:git-commit: 1 +include::date-formats.txt[] EXAMPLES -------- From 53970b92d9dc883669f3a9b79b5e39e73931b331 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 4 Dec 2009 07:31:44 +0800 Subject: [PATCH 064/384] builtin-push: don't access freed transport->url Move the failed push message to before transport_disconnect() so that it doesn't access transport->url after transport has been free()'d (in transport_disconnect()). Additionally, make the failed push message more accurate by moving it before transport_disconnect(), so that it doesn't report errors due to a failed disconnect. Signed-off-by: Tay Ray Chuan Acked-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-push.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin-push.c b/builtin-push.c index 9846c638a6..03be045bae 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -102,13 +102,14 @@ static int push_with_options(struct transport *transport, int flags) fprintf(stderr, "Pushing to %s\n", transport->url); err = transport_push(transport, refspec_nr, refspec, flags, &nonfastforward); + if (err != 0) + error("failed to push some refs to '%s'", transport->url); + err |= transport_disconnect(transport); if (!err) return 0; - error("failed to push some refs to '%s'", transport->url); - if (nonfastforward && advice_push_nonfastforward) { printf("To prevent you from losing history, non-fast-forward updates were rejected\n" "Merge the remote changes before pushing again. See the 'non-fast-forward'\n" From cb6020bb017405cc3e7f1faea6f30d4fd1b62e70 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Dec 2009 00:20:48 -0800 Subject: [PATCH 065/384] Teach --[no-]rerere-autoupdate option to merge, revert and friends Introduce a command line option to override rerere.autoupdate configuration variable to make it more useful. Signed-off-by: Junio C Hamano --- Documentation/git-merge.txt | 7 ++++++- builtin-commit.c | 2 +- builtin-merge.c | 4 +++- builtin-rerere.c | 21 +++++++++++++++------ builtin-revert.c | 4 +++- git-am.sh | 6 +++++- git-rebase.sh | 6 +++++- parse-options.c | 7 +++++++ parse-options.h | 3 +++ rerere.c | 8 +++++--- rerere.h | 10 ++++++++-- t/t4200-rerere.sh | 15 +++++++++++++++ 12 files changed, 76 insertions(+), 17 deletions(-) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index e886c2ef54..67470311e2 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s ]... - [-m ] ... + [--[no-]rerere-autoupdate] [-m ] ... 'git merge' HEAD ... DESCRIPTION @@ -33,6 +33,11 @@ include::merge-options.txt[] used to give a good default for automated 'git merge' invocations. +--rerere-autoupdate:: +--no-rerere-autoupdate:: + Allow the rerere mechanism to update the index with the + result of auto-conflict resolution if possible. + ...:: Other branch heads to merge into our branch. You need at least one . Specifying more than one diff --git a/builtin-commit.c b/builtin-commit.c index e93a647c59..72e0f0b563 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -1150,7 +1150,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "new_index file. Check that disk is not full or quota is\n" "not exceeded, and then \"git reset HEAD\" to recover."); - rerere(); + rerere(0); run_hook(get_index_file(), "post-commit", NULL); if (!quiet) print_summary(prefix, commit_sha1); diff --git a/builtin-merge.c b/builtin-merge.c index 56a1bb651f..c3faa6b9c3 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -52,6 +52,7 @@ static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; static const char *branch; static int verbosity; +static int allow_rerere_auto; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -170,6 +171,7 @@ static struct option builtin_merge_options[] = { "allow fast-forward (default)"), OPT_BOOLEAN(0, "ff-only", &fast_forward_only, "abort if fast-forward is not possible"), + OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), OPT_CALLBACK('m', "message", &merge_msg, "message", @@ -790,7 +792,7 @@ static int suggest_conflicts(void) } } fclose(fp); - rerere(); + rerere(allow_rerere_auto); printf("Automatic merge failed; " "fix conflicts and then commit the result.\n"); return 1; diff --git a/builtin-rerere.c b/builtin-rerere.c index 343d6cde48..7ec602cf55 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -101,15 +101,24 @@ static int diff_two(const char *file1, const char *label1, int cmd_rerere(int argc, const char **argv, const char *prefix) { struct string_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd; + int i, fd, flags = 0; + if (2 < argc) { + if (!strcmp(argv[1], "-h")) + usage(git_rerere_usage); + if (!strcmp(argv[1], "--rerere-autoupdate")) + flags = RERERE_AUTOUPDATE; + else if (!strcmp(argv[1], "--no-rerere-autoupdate")) + flags = RERERE_NOAUTOUPDATE; + if (flags) { + argc--; + argv++; + } + } if (argc < 2) - return rerere(); + return rerere(flags); - if (!strcmp(argv[1], "-h")) - usage(git_rerere_usage); - - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; diff --git a/builtin-revert.c b/builtin-revert.c index 151aa6a981..857ca2eefa 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -38,6 +38,7 @@ static const char * const cherry_pick_usage[] = { static int edit, no_replay, no_commit, mainline, signoff; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; +static int allow_rerere_auto; static const char *me; @@ -57,6 +58,7 @@ static void parse_args(int argc, const char **argv) OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), + OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_END(), }; @@ -395,7 +397,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed.%s\n", me, help_msg(commit->object.sha1)); - rerere(); + rerere(allow_rerere_auto); exit(1); } if (commit_lock_file(&msg_file) < 0) diff --git a/git-am.sh b/git-am.sh index 4838cdb9ed..2f46fda47b 100755 --- a/git-am.sh +++ b/git-am.sh @@ -30,6 +30,7 @@ skip skip the current patch abort restore the original branch and abort the patching operation. committer-date-is-author-date lie about committer date ignore-date use current timestamp for author date +rerere-autoupdate update the index with reused conflict resolution if possible rebasing* (internal use for git-rebase)" . git-sh-setup @@ -135,7 +136,7 @@ It does not apply to blobs recorded in its index." export GIT_MERGE_VERBOSITY=0 fi git-merge-recursive $orig_tree -- HEAD $his_tree || { - git rerere + git rerere $allow_rerere_autoupdate echo Failed to merge in the changes. exit 1 } @@ -293,6 +294,7 @@ resolvemsg= resume= scissors= no_inbody_headers= git_apply_opt= committer_date_is_author_date= ignore_date= +allow_rerere_autoupdate= while test $# != 0 do @@ -340,6 +342,8 @@ do committer_date_is_author_date=t ;; --ignore-date) ignore_date=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" ;; -q|--quiet) GIT_QUIET=t ;; --) diff --git a/git-rebase.sh b/git-rebase.sh index b121f4537c..398ea73716 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -50,6 +50,7 @@ diffstat=$(git config --bool rebase.stat) git_am_opt= rebase_root= force_rebase= +allow_rerere_autoupdate= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -118,7 +119,7 @@ call_merge () { return ;; 1) - git rerere + git rerere $allow_rerere_autoupdate die "$RESOLVEMSG" ;; 2) @@ -349,6 +350,9 @@ do -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase) force_rebase=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" + ;; -*) usage ;; diff --git a/parse-options.c b/parse-options.c index f5594114ed..10ec21fb89 100644 --- a/parse-options.c +++ b/parse-options.c @@ -633,3 +633,10 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) commit_list_insert(commit, opt->value); return 0; } + +int parse_opt_tertiary(const struct option *opt, const char *arg, int unset) +{ + int *target = opt->value; + *target = unset ? 2 : 1; + return 0; +} diff --git a/parse-options.h b/parse-options.h index f295a2cf85..91c1500661 100644 --- a/parse-options.h +++ b/parse-options.h @@ -123,6 +123,8 @@ struct option { (h), PARSE_OPT_NOARG, NULL, (p) } #define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) } #define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } +#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG, &parse_opt_tertiary } #define OPT_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \ parse_opt_approxidate_cb } @@ -190,6 +192,7 @@ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); +extern int parse_opt_tertiary(const struct option *, const char *, int); #define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") #define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") diff --git a/rerere.c b/rerere.c index 29f95f657d..e0ac5bcaed 100644 --- a/rerere.c +++ b/rerere.c @@ -367,7 +367,7 @@ static int is_rerere_enabled(void) return 1; } -int setup_rerere(struct string_list *merge_rr) +int setup_rerere(struct string_list *merge_rr, int flags) { int fd; @@ -375,6 +375,8 @@ int setup_rerere(struct string_list *merge_rr) if (!is_rerere_enabled()) return -1; + if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE)) + rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE); merge_rr_path = git_pathdup("MERGE_RR"); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, LOCK_DIE_ON_ERROR); @@ -382,12 +384,12 @@ int setup_rerere(struct string_list *merge_rr) return fd; } -int rerere(void) +int rerere(int flags) { struct string_list merge_rr = { NULL, 0, 0, 1 }; int fd; - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; return do_plain_rerere(&merge_rr, fd); diff --git a/rerere.h b/rerere.h index 13313f3f2b..10a94a4ea1 100644 --- a/rerere.h +++ b/rerere.h @@ -3,9 +3,15 @@ #include "string-list.h" -extern int setup_rerere(struct string_list *); -extern int rerere(void); +#define RERERE_AUTOUPDATE 01 +#define RERERE_NOAUTOUPDATE 02 + +extern int setup_rerere(struct string_list *, int); +extern int rerere(int); extern const char *rerere_path(const char *hex, const char *file); extern int has_rerere_resolution(const char *hex); +#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \ + "update the index with reused conflict resolution if possible") + #endif diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index a6bc028a57..bb402c3780 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -217,7 +217,22 @@ test_expect_success 'rerere.autoupdate' ' git checkout version2 && test_must_fail git merge fifth && test 0 = $(git ls-files -u | wc -l) +' +test_expect_success 'merge --rerere-autoupdate' ' + git config --unset rerere.autoupdate + git reset --hard && + git checkout version2 && + test_must_fail git merge --rerere-autoupdate fifth && + test 0 = $(git ls-files -u | wc -l) +' + +test_expect_success 'merge --no-rerere-autoupdate' ' + git config rerere.autoupdate true + git reset --hard && + git checkout version2 && + test_must_fail git merge --no-rerere-autoupdate fifth && + test 2 = $(git ls-files -u | wc -l) ' test_done From 0f6927c2296d6f92f3f233fe9e073c30ca66e0ce Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Fri, 4 Dec 2009 18:06:54 +0100 Subject: [PATCH 066/384] fast-import: put option parsing code in separate functions Putting the options in their own functions increases readability of the option parsing block and makes it easier to reuse the option parsing code later on. Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- fast-import.c | 115 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/fast-import.c b/fast-import.c index dd3c99d60d..fcd9e1e1e3 100644 --- a/fast-import.c +++ b/fast-import.c @@ -295,6 +295,7 @@ static unsigned long branch_count; static unsigned long branch_load_count; static int failure; static FILE *pack_edges; +static unsigned int show_stats = 1; /* Memory pools */ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); @@ -2420,7 +2421,7 @@ static void parse_progress(void) skip_optional_lf(); } -static void import_marks(const char *input_file) +static void option_import_marks(const char *input_file) { char line[512]; FILE *f = fopen(input_file, "r"); @@ -2455,6 +2456,76 @@ static void import_marks(const char *input_file) fclose(f); } +static void option_date_format(const char *fmt) +{ + if (!strcmp(fmt, "raw")) + whenspec = WHENSPEC_RAW; + else if (!strcmp(fmt, "rfc2822")) + whenspec = WHENSPEC_RFC2822; + else if (!strcmp(fmt, "now")) + whenspec = WHENSPEC_NOW; + else + die("unknown --date-format argument %s", fmt); +} + +static void option_max_pack_size(const char *packsize) +{ + max_packsize = strtoumax(packsize, NULL, 0) * 1024 * 1024; +} + +static void option_depth(const char *depth) +{ + max_depth = strtoul(depth, NULL, 0); + if (max_depth > MAX_DEPTH) + die("--depth cannot exceed %u", MAX_DEPTH); +} + +static void option_active_branches(const char *branches) +{ + max_active_branches = strtoul(branches, NULL, 0); +} + +static void option_export_marks(const char *marks) +{ + mark_file = xstrdup(marks); +} + +static void option_export_pack_edges(const char *edges) +{ + if (pack_edges) + fclose(pack_edges); + pack_edges = fopen(edges, "a"); + if (!pack_edges) + die_errno("Cannot open '%s'", edges); +} + +static void parse_one_option(const char *option) +{ + if (!prefixcmp(option, "date-format=")) { + option_date_format(option + 12); + } else if (!prefixcmp(option, "max-pack-size=")) { + option_max_pack_size(option + 14); + } else if (!prefixcmp(option, "depth=")) { + option_depth(option + 6); + } else if (!prefixcmp(option, "active-branches=")) { + option_active_branches(option + 16); + } else if (!prefixcmp(option, "import-marks=")) { + option_import_marks(option + 13); + } else if (!prefixcmp(option, "export-marks=")) { + option_export_marks(option + 13); + } else if (!prefixcmp(option, "export-pack-edges=")) { + option_export_pack_edges(option + 18); + } else if (!prefixcmp(option, "force")) { + force_update = 1; + } else if (!prefixcmp(option, "quiet")) { + show_stats = 0; + } else if (!prefixcmp(option, "stats")) { + show_stats = 1; + } else { + die("Unsupported option: %s", option); + } +} + static int git_pack_config(const char *k, const char *v, void *cb) { if (!strcmp(k, "pack.depth")) { @@ -2481,7 +2552,7 @@ static const char fast_import_usage[] = int main(int argc, const char **argv) { - unsigned int i, show_stats = 1; + unsigned int i; git_extract_argv0_path(argv[0]); @@ -2505,44 +2576,8 @@ int main(int argc, const char **argv) if (*a != '-' || !strcmp(a, "--")) break; - else if (!prefixcmp(a, "--date-format=")) { - const char *fmt = a + 14; - if (!strcmp(fmt, "raw")) - whenspec = WHENSPEC_RAW; - else if (!strcmp(fmt, "rfc2822")) - whenspec = WHENSPEC_RFC2822; - else if (!strcmp(fmt, "now")) - whenspec = WHENSPEC_NOW; - else - die("unknown --date-format argument %s", fmt); - } - else if (!prefixcmp(a, "--max-pack-size=")) - max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024; - else if (!prefixcmp(a, "--depth=")) { - max_depth = strtoul(a + 8, NULL, 0); - if (max_depth > MAX_DEPTH) - die("--depth cannot exceed %u", MAX_DEPTH); - } - else if (!prefixcmp(a, "--active-branches=")) - max_active_branches = strtoul(a + 18, NULL, 0); - else if (!prefixcmp(a, "--import-marks=")) - import_marks(a + 15); - else if (!prefixcmp(a, "--export-marks=")) - mark_file = a + 15; - else if (!prefixcmp(a, "--export-pack-edges=")) { - if (pack_edges) - fclose(pack_edges); - pack_edges = fopen(a + 20, "a"); - if (!pack_edges) - die_errno("Cannot open '%s'", a + 20); - } else if (!strcmp(a, "--force")) - force_update = 1; - else if (!strcmp(a, "--quiet")) - show_stats = 0; - else if (!strcmp(a, "--stats")) - show_stats = 1; - else - die("unknown option %s", a); + + parse_one_option(a + 2); } if (i != argc) usage(fast_import_usage); From 07cd9328b6256d3fdc05546e10c589144b5c63c5 Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Fri, 4 Dec 2009 18:06:55 +0100 Subject: [PATCH 067/384] fast-import: put marks reading in its own function All options do nothing but set settings, with the exception of the --input-marks option. Delay the reading of the marks file till after all options have been parsed. Also, rename mark_file to export_marks_file as it is now ambiguous. Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- fast-import.c | 93 ++++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/fast-import.c b/fast-import.c index fcd9e1e1e3..0458b03a2a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -318,7 +318,8 @@ static unsigned int object_entry_alloc = 5000; static struct object_entry_pool *blocks; static struct object_entry *object_table[1 << 16]; static struct mark_set *marks; -static const char *mark_file; +static const char *export_marks_file; +static const char *import_marks_file; /* Our last blob */ static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 }; @@ -455,8 +456,8 @@ static void write_crash_report(const char *err) fputc('\n', rpt); fputs("Marks\n", rpt); fputs("-----\n", rpt); - if (mark_file) - fprintf(rpt, " exported to %s\n", mark_file); + if (export_marks_file) + fprintf(rpt, " exported to %s\n", export_marks_file); else dump_marks_helper(rpt, 0, marks); @@ -1603,13 +1604,13 @@ static void dump_marks(void) int mark_fd; FILE *f; - if (!mark_file) + if (!export_marks_file) return; - mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0); + mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0); if (mark_fd < 0) { failure |= error("Unable to write marks file %s: %s", - mark_file, strerror(errno)); + export_marks_file, strerror(errno)); return; } @@ -1618,7 +1619,7 @@ static void dump_marks(void) int saved_errno = errno; rollback_lock_file(&mark_lock); failure |= error("Unable to write marks file %s: %s", - mark_file, strerror(saved_errno)); + export_marks_file, strerror(saved_errno)); return; } @@ -1634,7 +1635,7 @@ static void dump_marks(void) int saved_errno = errno; rollback_lock_file(&mark_lock); failure |= error("Unable to write marks file %s: %s", - mark_file, strerror(saved_errno)); + export_marks_file, strerror(saved_errno)); return; } @@ -1642,11 +1643,47 @@ static void dump_marks(void) int saved_errno = errno; rollback_lock_file(&mark_lock); failure |= error("Unable to commit marks file %s: %s", - mark_file, strerror(saved_errno)); + export_marks_file, strerror(saved_errno)); return; } } +static void read_marks(void) +{ + char line[512]; + FILE *f = fopen(import_marks_file, "r"); + if (!f) + die_errno("cannot read '%s'", import_marks_file); + while (fgets(line, sizeof(line), f)) { + uintmax_t mark; + char *end; + unsigned char sha1[20]; + struct object_entry *e; + + end = strchr(line, '\n'); + if (line[0] != ':' || !end) + die("corrupt mark line: %s", line); + *end = 0; + mark = strtoumax(line + 1, &end, 10); + if (!mark || end == line + 1 + || *end != ' ' || get_sha1(end + 1, sha1)) + die("corrupt mark line: %s", line); + e = find_object(sha1); + if (!e) { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("object not found: %s", sha1_to_hex(sha1)); + e = insert_object(sha1); + e->type = type; + e->pack_id = MAX_PACK_ID; + e->offset = 1; /* just not zero! */ + } + insert_mark(mark, e); + } + fclose(f); +} + + static int read_next_command(void) { static int stdin_eof = 0; @@ -2421,39 +2458,9 @@ static void parse_progress(void) skip_optional_lf(); } -static void option_import_marks(const char *input_file) +static void option_import_marks(const char *marks) { - char line[512]; - FILE *f = fopen(input_file, "r"); - if (!f) - die_errno("cannot read '%s'", input_file); - while (fgets(line, sizeof(line), f)) { - uintmax_t mark; - char *end; - unsigned char sha1[20]; - struct object_entry *e; - - end = strchr(line, '\n'); - if (line[0] != ':' || !end) - die("corrupt mark line: %s", line); - *end = 0; - mark = strtoumax(line + 1, &end, 10); - if (!mark || end == line + 1 - || *end != ' ' || get_sha1(end + 1, sha1)) - die("corrupt mark line: %s", line); - e = find_object(sha1); - if (!e) { - enum object_type type = sha1_object_info(sha1, NULL); - if (type < 0) - die("object not found: %s", sha1_to_hex(sha1)); - e = insert_object(sha1); - e->type = type; - e->pack_id = MAX_PACK_ID; - e->offset = 1; /* just not zero! */ - } - insert_mark(mark, e); - } - fclose(f); + import_marks_file = xstrdup(marks); } static void option_date_format(const char *fmt) @@ -2487,7 +2494,7 @@ static void option_active_branches(const char *branches) static void option_export_marks(const char *marks) { - mark_file = xstrdup(marks); + export_marks_file = xstrdup(marks); } static void option_export_pack_edges(const char *edges) @@ -2581,6 +2588,8 @@ int main(int argc, const char **argv) } if (i != argc) usage(fast_import_usage); + if (import_marks_file) + read_marks(); rc_free = pool_alloc(cmd_save * sizeof(*rc_free)); for (i = 0; i < (cmd_save - 1); i++) From f963bd5d71fd0db01f2c7f6f05df5a5f1af11b82 Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Fri, 4 Dec 2009 18:06:56 +0100 Subject: [PATCH 068/384] fast-import: add feature command This allows the fronted to require a specific feature to be supported by the backend, or abort. Also add support for four initial feature, date-format=, force=, import-marks=, export-marks=. Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- Documentation/git-fast-import.txt | 25 +++++++++++ fast-import.c | 38 +++++++++++++++++ t/t9300-fast-import.sh | 70 +++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 288032c7b8..4357c213ed 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -303,6 +303,10 @@ and control the current import process. More detailed discussion standard output. This command is optional and is not needed to perform an import. +`feature`:: + Require that fast-import supports the specified feature, or + abort if it does not. + `commit` ~~~~~~~~ Create or update a branch with a new commit, recording one logical @@ -846,6 +850,27 @@ Placing a `progress` command immediately after a `checkpoint` will inform the reader when the `checkpoint` has been completed and it can safely access the refs that fast-import updated. +`feature` +~~~~~~~~~ +Require that fast-import supports the specified feature, or abort if +it does not. + +.... + 'feature' SP LF +.... + +The part of the command may be any string matching +^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import. + +Feature work identical as their option counterparts. + +The following features are currently supported: + +* date-format +* import-marks +* export-marks +* force + Crash Reports ------------- If fast-import is supplied invalid input it will terminate with a diff --git a/fast-import.c b/fast-import.c index 0458b03a2a..ce0cd4e68a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -353,6 +353,7 @@ static struct recent_command *rc_free; static unsigned int cmd_save = 100; static uintmax_t next_mark; static struct strbuf new_data = STRBUF_INIT; +static int seen_data_command; static void write_branch_report(FILE *rpt, struct branch *b) { @@ -1704,6 +1705,11 @@ static int read_next_command(void) if (stdin_eof) return EOF; + if (!seen_data_command + && prefixcmp(command_buf.buf, "feature ")) { + seen_data_command = 1; + } + rc = rc_free; if (rc) rc_free = rc->next; @@ -2533,6 +2539,36 @@ static void parse_one_option(const char *option) } } +static int parse_one_feature(const char *feature) +{ + if (!prefixcmp(feature, "date-format=")) { + option_date_format(feature + 12); + } else if (!prefixcmp(feature, "import-marks=")) { + option_import_marks(feature + 13); + } else if (!prefixcmp(feature, "export-marks=")) { + option_export_marks(feature + 13); + } else if (!prefixcmp(feature, "force")) { + force_update = 1; + } else { + return 0; + } + + return 1; +} + +static void parse_feature(void) +{ + char *feature = command_buf.buf + 8; + + if (seen_data_command) + die("Got feature command '%s' after data command", feature); + + if (parse_one_feature(feature)) + return; + + die("This version of fast-import does not support feature %s.", feature); +} + static int git_pack_config(const char *k, const char *v, void *cb) { if (!strcmp(k, "pack.depth")) { @@ -2612,6 +2648,8 @@ int main(int argc, const char **argv) parse_checkpoint(); else if (!prefixcmp(command_buf.buf, "progress ")) parse_progress(); + else if (!prefixcmp(command_buf.buf, "feature ")) + parse_feature(); else die("Unsupported command: %s", command_buf.buf); } diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index b49815d108..b2c521f557 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1254,4 +1254,74 @@ test_expect_success \ 'Q: verify note for third commit' \ 'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual' +### +### series R (feature) +### + +cat >input <input <input << EOF +blob +data 3 +hi +feature date-format=now +EOF + +test_expect_success 'R: abort on receiving feature after data command' ' + test_must_fail git fast-import input << EOF +feature export-marks=git.marks +blob +mark :1 +data 3 +hi + +EOF + +test_expect_success \ + 'R: export-marks feature results in a marks file being created' \ + 'cat input | git fast-import && + grep :1 git.marks' + +test_expect_success \ + 'R: export-marks options can be overriden by commandline options' \ + 'cat input | git fast-import --export-marks=other.marks && + grep :1 other.marks' + +cat >input << EOF +feature import-marks=marks.out +feature export-marks=marks.new +EOF + +test_expect_success \ + 'R: import to output marks works without any content' \ + 'cat input | git fast-import && + test_cmp marks.out marks.new' + +cat >input < Date: Fri, 4 Dec 2009 18:06:57 +0100 Subject: [PATCH 069/384] fast-import: add option command This allows the frontend to specify any of the supported options as long as no non-option command has been given. This way the user does not have to include any frontend-specific options, but instead she can rely on the frontend to tell fast-import what it needs. Also factor out parsing of argv and have it execute when we reach the first non-option command, or after all commands have been read and no non-option command has been encountered. Non-git options are ignored, unrecognised options result in an error. Signed-off-by: Sverre Rabbelier Signed-off-by: Junio C Hamano --- Documentation/git-fast-import.txt | 32 ++++++++++++ fast-import.c | 87 ++++++++++++++++++++++--------- 2 files changed, 94 insertions(+), 25 deletions(-) diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 4357c213ed..2d5f533f63 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -307,6 +307,11 @@ and control the current import process. More detailed discussion Require that fast-import supports the specified feature, or abort if it does not. +`option`:: + Specify any of the options listed under OPTIONS that do not + change stream semantic to suit the frontend's needs. This + command is optional and is not needed to perform an import. + `commit` ~~~~~~~~ Create or update a branch with a new commit, recording one logical @@ -871,6 +876,33 @@ The following features are currently supported: * export-marks * force +`option` +~~~~~~~~ +Processes the specified option so that git fast-import behaves in a +way that suits the frontend's needs. +Note that options specified by the frontend are overridden by any +options the user may specify to git fast-import itself. + +.... + 'option' SP