diff options
94 files changed, 3453 insertions, 605 deletions
diff --git a/.gitignore b/.gitignore index 81e12c0621..4589cf0e26 100644 --- a/.gitignore +++ b/.gitignore @@ -178,6 +178,7 @@ /gitweb/static/gitweb.min.* /test-chmtime /test-ctype +/test-config /test-date /test-delta /test-dump-cache-tree @@ -200,6 +201,7 @@ /test-sha1 /test-sigchain /test-string-list +/test-submodule-config /test-subprocess /test-svn-fe /test-urlmatch-normalization diff --git a/Documentation/config.txt b/Documentation/config.txt index c55c22ab7b..81812530a4 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -201,6 +201,10 @@ advice.*:: rmHints:: In case of failure in the output of linkgit:git-rm[1], show directions on how to proceed from the current state. + checkoutLocked:: + In multiple checkout setup, attempting to checkout a + branch already checked out elsewhere will fail. Show + some useful options to proceed. -- core.fileMode:: @@ -380,6 +384,8 @@ false), while all other repositories are assumed to be bare (bare core.worktree:: Set the path to the root of the working tree. + If GIT_COMMON_DIR environment variable is set, core.worktree + is ignored and not used for determining the root of working tree. This can be overridden by the GIT_WORK_TREE environment variable and the '--work-tree' command-line option. The value can be an absolute path or relative to the path to @@ -1209,6 +1215,13 @@ gc.pruneexpire:: "now" may be used to disable this grace period and always prune unreachable objects immediately. +gc.prunereposexpire:: + When 'git gc' is run, it will call + 'prune --repos --expire 3.months.ago'. + Override the grace period with this config variable. The value + "now" may be used to disable the grace period and prune + $GIT_DIR/repos immediately. + gc.reflogexpire:: gc.<pattern>.reflogexpire:: 'git reflog expire' removes reflog entries older than diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 33ad2adf5c..38c70c5104 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -225,6 +225,13 @@ This means that you can use `git checkout -p` to selectively discard edits from your current working tree. See the ``Interactive Mode'' section of linkgit:git-add[1] to learn how to operate the `--patch` mode. +--to=<path>:: + Check out a new branch in a separate working directory at + `<path>`. A new working directory is linked to the current + repository, sharing everything except working directory + specific files such as HEAD, index... See "MULTIPLE CHECKOUT + MODE" section for more information. + <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that @@ -388,6 +395,33 @@ $ git reflog -2 HEAD # or $ git log -g -2 HEAD ------------ +MULTIPLE CHECKOUT MODE +---------------------- +Normally a working directory is attached to repository. When "git +checkout --to" is used, a new working directory is attached to the +current repository. This new working directory is called "linked +checkout" as compared to the "main checkout" prepared by "git init" or +"git clone". A repository has one main checkout and zero or more +linked checkouts. + +All checkouts share the same repository. Linked checkouts see the +repository a bit different from the main checkout. When the checkout +"new" reads the path $GIT_DIR/HEAD for example, the actual path +returned could be $GIT_DIR/repos/new/HEAD. This ensures checkouts +won't step on each other. + +Each linked checkout has a private space in $GIT_DIR/repos, usually +named after the base name of the working directory with a number added +to make it unique. The linked checkout's $GIT_DIR points to this +private space while $GIT_COMMON_DIR points to the main checkout's +$GIT_DIR. These settings are done by "git checkout --to". + +Because in this mode $GIT_DIR becomes a lightweight virtual file +system where a path could be rewritten to some place else, accessing +$GIT_DIR from scripts should use `git rev-parse --git-path` to resolve +a path instead of using it directly unless the path is known to be +private to the working directory. + EXAMPLES -------- diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index 7a493c80f7..50e39eceb7 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -48,6 +48,9 @@ OPTIONS --expire <time>:: Only expire loose objects older than <time>. +--repos:: + Prune directories in $GIT_DIR/repos. + <head>...:: In addition to objects reachable from any of our references, keep objects diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 0b84769bd9..c10cc62d1c 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -215,6 +215,9 @@ If `$GIT_DIR` is not defined and the current directory is not detected to lie in a Git repository or work tree print a message to stderr and exit with nonzero status. +--git-common-dir:: + Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`. + --is-inside-git-dir:: When the current working directory is below the repository directory print "true", otherwise "false". @@ -232,6 +235,13 @@ print a message to stderr and exit with nonzero status. repository. If <path> is a gitfile then the resolved path to the real repository is printed. +--git-path <path>:: + Resolve "$GIT_DIR/<path>" and takes other path relocation + variables such as $GIT_OBJECT_DIRECTORY, + $GIT_INDEX_FILE... into account. For example, if + $GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse + --git-path objects/abc" returns /foo/bar/abc. + --show-cdup:: When the command is invoked from a subdirectory, show the path of the top-level directory relative to the current diff --git a/Documentation/git.txt b/Documentation/git.txt index de7b870a35..e1d85688bf 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -796,6 +796,15 @@ Git so take care if using Cogito etc. an explicit repository directory set via 'GIT_DIR' or on the command line. +'GIT_COMMON_DIR':: + If this variable is set to a path, non-worktree files that are + normally in $GIT_DIR will be taken from this path + instead. Worktree-specific files such as HEAD or index are + taken from $GIT_DIR. See linkgit:gitrepository-layout[5] and + the section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1] + details. This variable has lower precedence than other path + variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY... + Git Commits ~~~~~~~~~~~ 'GIT_AUTHOR_NAME':: diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index 79653f3134..4a531777f8 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for use with dumb transports but otherwise is OK as long as `objects/info/alternates` points at the object stores it borrows from. ++ +This directory is ignored if $GIT_COMMON_DIR is set and +"$GIT_COMMON_DIR/objects" will be used instead. objects/[0-9a-f][0-9a-f]:: A newly created object is stored in its own file. @@ -92,7 +95,8 @@ refs:: References are stored in subdirectories of this directory. The 'git prune' command knows to preserve objects reachable from refs found in this directory and - its subdirectories. + its subdirectories. This directory is ignored if $GIT_COMMON_DIR + is set and "$GIT_COMMON_DIR/refs" will be used instead. refs/heads/`name`:: records tip-of-the-tree commit objects of branch `name` @@ -114,7 +118,8 @@ refs/replace/`<obj-sha1>`:: packed-refs:: records the same information as refs/heads/, refs/tags/, and friends record in a more efficient way. See - linkgit:git-pack-refs[1]. + linkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR + is set and "$GIT_COMMON_DIR/packed-refs" will be used instead. HEAD:: A symref (see glossary) to the `refs/heads/` namespace @@ -133,6 +138,11 @@ being a symref to point at the current branch. Such a state is often called 'detached HEAD.' See linkgit:git-checkout[1] for details. +config:: + Repository specific configuration file. This file is ignored if + $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be + used instead. + branches:: A slightly deprecated way to store shorthands to be used to specify a URL to 'git fetch', 'git pull' and 'git push'. @@ -140,7 +150,10 @@ branches:: 'name' can be given to these commands in place of 'repository' argument. See the REMOTES section in linkgit:git-fetch[1] for details. This mechanism is legacy - and not likely to be found in modern repositories. + and not likely to be found in modern repositories. This + directory is ignored if $GIT_COMMON_DIR is set and + "$GIT_COMMON_DIR/branches" will be used instead. + hooks:: Hooks are customization scripts used by various Git @@ -149,7 +162,9 @@ hooks:: default. To enable, the `.sample` suffix has to be removed from the filename by renaming. Read linkgit:githooks[5] for more details about - each hook. + each hook. This directory is ignored if $GIT_COMMON_DIR is set + and "$GIT_COMMON_DIR/hooks" will be used instead. + index:: The current index file for the repository. It is @@ -161,7 +176,8 @@ sharedindex.<SHA-1>:: info:: Additional information about the repository is recorded - in this directory. + in this directory. This directory is ignored if $GIT_COMMON_DIR + is set and "$GIT_COMMON_DIR/index" will be used instead. info/refs:: This file helps dumb transports discover what refs are @@ -201,12 +217,16 @@ remotes:: when interacting with remote repositories via 'git fetch', 'git pull' and 'git push' commands. See the REMOTES section in linkgit:git-fetch[1] for details. This mechanism is legacy - and not likely to be found in modern repositories. + and not likely to be found in modern repositories. This + directory is ignored if $GIT_COMMON_DIR is set and + "$GIT_COMMON_DIR/remotes" will be used instead. logs:: Records of changes made to refs are stored in this directory. See linkgit:git-update-ref[1] - for more information. + for more information. This directory is ignored if + $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/logs" will be used + instead. logs/refs/heads/`name`:: Records all changes made to the branch tip named `name`. @@ -217,10 +237,47 @@ logs/refs/tags/`name`:: shallow:: This is similar to `info/grafts` but is internally used and maintained by shallow clone mechanism. See `--depth` - option to linkgit:git-clone[1] and linkgit:git-fetch[1]. + option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This + file is ignored if $GIT_COMMON_DIR is set and + "$GIT_COMMON_DIR/shallow" will be used instead. + +commondir:: + If this file exists, $GIT_COMMON_DIR (see linkgit:git[1]) will + be set to the path specified in this file if it is not + explicitly set. If the specified path is relative, it is + relative to $GIT_DIR. The repository with commondir is + incomplete without the repository pointed by "commondir". modules:: - Contains the git-repositories of the submodules. + Contains the git-repositories of the submodules. This + directory is ignored if $GIT_COMMON_DIR is set and + "$GIT_COMMON_DIR/modules" will be used instead. + +repos:: + Contains worktree specific information of linked + checkouts. Each subdirectory contains the worktree-related + part of a linked checkout. This directory is ignored if + $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/repos" will be + used instead. + +repos/<id>/gitdir:: + A text file containing the absolute path back to the .git file + that points to here. This is used to check if the linked + repository has been manually removed and there is no need to + keep this directory any more. mtime of this file should be + updated every time the linked repository is accessed. + +repos/<id>/locked:: + If this file exists, the linked repository may be on a + portable device and not available. It does not mean that the + linked repository is gone and `repos/<id>` could be + removed. The file's content contains a reason string on why + the repository is locked. + +repos/<id>/link:: + If this file exists, it is a hard link to the linked .git + file. It is used to detect if the linked repository is + manually removed. SEE ALSO -------- diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt index edd5018e15..21f280ca6d 100644 --- a/Documentation/technical/api-config.txt +++ b/Documentation/technical/api-config.txt @@ -77,6 +77,86 @@ To read a specific file in git-config format, use `git_config_from_file`. This takes the same callback and data parameters as `git_config`. +Querying For Specific Variables +------------------------------- + +For programs wanting to query for specific variables in a non-callback +manner, the config API provides two functions `git_config_get_value` +and `git_config_get_value_multi`. They both read values from an internal +cache generated previously from reading the config files. + +`int git_config_get_value(const char *key, const char **value)`:: + + Finds the highest-priority value for the configuration variable `key`, + stores the pointer to it in `value` and returns 0. When the + configuration variable `key` is not found, returns 1 without touching + `value`. The caller should not free or modify `value`, as it is owned + by the cache. + +`const struct string_list *git_config_get_value_multi(const char *key)`:: + + Finds and returns the value list, sorted in order of increasing priority + for the configuration variable `key`. When the configuration variable + `key` is not found, returns NULL. The caller should not free or modify + the returned pointer, as it is owned by the cache. + +`void git_config_clear(void)`:: + + Resets and invalidates the config cache. + +The config API also provides type specific API functions which do conversion +as well as retrieval for the queried variable, including: + +`int git_config_get_int(const char *key, int *dest)`:: + + Finds and parses the value to an integer for the configuration variable + `key`. Dies on error; otherwise, stores the value of the parsed integer in + `dest` and returns 0. When the configuration variable `key` is not found, + returns 1 without touching `dest`. + +`int git_config_get_ulong(const char *key, unsigned long *dest)`:: + + Similar to `git_config_get_int` but for unsigned longs. + +`int git_config_get_bool(const char *key, int *dest)`:: + + Finds and parses the value into a boolean value, for the configuration + variable `key` respecting keywords like "true" and "false". Integer + values are converted into true/false values (when they are non-zero or + zero, respectively). Other values cause a die(). If parsing is successful, + stores the value of the parsed result in `dest` and returns 0. When the + configuration variable `key` is not found, returns 1 without touching + `dest`. + +`int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)`:: + + Similar to `git_config_get_bool`, except that integers are copied as-is, + and `is_bool` flag is unset. + +`int git_config_get_maybe_bool(const char *key, int *dest)`:: + + Similar to `git_config_get_bool`, except that it returns -1 on error + rather than dying. + +`int git_config_get_string_const(const char *key, const char **dest)`:: + + Allocates and copies the retrieved string into the `dest` parameter for + the configuration variable `key`; if NULL string is given, prints an + error message and returns -1. When the configuration variable `key` is + not found, returns 1 without touching `dest`. + +`int git_config_get_string(const char *key, char **dest)`:: + + Similar to `git_config_get_string_const`, except that retrieved value + copied into the `dest` parameter is a mutable string. + +`int git_config_get_pathname(const char *key, const char **dest)`:: + + Similar to `git_config_get_string`, but expands `~` or `~user` into + the user's home directory when found at the beginning of the path. + +See test-config.c for usage examples. + Value Parsing Helpers --------------------- @@ -134,6 +214,68 @@ int read_file_with_include(const char *file, config_fn_t fn, void *data) `git_config` respects includes automatically. The lower-level `git_config_from_file` does not. +Custom Configsets +----------------- + +A `config_set` can be used to construct an in-memory cache for +config-like files that the caller specifies (i.e., files like `.gitmodules`, +`~/.gitconfig` etc.). For example, + +--------------------------------------- +struct config_set gm_config; +git_configset_init(&gm_config); +int b; +/* we add config files to the config_set */ +git_configset_add_file(&gm_config, ".gitmodules"); +git_configset_add_file(&gm_config, ".gitmodules_alt"); + +if (!git_configset_get_bool(gm_config, "submodule.frotz.ignore", &b)) { + /* hack hack hack */ +} + +/* when we are done with the configset */ +git_configset_clear(&gm_config); +---------------------------------------- + +Configset API provides functions for the above mentioned work flow, including: + +`void git_configset_init(struct config_set *cs)`:: + + Initializes the config_set `cs`. + +`int git_configset_add_file(struct config_set *cs, const char *filename)`:: + + Parses the file and adds the variable-value pairs to the `config_set`, + dies if there is an error in parsing the file. Returns 0 on success, or + -1 if the file does not exist or is inaccessible. The user has to decide + if he wants to free the incomplete configset or continue using it when + the function returns -1. + +`int git_configset_get_value(struct config_set *cs, const char *key, const char **value)`:: + + Finds the highest-priority value for the configuration variable `key` + and config set `cs`, stores the pointer to it in `value` and returns 0. + When the configuration variable `key` is not found, returns 1 without + touching `value`. The caller should not free or modify `value`, as it + is owned by the cache. + +`const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)`:: + + Finds and returns the value list, sorted in order of increasing priority + for the configuration variable `key` and config set `cs`. When the + configuration variable `key` is not found, returns NULL. The caller + should not free or modify the returned pointer, as it is owned by the cache. + +`void git_configset_clear(struct config_set *cs)`:: + + Clears `config_set` structure, removes all saved variable-value pairs. + +In addition to above functions, the `config_set` API provides type specific +functions in the vein of `git_config_get_int` and family but with an extra +parameter, pointer to struct `config_set`. +They all behave similarly to the `git_config_get*()` family described in +"Querying For Specific Variables" above. + Writing Config Files -------------------- diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index f9c06a7573..430302c2f4 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -307,6 +307,16 @@ same behaviour as well. use it unless you need the correct position in the file descriptor. +`strbuf_getcwd`:: + + Set the buffer to the path of the current working directory. + +`strbuf_add_absolute_path` + + Add a path to a buffer, converting a relative path to an + absolute one in the process. Symbolic links are not + resolved. + `stripspace`:: Strip whitespace from a buffer. The second parameter controls if diff --git a/Documentation/technical/api-submodule-config.txt b/Documentation/technical/api-submodule-config.txt new file mode 100644 index 0000000000..2ea520ae9a --- /dev/null +++ b/Documentation/technical/api-submodule-config.txt @@ -0,0 +1,63 @@ +submodule config cache API +========================== + +The submodule config cache API allows to read submodule +configurations/information from specified revisions. Internally +information is lazily read into a cache that is used to avoid +unnecessary parsing of the same .gitmodule files. Lookups can be done by +submodule path or name. + +Usage +----- + +To initialize the cache with configurations from the worktree the caller +typically first calls `gitmodules_config()` to read values from the +worktree .gitmodules and then to overlay the local git config values +`parse_submodule_config_option()` from the config parsing +infrastructure. + +The caller can look up information about submodules by using the +`submodule_from_path()` or `submodule_from_name()` functions. They return +a `struct submodule` which contains the values. The API automatically +initializes and allocates the needed infrastructure on-demand. If the +caller does only want to lookup values from revisions the initialization +can be skipped. + +If the internal cache might grow too big or when the caller is done with +the API, all internally cached values can be freed with submodule_free(). + +Data Structures +--------------- + +`struct submodule`:: + + This structure is used to return the information about one + submodule for a certain revision. It is returned by the lookup + functions. + +Functions +--------- + +`void submodule_free()`:: + + Use these to free the internally cached values. + +`int parse_submodule_config_option(const char *var, const char *value)`:: + + Can be passed to the config parsing infrastructure to parse + local (worktree) submodule configurations. + +`const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path)`:: + + Lookup values for one submodule by its commit_sha1 and path or + name. + +`const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name)`:: + + The same as above but lookup by name. + +If given the null_sha1 as commit_sha1 the local configuration of a +submodule will be returned (e.g. consolidated values from local git +configuration and the .gitmodules file in the worktree). + +For an example usage see test-submodule-config.c. @@ -551,6 +551,7 @@ PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS)) TEST_PROGRAMS_NEED_X += test-chmtime TEST_PROGRAMS_NEED_X += test-ctype +TEST_PROGRAMS_NEED_X += test-config TEST_PROGRAMS_NEED_X += test-date TEST_PROGRAMS_NEED_X += test-delta TEST_PROGRAMS_NEED_X += test-dump-cache-tree @@ -573,6 +574,7 @@ TEST_PROGRAMS_NEED_X += test-scrap-cache-tree TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-string-list +TEST_PROGRAMS_NEED_X += test-submodule-config TEST_PROGRAMS_NEED_X += test-subprocess TEST_PROGRAMS_NEED_X += test-svn-fe TEST_PROGRAMS_NEED_X += test-urlmatch-normalization @@ -883,6 +885,7 @@ LIB_OBJS += strbuf.o LIB_OBJS += streaming.o LIB_OBJS += string-list.o LIB_OBJS += submodule.o +LIB_OBJS += submodule-config.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o @@ -1659,7 +1662,11 @@ endif profile:: profile-clean $(MAKE) PROFILE=GEN all $(MAKE) PROFILE=GEN -j1 test - $(MAKE) PROFILE=GEN -j1 perf + @if test -n "$$GIT_PERF_REPO" || test -d .git; then \ + $(MAKE) PROFILE=GEN -j1 perf; \ + else \ + echo "Skipping profile of perf tests..."; \ + fi $(MAKE) PROFILE=USE all profile-fast: profile-clean @@ -33,7 +33,7 @@ int is_directory(const char *path) */ static const char *real_path_internal(const char *path, int die_on_error) { - static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1]; + static struct strbuf sb = STRBUF_INIT; char *retval = NULL; /* @@ -41,16 +41,14 @@ static const char *real_path_internal(const char *path, int die_on_error) * here so that we can chdir() back to it at the end of the * function: */ - char cwd[1024] = ""; - - int buf_index = 1; + struct strbuf cwd = STRBUF_INIT; int depth = MAXDEPTH; char *last_elem = NULL; struct stat st; /* We've already done it */ - if (path == buf || path == next_buf) + if (path == sb.buf) return path; if (!*path) { @@ -60,41 +58,38 @@ static const char *real_path_internal(const char *path, int die_on_error) goto error_out; } - if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) { - if (die_on_error) - die("Too long path: %.*s", 60, path); - else - goto error_out; - } + strbuf_reset(&sb); + strbuf_addstr(&sb, path); while (depth--) { - if (!is_directory(buf)) { - char *last_slash = find_last_dir_sep(buf); + if (!is_directory(sb.buf)) { + char *last_slash = find_last_dir_sep(sb.buf); if (last_slash) { last_elem = xstrdup(last_slash + 1); - last_slash[1] = '\0'; + strbuf_setlen(&sb, last_slash - sb.buf + 1); } else { - last_elem = xstrdup(buf); - *buf = '\0'; + last_elem = xmemdupz(sb.buf, sb.len); + strbuf_reset(&sb); } } - if (*buf) { - if (!*cwd && !getcwd(cwd, sizeof(cwd))) { + if (sb.len) { + if (!cwd.len && strbuf_getcwd(&cwd)) { if (die_on_error) die_errno("Could not get current working directory"); else goto error_out; } - if (chdir(buf)) { + if (chdir(sb.buf)) { if (die_on_error) - die_errno("Could not switch to '%s'", buf); + die_errno("Could not switch to '%s'", + sb.buf); else goto error_out; } } - if (!getcwd(buf, PATH_MAX)) { + if (strbuf_getcwd(&sb)) { if (die_on_error) die_errno("Could not get current working directory"); else @@ -102,48 +97,35 @@ static const char *real_path_internal(const char *path, int die_on_error) } if (last_elem) { - size_t len = strlen(buf); - if (len + strlen(last_elem) + 2 > PATH_MAX) { - if (die_on_error) - die("Too long path name: '%s/%s'", - buf, last_elem); - else - goto error_out; - } - if (len && !is_dir_sep(buf[len - 1])) - buf[len++] = '/'; - strcpy(buf + len, last_elem); + if (sb.len && !is_dir_sep(sb.buf[sb.len - 1])) + strbuf_addch(&sb, '/'); + strbuf_addstr(&sb, last_elem); free(last_elem); last_elem = NULL; } - if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) { - ssize_t len = readlink(buf, next_buf, PATH_MAX); + if (!lstat(sb.buf, &st) && S_ISLNK(st.st_mode)) { + struct strbuf next_sb = STRBUF_INIT; + ssize_t len = strbuf_readlink(&next_sb, sb.buf, 0); if (len < 0) { if (die_on_error) - die_errno("Invalid symlink '%s'", buf); - else - goto error_out; - } - if (PATH_MAX <= len) { - if (die_on_error) - die("symbolic link too long: %s", buf); + die_errno("Invalid symlink '%s'", + sb.buf); else goto error_out; } - next_buf[len] = '\0'; - buf = next_buf; - buf_index = 1 - buf_index; - next_buf = bufs[buf_index]; + strbuf_swap(&sb, &next_sb); + strbuf_release(&next_sb); } else break; } - retval = buf; + retval = sb.buf; error_out: free(last_elem); - if (*cwd && chdir(cwd)) - die_errno("Could not change back to '%s'", cwd); + if (cwd.len && chdir(cwd.buf)) + die_errno("Could not change back to '%s'", cwd.buf); + strbuf_release(&cwd); return retval; } @@ -158,54 +140,16 @@ const char *real_path_if_valid(const char *path) return real_path_internal(path, 0); } -static const char *get_pwd_cwd(void) -{ - static char cwd[PATH_MAX + 1]; - char *pwd; - struct stat cwd_stat, pwd_stat; - if (getcwd(cwd, PATH_MAX) == NULL) - return NULL; - pwd = getenv("PWD"); - if (pwd && strcmp(pwd, cwd)) { - stat(cwd, &cwd_stat); - if ((cwd_stat.st_dev || cwd_stat.st_ino) && - !stat(pwd, &pwd_stat) && - pwd_stat.st_dev == cwd_stat.st_dev && - pwd_stat.st_ino == cwd_stat.st_ino) { - strlcpy(cwd, pwd, PATH_MAX); - } - } - return cwd; -} - /* * Use this to get an absolute path from a relative one. If you want * to resolve links, you should use real_path. - * - * If the path is already absolute, then return path. As the user is - * never meant to free the return value, we're safe. */ const char *absolute_path(const char *path) { - static char buf[PATH_MAX + 1]; - - if (!*path) { - die("The empty string is not a valid path"); - } else if (is_absolute_path(path)) { - if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) - die("Too long path: %.*s", 60, path); - } else { - size_t len; - const char *fmt; - const char *cwd = get_pwd_cwd(); - if (!cwd) - die_errno("Cannot determine the current working directory"); - len = strlen(cwd); - fmt = (len > 0 && is_dir_sep(cwd[len - 1])) ? "%s%s" : "%s/%s"; - if (snprintf(buf, PATH_MAX, fmt, cwd, path) >= PATH_MAX) - die("Too long path: %.*s", 60, path); - } - return buf; + static struct strbuf sb = STRBUF_INIT; + strbuf_reset(&sb); + strbuf_add_absolute_path(&sb, path); + return sb.buf; } /* @@ -15,6 +15,7 @@ int advice_detached_head = 1; int advice_set_upstream_failure = 1; int advice_object_name_warning = 1; int advice_rm_hints = 1; +int advice_checkout_locked = 1; static struct { const char *name; @@ -35,6 +36,7 @@ static struct { { "setupstreamfailure", &advice_set_upstream_failure }, { "objectnamewarning", &advice_object_name_warning }, { "rmhints", &advice_rm_hints }, + { "checkoutlocked", &advice_checkout_locked }, /* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected } @@ -18,6 +18,7 @@ extern int advice_detached_head; extern int advice_set_upstream_failure; extern int advice_object_name_warning; extern int advice_rm_hints; +extern int advice_checkout_locked; int git_default_advice_config(const char *var, const char *value); __attribute__((format (printf, 1, 2))) diff --git a/archive-tar.c b/archive-tar.c index 719b6298e6..603650fa3c 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -192,7 +192,7 @@ static int write_extended_header(struct archiver_args *args, unsigned int mode; memset(&header, 0, sizeof(header)); *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; + mode = 0100666 & ~tar_umask; sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); prepare_header(args, &header, mode, size); write_blocked(&header, sizeof(header)); @@ -300,7 +300,7 @@ static int write_global_extended_header(struct archiver_args *args) strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); memset(&header, 0, sizeof(header)); *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; + mode = 0100666 & ~tar_umask; strcpy(header.name, "pax_global_header"); prepare_header(args, &header, mode, ext_header.len); write_blocked(&header, sizeof(header)); diff --git a/builtin/branch.c b/builtin/branch.c index 0591b22a48..e4265a1f8b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -754,7 +754,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) { - FILE *fp; int status; struct strbuf buf = STRBUF_INIT; struct strbuf name = STRBUF_INIT; @@ -767,8 +766,7 @@ static int edit_branch_description(const char *branch_name) " %s\n" "Lines starting with '%c' will be stripped.\n", branch_name, comment_line_char); - fp = fopen(git_path(edit_description), "w"); - if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { + if (write_file(git_path(edit_description), 0, "%s", buf.buf)) { strbuf_release(&buf); return error(_("could not write branch description template: %s"), strerror(errno)); diff --git a/builtin/checkout.c b/builtin/checkout.c index f71e74531d..f865ed439f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -18,8 +18,10 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "resolve-undo.h" +#include "submodule-config.h" #include "submodule.h" #include "argv-array.h" +#include "sigchain.h" static const char * const checkout_usage[] = { N_("git checkout [options] <branch>"), @@ -48,6 +50,10 @@ struct checkout_opts { const char *prefix; struct pathspec pathspec; struct tree *source_tree; + + const char *new_worktree; + const char **saved_argv; + int new_worktree_mode; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -249,6 +255,9 @@ static int checkout_paths(const struct checkout_opts *opts, die(_("Cannot update paths and switch to branch '%s' at the same time."), opts->new_branch); + if (opts->new_worktree) + die(_("'%s' cannot be used with updating paths"), "--to"); + if (opts->patch_mode) return run_add_interactive(revision, "--patch=checkout", &opts->pathspec); @@ -423,6 +432,11 @@ struct branch_info { const char *name; /* The short name used */ const char *path; /* The full name of a real branch */ struct commit *commit; /* The named commit */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; }; static void setup_branch_path(struct branch_info *branch) @@ -484,7 +498,7 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.dir->flags |= DIR_SHOW_IGNORED; setup_standard_excludes(topts.dir); } - tree = parse_tree_indirect(old->commit ? + tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); @@ -552,6 +566,12 @@ static int merge_working_tree(const struct checkout_opts *opts, } } + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree)) + cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); @@ -582,18 +602,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { int temp; - char log_file[PATH_MAX]; - char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); + struct strbuf log_file = STRBUF_INIT; + int ret; + const char *ref_name; + ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); temp = log_all_ref_updates; log_all_ref_updates = 1; - if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { + ret = log_ref_setup(ref_name, &log_file); + log_all_ref_updates = temp; + strbuf_release(&log_file); + if (ret) { fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); - log_all_ref_updates = temp; return; } - log_all_ref_updates = temp; } } else @@ -791,7 +814,8 @@ static int switch_branches(const struct checkout_opts *opts, return ret; } - if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) + if (!opts->quiet && !old.path && old.commit && + new->commit != old.commit && !opts->new_worktree_mode) orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); @@ -801,6 +825,139 @@ static int switch_branches(const struct checkout_opts *opts, return ret || writeout_error; } +static char *junk_work_tree; +static char *junk_git_dir; +static int is_junk; +static pid_t junk_pid; + +static void remove_junk(void) +{ + struct strbuf sb = STRBUF_INIT; + if (!is_junk || getpid() != junk_pid) + return; + if (junk_git_dir) { + strbuf_addstr(&sb, junk_git_dir); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } + if (junk_work_tree) { + strbuf_addstr(&sb, junk_work_tree); + remove_dir_recursively(&sb, 0); + } + strbuf_release(&sb); +} + +static void remove_junk_on_signal(int signo) +{ + remove_junk(); + sigchain_pop(signo); + raise(signo); +} + +static int prepare_linked_checkout(const struct checkout_opts *opts, + struct branch_info *new) +{ + struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *path = opts->new_worktree, *name; + struct stat st; + struct child_process cp; + int counter = 0, len, ret; + + if (!new->commit) + die(_("no branch specified")); + if (file_exists(path)) + die(_("%s already exists"), path); + + len = strlen(path); + while (len && is_dir_sep(path[len - 1])) + len--; + + for (name = path + len - 1; name > path; name--) + if (is_dir_sep(*name)) { + name++; + break; + } + strbuf_addstr(&sb_repo, + git_path("repos/%.*s", (int)(path + len - name), name)); + len = sb_repo.len; + if (safe_create_leading_directories_const(sb_repo.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_repo.buf); + while (!stat(sb_repo.buf, &st)) { + counter++; + strbuf_setlen(&sb_repo, len); + strbuf_addf(&sb_repo, "%d", counter); + } + name = strrchr(sb_repo.buf, '/') + 1; + + junk_pid = getpid(); + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + + if (mkdir(sb_repo.buf, 0777)) + die_errno(_("could not create directory of '%s'"), sb_repo.buf); + junk_git_dir = xstrdup(sb_repo.buf); + is_junk = 1; + + /* + * lock the incomplete repo so prune won't delete it, unlock + * after the preparation is over. + */ + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + write_file(sb.buf, 1, "initializing\n"); + + strbuf_addf(&sb_git, "%s/.git", path); + if (safe_create_leading_directories_const(sb_git.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_git.buf); + junk_work_tree = xstrdup(path); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); + write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n", + real_path(get_git_common_dir()), name); + /* + * This is to keep resolve_ref() happy. We need a valid HEAD + * or is_git_directory() will reject the directory. Any valid + * value would do because this value will be ignored and + * replaced at the next (real) checkout. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1)); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/commondir", sb_repo.buf); + write_file(sb.buf, 1, "../..\n"); + + if (!opts->quiet) + fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + + setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1); + setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1); + memset(&cp, 0, sizeof(cp)); + cp.git_cmd = 1; + cp.argv = opts->saved_argv; + ret = run_command(&cp); + if (!ret) { + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + } + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + unlink_or_warn(sb.buf); + strbuf_release(&sb); + strbuf_release(&sb_repo); + strbuf_release(&sb_git); + return ret; + +} + static int git_checkout_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.ignoresubmodules")) { @@ -856,12 +1013,87 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) return NULL; } +static void check_linked_checkout(struct branch_info *new, const char *id) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; + const char *start, *end; + + if (id) + strbuf_addf(&path, "%s/repos/%s/HEAD", get_git_common_dir(), id); + else + strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); + + if (strbuf_read_file(&sb, path.buf, 0) < 0 || + !skip_prefix(sb.buf, "ref:", &start)) + goto done; + while (isspace(*start)) + start++; + end = start; + while (*end && !isspace(*end)) + end++; + if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0') + goto done; + if (id) { + strbuf_reset(&path); + strbuf_addf(&path, "%s/repos/%s/gitdir", get_git_common_dir(), id); + if (strbuf_read_file(&gitdir, path.buf, 0) <= 0) + goto done; + strbuf_rtrim(&gitdir); + } else + strbuf_addstr(&gitdir, get_git_common_dir()); + if (advice_checkout_locked) + die(_("'%s' is already checked out at %s\n" + "Either go there and continue working, or detach HEAD using\n" + " git checkout --detach [more options] %s\n" + "or create a new branch based off '%s' using\n" + " git checkout -b <branch name> [more options] %s\n" + "or switch to another branch at the other checkout and retry."), + new->name, gitdir.buf, new->name, new->name, new->name); + else + die(_("'%s' is already checked out at %s"), new->name, gitdir.buf); +done: + strbuf_release(&path); + strbuf_release(&sb); + strbuf_release(&gitdir); +} + +static void check_linked_checkouts(struct branch_info *new) +{ + struct strbuf path = STRBUF_INIT; + DIR *dir; + struct dirent *d; + + strbuf_addf(&path, "%s/repos", get_git_common_dir()); + if ((dir = opendir(path.buf)) == NULL) { + strbuf_release(&path); + return; + } + + /* + * $GIT_COMMON_DIR/HEAD is practically outside + * $GIT_DIR so resolve_ref_unsafe() won't work (it + * uses git_path). Parse the ref ourselves. + */ + check_linked_checkout(new, NULL); + + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + check_linked_checkout(new, d->d_name); + } + strbuf_release(&path); + closedir(dir); +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, struct tree **source_tree, unsigned char rev[20], - const char **new_branch) + const char **new_branch, + int force_detach) { int argcount = 0; unsigned char branch_rev[20]; @@ -983,6 +1215,16 @@ static int parse_branchname_arg(int argc, const char **argv, else new->path = NULL; /* not an existing branch */ + if (new->path && !force_detach && !*new_branch) { + unsigned char sha1[20]; + int flag; + char *head_ref = resolve_refdup("HEAD", sha1, 0, &flag); + if (head_ref && + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) + check_linked_checkouts(new); + free(head_ref); + } + new->commit = lookup_commit_reference_gently(rev, 1); if (!new->commit) { /* not a commit */ @@ -1062,6 +1304,9 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); + if (opts->new_worktree) + return prepare_linked_checkout(opts, new); + if (!new->commit && opts->new_branch) { unsigned char rev[20]; int flag; @@ -1104,6 +1349,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("do not limit pathspecs to sparse entries only")), OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, N_("second guess 'git checkout no-such-branch'")), + OPT_FILENAME(0, "to", &opts.new_worktree, + N_("check a branch out in a separate working directory")), OPT_END(), }; @@ -1112,6 +1359,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.overwrite_ignore = 1; opts.prefix = prefix; + opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2)); + memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1)); + gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1120,6 +1370,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + /* recursive execution from checkout_new_worktree() */ + opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; + if (opts.new_worktree_mode) + opts.new_worktree = NULL; + + if (!opts.new_worktree) + setup_work_tree(); + if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); @@ -1176,7 +1434,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) !opts.new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, &new, &opts.source_tree, - rev, &opts.new_branch); + rev, &opts.new_branch, + opts.force_detach); argv += n; argc -= n; } diff --git a/builtin/clone.c b/builtin/clone.c index bbd169ceb4..17b28cdf5a 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -289,16 +289,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in, '\n') != EOF) { - char *abs_path, abs_buf[PATH_MAX]; + char *abs_path; if (!line.len || line.buf[0] == '#') continue; if (is_absolute_path(line.buf)) { add_to_alternates_file(line.buf); continue; } - abs_path = mkpath("%s/objects/%s", src_repo, line.buf); - normalize_path_copy(abs_buf, abs_path); - add_to_alternates_file(abs_buf); + abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_path, abs_path); + add_to_alternates_file(abs_path); + free(abs_path); } strbuf_release(&line); fclose(in); diff --git a/builtin/commit.c b/builtin/commit.c index 5ed60364ce..ed89515b51 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -42,7 +42,20 @@ static const char * const builtin_status_usage[] = { NULL }; -static const char implicit_ident_advice[] = +static const char implicit_ident_advice_noconfig[] = +N_("Your name and email address were configured automatically based\n" +"on your username and hostname. Please check that they are accurate.\n" +"You can suppress this message by setting them explicitly. Run the\n" +"following command and follow the instructions in your editor to edit\n" +"your configuration file:\n" +"\n" +" git config --global --edit\n" +"\n" +"After doing this, you may fix the identity used for this commit with:\n" +"\n" +" git commit --amend --reset-author\n"); + +static const char implicit_ident_advice_config[] = N_("Your name and email address were configured automatically based\n" "on your username and hostname. Please check that they are accurate.\n" "You can suppress this message by setting them explicitly:\n" @@ -156,7 +169,7 @@ static void determine_whence(struct wt_status *s) whence = FROM_MERGE; else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path("sequencer"))) + if (file_exists(git_path(SEQ_DIR))) sequencer_in_use = 1; } else @@ -340,6 +353,13 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, discard_cache(); read_cache_from(index_lock.filename); + if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { + if (reopen_lock_file(&index_lock) < 0) + die(_("unable to write index file")); + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) + die(_("unable to update temporary index")); + } else + warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; return index_lock.filename; @@ -380,8 +400,12 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (!only && !pathspec.nr) { hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); - if (active_cache_changed) { + if (active_cache_changed + || !cache_tree_fully_valid(active_cache_tree)) { update_main_cache_tree(WRITE_TREE_SILENT); + active_cache_changed = 1; + } + if (active_cache_changed) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) die(_("unable to write new_index file")); @@ -431,6 +455,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, hold_locked_index(&index_lock, 1); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); + update_main_cache_tree(WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); @@ -1402,6 +1427,24 @@ int cmd_status(int argc, const char **argv, const char *prefix) return 0; } +static const char *implicit_ident_advice(void) +{ + char *user_config = NULL; + char *xdg_config = NULL; + int config_exists; + + home_config_paths(&user_config, &xdg_config, "config"); + config_exists = file_exists(user_config) || file_exists(xdg_config); + free(user_config); + free(xdg_config); + + if (config_exists) + return _(implicit_ident_advice_config); + else + return _(implicit_ident_advice_noconfig); + +} + static void print_summary(const char *prefix, const unsigned char *sha1, int initial_commit) { @@ -1440,7 +1483,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { strbuf_addch(&format, '\n'); - strbuf_addstr(&format, _(implicit_ident_advice)); + strbuf_addstr(&format, implicit_ident_advice()); } } strbuf_release(&author_ident); diff --git a/builtin/config.c b/builtin/config.c index fcd8474701..aba71355f8 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -445,6 +445,20 @@ static int get_urlmatch(const char *var, const char *url) return 0; } +static char *default_user_config(void) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, + _("# This is Git's per-user configuration file.\n" + "[core]\n" + "# Please adapt and uncomment the following lines:\n" + "# user = %s\n" + "# email = %s\n"), + ident_default_name(), + ident_default_email()); + return strbuf_detach(&buf, NULL); +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; @@ -551,6 +565,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) } } else if (actions == ACTION_EDIT) { + const char *config_file = given_config_source.file ? + given_config_source.file : git_path("config"); check_argc(argc, 0, 0); if (!given_config_source.file && nongit) die("not in a git directory"); @@ -559,9 +575,18 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (given_config_source.blob) die("editing blobs is not supported"); git_config(git_default_config, NULL); - launch_editor(given_config_source.file ? - given_config_source.file : git_path("config"), - NULL, NULL); + if (use_global_config) { + int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd) { + char *content = default_user_config(); + write_str_in_full(fd, content); + free(content); + close(fd); + } + else if (errno != EEXIST) + die_errno(_("cannot create configuration file %s"), config_file); + } + launch_editor(config_file, NULL, NULL); } else if (actions == ACTION_SET) { int ret; diff --git a/builtin/count-objects.c b/builtin/count-objects.c index a7f70cb858..d3a162095c 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -102,8 +102,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) /* we do not take arguments other than flags for now */ if (argc) usage_with_options(count_objects_usage, opts); - if (verbose) + if (verbose) { report_garbage = real_report_garbage; + report_linked_checkout_garbage(); + } memcpy(path, objdir, len); if (len && objdir[len-1] != '/') path[len++] = '/'; diff --git a/builtin/fetch.c b/builtin/fetch.c index e8d0cca3e4..7de94bcb40 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -12,6 +12,7 @@ #include "parse-options.h" #include "sigchain.h" #include "transport.h" +#include "submodule-config.h" #include "submodule.h" #include "connected.h" #include "argv-array.h" @@ -573,7 +574,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; - char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + char *url; + const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); int want_status; fp = fopen(filename, "a"); @@ -807,7 +809,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - char *filename = git_path("FETCH_HEAD"); + const char *filename = git_path("FETCH_HEAD"); FILE *fp = fopen(filename, "w"); if (!fp) diff --git a/builtin/fsck.c b/builtin/fsck.c index d42a27da89..92120f45ca 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj) printf("dangling %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); if (write_lost_and_found) { - char *filename = git_path("lost-found/%s/%s", + const char *filename = git_path("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", sha1_to_hex(obj->sha1)); FILE *f; - if (safe_create_leading_directories(filename)) { + if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); return; } diff --git a/builtin/gc.c b/builtin/gc.c index 8d219d8c42..0c65808dca 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -33,11 +33,13 @@ static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; static int detach_auto = 1; static const char *prune_expire = "2.weeks.ago"; +static const char *prune_repos_expire = "3.months.ago"; static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT; static struct argv_array reflog = ARGV_ARRAY_INIT; static struct argv_array repack = ARGV_ARRAY_INIT; static struct argv_array prune = ARGV_ARRAY_INIT; +static struct argv_array prune_repos = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; static char *pidfile; @@ -55,6 +57,17 @@ static void remove_pidfile_on_signal(int signo) raise(signo); } +static int git_config_date_string(const char **output, + const char *var, const char *value) +{ + if (value && strcmp(value, "now")) { + unsigned long now = approxidate("now"); + if (approxidate(value) >= now) + return error(_("Invalid %s: '%s'"), var, value); + } + return git_config_string(output, var, value); +} + static int gc_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "gc.packrefs")) { @@ -84,14 +97,10 @@ static int gc_config(const char *var, const char *value, void *cb) detach_auto = git_config_bool(var, value); return 0; } - if (!strcmp(var, "gc.pruneexpire")) { - if (value && strcmp(value, "now")) { - unsigned long now = approxidate("now"); - if (approxidate(value) >= now) - return error(_("Invalid %s: '%s'"), var, value); - } - return git_config_string(&prune_expire, var, value); - } + if (!strcmp(var, "gc.pruneexpire")) + return git_config_date_string(&prune_expire, var, value); + if (!strcmp(var, "gc.prunereposexpire")) + return git_config_date_string(&prune_repos_expire, var, value); return git_default_config(var, value, cb); } @@ -298,7 +307,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL); argv_array_pushl(&repack, "repack", "-d", "-l", NULL); - argv_array_pushl(&prune, "prune", "--expire", NULL ); + argv_array_pushl(&prune, "prune", "--expire", NULL); + argv_array_pushl(&prune_repos, "prune", "--repos", "--expire", NULL); argv_array_pushl(&rerere, "rerere", "gc", NULL); git_config(gc_config, NULL); @@ -368,6 +378,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return error(FAILED_RUN, prune.argv[0]); } + if (prune_repos_expire) { + argv_array_push(&prune_repos, prune_repos_expire); + if (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune_repos.argv[0]); + } + if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) return error(FAILED_RUN, rerere.argv[0]); diff --git a/builtin/init-db.c b/builtin/init-db.c index 56f85e239a..6b7fa5f21a 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -330,19 +330,18 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir, * moving the target repo later on in separate_git_dir() */ git_link = xstrdup(real_path(git_dir)); + set_git_dir(real_path(real_git_dir)); } else { - real_git_dir = real_path(git_dir); + set_git_dir(real_path(git_dir)); git_link = NULL; } - set_git_dir(real_path(real_git_dir)); return 0; } static void separate_git_dir(const char *git_dir) { struct stat st; - FILE *fp; if (!stat(git_link, &st)) { const char *src; @@ -358,11 +357,7 @@ static void separate_git_dir(const char *git_dir) die_errno(_("unable to move %s to %s"), src, git_dir); } - fp = fopen(git_link, "w"); - if (!fp) - die(_("Could not create git link %s"), git_link); - fprintf(fp, "gitdir: %s\n", git_dir); - fclose(fp); + write_file(git_link, 1, "gitdir: %s\n", git_dir); } int init_db(const char *template_dir, unsigned int flags) @@ -426,8 +421,9 @@ int init_db(const char *template_dir, unsigned int flags) static int guess_repository_type(const char *git_dir) { - char cwd[PATH_MAX]; const char *slash; + char *cwd; + int cwd_is_git_dir; /* * "GIT_DIR=. git init" is always bare. @@ -435,9 +431,10 @@ static int guess_repository_type(const char *git_dir) */ if (!strcmp(".", git_dir)) return 1; - if (!getcwd(cwd, sizeof(cwd))) - die_errno(_("cannot tell cwd")); - if (!strcmp(git_dir, cwd)) + cwd = xgetcwd(); + cwd_is_git_dir = !strcmp(git_dir, cwd); + free(cwd); + if (cwd_is_git_dir) return 1; /* * "GIT_DIR=.git or GIT_DIR=something/.git is usually not. @@ -535,10 +532,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) usage(init_db_usage[0]); } if (is_bare_repository_cfg == 1) { - static char git_dir[PATH_MAX+1]; - - setenv(GIT_DIR_ENVIRONMENT, - getcwd(git_dir, sizeof(git_dir)), argc > 0); + char *cwd = xgetcwd(); + setenv(GIT_DIR_ENVIRONMENT, cwd, argc > 0); + free(cwd); } if (init_shared_repository != -1) @@ -572,13 +568,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) git_work_tree_cfg = xstrdup(real_path(rel)); free(rel); } - if (!git_work_tree_cfg) { - git_work_tree_cfg = xcalloc(PATH_MAX, 1); - if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die_errno (_("Cannot access current working directory")); - } + if (!git_work_tree_cfg) + git_work_tree_cfg = xgetcwd(); if (work_tree) - set_git_work_tree(real_path(work_tree)); + set_git_work_tree(work_tree); else set_git_work_tree(git_work_tree_cfg); if (access(get_git_work_tree(), X_OK)) @@ -587,7 +580,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) } else { if (work_tree) - set_git_work_tree(real_path(work_tree)); + set_git_work_tree(work_tree); } set_git_dir_init(git_dir, real_git_dir, 1); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 238b5021eb..b59f5d895e 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2494,6 +2494,7 @@ static void get_object_list(int ac, const char **av) if (get_sha1_hex(line + 10, sha1)) die("not an SHA-1 '%s'", line + 10); register_shallow(sha1); + use_bitmap_index = 0; continue; } die("not a rev '%s'", line); diff --git a/builtin/prune.c b/builtin/prune.c index 144a3bdb33..e72c3917ef 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -112,6 +112,91 @@ static void prune_object_dir(const char *path) } } +static int prune_repo_dir(const char *id, struct strbuf *reason) +{ + struct stat st; + char *path; + int fd, len; + + if (!is_directory(git_path("repos/%s", id))) { + strbuf_addf(reason, _("Removing repos/%s: not a valid directory"), id); + return 1; + } + if (file_exists(git_path("repos/%s/locked", id))) + return 0; + if (stat(git_path("repos/%s/gitdir", id), &st)) { + strbuf_addf(reason, _("Removing repos/%s: gitdir file does not exist"), id); + return 1; + } + fd = open(git_path("repos/%s/gitdir", id), O_RDONLY); + if (fd < 0) { + strbuf_addf(reason, _("Removing repos/%s: unable to read gitdir file (%s)"), + id, strerror(errno)); + return 1; + } + len = st.st_size; + path = xmalloc(len + 1); + read_in_full(fd, path, len); + close(fd); + while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) + len--; + if (!len) { + strbuf_addf(reason, _("Removing repos/%s: invalid gitdir file"), id); + free(path); + return 1; + } + path[len] = '\0'; + if (!file_exists(path)) { + struct stat st_link; + free(path); + /* + * the repo is moved manually and has not been + * accessed since? + */ + if (!stat(git_path("repos/%s/link", id), &st_link) && + st_link.st_nlink > 1) + return 0; + strbuf_addf(reason, _("Removing repos/%s: gitdir file points to non-existent location"), id); + return 1; + } + free(path); + return st.st_mtime <= expire; +} + +static void prune_repos_dir(void) +{ + struct strbuf reason = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + DIR *dir = opendir(git_path("repos")); + struct dirent *d; + int ret; + if (!dir) + return; + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + strbuf_reset(&reason); + if (!prune_repo_dir(d->d_name, &reason)) + continue; + if (show_only || verbose) + printf("%s\n", reason.buf); + if (show_only) + continue; + strbuf_reset(&path); + strbuf_addstr(&path, git_path("repos/%s", d->d_name)); + ret = remove_dir_recursively(&path, 0); + if (ret < 0 && errno == ENOTDIR) + ret = unlink(path.buf); + if (ret) + error(_("failed to remove: %s"), strerror(errno)); + } + closedir(dir); + if (!show_only) + rmdir(git_path("repos")); + strbuf_release(&reason); + strbuf_release(&path); +} + /* * Write errors (particularly out of space) can result in * failed temporary packs (and more rarely indexes and other @@ -138,10 +223,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct progress *progress = NULL; + int prune_repos = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), + OPT_BOOL(0, "repos", &prune_repos, N_("prune .git/repos/")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("expire objects older than <time>")), OPT_END() @@ -154,6 +241,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix) init_revisions(&revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); + + if (prune_repos) { + if (argc) + die(_("--repos does not take extra arguments")); + prune_repos_dir(); + return 0; + } + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index f93ac454b4..f186a4ee3a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -599,7 +599,7 @@ static void run_update_post_hook(struct command *commands) int argc; const char **argv; struct child_process proc; - char *hook; + const char *hook; hook = find_hook("post-update"); for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { diff --git a/builtin/reflog.c b/builtin/reflog.c index e8a8fb13b9..9bd874d070 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, if (!reflog_exists(ref)) goto finish; if (!cmd->dry_run) { - newlog_path = git_pathdup("logs/%s.lock", ref); + newlog_path = mkpathdup("%s.lock", log_file); cb.newlog = fopen(newlog_path, "w"); } diff --git a/builtin/remote.c b/builtin/remote.c index 9a4640dbf0..d5324edd95 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -581,7 +581,7 @@ static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; int i; - char *path = NULL; + const char *path = NULL; strbuf_addf(&buf, "remote.%s.url", remote->name); for (i = 0; i < remote->url_nr; i++) diff --git a/builtin/repack.c b/builtin/repack.c index a77e743b94..31cc047ca3 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -284,7 +284,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -312,7 +313,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (failed) { struct string_list rollback_failure = STRING_LIST_INIT_DUP; for_each_string_list_item(item, &rollback) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/%s", packdir, item->string); fname_old = mkpath("%s/old-%s", packdir, item->string); if (rename(fname_old, fname)) @@ -365,7 +367,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* Remove the "old-" files */ for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname; + const char *fname; fname = mkpath("%s/old-%s%s", packdir, item->string, diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index d85e08cc9c..1ce73e830e 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -531,6 +531,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--git-path")) { + if (!argv[i + 1]) + die("--git-path requires an argument"); + puts(git_path("%s", argv[i + 1])); + i++; + continue; + } if (as_is) { if (show_file(arg, output_prefix) && as_is < 2) verify_filename(prefix, arg, 0); @@ -736,7 +743,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--git-dir")) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); - static char cwd[PATH_MAX]; + char *cwd; int len; if (gitdir) { puts(gitdir); @@ -746,10 +753,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) puts(".git"); continue; } - if (!getcwd(cwd, PATH_MAX)) - die_errno("unable to get current working directory"); + cwd = xgetcwd(); len = strlen(cwd); printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : ""); + free(cwd); + continue; + } + if (!strcmp(arg, "--git-common-dir")) { + puts(get_git_common_dir()); continue; } if (!strcmp(arg, "--resolve-git-dir")) { diff --git a/cache-tree.c b/cache-tree.c index c53f7de2b1..134ab4333d 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -246,9 +246,12 @@ static int update_one(struct cache_tree *it, struct strbuf buffer; int missing_ok = flags & WRITE_TREE_MISSING_OK; int dryrun = flags & WRITE_TREE_DRY_RUN; + int repair = flags & WRITE_TREE_REPAIR; int to_invalidate = 0; int i; + assert(!(dryrun && repair)); + *skip_count = 0; if (0 <= it->entry_count && has_sha1_file(it->sha1)) @@ -381,7 +384,14 @@ static int update_one(struct cache_tree *it, #endif } - if (dryrun) + if (repair) { + unsigned char sha1[20]; + hash_sha1_file(buffer.buf, buffer.len, tree_type, sha1); + if (has_sha1_file(sha1)) + hashcpy(it->sha1, sha1); + else + to_invalidate = 1; + } else if (dryrun) hash_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1); else if (write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1)) { strbuf_release(&buffer); diff --git a/cache-tree.h b/cache-tree.h index b47ccec7f6..aa7b3e4a0a 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -39,6 +39,7 @@ int update_main_cache_tree(int); #define WRITE_TREE_IGNORE_CACHE_TREE 2 #define WRITE_TREE_DRY_RUN 4 #define WRITE_TREE_SILENT 8 +#define WRITE_TREE_REPAIR 16 /* error return codes */ #define WRITE_TREE_UNREADABLE_INDEX (-1) @@ -376,6 +376,7 @@ static inline enum object_type object_type(unsigned int mode) /* Double-check local_repo_env below if you add to this list. */ #define GIT_DIR_ENVIRONMENT "GIT_DIR" +#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR" #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE" #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX" @@ -429,11 +430,13 @@ extern int is_inside_git_dir(void); extern char *git_work_tree_cfg; extern int is_inside_work_tree(void); extern const char *get_git_dir(void); +extern const char *get_git_common_dir(void); extern int is_git_directory(const char *path); extern char *get_object_directory(void); extern char *get_index_file(void); extern char *get_graft_file(void); extern int set_git_dir(const char *path); +extern int get_common_dir(struct strbuf *sb, const char *gitdir); extern const char *get_git_namespace(void); extern const char *strip_namespace(const char *namespaced_ref); extern const char *get_git_work_tree(void); @@ -585,6 +588,7 @@ extern NORETURN void unable_to_lock_index_die(const char *path, int err); extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); +extern int reopen_lock_file(struct lock_file *); extern void update_index_if_able(struct index_state *, struct lock_file *); extern int hold_locked_index(struct lock_file *, int); @@ -633,6 +637,7 @@ extern int fsync_object_files; extern int core_preload_index; extern int core_apply_sparse_checkout; extern int precomposed_unicode; +extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env; /* * The character that begins a commented line in user-editable file @@ -695,18 +700,19 @@ extern int check_repository_format(void); extern char *mksnpath(char *buf, size_t n, const char *fmt, ...) __attribute__((format (printf, 3, 4))); -extern char *git_snpath(char *buf, size_t n, const char *fmt, ...) - __attribute__((format (printf, 3, 4))); +extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); extern char *git_pathdup(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern char *mkpathdup(const char *fmt, ...) __attribute__((format (printf, 1, 2))); /* Return a statically allocated filename matching the sha1 signature */ -extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern char *git_path_submodule(const char *path, const char *fmt, ...) +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +extern const char *git_path_submodule(const char *path, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +extern void report_linked_checkout_garbage(void); /* * Return the name of the file in the local object database that would @@ -1061,6 +1067,7 @@ extern const char *git_author_info(int); extern const char *git_committer_info(int); extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); extern const char *fmt_name(const char *name, const char *email); +extern const char *ident_default_name(void); extern const char *ident_default_email(void); extern const char *git_editor(void); extern const char *git_pager(int stdout_is_tty); @@ -1351,6 +1358,38 @@ extern int parse_config_key(const char *var, const char **subsection, int *subsection_len, const char **key); +struct config_set { + struct hashmap config_hash; + int hash_initialized; +}; + +extern void git_configset_init(struct config_set *cs); +extern int git_configset_add_file(struct config_set *cs, const char *filename); +extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value); +extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key); +extern void git_configset_clear(struct config_set *cs); +extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest); +extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest); +extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest); +extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); +extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest); + +extern int git_config_get_value(const char *key, const char **value); +extern const struct string_list *git_config_get_value_multi(const char *key); +extern void git_config_clear(void); +extern void git_config_iter(config_fn_t fn, void *data); +extern int git_config_get_string_const(const char *key, const char **dest); +extern int git_config_get_string(const char *key, char **dest); +extern int git_config_get_int(const char *key, int *dest); +extern int git_config_get_ulong(const char *key, unsigned long *dest); +extern int git_config_get_bool(const char *key, int *dest); +extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest); +extern int git_config_get_maybe_bool(const char *key, int *dest); +extern int git_config_get_pathname(const char *key, const char **dest); + extern int committer_ident_sufficiently_given(void); extern int author_ident_sufficiently_given(void); @@ -1377,6 +1416,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str) { return write_in_full(fd, str, strlen(str)); } +__attribute__((format (printf,3,4))) +extern int write_file(const char *path, int fatal, const char *fmt, ...); /* pager.c */ extern void setup_pager(void); diff --git a/combine-diff.c b/combine-diff.c index f9975d2c2e..91edce58e1 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -12,6 +12,16 @@ #include "sha1-array.h" #include "revision.h" +static int compare_paths(const struct combine_diff_path *one, + const struct diff_filespec *two) +{ + if (!S_ISDIR(one->mode) && !S_ISDIR(two->mode)) + return strcmp(one->path, two->path); + + return base_name_compare(one->path, strlen(one->path), one->mode, + two->path, strlen(two->path), two->mode); +} + static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { struct diff_queue_struct *q = &diff_queued_diff; @@ -52,7 +62,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, i = 0; while ((p = *tail) != NULL) { cmp = ((i >= q->nr) - ? -1 : strcmp(p->path, q->queue[i]->two->path)); + ? -1 : compare_paths(p, q->queue[i]->two)); if (cmp < 0) { /* p->path not in q->queue[]; drop it */ @@ -1397,7 +1407,8 @@ void diff_tree_combined(const unsigned char *sha1, show_log(rev); if (rev->verbose_header && opt->output_format && - opt->output_format != DIFF_FORMAT_NO_OUTPUT) + opt->output_format != DIFF_FORMAT_NO_OUTPUT && + !commit_format_is_empty(rev->commit_format)) printf("%s%c", diff_line_prefix(opt), opt->line_termination); } @@ -159,6 +159,7 @@ extern void get_commit_format(const char *arg, struct rev_info *); extern const char *format_subject(struct strbuf *sb, const char *msg, const char *line_separator); extern void userformat_find_requirements(const char *fmt, struct userformat_want *w); +extern int commit_format_is_empty(enum cmit_fmt); extern void format_commit_message(const struct commit *commit, const char *format, struct strbuf *sb, const struct pretty_print_context *context); @@ -9,6 +9,8 @@ #include "exec_cmd.h" #include "strbuf.h" #include "quote.h" +#include "hashmap.h" +#include "string-list.h" struct config_source { struct config_source *prev; @@ -33,10 +35,23 @@ struct config_source { long (*do_ftell)(struct config_source *c); }; +struct config_set_element { + struct hashmap_entry ent; + char *key; + struct string_list value_list; +}; + static struct config_source *cf; static int zlib_compression_seen; +/* + * Default config_set that contains key-value pairs from the usual set of config + * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG + * config file and the global /etc/gitconfig) + */ +static struct config_set the_config_set; + static int config_file_fgetc(struct config_source *conf) { return fgetc(conf->u.file); @@ -1210,6 +1225,262 @@ int git_config(config_fn_t fn, void *data) return git_config_with_options(fn, data, NULL, 1); } +static struct config_set_element *configset_find_element(struct config_set *cs, const char *key) +{ + struct config_set_element k; + struct config_set_element *found_entry; + char *normalized_key; + int ret; + /* + * `key` may come from the user, so normalize it before using it + * for querying entries from the hashmap. + */ + ret = git_config_parse_key(key, &normalized_key, NULL); + + if (ret) + return NULL; + + hashmap_entry_init(&k, strhash(normalized_key)); + k.key = normalized_key; + found_entry = hashmap_get(&cs->config_hash, &k, NULL); + free(normalized_key); + return found_entry; +} + +static int configset_add_value(struct config_set *cs, const char *key, const char *value) +{ + struct config_set_element *e; + e = configset_find_element(cs, key); + /* + * Since the keys are being fed by git_config*() callback mechanism, they + * are already normalized. So simply add them without any further munging. + */ + if (!e) { + e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, strhash(key)); + e->key = xstrdup(key); + string_list_init(&e->value_list, 1); + hashmap_add(&cs->config_hash, e); + } + string_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL); + + return 0; +} + +static int config_set_element_cmp(const struct config_set_element *e1, + const struct config_set_element *e2, const void *unused) +{ + return strcmp(e1->key, e2->key); +} + +void git_configset_init(struct config_set *cs) +{ + hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0); + cs->hash_initialized = 1; +} + +void git_configset_clear(struct config_set *cs) +{ + struct config_set_element *entry; + struct hashmap_iter iter; + if (!cs->hash_initialized) + return; + + hashmap_iter_init(&cs->config_hash, &iter); + while ((entry = hashmap_iter_next(&iter))) { + free(entry->key); + string_list_clear(&entry->value_list, 0); + } + hashmap_free(&cs->config_hash, 1); + cs->hash_initialized = 0; +} + +static int config_set_callback(const char *key, const char *value, void *cb) +{ + struct config_set *cs = cb; + configset_add_value(cs, key, value); + return 0; +} + +int git_configset_add_file(struct config_set *cs, const char *filename) +{ + return git_config_from_file(config_set_callback, filename, cs); +} + +int git_configset_get_value(struct config_set *cs, const char *key, const char **value) +{ + const struct string_list *values = NULL; + /* + * Follows "last one wins" semantic, i.e., if there are multiple matches for the + * queried key in the files of the configset, the value returned will be the last + * value in the value list for that key. + */ + values = git_configset_get_value_multi(cs, key); + + if (!values) + return 1; + assert(values->nr > 0); + *value = values->items[values->nr - 1].string; + return 0; +} + +const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key) +{ + struct config_set_element *e = configset_find_element(cs, key); + return e ? &e->value_list : NULL; +} + +int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) + return git_config_string(dest, key, value); + else + return 1; +} + +int git_configset_get_string(struct config_set *cs, const char *key, char **dest) +{ + return git_configset_get_string_const(cs, key, (const char **)dest); +} + +int git_configset_get_int(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_int(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_ulong(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_bool(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_bool(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_bool_or_int(struct config_set *cs, const char *key, + int *is_bool, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_bool_or_int(key, value, is_bool); + return 0; + } else + return 1; +} + +int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_maybe_bool(key, value); + if (*dest == -1) + return -1; + return 0; + } else + return 1; +} + +int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) + return git_config_pathname(dest, key, value); + else + return 1; +} + +static void git_config_check_init(void) +{ + if (the_config_set.hash_initialized) + return; + git_configset_init(&the_config_set); + git_config(config_set_callback, &the_config_set); +} + +void git_config_clear(void) +{ + if (!the_config_set.hash_initialized) + return; + git_configset_clear(&the_config_set); +} + +int git_config_get_value(const char *key, const char **value) +{ + git_config_check_init(); + return git_configset_get_value(&the_config_set, key, value); +} + +const struct string_list *git_config_get_value_multi(const char *key) +{ + git_config_check_init(); + return git_configset_get_value_multi(&the_config_set, key); +} + +int git_config_get_string_const(const char *key, const char **dest) +{ + git_config_check_init(); + return git_configset_get_string_const(&the_config_set, key, dest); +} + +int git_config_get_string(const char *key, char **dest) +{ + git_config_check_init(); + return git_config_get_string_const(key, (const char **)dest); +} + +int git_config_get_int(const char *key, int *dest) +{ + git_config_check_init(); + return git_configset_get_int(&the_config_set, key, dest); +} + +int git_config_get_ulong(const char *key, unsigned long *dest) +{ + git_config_check_init(); + return git_configset_get_ulong(&the_config_set, key, dest); +} + +int git_config_get_bool(const char *key, int *dest) +{ + git_config_check_init(); + return git_configset_get_bool(&the_config_set, key, dest); +} + +int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest) +{ + git_config_check_init(); + return git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest); +} + +int git_config_get_maybe_bool(const char *key, int *dest) +{ + git_config_check_init(); + return git_configset_get_maybe_bool(&the_config_set, key, dest); +} + +int git_config_get_pathname(const char *key, const char **dest) +{ + git_config_check_init(); + return git_configset_get_pathname(&the_config_set, key, dest); +} + /* * Find all the stuff for git_config_set() below. */ @@ -1705,6 +1976,9 @@ int git_config_set_multivar_in_file(const char *config_filename, lock = NULL; ret = 0; + /* Invalidate the config cache */ + git_config_clear(); + out_free: if (lock) rollback_lock_file(lock); @@ -1093,15 +1093,6 @@ static struct credentials *prepare_credentials(const char *user_name, } #endif -static void store_pid(const char *path) -{ - FILE *f = fopen(path, "w"); - if (!f) - die_errno("cannot open pid file '%s'", path); - if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0) - die_errno("failed to write pid file '%s'", path); -} - static int serve(struct string_list *listen_addr, int listen_port, struct credentials *cred) { @@ -1312,7 +1303,7 @@ int main(int argc, char **argv) sanitize_stdfds(); if (pid_file) - store_pid(pid_file); + write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid()); /* prepare argv for serving-processes */ cld_argv = xmalloc(sizeof (char *) * (argc + 2)); @@ -13,6 +13,7 @@ #include "utf8.h" #include "userdiff.h" #include "sigchain.h" +#include "submodule-config.h" #include "submodule.h" #include "ll-merge.h" #include "string-list.h" @@ -1507,12 +1507,16 @@ int dir_inside_of(const char *subdir, const char *dir) int is_inside_dir(const char *dir) { - char cwd[PATH_MAX]; + char *cwd; + int rc; + if (!dir) return 0; - if (!getcwd(cwd, sizeof(cwd))) - die_errno("can't find the current directory"); - return dir_inside_of(cwd, dir) >= 0; + + cwd = xgetcwd(); + rc = (dir_inside_of(cwd, dir) >= 0); + free(cwd); + return rc; } int is_empty_dir(const char *path) diff --git a/environment.c b/environment.c index 565f65293b..d5b0788b48 100644 --- a/environment.c +++ b/environment.c @@ -81,8 +81,9 @@ static char *work_tree; static const char *namespace; static size_t namespace_len; -static const char *git_dir; +static const char *git_dir, *git_common_dir; static char *git_object_dir, *git_index_file, *git_graft_file; +int git_db_env, git_index_env, git_graft_env, git_common_dir_env; /* * Repository-local GIT_* environment variables; see cache.h for details. @@ -124,14 +125,23 @@ static char *expand_namespace(const char *raw_namespace) return strbuf_detach(&buf, NULL); } -static char *git_path_from_env(const char *envvar, const char *path) +static char *git_path_from_env(const char *envvar, const char *git_dir, + const char *path, int *fromenv) { const char *value = getenv(envvar); - return value ? xstrdup(value) : git_pathdup("%s", path); + if (!value) { + char *buf = xmalloc(strlen(git_dir) + strlen(path) + 2); + sprintf(buf, "%s/%s", git_dir, path); + return buf; + } + if (fromenv) + *fromenv = 1; + return xstrdup(value); } static void setup_git_env(void) { + struct strbuf sb = STRBUF_INIT; const char *gitfile; const char *shallow_file; @@ -140,9 +150,15 @@ static void setup_git_env(void) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; gitfile = read_gitfile(git_dir); git_dir = xstrdup(gitfile ? gitfile : git_dir); - git_object_dir = git_path_from_env(DB_ENVIRONMENT, "objects"); - git_index_file = git_path_from_env(INDEX_ENVIRONMENT, "index"); - git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, "info/grafts"); + if (get_common_dir(&sb, git_dir)) + git_common_dir_env = 1; + git_common_dir = strbuf_detach(&sb, NULL); + git_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir, + "objects", &git_db_env); + git_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir, + "index", &git_index_env); + git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, git_common_dir, + "info/grafts", &git_graft_env); if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT)) check_replace_refs = 0; namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT)); @@ -165,6 +181,11 @@ const char *get_git_dir(void) return git_dir; } +const char *get_git_common_dir(void) +{ + return git_common_dir; +} + const char *get_git_namespace(void) { if (!namespace) diff --git a/exec_cmd.c b/exec_cmd.c index 125fa6fabf..698e7526c4 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -86,11 +86,7 @@ const char *git_exec_path(void) static void add_path(struct strbuf *out, const char *path) { if (path && *path) { - if (is_absolute_path(path)) - strbuf_addstr(out, path); - else - strbuf_addstr(out, absolute_path(path)); - + strbuf_add_absolute_path(out, path); strbuf_addch(out, PATH_SEP); } } diff --git a/fast-import.c b/fast-import.c index d73f58cbe3..e0adf8333a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -404,7 +404,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *); static void write_crash_report(const char *err) { - char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid()); + const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid()); FILE *rpt = fopen(loc, "w"); struct branch *b; unsigned long lu; @@ -3103,12 +3103,9 @@ static void parse_progress(void) static char* make_fast_import_path(const char *path) { - struct strbuf abs_path = STRBUF_INIT; - if (!relative_marks_paths || is_absolute_path(path)) return xstrdup(path); - strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path); - return strbuf_detach(&abs_path, NULL); + return xstrdup(git_path("info/fast-import/%s", path)); } static void option_import_marks(const char *marks, @@ -810,10 +810,10 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"." continue fi - if test -x "$GIT_DIR"/hooks/applypatch-msg + hook="$(git rev-parse --git-path hooks/applypatch-msg)" + if test -x "$hook" then - "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" || - stop_here $this + "$hook" "$dotest/final-commit" || stop_here $this fi if test -f "$dotest/final-commit" @@ -887,9 +887,10 @@ did you forget to use 'git add'?" stop_here_user_resolve $this fi - if test -x "$GIT_DIR"/hooks/pre-applypatch + hook="$(git rev-parse --git-path hooks/pre-applypatch)" + if test -x "$hook" then - "$GIT_DIR"/hooks/pre-applypatch || stop_here $this + "$hook" || stop_here $this fi tree=$(git write-tree) && @@ -916,18 +917,17 @@ did you forget to use 'git add'?" echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten" fi - if test -x "$GIT_DIR"/hooks/post-applypatch - then - "$GIT_DIR"/hooks/post-applypatch - fi + hook="$(git rev-parse --git-path hooks/post-applypatch)" + test -x "$hook" && "$hook" go_next done if test -s "$dotest"/rewritten; then git notes copy --for-rewrite=rebase < "$dotest"/rewritten - if test -x "$GIT_DIR"/hooks/post-rewrite; then - "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten + hook="$(git rev-parse --git-path hooks/post-rewrite)" + if test -x "$hook"; then + "$hook" rebase < "$dotest"/rewritten fi fi diff --git a/git-compat-util.h b/git-compat-util.h index f587749b7c..c150e3f8e7 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -607,6 +607,7 @@ extern int xmkstemp(char *template); extern int xmkstemp_mode(char *template, int mode); extern int odb_mkstemp(char *template, size_t limit, const char *pattern); extern int odb_pack_keep(char *name, size_t namesz, const unsigned char *sha1); +extern char *xgetcwd(void); static inline size_t xsize_t(off_t len) { diff --git a/git-pull.sh b/git-pull.sh index 18a394fcc4..6ab0c31d24 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -240,7 +240,7 @@ test true = "$rebase" && { if ! git rev-parse -q --verify HEAD >/dev/null then # On an unborn branch - if test -f "$GIT_DIR/index" + if test -f "$(git rev-parse --git-path index)" then die "$(gettext "updating an unborn branch with changes added to the index")" fi diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index b64dd28acf..b32f797aa9 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -642,9 +642,9 @@ do_next () { git notes copy --for-rewrite=rebase < "$rewritten_list" || true # we don't care if this copying failed } && - if test -x "$GIT_DIR"/hooks/post-rewrite && - test -s "$rewritten_list"; then - "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list" + hook="$(git rev-parse --git-path hooks/post-rewrite)" + if test -x "$hook" && test -s "$rewritten_list"; then + "$hook" rebase < "$rewritten_list" true # we don't care if this hook failed fi && warn "Successfully rebased and updated $head_name." diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh index d3fb67d75b..2cc2a6d273 100644 --- a/git-rebase--merge.sh +++ b/git-rebase--merge.sh @@ -94,10 +94,8 @@ finish_rb_merge () { if test -s "$state_dir"/rewritten then git notes copy --for-rewrite=rebase <"$state_dir"/rewritten - if test -x "$GIT_DIR"/hooks/post-rewrite - then - "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten - fi + hook="$(git rev-parse --git-path hooks/post-rewrite)" + test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten fi say All done. } diff --git a/git-rebase.sh b/git-rebase.sh index 55da9db818..fb935a03f3 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -202,9 +202,9 @@ run_specific_rebase () { run_pre_rebase_hook () { if test -z "$ok_to_skip_pre_rebase" && - test -x "$GIT_DIR/hooks/pre-rebase" + test -x "$(git rev-parse --git-path hooks/pre-rebase)" then - "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || + "$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} || die "$(gettext "The pre-rebase hook refused to rebase.")" fi } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 9447980330..d3dbb2fb60 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -345,7 +345,7 @@ then echo >&2 "Unable to determine absolute path of git directory" exit 1 } - : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} + : ${GIT_OBJECT_DIRECTORY="$(git rev-parse --git-path objects)"} fi peel_committish () { diff --git a/git-stash.sh b/git-stash.sh index bcc757b390..41f8f6b19c 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -20,7 +20,7 @@ require_work_tree cd_to_toplevel TMP="$GIT_DIR/.git-stash.$$" -TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$ +TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$ trap 'rm -f "$TMP-"* "$TMPindex"' 0 ref_stash=refs/stash @@ -184,7 +184,7 @@ store_stash () { fi # Make sure the reflog for stash is kept. - : >>"$GIT_DIR/logs/$ref_stash" + : >>"$(git rev-parse --git-path logs/$ref_stash)" git update-ref -m "$stash_msg" $ref_stash $w_commit ret=$? test $ret != 0 && test -z $quiet && @@ -259,7 +259,7 @@ save_stash () { say "$(gettext "No local changes to save")" exit 0 fi - test -f "$GIT_DIR/logs/$ref_stash" || + test -f "$(git rev-parse --git-path logs/$ref_stash)" || clear_stash || die "$(gettext "Cannot initialize stash")" create_stash "$stash_msg" $untracked @@ -20,7 +20,7 @@ const char git_more_info_string[] = static struct startup_info git_startup_info; static int use_pager = -1; -static char orig_cwd[PATH_MAX]; +static char *orig_cwd; static const char *env_names[] = { GIT_DIR_ENVIRONMENT, GIT_WORK_TREE_ENVIRONMENT, @@ -36,8 +36,7 @@ static void save_env(void) if (saved_environment) return; saved_environment = 1; - if (!getcwd(orig_cwd, sizeof(orig_cwd))) - die_errno("cannot getcwd"); + orig_cwd = xgetcwd(); for (i = 0; i < ARRAY_SIZE(env_names); i++) { orig_env[i] = getenv(env_names[i]); if (orig_env[i]) @@ -48,8 +47,9 @@ static void save_env(void) static void restore_env(void) { int i; - if (*orig_cwd && chdir(orig_cwd)) + if (orig_cwd && chdir(orig_cwd)) die_errno("could not move to %s", orig_cwd); + free(orig_cwd); for (i = 0; i < ARRAY_SIZE(env_names); i++) { if (orig_env[i]) setenv(env_names[i], orig_env[i], 1); @@ -161,9 +161,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--bare")) { - static char git_dir[PATH_MAX+1]; + char *cwd = xgetcwd(); is_bare_repository_cfg = 1; - setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0); + setenv(GIT_DIR_ENVIRONMENT, cwd, 0); + free(cwd); setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1); if (envchanged) *envchanged = 1; @@ -383,7 +384,7 @@ static struct cmd_struct commands[] = { { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE }, { "check-mailmap", cmd_check_mailmap, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, - { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, + { "checkout", cmd_checkout, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP | NEED_WORK_TREE}, { "cherry", cmd_cherry, RUN_SETUP }, @@ -102,7 +102,7 @@ static void copy_email(const struct passwd *pw, struct strbuf *email) add_domainname(email); } -static const char *ident_default_name(void) +const char *ident_default_name(void) { if (!git_default_name.len) { copy_gecos(xgetpwuid_self(), &git_default_name); diff --git a/lockfile.c b/lockfile.c index 2564a7f544..2a800cef33 100644 --- a/lockfile.c +++ b/lockfile.c @@ -237,6 +237,16 @@ int close_lock_file(struct lock_file *lk) return close(fd); } +int reopen_lock_file(struct lock_file *lk) +{ + if (0 <= lk->fd) + die(_("BUG: reopen a lockfile that is still open")); + if (!lk->filename[0]) + die(_("BUG: reopen a lockfile that has been committed")); + lk->fd = open(lk->filename, O_WRONLY); + return lk->fd; +} + int commit_lock_file(struct lock_file *lk) { char result_file[PATH_MAX]; diff --git a/log-tree.c b/log-tree.c index 0c53dc11ab..95e9b1da25 100644 --- a/log-tree.c +++ b/log-tree.c @@ -649,7 +649,7 @@ void show_log(struct rev_info *opt) graph_show_commit_msg(opt->graph, &msgbuf); else fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); - if (opt->use_terminator) { + if (opt->use_terminator && !commit_format_is_empty(opt->commit_format)) { if (!opt->missing_newline) graph_show_padding(opt->graph); putchar(opt->diffopt.line_termination); @@ -676,7 +676,8 @@ int log_tree_diff_flush(struct rev_info *opt) show_log(opt); if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) && opt->verbose_header && - opt->commit_format != CMIT_FMT_ONELINE) { + opt->commit_format != CMIT_FMT_ONELINE && + !commit_format_is_empty(opt->commit_format)) { /* * When showing a verbose header (i.e. log message), * and not in --pretty=oneline format, we would want diff --git a/notes-merge.c b/notes-merge.c index fd5fae255d..a9e6b15826 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -280,7 +280,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o) "(%s exists).", git_path("NOTES_MERGE_*")); } - if (safe_create_leading_directories(git_path( + if (safe_create_leading_directories_const(git_path( NOTES_MERGE_WORKTREE "/.test"))) die_errno("unable to create directory %s", git_path(NOTES_MERGE_WORKTREE)); @@ -295,8 +295,8 @@ static void write_buf_to_worktree(const unsigned char *obj, const char *buf, unsigned long size) { int fd; - char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj)); - if (safe_create_leading_directories(path)) + const char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj)); + if (safe_create_leading_directories_const(path)) die_errno("unable to create directory for '%s'", path); if (file_exists(path)) die("found existing file at '%s'", path); @@ -4,6 +4,7 @@ #include "cache.h" #include "strbuf.h" #include "string-list.h" +#include "dir.h" static int get_st_mode_bits(const char *path, int *mode) { @@ -16,11 +17,15 @@ static int get_st_mode_bits(const char *path, int *mode) static char bad_path[] = "/bad-path/"; -static char *get_pathname(void) +static struct strbuf *get_pathname(void) { - static char pathname_array[4][PATH_MAX]; + static struct strbuf pathname_array[4] = { + STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT + }; static int index; - return pathname_array[3 & ++index]; + struct strbuf *sb = &pathname_array[3 & ++index]; + strbuf_reset(sb); + return sb; } static char *cleanup_path(char *path) @@ -34,6 +39,13 @@ static char *cleanup_path(char *path) return path; } +static void strbuf_cleanup_path(struct strbuf *sb) +{ + char *path = cleanup_path(sb->buf); + if (path > sb->buf) + strbuf_remove(sb, 0, path - sb->buf); +} + char *mksnpath(char *buf, size_t n, const char *fmt, ...) { va_list args; @@ -49,85 +61,167 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...) return cleanup_path(buf); } -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args) +static int dir_prefix(const char *buf, const char *dir) { - const char *git_dir = get_git_dir(); - size_t len; + int len = strlen(dir); + return !strncmp(buf, dir, len) && + (is_dir_sep(buf[len]) || buf[len] == '\0'); +} - len = strlen(git_dir); - if (n < len + 1) - goto bad; - memcpy(buf, git_dir, len); - if (len && !is_dir_sep(git_dir[len-1])) - buf[len++] = '/'; - len += vsnprintf(buf + len, n - len, fmt, args); - if (len >= n) - goto bad; - return cleanup_path(buf); -bad: - strlcpy(buf, bad_path, n); - return buf; +/* $buf =~ m|$dir/+$file| but without regex */ +static int is_dir_file(const char *buf, const char *dir, const char *file) +{ + int len = strlen(dir); + if (strncmp(buf, dir, len) || !is_dir_sep(buf[len])) + return 0; + while (is_dir_sep(buf[len])) + len++; + return !strcmp(buf + len, file); +} + +static void replace_dir(struct strbuf *buf, int len, const char *newdir) +{ + int newlen = strlen(newdir); + int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) && + !is_dir_sep(newdir[newlen - 1]); + if (need_sep) + len--; /* keep one char, to be replaced with '/' */ + strbuf_splice(buf, 0, len, newdir, newlen); + if (need_sep) + buf->buf[newlen] = '/'; +} + +static const char *common_list[] = { + "/branches", "/hooks", "/info", "!/logs", "/lost-found", "/modules", + "/objects", "/refs", "/remotes", "/repos", "/rr-cache", "/svn", + "config", "!gc.pid", "packed-refs", "shallow", + NULL +}; + +static void update_common_dir(struct strbuf *buf, int git_dir_len) +{ + char *base = buf->buf + git_dir_len; + const char **p; + + if (is_dir_file(base, "logs", "HEAD") || + is_dir_file(base, "info", "sparse-checkout")) + return; /* keep this in $GIT_DIR */ + for (p = common_list; *p; p++) { + const char *path = *p; + int is_dir = 0; + if (*path == '!') + path++; + if (*path == '/') { + path++; + is_dir = 1; + } + if (is_dir && dir_prefix(base, path)) { + replace_dir(buf, git_dir_len, get_git_common_dir()); + return; + } + if (!is_dir && !strcmp(base, path)) { + replace_dir(buf, git_dir_len, get_git_common_dir()); + return; + } + } +} + +void report_linked_checkout_garbage(void) +{ + struct strbuf sb = STRBUF_INIT; + const char **p; + int len; + + if (!git_common_dir_env) + return; + strbuf_addf(&sb, "%s/", get_git_dir()); + len = sb.len; + for (p = common_list; *p; p++) { + const char *path = *p; + if (*path == '!') + continue; + strbuf_setlen(&sb, len); + strbuf_addstr(&sb, path); + if (file_exists(sb.buf)) + report_garbage("unused in linked checkout", sb.buf); + } + strbuf_release(&sb); } -char *git_snpath(char *buf, size_t n, const char *fmt, ...) +static void adjust_git_path(struct strbuf *buf, int git_dir_len) +{ + const char *base = buf->buf + git_dir_len; + if (git_graft_env && is_dir_file(base, "info", "grafts")) + strbuf_splice(buf, 0, buf->len, + get_graft_file(), strlen(get_graft_file())); + else if (git_index_env && !strcmp(base, "index")) + strbuf_splice(buf, 0, buf->len, + get_index_file(), strlen(get_index_file())); + else if (git_db_env && dir_prefix(base, "objects")) + replace_dir(buf, git_dir_len + 7, get_object_directory()); + else if (git_common_dir_env) + update_common_dir(buf, git_dir_len); +} + +static void do_git_path(struct strbuf *buf, const char *fmt, va_list args) +{ + int gitdir_len; + strbuf_addstr(buf, get_git_dir()); + if (buf->len && !is_dir_sep(buf->buf[buf->len - 1])) + strbuf_addch(buf, '/'); + gitdir_len = buf->len; + strbuf_vaddf(buf, fmt, args); + adjust_git_path(buf, gitdir_len); + strbuf_cleanup_path(buf); +} + +void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) { - char *ret; va_list args; va_start(args, fmt); - ret = vsnpath(buf, n, fmt, args); + do_git_path(sb, fmt, args); va_end(args); - return ret; } -char *git_pathdup(const char *fmt, ...) +const char *git_path(const char *fmt, ...) { - char path[PATH_MAX], *ret; + struct strbuf *pathname = get_pathname(); va_list args; va_start(args, fmt); - ret = vsnpath(path, sizeof(path), fmt, args); + do_git_path(pathname, fmt, args); va_end(args); - return xstrdup(ret); + return pathname->buf; } -char *mkpathdup(const char *fmt, ...) +char *git_pathdup(const char *fmt, ...) { - char *path; - struct strbuf sb = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; va_list args; - va_start(args, fmt); - strbuf_vaddf(&sb, fmt, args); + do_git_path(&path, fmt, args); va_end(args); - path = xstrdup(cleanup_path(sb.buf)); - - strbuf_release(&sb); - return path; + return strbuf_detach(&path, NULL); } -char *mkpath(const char *fmt, ...) +char *mkpathdup(const char *fmt, ...) { + struct strbuf sb = STRBUF_INIT; va_list args; - unsigned len; - char *pathname = get_pathname(); - va_start(args, fmt); - len = vsnprintf(pathname, PATH_MAX, fmt, args); + strbuf_vaddf(&sb, fmt, args); va_end(args); - if (len >= PATH_MAX) - return bad_path; - return cleanup_path(pathname); + strbuf_cleanup_path(&sb); + return strbuf_detach(&sb, NULL); } -char *git_path(const char *fmt, ...) +const char *mkpath(const char *fmt, ...) { - char *pathname = get_pathname(); va_list args; - char *ret; - + struct strbuf *pathname = get_pathname(); va_start(args, fmt); - ret = vsnpath(pathname, PATH_MAX, fmt, args); + strbuf_vaddf(pathname, fmt, args); va_end(args); - return ret; + return cleanup_path(pathname->buf); } void home_config_paths(char **global, char **xdg, char *file) @@ -148,51 +242,39 @@ void home_config_paths(char **global, char **xdg, char *file) *global = mkpathdup("%s/.gitconfig", home); } - if (!xdg_home) - *xdg = NULL; - else - *xdg = mkpathdup("%s/git/%s", xdg_home, file); + if (xdg) { + if (!xdg_home) + *xdg = NULL; + else + *xdg = mkpathdup("%s/git/%s", xdg_home, file); + } free(to_free); } -char *git_path_submodule(const char *path, const char *fmt, ...) +const char *git_path_submodule(const char *path, const char *fmt, ...) { - char *pathname = get_pathname(); - struct strbuf buf = STRBUF_INIT; + struct strbuf *buf = get_pathname(); const char *git_dir; va_list args; - unsigned len; - len = strlen(path); - if (len > PATH_MAX-100) - return bad_path; + strbuf_addstr(buf, path); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, ".git"); - strbuf_addstr(&buf, path); - if (len && path[len-1] != '/') - strbuf_addch(&buf, '/'); - strbuf_addstr(&buf, ".git"); - - git_dir = read_gitfile(buf.buf); + git_dir = read_gitfile(buf->buf); if (git_dir) { - strbuf_reset(&buf); - strbuf_addstr(&buf, git_dir); + strbuf_reset(buf); + strbuf_addstr(buf, git_dir); } - strbuf_addch(&buf, '/'); - - if (buf.len >= PATH_MAX) - return bad_path; - memcpy(pathname, buf.buf, buf.len + 1); - - strbuf_release(&buf); - len = strlen(pathname); + strbuf_addch(buf, '/'); va_start(args, fmt); - len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args); + strbuf_vaddf(buf, fmt, args); va_end(args); - if (len >= PATH_MAX) - return bad_path; - return cleanup_path(pathname); + strbuf_cleanup_path(buf); + return buf->buf; } int validate_headref(const char *path) @@ -24,6 +24,11 @@ static size_t commit_formats_len; static size_t commit_formats_alloc; static struct cmt_fmt_map *find_commit_format(const char *sought); +int commit_format_is_empty(enum cmit_fmt fmt) +{ + return fmt == CMIT_FMT_USERFORMAT && !*user_format; +} + static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat) { free(user_format); @@ -65,7 +70,9 @@ static int git_pretty_formats_config(const char *var, const char *value, void *c commit_format->name = xstrdup(name); commit_format->format = CMIT_FMT_USERFORMAT; - git_config_string(&fmt, var, value); + if (git_config_string(&fmt, var, value)) + return -1; + if (starts_with(fmt, "format:") || starts_with(fmt, "tformat:")) { commit_format->is_tformat = fmt[0] == 't'; fmt = strchr(fmt, ':') + 1; @@ -146,7 +153,7 @@ void get_commit_format(const char *arg, struct rev_info *rev) struct cmt_fmt_map *commit_format; rev->use_terminator = 0; - if (!arg || !*arg) { + if (!arg) { rev->commit_format = CMIT_FMT_DEFAULT; return; } @@ -155,7 +162,7 @@ void get_commit_format(const char *arg, struct rev_info *rev) return; } - if (strchr(arg, '%')) { + if (!*arg || strchr(arg, '%')) { save_user_format(rev, arg, 1); return; } @@ -1243,7 +1243,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs, { int fd, len; char buffer[128], *p; - char *path; + const char *path; if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN) return -1; @@ -1337,10 +1337,12 @@ static const char *handle_missing_loose_ref(const char *refname, /* This function needs to return a meaningful errno on failure */ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) { + struct strbuf sb_path = STRBUF_INIT; int depth = MAXDEPTH; ssize_t len; char buffer[256]; static char refname_buffer[256]; + const char *ret; if (flag) *flag = 0; @@ -1351,17 +1353,19 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea } for (;;) { - char path[PATH_MAX]; + const char *path; struct stat st; char *buf; int fd; if (--depth < 0) { errno = ELOOP; - return NULL; + goto fail; } - git_snpath(path, sizeof(path), "%s", refname); + strbuf_reset(&sb_path); + strbuf_git_path(&sb_path, "%s", refname); + path = sb_path.buf; /* * We might have to loop back here to avoid a race @@ -1375,10 +1379,11 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea stat_ref: if (lstat(path, &st) < 0) { if (errno == ENOENT) - return handle_missing_loose_ref(refname, sha1, - reading, flag); + ret = handle_missing_loose_ref(refname, sha1, + reading, flag); else - return NULL; + ret = NULL; + goto done; } /* Follow "normalized" - ie "refs/.." symlinks by hand */ @@ -1389,7 +1394,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea /* inconsistent with lstat; retry */ goto stat_ref; else - return NULL; + goto fail; } buffer[len] = 0; if (starts_with(buffer, "refs/") && @@ -1405,7 +1410,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea /* Is it a directory? */ if (S_ISDIR(st.st_mode)) { errno = EISDIR; - return NULL; + goto fail; } /* @@ -1418,14 +1423,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea /* inconsistent with lstat; retry */ goto stat_ref; else - return NULL; + goto fail; } + len = read_in_full(fd, buffer, sizeof(buffer)-1); if (len < 0) { int save_errno = errno; close(fd); errno = save_errno; - return NULL; + goto fail; } close(fd); while (len && isspace(buffer[len-1])) @@ -1445,9 +1451,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea if (flag) *flag |= REF_ISBROKEN; errno = EINVAL; - return NULL; + goto fail; } - return refname; + ret = refname; + goto done; } if (flag) *flag |= REF_ISSYMREF; @@ -1458,10 +1465,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea if (flag) *flag |= REF_ISBROKEN; errno = EINVAL; - return NULL; + goto fail; } refname = strcpy(refname_buffer, buf); } +fail: + ret = NULL; +done: + strbuf_release(&sb_path); + return ret; } char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag) @@ -2073,7 +2085,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) { - char *ref_file; + const char *ref_file; const char *orig_refname = refname; struct ref_lock *lock; int last_errno = 0; @@ -2136,7 +2148,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, lock->force_write = 1; retry: - switch (safe_create_leading_directories(ref_file)) { + switch (safe_create_leading_directories_const(ref_file)) { case SCLD_OK: break; /* success */ case SCLD_VANISHED: @@ -2592,7 +2604,7 @@ static int rename_tmp_log(const char *newrefname) int attempts_remaining = 4; retry: - switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) { + switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) { case SCLD_OK: break; /* success */ case SCLD_VANISHED: @@ -2777,51 +2789,51 @@ static int copy_msg(char *buf, const char *msg) } /* This function must set a meaningful errno on failure */ -int log_ref_setup(const char *refname, char *logfile, int bufsize) +int log_ref_setup(const char *refname, struct strbuf *logfile) { int logfd, oflags = O_APPEND | O_WRONLY; - git_snpath(logfile, bufsize, "logs/%s", refname); + strbuf_git_path(logfile, "logs/%s", refname); if (log_all_ref_updates && (starts_with(refname, "refs/heads/") || starts_with(refname, "refs/remotes/") || starts_with(refname, "refs/notes/") || !strcmp(refname, "HEAD"))) { - if (safe_create_leading_directories(logfile) < 0) { + if (safe_create_leading_directories(logfile->buf) < 0) { int save_errno = errno; - error("unable to create directory for %s", logfile); + error("unable to create directory for %s", logfile->buf); errno = save_errno; return -1; } oflags |= O_CREAT; } - logfd = open(logfile, oflags, 0666); + logfd = open(logfile->buf, oflags, 0666); if (logfd < 0) { if (!(oflags & O_CREAT) && errno == ENOENT) return 0; if ((oflags & O_CREAT) && errno == EISDIR) { - if (remove_empty_directories(logfile)) { + if (remove_empty_directories(logfile->buf)) { int save_errno = errno; error("There are still logs under '%s'", - logfile); + logfile->buf); errno = save_errno; return -1; } - logfd = open(logfile, oflags, 0666); + logfd = open(logfile->buf, oflags, 0666); } if (logfd < 0) { int save_errno = errno; - error("Unable to append to %s: %s", logfile, + error("Unable to append to %s: %s", logfile->buf, strerror(errno)); errno = save_errno; return -1; } } - adjust_shared_perm(logfile); + adjust_shared_perm(logfile->buf); close(logfd); return 0; } @@ -2832,20 +2844,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1, int logfd, result, written, oflags = O_APPEND | O_WRONLY; unsigned maxlen, len; int msglen; - char log_file[PATH_MAX]; + struct strbuf sb_log_file = STRBUF_INIT; + const char *log_file; char *logrec; const char *committer; if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); - result = log_ref_setup(refname, log_file, sizeof(log_file)); + result = log_ref_setup(refname, &sb_log_file); if (result) - return result; + goto done; + log_file = sb_log_file.buf; logfd = open(log_file, oflags); if (logfd < 0) - return 0; + goto done; msglen = msg ? strlen(msg) : 0; committer = git_committer_info(0); maxlen = strlen(committer) + msglen + 100; @@ -2863,15 +2877,17 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1, close(logfd); error("Unable to append to %s", log_file); errno = save_errno; - return -1; + result = -1; } if (close(logfd)) { int save_errno = errno; error("Unable to append to %s", log_file); errno = save_errno; - return -1; + result = -1; } - return 0; +done: + strbuf_release(&sb_log_file); + return result; } int is_branch(const char *refname) @@ -168,7 +168,7 @@ extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, cons /* * Setup reflog before using. Set errno to something meaningful on failure. */ -int log_ref_setup(const char *refname, char *logfile, int bufsize); +int log_ref_setup(const char *refname, struct strbuf *logfile); /** Reads log for the value of ref during at_time. **/ extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, @@ -42,6 +42,7 @@ struct rewrites { static struct remote **remotes; static int remotes_alloc; static int remotes_nr; +static struct hashmap remotes_hash; static struct branch **branches; static int branches_alloc; @@ -136,26 +137,51 @@ static void add_url_alias(struct remote *remote, const char *url) add_pushurl_alias(remote, url); } +struct remotes_hash_key { + const char *str; + int len; +}; + +static int remotes_hash_cmp(const struct remote *a, const struct remote *b, const struct remotes_hash_key *key) +{ + if (key) + return strncmp(a->name, key->str, key->len) || a->name[key->len]; + else + return strcmp(a->name, b->name); +} + +static inline void init_remotes_hash(void) +{ + if (!remotes_hash.cmpfn) + hashmap_init(&remotes_hash, (hashmap_cmp_fn)remotes_hash_cmp, 0); +} + static struct remote *make_remote(const char *name, int len) { - struct remote *ret; - int i; + struct remote *ret, *replaced; + struct remotes_hash_key lookup; + struct hashmap_entry lookup_entry; - for (i = 0; i < remotes_nr; i++) { - if (len ? (!strncmp(name, remotes[i]->name, len) && - !remotes[i]->name[len]) : - !strcmp(name, remotes[i]->name)) - return remotes[i]; - } + if (!len) + len = strlen(name); + + init_remotes_hash(); + lookup.str = name; + lookup.len = len; + hashmap_entry_init(&lookup_entry, memhash(name, len)); + + if ((ret = hashmap_get(&remotes_hash, &lookup_entry, &lookup)) != NULL) + return ret; ret = xcalloc(1, sizeof(struct remote)); ret->prune = -1; /* unspecified */ ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc); remotes[remotes_nr++] = ret; - if (len) - ret->name = xstrndup(name, len); - else - ret->name = xstrdup(name); + ret->name = xstrndup(name, len); + + hashmap_entry_init(ret, lookup_entry.hash); + replaced = hashmap_put(&remotes_hash, ret); + assert(replaced == NULL); /* no previous entry overwritten */ return ret; } @@ -717,13 +743,16 @@ struct remote *pushremote_get(const char *name) int remote_is_configured(const char *name) { - int i; + struct remotes_hash_key lookup; + struct hashmap_entry lookup_entry; read_config(); - for (i = 0; i < remotes_nr; i++) - if (!strcmp(name, remotes[i]->name)) - return 1; - return 0; + init_remotes_hash(); + lookup.str = name; + lookup.len = strlen(name); + hashmap_entry_init(&lookup_entry, memhash(name, lookup.len)); + + return hashmap_get(&remotes_hash, &lookup_entry, &lookup) != NULL; } int for_each_remote(each_remote_fn fn, void *priv) @@ -2,6 +2,7 @@ #define REMOTE_H #include "parse-options.h" +#include "hashmap.h" enum { REMOTE_CONFIG, @@ -10,6 +11,8 @@ enum { }; struct remote { + struct hashmap_entry ent; /* must be first */ + const char *name; int origin; diff --git a/revision.c b/revision.c index 2571ada6bf..615535c984 100644 --- a/revision.c +++ b/revision.c @@ -1825,7 +1825,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--pretty")) { revs->verbose_header = 1; revs->pretty_given = 1; - get_commit_format(arg+8, revs); + get_commit_format(NULL, revs); } else if (starts_with(arg, "--pretty=") || starts_with(arg, "--format=")) { /* * Detached form ("--pretty X" as opposed to "--pretty=X") diff --git a/run-command.c b/run-command.c index 35a3ebf07b..f3581e9717 100644 --- a/run-command.c +++ b/run-command.c @@ -752,9 +752,9 @@ int finish_async(struct async *async) #endif } -char *find_hook(const char *name) +const char *find_hook(const char *name) { - char *path = git_path("hooks/%s", name); + const char *path = git_path("hooks/%s", name); if (access(path, X_OK) < 0) path = NULL; diff --git a/run-command.h b/run-command.h index ea73de309b..890cc95f2f 100644 --- a/run-command.h +++ b/run-command.h @@ -48,7 +48,7 @@ int start_command(struct child_process *); int finish_command(struct child_process *); int run_command(struct child_process *); -extern char *find_hook(const char *name); +extern const char *find_hook(const char *name); LAST_ARG_MUST_BE_NULL extern int run_hook_le(const char *const *env, const char *name, ...); extern int run_hook_ve(const char *const *env, const char *name, va_list args); @@ -224,6 +224,36 @@ void verify_non_filename(const char *prefix, const char *arg) "'git <command> [<revision>...] -- [<file>...]'", arg); } +int get_common_dir(struct strbuf *sb, const char *gitdir) +{ + struct strbuf data = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT); + int ret = 0; + if (git_common_dir) { + strbuf_addstr(sb, git_common_dir); + return 1; + } + strbuf_addf(&path, "%s/commondir", gitdir); + if (file_exists(path.buf)) { + if (strbuf_read_file(&data, path.buf, 0) <= 0) + die_errno(_("failed to read %s"), path.buf); + while (data.len && (data.buf[data.len - 1] == '\n' || + data.buf[data.len - 1] == '\r')) + data.len--; + data.buf[data.len] = '\0'; + strbuf_reset(&path); + if (!is_absolute_path(data.buf)) + strbuf_addf(&path, "%s/", gitdir); + strbuf_addbuf(&path, &data); + strbuf_addstr(sb, real_path(path.buf)); + ret = 1; + } else + strbuf_addstr(sb, gitdir); + strbuf_release(&data); + strbuf_release(&path); + return ret; +} /* * Test if it looks like we're at a git directory. @@ -238,31 +268,40 @@ void verify_non_filename(const char *prefix, const char *arg) */ int is_git_directory(const char *suspect) { - char path[PATH_MAX]; - size_t len = strlen(suspect); + struct strbuf path = STRBUF_INIT; + int ret = 0; + size_t len; - if (PATH_MAX <= len + strlen("/objects")) - die("Too long path: %.*s", 60, suspect); - strcpy(path, suspect); + /* Check worktree-related signatures */ + strbuf_addf(&path, "%s/HEAD", suspect); + if (validate_headref(path.buf)) + goto done; + + strbuf_reset(&path); + get_common_dir(&path, suspect); + len = path.len; + + /* Check non-worktree-related signatures */ if (getenv(DB_ENVIRONMENT)) { if (access(getenv(DB_ENVIRONMENT), X_OK)) - return 0; + goto done; } else { - strcpy(path + len, "/objects"); - if (access(path, X_OK)) - return 0; + strbuf_setlen(&path, len); + strbuf_addstr(&path, "/objects"); + if (access(path.buf, X_OK)) + goto done; } - strcpy(path + len, "/refs"); - if (access(path, X_OK)) - return 0; + strbuf_setlen(&path, len); + strbuf_addstr(&path, "/refs"); + if (access(path.buf, X_OK)) + goto done; - strcpy(path + len, "/HEAD"); - if (validate_headref(path)) - return 0; - - return 1; + ret = 1; +done: + strbuf_release(&path); + return ret; } int is_inside_git_dir(void) @@ -304,9 +343,28 @@ void setup_work_tree(void) initialized = 1; } +static int check_repo_format(const char *var, const char *value, void *cb) +{ + if (strcmp(var, "core.repositoryformatversion") == 0) + repository_format_version = git_config_int(var, value); + else if (strcmp(var, "core.sharedrepository") == 0) + shared_repository = git_config_perm(var, value); + return 0; +} + static int check_repository_format_gently(const char *gitdir, int *nongit_ok) { - char repo_config[PATH_MAX+1]; + struct strbuf sb = STRBUF_INIT; + const char *repo_config; + config_fn_t fn; + int ret = 0; + + if (get_common_dir(&sb, gitdir)) + fn = check_repo_format; + else + fn = check_repository_format_version; + strbuf_addstr(&sb, "/config"); + repo_config = sb.buf; /* * git_config() can't be used here because it calls git_pathdup() @@ -317,8 +375,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok) * Use a gentler version of git_config() to check if this repo * is a good one. */ - snprintf(repo_config, PATH_MAX, "%s/config", gitdir); - git_config_early(check_repository_format_version, NULL, repo_config); + git_config_early(fn, NULL, repo_config); if (GIT_REPO_VERSION < repository_format_version) { if (!nongit_ok) die ("Expected git repo version <= %d, found %d", @@ -327,9 +384,21 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok) GIT_REPO_VERSION, repository_format_version); warning("Please upgrade Git"); *nongit_ok = -1; - return -1; + ret = -1; } - return 0; + strbuf_release(&sb); + return ret; +} + +static void update_linked_gitdir(const char *gitfile, const char *gitdir) +{ + struct strbuf path = STRBUF_INIT; + struct stat st; + + strbuf_addf(&path, "%s/gitfile", gitdir); + if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL)) + write_file(path.buf, 0, "%s\n", gitfile); + strbuf_release(&path); } /* @@ -380,6 +449,8 @@ const char *read_gitfile(const char *path) if (!is_git_directory(dir)) die("Not a git repository: %s", dir); + + update_linked_gitdir(path, dir); path = real_path(dir); free(buf); @@ -387,7 +458,7 @@ const char *read_gitfile(const char *path) } static const char *setup_explicit_git_dir(const char *gitdirenv, - char *cwd, int len, + struct strbuf *cwd, int *nongit_ok) { const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); @@ -434,16 +505,16 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, if (is_absolute_path(git_work_tree_cfg)) set_git_work_tree(git_work_tree_cfg); else { - char core_worktree[PATH_MAX]; + char *core_worktree; if (chdir(gitdirenv)) die_errno("Could not chdir to '%s'", gitdirenv); if (chdir(git_work_tree_cfg)) die_errno("Could not chdir to '%s'", git_work_tree_cfg); - if (!getcwd(core_worktree, PATH_MAX)) - die_errno("Could not get directory '%s'", git_work_tree_cfg); - if (chdir(cwd)) + core_worktree = xgetcwd(); + if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); set_git_work_tree(core_worktree); + free(core_worktree); } } else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) { @@ -459,21 +530,20 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, worktree = get_git_work_tree(); /* both get_git_work_tree() and cwd are already normalized */ - if (!strcmp(cwd, worktree)) { /* cwd == worktree */ + if (!strcmp(cwd->buf, worktree)) { /* cwd == worktree */ set_git_dir(gitdirenv); free(gitfile); return NULL; } - offset = dir_inside_of(cwd, worktree); + offset = dir_inside_of(cwd->buf, worktree); if (offset >= 0) { /* cwd inside worktree? */ set_git_dir(real_path(gitdirenv)); if (chdir(worktree)) die_errno("Could not chdir to '%s'", worktree); - cwd[len++] = '/'; - cwd[len] = '\0'; + strbuf_addch(cwd, '/'); free(gitfile); - return cwd + offset; + return cwd->buf + offset; } /* cwd outside worktree */ @@ -483,7 +553,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, } static const char *setup_discovered_git_dir(const char *gitdir, - char *cwd, int offset, int len, + struct strbuf *cwd, int offset, int *nongit_ok) { if (check_repository_format_gently(gitdir, nongit_ok)) @@ -491,17 +561,17 @@ static const char *setup_discovered_git_dir(const char *gitdir, /* --work-tree is set without --git-dir; use discovered one */ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { - if (offset != len && !is_absolute_path(gitdir)) + if (offset != cwd->len && !is_absolute_path(gitdir)) gitdir = xstrdup(real_path(gitdir)); - if (chdir(cwd)) + if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); - return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok); + return setup_explicit_git_dir(gitdir, cwd, nongit_ok); } /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */ if (is_bare_repository_cfg > 0) { - set_git_dir(offset == len ? gitdir : real_path(gitdir)); - if (chdir(cwd)) + set_git_dir(offset == cwd->len ? gitdir : real_path(gitdir)); + if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); return NULL; } @@ -512,18 +582,18 @@ static const char *setup_discovered_git_dir(const char *gitdir, set_git_dir(gitdir); inside_git_dir = 0; inside_work_tree = 1; - if (offset == len) + if (offset == cwd->len) return NULL; /* Make "offset" point to past the '/', and add a '/' at the end */ offset++; - cwd[len++] = '/'; - cwd[len] = 0; - return cwd + offset; + strbuf_addch(cwd, '/'); + return cwd->buf + offset; } /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */ -static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongit_ok) +static const char *setup_bare_git_dir(struct strbuf *cwd, int offset, + int *nongit_ok) { int root_len; @@ -536,20 +606,20 @@ static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongi if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { const char *gitdir; - gitdir = offset == len ? "." : xmemdupz(cwd, offset); - if (chdir(cwd)) + gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset); + if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); - return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok); + return setup_explicit_git_dir(gitdir, cwd, nongit_ok); } inside_git_dir = 1; inside_work_tree = 0; - if (offset != len) { - if (chdir(cwd)) + if (offset != cwd->len) { + if (chdir(cwd->buf)) die_errno("Cannot come back to cwd"); - root_len = offset_1st_component(cwd); - cwd[offset > root_len ? offset : root_len] = '\0'; - set_git_dir(cwd); + root_len = offset_1st_component(cwd->buf); + strbuf_setlen(cwd, offset > root_len ? offset : root_len); + set_git_dir(cwd->buf); } else set_git_dir("."); @@ -617,14 +687,23 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) { const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; - static char cwd[PATH_MAX + 1]; + static struct strbuf cwd = STRBUF_INIT; const char *gitdirenv, *ret; char *gitfile; - int len, offset, offset_parent, ceil_offset = -1; + int offset, offset_parent, ceil_offset = -1; dev_t current_device = 0; int one_filesystem = 1; /* + * We may have read an incomplete configuration before + * setting-up the git directory. If so, clear the cache so + * that the next queries to the configuration reload complete + * configuration (including the per-repo config file that we + * ignored previously). + */ + git_config_clear(); + + /* * Let's assume that we are in a git repository. * If it turns out later that we are somewhere else, the value will be * updated accordingly. @@ -632,9 +711,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) if (nongit_ok) *nongit_ok = 0; - if (!getcwd(cwd, sizeof(cwd) - 1)) + if (strbuf_getcwd(&cwd)) die_errno("Unable to read current working directory"); - offset = len = strlen(cwd); + offset = cwd.len; /* * If GIT_DIR is set explicitly, we're not going @@ -643,7 +722,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); if (gitdirenv) - return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok); + return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok); if (env_ceiling_dirs) { int empty_entry_found = 0; @@ -651,11 +730,11 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1); filter_string_list(&ceiling_dirs, 0, canonicalize_ceiling_entry, &empty_entry_found); - ceil_offset = longest_ancestor_length(cwd, &ceiling_dirs); + ceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs); string_list_clear(&ceiling_dirs, 0); } - if (ceil_offset < 0 && has_dos_drive_prefix(cwd)) + if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf)) ceil_offset = 1; /* @@ -683,7 +762,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) if (gitdirenv) { ret = setup_discovered_git_dir(gitdirenv, - cwd, offset, len, + &cwd, offset, nongit_ok); free(gitfile); return ret; @@ -691,29 +770,31 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) free(gitfile); if (is_git_directory(".")) - return setup_bare_git_dir(cwd, offset, len, nongit_ok); + return setup_bare_git_dir(&cwd, offset, nongit_ok); offset_parent = offset; - while (--offset_parent > ceil_offset && cwd[offset_parent] != '/'); + while (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/'); if (offset_parent <= ceil_offset) - return setup_nongit(cwd, nongit_ok); + return setup_nongit(cwd.buf, nongit_ok); if (one_filesystem) { - dev_t parent_device = get_device_or_die("..", cwd, offset); + dev_t parent_device = get_device_or_die("..", cwd.buf, + offset); if (parent_device != current_device) { if (nongit_ok) { - if (chdir(cwd)) + if (chdir(cwd.buf)) die_errno("Cannot come back to cwd"); *nongit_ok = 1; return NULL; } - cwd[offset] = '\0'; + strbuf_setlen(&cwd, offset); die("Not a git repository (or any parent up to mount point %s)\n" - "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd); + "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", + cwd.buf); } } if (chdir("..")) { - cwd[offset] = '\0'; - die_errno("Cannot change to '%s/..'", cwd); + strbuf_setlen(&cwd, offset); + die_errno("Cannot change to '%s/..'", cwd.buf); } offset = offset_parent; } @@ -789,11 +870,10 @@ int git_config_perm(const char *var, const char *value) int check_repository_format_version(const char *var, const char *value, void *cb) { - if (strcmp(var, "core.repositoryformatversion") == 0) - repository_format_version = git_config_int(var, value); - else if (strcmp(var, "core.sharedrepository") == 0) - shared_repository = git_config_perm(var, value); - else if (strcmp(var, "core.bare") == 0) { + int ret = check_repo_format(var, value, cb); + if (ret) + return ret; + if (strcmp(var, "core.bare") == 0) { is_bare_repository_cfg = git_config_bool(var, value); if (is_bare_repository_cfg == 1) inside_work_tree = -1; diff --git a/sha1_file.c b/sha1_file.c index 3f70b1d86a..e7ca43f04e 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -350,7 +350,7 @@ static void link_alt_odb_entries(const char *alt, int len, int sep, return; } - strbuf_addstr(&objdirbuf, absolute_path(get_object_directory())); + strbuf_add_absolute_path(&objdirbuf, get_object_directory()); normalize_path_copy(objdirbuf.buf, objdirbuf.buf); alt_copy = xmemdupz(alt, len); @@ -404,7 +404,7 @@ void add_to_alternates_file(const char *reference) { struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR); - char *alt = mkpath("%s\n", reference); + const char *alt = mkpath("%s\n", reference); write_or_die(fd, alt, strlen(alt)); if (commit_lock_file(lock)) die("could not close alternates file"); @@ -406,6 +406,27 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) return -1; } +int strbuf_getcwd(struct strbuf *sb) +{ + size_t oldalloc = sb->alloc; + size_t guessed_len = 128; + + for (;; guessed_len *= 2) { + strbuf_grow(sb, guessed_len); + if (getcwd(sb->buf, sb->alloc)) { + strbuf_setlen(sb, strlen(sb->buf)); + return 0; + } + if (errno != ERANGE) + break; + } + if (oldalloc == 0) + strbuf_release(sb); + else + strbuf_reset(sb); + return -1; +} + int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term) { int ch; @@ -555,6 +576,31 @@ void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes) } } +void strbuf_add_absolute_path(struct strbuf *sb, const char *path) +{ + if (!*path) + die("The empty string is not a valid path"); + if (!is_absolute_path(path)) { + struct stat cwd_stat, pwd_stat; + size_t orig_len = sb->len; + char *cwd = xgetcwd(); + char *pwd = getenv("PWD"); + if (pwd && strcmp(pwd, cwd) && + !stat(cwd, &cwd_stat) && + (cwd_stat.st_dev || cwd_stat.st_ino) && + !stat(pwd, &pwd_stat) && + pwd_stat.st_dev == cwd_stat.st_dev && + pwd_stat.st_ino == cwd_stat.st_ino) + strbuf_addstr(sb, pwd); + else + strbuf_addstr(sb, cwd); + if (sb->len > orig_len && !is_dir_sep(sb->buf[sb->len - 1])) + strbuf_addch(sb, '/'); + free(cwd); + } + strbuf_addstr(sb, path); +} + int printf_ln(const char *fmt, ...) { int ret; @@ -174,6 +174,7 @@ extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint); extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); +extern int strbuf_getcwd(struct strbuf *sb); extern int strbuf_getwholeline(struct strbuf *, FILE *, int); extern int strbuf_getline(struct strbuf *, FILE *, int); @@ -189,6 +190,8 @@ extern void strbuf_addstr_urlencode(struct strbuf *, const char *, int reserved); extern void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes); +extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path); + __attribute__((format (printf,1,2))) extern int printf_ln(const char *fmt, ...); __attribute__((format (printf,2,3))) diff --git a/submodule-config.c b/submodule-config.c new file mode 100644 index 0000000000..96623adacb --- /dev/null +++ b/submodule-config.c @@ -0,0 +1,435 @@ +#include "cache.h" +#include "submodule-config.h" +#include "submodule.h" +#include "strbuf.h" + +/* + * submodule cache lookup structure + * There is one shared set of 'struct submodule' entries which can be + * looked up by their sha1 blob id of the .gitmodule file and either + * using path or name as key. + * for_path stores submodule entries with path as key + * for_name stores submodule entries with name as key + */ +struct submodule_cache { + struct hashmap for_path; + struct hashmap for_name; +}; + +/* + * thin wrapper struct needed to insert 'struct submodule' entries to + * the hashmap + */ +struct submodule_entry { + struct hashmap_entry ent; + struct submodule *config; +}; + +static struct submodule_cache cache; +static int is_cache_init; + +static int config_path_cmp(const struct submodule_entry *a, + const struct submodule_entry *b, + const void *unused) +{ + return strcmp(a->config->path, b->config->path) || + hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1); +} + +static int config_name_cmp(const struct submodule_entry *a, + const struct submodule_entry *b, + const void *unused) +{ + return strcmp(a->config->name, b->config->name) || + hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1); +} + +static void cache_init(struct submodule_cache *cache) +{ + hashmap_init(&cache->for_path, (hashmap_cmp_fn) config_path_cmp, 0); + hashmap_init(&cache->for_name, (hashmap_cmp_fn) config_name_cmp, 0); +} + +static void free_one_config(struct submodule_entry *entry) +{ + free((void *) entry->config->path); + free((void *) entry->config->name); + free(entry->config); +} + +static void cache_free(struct submodule_cache *cache) +{ + struct hashmap_iter iter; + struct submodule_entry *entry; + + /* + * We iterate over the name hash here to be symmetric with the + * allocation of struct submodule entries. Each is allocated by + * their .gitmodule blob sha1 and submodule name. + */ + hashmap_iter_init(&cache->for_name, &iter); + while ((entry = hashmap_iter_next(&iter))) + free_one_config(entry); + + hashmap_free(&cache->for_path, 1); + hashmap_free(&cache->for_name, 1); +} + +static unsigned int hash_sha1_string(const unsigned char *sha1, + const char *string) +{ + return memhash(sha1, 20) + strhash(string); +} + +static void cache_put_path(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->path); + struct submodule_entry *e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, hash); + e->config = submodule; + hashmap_put(&cache->for_path, e); +} + +static void cache_remove_path(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->path); + struct submodule_entry e; + struct submodule_entry *removed; + hashmap_entry_init(&e, hash); + e.config = submodule; + removed = hashmap_remove(&cache->for_path, &e, NULL); + free(removed); +} + +static void cache_add(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->name); + struct submodule_entry *e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, hash); + e->config = submodule; + hashmap_add(&cache->for_name, e); +} + +static const struct submodule *cache_lookup_path(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *path) +{ + struct submodule_entry *entry; + unsigned int hash = hash_sha1_string(gitmodules_sha1, path); + struct submodule_entry key; + struct submodule key_config; + + hashcpy(key_config.gitmodules_sha1, gitmodules_sha1); + key_config.path = path; + + hashmap_entry_init(&key, hash); + key.config = &key_config; + + entry = hashmap_get(&cache->for_path, &key, NULL); + if (entry) + return entry->config; + return NULL; +} + +static struct submodule *cache_lookup_name(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *name) +{ + struct submodule_entry *entry; + unsigned int hash = hash_sha1_string(gitmodules_sha1, name); + struct submodule_entry key; + struct submodule key_config; + + hashcpy(key_config.gitmodules_sha1, gitmodules_sha1); + key_config.name = name; + + hashmap_entry_init(&key, hash); + key.config = &key_config; + + entry = hashmap_get(&cache->for_name, &key, NULL); + if (entry) + return entry->config; + return NULL; +} + +static int name_and_item_from_var(const char *var, struct strbuf *name, + struct strbuf *item) +{ + const char *subsection, *key; + int subsection_len, parse; + parse = parse_config_key(var, "submodule", &subsection, + &subsection_len, &key); + if (parse < 0 || !subsection) + return 0; + + strbuf_add(name, subsection, subsection_len); + strbuf_addstr(item, key); + + return 1; +} + +static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *name) +{ + struct submodule *submodule; + struct strbuf name_buf = STRBUF_INIT; + + submodule = cache_lookup_name(cache, gitmodules_sha1, name); + if (submodule) + return submodule; + + submodule = xmalloc(sizeof(*submodule)); + + strbuf_addstr(&name_buf, name); + submodule->name = strbuf_detach(&name_buf, NULL); + + submodule->path = NULL; + submodule->url = NULL; + submodule->fetch_recurse = RECURSE_SUBMODULES_NONE; + submodule->ignore = NULL; + + hashcpy(submodule->gitmodules_sha1, gitmodules_sha1); + + cache_add(cache, submodule); + + return submodule; +} + +static int parse_fetch_recurse(const char *opt, const char *arg, + int die_on_error) +{ + switch (git_config_maybe_bool(opt, arg)) { + case 1: + return RECURSE_SUBMODULES_ON; + case 0: + return RECURSE_SUBMODULES_OFF; + default: + if (!strcmp(arg, "on-demand")) + return RECURSE_SUBMODULES_ON_DEMAND; + + if (die_on_error) + die("bad %s argument: %s", opt, arg); + else + return RECURSE_SUBMODULES_ERROR; + } +} + +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) +{ + return parse_fetch_recurse(opt, arg, 1); +} + +static void warn_multiple_config(const unsigned char *commit_sha1, + const char *name, const char *option) +{ + const char *commit_string = "WORKTREE"; + if (commit_sha1) + commit_string = sha1_to_hex(commit_sha1); + warning("%s:.gitmodules, multiple configurations found for " + "'submodule.%s.%s'. Skipping second one!", + commit_string, name, option); +} + +struct parse_config_parameter { + struct submodule_cache *cache; + const unsigned char *commit_sha1; + const unsigned char *gitmodules_sha1; + int overwrite; +}; + +static int parse_config(const char *var, const char *value, void *data) +{ + struct parse_config_parameter *me = data; + struct submodule *submodule; + struct strbuf name = STRBUF_INIT, item = STRBUF_INIT; + int ret = 0; + + /* this also ensures that we only parse submodule entries */ + if (!name_and_item_from_var(var, &name, &item)) + return 0; + + submodule = lookup_or_create_by_name(me->cache, me->gitmodules_sha1, + name.buf); + + if (!strcmp(item.buf, "path")) { + struct strbuf path = STRBUF_INIT; + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (!me->overwrite && submodule->path != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "path"); + goto release_return; + } + + if (submodule->path) + cache_remove_path(me->cache, submodule); + free((void *) submodule->path); + strbuf_addstr(&path, value); + submodule->path = strbuf_detach(&path, NULL); + cache_put_path(me->cache, submodule); + } else if (!strcmp(item.buf, "fetchrecursesubmodules")) { + /* when parsing worktree configurations we can die early */ + int die_on_error = is_null_sha1(me->gitmodules_sha1); + if (!me->overwrite && + submodule->fetch_recurse != RECURSE_SUBMODULES_NONE) { + warn_multiple_config(me->commit_sha1, submodule->name, + "fetchrecursesubmodules"); + goto release_return; + } + + submodule->fetch_recurse = parse_fetch_recurse(var, value, + die_on_error); + } else if (!strcmp(item.buf, "ignore")) { + struct strbuf ignore = STRBUF_INIT; + if (!me->overwrite && submodule->ignore != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "ignore"); + goto release_return; + } + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (strcmp(value, "untracked") && strcmp(value, "dirty") && + strcmp(value, "all") && strcmp(value, "none")) { + warning("Invalid parameter '%s' for config option " + "'submodule.%s.ignore'", value, var); + goto release_return; + } + + free((void *) submodule->ignore); + strbuf_addstr(&ignore, value); + submodule->ignore = strbuf_detach(&ignore, NULL); + } else if (!strcmp(item.buf, "url")) { + struct strbuf url = STRBUF_INIT; + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (!me->overwrite && submodule->url != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "url"); + goto release_return; + } + + free((void *) submodule->url); + strbuf_addstr(&url, value); + submodule->url = strbuf_detach(&url, NULL); + } + +release_return: + strbuf_release(&name); + strbuf_release(&item); + + return ret; +} + +static const struct submodule *config_from_path(struct submodule_cache *cache, + const unsigned char *commit_sha1, const char *path) +{ + struct strbuf rev = STRBUF_INIT; + unsigned long config_size; + char *config; + unsigned char sha1[20]; + enum object_type type; + const struct submodule *submodule = NULL; + struct parse_config_parameter parameter; + + /* + * If any parameter except the cache is a NULL pointer just + * return the first submodule. Can be used to check whether + * there are any submodules parsed. + */ + if (!commit_sha1 || !path) { + struct hashmap_iter iter; + struct submodule_entry *entry; + + hashmap_iter_init(&cache->for_name, &iter); + entry = hashmap_iter_next(&iter); + if (!entry) + return NULL; + return entry->config; + } + + if (is_null_sha1(commit_sha1)) + return cache_lookup_path(cache, null_sha1, path); + + strbuf_addf(&rev, "%s:.gitmodules", sha1_to_hex(commit_sha1)); + if (get_sha1(rev.buf, sha1) < 0) + goto free_rev; + + submodule = cache_lookup_path(cache, sha1, path); + if (submodule) + goto free_rev; + + config = read_sha1_file(sha1, &type, &config_size); + if (!config) + goto free_rev; + + if (type != OBJ_BLOB) { + free(config); + goto free_rev; + } + + /* fill the submodule config into the cache */ + parameter.cache = cache; + parameter.commit_sha1 = commit_sha1; + parameter.gitmodules_sha1 = sha1; + parameter.overwrite = 0; + git_config_from_buf(parse_config, rev.buf, config, config_size, + ¶meter); + free(config); + + submodule = cache_lookup_path(cache, sha1, path); + +free_rev: + strbuf_release(&rev); + return submodule; +} + +static void ensure_cache_init(void) +{ + if (is_cache_init) + return; + + cache_init(&cache); + is_cache_init = 1; +} + +int parse_submodule_config_option(const char *var, const char *value) +{ + struct parse_config_parameter parameter; + parameter.cache = &cache; + parameter.commit_sha1 = NULL; + parameter.gitmodules_sha1 = null_sha1; + parameter.overwrite = 1; + + ensure_cache_init(); + return parse_config(var, value, ¶meter); +} + +const struct submodule *submodule_from_name(const unsigned char *commit_sha1, + const char *name) +{ + ensure_cache_init(); + return cache_lookup_name(&cache, commit_sha1, name); +} + +const struct submodule *submodule_from_path(const unsigned char *commit_sha1, + const char *path) +{ + ensure_cache_init(); + return config_from_path(&cache, commit_sha1, path); +} + +void submodule_free(void) +{ + cache_free(&cache); + is_cache_init = 0; +} diff --git a/submodule-config.h b/submodule-config.h new file mode 100644 index 0000000000..58afc8320a --- /dev/null +++ b/submodule-config.h @@ -0,0 +1,29 @@ +#ifndef SUBMODULE_CONFIG_CACHE_H +#define SUBMODULE_CONFIG_CACHE_H + +#include "hashmap.h" +#include "strbuf.h" + +/* + * Submodule entry containing the information about a certain submodule + * in a certain revision. + */ +struct submodule { + const char *path; + const char *name; + const char *url; + int fetch_recurse; + const char *ignore; + /* the sha1 blob id of the responsible .gitmodules file */ + unsigned char gitmodules_sha1[20]; +}; + +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); +int parse_submodule_config_option(const char *var, const char *value); +const struct submodule *submodule_from_name(const unsigned char *commit_sha1, + const char *name); +const struct submodule *submodule_from_path(const unsigned char *commit_sha1, + const char *path); +void submodule_free(); + +#endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index c3a61e70f9..01516031dd 100644 --- a/submodule.c +++ b/submodule.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "submodule-config.h" #include "submodule.h" #include "dir.h" #include "diff.h" @@ -12,9 +13,6 @@ #include "argv-array.h" #include "blob.h" -static struct string_list config_name_for_path; -static struct string_list config_fetch_recurse_submodules_for_name; -static struct string_list config_ignore_for_name; static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static struct string_list changed_submodule_paths; static int initialized_fetch_ref_tips; @@ -41,7 +39,6 @@ static int gitmodules_is_unmerged; */ static int gitmodules_is_modified; - int is_staging_gitmodules_ok(void) { return !gitmodules_is_modified; @@ -55,7 +52,7 @@ int is_staging_gitmodules_ok(void) int update_path_in_gitmodules(const char *oldpath, const char *newpath) { struct strbuf entry = STRBUF_INIT; - struct string_list_item *path_option; + const struct submodule *submodule; if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ return -1; @@ -63,13 +60,13 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - path_option = unsorted_string_list_lookup(&config_name_for_path, oldpath); - if (!path_option) { + submodule = submodule_from_path(null_sha1, oldpath); + if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), oldpath); return -1; } strbuf_addstr(&entry, "submodule."); - strbuf_addstr(&entry, path_option->util); + strbuf_addstr(&entry, submodule->name); strbuf_addstr(&entry, ".path"); if (git_config_set_in_file(".gitmodules", entry.buf, newpath) < 0) { /* Maybe the user already did that, don't error out here */ @@ -89,7 +86,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) int remove_path_from_gitmodules(const char *path) { struct strbuf sect = STRBUF_INIT; - struct string_list_item *path_option; + const struct submodule *submodule; if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ return -1; @@ -97,13 +94,13 @@ int remove_path_from_gitmodules(const char *path) if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - path_option = unsorted_string_list_lookup(&config_name_for_path, path); - if (!path_option) { + submodule = submodule_from_path(null_sha1, path); + if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), path); return -1; } strbuf_addstr(§, "submodule."); - strbuf_addstr(§, path_option->util); + strbuf_addstr(§, submodule->name); if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) { /* Maybe the user already did that, don't error out here */ warning(_("Could not remove .gitmodules entry for %s"), path); @@ -165,12 +162,10 @@ done: void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path) { - struct string_list_item *path_option, *ignore_option; - path_option = unsorted_string_list_lookup(&config_name_for_path, path); - if (path_option) { - ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util); - if (ignore_option) - handle_ignore_submodules_arg(diffopt, ignore_option->util); + const struct submodule *submodule = submodule_from_path(null_sha1, path); + if (submodule) { + if (submodule->ignore) + handle_ignore_submodules_arg(diffopt, submodule->ignore); else if (gitmodules_is_unmerged) DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); } @@ -219,58 +214,6 @@ void gitmodules_config(void) } } -int parse_submodule_config_option(const char *var, const char *value) -{ - struct string_list_item *config; - const char *name, *key; - int namelen; - - if (parse_config_key(var, "submodule", &name, &namelen, &key) < 0 || !name) - return 0; - - if (!strcmp(key, "path")) { - if (!value) - return config_error_nonbool(var); - - config = unsorted_string_list_lookup(&config_name_for_path, value); - if (config) - free(config->util); - else - config = string_list_append(&config_name_for_path, xstrdup(value)); - config->util = xmemdupz(name, namelen); - } else if (!strcmp(key, "fetchrecursesubmodules")) { - char *name_cstr = xmemdupz(name, namelen); - config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name_cstr); - if (!config) - config = string_list_append(&config_fetch_recurse_submodules_for_name, name_cstr); - else - free(name_cstr); - config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value); - } else if (!strcmp(key, "ignore")) { - char *name_cstr; - - if (!value) - return config_error_nonbool(var); - - if (strcmp(value, "untracked") && strcmp(value, "dirty") && - strcmp(value, "all") && strcmp(value, "none")) { - warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var); - return 0; - } - - name_cstr = xmemdupz(name, namelen); - config = unsorted_string_list_lookup(&config_ignore_for_name, name_cstr); - if (config) { - free(config->util); - free(name_cstr); - } else - config = string_list_append(&config_ignore_for_name, name_cstr); - config->util = xstrdup(value); - return 0; - } - return 0; -} - void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { @@ -345,20 +288,6 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, strbuf_release(&sb); } -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) -{ - switch (git_config_maybe_bool(opt, arg)) { - case 1: - return RECURSE_SUBMODULES_ON; - case 0: - return RECURSE_SUBMODULES_OFF; - default: - if (!strcmp(arg, "on-demand")) - return RECURSE_SUBMODULES_ON_DEMAND; - die("bad %s argument: %s", opt, arg); - } -} - void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], @@ -650,7 +579,7 @@ static void calculate_changed_submodule_paths(void) struct argv_array argv = ARGV_ARRAY_INIT; /* No need to check if there are no submodules configured */ - if (!config_name_for_path.nr) + if (!submodule_from_path(NULL, NULL)) return; init_revisions(&rev, NULL); @@ -697,7 +626,6 @@ int fetch_populated_submodules(const struct argv_array *options, int i, result = 0; struct child_process cp; struct argv_array argv = ARGV_ARRAY_INIT; - struct string_list_item *name_for_path; const char *work_tree = get_git_work_tree(); if (!work_tree) goto out; @@ -723,24 +651,26 @@ int fetch_populated_submodules(const struct argv_array *options, struct strbuf submodule_git_dir = STRBUF_INIT; struct strbuf submodule_prefix = STRBUF_INIT; const struct cache_entry *ce = active_cache[i]; - const char *git_dir, *name, *default_argv; + const char *git_dir, *default_argv; + const struct submodule *submodule; if (!S_ISGITLINK(ce->ce_mode)) continue; - name = ce->name; - name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name); - if (name_for_path) - name = name_for_path->util; + submodule = submodule_from_path(null_sha1, ce->name); + if (!submodule) + submodule = submodule_from_name(null_sha1, ce->name); default_argv = "yes"; if (command_line_option == RECURSE_SUBMODULES_DEFAULT) { - struct string_list_item *fetch_recurse_submodules_option; - fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name); - if (fetch_recurse_submodules_option) { - if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_OFF) + if (submodule && + submodule->fetch_recurse != + RECURSE_SUBMODULES_NONE) { + if (submodule->fetch_recurse == + RECURSE_SUBMODULES_OFF) continue; - if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_ON_DEMAND) { + if (submodule->fetch_recurse == + RECURSE_SUBMODULES_ON_DEMAND) { if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name)) continue; default_argv = "on-demand"; @@ -1109,16 +1039,11 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir) struct strbuf file_name = STRBUF_INIT; struct strbuf rel_path = STRBUF_INIT; const char *real_work_tree = xstrdup(real_path(work_tree)); - FILE *fp; /* Update gitfile */ strbuf_addf(&file_name, "%s/.git", work_tree); - fp = fopen(file_name.buf, "w"); - if (!fp) - die(_("Could not create git link %s"), file_name.buf); - fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree, - &rel_path)); - fclose(fp); + write_file(file_name.buf, 1, "gitdir: %s\n", + relative_path(git_dir, real_work_tree, &rel_path)); /* Update core.worktree setting */ strbuf_reset(&file_name); diff --git a/submodule.h b/submodule.h index 7beec4822b..5507c3d9a0 100644 --- a/submodule.h +++ b/submodule.h @@ -5,6 +5,8 @@ struct diff_options; struct argv_array; enum { + RECURSE_SUBMODULES_ERROR = -3, + RECURSE_SUBMODULES_NONE = -2, RECURSE_SUBMODULES_ON_DEMAND = -1, RECURSE_SUBMODULES_OFF = 0, RECURSE_SUBMODULES_DEFAULT = 1, @@ -19,9 +21,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); -int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index c0143a0a70..93605f42f2 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -19,6 +19,14 @@ relative_path() { "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'" } +test_git_path() { + test_expect_success "git-path $1 $2 => $3" " + $1 git rev-parse --git-path $2 >actual && + echo $3 >expect && + test_cmp expect actual + " +} + # On Windows, we are using MSYS's bash, which mangles the paths. # Absolute paths are anchored at the MSYS installation directory, # which means that the path / accounts for this many characters: @@ -244,4 +252,32 @@ relative_path "<null>" "<empty>" ./ relative_path "<null>" "<null>" ./ relative_path "<null>" /foo/a/b ./ +test_git_path A=B info/grafts .git/info/grafts +test_git_path GIT_GRAFT_FILE=foo info/grafts foo +test_git_path GIT_GRAFT_FILE=foo info/////grafts foo +test_git_path GIT_INDEX_FILE=foo index foo +test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo +test_git_path GIT_INDEX_FILE=foo index2 .git/index2 +test_expect_success 'setup fake objects directory foo' 'mkdir foo' +test_git_path GIT_OBJECT_DIRECTORY=foo objects foo +test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo +test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2 +test_expect_success 'setup common repository' 'git --git-dir=bar init' +test_git_path GIT_COMMON_DIR=bar index .git/index +test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD +test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD +test_git_path GIT_COMMON_DIR=bar objects bar/objects +test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar +test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude +test_git_path GIT_COMMON_DIR=bar info/grafts bar/info/grafts +test_git_path GIT_COMMON_DIR=bar info/sparse-checkout .git/info/sparse-checkout +test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar +test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar +test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master +test_git_path GIT_COMMON_DIR=bar refs/heads/master bar/refs/heads/master +test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me +test_git_path GIT_COMMON_DIR=bar config bar/config +test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs +test_git_path GIT_COMMON_DIR=bar shallow bar/shallow + test_done diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index 6c33e28ee8..48c42409d2 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -8,7 +8,7 @@ cache-tree extension. . ./test-lib.sh cmp_cache_tree () { - test-dump-cache-tree >actual && + test-dump-cache-tree | sed -e '/#(ref)/d' >actual && sed "s/$_x40/SHA/" <actual >filtered && test_cmp "$1" filtered } @@ -16,15 +16,40 @@ cmp_cache_tree () { # We don't bother with actually checking the SHA1: # test-dump-cache-tree already verifies that all existing data is # correct. -test_shallow_cache_tree () { - printf "SHA (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >expect && +generate_expected_cache_tree_rec () { + dir="$1${1:+/}" && + parent="$2" && + # ls-files might have foo/bar, foo/bar/baz, and foo/bar/quux + # We want to count only foo because it's the only direct child + subtrees=$(git ls-files|grep /|cut -d / -f 1|uniq) && + subtree_count=$(echo "$subtrees"|awk '$1 {++c} END {print c}') && + entries=$(git ls-files|wc -l) && + printf "SHA $dir (%d entries, %d subtrees)\n" "$entries" "$subtree_count" && + for subtree in $subtrees + do + cd "$subtree" + generate_expected_cache_tree_rec "$dir$subtree" "$dir" || return 1 + cd .. + done && + dir=$parent +} + +generate_expected_cache_tree () { + ( + generate_expected_cache_tree_rec + ) +} + +test_cache_tree () { + generate_expected_cache_tree >expect && cmp_cache_tree expect } test_invalid_cache_tree () { - echo "invalid (0 subtrees)" >expect && - printf "SHA #(ref) (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >>expect && - cmp_cache_tree expect + printf "invalid %s ()\n" "" "$@" >expect && + test-dump-cache-tree | + sed -n -e "s/[0-9]* subtrees//" -e '/#(ref)/d' -e '/^invalid /p' >actual && + test_cmp expect actual } test_no_cache_tree () { @@ -32,26 +57,59 @@ test_no_cache_tree () { cmp_cache_tree expect } -test_expect_failure 'initial commit has cache-tree' ' +test_expect_success 'initial commit has cache-tree' ' test_commit foo && - test_shallow_cache_tree + test_cache_tree ' test_expect_success 'read-tree HEAD establishes cache-tree' ' git read-tree HEAD && - test_shallow_cache_tree + test_cache_tree ' test_expect_success 'git-add invalidates cache-tree' ' test_when_finished "git reset --hard; git read-tree HEAD" && - echo "I changed this file" > foo && + echo "I changed this file" >foo && git add foo && test_invalid_cache_tree ' +test_expect_success 'git-add in subdir invalidates cache-tree' ' + test_when_finished "git reset --hard; git read-tree HEAD" && + mkdir dirx && + echo "I changed this file" >dirx/foo && + git add dirx/foo && + test_invalid_cache_tree +' + +cat >before <<\EOF +SHA (3 entries, 2 subtrees) +SHA dir1/ (1 entries, 0 subtrees) +SHA dir2/ (1 entries, 0 subtrees) +EOF + +cat >expect <<\EOF +invalid (2 subtrees) +invalid dir1/ (0 subtrees) +SHA dir2/ (1 entries, 0 subtrees) +EOF + +test_expect_success 'git-add in subdir does not invalidate sibling cache-tree' ' + git tag no-children && + test_when_finished "git reset --hard no-children; git read-tree HEAD" && + mkdir dir1 dir2 && + test_commit dir1/a && + test_commit dir2/b && + echo "I changed this file" >dir1/a && + cmp_cache_tree before && + echo "I changed this file" >dir1/a && + git add dir1/a && + cmp_cache_tree expect +' + test_expect_success 'update-index invalidates cache-tree' ' test_when_finished "git reset --hard; git read-tree HEAD" && - echo "I changed this file" > foo && + echo "I changed this file" >foo && git update-index --add foo && test_invalid_cache_tree ' @@ -59,7 +117,7 @@ test_expect_success 'update-index invalidates cache-tree' ' test_expect_success 'write-tree establishes cache-tree' ' test-scrap-cache-tree && git write-tree && - test_shallow_cache_tree + test_cache_tree ' test_expect_success 'test-scrap-cache-tree works' ' @@ -70,24 +128,86 @@ test_expect_success 'test-scrap-cache-tree works' ' test_expect_success 'second commit has cache-tree' ' test_commit bar && - test_shallow_cache_tree + test_cache_tree +' + +test_expect_success 'commit --interactive gives cache-tree on partial commit' ' + cat <<-\EOT >foo.c && + int foo() + { + return 42; + } + int bar() + { + return 42; + } + EOT + git add foo.c && + test_invalid_cache_tree && + git commit -m "add a file" && + test_cache_tree && + cat <<-\EOT >foo.c && + int foo() + { + return 43; + } + int bar() + { + return 44; + } + EOT + (echo p; echo 1; echo; echo s; echo n; echo y; echo q) | + git commit --interactive -m foo && + test_cache_tree +' + +test_expect_success 'commit in child dir has cache-tree' ' + mkdir dir && + >dir/child.t && + git add dir/child.t && + git commit -m dir/child.t && + test_cache_tree ' test_expect_success 'reset --hard gives cache-tree' ' test-scrap-cache-tree && git reset --hard && - test_shallow_cache_tree + test_cache_tree ' test_expect_success 'reset --hard without index gives cache-tree' ' rm -f .git/index && git reset --hard && - test_shallow_cache_tree + test_cache_tree ' -test_expect_failure 'checkout gives cache-tree' ' +test_expect_success 'checkout gives cache-tree' ' + git tag current && git checkout HEAD^ && - test_shallow_cache_tree + test_cache_tree +' + +test_expect_success 'checkout -b gives cache-tree' ' + git checkout current && + git checkout -b prev HEAD^ && + test_cache_tree +' + +test_expect_success 'checkout -B gives cache-tree' ' + git checkout current && + git checkout -B prev HEAD^ && + test_cache_tree +' + +test_expect_success 'partial commit gives cache-tree' ' + git checkout -b partial no-children && + test_commit one && + test_commit two && + echo "some change" >one.t && + git add one.t && + echo "some other change" >two.t && + git commit two.t -m partial && + test_cache_tree ' test_done diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh new file mode 100755 index 0000000000..7fdf840b00 --- /dev/null +++ b/t/t1308-config-set.sh @@ -0,0 +1,200 @@ +#!/bin/sh + +test_description='Test git config-set API in different settings' + +. ./test-lib.sh + +# 'check_config get_* section.key value' verifies that the entry for +# section.key is 'value' +check_config () { + if test "$1" = expect_code + then + expect_code="$2" && shift && shift + else + expect_code=0 + fi && + op=$1 key=$2 && shift && shift && + if test $# != 0 + then + printf "%s\n" "$@" + fi >expect && + test_expect_code $expect_code test-config "$op" "$key" >actual && + test_cmp expect actual +} + +test_expect_success 'setup default config' ' + cat >.git/config <<\EOF + [case] + penguin = very blue + Movie = BadPhysics + UPPERCASE = true + MixedCase = true + my = + foo + baz = sam + [Cores] + WhatEver = Second + baz = bar + [cores] + baz = bat + [CORES] + baz = ball + [my "Foo bAr"] + hi = mixed-case + [my "FOO BAR"] + hi = upper-case + [my "foo bar"] + hi = lower-case + [case] + baz = bat + baz = hask + [lamb] + chop = 65 + head = none + [goat] + legs = 4 + head = true + skin = false + nose = 1 + horns + EOF +' + +test_expect_success 'get value for a simple key' ' + check_config get_value case.penguin "very blue" +' + +test_expect_success 'get value for a key with value as an empty string' ' + check_config get_value case.my "" +' + +test_expect_success 'get value for a key with value as NULL' ' + check_config get_value case.foo "(NULL)" +' + +test_expect_success 'upper case key' ' + check_config get_value case.UPPERCASE "true" && + check_config get_value case.uppercase "true" +' + +test_expect_success 'mixed case key' ' + check_config get_value case.MixedCase "true" && + check_config get_value case.MIXEDCASE "true" && + check_config get_value case.mixedcase "true" +' + +test_expect_success 'key and value with mixed case' ' + check_config get_value case.Movie "BadPhysics" +' + +test_expect_success 'key with case sensitive subsection' ' + check_config get_value "my.Foo bAr.hi" "mixed-case" && + check_config get_value "my.FOO BAR.hi" "upper-case" && + check_config get_value "my.foo bar.hi" "lower-case" +' + +test_expect_success 'key with case insensitive section header' ' + check_config get_value cores.baz "ball" && + check_config get_value Cores.baz "ball" && + check_config get_value CORES.baz "ball" && + check_config get_value coreS.baz "ball" +' + +test_expect_success 'key with case insensitive section header & variable' ' + check_config get_value CORES.BAZ "ball" && + check_config get_value cores.baz "ball" && + check_config get_value cores.BaZ "ball" && + check_config get_value cOreS.bAz "ball" +' + +test_expect_success 'find value with misspelled key' ' + check_config expect_code 1 get_value "my.fOo Bar.hi" "Value not found for \"my.fOo Bar.hi\"" +' + +test_expect_success 'find value with the highest priority' ' + check_config get_value case.baz "hask" +' + +test_expect_success 'find integer value for a key' ' + check_config get_int lamb.chop 65 +' + +test_expect_success 'find integer if value is non parse-able' ' + check_config expect_code 128 get_int lamb.head +' + +test_expect_success 'find bool value for the entered key' ' + check_config get_bool goat.head 1 && + check_config get_bool goat.skin 0 && + check_config get_bool goat.nose 1 && + check_config get_bool goat.horns 1 && + check_config get_bool goat.legs 1 +' + +test_expect_success 'find multiple values' ' + check_config get_value_multi case.baz sam bat hask +' + +test_expect_success 'find value from a configset' ' + cat >config2 <<-\EOF && + [case] + baz = lama + [my] + new = silk + [case] + baz = ball + EOF + echo silk >expect && + test-config configset_get_value my.new config2 .git/config >actual && + test_cmp expect actual +' + +test_expect_success 'find value with highest priority from a configset' ' + echo hask >expect && + test-config configset_get_value case.baz config2 .git/config >actual && + test_cmp expect actual +' + +test_expect_success 'find value_list for a key from a configset' ' + cat >except <<-\EOF && + sam + bat + hask + lama + ball + EOF + test-config configset_get_value case.baz config2 .git/config >actual && + test_cmp expect actual +' + +test_expect_success 'proper error on non-existent files' ' + echo "Error (-1) reading configuration file non-existent-file." >expect && + test_expect_code 2 test-config configset_get_value foo.bar non-existent-file 2>actual && + test_cmp expect actual +' + +test_expect_success POSIXPERM,SANITY 'proper error on non-accessible files' ' + chmod -r .git/config && + test_when_finished "chmod +r .git/config" && + echo "Error (-1) reading configuration file .git/config." >expect && + test_expect_code 2 test-config configset_get_value foo.bar .git/config 2>actual && + test_cmp expect actual +' + +test_expect_success 'proper error on error in default config files' ' + cp .git/config .git/config.old && + test_when_finished "mv .git/config.old .git/config" && + echo "[" >>.git/config && + echo "fatal: bad config file line 35 in .git/config" >expect && + test_expect_code 128 test-config get_value foo.bar 2>actual && + test_cmp expect actual +' + +test_expect_success 'proper error on error in custom config files' ' + echo "[" >>syntax-error && + echo "fatal: bad config file line 1 in syntax-error" >expect && + test_expect_code 128 test-config configset_get_value foo.bar syntax-error 2>actual && + test_cmp expect actual +' + +test_done diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 8f36aa9fc4..e6ac7a4b70 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -346,4 +346,80 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' ' test_cmp expected actual ' +test_expect_success 'Multi-worktree setup' ' + mkdir work && + mkdir -p repo.git/repos/foo && + cp repo.git/HEAD repo.git/index repo.git/repos/foo && + sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE +' + +test_expect_success 'GIT_DIR set (1)' ' + echo "gitdir: repo.git/repos/foo" >gitfile && + echo ../.. >repo.git/repos/foo/commondir && + ( + cd work && + GIT_DIR=../gitfile git rev-parse --git-common-dir >actual && + test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'GIT_DIR set (2)' ' + echo "gitdir: repo.git/repos/foo" >gitfile && + echo "$TRASH_DIRECTORY/repo.git" >repo.git/repos/foo/commondir && + ( + cd work && + GIT_DIR=../gitfile git rev-parse --git-common-dir >actual && + test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'Auto discovery' ' + echo "gitdir: repo.git/repos/foo" >.git && + echo ../.. >repo.git/repos/foo/commondir && + ( + cd work && + git rev-parse --git-common-dir >actual && + test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test_cmp expect actual && + echo haha >data1 && + git add data1 && + git ls-files --full-name :/ | grep data1 >actual && + echo work/data1 >expect && + test_cmp expect actual + ) +' + +test_expect_success '$GIT_DIR/common overrides core.worktree' ' + mkdir elsewhere && + git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" && + echo "gitdir: repo.git/repos/foo" >.git && + echo ../.. >repo.git/repos/foo/commondir && + ( + cd work && + git rev-parse --git-common-dir >actual && + test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test_cmp expect actual && + echo haha >data2 && + git add data2 && + git ls-files --full-name :/ | grep data2 >actual && + echo work/data2 >expect && + test_cmp expect actual + ) +' + +test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' ' + echo "gitdir: repo.git/repos/foo" >.git && + echo ../.. >repo.git/repos/foo/commondir && + ( + cd work && + echo haha >data3 && + git --git-dir=../.git --work-tree=. add data3 && + git ls-files --full-name -- :/ | grep data3 >actual && + echo data3 >expect && + test_cmp expect actual + ) +' + test_done diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh index e1b2a99f10..33c1a587b3 100755 --- a/t/t1510-repo-setup.sh +++ b/t/t1510-repo-setup.sh @@ -106,6 +106,7 @@ setup_env () { expect () { cat >"$1/expected" <<-EOF setup: git_dir: $2 + setup: git_common_dir: $2 setup: worktree: $3 setup: cwd: $4 setup: prefix: $5 diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh new file mode 100755 index 0000000000..508993f813 --- /dev/null +++ b/t/t2025-checkout-to.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='test git checkout --to' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init +' + +test_expect_success 'checkout --to not updating paths' ' + test_must_fail git checkout --to -- init.t +' + +test_expect_success 'checkout --to refuses to checkout locked branch' ' + test_must_fail git checkout --to zere master && + ! test -d zere && + ! test -d .git/repos/zere +' + +test_expect_success 'checkout --to a new worktree' ' + git rev-parse HEAD >expect && + git checkout --detach --to here master && + ( + cd here && + test_cmp ../init.t init.t && + test_must_fail git symbolic-ref HEAD && + git rev-parse HEAD >actual && + test_cmp ../expect actual && + git fsck + ) +' + +test_expect_success 'checkout --to from a linked checkout' ' + ( + cd here && + git checkout --detach --to nested-here master + cd nested-here && + git fsck + ) +' + +test_expect_success 'checkout --to a new worktree creating new branch' ' + git checkout --to there -b newmaster master && + ( + cd there && + test_cmp ../init.t init.t && + git symbolic-ref HEAD >actual && + echo refs/heads/newmaster >expect && + test_cmp expect actual && + git fsck + ) +' + +test_expect_success 'die the same branch is already checked out' ' + ( + cd here && + test_must_fail git checkout newmaster + ) +' + +test_expect_success 'not die on re-checking out current branch' ' + ( + cd there && + git checkout newmaster + ) +' + +test_expect_success 'checkout --to from a bare repo' ' + ( + git clone --bare . bare && + cd bare && + git checkout --to ../there2 -b bare-master master + ) +' + +test_expect_success 'checkout from a bare repo without --to' ' + ( + cd bare && + test_must_fail git checkout master + ) +' + +test_expect_success 'checkout with grafts' ' + test_when_finished rm .git/info/grafts && + test_commit abc && + SHA1=`git rev-parse HEAD` && + test_commit def && + test_commit xyz && + echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts && + cat >expected <<-\EOF && + xyz + abc + EOF + git log --format=%s -2 >actual && + test_cmp expected actual && + git checkout --detach --to grafted master && + git --git-dir=grafted/.git log --format=%s -2 >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh new file mode 100755 index 0000000000..79d84cba42 --- /dev/null +++ b/t/t2026-prune-linked-checkouts.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +test_description='prune $GIT_DIR/repos' + +. ./test-lib.sh + +test_expect_success 'prune --repos on normal repo' ' + git prune --repos && + test_must_fail git prune --repos abc +' + +test_expect_success 'prune files inside $GIT_DIR/repos' ' + mkdir .git/repos && + : >.git/repos/abc && + git prune --repos --verbose >actual && + cat >expect <<EOF && +Removing repos/abc: not a valid directory +EOF + test_i18ncmp expect actual && + ! test -f .git/repos/abc && + ! test -d .git/repos +' + +test_expect_success 'prune directories without gitdir' ' + mkdir -p .git/repos/def/abc && + : >.git/repos/def/def && + cat >expect <<EOF && +Removing repos/def: gitdir file does not exist +EOF + git prune --repos --verbose >actual && + test_i18ncmp expect actual && + ! test -d .git/repos/def && + ! test -d .git/repos +' + +test_expect_success POSIXPERM 'prune directories with unreadable gitdir' ' + mkdir -p .git/repos/def/abc && + : >.git/repos/def/def && + : >.git/repos/def/gitdir && + chmod u-r .git/repos/def/gitdir && + git prune --repos --verbose >actual && + test_i18ngrep "Removing repos/def: unable to read gitdir file" actual && + ! test -d .git/repos/def && + ! test -d .git/repos +' + +test_expect_success 'prune directories with invalid gitdir' ' + mkdir -p .git/repos/def/abc && + : >.git/repos/def/def && + : >.git/repos/def/gitdir && + git prune --repos --verbose >actual && + test_i18ngrep "Removing repos/def: invalid gitdir file" actual && + ! test -d .git/repos/def && + ! test -d .git/repos +' + +test_expect_success 'prune directories with gitdir pointing to nowhere' ' + mkdir -p .git/repos/def/abc && + : >.git/repos/def/def && + echo "$TRASH_DIRECTORY"/nowhere >.git/repos/def/gitdir && + git prune --repos --verbose >actual && + test_i18ngrep "Removing repos/def: gitdir file points to non-existent location" actual && + ! test -d .git/repos/def && + ! test -d .git/repos +' + +test_expect_success 'not prune locked checkout' ' + test_when_finished rm -r .git/repos + mkdir -p .git/repos/ghi && + : >.git/repos/ghi/locked && + git prune --repos && + test -d .git/repos/ghi +' + +test_expect_success 'not prune recent checkouts' ' + test_when_finished rm -r .git/repos + mkdir zz && + mkdir -p .git/repos/jlm && + echo "$TRASH_DIRECTORY"/zz >.git/repos/jlm/gitdir && + git prune --repos --verbose --expire=2.days.ago && + test -d .git/repos/jlm +' + +test_done diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 41913c3aa3..0b4f7dfdc6 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -401,4 +401,38 @@ test_expect_success 'combine diff missing delete bug' ' compare_diff_patch expected actual ' +test_expect_success 'combine diff gets tree sorting right' ' + # create a directory and a file that sort differently in trees + # versus byte-wise (implied "/" sorts after ".") + git checkout -f master && + mkdir foo && + echo base >foo/one && + echo base >foo/two && + echo base >foo.ext && + git add foo foo.ext && + git commit -m base && + + # one side modifies a file in the directory, along with the root + # file... + echo master >foo/one && + echo master >foo.ext && + git commit -a -m master && + + # the other side modifies the other file in the directory + git checkout -b other HEAD^ && + echo other >foo/two && + git commit -a -m other && + + # And now we merge. The files in the subdirectory will resolve cleanly, + # meaning that a combined diff will not find them interesting. But it + # will find the tree itself interesting, because it had to be merged. + git checkout master && + git merge other && + + printf "MM\tfoo\n" >expect && + git diff-tree -c --name-status -t HEAD >actual.tmp && + sed 1d <actual.tmp >actual && + test_cmp expect actual +' + test_done diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 305bcac6b7..83d20c4ba9 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -113,4 +113,9 @@ test_expect_success 'archive empty subtree by direct pathspec' ' check_dir extract sub ' +test_expect_success 'archive applies umask even for pax headers' ' + git archive --format=tar HEAD >archive.tar && + ! grep 0666 archive.tar +' + test_done diff --git a/t/t5311-pack-bitmaps-shallow.sh b/t/t5311-pack-bitmaps-shallow.sh new file mode 100755 index 0000000000..872a95df33 --- /dev/null +++ b/t/t5311-pack-bitmaps-shallow.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +test_description='check bitmap operation with shallow repositories' +. ./test-lib.sh + +# We want to create a situation where the shallow, grafted +# view of reachability does not match reality in a way that +# might cause us to send insufficient objects. +# +# We do this with a history that repeats a state, like: +# +# A -- B -- C +# file=1 file=2 file=1 +# +# and then create a shallow clone to the second commit, B. +# In a non-shallow clone, that would mean we already have +# the tree for A. But in a shallow one, we've grafted away +# A, and fetching A to B requires that the other side send +# us the tree for file=1. +test_expect_success 'setup shallow repo' ' + echo 1 >file && + git add file && + git commit -m orig && + echo 2 >file && + git commit -a -m update && + git clone --no-local --bare --depth=1 . shallow.git && + echo 1 >file && + git commit -a -m repeat +' + +test_expect_success 'turn on bitmaps in the parent' ' + git repack -adb +' + +test_expect_success 'shallow fetch from bitmapped repo' ' + (cd shallow.git && git fetch) +' + +test_done diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh new file mode 100755 index 0000000000..1ba0d64626 --- /dev/null +++ b/t/t7411-submodule-config.sh @@ -0,0 +1,141 @@ +#!/bin/sh +# +# Copyright (c) 2014 Heiko Voigt +# + +test_description='Test submodules config cache infrastructure + +This test verifies that parsing .gitmodules configurations directly +from the database and from the worktree works. +' + +TEST_NO_CREATE_REPO=1 +. ./test-lib.sh + +test_expect_success 'submodule config cache setup' ' + mkdir submodule && + (cd submodule && + git init && + echo a >a && + git add . && + git commit -ma + ) && + mkdir super && + (cd super && + git init && + git submodule add ../submodule && + git submodule add ../submodule a && + git commit -m "add as submodule and as a" && + git mv a b && + git commit -m "move a to b" + ) +' + +cat >super/expect <<EOF +Submodule name: 'a' for path 'a' +Submodule name: 'a' for path 'b' +Submodule name: 'submodule' for path 'submodule' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'test parsing of submodule config' ' + (cd super && + test-submodule-config \ + HEAD^ a \ + HEAD b \ + HEAD^ submodule \ + HEAD submodule \ + >actual && + test_cmp expect actual + ) +' + +cat >super/expect_error <<EOF +Submodule name: 'a' for path 'b' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'error in one submodule config lets continue' ' + (cd super && + cp .gitmodules .gitmodules.bak && + echo " value = \"" >>.gitmodules && + git add .gitmodules && + mv .gitmodules.bak .gitmodules && + git commit -m "add error" && + test-submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual + ) +' + +cat >super/expect_url <<EOF +Submodule url: 'git@somewhere.else.net:a.git' for path 'b' +Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule' +EOF + +cat >super/expect_local_path <<EOF +Submodule name: 'a' for path 'c' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'reading of local configuration' ' + (cd super && + old_a=$(git config submodule.a.url) && + old_submodule=$(git config submodule.submodule.url) && + git config submodule.a.url git@somewhere.else.net:a.git && + git config submodule.submodule.url git@somewhere.else.net:submodule.git && + test-submodule-config --url \ + "" b \ + "" submodule \ + >actual && + test_cmp expect_url actual && + git config submodule.a.path c && + test-submodule-config \ + "" c \ + "" submodule \ + >actual && + test_cmp expect_local_path actual && + git config submodule.a.url $old_a && + git config submodule.submodule.url $old_submodule && + git config --unset submodule.a.path c + ) +' + +cat >super/expect_fetchrecurse_die.err <<EOF +fatal: bad submodule.submodule.fetchrecursesubmodules argument: blabla +EOF + +test_expect_success 'local error in fetchrecursesubmodule dies early' ' + (cd super && + git config submodule.submodule.fetchrecursesubmodules blabla && + test_must_fail test-submodule-config \ + "" b \ + "" submodule \ + >actual.out 2>actual.err && + touch expect_fetchrecurse_die.out && + test_cmp expect_fetchrecurse_die.out actual.out && + test_cmp expect_fetchrecurse_die.err actual.err && + git config --unset submodule.submodule.fetchrecursesubmodules + ) +' + +test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' + (cd super && + git config -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules blabla && + git add .gitmodules && + git config --unset -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules && + git commit -m "add error in fetchrecursesubmodules" && + test-submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual && + git reset --hard HEAD^ + ) +' + +test_done diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample index 8b2a2fe84f..a5d7b84a67 100755 --- a/templates/hooks--applypatch-msg.sample +++ b/templates/hooks--applypatch-msg.sample @@ -10,6 +10,6 @@ # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup -test -x "$GIT_DIR/hooks/commit-msg" && - exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample index b1f187c2e9..4142082bcb 100755 --- a/templates/hooks--pre-applypatch.sample +++ b/templates/hooks--pre-applypatch.sample @@ -9,6 +9,6 @@ # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} : diff --git a/test-config.c b/test-config.c new file mode 100644 index 0000000000..9dd1b22630 --- /dev/null +++ b/test-config.c @@ -0,0 +1,142 @@ +#include "cache.h" +#include "string-list.h" + +/* + * This program exposes the C API of the configuration mechanism + * as a set of simple commands in order to facilitate testing. + * + * Reads stdin and prints result of command to stdout: + * + * get_value -> prints the value with highest priority for the entered key + * + * get_value_multi -> prints all values for the entered key in increasing order + * of priority + * + * get_int -> print integer value for the entered key or die + * + * get_bool -> print bool value for the entered key or die + * + * configset_get_value -> returns value with the highest priority for the entered key + * from a config_set constructed from files entered as arguments. + * + * configset_get_value_multi -> returns value_list for the entered key sorted in + * ascending order of priority from a config_set + * constructed from files entered as arguments. + * + * Examples: + * + * To print the value with highest priority for key "foo.bAr Baz.rock": + * test-config get_value "foo.bAr Baz.rock" + * + */ + + +int main(int argc, char **argv) +{ + int i, val; + const char *v; + const struct string_list *strptr; + struct config_set cs; + git_configset_init(&cs); + + if (argc < 2) { + fprintf(stderr, "Please, provide a command name on the command-line\n"); + goto exit1; + } else if (argc == 3 && !strcmp(argv[1], "get_value")) { + if (!git_config_get_value(argv[2], &v)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_value_multi")) { + strptr = git_config_get_value_multi(argv[2]); + if (strptr) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_int")) { + if (!git_config_get_int(argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_bool")) { + if (!git_config_get_bool(argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + if (!git_configset_get_value(&cs, argv[2], &v)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value_multi")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + strptr = git_configset_get_value_multi(&cs, argv[2]); + if (strptr) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } + + die("%s: Please check the syntax and the function name", argv[0]); + +exit0: + git_configset_clear(&cs); + return 0; + +exit1: + git_configset_clear(&cs); + return 1; + +exit2: + git_configset_clear(&cs); + return 2; +} diff --git a/test-dump-cache-tree.c b/test-dump-cache-tree.c index 330ba4f4dd..54c0872fcb 100644 --- a/test-dump-cache-tree.c +++ b/test-dump-cache-tree.c @@ -26,16 +26,16 @@ static int dump_cache_tree(struct cache_tree *it, return 0; if (it->entry_count < 0) { + /* invalid */ dump_one(it, pfx, ""); dump_one(ref, pfx, "#(ref) "); - if (it->subtree_nr != ref->subtree_nr) - errs = 1; } else { dump_one(it, pfx, ""); if (hashcmp(it->sha1, ref->sha1) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { + /* claims to be valid but is lying */ dump_one(ref, pfx, "#(ref) "); errs = 1; } diff --git a/test-submodule-config.c b/test-submodule-config.c new file mode 100644 index 0000000000..eda356e6d5 --- /dev/null +++ b/test-submodule-config.c @@ -0,0 +1,70 @@ +#include "cache.h" +#include "submodule-config.h" +#include "submodule.h" + +static void die_usage(int argc, char **argv, const char *msg) +{ + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "Usage: %s [<commit> <submodulepath>] ...\n", argv[0]); + exit(1); +} + +static int git_test_config(const char *var, const char *value, void *cb) +{ + return parse_submodule_config_option(var, value); +} + +int main(int argc, char **argv) +{ + char **arg = argv; + int my_argc = argc; + int output_url = 0; + + arg++; + my_argc--; + while (starts_with(arg[0], "--")) { + if (!strcmp(arg[0], "--url")) + output_url = 1; + arg++; + my_argc--; + } + + if (my_argc % 2 != 0) + die_usage(argc, argv, "Wrong number of arguments."); + + setup_git_directory(); + gitmodules_config(); + git_config(git_test_config, NULL); + + while (*arg) { + unsigned char commit_sha1[20]; + const struct submodule *submodule; + const char *commit; + const char *path; + + commit = arg[0]; + path = arg[1]; + + if (commit[0] == '\0') + hashcpy(commit_sha1, null_sha1); + else if (get_sha1(commit, commit_sha1) < 0) + die_usage(argc, argv, "Commit not found."); + + submodule = submodule_from_path(commit_sha1, path); + if (!submodule) + die_usage(argc, argv, "Submodule not found."); + + if (output_url) + printf("Submodule url: '%s' for path '%s'\n", + submodule->url, path); + else + printf("Submodule name: '%s' for path '%s'\n", + submodule->name, path); + + arg += 2; + } + + submodule_free(); + + return 0; +} @@ -298,13 +298,12 @@ void trace_repo_setup(const char *prefix) { static struct trace_key key = TRACE_KEY_INIT(SETUP); const char *git_work_tree; - char cwd[PATH_MAX]; + char *cwd; if (!trace_want(&key)) return; - if (!getcwd(cwd, PATH_MAX)) - die("Unable to get current working directory"); + cwd = xgetcwd(); if (!(git_work_tree = get_git_work_tree())) git_work_tree = "(null)"; @@ -313,9 +312,12 @@ void trace_repo_setup(const char *prefix) prefix = "(null)"; trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir())); + trace_printf_key(&key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir())); trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree)); trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd)); trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix)); + + free(cwd); } int trace_want(struct trace_key *key) diff --git a/transport.c b/transport.c index 662421bb5e..c25ee7c713 100644 --- a/transport.c +++ b/transport.c @@ -284,7 +284,6 @@ static int write_one_ref(const char *name, const unsigned char *sha1, { struct strbuf *buf = data; int len = buf->len; - FILE *f; /* when called via for_each_ref(), flags is non-zero */ if (flags && !starts_with(name, "refs/heads/") && @@ -293,10 +292,9 @@ static int write_one_ref(const char *name, const unsigned char *sha1, strbuf_addstr(buf, name); if (safe_create_leading_directories(buf->buf) || - !(f = fopen(buf->buf, "w")) || - fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 || - fclose(f)) - return error("problems writing temporary file %s", buf->buf); + write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1))) + return error("problems writing temporary file %s: %s", + buf->buf, strerror(errno)); strbuf_setlen(buf, len); return 0; } diff --git a/unix-socket.c b/unix-socket.c index 91bd6b89d4..19ed48be99 100644 --- a/unix-socket.c +++ b/unix-socket.c @@ -18,12 +18,12 @@ static int chdir_len(const char *orig, int len) } struct unix_sockaddr_context { - char orig_dir[PATH_MAX]; + char *orig_dir; }; static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx) { - if (!ctx->orig_dir[0]) + if (!ctx->orig_dir) return; /* * If we fail, we can't just return an error, since we have @@ -32,6 +32,7 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx) */ if (chdir(ctx->orig_dir) < 0) die("unable to restore original working directory"); + free(ctx->orig_dir); } static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path, @@ -39,10 +40,11 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path, { int size = strlen(path) + 1; - ctx->orig_dir[0] = '\0'; + ctx->orig_dir = NULL; if (size > sizeof(sa->sun_path)) { const char *slash = find_last_dir_sep(path); const char *dir; + struct strbuf cwd = STRBUF_INIT; if (!slash) { errno = ENAMETOOLONG; @@ -56,11 +58,9 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path, errno = ENAMETOOLONG; return -1; } - - if (!getcwd(ctx->orig_dir, sizeof(ctx->orig_dir))) { - errno = ENAMETOOLONG; + if (strbuf_getcwd(&cwd)) return -1; - } + ctx->orig_dir = strbuf_detach(&cwd, NULL); if (chdir_len(dir, slash - dir) < 0) return -1; } @@ -493,3 +493,42 @@ struct passwd *xgetpwuid_self(void) errno ? strerror(errno) : _("no such user")); return pw; } + +int write_file(const char *path, int fatal, const char *fmt, ...) +{ + struct strbuf sb = STRBUF_INIT; + va_list params; + int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + if (fatal) + die_errno(_("could not open %s for writing"), path); + return -1; + } + va_start(params, fmt); + strbuf_vaddf(&sb, fmt, params); + va_end(params); + if (write_in_full(fd, sb.buf, sb.len) != sb.len) { + int err = errno; + close(fd); + strbuf_release(&sb); + errno = err; + if (fatal) + die_errno(_("could not write to %s"), path); + return -1; + } + strbuf_release(&sb); + if (close(fd)) { + if (fatal) + die_errno(_("could not close %s"), path); + return -1; + } + return 0; +} + +char *xgetcwd(void) +{ + struct strbuf sb = STRBUF_INIT; + if (strbuf_getcwd(&sb)) + die_errno(_("unable to get current working directory")); + return strbuf_detach(&sb, NULL); +} |