diff options
39 files changed, 972 insertions, 169 deletions
diff --git a/Documentation/RelNotes-1.5.2.3.txt b/Documentation/RelNotes-1.5.2.3.txt new file mode 100644 index 000000000..addb22955 --- /dev/null +++ b/Documentation/RelNotes-1.5.2.3.txt @@ -0,0 +1,27 @@ +GIT v1.5.2.3 Release Notes +========================== + +Fixes since v1.5.2.2 +-------------------- + + * Bugfixes + + - Version 2 pack index format was introduced in version 1.5.2 + to support pack files that has offset that cannot be + represented in 32-bit. The runtime code to validate such + an index mishandled such an index for an empty pack. + + - Commit walkers (most notably, fetch over http protocol) + tried to traverse commit objects contained in trees (aka + subproject); they shouldn't. + + - A build option NO_R_TO_GCC_LINKER was not explained in Makefile + comment correctly. + + * Documentation Fixes and Updates + + - git-config --regexp was not documented properly. + + - git-repack -a was not documented properly. + + - git-remote -n was not documented properly. diff --git a/Documentation/config.txt b/Documentation/config.txt index a2057d9d2..50503e84b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -117,6 +117,18 @@ core.fileMode:: the working copy are ignored; useful on broken filesystems like FAT. See gitlink:git-update-index[1]. True by default. +core.quotepath:: + The commands that output paths (e.g. `ls-files`, + `diff`), when not given the `-z` option, will quote + "unusual" characters in the pathname by enclosing the + pathname in a double-quote pair and with backslashes the + same way strings in C source code are quoted. If this + variable is set to false, the bytes higher than 0x80 are + not quoted but output as verbatim. Note that double + quote, backslash and control characters are always + quoted without `-z` regardless of the setting of this + variable. + core.autocrlf:: If true, makes git convert `CRLF` at the end of lines in text files to `LF` when reading from the filesystem, and convert in reverse when @@ -172,6 +184,13 @@ repository that ends in "/.git" is assumed to be not bare (bare = false), while all other repositories are assumed to be bare (bare = true). +core.worktree:: + Set the path to the working tree. The value will not be + used in combination with repositories found automatically in + a .git directory (i.e. $GIT_DIR is not set). + This can be overriden by the GIT_WORK_TREE environment + variable and the '--work-tree' command line option. + core.logAllRefUpdates:: Updates to a ref <ref> is logged to the file "$GIT_DIR/logs/<ref>", by appending the new and old diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 18d49d2c3..001503205 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -126,6 +126,13 @@ the file that rename/copy produces, respectively. If there is need for such substitution then the whole pathname is put in double quotes. +The similarity index is the percentage of unchanged lines, and +the dissimilarity index is the percentage of changed lines. It +is a rounded down integer, followed by a percent sign. The +similarity index value of 100% is thus reserved for two equal +files, while 100% dissimilarity means that no line from the old +file made it into the new one. + combined diff format -------------------- diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 8d72bb936..bb6b57dc2 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -41,7 +41,7 @@ to happen. With a `-d` or `-D` option, `<branchname>` will be deleted. You may specify more than one branch for deletion. If the branch currently -has a ref log then the ref log will also be deleted. Use -r together with -d +has a reflog then the reflog will also be deleted. Use -r together with -d to delete remote-tracking branches. @@ -54,9 +54,9 @@ OPTIONS Delete a branch irrespective of its index status. -l:: - Create the branch's ref log. This activates recording of - all changes to made the branch ref, enabling use of date - based sha1 expressions such as "<branchname>@{yesterday}". + Create the branch's reflog. This activates recording of + all changes made to the branch ref, enabling use of date + based sha1 expressions such as "<branchname>@\{yesterday}". -f:: Force the creation of a new branch even if it means deleting diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index ea26da8e2..818b720b9 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -62,9 +62,9 @@ OPTIONS configuration variable. -l:: - Create the new branch's ref log. This activates recording of - all changes to made the branch ref, enabling use of date - based sha1 expressions such as "<branchname>@{yesterday}". + Create the new branch's reflog. This activates recording of + all changes made to the branch ref, enabling use of date + based sha1 expressions such as "<branchname>@\{yesterday}". -m:: If you have local modifications to one or more files that diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index a44578166..5f66a7fcd 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -9,9 +9,9 @@ git-config - Get and set repository or global options SYNOPSIS -------- [verse] -'git-config' [--system | --global] [-z|--null] name [value [value_regex]] -'git-config' [--system | --global] --add name value -'git-config' [--system | --global] --replace-all name [value [value_regex]] +'git-config' [--system | --global] [type] [-z|--null] name [value [value_regex]] +'git-config' [--system | --global] [type] --add name value +'git-config' [--system | --global] [type] --replace-all name [value [value_regex]] 'git-config' [--system | --global] [type] [-z|--null] --get name [value_regex] 'git-config' [--system | --global] [type] [-z|--null] --get-all name [value_regex] 'git-config' [--system | --global] [type] [-z|--null] --get-regexp name_regex [value_regex] @@ -37,8 +37,7 @@ prepend a single exclamation mark in front (see also <<EXAMPLES>>). The type specifier can be either '--int' or '--bool', which will make 'git-config' ensure that the variable(s) are of the given type and convert the value to the canonical form (simple decimal number for int, -a "true" or "false" string for bool). Type specifiers currently only -take effect for reading operations. If no type specifier is passed, +a "true" or "false" string for bool). If no type specifier is passed, no checks or transformations are performed on the value. This command will fail if: diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 6914aa59c..4ef184047 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -48,8 +48,8 @@ standard input of the hook will be one line per ref to be updated: The refname value is relative to $GIT_DIR; e.g. for the master head this is "refs/heads/master". The two sha1 values before each refname are the object names for the refname before and after -the update. Refs to be created will have sha1-old equal to 0{40}, -while refs to be deleted will have sha1-new equal to 0{40}, otherwise +the update. Refs to be created will have sha1-old equal to 0\{40}, +while refs to be deleted will have sha1-new equal to 0\{40}, otherwise sha1-old and sha1-new should be valid objects in the repository. This hook is called before any refname is updated and before any @@ -71,7 +71,7 @@ The refname parameter is relative to $GIT_DIR; e.g. for the master head this is "refs/heads/master". The two sha1 arguments are the object names for the refname before and after the update. Note that the hook is called before the refname is updated, -so either sha1-old is 0{40} (meaning there is no such ref yet), +so either sha1-old is 0\{40} (meaning there is no such ref yet), or it should match what is recorded in refname. The hook should exit with non-zero status if it wants to disallow @@ -96,8 +96,8 @@ The refname value is relative to $GIT_DIR; e.g. for the master head this is "refs/heads/master". The two sha1 values before each refname are the object names for the refname before and after the update. Refs that were created will have sha1-old equal to -0{40}, while refs that were deleted will have sha1-new equal to -0{40}, otherwise sha1-old and sha1-new should be valid objects in +0\{40}, while refs that were deleted will have sha1-new equal to +0\{40}, otherwise sha1-old and sha1-new should be valid objects in the repository. Using this hook, it is easy to generate mails describing the updates diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 32cb13fae..20dcac625 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -284,9 +284,9 @@ excluded from the output. + With '\--pretty' format other than oneline (for obvious reasons), this causes the output to have two extra lines of information -taken from the reflog. By default, 'commit@{Nth}' notation is +taken from the reflog. By default, 'commit@\{Nth}' notation is used in the output. When the starting commit is specified as -'commit@{now}', output also uses 'commit@{timestamp}' notation +'commit@{now}', output also uses 'commit@\{timestamp}' notation instead. Under '\--pretty=oneline', the commit message is prefixed with this information on the same line. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 87771b832..eea9c9cfe 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -90,8 +90,15 @@ OPTIONS Show `$GIT_DIR` if defined else show the path to the .git directory. --is-inside-git-dir:: - Return "true" if we are in the git directory, otherwise "false". - Some commands require to be run in a working directory. + When the current working directory is below the repository + directory print "true", otherwise "false". + +--is-inside-work-tree:: + When the current working directory is inside the work tree of the + repository print "true", otherwise "false". + +--is-bare-repository:: + When the repository is bare print "true", otherwise "false". --short, --short=number:: Instead of outputting the full SHA1 values of object names try to diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index f8fb80f18..7f0904e29 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -8,11 +8,19 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- +'git-submodule' [--quiet] [-b branch] add <repository> [<path>] 'git-submodule' [--quiet] [--cached] [status|init|update] [--] [<path>...] COMMANDS -------- +add:: + Add the given repository as a submodule at the given path + to the changeset to be committed next. In particular, the + repository is cloned at the specified path, added to the + changeset and registered in .gitmodules. If no path is + specified, the path is deduced from the repository specification. + status:: Show the status of the submodules. This will print the SHA-1 of the currently checked out commit for each submodule, along with the @@ -39,6 +47,9 @@ OPTIONS -q, --quiet:: Only print error messages. +-b, --branch:: + Branch of repository to add as submodule. + --cached:: Display the SHA-1 stored in the index, not the SHA-1 of the currently checked out submodule commit. This option is only valid for the diff --git a/Documentation/git.txt b/Documentation/git.txt index 826914837..10c7bb3f4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] - [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS] + [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] + [--help] COMMAND [ARGS] DESCRIPTION ----------- @@ -41,9 +42,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.5.2.2/git.html[documentation for release 1.5.2.2] +* link:v1.5.2.3/git.html[documentation for release 1.5.2.3] * release notes for + link:RelNotes-1.5.2.3.txt[1.5.2.3], link:RelNotes-1.5.2.2.txt[1.5.2.2], link:RelNotes-1.5.2.1.txt[1.5.2.1], link:RelNotes-1.5.2.txt[1.5.2]. @@ -103,6 +105,14 @@ OPTIONS Set the path to the repository. This can also be controlled by setting the GIT_DIR environment variable. +--work-tree=<path>:: + Set the path to the working tree. The value will not be + used in combination with repositories found automatically in + a .git directory (i.e. $GIT_DIR is not set). + This can also be controlled by setting the GIT_WORK_TREE + environment variable and the core.worktree configuration + variable. + --bare:: Same as --git-dir=`pwd`. @@ -347,6 +357,13 @@ git so take care if using Cogito etc. specifies a path to use instead of the default `.git` for the base of the repository. +'GIT_WORK_TREE':: + Set the path to the working tree. The value will not be + used in combination with repositories found automatically in + a .git directory (i.e. $GIT_DIR is not set). + This can also be controlled by the '--work-tree' command line + option and the core.worktree configuration variable. + git Commits ~~~~~~~~~~~ 'GIT_AUTHOR_NAME':: diff --git a/builtin-config.c b/builtin-config.c index b96c9aa74..7d2063c1d 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -138,9 +138,33 @@ free_strings: return ret; } +char *normalize_value(const char *key, const char *value) +{ + char *normalized; + + if (!value) + return NULL; + + if (type == T_RAW) + normalized = xstrdup(value); + else { + normalized = xmalloc(64); + if (type == T_INT) { + int v = git_config_int(key, value); + sprintf(normalized, "%d", v); + } + else if (type == T_BOOL) + sprintf(normalized, "%s", + git_config_bool(key, value) ? "true" : "false"); + } + + return normalized; +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = 0; + char* value; setup_git_directory_gently(&nongit); while (1 < argc) { @@ -154,14 +178,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) char *home = getenv("HOME"); if (home) { char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - setenv("GIT_CONFIG", user_config, 1); + setenv(CONFIG_ENVIRONMENT, user_config, 1); free(user_config); } else { die("$HOME not set"); } } else if (!strcmp(argv[1], "--system")) - setenv("GIT_CONFIG", ETC_GITCONFIG, 1); + setenv(CONFIG_ENVIRONMENT, ETC_GITCONFIG, 1); else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) { term = '\0'; delim = '\n'; @@ -217,9 +241,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) use_key_regexp = 1; do_all = 1; return get_value(argv[2], NULL); - } else - - return git_config_set(argv[1], argv[2]); + } else { + value = normalize_value(argv[1], argv[2]); + return git_config_set(argv[1], value); + } case 4: if (!strcmp(argv[1], "--unset")) return git_config_set_multivar(argv[2], NULL, argv[3], 0); @@ -235,17 +260,21 @@ int cmd_config(int argc, const char **argv, const char *prefix) use_key_regexp = 1; do_all = 1; return get_value(argv[2], argv[3]); - } else if (!strcmp(argv[1], "--add")) - return git_config_set_multivar(argv[2], argv[3], "^$", 0); - else if (!strcmp(argv[1], "--replace-all")) - - return git_config_set_multivar(argv[2], argv[3], NULL, 1); - else - - return git_config_set_multivar(argv[1], argv[2], argv[3], 0); + } else if (!strcmp(argv[1], "--add")) { + value = normalize_value(argv[2], argv[3]); + return git_config_set_multivar(argv[2], value, "^$", 0); + } else if (!strcmp(argv[1], "--replace-all")) { + value = normalize_value(argv[2], argv[3]); + return git_config_set_multivar(argv[2], value, NULL, 1); + } else { + value = normalize_value(argv[1], argv[2]); + return git_config_set_multivar(argv[1], value, argv[3], 0); + } case 5: - if (!strcmp(argv[1], "--replace-all")) - return git_config_set_multivar(argv[2], argv[3], argv[4], 1); + if (!strcmp(argv[1], "--replace-all")) { + value = normalize_value(argv[2], argv[3]); + return git_config_set_multivar(argv[2], value, argv[4], 1); + } case 1: default: usage(git_config_set_usage); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 5398a4141..61577ea13 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -470,7 +470,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (require_work_tree && - (is_bare_repository() || is_inside_git_dir())) + (!is_inside_work_tree() || is_inside_git_dir())) die("This operation must be run in a work tree"); pathspec = get_pathspec(prefix, argv + i); diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 37addb25f..497903a85 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -352,6 +352,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) : "false"); continue; } + if (!strcmp(arg, "--is-inside-work-tree")) { + printf("%s\n", is_inside_work_tree() ? "true" + : "false"); + continue; + } + if (!strcmp(arg, "--is-bare-repository")) { + printf("%s\n", is_bare_repository() ? "true" + : "false"); + continue; + } if (!prefixcmp(arg, "--since=")) { show_datestring("--max-age=", arg+8); continue; @@ -192,6 +192,7 @@ enum object_type { }; #define GIT_DIR_ENVIRONMENT "GIT_DIR" +#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" #define INDEX_ENVIRONMENT "GIT_INDEX_FILE" @@ -207,6 +208,7 @@ enum object_type { extern int is_bare_repository_cfg; extern int is_bare_repository(void); extern int is_inside_git_dir(void); +extern int is_inside_work_tree(void); extern const char *get_git_dir(void); extern char *get_object_directory(void); extern char *get_refs_directory(void); @@ -292,6 +294,7 @@ extern int delete_ref(const char *, const unsigned char *sha1); /* Environment bits from configuration mechanism */ extern int trust_executable_bit; +extern int quote_path_fully; extern int has_symlinks; extern int assume_unchanged; extern int prefer_symlink_refs; @@ -271,6 +271,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.quotepath")) { + quote_path_fully = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "core.symlinks")) { has_symlinks = git_config_bool(var, value); return 0; @@ -587,6 +587,7 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags) unsetenv(ALTERNATE_DB_ENVIRONMENT); unsetenv(DB_ENVIRONMENT); unsetenv(GIT_DIR_ENVIRONMENT); + unsetenv(GIT_WORK_TREE_ENVIRONMENT); unsetenv(GRAFT_ENVIRONMENT); unsetenv(INDEX_ENVIRONMENT); execlp("sh", "sh", "-c", command, NULL); @@ -1813,6 +1813,11 @@ static void diff_fill_sha1_info(struct diff_filespec *one) hashclr(one->sha1); } +static int similarity_index(struct diff_filepair *p) +{ + return p->score * 100 / MAX_SCORE; +} + static void run_diff(struct diff_filepair *p, struct diff_options *o) { const char *pgm = external_diff(); @@ -1847,23 +1852,20 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) "similarity index %d%%\n" "copy from %s\n" "copy to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); + similarity_index(p), name_munged, other_munged); break; case DIFF_STATUS_RENAMED: len += snprintf(msg + len, sizeof(msg) - len, "similarity index %d%%\n" "rename from %s\n" "rename to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); + similarity_index(p), name_munged, other_munged); break; case DIFF_STATUS_MODIFIED: if (p->score) { len += snprintf(msg + len, sizeof(msg) - len, "dissimilarity index %d%%\n", - (int)(0.5 + p->score * - 100.0/MAX_SCORE)); + similarity_index(p)); complete_rewrite = 1; break; } @@ -2387,8 +2389,7 @@ static void diff_flush_raw(struct diff_filepair *p, } if (p->score) - sprintf(status, "%c%03d", p->status, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); + sprintf(status, "%c%03d", p->status, similarity_index(p)); else { status[0] = p->status; status[1] = 0; @@ -2670,8 +2671,7 @@ static void show_rename_copy(const char *renamecopy, struct diff_filepair *p) { char *names = pprint_rename(p->one->path, p->two->path); - printf(" %s %s (%d%%)\n", renamecopy, names, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); + printf(" %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); free(names); show_mode_change(p, 0); } @@ -2695,7 +2695,7 @@ static void diff_summary(struct diff_filepair *p) if (p->score) { char *name = quote_one(p->two->path); printf(" rewrite %s (%d%%)\n", name, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); + similarity_index(p)); free(name); show_mode_change(p, 0); } else show_mode_change(p, 1); diff --git a/diffcore-rename.c b/diffcore-rename.c index cb227366b..6bde4396f 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -138,6 +138,7 @@ struct diff_score { int src; /* index in rename_src */ int dst; /* index in rename_dst */ int score; + int name_score; }; static int estimate_similarity(struct diff_filespec *src, @@ -200,11 +201,8 @@ static int estimate_similarity(struct diff_filespec *src, */ if (!dst->size) score = 0; /* should not happen */ - else { + else score = (int)(src_copied * MAX_SCORE / max_size); - if (basename_same(src, dst)) - score++; - } return score; } @@ -241,6 +239,10 @@ static void record_rename_pair(int dst_index, int src_index, int score) static int score_compare(const void *a_, const void *b_) { const struct diff_score *a = a_, *b = b_; + + if (a->score == b->score) + return b->name_score - a->name_score; + return b->score - a->score; } @@ -359,6 +361,7 @@ void diffcore_rename(struct diff_options *options) m->dst = i; m->score = estimate_similarity(one, two, minimum_score); + m->name_score = basename_same(one, two); diff_free_filespec_data(one); } /* We do not need the text anymore */ diff --git a/environment.c b/environment.c index 8b9b89d0a..1c2773f1b 100644 --- a/environment.c +++ b/environment.c @@ -12,6 +12,7 @@ char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; int trust_executable_bit = 1; +int quote_path_fully = 1; int has_symlinks = 1; int assume_unchanged; int prefer_symlink_refs; diff --git a/git-clone.sh b/git-clone.sh index bd44ce1c8..4cbf60f55 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -72,6 +72,17 @@ Perhaps git-update-server-info needs to be run there?" rm -fr "$clone_tmp" http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" || rm -f "$GIT_DIR/REMOTE_HEAD" + if test -f "$GIT_DIR/REMOTE_HEAD"; then + head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"` + case "$head_sha1" in + 'ref: refs/'*) + ;; + *) + git-http-fetch $v -a "$head_sha1" "$1" || + rm -f "$GIT_DIR/REMOTE_HEAD" + ;; + esac + fi } quiet= diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 8fa5ce646..a2fcebc1c 100644 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -312,9 +312,10 @@ case "$GIT_DIR" in /*) ;; *) - export GIT_DIR="$(pwd)/../../$GIT_DIR" + GIT_DIR="$(pwd)/../../$GIT_DIR" ;; esac +export GIT_DIR GIT_WORK_TREE=. export GIT_INDEX_FILE="$(pwd)/../index" git-read-tree # seed the index file diff --git a/git-sh-setup.sh b/git-sh-setup.sh index f24c7f2d2..0de49e845 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -29,11 +29,7 @@ set_reflog_action() { } is_bare_repository () { - git-config --bool --get core.bare || - case "$GIT_DIR" in - .git | */.git) echo false ;; - *) echo true ;; - esac + git-rev-parse --is-bare-repository } cd_to_toplevel () { @@ -48,7 +44,7 @@ cd_to_toplevel () { } require_work_tree () { - test $(is_bare_repository) = false && + test $(git-rev-parse --is-inside-work-tree) = true && test $(git-rev-parse --is-inside-git-dir) = false || die "fatal: $0 cannot be used without a working tree." } diff --git a/git-submodule.sh b/git-submodule.sh index 89a388535..c29e2c3c9 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -1,13 +1,15 @@ #!/bin/sh # -# git-submodules.sh: init, update or list git submodules +# git-submodules.sh: add, init, update or list git submodules # # Copyright (c) 2007 Lars Hjemli -USAGE='[--quiet] [--cached] [status|init|update] [--] [<path>...]' +USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]' . git-sh-setup require_work_tree +add= +branch= init= update= status= @@ -25,6 +27,18 @@ say() fi } +# NEEDSWORK: identical function exists in get_repo_base in clone.sh +get_repo_base() { + ( + cd "`/bin/pwd`" && + cd "$1" || cd "$1.git" && + { + cd .git + pwd + } + ) 2>/dev/null +} + # # Map submodule path to submodule name # @@ -42,6 +56,11 @@ module_name() # # Clone a submodule # +# Prior to calling, modules_update checks that a possibly existing +# path is not a git repository. +# Likewise, module_add checks that path does not exist at all, +# since it is the location of a new submodule. +# module_clone() { path=$1 @@ -66,6 +85,53 @@ module_clone() } # +# Add a new submodule to the working tree, .gitmodules and the index +# +# $@ = repo [path] +# +# optional branch is stored in global branch variable +# +module_add() +{ + repo=$1 + path=$2 + + if test -z "$repo"; then + usage + fi + + # Turn the source into an absolute path if + # it is local + if base=$(get_repo_base "$repo"); then + repo="$base" + fi + + # Guess path from repo if not specified or strip trailing slashes + if test -z "$path"; then + path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') + else + path=$(echo "$path" | sed -e 's|/*$||') + fi + + test -e "$path" && + die "'$path' already exists" + + git-ls-files --error-unmatch "$path" > /dev/null 2>&1 && + die "'$path' already exists in the index" + + module_clone "$path" "$repo" || exit + (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || + die "Unable to checkout submodule '$path'" + git add "$path" || + die "Failed to add submodule '$path'" + + GIT_CONFIG=.gitmodules git config submodule."$path".path "$path" && + GIT_CONFIG=.gitmodules git config submodule."$path".url "$repo" && + git add .gitmodules || + die "Failed to register submodule '$path'" +} + +# # Register submodules in .git/config # # $@ = requested paths (default to all) @@ -133,6 +199,18 @@ modules_update() done } +set_name_rev () { + revname=$( ( + unset GIT_DIR && + cd "$1" && { + git-describe "$2" 2>/dev/null || + git-describe --tags "$2" 2>/dev/null || + git-describe --contains --tags "$2" + } + ) ) + test -z "$revname" || revname=" ($revname)" +} + # # List all submodules, prefixed with: # - submodule not initialized @@ -155,17 +233,18 @@ modules_list() say "-$sha1 $path" continue; fi - revname=$(unset GIT_DIR && cd "$path" && git-describe $sha1) + revname=$(unset GIT_DIR && cd "$path" && git-describe --tags $sha1) + set_name_rev "$path" $"sha1" if git diff-files --quiet -- "$path" then - say " $sha1 $path ($revname)" + say " $sha1 $path$revname" else if test -z "$cached" then sha1=$(unset GIT_DIR && cd "$path" && git-rev-parse --verify HEAD) - revname=$(unset GIT_DIR && cd "$path" && git-describe $sha1) + set_name_rev "$path" $"sha1" fi - say "+$sha1 $path ($revname)" + say "+$sha1 $path$revname" fi done } @@ -173,6 +252,9 @@ modules_list() while case "$#" in 0) break ;; esac do case "$1" in + add) + add=1 + ;; init) init=1 ;; @@ -185,6 +267,14 @@ do -q|--quiet) quiet=1 ;; + -b|--branch) + case "$2" in + '') + usage + ;; + esac + branch="$2"; shift + ;; --cached) cached=1 ;; @@ -201,14 +291,27 @@ do shift done -case "$init,$update,$status,$cached" in -1,,,) +case "$add,$branch" in +1,*) + ;; +,) + ;; +,*) + usage + ;; +esac + +case "$add,$init,$update,$status,$cached" in +1,,,,) + module_add "$@" + ;; +,1,,,) modules_init "$@" ;; -,1,,) +,,1,,) modules_update "$@" ;; -,,*,*) +,,,1,*) modules_list "$@" ;; *) diff --git a/git-svn.perl b/git-svn.perl index 50128d728..03d5e2d97 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -596,8 +596,7 @@ sub post_fetch_checkout { my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index"; return if -f $index; - chomp(my $bare = `git config --bool --get core.bare`); - return if $bare eq 'true'; + return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false'; return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true'; command_noisy(qw/read-tree -m -u -v HEAD HEAD/); print STDERR "Checked out HEAD:\n ", @@ -787,12 +786,12 @@ sub read_repo_config { sub extract_metadata { my $id = shift or return (undef, undef, undef); - my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+) + my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+) \s([a-f\d\-]+)$/x); if (!defined $rev || !$uuid || !$url) { # some of the original repositories I made had # identifiers like this: - ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/); + ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/); } return ($url, $rev, $uuid); } @@ -804,20 +803,29 @@ sub cmt_metadata { sub working_head_info { my ($head, $refs) = @_; - my ($fh, $ctx) = command_output_pipe('rev-list', $head); - while (my $hash = <$fh>) { - chomp($hash); - my ($url, $rev, $uuid) = cmt_metadata($hash); + my ($fh, $ctx) = command_output_pipe('log', $head); + my $hash; + my %max; + while (<$fh>) { + if ( m{^commit ($::sha1)$} ) { + unshift @$refs, $hash if $hash and $refs; + $hash = $1; + next; + } + next unless s{^\s*(git-svn-id:)}{$1}; + my ($url, $rev, $uuid) = extract_metadata($_); if (defined $url && defined $rev) { + next if $max{$url} and $max{$url} < $rev; if (my $gs = Git::SVN->find_by_url($url)) { my $c = $gs->rev_db_get($rev); if ($c && $c eq $hash) { close $fh; # break the pipe return ($url, $rev, $uuid, $gs); + } else { + $max{$url} ||= $gs->rev_db_max; } } } - unshift @$refs, $hash if $refs; } command_close_pipe($fh, $ctx); (undef, undef, undef, undef); @@ -1966,16 +1974,19 @@ sub rebuild { return; } print "Rebuilding $db_path ...\n"; - my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); + my ($log, $ctx) = command_output_pipe("log", $self->refname); my $latest; my $full_url = $self->full_url; remove_username($full_url); my $svn_uuid; - while (<$rev_list>) { - chomp; - my $c = $_; - die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o; - my ($url, $rev, $uuid) = ::cmt_metadata($c); + my $c; + while (<$log>) { + if ( m{^commit ($::sha1)$} ) { + $c = $1; + next; + } + next unless s{^\s*(git-svn-id:)}{$1}; + my ($url, $rev, $uuid) = ::extract_metadata($_); remove_username($url); # ignore merges (from set-tree) @@ -1993,7 +2004,7 @@ sub rebuild { $self->rev_db_set($rev, $c); print "r$rev = $c\n"; } - command_close_pipe($rev_list, $ctx); + command_close_pipe($log, $ctx); print "Done rebuilding $db_path\n"; } @@ -2925,6 +2936,7 @@ sub new { SVN::Client::get_ssl_server_trust_file_provider(), SVN::Client::get_simple_prompt_provider( \&Git::SVN::Prompt::simple, 2), + SVN::Client::get_ssl_client_cert_file_provider(), SVN::Client::get_ssl_client_cert_prompt_provider( \&Git::SVN::Prompt::ssl_client_cert, 2), SVN::Client::get_ssl_client_cert_pw_prompt_provider( @@ -4,7 +4,7 @@ #include "quote.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; static void prepend_to_path(const char *dir, int len) { @@ -28,7 +28,7 @@ static void prepend_to_path(const char *dir, int len) free(path); } -static int handle_options(const char*** argv, int* argc) +static int handle_options(const char*** argv, int* argc, int* envchanged) { int handled = 0; @@ -64,14 +64,34 @@ static int handle_options(const char*** argv, int* argc) usage(git_usage_string); } setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1); + if (envchanged) + *envchanged = 1; (*argv)++; (*argc)--; handled++; } else if (!prefixcmp(cmd, "--git-dir=")) { setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1); + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--work-tree")) { + if (*argc < 2) { + fprintf(stderr, "No directory given for --work-tree.\n" ); + usage(git_usage_string); + } + setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1); + if (envchanged) + *envchanged = 1; + (*argv)++; + (*argc)--; + } else if (!prefixcmp(cmd, "--work-tree=")) { + setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1); + if (envchanged) + *envchanged = 1; } else if (!strcmp(cmd, "--bare")) { static char git_dir[PATH_MAX+1]; setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1); + if (envchanged) + *envchanged = 1; } else { fprintf(stderr, "Unknown option: %s\n", cmd); usage(git_usage_string); @@ -150,7 +170,7 @@ static int split_cmdline(char *cmdline, const char ***argv) static int handle_alias(int *argcp, const char ***argv) { - int nongit = 0, ret = 0, saved_errno = errno; + int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno; const char *subdir; int count, option_count; const char** new_argv; @@ -161,6 +181,21 @@ static int handle_alias(int *argcp, const char ***argv) git_config(git_alias_config); if (alias_string) { if (alias_string[0] == '!') { + if (*argcp > 1) { + int i, sz = PATH_MAX; + char *s = xmalloc(sz), *new_alias = s; + + add_to_string(&s, &sz, alias_string, 0); + free(alias_string); + alias_string = new_alias; + for (i = 1; i < *argcp && + !add_to_string(&s, &sz, " ", 0) && + !add_to_string(&s, &sz, (*argv)[i], 1) + ; i++) + ; /* do nothing */ + if (!sz) + die("Too many or long arguments"); + } trace_printf("trace: alias to shell cmd: %s => %s\n", alias_command, alias_string + 1); ret = system(alias_string + 1); @@ -171,7 +206,11 @@ static int handle_alias(int *argcp, const char ***argv) alias_string + 1, alias_command); } count = split_cmdline(alias_string, &new_argv); - option_count = handle_options(&new_argv, &count); + option_count = handle_options(&new_argv, &count, &envchanged); + if (envchanged) + die("alias '%s' changes environment variables\n" + "You can use '!git' in the alias to do this.", + alias_command); memmove(new_argv - option_count, new_argv, count * sizeof(char *)); new_argv -= option_count; @@ -214,7 +253,7 @@ const char git_version_string[] = GIT_VERSION; * require working tree to be present -- anything uses this needs * RUN_SETUP for reading from the configuration file. */ -#define NOT_BARE (1<<2) +#define NEED_WORK_TREE (1<<2) struct cmd_struct { const char *cmd; @@ -233,10 +272,9 @@ static int run_command(struct cmd_struct *p, int argc, const char **argv) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if (p->option & NOT_BARE) { - if (is_bare_repository() || is_inside_git_dir()) - die("%s must be run in a work tree", p->cmd); - } + if ((p->option & NEED_WORK_TREE) && + (!is_inside_work_tree() || is_inside_git_dir())) + die("%s must be run in a work tree", p->cmd); trace_argv_printf(argv, argc, "trace: built-in: git"); status = p->fn(argc, argv, prefix); @@ -264,7 +302,7 @@ static void handle_internal_command(int argc, const char **argv) { const char *cmd = argv[0]; static struct cmd_struct commands[] = { - { "add", cmd_add, RUN_SETUP | NOT_BARE }, + { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "annotate", cmd_annotate, RUN_SETUP | USE_PAGER }, { "apply", cmd_apply }, { "archive", cmd_archive }, @@ -274,9 +312,9 @@ static void handle_internal_command(int argc, const char **argv) { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, - { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE }, + { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, { "cherry", cmd_cherry, RUN_SETUP }, - { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE }, + { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP }, @@ -304,7 +342,7 @@ static void handle_internal_command(int argc, const char **argv) { "mailsplit", cmd_mailsplit }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file }, - { "mv", cmd_mv, RUN_SETUP | NOT_BARE }, + { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER }, @@ -317,9 +355,9 @@ static void handle_internal_command(int argc, const char **argv) { "rerere", cmd_rerere, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, { "rev-parse", cmd_rev_parse, RUN_SETUP }, - { "revert", cmd_revert, RUN_SETUP | NOT_BARE }, - { "rm", cmd_rm, RUN_SETUP | NOT_BARE }, - { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE }, + { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, + { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE }, + { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE }, { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, @@ -393,7 +431,7 @@ int main(int argc, const char **argv) /* Look for flags.. */ argv++; argc--; - handle_options(&argv, &argc); + handle_options(&argv, &argc, NULL); if (argc > 0) { if (!prefixcmp(argv[0], "--")) argv[0] += 2; @@ -252,7 +252,7 @@ char *enter_repo(char *path, int strict) if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && validate_headref("HEAD") == 0) { - setenv("GIT_DIR", ".", 1); + setenv(GIT_DIR_ENVIRONMENT, ".", 1); check_repository_format(); return path; } @@ -188,7 +188,8 @@ static int quote_c_style_counted(const char *name, int namelen, #define EMITQ() EMIT('\\') const char *sp; - int ch, count = 0, needquote = 0; + unsigned char ch; + int count = 0, needquote = 0; if (!no_dq) EMIT('"'); @@ -197,7 +198,7 @@ static int quote_c_style_counted(const char *name, int namelen, if (!ch) break; if ((ch < ' ') || (ch == '"') || (ch == '\\') || - (ch >= 0177)) { + (quote_path_fully && (ch >= 0177))) { needquote = 1; switch (ch) { case '\a': EMITQ(); ch = 'a'; break; diff --git a/read-cache.c b/read-cache.c index 4362b11f4..a363f312c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -350,6 +350,34 @@ int remove_file_from_index(struct index_state *istate, const char *path) return 0; } +static int compare_name(struct cache_entry *ce, const char *path, int namelen) +{ + return namelen != ce_namelen(ce) || memcmp(path, ce->name, namelen); +} + +static int index_name_pos_also_unmerged(struct index_state *istate, + const char *path, int namelen) +{ + int pos = index_name_pos(istate, path, namelen); + struct cache_entry *ce; + + if (pos >= 0) + return pos; + + /* maybe unmerged? */ + pos = -1 - pos; + if (pos >= istate->cache_nr || + compare_name((ce = istate->cache[pos]), path, namelen)) + return -1; + + /* order of preference: stage 2, 1, 3 */ + if (ce_stage(ce) == 1 && pos + 1 < istate->cache_nr && + ce_stage((ce = istate->cache[pos + 1])) == 2 && + !compare_name(ce, path, namelen)) + pos++; + return pos; +} + int add_file_to_index(struct index_state *istate, const char *path, int verbose) { int size, namelen; @@ -380,7 +408,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) * from it, otherwise assume unexecutable regular file. */ struct cache_entry *ent; - int pos = index_name_pos(istate, path, namelen); + int pos = index_name_pos_also_unmerged(istate, path, namelen); ent = (0 <= pos) ? istate->cache[pos] : NULL; ce->ce_mode = ce_mode_from_stat(ent, st.st_mode); @@ -95,7 +95,7 @@ void verify_non_filename(const char *prefix, const char *arg) const char *name; struct stat st; - if (is_inside_git_dir()) + if (!is_inside_work_tree() || is_inside_git_dir()) return; if (*arg == '-') return; /* flag */ @@ -174,41 +174,96 @@ static int inside_git_dir = -1; int is_inside_git_dir(void) { - if (inside_git_dir < 0) { - char buffer[1024]; - - if (is_bare_repository()) - return (inside_git_dir = 1); - if (getcwd(buffer, sizeof(buffer))) { - const char *git_dir = get_git_dir(), *cwd = buffer; - while (*git_dir && *git_dir == *cwd) { - git_dir++; - cwd++; - } - inside_git_dir = !*git_dir; - } else - inside_git_dir = 0; + if (inside_git_dir >= 0) + return inside_git_dir; + die("BUG: is_inside_git_dir called before setup_git_directory"); +} + +static int inside_work_tree = -1; + +int is_inside_work_tree(void) +{ + if (inside_git_dir >= 0) + return inside_work_tree; + die("BUG: is_inside_work_tree called before setup_git_directory"); +} + +static char *gitworktree_config; + +static int git_setup_config(const char *var, const char *value) +{ + if (!strcmp(var, "core.worktree")) { + if (gitworktree_config) + strlcpy(gitworktree_config, value, PATH_MAX); + return 0; } - return inside_git_dir; + return git_default_config(var, value); } const char *setup_git_directory_gently(int *nongit_ok) { static char cwd[PATH_MAX+1]; - const char *gitdirenv; - int len, offset; + char worktree[PATH_MAX+1], gitdir[PATH_MAX+1]; + const char *gitdirenv, *gitworktree; + int wt_rel_gitdir = 0; - /* - * If GIT_DIR is set explicitly, we're not going - * to do any discovery, but we still do repository - * validation. - */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); - if (gitdirenv) { - if (PATH_MAX - 40 < strlen(gitdirenv)) - die("'$%s' too big", GIT_DIR_ENVIRONMENT); - if (is_git_directory(gitdirenv)) + if (!gitdirenv) { + int len, offset; + + if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/') + die("Unable to read current working directory"); + + offset = len = strlen(cwd); + for (;;) { + if (is_git_directory(".git")) + break; + if (offset == 0) { + offset = -1; + break; + } + chdir(".."); + while (cwd[--offset] != '/') + ; /* do nothing */ + } + + if (offset >= 0) { + inside_work_tree = 1; + git_config(git_default_config); + if (offset == len) { + inside_git_dir = 0; + return NULL; + } + + cwd[len++] = '/'; + cwd[len] = '\0'; + inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/"); + return cwd + offset + 1; + } + + if (chdir(cwd)) + die("Cannot come back to cwd"); + if (!is_git_directory(".")) { + if (nongit_ok) { + *nongit_ok = 1; + return NULL; + } + die("Not a git repository"); + } + setenv(GIT_DIR_ENVIRONMENT, cwd, 1); + gitdirenv = getenv(GIT_DIR_ENVIRONMENT); + if (!gitdirenv) + die("getenv after setenv failed"); + } + + if (PATH_MAX - 40 < strlen(gitdirenv)) { + if (nongit_ok) { + *nongit_ok = 1; return NULL; + } + die("$%s too big", GIT_DIR_ENVIRONMENT); + } + if (!is_git_directory(gitdirenv)) { if (nongit_ok) { *nongit_ok = 1; return NULL; @@ -218,41 +273,92 @@ const char *setup_git_directory_gently(int *nongit_ok) if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/') die("Unable to read current working directory"); + if (chdir(gitdirenv)) { + if (nongit_ok) { + *nongit_ok = 1; + return NULL; + } + die("Cannot change directory to $%s '%s'", + GIT_DIR_ENVIRONMENT, gitdirenv); + } + if (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/') + die("Unable to read current working directory"); + if (chdir(cwd)) + die("Cannot come back to cwd"); - offset = len = strlen(cwd); - for (;;) { - if (is_git_directory(".git")) - break; - chdir(".."); - do { - if (!offset) { - if (is_git_directory(cwd)) { - if (chdir(cwd)) - die("Cannot come back to cwd"); - setenv(GIT_DIR_ENVIRONMENT, cwd, 1); - inside_git_dir = 1; - return NULL; - } - if (nongit_ok) { - if (chdir(cwd)) - die("Cannot come back to cwd"); - *nongit_ok = 1; - return NULL; - } - die("Not a git repository"); + /* + * In case there is a work tree we may change the directory, + * therefore make GIT_DIR an absolute path. + */ + if (gitdirenv[0] != '/') { + setenv(GIT_DIR_ENVIRONMENT, gitdir, 1); + gitdirenv = getenv(GIT_DIR_ENVIRONMENT); + if (!gitdirenv) + die("getenv after setenv failed"); + if (PATH_MAX - 40 < strlen(gitdirenv)) { + if (nongit_ok) { + *nongit_ok = 1; + return NULL; } - } while (cwd[--offset] != '/'); + die("$%s too big after expansion to absolute path", + GIT_DIR_ENVIRONMENT); + } + } + + strcat(cwd, "/"); + strcat(gitdir, "/"); + inside_git_dir = !prefixcmp(cwd, gitdir); + + gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT); + if (!gitworktree) { + gitworktree_config = worktree; + worktree[0] = '\0'; + } + git_config(git_setup_config); + if (!gitworktree) { + gitworktree_config = NULL; + if (worktree[0]) + gitworktree = worktree; + if (gitworktree && gitworktree[0] != '/') + wt_rel_gitdir = 1; + } + + if (wt_rel_gitdir && chdir(gitdirenv)) + die("Cannot change directory to $%s '%s'", + GIT_DIR_ENVIRONMENT, gitdirenv); + if (gitworktree && chdir(gitworktree)) { + if (nongit_ok) { + if (wt_rel_gitdir && chdir(cwd)) + die("Cannot come back to cwd"); + *nongit_ok = 1; + return NULL; + } + if (wt_rel_gitdir) + die("Cannot change directory to working tree '%s'" + " from $%s", gitworktree, GIT_DIR_ENVIRONMENT); + else + die("Cannot change directory to working tree '%s'", + gitworktree); } + if (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/') + die("Unable to read current working directory"); + strcat(worktree, "/"); + inside_work_tree = !prefixcmp(cwd, worktree); - if (offset == len) + if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) && + strcmp(worktree, gitdir)) { + inside_git_dir = 0; + } + + if (!inside_work_tree) { + if (chdir(cwd)) + die("Cannot come back to cwd"); return NULL; + } - /* Make "offset" point to past the '/', and add a '/' at the end */ - offset++; - cwd[len++] = '/'; - cwd[len] = 0; - inside_git_dir = !prefixcmp(cwd + offset, ".git/"); - return cwd + offset; + if (!strcmp(cwd, worktree)) + return NULL; + return cwd+strlen(worktree); } int git_config_perm(const char *var, const char *value) diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 27486de4d..a2c11c463 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -471,11 +471,57 @@ test_expect_success bool ' done && cmp expect result' -test_expect_failure 'invalid bool' ' +test_expect_failure 'invalid bool (--get)' ' git-config bool.nobool foobar && git-config --bool --get bool.nobool' +test_expect_failure 'invalid bool (set)' ' + + git-config --bool bool.nobool foobar' + +rm .git/config + +cat > expect <<\EOF +[bool] + true1 = true + true2 = true + true3 = true + true4 = true + false1 = false + false2 = false + false3 = false + false4 = false +EOF + +test_expect_success 'set --bool' ' + + git-config --bool bool.true1 01 && + git-config --bool bool.true2 -1 && + git-config --bool bool.true3 YeS && + git-config --bool bool.true4 true && + git-config --bool bool.false1 000 && + git-config --bool bool.false2 "" && + git-config --bool bool.false3 nO && + git-config --bool bool.false4 FALSE && + cmp expect .git/config' + +rm .git/config + +cat > expect <<\EOF +[int] + val1 = 1 + val2 = -1 + val3 = 5242880 +EOF + +test_expect_success 'set --int' ' + + git-config --int int.val1 01 && + git-config --int int.val2 -1 && + git-config --int int.val3 5m && + cmp expect .git/config' + rm .git/config git-config quote.leading " test" diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh new file mode 100755 index 000000000..ec4996637 --- /dev/null +++ b/t/t1500-rev-parse.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +test_description='test git rev-parse' +. ./test-lib.sh + +test_rev_parse() { + name=$1 + shift + + test_expect_success "$name: is-bare-repository" \ + "test '$1' = \"\$(git rev-parse --is-bare-repository)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-git-dir" \ + "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-work-tree" \ + "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: prefix" \ + "test '$1' = \"\$(git rev-parse --show-prefix)\"" + shift + [ $# -eq 0 ] && return +} + +test_rev_parse toplevel false false true '' + +cd .git || exit 1 +test_rev_parse .git/ false true true .git/ +cd objects || exit 1 +test_rev_parse .git/objects/ false true true .git/objects/ +cd ../.. || exit 1 + +mkdir -p sub/dir || exit 1 +cd sub/dir || exit 1 +test_rev_parse subdirectory false false true sub/dir/ +cd ../.. || exit 1 + +git config core.bare true +test_rev_parse 'core.bare = true' true false true + +git config --unset core.bare +test_rev_parse 'core.bare undefined' false false true + +mkdir work || exit 1 +cd work || exit 1 +export GIT_DIR=../.git +export GIT_CONFIG="$GIT_DIR"/config + +git config core.bare false +test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true '' + +git config core.bare true +test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false true '' + +git config --unset core.bare +test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true '' + +mv ../.git ../repo.git || exit 1 +export GIT_DIR=../repo.git +export GIT_CONFIG="$GIT_DIR"/config + +git config core.bare false +test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true '' + +git config core.bare true +test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false true '' + +git config --unset core.bare +test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' true false true '' + +test_done diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh new file mode 100755 index 000000000..aadeeab9a --- /dev/null +++ b/t/t1501-worktree.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='test separate work tree' +. ./test-lib.sh + +test_rev_parse() { + name=$1 + shift + + test_expect_success "$name: is-bare-repository" \ + "test '$1' = \"\$(git rev-parse --is-bare-repository)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-git-dir" \ + "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-work-tree" \ + "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: prefix" \ + "test '$1' = \"\$(git rev-parse --show-prefix)\"" + shift + [ $# -eq 0 ] && return +} + +mkdir -p work/sub/dir || exit 1 +mv .git repo.git || exit 1 + +say "core.worktree = relative path" +export GIT_DIR=repo.git +export GIT_CONFIG=$GIT_DIR/config +unset GIT_WORK_TREE +git config core.worktree ../work +test_rev_parse 'outside' false false false +cd work || exit 1 +export GIT_DIR=../repo.git +export GIT_CONFIG=$GIT_DIR/config +test_rev_parse 'inside' false false true '' +cd sub/dir || exit 1 +export GIT_DIR=../../../repo.git +export GIT_CONFIG=$GIT_DIR/config +test_rev_parse 'subdirectory' false false true sub/dir/ +cd ../../.. || exit 1 + +say "core.worktree = absolute path" +export GIT_DIR=$(pwd)/repo.git +export GIT_CONFIG=$GIT_DIR/config +git config core.worktree "$(pwd)/work" +test_rev_parse 'outside' false false false +cd work || exit 1 +test_rev_parse 'inside' false false true '' +cd sub/dir || exit 1 +test_rev_parse 'subdirectory' false false true sub/dir/ +cd ../../.. || exit 1 + +say "GIT_WORK_TREE=relative path (override core.worktree)" +export GIT_DIR=$(pwd)/repo.git +export GIT_CONFIG=$GIT_DIR/config +git config core.worktree non-existent +export GIT_WORK_TREE=work +test_rev_parse 'outside' false false false +cd work || exit 1 +export GIT_WORK_TREE=. +test_rev_parse 'inside' false false true '' +cd sub/dir || exit 1 +export GIT_WORK_TREE=../.. +test_rev_parse 'subdirectory' false false true sub/dir/ +cd ../../.. || exit 1 + +mv work repo.git/work + +say "GIT_WORK_TREE=absolute path, work tree below git dir" +export GIT_DIR=$(pwd)/repo.git +export GIT_CONFIG=$GIT_DIR/config +export GIT_WORK_TREE=$(pwd)/repo.git/work +test_rev_parse 'outside' false false false +cd repo.git || exit 1 +test_rev_parse 'in repo.git' false true false +cd objects || exit 1 +test_rev_parse 'in repo.git/objects' false true false +cd ../work || exit 1 +test_rev_parse 'in repo.git/work' false false true '' +cd sub/dir || exit 1 +test_rev_parse 'in repo.git/sub/dir' false false true sub/dir/ +cd ../../../.. || exit 1 + +test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index ad8cc7d4a..0d80c6aea 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -110,4 +110,30 @@ test_expect_success 'check correct prefix detection' ' git add 1/2/a 1/3/b 1/2/c ' +test_expect_success 'git add and filemode=0 with unmerged entries' ' + echo 1 > stage1 && + echo 2 > stage2 && + echo 3 > stage3 && + for s in 1 2 3 + do + echo "100755 $(git hash-object -w stage$s) $s file" + done | git update-index --index-info && + git config core.filemode 0 && + echo new > file && + git add file && + git ls-files --stage | grep "^100755 .* 0 file$" +' + +test_expect_success 'git add and filemode=0 prefers stage 2 over stage 1' ' + git rm --cached -f file && + ( + echo "100644 $(git hash-object -w stage1) 1 file" + echo "100755 $(git hash-object -w stage2) 2 file" + ) | git update-index --index-info && + git config core.filemode 0 && + echo new > file && + git add file && + git ls-files --stage | grep "^100755 .* 0 file$" +' + test_done diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh new file mode 100755 index 000000000..63f950b17 --- /dev/null +++ b/t/t3902-quoted.sh @@ -0,0 +1,126 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='quoted output' + +. ./test-lib.sh + +FN='濱野' +GN='純' +HT=' ' +LF=' +' +DQ='"' + +for_each_name () { + for name in \ + Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \ + "$FN$HT$GN" "$FN$LF$GN" "$FN $GN" "$FN$GN" "$FN$DQ$GN" \ + "With SP in it" + do + eval "$1" + done +} + +test_expect_success setup ' + + for_each_name "echo initial >\"\$name\"" + git add . && + git commit -q -m Initial && + + for_each_name "echo second >\"\$name\"" && + git commit -a -m Second + + for_each_name "echo modified >\"\$name\"" + +' + +cat >expect.quoted <<\EOF +Name +"Name and a\nLF" +"Name and an\tHT" +"Name\"" +With SP in it +"\346\277\261\351\207\216\t\347\264\224" +"\346\277\261\351\207\216\n\347\264\224" +"\346\277\261\351\207\216 \347\264\224" +"\346\277\261\351\207\216\"\347\264\224" +"\346\277\261\351\207\216\347\264\224" +EOF + +cat >expect.raw <<\EOF +Name +"Name and a\nLF" +"Name and an\tHT" +"Name\"" +With SP in it +"濱野\t純" +"濱野\n純" +濱野 純 +"濱野\"純" +濱野純 +EOF + +test_expect_success 'check fully quoted output from ls-files' ' + + git ls-files >current && diff -u expect.quoted current + +' + +test_expect_success 'check fully quoted output from diff-files' ' + + git diff --name-only >current && + diff -u expect.quoted current + +' + +test_expect_success 'check fully quoted output from diff-index' ' + + git diff --name-only HEAD >current && + diff -u expect.quoted current + +' + +test_expect_success 'check fully quoted output from diff-tree' ' + + git diff --name-only HEAD^ HEAD >current && + diff -u expect.quoted current + +' + +test_expect_success 'setting core.quotepath' ' + + git config --bool core.quotepath false + +' + +test_expect_success 'check fully quoted output from ls-files' ' + + git ls-files >current && diff -u expect.raw current + +' + +test_expect_success 'check fully quoted output from diff-files' ' + + git diff --name-only >current && + diff -u expect.raw current + +' + +test_expect_success 'check fully quoted output from diff-index' ' + + git diff --name-only HEAD >current && + diff -u expect.raw current + +' + +test_expect_success 'check fully quoted output from diff-tree' ' + + git diff --name-only HEAD^ HEAD >current && + diff -u expect.raw current + +' + +test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 08d58e1c8..c0fa2ba40 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -226,7 +226,7 @@ test_expect_success 'push with colon-less refspec (3)' ' git branch -f frotz master && git push testrepo frotz && check_push_result $the_commit heads/frotz && - test "$( cd testrepo && git show-ref | wc -l )" = 1 + test 1 = $( cd testrepo && git show-ref | wc -l ) ' test_expect_success 'push with colon-less refspec (4)' ' @@ -239,7 +239,7 @@ test_expect_success 'push with colon-less refspec (4)' ' git tag -f frotz && git push testrepo frotz && check_push_result $the_commit tags/frotz && - test "$( cd testrepo && git show-ref | wc -l )" = 1 + test 1 = $( cd testrepo && git show-ref | wc -l ) ' diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg Binary files differnew file mode 100644 index 000000000..abace962b --- /dev/null +++ b/t/t7004/trustdb.gpg diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 033177068..641303e0a 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -38,7 +38,7 @@ echo >empty && git commit -q -m "First Commit" && git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && - GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" || + GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" || exit 1 # note that cvs doesn't accept absolute pathnames @@ -255,7 +255,7 @@ rm -fr "$SERVERDIR" cd "$WORKDIR" && git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && -GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" || +GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" || exit 1 test_expect_success 'cvs update (create new file)' \ diff --git a/t/test-lib.sh b/t/test-lib.sh index 8bf4cf49a..78d7e87e8 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -26,6 +26,7 @@ GIT_COMMITTER_EMAIL=committer@example.com GIT_COMMITTER_NAME='C O Mitter' unset GIT_DIFF_OPTS unset GIT_DIR +unset GIT_WORK_TREE unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY |