diff options
116 files changed, 2104 insertions, 1075 deletions
diff --git a/Documentation/RelNotes/2.15.0.txt b/Documentation/RelNotes/2.15.0.txt index 31f7b0ed03..9b5c417b0e 100644 --- a/Documentation/RelNotes/2.15.0.txt +++ b/Documentation/RelNotes/2.15.0.txt @@ -136,6 +136,38 @@ Performance, Internal Implementation, Development Support etc. piece of memory while writing each index entry out. This has been optimized. + * Platforms that ship with a separate sha1 with collision detection + library can link to it instead of using the copy we ship as part of + our source tree. + + * Code around "notes" have been cleaned up. + (merge 3964281524 mh/notes-cleanup later to maint). + + * The long-standing rule that an in-core lockfile instance, once it + is used, must not be freed, has been lifted and the lockfile and + tempfile APIs have been updated to reduce the chance of programming + errors. + + * Our hashmap implementation in hashmap.[ch] is not thread-safe when + adding a new item needs to expand the hashtable by rehashing; add + an API to disable the automatic rehashing to work it around. + + * Many of our programs consider that it is OK to release dynamic + storage that is used throughout the life of the program by simply + exiting, but this makes it harder to leak detection tools to avoid + reporting false positives. Plug many existing leaks and introduce + a mechanism for developers to mark that the region of memory + pointed by a pointer is not lost/leaking to help these tools. + + * As "git commit" to conclude a conflicted "git merge" honors the + commit-msg hook, "git merge" that records a merge commit that + cleanly auto-merges should, but it didn't. + + * The codepath for "git merge-recursive" has been cleaned up. + + * Many leaks of strbuf have been fixed. + + Also contains various documentation updates and code clean-ups. @@ -245,9 +277,35 @@ Fixes since v2.14 was in use. This has been fixed. (merge 31824d180d nd/worktree-kill-parse-ref later to maint). + * "git gc" and friends when multiple worktrees are used off of a + single repository did not consider the index and per-worktree refs + of other worktrees as the root for reachability traversal, making + objects that are in use only in other worktrees to be subject to + garbage collection. + + * A regression to "gitk --bisect" by a recent update has been fixed. + (merge 1d0538e486 mh/packed-ref-store-prep later to maint). + + * "git -c submodule.recurse=yes pull" did not work as if the + "--recurse-submodules" option was given from the command line. + This has been corrected. + + * Unlike "git commit-tree < file", "git commit-tree -F file" did not + pass the contents of the file verbatim and instead completed an + incomplete line at the end, if exists. The latter has been updated + to match the behaviour of the former. + (merge c818e74332 rk/commit-tree-make-F-verbatim later to maint). + * Other minor doc, test and build updates and code cleanups. (merge f094b89a4d ma/parse-maybe-bool later to maint). (merge 39b00fa4d4 jk/drop-sha1-entry-pos later to maint). (merge 6cdf8a7929 ma/ts-cleanups later to maint). (merge 7560f547e6 ma/up-to-date later to maint). (merge 0db3dc75f3 rs/apply-epoch later to maint). + (merge 74f1bd912b dw/diff-highlight-makefile-fix later to maint). + (merge f991761eb8 jk/config-lockfile-leak-fix later to maint). + (merge 150efef1e7 ma/pkt-line-leakfix later to maint). + (merge 5554451de6 mg/timestamp-t-fix later to maint). + (merge 276d0e35c0 ma/split-symref-update-fix later to maint). + (merge 3bc4b8f7c7 bb/doc-eol-dirty later to maint). + (merge c1bb33c99c jk/system-path-cleanup later to maint). diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 204541c690..fb09cd69d6 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -192,7 +192,7 @@ newline. The available atoms are: The 40-hex object name of the object. `objecttype`:: - The type of of the object (the same as `cat-file -t` reports). + The type of the object (the same as `cat-file -t` reports). `objectsize`:: The size, in bytes, of the object (the same as `cat-file -s` diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index d6399c0af8..bd268a8fcc 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -38,7 +38,7 @@ $ git checkout -b <branch> --track <remote>/<branch> ------------ + You could omit <branch>, in which case the command degenerates to -"check out the current branch", which is a glorified no-op with a +"check out the current branch", which is a glorified no-op with rather expensive side-effects to show only the tracking information, if exists, for the current branch. diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index bb370c9c7b..66b4e0a405 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -10,8 +10,9 @@ SYNOPSIS [verse] 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl] [(--sort=<key>)...] [--format=<format>] [<pattern>...] - [--points-at <object>] [(--merged | --no-merged) [<object>]] - [--contains [<object>]] [--no-contains [<object>]] + [--points-at=<object>] + (--merged[=<object>] | --no-merged[=<object>]) + [--contains[=<object>]] [--no-contains[=<object>]] DESCRIPTION ----------- @@ -25,19 +26,25 @@ host language allowing their direct evaluation in that language. OPTIONS ------- -<count>:: +<pattern>...:: + If one or more patterns are given, only refs are shown that + match against at least one pattern, either using fnmatch(3) or + literally, in the latter case matching completely or from the + beginning up to a slash. + +--count=<count>:: By default the command shows all refs that match `<pattern>`. This option makes it stop after showing that many refs. -<key>:: +--sort=<key>:: A field name to sort on. Prefix `-` to sort in descending order of the value. When unspecified, `refname` is used. You may use the --sort=<key> option multiple times, in which case the last key becomes the primary key. -<format>:: +--format=<format>:: A string that interpolates `%(fieldname)` from a ref being shown and the object it points at. If `fieldname` is prefixed with an asterisk (`*`) and the ref points @@ -50,12 +57,6 @@ OPTIONS `xx`; for example `%00` interpolates to `\0` (NUL), `%09` to `\t` (TAB) and `%0a` to `\n` (LF). -<pattern>...:: - If one or more patterns are given, only refs are shown that - match against at least one pattern, either using fnmatch(3) or - literally, in the latter case matching completely or from the - beginning up to a slash. - --shell:: --perl:: --python:: @@ -65,24 +66,24 @@ OPTIONS the specified host language. This is meant to produce a scriptlet that can directly be `eval`ed. ---points-at <object>:: +--points-at=<object>:: Only list refs which points at the given object. ---merged [<object>]:: +--merged[=<object>]:: Only list refs whose tips are reachable from the specified commit (HEAD if not specified), incompatible with `--no-merged`. ---no-merged [<object>]:: +--no-merged[=<object>]:: Only list refs whose tips are not reachable from the specified commit (HEAD if not specified), incompatible with `--merged`. ---contains [<object>]:: +--contains[=<object>]:: Only list refs which contain the specified commit (HEAD if not specified). ---no-contains [<object>]:: +--no-contains[=<object>]:: Only list refs which don't contain the specified commit (HEAD if not specified). diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index be7db3048d..43677297f3 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -171,7 +171,7 @@ OPTIONS object that does not have notes attached to it. --stdin:: - Also read the object names to remove notes from from the standard + Also read the object names to remove notes from the standard input (there is no reason you cannot combine this with object names from the command line). diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index e19eba62cd..75c7dd9dea 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -153,7 +153,7 @@ you will need to handle the situation manually. + Version 4 performs a simple pathname compression that reduces index size by 30%-50% on large repositories, which results in faster load -time. Version 4 is relatively young (first released in in 1.8.0 in +time. Version 4 is relatively young (first released in 1.8.0 in October 2012). Other Git implementations such as JGit and libgit2 may not support it yet. diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index c4f2be2542..4c68bc19d5 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -151,7 +151,10 @@ unspecified. This attribute sets a specific line-ending style to be used in the working directory. It enables end-of-line conversion without any -content checks, effectively setting the `text` attribute. +content checks, effectively setting the `text` attribute. Note that +setting this attribute on paths which are in the index with CRLF line +endings may make the paths to be considered dirty. Adding the path to +the index again will normalize the line endings in the index. Set to string value "crlf":: diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a6cf9eb380..7d860bfca1 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -184,6 +184,14 @@ explicitly. Pretend as if all objects mentioned by reflogs are listed on the command line as `<commit>`. +--single-worktree:: + By default, all working trees will be examined by the + following options when there are more than one (see + linkgit:git-worktree[1]): `--all`, `--reflog` and + `--indexed-objects`. + This option forces them to examine the current working tree + only. + --ignore-missing:: Upon seeing an invalid object name in the input, pretend as if the bad input was not given. diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt index 7a83a3a6e2..9a778b0cad 100644 --- a/Documentation/technical/api-config.txt +++ b/Documentation/technical/api-config.txt @@ -186,10 +186,6 @@ parsing is successful, the return value is the result. Same as `git_config_bool`, except that integers are returned as-is, and an `is_bool` flag is unset. -`git_config_maybe_bool`:: -Deprecated. Use `git_parse_maybe_bool` instead. They are exactly the -same, except this function takes an unused argument `name`. - `git_parse_maybe_bool`:: Same as `git_config_bool`, except that it returns -1 on error rather than dying. diff --git a/Documentation/technical/api-ref-iteration.txt b/Documentation/technical/api-ref-iteration.txt index 37379d8337..46c3d5c355 100644 --- a/Documentation/technical/api-ref-iteration.txt +++ b/Documentation/technical/api-ref-iteration.txt @@ -32,11 +32,8 @@ Iteration functions * `for_each_glob_ref_in()` the previous and `for_each_ref_in()` combined. -* `head_ref_submodule()`, `for_each_ref_submodule()`, - `for_each_ref_in_submodule()`, `for_each_tag_ref_submodule()`, - `for_each_branch_ref_submodule()`, `for_each_remote_ref_submodule()` - do the same as the functions described above but for a specified - submodule. +* Use `refs_` API for accessing submodules. The submodule ref store could + be obtained with `get_submodule_ref_store()`. * `for_each_rawref()` can be used to learn about broken ref and symref. @@ -162,6 +162,11 @@ all:: # algorithm. This is slower, but may detect attempted collision attacks. # Takes priority over other *_SHA1 knobs. # +# Define DC_SHA1_EXTERNAL in addition to DC_SHA1 if you want to build / link +# git with the external SHA1 collision-detect library. +# Without this option, i.e. the default behavior is to build git with its +# own built-in code (or submodule). +# # Define DC_SHA1_SUBMODULE in addition to DC_SHA1 to use the # sha1collisiondetection shipped as a submodule instead of the # non-submodule copy in sha1dc/. This is an experimental option used @@ -1036,6 +1041,9 @@ BASIC_CFLAGS += -fno-omit-frame-pointer ifneq ($(filter undefined,$(SANITIZERS)),) BASIC_CFLAGS += -DNO_UNALIGNED_LOADS endif +ifneq ($(filter leak,$(SANITIZERS)),) +BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS +endif endif ifndef sysconfdir @@ -1475,6 +1483,15 @@ ifdef APPLE_COMMON_CRYPTO BASIC_CFLAGS += -DSHA1_APPLE else DC_SHA1 := YesPlease + BASIC_CFLAGS += -DSHA1_DC + LIB_OBJS += sha1dc_git.o +ifdef DC_SHA1_EXTERNAL + ifdef DC_SHA1_SUBMODULE +$(error Only set DC_SHA1_EXTERNAL or DC_SHA1_SUBMODULE, not both) + endif + BASIC_CFLAGS += -DDC_SHA1_EXTERNAL + EXTLIBS += -lsha1detectcoll +else ifdef DC_SHA1_SUBMODULE LIB_OBJS += sha1collisiondetection/lib/sha1.o LIB_OBJS += sha1collisiondetection/lib/ubc_check.o @@ -1484,17 +1501,15 @@ else LIB_OBJS += sha1dc/ubc_check.o endif BASIC_CFLAGS += \ - -DSHA1_DC \ -DSHA1DC_NO_STANDARD_INCLUDES \ -DSHA1DC_INIT_SAFE_HASH_DEFAULT=0 \ -DSHA1DC_CUSTOM_INCLUDE_SHA1_C="\"cache.h\"" \ - -DSHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C="\"sha1dc_git.c\"" \ - -DSHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H="\"sha1dc_git.h\"" \ -DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C="\"git-compat-util.h\"" endif endif endif endif +endif ifdef SHA1_MAX_BLOCK_SIZE LIB_OBJS += compat/sha1-chunked.o @@ -151,10 +151,12 @@ struct all_attrs_item { static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check) { int i; + unsigned int size; hashmap_lock(map); - if (map->map.size < check->all_attrs_nr) + size = hashmap_get_size(&map->map); + if (size < check->all_attrs_nr) die("BUG: interned attributes shouldn't be deleted"); /* @@ -163,13 +165,13 @@ static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check) * field), reallocate the provided attr_check instance's all_attrs * field and fill each entry with its corresponding git_attr. */ - if (map->map.size != check->all_attrs_nr) { + if (size != check->all_attrs_nr) { struct attr_hash_entry *e; struct hashmap_iter iter; hashmap_iter_init(&map->map, &iter); - REALLOC_ARRAY(check->all_attrs, map->map.size); - check->all_attrs_nr = map->map.size; + REALLOC_ARRAY(check->all_attrs, size); + check->all_attrs_nr = size; while ((e = hashmap_iter_next(&iter))) { const struct git_attr *a = e->value; @@ -237,10 +239,11 @@ static const struct git_attr *git_attr_internal(const char *name, int namelen) if (!a) { FLEX_ALLOC_MEM(a, name, name, namelen); - a->attr_nr = g_attr_hashmap.map.size; + a->attr_nr = hashmap_get_size(&g_attr_hashmap.map); attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a); - assert(a->attr_nr == (g_attr_hashmap.map.size - 1)); + assert(a->attr_nr == + (hashmap_get_size(&g_attr_hashmap.map) - 1)); } hashmap_unlock(&g_attr_hashmap); diff --git a/builtin/add.c b/builtin/add.c index c20548e4f5..a648cf4c56 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -119,6 +119,7 @@ int add_files_to_cache(const char *prefix, rev.diffopt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); + clear_pathspec(&rev.prune_data); return !!data.add_errors; } @@ -514,5 +515,7 @@ finish: die(_("Unable to write new index file")); } + UNLEAK(pathspec); + UNLEAK(dir); return exit_status; } diff --git a/builtin/am.c b/builtin/am.c index c369dd1dce..d7513f5375 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -671,9 +671,7 @@ static int detect_patch_format(const char **paths) goto done; } - strbuf_reset(&l2); strbuf_getline(&l2, fp); - strbuf_reset(&l3); strbuf_getline(&l3, fp); /* @@ -696,6 +694,8 @@ static int detect_patch_format(const char **paths) done: fclose(fp); strbuf_release(&l1); + strbuf_release(&l2); + strbuf_release(&l3); return ret; } @@ -881,6 +881,7 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths, static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) { struct strbuf sb = STRBUF_INIT; + int rc = 0; while (!strbuf_getline_lf(&sb, in)) { const char *str; @@ -894,19 +895,27 @@ static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) errno = 0; timestamp = parse_timestamp(str, &end, 10); - if (errno) - return error(_("invalid timestamp")); + if (errno) { + rc = error(_("invalid timestamp")); + goto exit; + } - if (!skip_prefix(end, " ", &str)) - return error(_("invalid Date line")); + if (!skip_prefix(end, " ", &str)) { + rc = error(_("invalid Date line")); + goto exit; + } errno = 0; tz = strtol(str, &end, 10); - if (errno) - return error(_("invalid timezone offset")); + if (errno) { + rc = error(_("invalid timezone offset")); + goto exit; + } - if (*end) - return error(_("invalid Date line")); + if (*end) { + rc = error(_("invalid Date line")); + goto exit; + } /* * mercurial's timezone is in seconds west of UTC, @@ -931,9 +940,9 @@ static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) fwrite(sb.buf, 1, sb.len, out); strbuf_reset(&sb); } - +exit: strbuf_release(&sb); - return 0; + return rc; } /** @@ -2096,6 +2105,7 @@ static int safe_to_abort(const struct am_state *state) die(_("could not parse %s"), am_path(state, "abort-safety")); } else oidclr(&abort_safety); + strbuf_release(&sb); if (get_oid("HEAD", &head)) oidclr(&head); diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index eac499450f..6c40ff110b 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -45,6 +45,7 @@ static int check_ref_format_branch(const char *arg) if (strbuf_check_branch_ref(&sb, arg)) die("'%s' is not a valid branch name", arg); printf("%s\n", sb.buf + 11); + strbuf_release(&sb); return 0; } diff --git a/builtin/clean.c b/builtin/clean.c index 21a7a32994..733b6d3745 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -167,7 +167,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, } *dir_gone = 0; - return 0; + goto out; } dir = opendir(path->buf); @@ -181,7 +181,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; } - return res; + ret = res; + goto out; } strbuf_complete(path, '/'); @@ -249,6 +250,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, for (i = 0; i < dels.nr; i++) printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string); } +out: + strbuf_release("ed); string_list_clear(&dels, 0); return ret; } diff --git a/builtin/clone.c b/builtin/clone.c index 8d11b570a1..dbddd98f80 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -507,8 +507,8 @@ static void remove_junk(void) if (junk_work_tree) { strbuf_addstr(&sb, junk_work_tree); remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); } + strbuf_release(&sb); } static void remove_junk_on_signal(int signo) diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 19e898fa4e..2177251e24 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -102,7 +102,6 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) if (fd && close(fd)) die_errno("git commit-tree: failed to close '%s'", argv[i]); - strbuf_complete_line(&buffer); continue; } diff --git a/builtin/commit.c b/builtin/commit.c index b3b04f5dd3..58f9747c2f 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1818,6 +1818,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!quiet) print_summary(prefix, &oid, !current_head); - strbuf_release(&err); + UNLEAK(err); + UNLEAK(sb); return 0; } diff --git a/builtin/config.c b/builtin/config.c index 70ff231e9c..d13daeeb55 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -518,10 +518,13 @@ int cmd_config(int argc, const char **argv, const char *prefix) die("$HOME not set"); if (access_or_warn(user_config, R_OK, 0) && - xdg_config && !access_or_warn(xdg_config, R_OK, 0)) + xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { given_config_source.file = xdg_config; - else + free(user_config); + } else { given_config_source.file = user_config; + free(xdg_config); + } } else if (use_system_config) given_config_source.file = git_etc_gitconfig(); @@ -628,6 +631,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value); if (ret == CONFIG_NOTHING_SET) error(_("cannot overwrite multiple values with a single value\n" @@ -638,6 +642,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, argv[2], 0); } @@ -645,6 +650,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, CONFIG_REGEX_NONE, 0); @@ -653,6 +659,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, argv[2], 1); } diff --git a/builtin/describe.c b/builtin/describe.c index 9c13c6817b..e77163e909 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -508,7 +508,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) hashmap_init(&names, commit_name_cmp, NULL, 0); for_each_rawref(get_name, NULL); - if (!names.size && !always) + if (!hashmap_get_size(&names) && !always) die(_("No names found, cannot describe anything.")); if (argc == 0) { diff --git a/builtin/gc.c b/builtin/gc.c index 3c78fcb9b1..c22787ac72 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -47,7 +47,7 @@ static struct argv_array prune = ARGV_ARRAY_INIT; static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; -static struct tempfile pidfile; +static struct tempfile *pidfile; static struct lock_file log_lock; static struct string_list pack_garbage = STRING_LIST_INIT_DUP; @@ -78,7 +78,7 @@ static void process_log_file(void) */ int saved_errno = errno; fprintf(stderr, _("Failed to fstat %s: %s"), - get_tempfile_path(&log_lock.tempfile), + get_tempfile_path(log_lock.tempfile), strerror(saved_errno)); fflush(stderr); commit_lock_file(&log_lock); @@ -242,7 +242,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) int fd; char *pidfile_path; - if (is_tempfile_active(&pidfile)) + if (is_tempfile_active(pidfile)) /* already locked */ return NULL; @@ -293,7 +293,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); commit_lock_file(&lock); - register_tempfile(&pidfile, pidfile_path); + pidfile = register_tempfile(pidfile_path); free(pidfile_path); return NULL; } diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c index e21c5416cd..6d9a79f9b3 100644 --- a/builtin/get-tar-commit-id.c +++ b/builtin/get-tar-commit-id.c @@ -33,8 +33,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) if (!skip_prefix(content, "52 comment=", &comment)) return 1; - n = write_in_full(1, comment, 41); - if (n < 41) + if (write_in_full(1, comment, 41) < 0) die_errno("git get-tar-commit-id: write error"); return 0; diff --git a/builtin/help.c b/builtin/help.c index 334a8494ab..b3f60a8f30 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -131,6 +131,7 @@ static void exec_woman_emacs(const char *path, const char *page) strbuf_addf(&man_page, "(woman \"%s\")", page); execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); warning_errno(_("failed to exec '%s'"), path); + strbuf_release(&man_page); } } @@ -152,6 +153,7 @@ static void exec_man_konqueror(const char *path, const char *page) strbuf_addf(&man_page, "man:%s(1)", page); execlp(path, filename, "newTab", man_page.buf, (char *)NULL); warning_errno(_("failed to exec '%s'"), path); + strbuf_release(&man_page); } } @@ -169,6 +171,7 @@ static void exec_man_cmd(const char *cmd, const char *page) strbuf_addf(&shell_cmd, "%s %s", cmd, page); execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); warning(_("failed to exec '%s'"), cmd); + strbuf_release(&shell_cmd); } static void add_man_viewer(const char *name) diff --git a/builtin/init-db.c b/builtin/init-db.c index 47823f9aa4..c9b7946bad 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -579,6 +579,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) set_git_work_tree(work_tree); } + UNLEAK(real_git_dir); + flags |= INIT_DB_EXIST_OK; return init_db(git_dir, real_git_dir, template_dir, flags); } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index e1339e6d17..8c713c47ac 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -673,5 +673,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) return bad ? 1 : 0; } + UNLEAK(dir); return 0; } diff --git a/builtin/merge.c b/builtin/merge.c index 3672e38974..ab5ffe85e8 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -73,6 +73,7 @@ static int show_progress = -1; static int default_to_upstream = 1; static int signoff; static const char *sign_commit; +static int verify_msg = 1; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -236,6 +237,7 @@ static struct option builtin_merge_options[] = { N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "verify", &verify_msg, N_("verify commit-msg hook")), OPT_END() }; @@ -253,6 +255,7 @@ static int save_state(struct object_id *stash) struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buffer = STRBUF_INIT; const char *argv[] = {"stash", "create", NULL}; + int rc = -1; cp.argv = argv; cp.out = -1; @@ -266,11 +269,14 @@ static int save_state(struct object_id *stash) if (finish_command(&cp) || len < 0) die(_("stash failed")); else if (!len) /* no changes */ - return -1; + goto out; strbuf_setlen(&buffer, buffer.len-1); if (get_oid(buffer.buf, stash)) die(_("not a valid object: %s"), buffer.buf); - return 0; + rc = 0; +out: + strbuf_release(&buffer); + return rc; } static void read_empty(unsigned const char *sha1, int verbose) @@ -780,6 +786,12 @@ static void prepare_to_commit(struct commit_list *remoteheads) if (launch_editor(git_path_merge_msg(), NULL, NULL)) abort_commit(remoteheads, NULL); } + + if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(), + "commit-msg", + git_path_merge_msg(), NULL)) + abort_commit(remoteheads, NULL); + read_merge_msg(&msg); strbuf_stripspace(&msg, 0 < option_edit); if (!msg.len) @@ -934,6 +946,7 @@ static void write_merge_heads(struct commit_list *remoteheads) if (fast_forward == FF_NO) strbuf_addstr(&buf, "no-ff"); write_file_buf(git_path_merge_mode(), buf.buf, buf.len); + strbuf_release(&buf); } static void write_merge_state(struct commit_list *remoteheads) diff --git a/builtin/name-rev.c b/builtin/name-rev.c index c41ea7c2a6..598da6c8bc 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -253,7 +253,7 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo struct commit *commit = (struct commit *)o; int from_tag = starts_with(path, "refs/tags/"); - if (taggerdate == ULONG_MAX) + if (taggerdate == TIME_MAX) taggerdate = ((struct commit *)o)->date; path = name_ref_abbrev(path, can_abbreviate_output); name_rev(commit, xstrdup(path), taggerdate, 0, 0, diff --git a/builtin/notes.c b/builtin/notes.c index 4303848e04..8e54f2d146 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -328,6 +328,7 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } else { finish_copy_notes_for_rewrite(c, msg); } + strbuf_release(&buf); return ret; } diff --git a/builtin/pull.c b/builtin/pull.c index 7fe281414e..6f772e8a22 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -325,6 +325,10 @@ static int git_pull_config(const char *var, const char *value, void *cb) if (!strcmp(var, "rebase.autostash")) { config_autostash = git_config_bool(var, value); return 0; + } else if (!strcmp(var, "submodule.recurse")) { + recurse_submodules = git_config_bool(var, value) ? + RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; + return 0; } return git_default_config(var, value, cb); } @@ -815,6 +819,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); + git_config(git_pull_config, NULL); + argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); parse_repo_refspecs(argc, argv, &repo, &refspecs); @@ -825,8 +831,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase < 0) opt_rebase = config_get_rebase(); - git_config(git_pull_config, NULL); - if (read_cache_unmerged()) die_resolve_conflict("pull"); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 52c63ebfdc..dd06b3fb4f 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -743,7 +743,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, size_t n; if (feed(feed_state, &buf, &n)) break; - if (write_in_full(proc.in, buf, n) != n) + if (write_in_full(proc.in, buf, n) < 0) break; } close(proc.in); diff --git a/builtin/remote.c b/builtin/remote.c index a995ea86c1..33ba739332 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -571,6 +571,7 @@ static int read_remote_branches(const char *refname, else item->util = NULL; } + strbuf_release(&buf); return 0; } @@ -595,6 +596,7 @@ static int migrate_file(struct remote *remote) unlink_or_warn(git_path("remotes/%s", remote->name)); else if (remote->origin == REMOTE_BRANCHES) unlink_or_warn(git_path("branches/%s", remote->name)); + strbuf_release(&buf); return 0; } @@ -1563,9 +1565,7 @@ static int set_url(int argc, const char **argv) "^$", 0); else git_config_set(name_buf.buf, newurl); - strbuf_release(&name_buf); - - return 0; + goto out; } /* Old URL specified. Demand that one matches. */ @@ -1588,6 +1588,8 @@ static int set_url(int argc, const char **argv) git_config_set_multivar(name_buf.buf, newurl, oldurl, 0); else git_config_set_multivar(name_buf.buf, NULL, oldurl, 1); +out: + strbuf_release(&name_buf); return 0; } diff --git a/builtin/rerere.c b/builtin/rerere.c index ffb66e2907..0bc40298c2 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -18,7 +18,7 @@ static int outf(void *dummy, mmbuffer_t *ptr, int nbuf) { int i; for (i = 0; i < nbuf; i++) - if (write_in_full(1, ptr[i].ptr, ptr[i].size) != ptr[i].size) + if (write_in_full(1, ptr[i].ptr, ptr[i].size) < 0) return -1; return 0; } diff --git a/builtin/reset.c b/builtin/reset.c index d72c7d1c96..9cd89b2305 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -44,10 +44,11 @@ static inline int is_merge(void) static int reset_index(const struct object_id *oid, int reset_type, int quiet) { - int nr = 1; + int i, nr = 0; struct tree_desc desc[2]; struct tree *tree; struct unpack_trees_options opts; + int ret = -1; memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; @@ -75,23 +76,32 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) struct object_id head_oid; if (get_oid("HEAD", &head_oid)) return error(_("You do not have a valid HEAD.")); - if (!fill_tree_descriptor(desc, &head_oid)) + if (!fill_tree_descriptor(desc + nr, &head_oid)) return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } - if (!fill_tree_descriptor(desc + nr - 1, oid)) - return error(_("Failed to find tree of %s."), oid_to_hex(oid)); + if (!fill_tree_descriptor(desc + nr, oid)) { + error(_("Failed to find tree of %s."), oid_to_hex(oid)); + goto out; + } + nr++; + if (unpack_trees(nr, desc, &opts)) - return -1; + goto out; if (reset_type == MIXED || reset_type == HARD) { tree = parse_tree_indirect(oid); prime_cache_tree(&the_index, tree); } - return 0; + ret = 0; + +out: + for (i = 0; i < nr; i++) + free((void *)desc[i].buffer); + return ret; } static void print_new_head_line(struct commit *commit) @@ -367,8 +377,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die_if_unmerged_cache(reset_type); if (reset_type != SOFT) { - struct lock_file *lock = xcalloc(1, sizeof(*lock)); - hold_locked_index(lock, LOCK_DIE_ON_ERROR); + struct lock_file lock = LOCK_INIT; + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) @@ -384,7 +394,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die(_("Could not reset index file to revision '%s'."), rev); } - if (write_locked_index(&the_index, lock, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock, COMMIT_LOCK)) die(_("Could not write new index file.")); } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 2bd28d3c08..9f24004c0a 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -757,8 +757,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--bisect")) { - for_each_ref_in("refs/bisect/bad", show_reference, NULL); - for_each_ref_in("refs/bisect/good", anti_reference, NULL); + for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0); + for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0); continue; } if (opt_with_value(arg, "--branches", &arg)) { diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 43c4799ea9..e29875b843 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -52,26 +52,8 @@ static void insert_one_record(struct shortlog *log, const char *oneline) { struct string_list_item *item; - const char *mailbuf, *namebuf; - size_t namelen, maillen; - struct strbuf namemailbuf = STRBUF_INIT; - struct ident_split ident; - if (split_ident_line(&ident, author, strlen(author))) - return; - - namebuf = ident.name_begin; - mailbuf = ident.mail_begin; - namelen = ident.name_end - ident.name_begin; - maillen = ident.mail_end - ident.mail_begin; - - map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); - strbuf_add(&namemailbuf, namebuf, namelen); - - if (log->email) - strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf); - - item = string_list_insert(&log->list, namemailbuf.buf); + item = string_list_insert(&log->list, author); if (log->summary) item->util = (void *)(UTIL_TO_INT(item) + 1); @@ -114,9 +96,33 @@ static void insert_one_record(struct shortlog *log, } } +static int parse_stdin_author(struct shortlog *log, + struct strbuf *out, const char *in) +{ + const char *mailbuf, *namebuf; + size_t namelen, maillen; + struct ident_split ident; + + if (split_ident_line(&ident, in, strlen(in))) + return -1; + + namebuf = ident.name_begin; + mailbuf = ident.mail_begin; + namelen = ident.name_end - ident.name_begin; + maillen = ident.mail_end - ident.mail_begin; + + map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); + strbuf_add(out, namebuf, namelen); + if (log->email) + strbuf_addf(out, " <%.*s>", (int)maillen, mailbuf); + + return 0; +} + static void read_from_stdin(struct shortlog *log) { struct strbuf author = STRBUF_INIT; + struct strbuf mapped_author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; static const char *author_match[2] = { "Author: ", "author " }; static const char *committer_match[2] = { "Commit: ", "committer " }; @@ -134,9 +140,15 @@ static void read_from_stdin(struct shortlog *log) while (strbuf_getline_lf(&oneline, stdin) != EOF && !oneline.len) ; /* discard blanks */ - insert_one_record(log, v, oneline.buf); + + strbuf_reset(&mapped_author); + if (parse_stdin_author(log, &mapped_author, v) < 0) + continue; + + insert_one_record(log, mapped_author.buf, oneline.buf); } strbuf_release(&author); + strbuf_release(&mapped_author); strbuf_release(&oneline); } @@ -153,7 +165,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - fmt = log->committer ? "%cn <%ce>" : "%an <%ae>"; + fmt = log->committer ? + (log->email ? "%cN <%cE>" : "%cN") : + (log->email ? "%aN <%aE>" : "%aN"); format_commit_message(commit, fmt, &author, &ctx); if (!log->summary) { diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 281ca1db6c..32e0155577 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -15,7 +15,7 @@ static char *create_temp_file(struct object_id *oid) xsnprintf(path, sizeof(path), ".merge_file_XXXXXX"); fd = xmkstemp(path); - if (write_in_full(fd, buf, size) != size) + if (write_in_full(fd, buf, size) < 0) die_errno("unable to write temp-file"); close(fd); return path; diff --git a/builtin/update-index.c b/builtin/update-index.c index d562f2ec69..bf7420b808 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -287,8 +287,10 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len } option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; - if (add_cache_entry(ce, option)) + if (add_cache_entry(ce, option)) { + free(ce); return error("%s: cannot add to the index - missing --add option?", path); + } return 0; } @@ -915,7 +917,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; int split_index = -1; - struct lock_file *lock_file; + struct lock_file lock_file = LOCK_INIT; struct parse_opt_ctx_t ctx; strbuf_getline_fn getline_fn; int parseopt_state = PARSE_OPT_UNKNOWN; @@ -1014,11 +1016,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - /* We can't free this memory, it becomes part of a linked list parsed atexit() */ - lock_file = xcalloc(1, sizeof(struct lock_file)); - /* we will diagnose later if it turns out that we need to update it */ - newfd = hold_locked_index(lock_file, 0); + newfd = hold_locked_index(&lock_file, 0); if (newfd < 0) lock_error = errno; @@ -1153,11 +1152,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) exit(128); unable_to_lock_die(get_index_file(), lock_error); } - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); } - rollback_lock_file(lock_file); + rollback_lock_file(&lock_file); return has_errors ? 1 : 0; } diff --git a/builtin/worktree.c b/builtin/worktree.c index c98e2ce5f5..de26849f55 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -381,6 +381,8 @@ static int add(int ac, const char **av, const char *prefix) branch = opts.new_branch; } + UNLEAK(path); + UNLEAK(opts); return add_worktree(path, branch, &opts); } diff --git a/cache-tree.c b/cache-tree.c index 2440d1dc89..71d092ed51 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -603,19 +603,16 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix) { int entries, was_valid, newfd; - struct lock_file *lock_file; + struct lock_file lock_file = LOCK_INIT; + int ret = 0; - /* - * We can't free this memory, it becomes part of a linked list - * parsed atexit() - */ - lock_file = xcalloc(1, sizeof(struct lock_file)); - - newfd = hold_lock_file_for_update(lock_file, index_path, LOCK_DIE_ON_ERROR); + newfd = hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR); entries = read_index_from(index_state, index_path); - if (entries < 0) - return WRITE_TREE_UNREADABLE_INDEX; + if (entries < 0) { + ret = WRITE_TREE_UNREADABLE_INDEX; + goto out; + } if (flags & WRITE_TREE_IGNORE_CACHE_TREE) cache_tree_free(&index_state->cache_tree); @@ -624,10 +621,12 @@ int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, co was_valid = cache_tree_fully_valid(index_state->cache_tree); if (!was_valid) { - if (cache_tree_update(index_state, flags) < 0) - return WRITE_TREE_UNMERGED_INDEX; + if (cache_tree_update(index_state, flags) < 0) { + ret = WRITE_TREE_UNMERGED_INDEX; + goto out; + } if (0 <= newfd) { - if (!write_locked_index(index_state, lock_file, COMMIT_LOCK)) + if (!write_locked_index(index_state, &lock_file, COMMIT_LOCK)) newfd = -1; } /* Not being able to write is fine -- we are only interested @@ -641,17 +640,19 @@ int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, co if (prefix) { struct cache_tree *subtree; subtree = cache_tree_find(index_state->cache_tree, prefix); - if (!subtree) - return WRITE_TREE_PREFIX_ERROR; + if (!subtree) { + ret = WRITE_TREE_PREFIX_ERROR; + goto out; + } hashcpy(sha1, subtree->oid.hash); } else hashcpy(sha1, index_state->cache_tree->oid.hash); +out: if (0 <= newfd) - rollback_lock_file(lock_file); - - return 0; + rollback_lock_file(&lock_file); + return ret; } int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) @@ -1570,10 +1570,13 @@ int commit_tree_extended(const char *msg, size_t msg_len, if (encoding_is_utf8 && !verify_utf8(&buffer)) fprintf(stderr, _(commit_utf8_warn)); - if (sign_commit && do_sign_commit(&buffer, sign_commit)) - return -1; + if (sign_commit && do_sign_commit(&buffer, sign_commit)) { + result = -1; + goto out; + } result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); +out: strbuf_release(&buffer); return result; } @@ -956,11 +956,6 @@ int git_parse_maybe_bool(const char *value) return -1; } -int git_config_maybe_bool(const char *name, const char *value) -{ - return git_parse_maybe_bool(value); -} - int git_config_bool_or_int(const char *name, const char *value, int *is_bool) { int v = git_parse_maybe_bool_text(value); @@ -2297,10 +2292,11 @@ static int write_error(const char *filename) return 4; } -static int store_write_section(int fd, const char *key) +static ssize_t write_section(int fd, const char *key) { const char *dot; - int i, success; + int i; + ssize_t ret; struct strbuf sb = STRBUF_INIT; dot = memchr(key, '.', store.baselen); @@ -2316,15 +2312,16 @@ static int store_write_section(int fd, const char *key) strbuf_addf(&sb, "[%.*s]\n", store.baselen, key); } - success = write_in_full(fd, sb.buf, sb.len) == sb.len; + ret = write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); - return success; + return ret; } -static int store_write_pair(int fd, const char *key, const char *value) +static ssize_t write_pair(int fd, const char *key, const char *value) { - int i, success; + int i; + ssize_t ret; int length = strlen(key + store.baselen + 1); const char *quote = ""; struct strbuf sb = STRBUF_INIT; @@ -2364,10 +2361,10 @@ static int store_write_pair(int fd, const char *key, const char *value) } strbuf_addf(&sb, "%s\n", quote); - success = write_in_full(fd, sb.buf, sb.len) == sb.len; + ret = write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); - return success; + return ret; } static ssize_t find_beginning_of_line(const char *contents, size_t size, @@ -2450,7 +2447,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, { int fd = -1, in_fd = -1; int ret; - struct lock_file *lock = NULL; + struct lock_file lock = LOCK_INIT; char *filename_buf = NULL; char *contents = NULL; size_t contents_sz; @@ -2469,8 +2466,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, * The lock serves a purpose in addition to locking: the new * contents of .git/config will be written into it. */ - lock = xcalloc(1, sizeof(struct lock_file)); - fd = hold_lock_file_for_update(lock, config_filename, 0); + fd = hold_lock_file_for_update(&lock, config_filename, 0); if (fd < 0) { error_errno("could not lock config file %s", config_filename); free(store.key); @@ -2497,8 +2493,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, } store.key = (char *)key; - if (!store_write_section(fd, key) || - !store_write_pair(fd, key, value)) + if (write_section(fd, key) < 0 || + write_pair(fd, key, value) < 0) goto write_err_out; } else { struct stat st; @@ -2583,8 +2579,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, close(in_fd); in_fd = -1; - if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { - error_errno("chmod on %s failed", get_lock_file_path(lock)); + if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) { + error_errno("chmod on %s failed", get_lock_file_path(&lock)); ret = CONFIG_NO_WRITE; goto out_free; } @@ -2608,11 +2604,10 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, /* write the first part of the config */ if (copy_end > copy_begin) { if (write_in_full(fd, contents + copy_begin, - copy_end - copy_begin) < - copy_end - copy_begin) + copy_end - copy_begin) < 0) goto write_err_out; if (new_line && - write_str_in_full(fd, "\n") != 1) + write_str_in_full(fd, "\n") < 0) goto write_err_out; } copy_begin = store.offset[i]; @@ -2621,46 +2616,36 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, /* write the pair (value == NULL means unset) */ if (value != NULL) { if (store.state == START) { - if (!store_write_section(fd, key)) + if (write_section(fd, key) < 0) goto write_err_out; } - if (!store_write_pair(fd, key, value)) + if (write_pair(fd, key, value) < 0) goto write_err_out; } /* write the rest of the config */ if (copy_begin < contents_sz) if (write_in_full(fd, contents + copy_begin, - contents_sz - copy_begin) < - contents_sz - copy_begin) + contents_sz - copy_begin) < 0) goto write_err_out; munmap(contents, contents_sz); contents = NULL; } - if (commit_lock_file(lock) < 0) { + if (commit_lock_file(&lock) < 0) { error_errno("could not write config file %s", config_filename); ret = CONFIG_NO_WRITE; - lock = NULL; goto out_free; } - /* - * lock is committed, so don't try to roll it back below. - * NOTE: Since lockfile.c keeps a linked list of all created - * lock_file structures, it isn't safe to free(lock). It's - * better to just leave it hanging around. - */ - lock = NULL; ret = 0; /* Invalidate the config cache */ git_config_clear(); out_free: - if (lock) - rollback_lock_file(lock); + rollback_lock_file(&lock); free(filename_buf); if (contents) munmap(contents, contents_sz); @@ -2669,7 +2654,7 @@ out_free: return ret; write_err_out: - ret = write_error(get_lock_file_path(lock)); + ret = write_error(get_lock_file_path(&lock)); goto out_free; } @@ -2818,7 +2803,7 @@ int git_config_rename_section_in_file(const char *config_filename, continue; } store.baselen = strlen(new_name); - if (!store_write_section(out_fd, new_name)) { + if (write_section(out_fd, new_name) < 0) { ret = write_error(get_lock_file_path(lock)); goto out; } @@ -2844,7 +2829,7 @@ int git_config_rename_section_in_file(const char *config_filename, if (remove) continue; length = strlen(output); - if (write_in_full(out_fd, output, length) != length) { + if (write_in_full(out_fd, output, length) < 0) { ret = write_error(get_lock_file_path(lock)); goto out; } @@ -56,7 +56,6 @@ extern unsigned long git_config_ulong(const char *, const char *); extern ssize_t git_config_ssize_t(const char *, const char *); extern int git_config_bool_or_int(const char *, const char *, int *); extern int git_config_bool(const char *, const char *); -extern int git_config_maybe_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); extern int git_config_pathname(const char **, const char *, const char *); extern int git_config_set_in_file_gently(const char *, const char *, const char *); @@ -778,7 +778,6 @@ struct child_process *git_connect(int fd[2], const char *url, char *hostandport, *path; struct child_process *conn = &no_fork; enum protocol protocol; - struct strbuf cmd = STRBUF_INIT; /* Without this we cannot rely on waitpid() to tell * what happened to our children. @@ -826,6 +825,8 @@ struct child_process *git_connect(int fd[2], const char *url, target_host, 0); free(target_host); } else { + struct strbuf cmd = STRBUF_INIT; + conn = xmalloc(sizeof(*conn)); child_process_init(conn); @@ -862,6 +863,7 @@ struct child_process *git_connect(int fd[2], const char *url, free(hostandport); free(path); free(conn); + strbuf_release(&cmd); return NULL; } diff --git a/contrib/diff-highlight/Makefile b/contrib/diff-highlight/Makefile index fbf5c58249..f2be7cc924 100644 --- a/contrib/diff-highlight/Makefile +++ b/contrib/diff-highlight/Makefile @@ -17,4 +17,7 @@ shebang.perl: FORCE test: all $(MAKE) -C t +clean: + $(RM) diff-highlight + .PHONY: FORCE @@ -423,8 +423,10 @@ static int filter_buffer_or_fd(int in, int out, void *data) child_process.in = -1; child_process.out = out; - if (start_command(&child_process)) + if (start_command(&child_process)) { + strbuf_release(&cmd); return error("cannot fork to run external filter '%s'", params->cmd); + } sigchain_push(SIGPIPE, SIG_IGN); diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index 0d5c625094..4dfbc8c9f9 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -5,8 +5,6 @@ #include "unix-socket.h" #include "parse-options.h" -static struct tempfile socket_file; - struct credential_cache_entry { struct credential item; timestamp_t expiration; @@ -260,6 +258,7 @@ static void init_socket_directory(const char *path) int cmd_main(int argc, const char **argv) { + struct tempfile *socket_file; const char *socket_path; int ignore_sighup = 0; static const char *usage[] = { @@ -285,7 +284,7 @@ int cmd_main(int argc, const char **argv) die("socket directory must be an absolute path"); init_socket_directory(socket_path); - register_tempfile(&socket_file, socket_path); + socket_file = register_tempfile(socket_path); if (ignore_sighup) signal(SIGHUP, SIG_IGN); @@ -459,7 +459,7 @@ static struct diff_tempfile { * If this diff_tempfile instance refers to a temporary file, * this tempfile object is used to manage its lifetime. */ - struct tempfile tempfile; + struct tempfile *tempfile; } diff_temp[2]; struct emit_callback { @@ -1414,7 +1414,7 @@ static void remove_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) { - if (is_tempfile_active(&diff_temp[i].tempfile)) + if (is_tempfile_active(diff_temp[i].tempfile)) delete_tempfile(&diff_temp[i].tempfile); diff_temp[i].name = NULL; } @@ -2583,6 +2583,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) } print_stat_summary_inserts_deletes(options, total_files, adds, dels); + strbuf_release(&out); } static void show_shortstats(struct diffstat_t *data, struct diff_options *options) @@ -3720,7 +3721,6 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, const struct object_id *oid, int mode) { - int fd; struct strbuf buf = STRBUF_INIT; struct strbuf template = STRBUF_INIT; char *path_dup = xstrdup(path); @@ -3730,18 +3730,18 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, strbuf_addstr(&template, "XXXXXX_"); strbuf_addstr(&template, base); - fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1); - if (fd < 0) + temp->tempfile = mks_tempfile_ts(template.buf, strlen(base) + 1); + if (!temp->tempfile) die_errno("unable to create temp-file"); if (convert_to_working_tree(path, (const char *)blob, (size_t)size, &buf)) { blob = buf.buf; size = buf.len; } - if (write_in_full(fd, blob, size) != size) + if (write_in_full(temp->tempfile->fd, blob, size) < 0 || + close_tempfile_gently(temp->tempfile)) die_errno("unable to write temp-file"); - close_tempfile(&temp->tempfile); - temp->name = get_tempfile_path(&temp->tempfile); + temp->name = get_tempfile_path(temp->tempfile); oid_to_hex_r(temp->hex, oid); xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode); strbuf_release(&buf); @@ -5289,6 +5289,7 @@ static void show_rename_copy(struct diff_options *opt, const char *renamecopy, emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, sb.buf, sb.len, 0); show_mode_change(opt, p, 0); + strbuf_release(&sb); } static void diff_summary(struct diff_options *opt, struct diff_filepair *p) @@ -5314,6 +5315,7 @@ static void diff_summary(struct diff_options *opt, struct diff_filepair *p) strbuf_addf(&sb, " (%d%%)\n", similarity_index(p)); emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, sb.buf, sb.len, 0); + strbuf_release(&sb); } show_mode_change(opt, p, !p->score); break; @@ -257,7 +257,8 @@ static int write_entry(struct cache_entry *ce, char *new; struct strbuf buf = STRBUF_INIT; unsigned long size; - size_t wrote, newsize = 0; + ssize_t wrote; + size_t newsize = 0; struct stat st; const struct submodule *sub; @@ -332,7 +333,7 @@ static int write_entry(struct cache_entry *ce, fstat_done = fstat_output(fd, state, &st); close(fd); free(new); - if (wrote != size) + if (wrote < 0) return error("unable to write file %s", path); break; case S_IFGITLINK: diff --git a/environment.c b/environment.c index 3fd4b10845..f1f934b6fd 100644 --- a/environment.c +++ b/environment.c @@ -97,7 +97,7 @@ int ignore_untracked_cache_config; /* This is set by setup_git_dir_gently() and/or git_default_config() */ char *git_work_tree_cfg; -static const char *namespace; +static char *namespace; static const char *super_prefix; @@ -152,8 +152,10 @@ void setup_git_env(void) if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT)) check_replace_refs = 0; replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT); + free(git_replace_ref_base); git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base : "refs/replace/"); + free(namespace); namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT)); shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT); if (shallow_file) diff --git a/exec_cmd.c b/exec_cmd.c index fb94aeba9c..ce192a2d64 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,21 +5,14 @@ #define MAX_ARGS 32 static const char *argv_exec_path; + +#ifdef RUNTIME_PREFIX static const char *argv0_path; -char *system_path(const char *path) +static const char *system_prefix(void) { -#ifdef RUNTIME_PREFIX static const char *prefix; -#else - static const char *prefix = PREFIX; -#endif - struct strbuf d = STRBUF_INIT; - - if (is_absolute_path(path)) - return xstrdup(path); -#ifdef RUNTIME_PREFIX assert(argv0_path); assert(is_absolute_path(argv0_path)); @@ -32,10 +25,7 @@ char *system_path(const char *path) "but prefix computation failed. " "Using static fallback '%s'.\n", prefix); } -#endif - - strbuf_addf(&d, "%s/%s", prefix, path); - return strbuf_detach(&d, NULL); + return prefix; } void git_extract_argv0_path(const char *argv0) @@ -51,6 +41,30 @@ void git_extract_argv0_path(const char *argv0) argv0_path = xstrndup(argv0, slash - argv0); } +#else + +static const char *system_prefix(void) +{ + return PREFIX; +} + +void git_extract_argv0_path(const char *argv0) +{ +} + +#endif /* RUNTIME_PREFIX */ + +char *system_path(const char *path) +{ + struct strbuf d = STRBUF_INIT; + + if (is_absolute_path(path)) + return xstrdup(path); + + strbuf_addf(&d, "%s/%s", system_prefix(), path); + return strbuf_detach(&d, NULL); +} + void git_set_argv_exec_path(const char *exec_path) { argv_exec_path = exec_path; diff --git a/fast-import.c b/fast-import.c index 49516d60e6..35bf671f12 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2952,7 +2952,7 @@ static void parse_reset_branch(const char *arg) static void cat_blob_write(const char *buf, unsigned long size) { - if (write_in_full(cat_blob_fd, buf, size) != size) + if (write_in_full(cat_blob_fd, buf, size) < 0) die_errno("Write to frontend failed"); } diff --git a/git-compat-util.h b/git-compat-util.h index 6678b488cc..9bc15b0363 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -1169,4 +1169,24 @@ static inline int is_missing_file_error(int errno_) extern int cmd_main(int, const char **); +/* + * You can mark a stack variable with UNLEAK(var) to avoid it being + * reported as a leak by tools like LSAN or valgrind. The argument + * should generally be the variable itself (not its address and not what + * it points to). It's safe to use this on pointers which may already + * have been freed, or on pointers which may still be in use. + * + * Use this _only_ for a variable that leaks by going out of scope at + * program exit (so only from cmd_* functions or their direct helpers). + * Normal functions, especially those which may be called multiple + * times, should actually free their memory. This is only meant as + * an annotation, and does nothing in non-leak-checking builds. + */ +#ifdef SUPPRESS_ANNOTATED_LEAKS +extern void unleak_memory(const void *ptr, size_t len); +#define UNLEAK(var) unleak_memory(&(var), sizeof(var)) +#else +#define UNLEAK(var) do {} while (0) +#endif + #endif diff --git a/gpg-interface.c b/gpg-interface.c index d936f3a32f..4feacf16e5 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -202,26 +202,26 @@ int verify_signed_buffer(const char *payload, size_t payload_size, struct strbuf *gpg_output, struct strbuf *gpg_status) { struct child_process gpg = CHILD_PROCESS_INIT; - static struct tempfile temp; - int fd, ret; + struct tempfile *temp; + int ret; struct strbuf buf = STRBUF_INIT; - fd = mks_tempfile_t(&temp, ".git_vtag_tmpXXXXXX"); - if (fd < 0) + temp = mks_tempfile_t(".git_vtag_tmpXXXXXX"); + if (!temp) return error_errno(_("could not create temporary file")); - if (write_in_full(fd, signature, signature_size) < 0) { + if (write_in_full(temp->fd, signature, signature_size) < 0 || + close_tempfile_gently(temp) < 0) { error_errno(_("failed writing detached signature to '%s'"), - temp.filename.buf); + temp->filename.buf); delete_tempfile(&temp); return -1; } - close(fd); argv_array_pushl(&gpg.args, gpg_program, "--status-fd=1", "--keyid-format=long", - "--verify", temp.filename.buf, "-", + "--verify", temp->filename.buf, "-", NULL); if (!gpg_status) @@ -8,11 +8,7 @@ #elif defined(SHA1_OPENSSL) #include <openssl/sha.h> #elif defined(SHA1_DC) -#ifdef DC_SHA1_SUBMODULE -#include "sha1collisiondetection/lib/sha1.h" -#else -#include "sha1dc/sha1.h" -#endif +#include "sha1dc_git.h" #else /* SHA1_BLK */ #include "block-sha1/sha1.h" #endif @@ -116,9 +116,6 @@ static void rehash(struct hashmap *map, unsigned int newsize) unsigned int i, oldsize = map->tablesize; struct hashmap_entry **oldtable = map->table; - if (map->disallow_rehash) - return; - alloc_table(map, newsize); for (i = 0; i < oldsize; i++) { struct hashmap_entry *e = oldtable[i]; @@ -166,6 +163,12 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, while (initial_size > size) size <<= HASHMAP_RESIZE_BITS; alloc_table(map, size); + + /* + * Keep track of the number of items in the map and + * allow the map to automatically grow as necessary. + */ + map->do_count_items = 1; } void hashmap_free(struct hashmap *map, int free_entries) @@ -206,9 +209,11 @@ void hashmap_add(struct hashmap *map, void *entry) map->table[b] = entry; /* fix size and rehash if appropriate */ - map->size++; - if (map->size > map->grow_at) - rehash(map, map->tablesize << HASHMAP_RESIZE_BITS); + if (map->do_count_items) { + map->private_size++; + if (map->private_size > map->grow_at) + rehash(map, map->tablesize << HASHMAP_RESIZE_BITS); + } } void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata) @@ -224,9 +229,12 @@ void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata) old->next = NULL; /* fix size and rehash if appropriate */ - map->size--; - if (map->size < map->shrink_at) - rehash(map, map->tablesize >> HASHMAP_RESIZE_BITS); + if (map->do_count_items) { + map->private_size--; + if (map->private_size < map->shrink_at) + rehash(map, map->tablesize >> HASHMAP_RESIZE_BITS); + } + return old; } @@ -183,7 +183,7 @@ struct hashmap { const void *cmpfn_data; /* total number of entries (0 means the hashmap is empty) */ - unsigned int size; + unsigned int private_size; /* use hashmap_get_size() */ /* * tablesize is the allocated size of the hash table. A non-0 value @@ -196,8 +196,7 @@ struct hashmap { unsigned int grow_at; unsigned int shrink_at; - /* See `hashmap_disallow_rehash`. */ - unsigned disallow_rehash : 1; + unsigned int do_count_items : 1; }; /* hashmap functions */ @@ -253,6 +252,18 @@ static inline void hashmap_entry_init(void *entry, unsigned int hash) } /* + * Return the number of items in the map. + */ +static inline unsigned int hashmap_get_size(struct hashmap *map) +{ + if (map->do_count_items) + return map->private_size; + + BUG("hashmap_get_size: size not set"); + return 0; +} + +/* * Returns the hashmap entry for the specified key, or NULL if not found. * * `map` is the hashmap structure. @@ -345,24 +356,6 @@ extern void *hashmap_remove(struct hashmap *map, const void *key, int hashmap_bucket(const struct hashmap *map, unsigned int hash); /* - * Disallow/allow rehashing of the hashmap. - * This is useful if the caller knows that the hashmap needs multi-threaded - * access. The caller is still required to guard/lock searches and inserts - * in a manner appropriate to their usage. This simply prevents the table - * from being unexpectedly re-mapped. - * - * It is up to the caller to ensure that the hashmap is initialized to a - * reasonable size to prevent poor performance. - * - * A call to allow rehashing does not force a rehash; that might happen - * with the next insert or delete. - */ -static inline void hashmap_disallow_rehash(struct hashmap *map, unsigned value) -{ - map->disallow_rehash = value; -} - -/* * Used to iterate over all entries of a hashmap. Note that it is * not safe to add or remove entries to the hashmap while * iterating. @@ -387,6 +380,43 @@ static inline void *hashmap_iter_first(struct hashmap *map, return hashmap_iter_next(iter); } +/* + * Disable item counting and automatic rehashing when adding/removing items. + * + * Normally, the hashmap keeps track of the number of items in the map + * and uses it to dynamically resize it. This (both the counting and + * the resizing) can cause problems when the map is being used by + * threaded callers (because the hashmap code does not know about the + * locking strategy used by the threaded callers and therefore, does + * not know how to protect the "private_size" counter). + */ +static inline void hashmap_disable_item_counting(struct hashmap *map) +{ + map->do_count_items = 0; +} + +/* + * Re-enable item couting when adding/removing items. + * If counting is currently disabled, it will force count them. + * It WILL NOT automatically rehash them. + */ +static inline void hashmap_enable_item_counting(struct hashmap *map) +{ + void *item; + unsigned int n = 0; + struct hashmap_iter iter; + + if (map->do_count_items) + return; + + hashmap_iter_init(map, &iter); + while ((item = hashmap_iter_next(&iter))) + n++; + + map->do_count_items = 1; + map->private_size = n; +} + /* String interning */ /* diff --git a/http-backend.c b/http-backend.c index 8076b1d5e5..e51c7805c8 100644 --- a/http-backend.c +++ b/http-backend.c @@ -358,7 +358,7 @@ static void inflate_request(const char *prog_name, int out, int buffer_input) die("zlib error inflating request, result %d", ret); n = stream.total_out - cnt; - if (write_in_full(out, out_buf, n) != n) + if (write_in_full(out, out_buf, n) < 0) die("%s aborted reading request", prog_name); cnt += n; @@ -379,7 +379,7 @@ static void copy_request(const char *prog_name, int out) ssize_t n = read_request(0, &buf); if (n < 0) die_errno("error reading request body"); - if (write_in_full(out, buf, n) != n) + if (write_in_full(out, buf, n) < 0) die("%s aborted reading request", prog_name); close(out); free(buf); @@ -163,4 +163,42 @@ static inline void list_replace_init(struct list_head *old, INIT_LIST_HEAD(old); } +/* + * This is exactly the same as a normal list_head, except that it can be + * declared volatile (e.g., if you have a list that may be accessed from signal + * handlers). + */ +struct volatile_list_head { + volatile struct volatile_list_head *next, *prev; +}; + +#define VOLATILE_LIST_HEAD(name) \ + volatile struct volatile_list_head name = { &(name), &(name) } + +static inline void __volatile_list_del(volatile struct volatile_list_head *prev, + volatile struct volatile_list_head *next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void volatile_list_del(volatile struct volatile_list_head *elem) +{ + __volatile_list_del(elem->prev, elem->next); +} + +static inline int volatile_list_empty(volatile struct volatile_list_head *head) +{ + return head == head->next; +} + +static inline void volatile_list_add(volatile struct volatile_list_head *newp, + volatile struct volatile_list_head *head) +{ + head->next->prev = newp; + newp->next = head->next; + newp->prev = head; + head->next = newp; +} + #endif /* LIST_H */ diff --git a/ll-merge.c b/ll-merge.c index 9fb855a900..a6ad2ec12d 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -154,7 +154,7 @@ static void create_temp(mmfile_t *src, char *path, size_t len) xsnprintf(path, len, ".merge_file_XXXXXX"); fd = xmkstemp(path); - if (write_in_full(fd, src->ptr, src->size) != src->size) + if (write_in_full(fd, src->ptr, src->size) < 0) die_errno("unable to write temp-file"); close(fd); } diff --git a/lockfile.c b/lockfile.c index aa69210d8b..efcb7d7dfe 100644 --- a/lockfile.c +++ b/lockfile.c @@ -72,7 +72,6 @@ static void resolve_symlink(struct strbuf *path) /* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { - int fd; struct strbuf filename = STRBUF_INIT; strbuf_addstr(&filename, path); @@ -80,9 +79,9 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) resolve_symlink(&filename); strbuf_addstr(&filename, LOCK_SUFFIX); - fd = create_tempfile(&lk->tempfile, filename.buf); + lk->tempfile = create_tempfile(filename.buf); strbuf_release(&filename); - return fd; + return lk->tempfile ? lk->tempfile->fd : -1; } /* @@ -191,7 +190,7 @@ char *get_locked_file_path(struct lock_file *lk) { struct strbuf ret = STRBUF_INIT; - strbuf_addstr(&ret, get_tempfile_path(&lk->tempfile)); + strbuf_addstr(&ret, get_tempfile_path(lk->tempfile)); if (ret.len <= LOCK_SUFFIX_LEN || strcmp(ret.buf + ret.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) die("BUG: get_locked_file_path() called for malformed lock object"); diff --git a/lockfile.h b/lockfile.h index 572064939c..7c1c484d7c 100644 --- a/lockfile.h +++ b/lockfile.h @@ -37,12 +37,12 @@ * * The caller: * - * * Allocates a `struct lock_file` either as a static variable or on - * the heap, initialized to zeros. Once you use the structure to - * call the `hold_lock_file_for_*()` family of functions, it belongs - * to the lockfile subsystem and its storage must remain valid - * throughout the life of the program (i.e. you cannot use an - * on-stack variable to hold this structure). + * * Allocates a `struct lock_file` with whatever storage duration you + * desire. The struct does not have to be initialized before being + * used, but it is good practice to do so using by setting it to + * all-zeros (or using the LOCK_INIT macro). This puts the object in a + * consistent state that allows you to call rollback_lock_file() even + * if the lock was never taken (in which case it is a noop). * * * Attempts to create a lockfile by calling `hold_lock_file_for_update()`. * @@ -69,14 +69,12 @@ * `rollback_lock_file()`. * * * Close the file descriptor without removing or renaming the - * lockfile by calling `close_lock_file()`, and later call + * lockfile by calling `close_lock_file_gently()`, and later call * `commit_lock_file()`, `commit_lock_file_to()`, * `rollback_lock_file()`, or `reopen_lock_file()`. * - * Even after the lockfile is committed or rolled back, the - * `lock_file` object must not be freed or altered by the caller. - * However, it may be reused; just pass it to another call of - * `hold_lock_file_for_update()`. + * After the lockfile is committed or rolled back, the `lock_file` + * object can be discarded or reused. * * If the program exits before `commit_lock_file()`, * `commit_lock_file_to()`, or `rollback_lock_file()` is called, the @@ -85,7 +83,7 @@ * * If you need to close the file descriptor you obtained from a * `hold_lock_file_for_*()` function yourself, do so by calling - * `close_lock_file()`. See "tempfile.h" for more information. + * `close_lock_file_gently()`. See "tempfile.h" for more information. * * * Under the covers, a lockfile is just a tempfile with a few helper @@ -104,16 +102,18 @@ * * Similarly, `commit_lock_file`, `commit_lock_file_to`, and * `close_lock_file` return 0 on success. On failure they set `errno` - * appropriately, do their best to roll back the lockfile, and return - * -1. + * appropriately and return -1. The `commit` variants (but not `close`) + * do their best to delete the temporary file before returning. */ #include "tempfile.h" struct lock_file { - struct tempfile tempfile; + struct tempfile *tempfile; }; +#define LOCK_INIT { NULL } + /* String appended to a filename to derive the lockfile name: */ #define LOCK_SUFFIX ".lock" #define LOCK_SUFFIX_LEN 5 @@ -180,7 +180,7 @@ static inline int hold_lock_file_for_update( */ static inline int is_lock_file_locked(struct lock_file *lk) { - return is_tempfile_active(&lk->tempfile); + return is_tempfile_active(lk->tempfile); } /* @@ -202,12 +202,13 @@ extern NORETURN void unable_to_lock_die(const char *path, int err); /* * Associate a stdio stream with the lockfile (which must still be * open). Return `NULL` (*without* rolling back the lockfile) on - * error. The stream is closed automatically when `close_lock_file()` - * is called or when the file is committed or rolled back. + * error. The stream is closed automatically when + * `close_lock_file_gently()` is called or when the file is committed or + * rolled back. */ static inline FILE *fdopen_lock_file(struct lock_file *lk, const char *mode) { - return fdopen_tempfile(&lk->tempfile, mode); + return fdopen_tempfile(lk->tempfile, mode); } /* @@ -216,17 +217,17 @@ static inline FILE *fdopen_lock_file(struct lock_file *lk, const char *mode) */ static inline const char *get_lock_file_path(struct lock_file *lk) { - return get_tempfile_path(&lk->tempfile); + return get_tempfile_path(lk->tempfile); } static inline int get_lock_file_fd(struct lock_file *lk) { - return get_tempfile_fd(&lk->tempfile); + return get_tempfile_fd(lk->tempfile); } static inline FILE *get_lock_file_fp(struct lock_file *lk) { - return get_tempfile_fp(&lk->tempfile); + return get_tempfile_fp(lk->tempfile); } /* @@ -241,22 +242,21 @@ extern char *get_locked_file_path(struct lock_file *lk); * lockfile over the file being locked. Return 0 upon success. On * failure to `close(2)`, return a negative value and roll back the * lock file. Usually `commit_lock_file()`, `commit_lock_file_to()`, - * or `rollback_lock_file()` should eventually be called if - * `close_lock_file()` succeeds. + * or `rollback_lock_file()` should eventually be called. */ -static inline int close_lock_file(struct lock_file *lk) +static inline int close_lock_file_gently(struct lock_file *lk) { - return close_tempfile(&lk->tempfile); + return close_tempfile_gently(lk->tempfile); } /* - * Re-open a lockfile that has been closed using `close_lock_file()` + * Re-open a lockfile that has been closed using `close_lock_file_gently()` * but not yet committed or rolled back. This can be used to implement * a sequence of operations like the following: * * * Lock file. * - * * Write new contents to lockfile, then `close_lock_file()` to + * * Write new contents to lockfile, then `close_lock_file_gently()` to * cause the contents to be written to disk. * * * Pass the name of the lockfile to another program to allow it (and @@ -270,7 +270,7 @@ static inline int close_lock_file(struct lock_file *lk) */ static inline int reopen_lock_file(struct lock_file *lk) { - return reopen_tempfile(&lk->tempfile); + return reopen_tempfile(lk->tempfile); } /* diff --git a/mailinfo.c b/mailinfo.c index bd574cb752..f2387a3267 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -149,16 +149,14 @@ static void handle_from(struct mailinfo *mi, const struct strbuf *from) at = strchr(f.buf, '@'); if (!at) { parse_bogus_from(mi, from); - return; + goto out; } /* * If we already have one email, don't take any confusing lines */ - if (mi->email.len && strchr(at + 1, '@')) { - strbuf_release(&f); - return; - } + if (mi->email.len && strchr(at + 1, '@')) + goto out; /* Pick up the string around '@', possibly delimited with <> * pair; that is the email part. @@ -198,6 +196,7 @@ static void handle_from(struct mailinfo *mi, const struct strbuf *from) } get_sane_name(&mi->name, &f, &mi->email); +out: strbuf_release(&f); } @@ -929,6 +928,7 @@ again: error("Detected mismatched boundaries, can't recover"); mi->input_error = -1; mi->content_top = mi->content; + strbuf_release(&newline); return 0; } handle_filter(mi, &newline); diff --git a/merge-recursive.c b/merge-recursive.c index 182626c027..1d3f8f0d22 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -24,6 +24,31 @@ #include "dir.h" #include "submodule.h" +struct path_hashmap_entry { + struct hashmap_entry e; + char path[FLEX_ARRAY]; +}; + +static int path_hashmap_cmp(const void *cmp_data, + const void *entry, + const void *entry_or_key, + const void *keydata) +{ + const struct path_hashmap_entry *a = entry; + const struct path_hashmap_entry *b = entry_or_key; + const char *key = keydata; + + if (ignore_case) + return strcasecmp(a->path, key ? key : b->path); + else + return strcmp(a->path, key ? key : b->path); +} + +static unsigned int path_hash(const char *path) +{ + return ignore_case ? strihash(path) : strhash(path); +} + static void flush_output(struct merge_options *o) { if (o->buffer_output < 2 && o->obuf.len) { @@ -314,29 +339,25 @@ static int save_files_dirs(const unsigned char *sha1, struct strbuf *base, const char *path, unsigned int mode, int stage, void *context) { + struct path_hashmap_entry *entry; int baselen = base->len; struct merge_options *o = context; strbuf_addstr(base, path); - if (S_ISDIR(mode)) - string_list_insert(&o->current_directory_set, base->buf); - else - string_list_insert(&o->current_file_set, base->buf); + FLEX_ALLOC_MEM(entry, path, base->buf, base->len); + hashmap_entry_init(entry, path_hash(entry->path)); + hashmap_add(&o->current_file_dir_set, entry); strbuf_setlen(base, baselen); return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); } -static int get_files_dirs(struct merge_options *o, struct tree *tree) +static void get_files_dirs(struct merge_options *o, struct tree *tree) { - int n; struct pathspec match_all; memset(&match_all, 0, sizeof(match_all)); - if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o)) - return 0; - n = o->current_file_set.nr + o->current_directory_set.nr; - return n; + read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o); } /* @@ -646,6 +667,7 @@ static void add_flattened_path(struct strbuf *out, const char *s) static char *unique_path(struct merge_options *o, const char *path, const char *branch) { + struct path_hashmap_entry *entry; struct strbuf newpath = STRBUF_INIT; int suffix = 0; size_t base_len; @@ -654,14 +676,16 @@ static char *unique_path(struct merge_options *o, const char *path, const char * add_flattened_path(&newpath, branch); base_len = newpath.len; - while (string_list_has_string(&o->current_file_set, newpath.buf) || - string_list_has_string(&o->current_directory_set, newpath.buf) || + while (hashmap_get_from_hash(&o->current_file_dir_set, + path_hash(newpath.buf), newpath.buf) || (!o->call_depth && file_exists(newpath.buf))) { strbuf_setlen(&newpath, base_len); strbuf_addf(&newpath, "_%d", suffix++); } - string_list_insert(&o->current_file_set, newpath.buf); + FLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len); + hashmap_entry_init(entry, path_hash(entry->path)); + hashmap_add(&o->current_file_dir_set, entry); return strbuf_detach(&newpath, NULL); } @@ -1945,8 +1969,14 @@ int merge_trees(struct merge_options *o, if (unmerged_cache()) { struct string_list *entries, *re_head, *re_merge; int i; - string_list_clear(&o->current_file_set, 1); - string_list_clear(&o->current_directory_set, 1); + /* + * Only need the hashmap while processing entries, so + * initialize it here and free it when we are done running + * through the entries. Keeping it in the merge_options as + * opposed to decaring a local hashmap is for convenience + * so that we don't have to pass it to around. + */ + hashmap_init(&o->current_file_dir_set, path_hashmap_cmp, NULL, 512); get_files_dirs(o, head); get_files_dirs(o, merge); @@ -1956,7 +1986,7 @@ int merge_trees(struct merge_options *o, re_merge = get_renames(o, merge, common, head, merge, entries); clean = process_renames(o, re_head, re_merge); if (clean < 0) - return clean; + goto cleanup; for (i = entries->nr-1; 0 <= i; i--) { const char *path = entries->items[i].string; struct stage_data *e = entries->items[i].util; @@ -1964,8 +1994,10 @@ int merge_trees(struct merge_options *o, int ret = process_entry(o, path, e); if (!ret) clean = 0; - else if (ret < 0) - return ret; + else if (ret < 0) { + clean = ret; + goto cleanup; + } } } for (i = 0; i < entries->nr; i++) { @@ -1975,13 +2007,19 @@ int merge_trees(struct merge_options *o, entries->items[i].string); } +cleanup: string_list_clear(re_merge, 0); string_list_clear(re_head, 0); string_list_clear(entries, 1); + hashmap_free(&o->current_file_dir_set, 1); + free(re_merge); free(re_head); free(entries); + + if (clean < 0) + return clean; } else clean = 1; @@ -2177,8 +2215,6 @@ void init_merge_options(struct merge_options *o) if (o->verbosity >= 5) o->buffer_output = 0; strbuf_init(&o->obuf, 0); - string_list_init(&o->current_file_set, 1); - string_list_init(&o->current_directory_set, 1); string_list_init(&o->df_conflict_file_set, 1); } diff --git a/merge-recursive.h b/merge-recursive.h index 735343b413..80d69d1401 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -25,8 +25,7 @@ struct merge_options { int show_rename_progress; int call_depth; struct strbuf obuf; - struct string_list current_file_set; - struct string_list current_directory_set; + struct hashmap current_file_dir_set; struct string_list df_conflict_file_set; }; diff --git a/name-hash.c b/name-hash.c index bd8dc7a6a7..45c98db0a0 100644 --- a/name-hash.c +++ b/name-hash.c @@ -584,9 +584,15 @@ static void lazy_init_name_hash(struct index_state *istate) hashmap_init(&istate->dir_hash, dir_entry_cmp, NULL, istate->cache_nr); if (lookup_lazy_params(istate)) { - hashmap_disallow_rehash(&istate->dir_hash, 1); + /* + * Disable item counting and automatic rehashing because + * we do per-chain (mod n) locking rather than whole hashmap + * locking and we need to prevent the table-size from changing + * and bucket items from being redistributed. + */ + hashmap_disable_item_counting(&istate->dir_hash); threaded_lazy_init_name_hash(istate); - hashmap_disallow_rehash(&istate->dir_hash, 0); + hashmap_enable_item_counting(&istate->dir_hash); } else { int nr; for (nr = 0; nr < istate->cache_nr; nr++) diff --git a/notes-merge.c b/notes-merge.c index b04d2f2131..597d43f65c 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -302,7 +302,7 @@ static void write_buf_to_worktree(const struct object_id *obj, fd = xopen(path, O_WRONLY | O_EXCL | O_CREAT, 0666); while (size > 0) { - long ret = write_in_full(fd, buf, size); + ssize_t ret = write_in_full(fd, buf, size); if (ret < 0) { /* Ignore epipe */ if (errno == EPIPE) @@ -64,7 +64,7 @@ struct non_note { #define CLR_PTR_TYPE(ptr) ((void *) ((uintptr_t) (ptr) & ~3)) #define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type))) -#define GET_NIBBLE(n, sha1) (((sha1[(n) >> 1]) >> ((~(n) & 0x01) << 2)) & 0x0f) +#define GET_NIBBLE(n, sha1) ((((sha1)[(n) >> 1]) >> ((~(n) & 0x01) << 2)) & 0x0f) #define KEY_INDEX (GIT_SHA1_RAWSZ - 1) #define FANOUT_PATH_SEPARATORS ((GIT_SHA1_HEXSZ / 2) - 1) @@ -335,31 +335,20 @@ static void note_tree_free(struct int_node *tree) } /* - * Convert a partial SHA1 hex string to the corresponding partial SHA1 value. - * - hex - Partial SHA1 segment in ASCII hex format - * - hex_len - Length of above segment. Must be multiple of 2 between 0 and 40 - * - sha1 - Partial SHA1 value is written here - * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20 - * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format)). - * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2). - * Pads sha1 with NULs up to sha1_len (not included in returned length). + * Read `len` pairs of hexadecimal digits from `hex` and write the + * values to `binary` as `len` bytes. Return 0 on success, or -1 if + * the input does not consist of hex digits). */ -static int get_oid_hex_segment(const char *hex, unsigned int hex_len, - unsigned char *oid, unsigned int oid_len) +static int hex_to_bytes(unsigned char *binary, const char *hex, size_t len) { - unsigned int i, len = hex_len >> 1; - if (hex_len % 2 != 0 || len > oid_len) - return -1; - for (i = 0; i < len; i++) { + for (; len; len--, hex += 2) { unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + if (val & ~0xff) return -1; - *oid++ = val; - hex += 2; + *binary++ = val; } - for (; i < oid_len; i++) - *oid++ = 0; - return len; + return 0; } static int non_note_cmp(const struct non_note *a, const struct non_note *b) @@ -417,13 +406,10 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, struct int_node *node, unsigned int n) { struct object_id object_oid; - unsigned int prefix_len; + size_t prefix_len; void *buf; struct tree_desc desc; struct name_entry entry; - int len, path_len; - unsigned char type; - struct leaf_node *l; buf = fill_tree_descriptor(&desc, &subtree->val_oid); if (!buf) @@ -431,66 +417,79 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, oid_to_hex(&subtree->val_oid)); prefix_len = subtree->key_oid.hash[KEY_INDEX]; - assert(prefix_len * 2 >= n); + if (prefix_len >= GIT_SHA1_RAWSZ) + BUG("prefix_len (%"PRIuMAX") is out of range", (uintmax_t)prefix_len); + if (prefix_len * 2 < n) + BUG("prefix_len (%"PRIuMAX") is too small", (uintmax_t)prefix_len); memcpy(object_oid.hash, subtree->key_oid.hash, prefix_len); while (tree_entry(&desc, &entry)) { - path_len = strlen(entry.path); - len = get_oid_hex_segment(entry.path, path_len, - object_oid.hash + prefix_len, GIT_SHA1_RAWSZ - prefix_len); - if (len < 0) - goto handle_non_note; /* entry.path is not a SHA1 */ - len += prefix_len; + unsigned char type; + struct leaf_node *l; + size_t path_len = strlen(entry.path); + + if (path_len == 2 * (GIT_SHA1_RAWSZ - prefix_len)) { + /* This is potentially the remainder of the SHA-1 */ + + if (!S_ISREG(entry.mode)) + /* notes must be blobs */ + goto handle_non_note; + + if (hex_to_bytes(object_oid.hash + prefix_len, entry.path, + GIT_SHA1_RAWSZ - prefix_len)) + goto handle_non_note; /* entry.path is not a SHA1 */ - /* - * If object SHA1 is complete (len == 20), assume note object - * If object SHA1 is incomplete (len < 20), and current - * component consists of 2 hex chars, assume note subtree - */ - if (len <= GIT_SHA1_RAWSZ) { type = PTR_TYPE_NOTE; - l = (struct leaf_node *) - xcalloc(1, sizeof(struct leaf_node)); - oidcpy(&l->key_oid, &object_oid); - oidcpy(&l->val_oid, entry.oid); - if (len < GIT_SHA1_RAWSZ) { - if (!S_ISDIR(entry.mode) || path_len != 2) - goto handle_non_note; /* not subtree */ - l->key_oid.hash[KEY_INDEX] = (unsigned char) len; - type = PTR_TYPE_SUBTREE; - } - if (note_tree_insert(t, node, n, l, type, - combine_notes_concatenate)) - die("Failed to load %s %s into notes tree " - "from %s", - type == PTR_TYPE_NOTE ? "note" : "subtree", - oid_to_hex(&l->key_oid), t->ref); + } else if (path_len == 2) { + /* This is potentially an internal node */ + size_t len = prefix_len; + + if (!S_ISDIR(entry.mode)) + /* internal nodes must be trees */ + goto handle_non_note; + + if (hex_to_bytes(object_oid.hash + len++, entry.path, 1)) + goto handle_non_note; /* entry.path is not a SHA1 */ + + /* + * Pad the rest of the SHA-1 with zeros, + * except for the last byte, where we write + * the length: + */ + memset(object_oid.hash + len, 0, GIT_SHA1_RAWSZ - len - 1); + object_oid.hash[KEY_INDEX] = (unsigned char)len; + + type = PTR_TYPE_SUBTREE; + } else { + /* This can't be part of a note */ + goto handle_non_note; } + + l = xcalloc(1, sizeof(*l)); + oidcpy(&l->key_oid, &object_oid); + oidcpy(&l->val_oid, entry.oid); + if (note_tree_insert(t, node, n, l, type, + combine_notes_concatenate)) + die("Failed to load %s %s into notes tree " + "from %s", + type == PTR_TYPE_NOTE ? "note" : "subtree", + oid_to_hex(&l->key_oid), t->ref); + continue; handle_non_note: /* - * Determine full path for this non-note entry: - * The filename is already found in entry.path, but the - * directory part of the path must be deduced from the subtree - * containing this entry. We assume here that the overall notes - * tree follows a strict byte-based progressive fanout - * structure (i.e. using 2/38, 2/2/36, etc. fanouts, and not - * e.g. 4/36 fanout). This means that if a non-note is found at - * path "dead/beef", the following code will register it as - * being found on "de/ad/beef". - * On the other hand, if you use such non-obvious non-note - * paths in the middle of a notes tree, you deserve what's - * coming to you ;). Note that for non-notes that are not - * SHA1-like at the top level, there will be no problems. - * - * To conclude, it is strongly advised to make sure non-notes - * have at least one non-hex character in the top-level path - * component. + * Determine full path for this non-note entry. The + * filename is already found in entry.path, but the + * directory part of the path must be deduced from the + * subtree containing this entry based on our + * knowledge that the overall notes tree follows a + * strict byte-based progressive fanout structure + * (i.e. using 2/38, 2/2/36, etc. fanouts). */ { struct strbuf non_note_path = STRBUF_INIT; const char *q = oid_to_hex(&subtree->key_oid); - int i; + size_t i; for (i = 0; i < prefix_len; i++) { strbuf_addch(&non_note_path, *q++); strbuf_addch(&non_note_path, *q++); diff --git a/outgoing/packfile.h b/outgoing/packfile.h deleted file mode 100644 index e69de29bb2..0000000000 --- a/outgoing/packfile.h +++ /dev/null diff --git a/pkt-line.c b/pkt-line.c index 7db9119573..647bbd3bce 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -94,9 +94,9 @@ void packet_flush(int fd) int packet_flush_gently(int fd) { packet_trace("0000", 4, 1); - if (write_in_full(fd, "0000", 4) == 4) - return 0; - return error("flush packet write failed"); + if (write_in_full(fd, "0000", 4) < 0) + return error("flush packet write failed"); + return 0; } void packet_buf_flush(struct strbuf *buf) @@ -136,19 +136,19 @@ static void format_packet(struct strbuf *out, const char *fmt, va_list args) static int packet_write_fmt_1(int fd, int gently, const char *fmt, va_list args) { - struct strbuf buf = STRBUF_INIT; - ssize_t count; + static struct strbuf buf = STRBUF_INIT; + strbuf_reset(&buf); format_packet(&buf, fmt, args); - count = write_in_full(fd, buf.buf, buf.len); - if (count == buf.len) - return 0; - - if (!gently) { - check_pipe(errno); - die_errno("packet write with format failed"); + if (write_in_full(fd, buf.buf, buf.len) < 0) { + if (!gently) { + check_pipe(errno); + die_errno("packet write with format failed"); + } + return error("packet write with format failed"); } - return error("packet write with format failed"); + + return 0; } void packet_write_fmt(int fd, const char *fmt, ...) @@ -183,9 +183,9 @@ static int packet_write_gently(const int fd_out, const char *buf, size_t size) packet_size = size + 4; set_packet_header(packet_write_buffer, packet_size); memcpy(packet_write_buffer + 4, buf, size); - if (write_in_full(fd_out, packet_write_buffer, packet_size) == packet_size) - return 0; - return error("packet write failed"); + if (write_in_full(fd_out, packet_write_buffer, packet_size) < 0) + return error("packet write failed"); + return 0; } void packet_buf_write(struct strbuf *buf, const char *fmt, ...) diff --git a/reachable.c b/reachable.c index d1ac5d97ef..88d7d679da 100644 --- a/reachable.c +++ b/reachable.c @@ -10,6 +10,7 @@ #include "progress.h" #include "list-objects.h" #include "packfile.h" +#include "worktree.h" struct connectivity_progress { struct progress *progress; @@ -177,6 +178,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog, /* detached HEAD is not included in the list above */ head_ref(add_one_ref, revs); + other_head_refs(add_one_ref, revs); /* Add all reflog info */ if (mark_reflog) diff --git a/read-cache.c b/read-cache.c index 40da87ea71..cdcd11c71e 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1922,7 +1922,7 @@ static int ce_write_flush(git_SHA_CTX *context, int fd) unsigned int buffered = write_buffer_len; if (buffered) { git_SHA1_Update(context, write_buffer, buffered); - if (write_in_full(fd, write_buffer, buffered) != buffered) + if (write_in_full(fd, write_buffer, buffered) < 0) return -1; write_buffer_len = 0; } @@ -1971,7 +1971,7 @@ static int ce_flush(git_SHA_CTX *context, int fd, unsigned char *sha1) /* Flush first if not enough space for SHA1 signature */ if (left + 20 > WRITE_BUFFER_SIZE) { - if (write_in_full(fd, write_buffer, left) != left) + if (write_in_full(fd, write_buffer, left) < 0) return -1; left = 0; } @@ -1980,7 +1980,7 @@ static int ce_flush(git_SHA_CTX *context, int fd, unsigned char *sha1) git_SHA1_Final(write_buffer + left, context); hashcpy(sha1, write_buffer + left); left += 20; - return (write_in_full(fd, write_buffer, left) != left) ? -1 : 0; + return (write_in_full(fd, write_buffer, left) < 0) ? -1 : 0; } static void ce_smudge_racily_clean_entry(struct cache_entry *ce) @@ -2103,7 +2103,9 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce, if (!result) result = ce_write(c, fd, to_remove_vi, prefix_size); if (!result) - result = ce_write(c, fd, ce->name + common, ce_namelen(ce) - common + 1); + result = ce_write(c, fd, ce->name + common, ce_namelen(ce) - common); + if (!result) + result = ce_write(c, fd, padding, 1); strbuf_splice(previous_name, common, to_remove, ce->name + common, ce_namelen(ce) - common); @@ -2309,8 +2311,11 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (ce_flush(&c, newfd, istate->sha1)) return -1; - if (close_tempfile(tempfile)) - return error(_("could not close '%s'"), tempfile->filename.buf); + if (close_tempfile_gently(tempfile)) { + error(_("could not close '%s'"), tempfile->filename.buf); + delete_tempfile(&tempfile); + return -1; + } if (stat(tempfile->filename.buf, &st)) return -1; istate->timestamp.sec = (unsigned int)st.st_mtime; @@ -2334,7 +2339,7 @@ static int commit_locked_index(struct lock_file *lk) static int do_write_locked_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { - int ret = do_write_index(istate, &lock->tempfile, 0); + int ret = do_write_index(istate, lock->tempfile, 0); if (ret) return ret; assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) != @@ -2342,7 +2347,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l if (flags & COMMIT_LOCK) return commit_locked_index(lock); else if (flags & CLOSE_LOCK) - return close_lock_file(lock); + return close_lock_file_gently(lock); else return ret; } @@ -2417,34 +2422,33 @@ static int clean_shared_index_files(const char *current_hex) return 0; } -static struct tempfile temporary_sharedindex; - static int write_shared_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { + struct tempfile *temp; struct split_index *si = istate->split_index; - int fd, ret; + int ret; - fd = mks_tempfile(&temporary_sharedindex, git_path("sharedindex_XXXXXX")); - if (fd < 0) { + temp = mks_tempfile(git_path("sharedindex_XXXXXX")); + if (!temp) { hashclr(si->base_sha1); return do_write_locked_index(istate, lock, flags); } move_cache_to_base_index(istate); - ret = do_write_index(si->base, &temporary_sharedindex, 1); + ret = do_write_index(si->base, temp, 1); if (ret) { - delete_tempfile(&temporary_sharedindex); + delete_tempfile(&temp); return ret; } - ret = adjust_shared_perm(get_tempfile_path(&temporary_sharedindex)); + ret = adjust_shared_perm(get_tempfile_path(temp)); if (ret) { int save_errno = errno; - error("cannot fix permission bits on %s", get_tempfile_path(&temporary_sharedindex)); - delete_tempfile(&temporary_sharedindex); + error("cannot fix permission bits on %s", get_tempfile_path(temp)); + delete_tempfile(&temp); errno = save_errno; return ret; } - ret = rename_tempfile(&temporary_sharedindex, + ret = rename_tempfile(&temp, git_path("sharedindex.%s", sha1_to_hex(si->base->sha1))); if (!ret) { hashcpy(si->base_sha1, si->base->sha1); @@ -336,12 +336,6 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data) return refs_for_each_tag_ref(get_main_ref_store(), fn, cb_data); } -int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return refs_for_each_tag_ref(get_submodule_ref_store(submodule), - fn, cb_data); -} - int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data); @@ -352,12 +346,6 @@ int for_each_branch_ref(each_ref_fn fn, void *cb_data) return refs_for_each_branch_ref(get_main_ref_store(), fn, cb_data); } -int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return refs_for_each_branch_ref(get_submodule_ref_store(submodule), - fn, cb_data); -} - int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data); @@ -368,12 +356,6 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data) return refs_for_each_remote_ref(get_main_ref_store(), fn, cb_data); } -int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return refs_for_each_remote_ref(get_submodule_ref_store(submodule), - fn, cb_data); -} - int head_ref_namespaced(each_ref_fn fn, void *cb_data) { struct strbuf buf = STRBUF_INIT; @@ -612,7 +594,7 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, if (fd < 0) { strbuf_addf(err, "could not open '%s' for writing: %s", filename, strerror(errno)); - return -1; + goto done; } if (old_sha1) { @@ -627,7 +609,7 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, } } - if (write_in_full(fd, buf.buf, buf.len) != buf.len) { + if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_addf(err, "could not write to '%s'", filename); rollback_lock_file(&lock); goto done; @@ -1266,19 +1248,13 @@ int refs_rename_ref_available(struct ref_store *refs, return ok; } -int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { struct object_id oid; int flag; - if (submodule) { - if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0) - return fn("HEAD", &oid, 0, cb_data); - - return 0; - } - - if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag)) + if (!refs_read_ref_full(refs, "HEAD", RESOLVE_REF_READING, + oid.hash, &flag)) return fn("HEAD", &oid, flag, cb_data); return 0; @@ -1286,7 +1262,7 @@ int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) int head_ref(each_ref_fn fn, void *cb_data) { - return head_ref_submodule(NULL, fn, cb_data); + return refs_head_ref(get_main_ref_store(), fn, cb_data); } struct ref_iterator *refs_ref_iterator_begin( @@ -1344,11 +1320,6 @@ int for_each_ref(each_ref_fn fn, void *cb_data) return refs_for_each_ref(get_main_ref_store(), fn, cb_data); } -int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return refs_for_each_ref(get_submodule_ref_store(submodule), fn, cb_data); -} - int refs_for_each_ref_in(struct ref_store *refs, const char *prefix, each_ref_fn fn, void *cb_data) { @@ -1370,23 +1341,15 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsig prefix, fn, 0, flag, cb_data); } -int for_each_ref_in_submodule(const char *submodule, const char *prefix, - each_ref_fn fn, void *cb_data) -{ - return refs_for_each_ref_in(get_submodule_ref_store(submodule), - prefix, fn, cb_data); -} - -int for_each_fullref_in_submodule(const char *submodule, const char *prefix, - each_ref_fn fn, void *cb_data, - unsigned int broken) +int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, + each_ref_fn fn, void *cb_data, + unsigned int broken) { unsigned int flag = 0; if (broken) flag = DO_FOR_EACH_INCLUDE_BROKEN; - return do_for_each_ref(get_submodule_ref_store(submodule), - prefix, fn, 0, flag, cb_data); + return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data); } int for_each_replace_ref(each_ref_fn fn, void *cb_data) @@ -1521,25 +1484,10 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, int resolve_gitlink_ref(const char *submodule, const char *refname, unsigned char *sha1) { - size_t len = strlen(submodule); struct ref_store *refs; int flags; - while (len && submodule[len - 1] == '/') - len--; - - if (!len) - return -1; - - if (submodule[len]) { - /* We need to strip off one or more trailing slashes */ - char *stripped = xmemdupz(submodule, len); - - refs = get_submodule_ref_store(stripped); - free(stripped); - } else { - refs = get_submodule_ref_store(submodule); - } + refs = get_submodule_ref_store(submodule); if (!refs) return -1; @@ -1654,31 +1602,32 @@ struct ref_store *get_submodule_ref_store(const char *submodule) { struct strbuf submodule_sb = STRBUF_INIT; struct ref_store *refs; - int ret; + char *to_free = NULL; + size_t len; - if (!submodule || !*submodule) { - /* - * FIXME: This case is ideally not allowed. But that - * can't happen until we clean up all the callers. - */ - return get_main_ref_store(); - } + if (!submodule) + return NULL; + + len = strlen(submodule); + while (len && is_dir_sep(submodule[len - 1])) + len--; + if (!len) + return NULL; + + if (submodule[len]) + /* We need to strip off one or more trailing slashes */ + submodule = to_free = xmemdupz(submodule, len); refs = lookup_ref_store_map(&submodule_ref_stores, submodule); if (refs) - return refs; + goto done; strbuf_addstr(&submodule_sb, submodule); - ret = is_nonbare_repository_dir(&submodule_sb); - strbuf_release(&submodule_sb); - if (!ret) - return NULL; + if (!is_nonbare_repository_dir(&submodule_sb)) + goto done; - ret = submodule_to_gitdir(&submodule_sb, submodule); - if (ret) { - strbuf_release(&submodule_sb); - return NULL; - } + if (submodule_to_gitdir(&submodule_sb, submodule)) + goto done; /* assume that add_submodule_odb() has been called */ refs = ref_store_init(submodule_sb.buf, @@ -1686,7 +1635,10 @@ struct ref_store *get_submodule_ref_store(const char *submodule) register_ref_store_map(&submodule_ref_stores, "submodule", refs, submodule); +done: strbuf_release(&submodule_sb); + free(to_free); + return refs; } @@ -275,6 +275,8 @@ typedef int each_ref_fn(const char *refname, * modifies the reference also returns a nonzero value to immediately * stop the iteration. Returned references are sorted. */ +int refs_head_ref(struct ref_store *refs, + each_ref_fn fn, void *cb_data); int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data); int refs_for_each_ref_in(struct ref_store *refs, const char *prefix, @@ -289,6 +291,9 @@ int refs_for_each_remote_ref(struct ref_store *refs, int head_ref(each_ref_fn fn, void *cb_data); int for_each_ref(each_ref_fn fn, void *cb_data); int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); +int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, + each_ref_fn fn, void *cb_data, + unsigned int broken); int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken); int for_each_tag_ref(each_ref_fn fn, void *cb_data); @@ -299,21 +304,6 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data); int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data); -int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); -int for_each_ref_submodule(const char *submodule, - each_ref_fn fn, void *cb_data); -int for_each_ref_in_submodule(const char *submodule, const char *prefix, - each_ref_fn fn, void *cb_data); -int for_each_fullref_in_submodule(const char *submodule, const char *prefix, - each_ref_fn fn, void *cb_data, - unsigned int broken); -int for_each_tag_ref_submodule(const char *submodule, - each_ref_fn fn, void *cb_data); -int for_each_branch_ref_submodule(const char *submodule, - each_ref_fn fn, void *cb_data); -int for_each_remote_ref_submodule(const char *submodule, - each_ref_fn fn, void *cb_data); - int head_ref_namespaced(each_ref_fn fn, void *cb_data); int for_each_namespaced_ref(each_ref_fn fn, void *cb_data); diff --git a/refs/files-backend.c b/refs/files-backend.c index fccbc24ac4..dac33628b3 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -12,7 +12,7 @@ struct ref_lock { char *ref_name; - struct lock_file *lk; + struct lock_file lk; struct object_id old_oid; }; @@ -106,15 +106,6 @@ static void files_reflog_path(struct files_ref_store *refs, struct strbuf *sb, const char *refname) { - if (!refname) { - /* - * FIXME: of course this is wrong in multi worktree - * setting. To be fixed real soon. - */ - strbuf_addf(sb, "%s/logs", refs->gitcommondir); - return; - } - switch (ref_type(refname)) { case REF_TYPE_PER_WORKTREE: case REF_TYPE_PSEUDOREF: @@ -418,9 +409,7 @@ out: static void unlock_ref(struct ref_lock *lock) { - /* Do not free lock->lk -- atexit() still looks at them */ - if (lock->lk) - rollback_lock_file(lock->lk); + rollback_lock_file(&lock->lk); free(lock->ref_name); free(lock); } @@ -534,11 +523,8 @@ retry: goto error_return; } - if (!lock->lk) - lock->lk = xcalloc(1, sizeof(struct lock_file)); - if (hold_lock_file_for_update_timeout( - lock->lk, ref_file.buf, LOCK_NO_DEREF, + &lock->lk, ref_file.buf, LOCK_NO_DEREF, get_files_ref_lock_timeout_ms()) < 0) { if (errno == ENOENT && --attempts_remaining > 0) { /* @@ -949,11 +935,9 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs, goto error_return; } - lock->lk = xcalloc(1, sizeof(struct lock_file)); - lock->ref_name = xstrdup(refname); - if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) { + if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) { last_errno = errno; unable_to_lock_message(ref_file.buf, errno, err); goto error_return; @@ -1057,11 +1041,17 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) strbuf_release(&err); } -static void prune_refs(struct files_ref_store *refs, struct ref_to_prune *r) +/* + * Prune the loose versions of the references in the linked list + * `*refs_to_prune`, freeing the entries in the list as we go. + */ +static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_to_prune) { - while (r) { + while (*refs_to_prune) { + struct ref_to_prune *r = *refs_to_prune; + *refs_to_prune = r->next; prune_ref(refs, r); - r = r->next; + free(r); } } @@ -1100,6 +1090,11 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) int ok; struct ref_to_prune *refs_to_prune = NULL; struct strbuf err = STRBUF_INIT; + struct ref_transaction *transaction; + + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + if (!transaction) + return -1; packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err); @@ -1115,12 +1110,14 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) continue; /* - * Create an entry in the packed-refs cache equivalent - * to the one from the loose ref cache, except that - * we don't copy the peeled status, because we want it - * to be re-peeled. + * Add a reference creation for this reference to the + * packed-refs transaction: */ - add_packed_ref(refs->packed_ref_store, iter->refname, iter->oid); + if (ref_transaction_update(transaction, iter->refname, + iter->oid->hash, NULL, + REF_NODEREF, NULL, &err)) + die("failure preparing to create packed reference %s: %s", + iter->refname, err.buf); /* Schedule the loose reference for pruning if requested. */ if ((flags & PACK_REFS_PRUNE)) { @@ -1134,11 +1131,14 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) if (ok != ITER_DONE) die("error while iterating over references"); - if (commit_packed_refs(refs->packed_ref_store, &err)) - die("unable to overwrite old ref-pack file: %s", err.buf); + if (ref_transaction_commit(transaction, &err)) + die("unable to write new packed-refs: %s", err.buf); + + ref_transaction_free(transaction); + packed_refs_unlock(refs->packed_ref_store); - prune_refs(refs, refs_to_prune); + prune_refs(refs, &refs_to_prune); strbuf_release(&err); return 0; } @@ -1157,7 +1157,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - if (repack_without_refs(refs->packed_ref_store, refnames, &err)) { + if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { packed_refs_unlock(refs->packed_ref_store); goto error; } @@ -1402,16 +1402,16 @@ static int files_rename_ref(struct ref_store *ref_store, return ret; } -static int close_ref(struct ref_lock *lock) +static int close_ref_gently(struct ref_lock *lock) { - if (close_lock_file(lock->lk)) + if (close_lock_file_gently(&lock->lk)) return -1; return 0; } static int commit_ref(struct ref_lock *lock) { - char *path = get_locked_file_path(lock->lk); + char *path = get_locked_file_path(&lock->lk); struct stat st; if (!lstat(path, &st) && S_ISDIR(st.st_mode)) { @@ -1435,7 +1435,7 @@ static int commit_ref(struct ref_lock *lock) free(path); } - if (commit_lock_file(lock->lk)) + if (commit_lock_file(&lock->lk)) return -1; return 0; } @@ -1549,7 +1549,7 @@ static int log_ref_write_fd(int fd, const struct object_id *old_oid, written = len <= maxlen ? write_in_full(fd, logrec, len) : -1; free(logrec); - if (written != len) + if (written < 0) return -1; return 0; @@ -1627,12 +1627,12 @@ static int write_ref_to_lockfile(struct ref_lock *lock, unlock_ref(lock); return -1; } - fd = get_lock_file_fd(lock->lk); - if (write_in_full(fd, oid_to_hex(oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ || - write_in_full(fd, &term, 1) != 1 || - close_ref(lock) < 0) { + fd = get_lock_file_fd(&lock->lk); + if (write_in_full(fd, oid_to_hex(oid), GIT_SHA1_HEXSZ) < 0 || + write_in_full(fd, &term, 1) < 0 || + close_ref_gently(lock) < 0) { strbuf_addf(err, - "couldn't write '%s'", get_lock_file_path(lock->lk)); + "couldn't write '%s'", get_lock_file_path(&lock->lk)); unlock_ref(lock); return -1; } @@ -1709,7 +1709,7 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target) { int ret = -1; #ifndef NO_SYMLINK_HEAD - char *ref_path = get_locked_file_path(lock->lk); + char *ref_path = get_locked_file_path(&lock->lk); unlink(ref_path); ret = symlink(target, ref_path); free(ref_path); @@ -1745,14 +1745,14 @@ static int create_symref_locked(struct files_ref_store *refs, return 0; } - if (!fdopen_lock_file(lock->lk, "w")) + if (!fdopen_lock_file(&lock->lk, "w")) return error("unable to fdopen %s: %s", - lock->lk->tempfile.filename.buf, strerror(errno)); + lock->lk.tempfile->filename.buf, strerror(errno)); update_symref_reflog(refs, lock, refname, target, logmsg); /* no error check; commit_ref will check ferror */ - fprintf(lock->lk->tempfile.fp, "ref: %s\n", target); + fprintf(lock->lk.tempfile->fp, "ref: %s\n", target); if (commit_ref(lock) < 0) return error("unable to write symref for %s: %s", refname, strerror(errno)); @@ -2059,23 +2059,63 @@ static struct ref_iterator_vtable files_reflog_iterator_vtable = { files_reflog_iterator_abort }; -static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store) +static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store, + const char *gitdir) { - struct files_ref_store *refs = - files_downcast(ref_store, REF_STORE_READ, - "reflog_iterator_begin"); struct files_reflog_iterator *iter = xcalloc(1, sizeof(*iter)); struct ref_iterator *ref_iterator = &iter->base; struct strbuf sb = STRBUF_INIT; base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable); - files_reflog_path(refs, &sb, NULL); + strbuf_addf(&sb, "%s/logs", gitdir); iter->dir_iterator = dir_iterator_begin(sb.buf); iter->ref_store = ref_store; strbuf_release(&sb); + return ref_iterator; } +static enum iterator_selection reflog_iterator_select( + struct ref_iterator *iter_worktree, + struct ref_iterator *iter_common, + void *cb_data) +{ + if (iter_worktree) { + /* + * We're a bit loose here. We probably should ignore + * common refs if they are accidentally added as + * per-worktree refs. + */ + return ITER_SELECT_0; + } else if (iter_common) { + if (ref_type(iter_common->refname) == REF_TYPE_NORMAL) + return ITER_SELECT_1; + + /* + * The main ref store may contain main worktree's + * per-worktree refs, which should be ignored + */ + return ITER_SKIP_1; + } else + return ITER_DONE; +} + +static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store) +{ + struct files_ref_store *refs = + files_downcast(ref_store, REF_STORE_READ, + "reflog_iterator_begin"); + + if (!strcmp(refs->gitdir, refs->gitcommondir)) { + return reflog_iterator_begin(ref_store, refs->gitcommondir); + } else { + return merge_ref_iterator_begin( + reflog_iterator_begin(ref_store, refs->gitdir), + reflog_iterator_begin(ref_store, refs->gitcommondir), + reflog_iterator_select, refs); + } +} + /* * If update is a direct update of head_ref (the reference pointed to * by HEAD), then add an extra REF_LOG_ONLY update for HEAD. @@ -2099,11 +2139,10 @@ static int split_head_update(struct ref_update *update, /* * First make sure that HEAD is not already in the - * transaction. This insertion is O(N) in the transaction + * transaction. This check is O(lg N) in the transaction * size, but it happens at most once per transaction. */ - item = string_list_insert(affected_refnames, "HEAD"); - if (item->util) { + if (string_list_has_string(affected_refnames, "HEAD")) { /* An entry already existed */ strbuf_addf(err, "multiple updates for 'HEAD' (including one " @@ -2118,6 +2157,14 @@ static int split_head_update(struct ref_update *update, update->new_oid.hash, update->old_oid.hash, update->msg); + /* + * Add "HEAD". This insertion is O(N) in the transaction + * size, but it happens at most once per transaction. + * Add new_update->refname instead of a literal "HEAD". + */ + if (strcmp(new_update->refname, "HEAD")) + BUG("%s unexpectedly not 'HEAD'", new_update->refname); + item = string_list_insert(affected_refnames, new_update->refname); item->util = new_update; return 0; @@ -2144,13 +2191,12 @@ static int split_symref_update(struct files_ref_store *refs, /* * First make sure that referent is not already in the - * transaction. This insertion is O(N) in the transaction + * transaction. This check is O(lg N) in the transaction * size, but it happens at most once per symref in a * transaction. */ - item = string_list_insert(affected_refnames, referent); - if (item->util) { - /* An entry already existed */ + if (string_list_has_string(affected_refnames, referent)) { + /* An entry already exists */ strbuf_addf(err, "multiple updates for '%s' (including one " "via symref '%s') are not allowed", @@ -2185,6 +2231,17 @@ static int split_symref_update(struct files_ref_store *refs, update->flags |= REF_LOG_ONLY | REF_NODEREF; update->flags &= ~REF_HAVE_OLD; + /* + * Add the referent. This insertion is O(N) in the transaction + * size, but it happens at most once per symref in a + * transaction. Make sure to add new_update->refname, which will + * be valid as long as affected_refnames is in use, and NOT + * referent, which might soon be freed by our caller. + */ + item = string_list_insert(affected_refnames, new_update->refname); + if (item->util) + BUG("%s unexpectedly found in affected_refnames", + new_update->refname); item->util = new_update; return 0; @@ -2256,7 +2313,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, struct strbuf referent = STRBUF_INIT; int mustexist = (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid); - int ret; + int ret = 0; struct ref_lock *lock; files_assert_main_repository(refs, "lock_ref_for_update"); @@ -2268,7 +2325,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, ret = split_head_update(update, transaction, head_ref, affected_refnames, err); if (ret) - return ret; + goto out; } ret = lock_raw_ref(refs, update->refname, mustexist, @@ -2282,7 +2339,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, strbuf_addf(err, "cannot lock ref '%s': %s", original_update_refname(update), reason); free(reason); - return ret; + goto out; } update->backend_data = lock; @@ -2301,10 +2358,12 @@ static int lock_ref_for_update(struct files_ref_store *refs, strbuf_addf(err, "cannot lock ref '%s': " "error reading reference", original_update_refname(update)); - return -1; + ret = TRANSACTION_GENERIC_ERROR; + goto out; } } else if (check_old_oid(update, &lock->old_oid, err)) { - return TRANSACTION_GENERIC_ERROR; + ret = TRANSACTION_GENERIC_ERROR; + goto out; } } else { /* @@ -2318,13 +2377,15 @@ static int lock_ref_for_update(struct files_ref_store *refs, referent.buf, transaction, affected_refnames, err); if (ret) - return ret; + goto out; } } else { struct ref_update *parent_update; - if (check_old_oid(update, &lock->old_oid, err)) - return TRANSACTION_GENERIC_ERROR; + if (check_old_oid(update, &lock->old_oid, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto out; + } /* * If this update is happening indirectly because of a @@ -2361,7 +2422,8 @@ static int lock_ref_for_update(struct files_ref_store *refs, "cannot update ref '%s': %s", update->refname, write_err); free(write_err); - return TRANSACTION_GENERIC_ERROR; + ret = TRANSACTION_GENERIC_ERROR; + goto out; } else { update->flags |= REF_NEEDS_COMMIT; } @@ -2372,22 +2434,35 @@ static int lock_ref_for_update(struct files_ref_store *refs, * the lockfile is still open. Close it to * free up the file descriptor: */ - if (close_ref(lock)) { + if (close_ref_gently(lock)) { strbuf_addf(err, "couldn't close '%s.lock'", update->refname); - return TRANSACTION_GENERIC_ERROR; + ret = TRANSACTION_GENERIC_ERROR; + goto out; } } - return 0; + +out: + strbuf_release(&referent); + return ret; } +struct files_transaction_backend_data { + struct ref_transaction *packed_transaction; + int packed_refs_locked; +}; + /* * Unlock any references in `transaction` that are still locked, and * mark the transaction closed. */ -static void files_transaction_cleanup(struct ref_transaction *transaction) +static void files_transaction_cleanup(struct files_ref_store *refs, + struct ref_transaction *transaction) { size_t i; + struct files_transaction_backend_data *backend_data = + transaction->backend_data; + struct strbuf err = STRBUF_INIT; for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; @@ -2399,6 +2474,17 @@ static void files_transaction_cleanup(struct ref_transaction *transaction) } } + if (backend_data->packed_transaction && + ref_transaction_abort(backend_data->packed_transaction, &err)) { + error("error aborting transaction: %s", err.buf); + strbuf_release(&err); + } + + if (backend_data->packed_refs_locked) + packed_refs_unlock(refs->packed_ref_store); + + free(backend_data); + transaction->state = REF_TRANSACTION_CLOSED; } @@ -2415,12 +2501,17 @@ static int files_transaction_prepare(struct ref_store *ref_store, char *head_ref = NULL; int head_type; struct object_id head_oid; + struct files_transaction_backend_data *backend_data; + struct ref_transaction *packed_transaction = NULL; assert(err); if (!transaction->nr) goto cleanup; + backend_data = xcalloc(1, sizeof(*backend_data)); + transaction->backend_data = backend_data; + /* * Fail if a refname appears more than once in the * transaction. (If we end up splitting up any updates using @@ -2487,6 +2578,41 @@ static int files_transaction_prepare(struct ref_store *ref_store, head_ref, &affected_refnames, err); if (ret) break; + + if (update->flags & REF_DELETING && + !(update->flags & REF_LOG_ONLY) && + !(update->flags & REF_ISPRUNING)) { + /* + * This reference has to be deleted from + * packed-refs if it exists there. + */ + if (!packed_transaction) { + packed_transaction = ref_store_transaction_begin( + refs->packed_ref_store, err); + if (!packed_transaction) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + backend_data->packed_transaction = + packed_transaction; + } + + ref_transaction_add_update( + packed_transaction, update->refname, + update->flags & ~REF_HAVE_OLD, + update->new_oid.hash, update->old_oid.hash, + NULL); + } + } + + if (packed_transaction) { + if (packed_refs_lock(refs->packed_ref_store, 0, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + backend_data->packed_refs_locked = 1; + ret = ref_transaction_prepare(packed_transaction, err); } cleanup: @@ -2494,7 +2620,7 @@ cleanup: string_list_clear(&affected_refnames, 0); if (ret) - files_transaction_cleanup(transaction); + files_transaction_cleanup(refs, transaction); else transaction->state = REF_TRANSACTION_PREPARED; @@ -2509,9 +2635,10 @@ static int files_transaction_finish(struct ref_store *ref_store, files_downcast(ref_store, 0, "ref_transaction_finish"); size_t i; int ret = 0; - struct string_list refs_to_delete = STRING_LIST_INIT_NODUP; - struct string_list_item *ref_to_delete; struct strbuf sb = STRBUF_INIT; + struct files_transaction_backend_data *backend_data; + struct ref_transaction *packed_transaction; + assert(err); @@ -2520,6 +2647,9 @@ static int files_transaction_finish(struct ref_store *ref_store, return 0; } + backend_data = transaction->backend_data; + packed_transaction = backend_data->packed_transaction; + /* Perform updates first so live commits remain referenced */ for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; @@ -2555,7 +2685,44 @@ static int files_transaction_finish(struct ref_store *ref_store, } } } - /* Perform deletes now that updates are safely completed */ + + /* + * Now that updates are safely completed, we can perform + * deletes. First delete the reflogs of any references that + * will be deleted, since (in the unexpected event of an + * error) leaving a reference without a reflog is less bad + * than leaving a reflog without a reference (the latter is a + * mildly invalid repository state): + */ + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; + if (update->flags & REF_DELETING && + !(update->flags & REF_LOG_ONLY) && + !(update->flags & REF_ISPRUNING)) { + strbuf_reset(&sb); + files_reflog_path(refs, &sb, update->refname); + if (!unlink_or_warn(sb.buf)) + try_remove_empty_parents(refs, update->refname, + REMOVE_EMPTY_PARENTS_REFLOG); + } + } + + /* + * Perform deletes now that updates are safely completed. + * + * First delete any packed versions of the references, while + * retaining the packed-refs lock: + */ + if (packed_transaction) { + ret = ref_transaction_commit(packed_transaction, err); + ref_transaction_free(packed_transaction); + packed_transaction = NULL; + backend_data->packed_transaction = NULL; + if (ret) + goto cleanup; + } + + /* Now delete the loose versions of the references: */ for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; struct ref_lock *lock = update->backend_data; @@ -2573,39 +2740,13 @@ static int files_transaction_finish(struct ref_store *ref_store, } update->flags |= REF_DELETED_LOOSE; } - - if (!(update->flags & REF_ISPRUNING)) - string_list_append(&refs_to_delete, - lock->ref_name); } } - if (packed_refs_lock(refs->packed_ref_store, 0, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - - if (repack_without_refs(refs->packed_ref_store, &refs_to_delete, err)) { - ret = TRANSACTION_GENERIC_ERROR; - packed_refs_unlock(refs->packed_ref_store); - goto cleanup; - } - - packed_refs_unlock(refs->packed_ref_store); - - /* Delete the reflogs of any references that were deleted: */ - for_each_string_list_item(ref_to_delete, &refs_to_delete) { - strbuf_reset(&sb); - files_reflog_path(refs, &sb, ref_to_delete->string); - if (!unlink_or_warn(sb.buf)) - try_remove_empty_parents(refs, ref_to_delete->string, - REMOVE_EMPTY_PARENTS_REFLOG); - } - clear_loose_ref_cache(refs); cleanup: - files_transaction_cleanup(transaction); + files_transaction_cleanup(refs, transaction); for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; @@ -2623,7 +2764,6 @@ cleanup: } strbuf_release(&sb); - string_list_clear(&refs_to_delete, 0); return ret; } @@ -2631,7 +2771,10 @@ static int files_transaction_abort(struct ref_store *ref_store, struct ref_transaction *transaction, struct strbuf *err) { - files_transaction_cleanup(transaction); + struct files_ref_store *refs = + files_downcast(ref_store, 0, "ref_transaction_abort"); + + files_transaction_cleanup(refs, transaction); return 0; } @@ -2653,6 +2796,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, size_t i; int ret = 0; struct string_list affected_refnames = STRING_LIST_INIT_NODUP; + struct ref_transaction *packed_transaction = NULL; assert(err); @@ -2685,6 +2829,12 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) die("BUG: initial ref transaction called with existing refs"); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err); + if (!packed_transaction) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; @@ -2697,6 +2847,15 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, ret = TRANSACTION_NAME_CONFLICT; goto cleanup; } + + /* + * Add a reference creation for this reference to the + * packed-refs transaction: + */ + ref_transaction_add_update(packed_transaction, update->refname, + update->flags & ~REF_HAVE_OLD, + update->new_oid.hash, update->old_oid.hash, + NULL); } if (packed_refs_lock(refs->packed_ref_store, 0, err)) { @@ -2704,21 +2863,14 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, goto cleanup; } - for (i = 0; i < transaction->nr; i++) { - struct ref_update *update = transaction->updates[i]; - - if ((update->flags & REF_HAVE_NEW) && - !is_null_oid(&update->new_oid)) - add_packed_ref(refs->packed_ref_store, update->refname, - &update->new_oid); - } - - if (commit_packed_refs(refs->packed_ref_store, err)) { + if (initial_ref_transaction_commit(packed_transaction, err)) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } cleanup: + if (packed_transaction) + ref_transaction_free(packed_transaction); packed_refs_unlock(refs->packed_ref_store); transaction->state = REF_TRANSACTION_CLOSED; string_list_clear(&affected_refnames, 0); @@ -2848,16 +3000,17 @@ static int files_reflog_expire(struct ref_store *ref_store, !(type & REF_ISSYMREF) && !is_null_oid(&cb.last_kept_oid); - if (close_lock_file(&reflog_lock)) { + if (close_lock_file_gently(&reflog_lock)) { status |= error("couldn't write %s: %s", log_file, strerror(errno)); + rollback_lock_file(&reflog_lock); } else if (update && - (write_in_full(get_lock_file_fd(lock->lk), - oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ || - write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 || - close_ref(lock) < 0)) { + (write_in_full(get_lock_file_fd(&lock->lk), + oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) < 0 || + write_str_in_full(get_lock_file_fd(&lock->lk), "\n") < 1 || + close_ref_gently(lock) < 0)) { status |= error("couldn't write %s", - get_lock_file_path(lock->lk)); + get_lock_file_path(&lock->lk)); rollback_lock_file(&reflog_lock); } else if (commit_lock_file(&reflog_lock)) { status |= error("unable to write reflog '%s' (%s)", diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 412c85034f..3bc47ffd5e 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -75,7 +75,7 @@ struct packed_ref_store { * "packed-refs" file. Note that this (and thus the enclosing * `packed_ref_store`) must not be freed. */ - struct tempfile tempfile; + struct tempfile *tempfile; }; struct ref_store *packed_ref_store_create(const char *path, @@ -92,19 +92,6 @@ struct ref_store *packed_ref_store_create(const char *path, } /* - * Die if refs is not the main ref store. caller is used in any - * necessary error messages. - */ -static void packed_assert_main_repository(struct packed_ref_store *refs, - const char *caller) -{ - if (refs->store_flags & REF_STORE_MAIN) - return; - - die("BUG: operation %s only allowed for main ref store", caller); -} - -/* * Downcast `ref_store` to `packed_ref_store`. Die if `ref_store` is * not a `packed_ref_store`. Also die if `packed_ref_store` doesn't * support at least the flags specified in `required_flags`. `caller` @@ -322,40 +309,6 @@ static struct ref_dir *get_packed_refs(struct packed_ref_store *refs) } /* - * Add or overwrite a reference in the in-memory packed reference - * cache. This may only be called while the packed-refs file is locked - * (see packed_refs_lock()). To actually write the packed-refs file, - * call commit_packed_refs(). - */ -void add_packed_ref(struct ref_store *ref_store, - const char *refname, const struct object_id *oid) -{ - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE, - "add_packed_ref"); - struct ref_dir *packed_refs; - struct ref_entry *packed_entry; - - if (!is_lock_file_locked(&refs->lock)) - die("BUG: packed refs not locked"); - - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) - die("Reference has invalid format: '%s'", refname); - - packed_refs = get_packed_refs(refs); - packed_entry = find_ref_entry(packed_refs, refname); - if (packed_entry) { - /* Overwrite the existing entry: */ - oidcpy(&packed_entry->u.value.oid, oid); - packed_entry->flag = REF_ISPACKED; - oidclr(&packed_entry->u.value.peeled); - } else { - packed_entry = create_ref_entry(refname, oid, REF_ISPACKED); - add_ref_entry(packed_refs, packed_entry); - } -} - -/* * Return the ref_entry for the given refname from the packed * references. If it does not exist, return NULL. */ @@ -525,7 +478,6 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) "packed_refs_lock"); static int timeout_configured = 0; static int timeout_value = 1000; - struct packed_ref_cache *packed_ref_cache; if (!timeout_configured) { git_config_get_int("core.packedrefstimeout", &timeout_value); @@ -545,8 +497,9 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) return -1; } - if (close_lock_file(&refs->lock)) { + if (close_lock_file_gently(&refs->lock)) { strbuf_addf(err, "unable to close %s: %s", refs->path, strerror(errno)); + rollback_lock_file(&refs->lock); return -1; } @@ -560,9 +513,11 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) */ validate_packed_ref_cache(refs); - packed_ref_cache = get_packed_ref_cache(refs); - /* Increment the reference count to prevent it from being freed: */ - acquire_packed_ref_cache(packed_ref_cache); + /* + * Now make sure that the packed-refs file as it exists in the + * locked state is loaded into the cache: + */ + get_packed_ref_cache(refs); return 0; } @@ -576,7 +531,6 @@ void packed_refs_unlock(struct ref_store *ref_store) if (!is_lock_file_locked(&refs->lock)) die("BUG: packed_refs_unlock() called when not locked"); rollback_lock_file(&refs->lock); - release_packed_ref_cache(refs->cache); } int packed_refs_is_locked(struct ref_store *ref_store) @@ -596,29 +550,35 @@ int packed_refs_is_locked(struct ref_store *ref_store) static const char PACKED_REFS_HEADER[] = "# pack-refs with: peeled fully-peeled \n"; +static int packed_init_db(struct ref_store *ref_store, struct strbuf *err) +{ + /* Nothing to do. */ + return 0; +} + /* - * Write the current version of the packed refs cache from memory to - * disk. The packed-refs file must already be locked for writing (see - * packed_refs_lock()). Return zero on success. On errors, rollback - * the lockfile, write an error message to `err`, and return a nonzero - * value. + * Write the packed-refs from the cache to the packed-refs tempfile, + * incorporating any changes from `updates`. `updates` must be a + * sorted string list whose keys are the refnames and whose util + * values are `struct ref_update *`. On error, rollback the tempfile, + * write an error message to `err`, and return a nonzero value. + * + * The packfile must be locked before calling this function and will + * remain locked when it is done. */ -int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err) +static int write_with_updates(struct packed_ref_store *refs, + struct string_list *updates, + struct strbuf *err) { - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN, - "commit_packed_refs"); - struct packed_ref_cache *packed_ref_cache = - get_packed_ref_cache(refs); + struct ref_iterator *iter = NULL; + size_t i; int ok; - int ret = -1; - struct strbuf sb = STRBUF_INIT; FILE *out; - struct ref_iterator *iter; + struct strbuf sb = STRBUF_INIT; char *packed_refs_path; if (!is_lock_file_locked(&refs->lock)) - die("BUG: commit_packed_refs() called when unlocked"); + die("BUG: write_with_updates() called while unlocked"); /* * If packed-refs is a symlink, we want to overwrite the @@ -627,146 +587,303 @@ int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err) */ packed_refs_path = get_locked_file_path(&refs->lock); strbuf_addf(&sb, "%s.new", packed_refs_path); - if (create_tempfile(&refs->tempfile, sb.buf) < 0) { + free(packed_refs_path); + refs->tempfile = create_tempfile(sb.buf); + if (!refs->tempfile) { strbuf_addf(err, "unable to create file %s: %s", sb.buf, strerror(errno)); strbuf_release(&sb); - goto out; + return -1; } strbuf_release(&sb); - out = fdopen_tempfile(&refs->tempfile, "w"); + out = fdopen_tempfile(refs->tempfile, "w"); if (!out) { strbuf_addf(err, "unable to fdopen packed-refs tempfile: %s", strerror(errno)); goto error; } - if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0) { - strbuf_addf(err, "error writing to %s: %s", - get_tempfile_path(&refs->tempfile), strerror(errno)); - goto error; - } + if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0) + goto write_error; + + /* + * We iterate in parallel through the current list of refs and + * the list of updates, processing an entry from at least one + * of the lists each time through the loop. When the current + * list of refs is exhausted, set iter to NULL. When the list + * of updates is exhausted, leave i set to updates->nr. + */ + iter = packed_ref_iterator_begin(&refs->base, "", + DO_FOR_EACH_INCLUDE_BROKEN); + if ((ok = ref_iterator_advance(iter)) != ITER_OK) + iter = NULL; + + i = 0; - iter = cache_ref_iterator_begin(packed_ref_cache->cache, NULL, 0); - while ((ok = ref_iterator_advance(iter)) == ITER_OK) { - struct object_id peeled; - int peel_error = ref_iterator_peel(iter, &peeled); - - if (write_packed_entry(out, iter->refname, iter->oid->hash, - peel_error ? NULL : peeled.hash)) { - strbuf_addf(err, "error writing to %s: %s", - get_tempfile_path(&refs->tempfile), - strerror(errno)); - ref_iterator_abort(iter); - goto error; + while (iter || i < updates->nr) { + struct ref_update *update = NULL; + int cmp; + + if (i >= updates->nr) { + cmp = -1; + } else { + update = updates->items[i].util; + + if (!iter) + cmp = +1; + else + cmp = strcmp(iter->refname, update->refname); + } + + if (!cmp) { + /* + * There is both an old value and an update + * for this reference. Check the old value if + * necessary: + */ + if ((update->flags & REF_HAVE_OLD)) { + if (is_null_oid(&update->old_oid)) { + strbuf_addf(err, "cannot update ref '%s': " + "reference already exists", + update->refname); + goto error; + } else if (oidcmp(&update->old_oid, iter->oid)) { + strbuf_addf(err, "cannot update ref '%s': " + "is at %s but expected %s", + update->refname, + oid_to_hex(iter->oid), + oid_to_hex(&update->old_oid)); + goto error; + } + } + + /* Now figure out what to use for the new value: */ + if ((update->flags & REF_HAVE_NEW)) { + /* + * The update takes precedence. Skip + * the iterator over the unneeded + * value. + */ + if ((ok = ref_iterator_advance(iter)) != ITER_OK) + iter = NULL; + cmp = +1; + } else { + /* + * The update doesn't actually want to + * change anything. We're done with it. + */ + i++; + cmp = -1; + } + } else if (cmp > 0) { + /* + * There is no old value but there is an + * update for this reference. Make sure that + * the update didn't expect an existing value: + */ + if ((update->flags & REF_HAVE_OLD) && + !is_null_oid(&update->old_oid)) { + strbuf_addf(err, "cannot update ref '%s': " + "reference is missing but expected %s", + update->refname, + oid_to_hex(&update->old_oid)); + goto error; + } + } + + if (cmp < 0) { + /* Pass the old reference through. */ + + struct object_id peeled; + int peel_error = ref_iterator_peel(iter, &peeled); + + if (write_packed_entry(out, iter->refname, + iter->oid->hash, + peel_error ? NULL : peeled.hash)) + goto write_error; + + if ((ok = ref_iterator_advance(iter)) != ITER_OK) + iter = NULL; + } else if (is_null_oid(&update->new_oid)) { + /* + * The update wants to delete the reference, + * and the reference either didn't exist or we + * have already skipped it. So we're done with + * the update (and don't have to write + * anything). + */ + i++; + } else { + struct object_id peeled; + int peel_error = peel_object(update->new_oid.hash, + peeled.hash); + + if (write_packed_entry(out, update->refname, + update->new_oid.hash, + peel_error ? NULL : peeled.hash)) + goto write_error; + + i++; } } if (ok != ITER_DONE) { - strbuf_addf(err, "unable to rewrite packed-refs file: " + strbuf_addf(err, "unable to write packed-refs file: " "error iterating over old contents"); goto error; } - if (rename_tempfile(&refs->tempfile, packed_refs_path)) { - strbuf_addf(err, "error replacing %s: %s", - refs->path, strerror(errno)); - goto out; + if (close_tempfile_gently(refs->tempfile)) { + strbuf_addf(err, "error closing file %s: %s", + get_tempfile_path(refs->tempfile), + strerror(errno)); + strbuf_release(&sb); + delete_tempfile(&refs->tempfile); + return -1; } - ret = 0; - goto out; + return 0; + +write_error: + strbuf_addf(err, "error writing to %s: %s", + get_tempfile_path(refs->tempfile), strerror(errno)); error: - delete_tempfile(&refs->tempfile); + if (iter) + ref_iterator_abort(iter); -out: - free(packed_refs_path); - return ret; + delete_tempfile(&refs->tempfile); + return -1; } -/* - * Rewrite the packed-refs file, omitting any refs listed in - * 'refnames'. On error, leave packed-refs unchanged, write an error - * message to 'err', and return a nonzero value. The packed refs lock - * must be held when calling this function; it will still be held when - * the function returns. - * - * The refs in 'refnames' needn't be sorted. `err` must not be NULL. - */ -int repack_without_refs(struct ref_store *ref_store, - struct string_list *refnames, struct strbuf *err) +struct packed_transaction_backend_data { + /* True iff the transaction owns the packed-refs lock. */ + int own_lock; + + struct string_list updates; +}; + +static void packed_transaction_cleanup(struct packed_ref_store *refs, + struct ref_transaction *transaction) { - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN, - "repack_without_refs"); - struct ref_dir *packed; - struct string_list_item *refname; - int needs_repacking = 0, removed = 0; + struct packed_transaction_backend_data *data = transaction->backend_data; - packed_assert_main_repository(refs, "repack_without_refs"); - assert(err); + if (data) { + string_list_clear(&data->updates, 0); - if (!is_lock_file_locked(&refs->lock)) - die("BUG: repack_without_refs called without holding lock"); + if (is_tempfile_active(refs->tempfile)) + delete_tempfile(&refs->tempfile); - /* Look for a packed ref */ - for_each_string_list_item(refname, refnames) { - if (get_packed_ref(refs, refname->string)) { - needs_repacking = 1; - break; + if (data->own_lock && is_lock_file_locked(&refs->lock)) { + packed_refs_unlock(&refs->base); + data->own_lock = 0; } - } - /* Avoid locking if we have nothing to do */ - if (!needs_repacking) - return 0; /* no refname exists in packed refs */ - - packed = get_packed_refs(refs); - - /* Remove refnames from the cache */ - for_each_string_list_item(refname, refnames) - if (remove_entry_from_dir(packed, refname->string) != -1) - removed = 1; - if (!removed) { - /* - * All packed entries disappeared while we were - * acquiring the lock. - */ - clear_packed_ref_cache(refs); - return 0; + free(data); + transaction->backend_data = NULL; } - /* Write what remains */ - return commit_packed_refs(&refs->base, err); -} - -static int packed_init_db(struct ref_store *ref_store, struct strbuf *err) -{ - /* Nothing to do. */ - return 0; + transaction->state = REF_TRANSACTION_CLOSED; } static int packed_transaction_prepare(struct ref_store *ref_store, struct ref_transaction *transaction, struct strbuf *err) { - die("BUG: not implemented yet"); + struct packed_ref_store *refs = packed_downcast( + ref_store, + REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB, + "ref_transaction_prepare"); + struct packed_transaction_backend_data *data; + size_t i; + int ret = TRANSACTION_GENERIC_ERROR; + + /* + * Note that we *don't* skip transactions with zero updates, + * because such a transaction might be executed for the side + * effect of ensuring that all of the references are peeled. + * If the caller wants to optimize away empty transactions, it + * should do so itself. + */ + + data = xcalloc(1, sizeof(*data)); + string_list_init(&data->updates, 0); + + transaction->backend_data = data; + + /* + * Stick the updates in a string list by refname so that we + * can sort them: + */ + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; + struct string_list_item *item = + string_list_append(&data->updates, update->refname); + + /* Store a pointer to update in item->util: */ + item->util = update; + } + string_list_sort(&data->updates); + + if (ref_update_reject_duplicates(&data->updates, err)) + goto failure; + + if (!is_lock_file_locked(&refs->lock)) { + if (packed_refs_lock(ref_store, 0, err)) + goto failure; + data->own_lock = 1; + } + + if (write_with_updates(refs, &data->updates, err)) + goto failure; + + transaction->state = REF_TRANSACTION_PREPARED; + return 0; + +failure: + packed_transaction_cleanup(refs, transaction); + return ret; } static int packed_transaction_abort(struct ref_store *ref_store, struct ref_transaction *transaction, struct strbuf *err) { - die("BUG: not implemented yet"); + struct packed_ref_store *refs = packed_downcast( + ref_store, + REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB, + "ref_transaction_abort"); + + packed_transaction_cleanup(refs, transaction); + return 0; } static int packed_transaction_finish(struct ref_store *ref_store, struct ref_transaction *transaction, struct strbuf *err) { - die("BUG: not implemented yet"); + struct packed_ref_store *refs = packed_downcast( + ref_store, + REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB, + "ref_transaction_finish"); + int ret = TRANSACTION_GENERIC_ERROR; + char *packed_refs_path; + + packed_refs_path = get_locked_file_path(&refs->lock); + if (rename_tempfile(&refs->tempfile, packed_refs_path)) { + strbuf_addf(err, "error replacing %s: %s", + refs->path, strerror(errno)); + goto cleanup; + } + + clear_packed_ref_cache(refs); + ret = 0; + +cleanup: + free(packed_refs_path); + packed_transaction_cleanup(refs, transaction); + return ret; } static int packed_initial_transaction_commit(struct ref_store *ref_store, @@ -779,7 +896,50 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, static int packed_delete_refs(struct ref_store *ref_store, const char *msg, struct string_list *refnames, unsigned int flags) { - die("BUG: not implemented yet"); + struct packed_ref_store *refs = + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct strbuf err = STRBUF_INIT; + struct ref_transaction *transaction; + struct string_list_item *item; + int ret; + + (void)refs; /* We need the check above, but don't use the variable */ + + if (!refnames->nr) + return 0; + + /* + * Since we don't check the references' old_oids, the + * individual updates can't fail, so we can pack all of the + * updates into a single transaction. + */ + + transaction = ref_store_transaction_begin(ref_store, &err); + if (!transaction) + return -1; + + for_each_string_list_item(item, refnames) { + if (ref_transaction_delete(transaction, item->string, NULL, + flags, msg, &err)) { + warning(_("could not delete reference %s: %s"), + item->string, err.buf); + strbuf_reset(&err); + } + } + + ret = ref_transaction_commit(transaction, &err); + + if (ret) { + if (refnames->nr == 1) + error(_("could not delete reference %s: %s"), + refnames->items[0].string, err.buf); + else + error(_("could not delete references: %s"), err.buf); + } + + ref_transaction_free(transaction); + strbuf_release(&err); + return ret; } static int packed_pack_refs(struct ref_store *ref_store, unsigned int flags) diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 03b7c1de95..61687e408a 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -1,6 +1,15 @@ #ifndef REFS_PACKED_BACKEND_H #define REFS_PACKED_BACKEND_H +/* + * Support for storing references in a `packed-refs` file. + * + * Note that this backend doesn't check for D/F conflicts, because it + * doesn't care about them. But usually it should be wrapped in a + * `files_ref_store` that prevents D/F conflicts from being created, + * even among packed refs. + */ + struct ref_store *packed_ref_store_create(const char *path, unsigned int store_flags); @@ -14,12 +23,4 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) void packed_refs_unlock(struct ref_store *ref_store); int packed_refs_is_locked(struct ref_store *ref_store); -void add_packed_ref(struct ref_store *ref_store, - const char *refname, const struct object_id *oid); - -int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err); - -int repack_without_refs(struct ref_store *ref_store, - struct string_list *refnames, struct strbuf *err); - #endif /* REFS_PACKED_BACKEND_H */ diff --git a/refs/refs-internal.h b/refs/refs-internal.h index b02dc5a7e3..d7d344de73 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -242,6 +242,7 @@ struct ref_transaction { size_t alloc; size_t nr; enum ref_transaction_state state; + void *backend_data; }; /* diff --git a/repository.c b/repository.c index f107af7d76..97c732bd48 100644 --- a/repository.c +++ b/repository.c @@ -40,11 +40,15 @@ static void repo_setup_env(struct repository *repo) repo->different_commondir = find_common_dir(&sb, repo->gitdir, !repo->ignore_env); + free(repo->commondir); repo->commondir = strbuf_detach(&sb, NULL); + free(repo->objectdir); repo->objectdir = git_path_from_env(DB_ENVIRONMENT, repo->commondir, "objects", !repo->ignore_env); + free(repo->graft_file); repo->graft_file = git_path_from_env(GRAFT_ENVIRONMENT, repo->commondir, "info/grafts", !repo->ignore_env); + free(repo->index_file); repo->index_file = git_path_from_env(INDEX_ENVIRONMENT, repo->gitdir, "index", !repo->ignore_env); } @@ -52,16 +56,12 @@ static void repo_setup_env(struct repository *repo) void repo_set_gitdir(struct repository *repo, const char *path) { const char *gitfile = read_gitfile(path); + char *old_gitdir = repo->gitdir; - /* - * NEEDSWORK: Eventually we want to be able to free gitdir and the rest - * of the environment before reinitializing it again, but we have some - * crazy code paths where we try to set gitdir with the current gitdir - * and we don't want to free gitdir before copying the passed in value. - */ repo->gitdir = xstrdup(gitfile ? gitfile : path); - repo_setup_env(repo); + + free(old_gitdir); } /* @@ -258,7 +258,7 @@ static int write_rr(struct string_list *rr, int out_fd) rerere_id_hex(id), rr->items[i].string, 0); - if (write_in_full(out_fd, buf.buf, buf.len) != buf.len) + if (write_in_full(out_fd, buf.buf, buf.len) < 0) die("unable to write rerere record"); strbuf_release(&buf); diff --git a/revision.c b/revision.c index 94a5e98525..f9a90d71d2 100644 --- a/revision.c +++ b/revision.c @@ -20,6 +20,7 @@ #include "cache-tree.h" #include "bisect.h" #include "packfile.h" +#include "worktree.h" volatile show_early_output_fn_t show_early_output; @@ -1132,6 +1133,7 @@ struct all_refs_cb { int warned_bad_reflog; struct rev_info *all_revs; const char *name_for_errormsg; + struct ref_store *refs; }; int ref_excluded(struct string_list *ref_excludes, const char *path) @@ -1168,6 +1170,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, cb->all_revs = revs; cb->all_flags = flags; revs->rev_input_given = 1; + cb->refs = NULL; } void clear_ref_exclusion(struct string_list **ref_excludes_p) @@ -1188,12 +1191,19 @@ void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude) string_list_append(*ref_excludes_p, exclude); } -static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags, - int (*for_each)(const char *, each_ref_fn, void *)) +static void handle_refs(struct ref_store *refs, + struct rev_info *revs, unsigned flags, + int (*for_each)(struct ref_store *, each_ref_fn, void *)) { struct all_refs_cb cb; + + if (!refs) { + /* this could happen with uninitialized submodules */ + return; + } + init_all_refs_cb(&cb, revs, flags); - for_each(submodule, handle_one_ref, &cb); + for_each(refs, handle_one_ref, &cb); } static void handle_one_reflog_commit(struct object_id *oid, void *cb_data) @@ -1229,17 +1239,41 @@ static int handle_one_reflog(const char *path, const struct object_id *oid, struct all_refs_cb *cb = cb_data; cb->warned_bad_reflog = 0; cb->name_for_errormsg = path; - for_each_reflog_ent(path, handle_one_reflog_ent, cb_data); + refs_for_each_reflog_ent(cb->refs, path, + handle_one_reflog_ent, cb_data); return 0; } +static void add_other_reflogs_to_pending(struct all_refs_cb *cb) +{ + struct worktree **worktrees, **p; + + worktrees = get_worktrees(0); + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + + if (wt->is_current) + continue; + + cb->refs = get_worktree_ref_store(wt); + refs_for_each_reflog(cb->refs, + handle_one_reflog, + cb); + } + free_worktrees(worktrees); +} + void add_reflogs_to_pending(struct rev_info *revs, unsigned flags) { struct all_refs_cb cb; cb.all_revs = revs; cb.all_flags = flags; + cb.refs = get_main_ref_store(); for_each_reflog(handle_one_reflog, &cb); + + if (!revs->single_worktree) + add_other_reflogs_to_pending(&cb); } static void add_cache_tree(struct cache_tree *it, struct rev_info *revs, @@ -1263,13 +1297,13 @@ static void add_cache_tree(struct cache_tree *it, struct rev_info *revs, } -void add_index_objects_to_pending(struct rev_info *revs, unsigned flags) +static void do_add_index_objects_to_pending(struct rev_info *revs, + struct index_state *istate) { int i; - read_cache(); - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; struct blob *blob; if (S_ISGITLINK(ce->ce_mode)) @@ -1282,13 +1316,39 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned flags) ce->ce_mode, ce->name); } - if (active_cache_tree) { + if (istate->cache_tree) { struct strbuf path = STRBUF_INIT; - add_cache_tree(active_cache_tree, revs, &path); + add_cache_tree(istate->cache_tree, revs, &path); strbuf_release(&path); } } +void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags) +{ + struct worktree **worktrees, **p; + + read_cache(); + do_add_index_objects_to_pending(revs, &the_index); + + if (revs->single_worktree) + return; + + worktrees = get_worktrees(0); + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + struct index_state istate = { NULL }; + + if (wt->is_current) + continue; /* current index already taken care of */ + + if (read_index_from(&istate, + worktree_git_path(wt, "index")) > 0) + do_add_index_objects_to_pending(revs, &istate); + discard_index(&istate); + } + free_worktrees(worktrees); +} + static int add_parents_only(struct rev_info *revs, const char *arg_, int flags, int exclude_parent) { @@ -2069,23 +2129,25 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, ctx->argc -= n; } -static int for_each_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data, const char *term) { +static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn, + void *cb_data, const char *term) +{ struct strbuf bisect_refs = STRBUF_INIT; int status; strbuf_addf(&bisect_refs, "refs/bisect/%s", term); - status = for_each_fullref_in_submodule(submodule, bisect_refs.buf, fn, cb_data, 0); + status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data, 0); strbuf_release(&bisect_refs); return status; } -static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data) +static int for_each_bad_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { - return for_each_bisect_ref(submodule, fn, cb_data, term_bad); + return for_each_bisect_ref(refs, fn, cb_data, term_bad); } -static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data) +static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { - return for_each_bisect_ref(submodule, fn, cb_data, term_good); + return for_each_bisect_ref(refs, fn, cb_data, term_good); } static int handle_revision_pseudo_opt(const char *submodule, @@ -2094,8 +2156,22 @@ static int handle_revision_pseudo_opt(const char *submodule, { const char *arg = argv[0]; const char *optarg; + struct ref_store *refs; int argcount; + if (submodule) { + /* + * We need some something like get_submodule_worktrees() + * before we can go through all worktrees of a submodule, + * .e.g with adding all HEADs from --all, which is not + * supported right now, so stick to single worktree. + */ + if (!revs->single_worktree) + die("BUG: --single-worktree cannot be used together with submodule"); + refs = get_submodule_ref_store(submodule); + } else + refs = get_main_ref_store(); + /* * NOTE! * @@ -2107,22 +2183,29 @@ static int handle_revision_pseudo_opt(const char *submodule, * register it in the list at the top of handle_revision_opt. */ if (!strcmp(arg, "--all")) { - handle_refs(submodule, revs, *flags, for_each_ref_submodule); - handle_refs(submodule, revs, *flags, head_ref_submodule); + handle_refs(refs, revs, *flags, refs_for_each_ref); + handle_refs(refs, revs, *flags, refs_head_ref); + if (!revs->single_worktree) { + struct all_refs_cb cb; + + init_all_refs_cb(&cb, revs, *flags); + other_head_refs(handle_one_ref, &cb); + } clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--branches")) { - handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule); + handle_refs(refs, revs, *flags, refs_for_each_branch_ref); clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--bisect")) { read_bisect_terms(&term_bad, &term_good); - handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref); - handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref); + handle_refs(refs, revs, *flags, for_each_bad_bisect_ref); + handle_refs(refs, revs, *flags ^ (UNINTERESTING | BOTTOM), + for_each_good_bisect_ref); revs->bisect = 1; } else if (!strcmp(arg, "--tags")) { - handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule); + handle_refs(refs, revs, *flags, refs_for_each_tag_ref); clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--remotes")) { - handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule); + handle_refs(refs, revs, *flags, refs_for_each_remote_ref); clear_ref_exclusion(&revs->ref_excludes); } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { struct all_refs_cb cb; @@ -2169,6 +2252,8 @@ static int handle_revision_pseudo_opt(const char *submodule, return error("invalid argument to --no-walk"); } else if (!strcmp(arg, "--do-walk")) { revs->no_walk = 0; + } else if (!strcmp(arg, "--single-worktree")) { + revs->single_worktree = 1; } else { return 0; } diff --git a/revision.h b/revision.h index bc18487d6f..3a3d3e2cf8 100644 --- a/revision.h +++ b/revision.h @@ -96,6 +96,7 @@ struct rev_info { topo_order:1, simplify_merges:1, simplify_by_decoration:1, + single_worktree:1, tag_objects:1, tree_objects:1, blob_objects:1, diff --git a/send-pack.c b/send-pack.c index 11d6f3d983..b865f662e4 100644 --- a/send-pack.c +++ b/send-pack.c @@ -492,8 +492,11 @@ int send_pack(struct send_pack_args *args, * we were to send it and we're trying to send the refs * atomically, abort the whole operation. */ - if (use_atomic) + if (use_atomic) { + strbuf_release(&req_buf); + strbuf_release(&cap_buf); return atomic_push_failure(args, remote_refs, ref); + } /* Fallthrough for non atomic case. */ default: continue; diff --git a/sequencer.c b/sequencer.c index fcceabb80f..60636ce54b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1565,6 +1565,7 @@ static int save_head(const char *head) static struct lock_file head_lock; struct strbuf buf = STRBUF_INIT; int fd; + ssize_t written; fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0); if (fd < 0) { @@ -1572,7 +1573,9 @@ static int save_head(const char *head) return error_errno(_("could not lock HEAD")); } strbuf_addf(&buf, "%s\n", head); - if (write_in_full(fd, buf.buf, buf.len) < 0) { + written = write_in_full(fd, buf.buf, buf.len); + strbuf_release(&buf); + if (written < 0) { rollback_lock_file(&head_lock); return error_errno(_("could not write to '%s'"), git_path_head_file()); @@ -399,11 +399,6 @@ void setup_work_tree(void) if (getenv(GIT_WORK_TREE_ENVIRONMENT)) setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); - /* - * NEEDSWORK: this call can essentially be set_git_dir(get_git_dir()) - * which can cause some problems when trying to free the old value of - * gitdir. - */ set_git_dir(remove_leading_path(git_dir, work_tree)); initialized = 1; } diff --git a/sha1_file.c b/sha1_file.c index 5f71bbac3e..dd7cbe52ef 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1820,6 +1820,7 @@ int index_path(struct object_id *oid, const char *path, struct stat *st, unsigne { int fd; struct strbuf sb = STRBUF_INIT; + int rc = 0; switch (st->st_mode & S_IFMT) { case S_IFREG: @@ -1836,8 +1837,7 @@ int index_path(struct object_id *oid, const char *path, struct stat *st, unsigne if (!(flags & HASH_WRITE_OBJECT)) hash_sha1_file(sb.buf, sb.len, blob_type, oid->hash); else if (write_sha1_file(sb.buf, sb.len, blob_type, oid->hash)) - return error("%s: failed to insert into database", - path); + rc = error("%s: failed to insert into database", path); strbuf_release(&sb); break; case S_IFDIR: @@ -1845,12 +1845,12 @@ int index_path(struct object_id *oid, const char *path, struct stat *st, unsigne default: return error("%s: unsupported file type", path); } - return 0; + return rc; } int read_pack_header(int fd, struct pack_header *header) { - if (read_in_full(fd, header, sizeof(*header)) < sizeof(*header)) + if (read_in_full(fd, header, sizeof(*header)) != sizeof(*header)) /* "eof before pack header was fully read" */ return PH_ERROR_EOF; diff --git a/sha1dc_git.c b/sha1dc_git.c index 4d32b4f77e..e0cc9d988c 100644 --- a/sha1dc_git.c +++ b/sha1dc_git.c @@ -1,8 +1,19 @@ +#include "cache.h" + +#ifdef DC_SHA1_EXTERNAL /* - * This code is included at the end of sha1dc/sha1.c with the - * SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C macro. + * Same as SHA1DCInit, but with default save_hash=0 */ +void git_SHA1DCInit(SHA1_CTX *ctx) +{ + SHA1DCInit(ctx); + SHA1DCSetSafeHash(ctx, 0); +} +#endif +/* + * Same as SHA1DCFinal, but convert collision attack case into a verbose die(). + */ void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx) { if (!SHA1DCFinal(hash, ctx)) @@ -11,6 +22,9 @@ void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx) sha1_to_hex(hash)); } +/* + * Same as SHA1DCUpdate, but adjust types to match git's usual interface. + */ void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len) { const char *data = vdata; diff --git a/sha1dc_git.h b/sha1dc_git.h index a8a5c1da16..a8c2729278 100644 --- a/sha1dc_git.h +++ b/sha1dc_git.h @@ -1,19 +1,23 @@ -/* - * This code is included at the end of sha1dc/sha1.h with the - * SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H macro. - */ +/* Plumbing with collition-detecting SHA1 code */ -/* - * Same as SHA1DCFinal, but convert collision attack case into a verbose die(). - */ -void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *); +#ifdef DC_SHA1_SUBMODULE +#include "sha1collisiondetection/lib/sha1.h" +#elif defined(DC_SHA1_EXTERNAL) +#include <sha1dc/sha1.h> +#else +#include "sha1dc/sha1.h" +#endif + +#ifdef DC_SHA1_EXTERNAL +void git_SHA1DCInit(SHA1_CTX *); +#else +#define git_SHA1DCInit SHA1DCInit +#endif -/* - * Same as SHA1DCUpdate, but adjust types to match git's usual interface. - */ +void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *); void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len); #define platform_SHA_CTX SHA1_CTX -#define platform_SHA1_Init SHA1DCInit +#define platform_SHA1_Init git_SHA1DCInit #define platform_SHA1_Update git_SHA1DCUpdate #define platform_SHA1_Final git_SHA1DCFinal @@ -286,28 +286,26 @@ int write_shallow_commits(struct strbuf *out, int use_pack_protocol, return write_shallow_commits_1(out, use_pack_protocol, extra, 0); } -static struct tempfile temporary_shallow; - const char *setup_temporary_shallow(const struct oid_array *extra) { + struct tempfile *temp; struct strbuf sb = STRBUF_INIT; - int fd; if (write_shallow_commits(&sb, 0, extra)) { - fd = xmks_tempfile(&temporary_shallow, git_path("shallow_XXXXXX")); + temp = xmks_tempfile(git_path("shallow_XXXXXX")); - if (write_in_full(fd, sb.buf, sb.len) != sb.len) + if (write_in_full(temp->fd, sb.buf, sb.len) < 0 || + close_tempfile_gently(temp) < 0) die_errno("failed to write to %s", - get_tempfile_path(&temporary_shallow)); - close_tempfile(&temporary_shallow); + get_tempfile_path(temp)); strbuf_release(&sb); - return get_tempfile_path(&temporary_shallow); + return get_tempfile_path(temp); } /* * is_repository_shallow() sees empty string as "no shallow * file". */ - return get_tempfile_path(&temporary_shallow); + return ""; } void setup_alternate_shallow(struct lock_file *shallow_lock, @@ -321,7 +319,7 @@ void setup_alternate_shallow(struct lock_file *shallow_lock, LOCK_DIE_ON_ERROR); check_shallow_file_for_update(); if (write_shallow_commits(&sb, 0, extra)) { - if (write_in_full(fd, sb.buf, sb.len) != sb.len) + if (write_in_full(fd, sb.buf, sb.len) < 0) die_errno("failed to write to %s", get_lock_file_path(shallow_lock)); *alternate_shallow_file = get_lock_file_path(shallow_lock); @@ -368,7 +366,7 @@ void prune_shallow(int show_only) LOCK_DIE_ON_ERROR); check_shallow_file_for_update(); if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) { - if (write_in_full(fd, sb.buf, sb.len) != sb.len) + if (write_in_full(fd, sb.buf, sb.len) < 0) die_errno("failed to write to %s", get_lock_file_path(&shallow_lock)); commit_lock_file(&shallow_lock); diff --git a/streaming.c b/streaming.c index 6f1c60f12b..5892b50bd8 100644 --- a/streaming.c +++ b/streaming.c @@ -540,7 +540,7 @@ int stream_blob_to_fd(int fd, const struct object_id *oid, struct stream_filter kept = 0; wrote = write_in_full(fd, buf, readlen); - if (wrote != readlen) + if (wrote < 0) goto close_and_exit; } if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 || diff --git a/sub-process.c b/sub-process.c index 6ccfaaba99..6dde5062be 100644 --- a/sub-process.c +++ b/sub-process.c @@ -184,8 +184,8 @@ static int handshake_capabilities(struct child_process *process, if (supported_capabilities) *supported_capabilities |= capabilities[i].flag; } else { - warning("subprocess '%s' requested unsupported capability '%s'", - process->argv[0], p); + die("subprocess '%s' requested unsupported capability '%s'", + process->argv[0], p); } } diff --git a/submodule.c b/submodule.c index e0da55920d..b12600fc79 100644 --- a/submodule.c +++ b/submodule.c @@ -69,6 +69,13 @@ int is_staging_gitmodules_ok(const struct index_state *istate) return 1; } +static int for_each_remote_ref_submodule(const char *submodule, + each_ref_fn fn, void *cb_data) +{ + return refs_for_each_remote_ref(get_submodule_ref_store(submodule), + fn, cb_data); +} + /* * Try to update the "path" entry in the "submodule.<name>" section of the * .gitmodules file. Return 0 only if a .gitmodules file was found, a section @@ -1644,6 +1651,8 @@ static int find_first_merges(struct object_array *result, const char *path, oid_to_hex(&a->object.oid)); init_revisions(&revs, NULL); rev_opts.submodule = path; + /* FIXME: can't handle linked worktrees in submodules yet */ + revs.single_worktree = path != NULL; setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts); /* save all revisions from the above list that contain b */ diff --git a/t/helper/.gitignore b/t/helper/.gitignore index 721650256e..7c9d28a834 100644 --- a/t/helper/.gitignore +++ b/t/helper/.gitignore @@ -35,3 +35,4 @@ /test-svn-fe /test-urlmatch-normalization /test-wildmatch +/test-write-cache diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c index 59937dc1be..591730adc4 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -69,7 +69,7 @@ int cmd_main(int argc, const char **argv) } fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); - if (fd < 0 || write_in_full(fd, out_buf, out_size) != out_size) { + if (fd < 0 || write_in_full(fd, out_buf, out_size) < 0) { perror(argv[4]); return 1; } diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index 6004c81f0b..1145d51671 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -235,7 +235,8 @@ int cmd_main(int argc, const char **argv) } else if (!strcmp("size", cmd)) { /* print table sizes */ - printf("%u %u\n", map.tablesize, map.size); + printf("%u %u\n", map.tablesize, + hashmap_get_size(&map)); } else if (!strcmp("intern", cmd) && l1) { diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index c34ece48f5..100d50e362 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -404,4 +404,77 @@ test_expect_success 'broken reference blocks indirect create' ' test_cmp expected output.err ' +test_expect_success 'no bogus intermediate values during delete' ' + prefix=refs/slow-transaction && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # Now try to update the reference, but hold the `packed-refs` lock + # for a while to see what happens while the process is blocked: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + { + # Note: the following command is intentionally run in the + # background. We increase the timeout so that `update-ref` + # attempts to acquire the `packed-refs` lock for longer than + # it takes for us to do the check then delete it: + git -c core.packedrefstimeout=3000 update-ref -d $prefix/foo & + } && + pid2=$! && + # Give update-ref plenty of time to get to the point where it tries + # to lock packed-refs: + sleep 1 && + # Make sure that update-ref did not complete despite the lock: + kill -0 $pid2 && + # Verify that the reference still has its old value: + sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) && + case "$sha1" in + $D) + # This is what we hope for; it means that nothing + # user-visible has changed yet. + : ;; + undefined) + # This is not correct; it means the deletion has happened + # already even though update-ref should not have been + # able to acquire the lock yet. + echo "$prefix/foo deleted prematurely" && + break + ;; + $C) + # This value should never be seen. Probably the loose + # reference has been deleted but the packed reference + # is still there: + echo "$prefix/foo incorrectly observed to be C" && + break + ;; + *) + # WTF? + echo "unexpected value observed for $prefix/foo: $sha1" && + break + ;; + esac >out && + rm -f .git/packed-refs.lock && + wait $pid2 && + test_must_be_empty out && + test_must_fail git rev-parse --verify --quiet $prefix/foo +' + +test_expect_success 'delete fails cleanly if packed-refs file is locked' ' + prefix=refs/locked-packed-refs && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # Now try to delete it while the `packed-refs` lock is held: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + test_must_fail git update-ref -d $prefix/foo >out 2>err && + git for-each-ref $prefix >actual && + test_i18ngrep "Unable to create $Q.*packed-refs.lock$Q: File exists" err && + test_cmp unchanged actual +' + test_done diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh index 5df06f3556..8842d0329f 100755 --- a/t/t1407-worktree-ref-store.sh +++ b/t/t1407-worktree-ref-store.sh @@ -49,4 +49,34 @@ test_expect_success 'create_symref(FOO, refs/heads/master)' ' test_cmp expected actual ' +test_expect_success 'for_each_reflog()' ' + echo $_z40 > .git/logs/PSEUDO-MAIN && + mkdir -p .git/logs/refs/bisect && + echo $_z40 > .git/logs/refs/bisect/random && + + echo $_z40 > .git/worktrees/wt/logs/PSEUDO-WT && + mkdir -p .git/worktrees/wt/logs/refs/bisect && + echo $_z40 > .git/worktrees/wt/logs/refs/bisect/wt-random && + + $RWT for-each-reflog | cut -c 42- | sort >actual && + cat >expected <<-\EOF && + HEAD 0x1 + PSEUDO-WT 0x0 + refs/bisect/wt-random 0x0 + refs/heads/master 0x0 + refs/heads/wt-master 0x0 + EOF + test_cmp expected actual && + + $RMAIN for-each-reflog | cut -c 42- | sort >actual && + cat >expected <<-\EOF && + HEAD 0x1 + PSEUDO-MAIN 0x0 + refs/bisect/random 0x0 + refs/heads/master 0x0 + refs/heads/wt-master 0x0 + EOF + test_cmp expected actual +' + test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 133b5842b1..6694c19a1e 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -283,4 +283,41 @@ test_expect_success 'prune: handle alternate object database' ' git -C B prune ' +test_expect_success 'prune: handle index in multiple worktrees' ' + git worktree add second-worktree && + echo "new blob for second-worktree" >second-worktree/blob && + git -C second-worktree add blob && + git prune --expire=now && + git -C second-worktree show :blob >actual && + test_cmp second-worktree/blob actual +' + +test_expect_success 'prune: handle HEAD in multiple worktrees' ' + git worktree add --detach third-worktree && + echo "new blob for third-worktree" >third-worktree/blob && + git -C third-worktree add blob && + git -C third-worktree commit -m "third" && + rm .git/worktrees/third-worktree/index && + test_must_fail git -C third-worktree show :blob && + git prune --expire=now && + git -C third-worktree show HEAD:blob >actual && + test_cmp third-worktree/blob actual +' + +test_expect_success 'prune: handle HEAD reflog in multiple worktrees' ' + git config core.logAllRefUpdates true && + echo "lost blob for third-worktree" >expected && + ( + cd third-worktree && + cat ../expected >blob && + git add blob && + git commit -m "second commit in third" && + git reset --hard HEAD^ + ) && + git prune --expire=now && + SHA1=`git hash-object expected` && + git -C third-worktree show "$SHA1" >actual && + test_cmp expected actual +' + test_done diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index 077eb07e11..321bd37deb 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -65,6 +65,38 @@ test_expect_success 'recursive pull updates working tree' ' test_path_is_file super/sub/merge_strategy.t ' +test_expect_success "submodule.recurse option triggers recursive pull" ' + test_commit -C child merge_strategy_2 && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + git -C super -c submodule.recurse pull --no-rebase && + test_path_is_file super/sub/merge_strategy_2.t +' + +test_expect_success " --[no-]recurse-submodule and submodule.recurse" ' + test_commit -C child merge_strategy_3 && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + git -C super -c submodule.recurse pull --no-recurse-submodules --no-rebase && + test_path_is_missing super/sub/merge_strategy_3.t && + git -C super -c submodule.recurse=false pull --recurse-submodules --no-rebase && + test_path_is_file super/sub/merge_strategy_3.t && + + test_commit -C child merge_strategy_4 && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + git -C super -c submodule.recurse=false pull --no-recurse-submodules --no-rebase && + test_path_is_missing super/sub/merge_strategy_4.t && + git -C super -c submodule.recurse=true pull --recurse-submodules --no-rebase && + test_path_is_file super/sub/merge_strategy_4.t +' + test_expect_success 'recursive rebasing pull' ' # change upstream test_commit -C child rebase_strategy && diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index 534903bbd2..a661408038 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -236,17 +236,31 @@ test_sequence "--bisect" # # -test_expect_success '--bisect can default to good/bad refs' ' +test_expect_success 'set up fake --bisect refs' ' git update-ref refs/bisect/bad c3 && good=$(git rev-parse b1) && git update-ref refs/bisect/good-$good $good && good=$(git rev-parse c1) && - git update-ref refs/bisect/good-$good $good && + git update-ref refs/bisect/good-$good $good +' +test_expect_success 'rev-list --bisect can default to good/bad refs' ' # the only thing between c3 and c1 is c2 git rev-parse c2 >expect && git rev-list --bisect >actual && test_cmp expect actual ' +test_expect_success 'rev-parse --bisect can default to good/bad refs' ' + git rev-parse c3 ^b1 ^c1 >expect && + git rev-parse --bisect >actual && + + # output order depends on the refnames, which in turn depends on + # the exact sha1s. We just want to make sure we have the same set + # of lines in any order. + sort <expect >expect.sorted && + sort <actual >actual.sorted && + test_cmp expect.sorted actual.sorted +' + test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index aa74eb8f0d..dd6dd9df9b 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -198,6 +198,31 @@ test_expect_success 'name-rev with exact tags' ' test_cmp expect actual ' +test_expect_success 'name-rev --all' ' + >expect.unsorted && + for rev in $(git rev-list --all) + do + git name-rev $rev >>expect.unsorted + done && + sort <expect.unsorted >expect && + git name-rev --all >actual.unsorted && + sort <actual.unsorted >actual && + test_cmp expect actual +' + +test_expect_success 'name-rev --stdin' ' + >expect.unsorted && + for rev in $(git rev-list --all) + do + name=$(git name-rev --name-only $rev) && + echo "$rev ($name)" >>expect.unsorted + done && + sort <expect.unsorted >expect && + git rev-list --all | git name-rev --stdin >actual.unsorted && + sort <actual.unsorted >actual && + test_cmp expect actual +' + test_expect_success 'describe --contains with the exact tags' ' echo "A^0" >expect && tag_object=$(git rev-parse refs/tags/A) && @@ -250,7 +275,39 @@ test_expect_success 'describe chokes on severely broken submodules' ' ' test_expect_success 'describe ignoring a borken submodule' ' git describe --broken >out && + test_when_finished "mv .git/modules/sub_moved .git/modules/sub1" && grep broken out ' +# we require ulimit, this excludes Windows +test_expect_failure ULIMIT_STACK_SIZE 'name-rev works in a deep repo' ' + i=1 && + while test $i -lt 8000 + do + echo "commit refs/heads/master +committer A U Thor <author@example.com> $((1000000000 + $i * 100)) +0200 +data <<EOF +commit #$i +EOF" + test $i = 1 && echo "from refs/heads/master^0" + i=$(($i + 1)) + done | git fast-import && + git checkout master && + git tag far-far-away HEAD^ && + echo "HEAD~4000 tags/far-far-away~3999" >expect && + git name-rev HEAD~4000 >actual && + test_cmp expect actual && + run_with_limited_stack git name-rev HEAD~4000 >actual && + test_cmp expect actual +' + +test_expect_success ULIMIT_STACK_SIZE 'describe works in a deep repo' ' + git tag -f far-far-away HEAD~7999 && + echo "far-far-away" >expect && + git describe --tags --abbrev=0 HEAD~4000 >actual && + test_cmp expect actual && + run_with_limited_stack git describe --tags --abbrev=0 HEAD~4000 >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index dbcd6f623c..5bf5ace56b 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1863,12 +1863,6 @@ test_expect_success 'version sort with very long prerelease suffix' ' git tag -l --sort=version:refname ' -run_with_limited_stack () { - (ulimit -s 128 && "$@") -} - -test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true' - # we require ulimit, this excludes Windows test_expect_success ULIMIT_STACK_SIZE '--contains and --no-contains work in a deep repo' ' >expect && diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh index 88d4cda299..302a3a2082 100755 --- a/t/t7504-commit-msg-hook.sh +++ b/t/t7504-commit-msg-hook.sh @@ -101,6 +101,10 @@ cat > "$HOOK" <<EOF exit 1 EOF +commit_msg_is () { + test "$(git log --pretty=format:%s%b -1)" = "$1" +} + test_expect_success 'with failing hook' ' echo "another" >> file && @@ -135,6 +139,32 @@ test_expect_success '--no-verify with failing hook (editor)' ' ' +test_expect_success 'merge fails with failing hook' ' + + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout --orphan newbranch && + : >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + test_must_fail git merge --allow-unrelated-histories master && + commit_msg_is "in-side-branch" # HEAD before merge + +' + +test_expect_success 'merge bypasses failing hook with --no-verify' ' + + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout --orphan newbranch && + : >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + git merge --no-verify --allow-unrelated-histories master && + commit_msg_is "Merge branch '\''master'\'' into newbranch" +' + + chmod -x "$HOOK" test_expect_success POSIXPERM 'with non-executable hook' ' @@ -178,10 +208,6 @@ exit 0 EOF chmod +x "$HOOK" -commit_msg_is () { - test "$(git log --pretty=format:%s%b -1)" = "$1" -} - test_expect_success 'hook edits commit message' ' echo "additional" >> file && @@ -217,7 +243,36 @@ test_expect_success "hook doesn't edit commit message (editor)" ' echo "more plus" > FAKE_MSG && GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit --no-verify && commit_msg_is "more plus" +' +test_expect_success 'hook called in git-merge picks up commit message' ' + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout --orphan newbranch && + : >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + git merge --allow-unrelated-histories master && + commit_msg_is "new message" +' + +test_expect_failure 'merge --continue remembers --no-verify' ' + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout master && + echo a >file2 && + git add file2 && + git commit --no-verify -m "add file2 to master" && + git checkout -b newbranch master^ && + echo b >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + git merge --no-verify -m not-rewritten-by-hook master && + # resolve conflict: + echo c >file2 && + git add file2 && + git merge --continue && + commit_msg_is not-rewritten-by-hook ' # set up fake editor to replace `pick` by `reword` @@ -237,4 +292,5 @@ test_expect_success 'hook is called for reword during `rebase -i`' ' ' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 5fbd8d4a90..83f5d3dd21 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -44,6 +44,11 @@ GIT_BUILD_DIR="$TEST_DIRECTORY"/.. : ${ASAN_OPTIONS=detect_leaks=0:abort_on_error=1} export ASAN_OPTIONS +# If LSAN is in effect we _do_ want leak checking, but we still +# want to abort so that we notice the problems. +: ${LSAN_OPTIONS=abort_on_error=1} +export LSAN_OPTIONS + ################################################################ # It appears that people try to run tests without building... "$GIT_BUILD_DIR/git" >/dev/null @@ -274,7 +279,7 @@ then test -z "$verbose" && verbose_only="$valgrind_only" elif test -n "$valgrind" then - verbose=t + test -z "$verbose_log" && verbose=t fi if test -n "$color" @@ -1167,6 +1172,12 @@ run_with_limited_cmdline () { test_lazy_prereq CMDLINE_LIMIT 'run_with_limited_cmdline true' +run_with_limited_stack () { + (ulimit -s 128 && "$@") +} + +test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true' + build_option () { git version --build-options | sed -ne "s/^$1: //p" diff --git a/tempfile.c b/tempfile.c index 6843710670..5fdafdd2d2 100644 --- a/tempfile.c +++ b/tempfile.c @@ -30,21 +30,19 @@ * `fdopen_tempfile()` has been called on the object * - `owner` holds the PID of the process that created the file * - * - Active, file closed (after successful `close_tempfile()`). Same + * - Active, file closed (after `close_tempfile_gently()`). Same * as the previous state, except that the temporary file is closed, * `fd` is -1, and `fp` is `NULL`. * - * - Inactive (after `delete_tempfile()`, `rename_tempfile()`, a - * failed attempt to create a temporary file, or a failed - * `close_tempfile()`). In this state: + * - Inactive (after `delete_tempfile()`, `rename_tempfile()`, or a + * failed attempt to create a temporary file). In this state: * * - `active` is unset * - `filename` is empty (usually, though there are transitory * states in which this condition doesn't hold). Client code should * *not* rely on the filename being empty in this state. * - `fd` is -1 and `fp` is `NULL` - * - the object is left registered in the `tempfile_list`, and - * `on_list` is set. + * - the object is removed from `tempfile_list` (but could be used again) * * A temporary file is owned by the process that created it. The * `tempfile` has an `owner` field that records the owner's PID. This @@ -56,20 +54,28 @@ #include "tempfile.h" #include "sigchain.h" -static struct tempfile *volatile tempfile_list; +static VOLATILE_LIST_HEAD(tempfile_list); -static void remove_tempfiles(int skip_fclose) +static void remove_tempfiles(int in_signal_handler) { pid_t me = getpid(); + volatile struct volatile_list_head *pos; - while (tempfile_list) { - if (tempfile_list->owner == me) { - /* fclose() is not safe to call in a signal handler */ - if (skip_fclose) - tempfile_list->fp = NULL; - delete_tempfile(tempfile_list); - } - tempfile_list = tempfile_list->next; + list_for_each(pos, &tempfile_list) { + struct tempfile *p = list_entry(pos, struct tempfile, list); + + if (!is_tempfile_active(p) || p->owner != me) + continue; + + if (p->fd >= 0) + close(p->fd); + + if (in_signal_handler) + unlink(p->filename.buf); + else + unlink_or_warn(p->filename.buf); + + p->active = 0; } } @@ -85,39 +91,48 @@ static void remove_tempfiles_on_signal(int signo) raise(signo); } -/* - * Initialize *tempfile if necessary and add it to tempfile_list. - */ -static void prepare_tempfile_object(struct tempfile *tempfile) +static struct tempfile *new_tempfile(void) { - if (!tempfile_list) { - /* One-time initialization */ + struct tempfile *tempfile = xmalloc(sizeof(*tempfile)); + tempfile->fd = -1; + tempfile->fp = NULL; + tempfile->active = 0; + tempfile->owner = 0; + INIT_LIST_HEAD(&tempfile->list); + strbuf_init(&tempfile->filename, 0); + return tempfile; +} + +static void activate_tempfile(struct tempfile *tempfile) +{ + static int initialized; + + if (is_tempfile_active(tempfile)) + BUG("activate_tempfile called for active object"); + + if (!initialized) { sigchain_push_common(remove_tempfiles_on_signal); atexit(remove_tempfiles_on_exit); + initialized = 1; } - if (tempfile->active) - die("BUG: prepare_tempfile_object called for active object"); - if (!tempfile->on_list) { - /* Initialize *tempfile and add it to tempfile_list: */ - tempfile->fd = -1; - tempfile->fp = NULL; - tempfile->active = 0; - tempfile->owner = 0; - strbuf_init(&tempfile->filename, 0); - tempfile->next = tempfile_list; - tempfile_list = tempfile; - tempfile->on_list = 1; - } else if (tempfile->filename.len) { - /* This shouldn't happen, but better safe than sorry. */ - die("BUG: prepare_tempfile_object called for improperly-reset object"); - } + volatile_list_add(&tempfile->list, &tempfile_list); + tempfile->owner = getpid(); + tempfile->active = 1; +} + +static void deactivate_tempfile(struct tempfile *tempfile) +{ + tempfile->active = 0; + strbuf_release(&tempfile->filename); + volatile_list_del(&tempfile->list); + free(tempfile); } /* Make sure errno contains a meaningful value on error */ -int create_tempfile(struct tempfile *tempfile, const char *path) +struct tempfile *create_tempfile(const char *path) { - prepare_tempfile_object(tempfile); + struct tempfile *tempfile = new_tempfile(); strbuf_add_absolute_path(&tempfile->filename, path); tempfile->fd = open(tempfile->filename.buf, @@ -127,52 +142,48 @@ int create_tempfile(struct tempfile *tempfile, const char *path) tempfile->fd = open(tempfile->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); if (tempfile->fd < 0) { - strbuf_reset(&tempfile->filename); - return -1; + deactivate_tempfile(tempfile); + return NULL; } - tempfile->owner = getpid(); - tempfile->active = 1; + activate_tempfile(tempfile); if (adjust_shared_perm(tempfile->filename.buf)) { int save_errno = errno; error("cannot fix permission bits on %s", tempfile->filename.buf); - delete_tempfile(tempfile); + delete_tempfile(&tempfile); errno = save_errno; - return -1; + return NULL; } - return tempfile->fd; + + return tempfile; } -void register_tempfile(struct tempfile *tempfile, const char *path) +struct tempfile *register_tempfile(const char *path) { - prepare_tempfile_object(tempfile); + struct tempfile *tempfile = new_tempfile(); strbuf_add_absolute_path(&tempfile->filename, path); - tempfile->owner = getpid(); - tempfile->active = 1; + activate_tempfile(tempfile); + return tempfile; } -int mks_tempfile_sm(struct tempfile *tempfile, - const char *template, int suffixlen, int mode) +struct tempfile *mks_tempfile_sm(const char *template, int suffixlen, int mode) { - prepare_tempfile_object(tempfile); + struct tempfile *tempfile = new_tempfile(); strbuf_add_absolute_path(&tempfile->filename, template); tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode); if (tempfile->fd < 0) { - strbuf_reset(&tempfile->filename); - return -1; + deactivate_tempfile(tempfile); + return NULL; } - tempfile->owner = getpid(); - tempfile->active = 1; - return tempfile->fd; + activate_tempfile(tempfile); + return tempfile; } -int mks_tempfile_tsm(struct tempfile *tempfile, - const char *template, int suffixlen, int mode) +struct tempfile *mks_tempfile_tsm(const char *template, int suffixlen, int mode) { + struct tempfile *tempfile = new_tempfile(); const char *tmpdir; - prepare_tempfile_object(tempfile); - tmpdir = getenv("TMPDIR"); if (!tmpdir) tmpdir = "/tmp"; @@ -180,35 +191,34 @@ int mks_tempfile_tsm(struct tempfile *tempfile, strbuf_addf(&tempfile->filename, "%s/%s", tmpdir, template); tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode); if (tempfile->fd < 0) { - strbuf_reset(&tempfile->filename); - return -1; + deactivate_tempfile(tempfile); + return NULL; } - tempfile->owner = getpid(); - tempfile->active = 1; - return tempfile->fd; + activate_tempfile(tempfile); + return tempfile; } -int xmks_tempfile_m(struct tempfile *tempfile, const char *template, int mode) +struct tempfile *xmks_tempfile_m(const char *template, int mode) { - int fd; + struct tempfile *tempfile; struct strbuf full_template = STRBUF_INIT; strbuf_add_absolute_path(&full_template, template); - fd = mks_tempfile_m(tempfile, full_template.buf, mode); - if (fd < 0) + tempfile = mks_tempfile_m(full_template.buf, mode); + if (!tempfile) die_errno("Unable to create temporary file '%s'", full_template.buf); strbuf_release(&full_template); - return fd; + return tempfile; } FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode) { - if (!tempfile->active) - die("BUG: fdopen_tempfile() called for inactive object"); + if (!is_tempfile_active(tempfile)) + BUG("fdopen_tempfile() called for inactive object"); if (tempfile->fp) - die("BUG: fdopen_tempfile() called for open object"); + BUG("fdopen_tempfile() called for open object"); tempfile->fp = fdopen(tempfile->fd, mode); return tempfile->fp; @@ -216,34 +226,36 @@ FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode) const char *get_tempfile_path(struct tempfile *tempfile) { - if (!tempfile->active) - die("BUG: get_tempfile_path() called for inactive object"); + if (!is_tempfile_active(tempfile)) + BUG("get_tempfile_path() called for inactive object"); return tempfile->filename.buf; } int get_tempfile_fd(struct tempfile *tempfile) { - if (!tempfile->active) - die("BUG: get_tempfile_fd() called for inactive object"); + if (!is_tempfile_active(tempfile)) + BUG("get_tempfile_fd() called for inactive object"); return tempfile->fd; } FILE *get_tempfile_fp(struct tempfile *tempfile) { - if (!tempfile->active) - die("BUG: get_tempfile_fp() called for inactive object"); + if (!is_tempfile_active(tempfile)) + BUG("get_tempfile_fp() called for inactive object"); return tempfile->fp; } -int close_tempfile(struct tempfile *tempfile) +int close_tempfile_gently(struct tempfile *tempfile) { - int fd = tempfile->fd; - FILE *fp = tempfile->fp; + int fd; + FILE *fp; int err; - if (fd < 0) + if (!is_tempfile_active(tempfile) || tempfile->fd < 0) return 0; + fd = tempfile->fd; + fp = tempfile->fp; tempfile->fd = -1; if (fp) { tempfile->fp = NULL; @@ -258,54 +270,52 @@ int close_tempfile(struct tempfile *tempfile) err = close(fd); } - if (err) { - int save_errno = errno; - delete_tempfile(tempfile); - errno = save_errno; - return -1; - } - - return 0; + return err ? -1 : 0; } int reopen_tempfile(struct tempfile *tempfile) { + if (!is_tempfile_active(tempfile)) + BUG("reopen_tempfile called for an inactive object"); if (0 <= tempfile->fd) - die("BUG: reopen_tempfile called for an open object"); - if (!tempfile->active) - die("BUG: reopen_tempfile called for an inactive object"); + BUG("reopen_tempfile called for an open object"); tempfile->fd = open(tempfile->filename.buf, O_WRONLY); return tempfile->fd; } -int rename_tempfile(struct tempfile *tempfile, const char *path) +int rename_tempfile(struct tempfile **tempfile_p, const char *path) { - if (!tempfile->active) - die("BUG: rename_tempfile called for inactive object"); + struct tempfile *tempfile = *tempfile_p; + + if (!is_tempfile_active(tempfile)) + BUG("rename_tempfile called for inactive object"); - if (close_tempfile(tempfile)) + if (close_tempfile_gently(tempfile)) { + delete_tempfile(tempfile_p); return -1; + } if (rename(tempfile->filename.buf, path)) { int save_errno = errno; - delete_tempfile(tempfile); + delete_tempfile(tempfile_p); errno = save_errno; return -1; } - tempfile->active = 0; - strbuf_reset(&tempfile->filename); + deactivate_tempfile(tempfile); + *tempfile_p = NULL; return 0; } -void delete_tempfile(struct tempfile *tempfile) +void delete_tempfile(struct tempfile **tempfile_p) { - if (!tempfile->active) + struct tempfile *tempfile = *tempfile_p; + + if (!is_tempfile_active(tempfile)) return; - if (!close_tempfile(tempfile)) { - unlink_or_warn(tempfile->filename.buf); - tempfile->active = 0; - strbuf_reset(&tempfile->filename); - } + close_tempfile_gently(tempfile); + unlink_or_warn(tempfile->filename.buf); + deactivate_tempfile(tempfile); + *tempfile_p = NULL; } diff --git a/tempfile.h b/tempfile.h index 2f0038decd..b8f4b5e145 100644 --- a/tempfile.h +++ b/tempfile.h @@ -1,6 +1,8 @@ #ifndef TEMPFILE_H #define TEMPFILE_H +#include "list.h" + /* * Handle temporary files. * @@ -15,25 +17,18 @@ * * The caller: * - * * Allocates a `struct tempfile` either as a static variable or on - * the heap, initialized to zeros. Once you use the structure to - * call `create_tempfile()`, it belongs to the tempfile subsystem - * and its storage must remain valid throughout the life of the - * program (i.e. you cannot use an on-stack variable to hold this - * structure). - * * * Attempts to create a temporary file by calling - * `create_tempfile()`. + * `create_tempfile()`. The resources used for the temporary file are + * managed by the tempfile API. * * * Writes new content to the file by either: * - * * writing to the file descriptor returned by `create_tempfile()` - * (also available via `tempfile->fd`). + * * writing to the `tempfile->fd` file descriptor * * * calling `fdopen_tempfile()` to get a `FILE` pointer for the * open file and writing to the file using stdio. * - * Note that the file descriptor returned by create_tempfile() + * Note that the file descriptor created by create_tempfile() * is marked O_CLOEXEC, so the new contents must be written by * the current process, not any spawned one. * @@ -47,19 +42,18 @@ * control of the file. * * * Close the file descriptor without removing or renaming the - * temporary file by calling `close_tempfile()`, and later call + * temporary file by calling `close_tempfile_gently()`, and later call * `delete_tempfile()` or `rename_tempfile()`. * - * Even after the temporary file is renamed or deleted, the `tempfile` - * object must not be freed or altered by the caller. However, it may - * be reused; just pass it to another call of `create_tempfile()`. + * After the temporary file is renamed or deleted, the `tempfile` + * object is no longer valid and should not be reused. * * If the program exits before `rename_tempfile()` or * `delete_tempfile()` is called, an `atexit(3)` handler will close * and remove the temporary file. * * If you need to close the file descriptor yourself, do so by calling - * `close_tempfile()`. You should never call `close(2)` or `fclose(3)` + * `close_tempfile_gently()`. You should never call `close(2)` or `fclose(3)` * yourself, otherwise the `struct tempfile` structure would still * think that the file descriptor needs to be closed, and a later * cleanup would result in duplicate calls to `close(2)`. Worse yet, @@ -71,30 +65,30 @@ * Error handling * -------------- * - * `create_tempfile()` returns a file descriptor on success or -1 on - * failure. On errors, `errno` describes the reason for failure. + * `create_tempfile()` returns an allocated tempfile on success or NULL + * on failure. On errors, `errno` describes the reason for failure. * - * `delete_tempfile()`, `rename_tempfile()`, and `close_tempfile()` - * return 0 on success. On failure they set `errno` appropriately, do - * their best to delete the temporary file, and return -1. + * `delete_tempfile()`, `rename_tempfile()`, and `close_tempfile_gently()` + * return 0 on success. On failure they set `errno` appropriately and return + * -1. `delete` and `rename` (but not `close`) do their best to delete the + * temporary file before returning. */ struct tempfile { - struct tempfile *volatile next; + volatile struct volatile_list_head list; volatile sig_atomic_t active; volatile int fd; FILE *volatile fp; volatile pid_t owner; - char on_list; struct strbuf filename; }; /* * Attempt to create a temporary file at the specified `path`. Return - * a file descriptor for writing to it, or -1 on error. It is an error - * if a file already exists at that path. + * a tempfile (whose "fd" member can be used for writing to it), or + * NULL on error. It is an error if a file already exists at that path. */ -extern int create_tempfile(struct tempfile *tempfile, const char *path); +extern struct tempfile *create_tempfile(const char *path); /* * Register an existing file as a tempfile, meaning that it will be @@ -102,7 +96,7 @@ extern int create_tempfile(struct tempfile *tempfile, const char *path); * but it can be worked with like any other closed tempfile (for * example, it can be opened using reopen_tempfile()). */ -extern void register_tempfile(struct tempfile *tempfile, const char *path); +extern struct tempfile *register_tempfile(const char *path); /* @@ -134,83 +128,78 @@ extern void register_tempfile(struct tempfile *tempfile, const char *path); * know the (absolute) path of the file that was created, it can be * read from tempfile->filename. * - * On success, the functions return a file descriptor that is open for - * writing the temporary file. On errors, they return -1 and set errno - * appropriately (except for the "x" variants, which die() on errors). + * On success, the functions return a tempfile whose "fd" member is open + * for writing the temporary file. On errors, they return NULL and set + * errno appropriately (except for the "x" variants, which die() on + * errors). */ /* See "mks_tempfile functions" above. */ -extern int mks_tempfile_sm(struct tempfile *tempfile, - const char *template, int suffixlen, int mode); +extern struct tempfile *mks_tempfile_sm(const char *template, + int suffixlen, int mode); /* See "mks_tempfile functions" above. */ -static inline int mks_tempfile_s(struct tempfile *tempfile, - const char *template, int suffixlen) +static inline struct tempfile *mks_tempfile_s(const char *template, + int suffixlen) { - return mks_tempfile_sm(tempfile, template, suffixlen, 0600); + return mks_tempfile_sm(template, suffixlen, 0600); } /* See "mks_tempfile functions" above. */ -static inline int mks_tempfile_m(struct tempfile *tempfile, - const char *template, int mode) +static inline struct tempfile *mks_tempfile_m(const char *template, int mode) { - return mks_tempfile_sm(tempfile, template, 0, mode); + return mks_tempfile_sm(template, 0, mode); } /* See "mks_tempfile functions" above. */ -static inline int mks_tempfile(struct tempfile *tempfile, - const char *template) +static inline struct tempfile *mks_tempfile(const char *template) { - return mks_tempfile_sm(tempfile, template, 0, 0600); + return mks_tempfile_sm(template, 0, 0600); } /* See "mks_tempfile functions" above. */ -extern int mks_tempfile_tsm(struct tempfile *tempfile, - const char *template, int suffixlen, int mode); +extern struct tempfile *mks_tempfile_tsm(const char *template, + int suffixlen, int mode); /* See "mks_tempfile functions" above. */ -static inline int mks_tempfile_ts(struct tempfile *tempfile, - const char *template, int suffixlen) +static inline struct tempfile *mks_tempfile_ts(const char *template, + int suffixlen) { - return mks_tempfile_tsm(tempfile, template, suffixlen, 0600); + return mks_tempfile_tsm(template, suffixlen, 0600); } /* See "mks_tempfile functions" above. */ -static inline int mks_tempfile_tm(struct tempfile *tempfile, - const char *template, int mode) +static inline struct tempfile *mks_tempfile_tm(const char *template, int mode) { - return mks_tempfile_tsm(tempfile, template, 0, mode); + return mks_tempfile_tsm(template, 0, mode); } /* See "mks_tempfile functions" above. */ -static inline int mks_tempfile_t(struct tempfile *tempfile, - const char *template) +static inline struct tempfile *mks_tempfile_t(const char *template) { - return mks_tempfile_tsm(tempfile, template, 0, 0600); + return mks_tempfile_tsm(template, 0, 0600); } /* See "mks_tempfile functions" above. */ -extern int xmks_tempfile_m(struct tempfile *tempfile, - const char *template, int mode); +extern struct tempfile *xmks_tempfile_m(const char *template, int mode); /* See "mks_tempfile functions" above. */ -static inline int xmks_tempfile(struct tempfile *tempfile, - const char *template) +static inline struct tempfile *xmks_tempfile(const char *template) { - return xmks_tempfile_m(tempfile, template, 0600); + return xmks_tempfile_m(template, 0600); } /* * Associate a stdio stream with the temporary file (which must still * be open). Return `NULL` (*without* deleting the file) on error. The - * stream is closed automatically when `close_tempfile()` is called or + * stream is closed automatically when `close_tempfile_gently()` is called or * when the file is deleted or renamed. */ extern FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode); static inline int is_tempfile_active(struct tempfile *tempfile) { - return tempfile->active; + return tempfile && tempfile->active; } /* @@ -226,20 +215,20 @@ extern FILE *get_tempfile_fp(struct tempfile *tempfile); * If the temporary file is still open, close it (and the file pointer * too, if it has been opened using `fdopen_tempfile()`) without * deleting the file. Return 0 upon success. On failure to `close(2)`, - * return a negative value and delete the file. Usually - * `delete_tempfile()` or `rename_tempfile()` should eventually be - * called if `close_tempfile()` succeeds. + * return a negative value. Usually `delete_tempfile()` or `rename_tempfile()` + * should eventually be called regardless of whether `close_tempfile_gently()` + * succeeds. */ -extern int close_tempfile(struct tempfile *tempfile); +extern int close_tempfile_gently(struct tempfile *tempfile); /* * Re-open a temporary file that has been closed using - * `close_tempfile()` but not yet deleted or renamed. This can be used + * `close_tempfile_gently()` but not yet deleted or renamed. This can be used * to implement a sequence of operations like the following: * * * Create temporary file. * - * * Write new contents to file, then `close_tempfile()` to cause the + * * Write new contents to file, then `close_tempfile_gently()` to cause the * contents to be written to disk. * * * Pass the name of the temporary file to another program to allow @@ -259,7 +248,7 @@ extern int reopen_tempfile(struct tempfile *tempfile); * `delete_tempfile()` for a `tempfile` object that has already been * deleted or renamed. */ -extern void delete_tempfile(struct tempfile *tempfile); +extern void delete_tempfile(struct tempfile **tempfile_p); /* * Close the file descriptor and/or file pointer if they are still @@ -270,6 +259,6 @@ extern void delete_tempfile(struct tempfile *tempfile); * `rename(2)`. It is a bug to call `rename_tempfile()` for a * `tempfile` object that is not currently active. */ -extern int rename_tempfile(struct tempfile *tempfile, const char *path); +extern int rename_tempfile(struct tempfile **tempfile_p, const char *path); #endif /* TEMPFILE_H */ @@ -995,7 +995,7 @@ static void free_all(struct list_head *head) } } -static struct tempfile trailers_tempfile; +static struct tempfile *trailers_tempfile; static FILE *create_in_place_tempfile(const char *file) { @@ -1017,9 +1017,9 @@ static FILE *create_in_place_tempfile(const char *file) strbuf_add(&template, file, tail - file + 1); strbuf_addstr(&template, "git-interpret-trailers-XXXXXX"); - xmks_tempfile_m(&trailers_tempfile, template.buf, st.st_mode); + trailers_tempfile = xmks_tempfile_m(template.buf, st.st_mode); strbuf_release(&template); - outfile = fdopen_tempfile(&trailers_tempfile, "w"); + outfile = fdopen_tempfile(trailers_tempfile, "w"); if (!outfile) die_errno(_("could not open temporary file")); diff --git a/transport-helper.c b/transport-helper.c index f50b34df2d..c948d5215c 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -44,8 +44,7 @@ static void sendline(struct helper_data *helper, struct strbuf *buffer) { if (debug) fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf); - if (write_in_full(helper->helper->in, buffer->buf, buffer->len) - != buffer->len) + if (write_in_full(helper->helper->in, buffer->buf, buffer->len) < 0) die_errno("Full write to remote helper failed"); } @@ -74,7 +73,7 @@ static void write_constant(int fd, const char *str) { if (debug) fprintf(stderr, "Debug: Remote helper: -> %s", str); - if (write_in_full(fd, str, strlen(str)) != strlen(str)) + if (write_in_full(fd, str, strlen(str)) < 0) die_errno("Full write to remote helper failed"); } @@ -604,6 +603,7 @@ static int process_connect_service(struct transport *transport, cmdbuf.buf); exit: + strbuf_release(&cmdbuf); fclose(input); return ret; } @@ -241,3 +241,18 @@ NORETURN void BUG(const char *fmt, ...) va_end(ap); } #endif + +#ifdef SUPPRESS_ANNOTATED_LEAKS +void unleak_memory(const void *ptr, size_t len) +{ + static struct suppressed_leak_root { + struct suppressed_leak_root *next; + char data[FLEX_ARRAY]; + } *suppressed_leaks; + struct suppressed_leak_root *root; + + FLEX_ALLOC_MEM(root, data, ptr, len); + root->next = suppressed_leaks; + suppressed_leaks = root; +} +#endif diff --git a/userdiff.c b/userdiff.c index 2c1502f719..6321103ce2 100644 --- a/userdiff.c +++ b/userdiff.c @@ -293,6 +293,7 @@ struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver) strbuf_addf(&name, "textconv/%s", driver->name); notes_cache_init(c, name.buf, driver->textconv); driver->textconv_cache = c; + strbuf_release(&name); } return driver; @@ -381,7 +381,7 @@ void strbuf_utf8_replace(struct strbuf *sb_src, int pos, int width, old = src; n = utf8_width((const char**)&src, NULL); if (!src) /* broken utf-8, do nothing */ - return; + goto out; if (n && w >= pos && w < pos + width) { if (subst) { memcpy(dst, subst, subst_len); @@ -397,6 +397,7 @@ void strbuf_utf8_replace(struct strbuf *sb_src, int pos, int width, } strbuf_setlen(&sb_dst, dst - sb_dst.buf); strbuf_swap(sb_src, &sb_dst); +out: strbuf_release(&sb_dst); } diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c index ec6b350611..08d136b8cc 100644 --- a/vcs-svn/svndump.c +++ b/vcs-svn/svndump.c @@ -318,6 +318,7 @@ static void end_revision(const char *note_ref) strbuf_addf(&mark, ":%"PRIu32, rev_ctx.revision); fast_export_note(mark.buf, "inline"); fast_export_buf_to_data(&rev_ctx.note); + strbuf_release(&mark); } } diff --git a/worktree.c b/worktree.c index c0c5a2b373..8aaeea0377 100644 --- a/worktree.c +++ b/worktree.c @@ -386,3 +386,25 @@ int submodule_uses_worktrees(const char *path) closedir(dir); return ret; } + +int other_head_refs(each_ref_fn fn, void *cb_data) +{ + struct worktree **worktrees, **p; + int ret = 0; + + worktrees = get_worktrees(0); + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + struct ref_store *refs; + + if (wt->is_current) + continue; + + refs = get_worktree_ref_store(wt); + ret = refs_head_ref(refs, fn, cb_data); + if (ret) + break; + } + free_worktrees(worktrees); + return ret; +} diff --git a/worktree.h b/worktree.h index 5ea5e503fb..9276c81ae7 100644 --- a/worktree.h +++ b/worktree.h @@ -1,6 +1,8 @@ #ifndef WORKTREE_H #define WORKTREE_H +#include "refs.h" + struct worktree { char *path; char *id; @@ -70,6 +72,12 @@ extern void free_worktrees(struct worktree **); extern const struct worktree *find_shared_symref(const char *symref, const char *target); +/* + * Similar to head_ref() for all HEADs _except_ one from the current + * worktree, which is covered by head_ref(). + */ +int other_head_refs(each_ref_fn fn, void *cb_data); + int is_worktree_being_rebased(const struct worktree *wt, const char *target); int is_worktree_being_bisected(const struct worktree *wt, const char *target); @@ -652,7 +652,7 @@ int xsnprintf(char *dst, size_t max, const char *fmt, ...) void write_file_buf(const char *path, const char *buf, size_t len) { int fd = xopen(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (write_in_full(fd, buf, len) != len) + if (write_in_full(fd, buf, len) < 0) die_errno(_("could not write to %s"), path); if (close(fd)) die_errno(_("could not close %s"), path); diff --git a/wt-status.c b/wt-status.c index 77c27c5113..ac972acbab 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1026,6 +1026,7 @@ static void wt_longstatus_print_tracking(struct wt_status *s) comment_line_char); else fputs("\n", s->fp); + strbuf_release(&sb); } static int has_unmerged(struct wt_status *s) @@ -1193,6 +1194,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines) string_list_append(lines, line.buf); } fclose(f); + strbuf_release(&line); return 0; } |