--- /dev/null
+# Suppressions for ThreadSanitizer (tsan).
+#
+# This file is used by setting the environment variable TSAN_OPTIONS to, e.g.,
+# "suppressions=$(pwd)/.tsan-suppressions". Observe that relative paths such as
+# ".tsan-suppressions" might not work.
+
+# A static variable is written to racily, but we always write the same value, so
+# in practice it (hopefully!) doesn't matter.
+race:^want_color$
+race:^transfer_debug$
daemon is torn down were flaky. This was fixed by reacting to
ECONNRESET and behaving as if we got an EOF.
+ * Some versions of GnuPG fail to kill gpg-agent it auto-spawned
+ and such a left-over agent can interfere with a test. Work it
+ around by attempting to kill one before starting a new test.
+
+ * "git log --tag=no-such-tag" showed log starting from HEAD, which
+ has been fixed---it now shows nothing.
+
+ * The "tag.pager" configuration variable was useless for those who
+ actually create tag objects, as it interfered with the use of an
+ editor. A new mechanism has been introduced for commands to enable
+ pager depending on what operation is being carried out to fix this,
+ and then "git tag -l" is made to run pager by default.
+
+ * "git push --recurse-submodules $there HEAD:$target" was not
+ propagated down to the submodules, but now it is.
+
+ * Commands like "git rebase" accepted the --rerere-autoupdate option
+ from the command line, but did not always use it. This has been
+ fixed.
+
+ * "git clone --recurse-submodules --quiet" did not pass the quiet
+ option down to submodules.
+
+ * "git am -s" has been taught that some input may end with a trailer
+ block that is not Signed-off-by: and it should refrain from adding
+ an extra blank line before adding a new sign-off in such a case.
+
+ * "git svn" used with "--localtime" option did not compute the tz
+ offset for the timestamp in question and instead always used the
+ current time, which has been corrected.
+
+ * Memory leaks in a few error codepaths have been plugged.
+
+ * bash 4.4 or newer gave a warning on NUL byte in command
+ substitution done in "git stash"; this has been squelched.
+
+ * "git grep -L" and "git grep --quiet -L" reported different exit
+ codes; this has been corrected.
+
+ * When handshake with a subprocess filter notices that the process
+ asked for an unknown capability, Git did not report what program
+ the offending subprocess was running. This has been corrected.
+
+ * "git apply" that is used as a better "patch -p1" failed to apply a
+ taken from a file with CRLF line endings to a file with CRLF line
+ endings. The root cause was because it misused convert_to_git()
+ that tried to do "safe-crlf" processing by looking at the index
+ entry at the same path, which is a nonsense---in that mode, "apply"
+ is not working on the data in (or derived from) the index at all.
+ This has been fixed.
+
+ * Killing "git merge --edit" before the editor returns control left
+ the repository in a state with MERGE_MSG but without MERGE_HEAD,
+ which incorrectly tells the subsequent "git commit" that there was
+ a squash merge in progress. This has been fixed.
+
+ * "git archive" did not work well with pathspecs and the
+ export-ignore attribute.
+
Also contains various documentation updates and code clean-ups.
might still be cases that need to be addressed--bug reports are
greatly appreciated.
+ * "branch --set-upstream" that has been deprecated in Git 1.8 has
+ finally been retired.
+
Updates since v2.14
-------------------
which can make it succeed if the lock holder was holding it during
a read-only operation.
+ * "branch --set-upstream" that has been deprecated in Git 1.8 has
+ finally been retired.
+
+ * The codepath to call external process filter for smudge/clean
+ operation learned to show the progress meter.
+
Performance, Internal Implementation, Development Support etc.
* Some versions of GnuPG fails to kill gpg-agent it auto-spawned
and such a left-over agent can interfere with a test. Work it
around by attempting to kill one before starting a new test.
- (merge 29ff1f8f74 st/lib-gpg-kill-stray-agent later to maint).
* A recently added test for the "credential-cache" helper revealed
that EOF detection done around the time the connection to the cache
* "git log --tag=no-such-tag" showed log starting from HEAD, which
has been fixed---it now shows nothing.
- (merge 5d34d1ac06 jk/rev-list-empty-input later to maint).
* The "tag.pager" configuration variable was useless for those who
actually create tag objects, as it interfered with the use of an
editor. A new mechanism has been introduced for commands to enable
pager depending on what operation is being carried out to fix this,
and then "git tag -l" is made to run pager by default.
- (merge 595d59e2b5 ma/pager-per-subcommand-action later to maint).
* "git push --recurse-submodules $there HEAD:$target" was not
propagated down to the submodules, but now it is.
- (merge c7be7201a7 bw/push-options-recursively-to-submodules later to maint).
* Commands like "git rebase" accepted the --rerere-autoupdate option
from the command line, but did not always use it. This has been
fixed.
- (merge f826fb799e pw/sequence-rerere-autoupdate later to maint).
* "git clone --recurse-submodules --quiet" did not pass the quiet
option down to submodules.
- (merge 03c004c581 bw/clone-recursive-quiet later to maint).
* Test portability fix for OBSD.
- (merge bed67874e2 rs/obsd-getcwd-workaround later to maint).
- (merge 4c7fda8fc1 rs/t4062-obsd later to maint).
* Portability fix for OBSD.
- (merge 29c2eda80b rs/in-obsd-basename-dirname-take-const later to maint).
* "git am -s" has been taught that some input may end with a trailer
block that is not Signed-off-by: and it should refrain from adding
an extra blank line before adding a new sign-off in such a case.
- (merge 735285b403 pw/am-signoff later to maint).
* "git svn" used with "--localtime" option did not compute the tz
offset for the timestamp in question and instead always used the
current time, which has been corrected.
- (merge 1adc4b9a58 ur/svn-local-zone later to maint).
* Memory leak in an error codepath has been plugged.
- (merge 83cd6f9017 rs/fsck-obj-leakfix later to maint).
- (merge 896dca3ab7 rs/unpack-entry-leakfix later to maint).
- (merge 149d8cbb2e rs/win32-syslog-leakfix later to maint).
* "git stash -u" used the contents of the committed version of the
".gitignore" file to decide which paths are ignored, even when the
* bash 4.4 or newer gave a warning on NUL byte in command
substitution done in "git stash"; this has been squelched.
- (merge 5fc92f8828 kd/stash-with-bash-4.4 later to maint).
* "git grep -L" and "git grep --quiet -L" reported different exit
codes; this has been corrected.
- (merge e1f68c66d5 as/grep-quiet-no-match-exit-code-fix later to maint).
* When handshake with a subprocess filter notices that the process
asked for an unknown capability, Git did not report what program
the offending subprocess was running. This has been corrected.
- (merge d3ba566342 cc/subprocess-handshake-missing-capabilities later to maint).
* "git apply" that is used as a better "patch -p1" failed to apply a
taken from a file with CRLF line endings to a file with CRLF line
entry at the same path, which is a nonsense---in that mode, "apply"
is not working on the data in (or derived from) the index at all.
This has been fixed.
- (merge c24f3abace tb/apply-with-crlf later to maint).
* Killing "git merge --edit" before the editor returns control left
the repository in a state with MERGE_MSG but without MERGE_HEAD,
which incorrectly tells the subsequent "git commit" that there was
a squash merge in progress. This has been fixed.
- (merge 9d89b35526 mg/killed-merge later to maint).
+
+ * "git archive" did not work well with pathspecs and the
+ export-ignore attribute.
+
+ * In addition to "cc: <a@dd.re.ss> # cruft", "cc: a@dd.re.ss # cruft"
+ was taught to "git send-email" as a valid way to tell it that it
+ needs to also send a carbon copy to <a@dd.re.ss> in the trailer
+ section.
+ (merge cc90750677 mm/send-email-cc-cruft later to maint).
+
+ * "git branch -M a b" while on a branch that is completely unrelated
+ to either branch a or branch b misbehaved when multiple worktree
+ was in use. This has been fixed.
+ (merge 31824d180d nd/worktree-kill-parse-ref later to maint).
* Other minor doc, test and build updates and code cleanups.
- (merge dff2813391 ab/ref-filter-no-contains later to maint).
(merge f094b89a4d ma/parse-maybe-bool later to maint).
- (merge 974ce8078c mf/no-dashed-subcommands later to maint).
- (merge f81935cc4d jc/perl-git-comment-typofix later to maint).
- (merge 57ea241ef0 rs/t3700-clean-leftover later to maint).
(merge 39b00fa4d4 jk/drop-sha1-entry-pos later to maint).
- (merge 0b006014c8 jk/hashcmp-memcmp later to maint).
- (merge 1e22a9917b rj/add-chmod-error-message later to maint).
- (merge 881529c846 rs/apply-lose-prefix-length later to maint).
- (merge 6355a76802 rs/find-pack-entry-bisection later to maint).
- (merge de3ce210ed rs/merge-microcleanup later to maint).
- (merge 7f0a02be2f ah/doc-empty-string-is-false later to maint).
- (merge 70ec6bd63b rs/t1002-do-not-use-sum later to maint).
- (merge 2456990dfd sb/sha1-file-cleanup later to maint).
- (merge 2aac933c62 hv/t5526-andand-chain-fix later to maint).
- (merge c8d0c4fe9b sb/submodule-parallel-update later to maint).
- (merge 794b7e1674 mg/format-ref-doc-fix later to maint).
- (merge 24da8a26a9 rs/commit-h-single-parent-cleanup later to maint).
- (merge 4e36907fa3 jk/doc-the-this later to maint).
- (merge 15d1d0951e bc/vcs-svn-cleanup later to maint).
- (merge b8f43b120b jn/vcs-svn-cleanup later to maint).
- (merge 4a4becfb23 jt/doc-pack-objects-fix 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).
disables it is in effect), make sure the patch is
applicable to what the current index file records. If
the file to be patched in the working tree is not
- up-to-date, it is flagged as an error. This flag also
+ up to date, it is flagged as an error. This flag also
causes the index file to be updated.
--cached::
If `--index` is specified (explicitly or implicitly), then the submodule
commits must match the index exactly for the patch to apply. If any
of the submodules are checked-out, then these check-outs are completely
-ignored, i.e., they are not required to be up-to-date or clean and they
+ignored, i.e., they are not required to be up to date or clean and they
are not updated.
If `--index` is not specified, then the submodule commits in the patch
branch.autoSetupMerge configuration variable is true.
--set-upstream::
- If specified branch does not exist yet or if `--force` has been
- given, acts exactly like `--track`. Otherwise sets up configuration
- like `--track` would when creating the branch, except that where
- branch points to is not changed.
+ As this option had confusing syntax, it is no longer supported.
+ Please use `--track` or `--set-upstream-to` instead.
-u <upstream>::
--set-upstream-to=<upstream>::
That means that even if you offer only read access (e.g. by using
the pserver method), 'git-cvsserver' should have write access to
the database to work reliably (otherwise you need to make sure
-that the database is up-to-date any time 'git-cvsserver' is executed).
+that the database is up to date any time 'git-cvsserver' is executed).
By default it uses SQLite databases in the Git directory, named
`gitcvs.<module_name>.sqlite`. Note that the SQLite backend creates
The non-cached version asks the question:
show me the differences between HEAD and the currently checked out
- tree - index contents _and_ files that aren't up-to-date
+ tree - index contents _and_ files that aren't up to date
which is obviously a very useful question too, since that tells you what
you *could* commit. Again, the output matches the 'git diff-tree -r'
torvalds@ppc970:~/v2.6/linux> git diff-index --abbrev HEAD
:100644 100664 7476bb... 000000... kernel/sched.c
-i.e., it shows that the tree has changed, and that `kernel/sched.c` has is
-not up-to-date and may contain new stuff. The all-zero sha1 means that to
+i.e., it shows that the tree has changed, and that `kernel/sched.c` is
+not up to date and may contain new stuff. The all-zero sha1 means that to
get the real diff, you need to look at the object in the working directory
directly rather than do an object-to-object diff.
would result from the merge already.)
If all named commits are already ancestors of `HEAD`, 'git merge'
-will exit early with the message "Already up-to-date."
+will exit early with the message "Already up to date."
FAST-FORWARD MERGE
------------------
-f::
--force-rebase::
- Force a rebase even if the current branch is up-to-date and
+ Force a rebase even if the current branch is up to date and
the command without `--force` would return without doing anything.
+
You may find this (or --no-ff with an interactive rebase) helpful after
------------
you could run `git rebase master topic`, to bring yourself
-up-to-date before your topic is ready to be sent upstream.
+up to date before your topic is ready to be sent upstream.
This would result in falling back to a three-way merge, and it
would conflict the same way as the test merge you resolved earlier.
'git rerere' will be run by 'git rebase' to help you resolve this
in the linkgit:gitmodules[5] file will also be removed and that file
will be staged (unless --cached or -n are used).
-A submodule is considered up-to-date when the HEAD is the same as
+A submodule is considered up to date when the HEAD is the same as
recorded in the index, no tracked files are modified and no untracked
files that aren't ignored are present in the submodules work tree.
Ignored files are deemed expendable and won't stop a submodule's work
'set-tree'::
You should consider using 'dcommit' instead of this command.
Commit specified commit or tree objects to SVN. This relies on
- your imported fetch data being up-to-date. This makes
+ your imported fetch data being up to date. This makes
absolutely no attempts to do patching when committing to SVN, it
simply overwrites files with those specified in the tree or
commit. All merging is assumed to have taken place
Using --refresh
---------------
`--refresh` does not calculate a new sha1 file or bring the index
-up-to-date for mode/content changes. But what it *does* do is to
+up to date for mode/content changes. But what it *does* do is to
"re-match" the stat information of a file with the index, so that you
can refresh the index for a file that hasn't been changed but where
the stat entry is out of date.
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"::
$ git update-index --refresh
----------------
+
-in the new repository to make sure that the index file is up-to-date.
+in the new repository to make sure that the index file is up to date.
Note that the second point is true even across machines. You can
duplicate a remote Git repository with *any* regular copy mechanism, be it
----------------
where the `-u` flag means that you want the checkout to keep the index
-up-to-date (so that you don't have to refresh it afterward), and the
+up to date (so that you don't have to refresh it afterward), and the
`-a` flag means "check out all files" (if you have a stale copy or an
older version of a checked out tree you may also need to add the `-f`
flag first, to tell 'git checkout-index' to *force* overwriting of any old
First, you need to create an empty repository on the remote
machine that will house your public repository. This empty
-repository will be populated and be kept up-to-date by pushing
+repository will be populated and be kept up to date by pushing
into it later. Obviously, this repository creation needs to be
done only once.
would contain a call to 'git update-server-info'
but you need to manually enable the hook with
`mv post-update.sample post-update`. This makes sure
-'git update-server-info' keeps the necessary files up-to-date.
+'git update-server-info' keeps the necessary files up to date.
3. Push into the public repository from your primary
repository.
When enabled, the default 'post-update' hook runs
'git update-server-info' to keep the information used by dumb
-transports (e.g., HTTP) up-to-date. If you are publishing
+transports (e.g., HTTP) up to date. If you are publishing
a Git repository that is accessible via HTTP, you should
probably enable this hook.
This file is to help dumb transports discover what packs
are available in this object store. Whenever a pack is
added or removed, `git update-server-info` should be run
- to keep this file up-to-date if the repository is
+ to keep this file up to date if the repository is
published for dumb transports. 'git repack' does this
by default.
$ git status
On branch master
Changes to be committed:
-Your branch is up-to-date with 'origin/master'.
+Your branch is up to date with 'origin/master'.
(use "git reset HEAD <file>..." to unstage)
modified: file1
--ff-only::
Refuse to merge and exit with a non-zero status unless the
- current `HEAD` is already up-to-date or the merge can be
+ current `HEAD` is already up to date or the merge can be
resolved as a fast-forward.
--log[=<n>]::
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.
* `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.
terminate the connection by sending a flush-pkt, telling the server it can
now gracefully terminate, and disconnect, when it does not need any pack
data. This can happen with the ls-remote command, and also can happen when
-the client already is up-to-date.
+the client already is up to date.
Otherwise, it enters the negotiation phase, where the client and
server determine what the minimal packfile necessary for transport is,
If multiple cases apply, the one used is listed first.
A result which changes the index is an error if the index is not empty
-and not up-to-date.
+and not up to date.
Entries marked '+' have stat information. Spaces marked '*' don't
affect the result.
left in stage 0, and there are no other entries.
A result of "no merge" is an error if the index is not empty and not
-up-to-date.
+up to date.
*empty* means that the tree must not have a directory-file conflict
with the entry.
remote branch, then it will fail with an error like:
-------------------------------------------------
-error: remote 'refs/heads/master' is not an ancestor of
- local 'refs/heads/master'.
- Maybe you are not up-to-date and need to pull first?
-error: failed to push to 'ssh://yourserver.com/~you/proj.git'
+ ! [rejected] master -> master (non-fast-forward)
+error: failed to push some refs to '...'
+hint: Updates were rejected because the tip of your current branch is behind
+hint: its remote counterpart. Integrate the remote changes (e.g.
+hint: 'git pull ...') before pushing again.
+hint: See the 'Note about fast-forwards' in 'git push --help' for details.
-------------------------------------------------
This can happen, for example, if you:
Linus's tree will be stored in the remote-tracking branch named origin/master,
and can be updated using linkgit:git-fetch[1]; you can track other
public trees using linkgit:git-remote[1] to set up a "remote" and
-linkgit:git-fetch[1] to keep them up-to-date; see
+linkgit:git-fetch[1] to keep them up to date; see
<<repositories-and-branches>>.
Now create the branches in which you are going to work; these start out
# 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
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
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
* 1970-01-01, and the seconds part must be "00".
*/
const char stamp_regexp[] =
- "^(1969-12-31|1970-01-01)"
- " "
- "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
+ "^[0-2][0-9]:([0-5][0-9]):00(\\.0+)?"
" "
"([-+][0-2][0-9]:?[0-5][0-9])\n";
const char *timestamp = NULL, *cp, *colon;
static regex_t *stamp;
regmatch_t m[10];
- int zoneoffset;
- int hourminute;
+ int zoneoffset, epoch_hour, hour, minute;
int status;
for (cp = nameline; *cp != '\n'; cp++) {
}
if (!timestamp)
return 0;
+
+ /*
+ * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
+ * (west of GMT) or 1970-01-01 (east of GMT)
+ */
+ if (skip_prefix(timestamp, "1969-12-31 ", ×tamp))
+ epoch_hour = 24;
+ else if (skip_prefix(timestamp, "1970-01-01 ", ×tamp))
+ epoch_hour = 0;
+ else
+ return 0;
+
if (!stamp) {
stamp = xmalloc(sizeof(*stamp));
if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
return 0;
}
+ hour = strtol(timestamp, NULL, 10);
+ minute = strtol(timestamp + m[1].rm_so, NULL, 10);
+
zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10);
if (*colon == ':')
zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
if (timestamp[m[3].rm_so] == '-')
zoneoffset = -zoneoffset;
- /*
- * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
- * (west of GMT) or 1970-01-01 (east of GMT)
- */
- if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
- (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
- return 0;
-
- hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
- strtol(timestamp + 14, NULL, 10) -
- zoneoffset);
-
- return ((zoneoffset < 0 && hourminute == 1440) ||
- (0 <= zoneoffset && !hourminute));
+ return hour * 60 + minute - zoneoffset == epoch_hour * 60;
}
/*
struct directory *bottom;
};
+static const struct attr_check *get_archive_attrs(const char *path)
+{
+ static struct attr_check *check;
+ if (!check)
+ check = attr_check_initl("export-ignore", "export-subst", NULL);
+ return git_check_attr(path, check) ? NULL : check;
+}
+
+static int check_attr_export_ignore(const struct attr_check *check)
+{
+ return check && ATTR_TRUE(check->items[0].value);
+}
+
+static int check_attr_export_subst(const struct attr_check *check)
+{
+ return check && ATTR_TRUE(check->items[1].value);
+}
+
+static int should_queue_directories(const struct archiver_args *args)
+{
+ return args->pathspec.has_wildcard;
+}
+
static int write_archive_entry(const unsigned char *sha1, const char *base,
int baselen, const char *filename, unsigned mode, int stage,
void *context)
{
static struct strbuf path = STRBUF_INIT;
- static struct attr_check *check;
struct archiver_context *c = context;
struct archiver_args *args = c->args;
write_archive_entry_fn_t write_entry = c->write_entry;
- const char *path_without_prefix;
int err;
+ const char *path_without_prefix;
args->convert = 0;
strbuf_reset(&path);
strbuf_addch(&path, '/');
path_without_prefix = path.buf + args->baselen;
- if (!check)
- check = attr_check_initl("export-ignore", "export-subst", NULL);
- if (!git_check_attr(path_without_prefix, check)) {
- if (ATTR_TRUE(check->items[0].value))
+ if (!S_ISDIR(mode) || !should_queue_directories(args)) {
+ const struct attr_check *check;
+ check = get_archive_attrs(path_without_prefix);
+ if (check_attr_export_ignore(check))
return 0;
- args->convert = ATTR_TRUE(check->items[1].value);
+ args->convert = check_attr_export_subst(check);
}
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
}
if (S_ISDIR(mode)) {
+ size_t baselen = base->len;
+ const struct attr_check *check;
+
+ /* Borrow base, but restore its original value when done. */
+ strbuf_addstr(base, filename);
+ strbuf_addch(base, '/');
+ check = get_archive_attrs(base->buf);
+ strbuf_setlen(base, baselen);
+
+ if (check_attr_export_ignore(check))
+ return 0;
queue_directory(sha1, base, filename,
mode, stage, c);
return READ_TREE_RECURSIVE;
}
err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
- args->pathspec.has_wildcard ?
+ should_queue_directories(args) ?
queue_or_write_archive_entry :
write_archive_entry_buf,
&context);
if (shortname) {
if (origin)
printf_ln(rebasing ?
- _("Branch %s set up to track remote branch %s from %s by rebasing.") :
- _("Branch %s set up to track remote branch %s from %s."),
+ _("Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.") :
+ _("Branch '%s' set up to track remote branch '%s' from '%s'."),
local, shortname, origin);
else
printf_ln(rebasing ?
- _("Branch %s set up to track local branch %s by rebasing.") :
- _("Branch %s set up to track local branch %s."),
+ _("Branch '%s' set up to track local branch '%s' by rebasing.") :
+ _("Branch '%s' set up to track local branch '%s'."),
local, shortname);
} else {
if (origin)
printf_ln(rebasing ?
- _("Branch %s set up to track remote ref %s by rebasing.") :
- _("Branch %s set up to track remote ref %s."),
+ _("Branch '%s' set up to track remote ref '%s' by rebasing.") :
+ _("Branch '%s' set up to track remote ref '%s'."),
local, remote);
else
printf_ln(rebasing ?
- _("Branch %s set up to track local ref %s by rebasing.") :
- _("Branch %s set up to track local ref %s."),
+ _("Branch '%s' set up to track local ref '%s' by rebasing.") :
+ _("Branch '%s' set up to track local ref '%s'."),
local, remote);
}
}
if (worktrees[i]->is_detached)
continue;
- if (worktrees[i]->head_ref &&
- strcmp(oldref, worktrees[i]->head_ref))
+ if (!worktrees[i]->head_ref)
+ continue;
+ if (strcmp(oldref, worktrees[i]->head_ref))
continue;
refs = get_worktree_ref_store(worktrees[i]);
return -1;
while (!strbuf_getwholeline(&buf, fp, '\n')) {
/* The format is just "Commit Parent1 Parent2 ...\n" */
- struct commit_graft *graft = read_graft_line(buf.buf, buf.len);
+ struct commit_graft *graft = read_graft_line(&buf);
if (graft)
register_commit_graft(graft, 0);
}
OPT__QUIET(&quiet, N_("suppress informational messages")),
OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"),
BRANCH_TRACK_EXPLICIT),
- OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"),
- BRANCH_TRACK_OVERRIDE),
+ { OPTION_SET_INT, 0, "set-upstream", &track, NULL, N_("do not use"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, BRANCH_TRACK_OVERRIDE },
OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
OPT__COLOR(&branch_use_color, N_("use colored output")),
strbuf_release(&buf);
} else if (argc > 0 && argc <= 2) {
struct branch *branch = branch_get(argv[0]);
- int branch_existed = 0, remote_tracking = 0;
- struct strbuf buf = STRBUF_INIT;
if (!strcmp(argv[0], "HEAD"))
die(_("it does not make sense to create 'HEAD' manually"));
die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
if (track == BRANCH_TRACK_OVERRIDE)
- fprintf(stderr, _("The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to\n"));
-
- strbuf_addf(&buf, "refs/remotes/%s", branch->name);
- remote_tracking = ref_exists(buf.buf);
- strbuf_release(&buf);
+ die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
- branch_existed = ref_exists(branch->refname);
create_branch(argv[0], (argc == 2) ? argv[1] : head,
force, reflog, 0, quiet, track);
- /*
- * We only show the instructions if the user gave us
- * one branch which doesn't exist locally, but is the
- * name of a remote-tracking branch.
- */
- if (argc == 1 && track == BRANCH_TRACK_OVERRIDE &&
- !branch_existed && remote_tracking) {
- fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name);
- fprintf(stderr, " git branch -d %s\n", branch->name);
- fprintf(stderr, " git branch --set-upstream-to %s\n", branch->name);
- }
-
} else
usage_with_options(builtin_branch_usage, options);
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;
*/
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);
int fd;
char *pidfile_path;
- if (is_tempfile_active(&pidfile))
+ if (is_tempfile_active(pidfile))
/* already locked */
return NULL;
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;
}
* If head can reach all the merge then we are up to date.
* but first the most common case of merging one remote.
*/
- finish_up_to_date(_("Already up-to-date."));
+ finish_up_to_date(_("Already up to date."));
goto done;
} else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
}
}
if (up_to_date) {
- finish_up_to_date(_("Already up-to-date. Yeeah!"));
+ finish_up_to_date(_("Already up to date. Yeeah!"));
goto done;
}
}
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,
{
struct thread_params *me = arg;
+ progress_lock();
while (me->remaining) {
+ progress_unlock();
+
find_deltas(me->list, &me->remaining,
me->window, me->depth, me->processed);
pthread_cond_wait(&me->cond, &me->mutex);
me->data_ready = 0;
pthread_mutex_unlock(&me->mutex);
+
+ progress_lock();
}
+ progress_unlock();
/* leave ->working 1 so that this doesn't get more work assigned */
return NULL;
}
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))
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."));
}
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;
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;
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;
}
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);
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
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)
int want_color(int var)
{
+ /*
+ * NEEDSWORK: This function is sometimes used from multiple threads, and
+ * we end up using want_auto racily. That "should not matter" since
+ * we always write the same value, but it's still wrong. This function
+ * is listed in .tsan-suppressions for the time being.
+ */
+
static int want_auto = -1;
if (var < 0)
return 0;
}
-struct commit_graft *read_graft_line(char *buf, int len)
+struct commit_graft *read_graft_line(struct strbuf *line)
{
/* The format is just "Commit Parent1 Parent2 ...\n" */
- int i;
+ int i, phase;
+ const char *tail = NULL;
struct commit_graft *graft = NULL;
- const int entry_size = GIT_SHA1_HEXSZ + 1;
+ struct object_id dummy_oid, *oid;
- while (len && isspace(buf[len-1]))
- buf[--len] = '\0';
- if (buf[0] == '#' || buf[0] == '\0')
+ strbuf_rtrim(line);
+ if (!line->len || line->buf[0] == '#')
return NULL;
- if ((len + 1) % entry_size)
- goto bad_graft_data;
- i = (len + 1) / entry_size - 1;
- graft = xmalloc(st_add(sizeof(*graft), st_mult(GIT_SHA1_RAWSZ, i)));
- graft->nr_parent = i;
- if (get_oid_hex(buf, &graft->oid))
- goto bad_graft_data;
- for (i = GIT_SHA1_HEXSZ; i < len; i += entry_size) {
- if (buf[i] != ' ')
- goto bad_graft_data;
- if (get_sha1_hex(buf + i + 1, graft->parent[i/entry_size].hash))
+ /*
+ * phase 0 verifies line, counts hashes in line and allocates graft
+ * phase 1 fills graft
+ */
+ for (phase = 0; phase < 2; phase++) {
+ oid = graft ? &graft->oid : &dummy_oid;
+ if (parse_oid_hex(line->buf, oid, &tail))
goto bad_graft_data;
+ for (i = 0; *tail != '\0'; i++) {
+ oid = graft ? &graft->parent[i] : &dummy_oid;
+ if (!isspace(*tail++) || parse_oid_hex(tail, oid, &tail))
+ goto bad_graft_data;
+ }
+ if (!graft) {
+ graft = xmalloc(st_add(sizeof(*graft),
+ st_mult(sizeof(struct object_id), i)));
+ graft->nr_parent = i;
+ }
}
return graft;
bad_graft_data:
- error("bad graft data: %s", buf);
- free(graft);
+ error("bad graft data: %s", line->buf);
+ assert(!graft);
return NULL;
}
return -1;
while (!strbuf_getwholeline(&buf, fp, '\n')) {
/* The format is just "Commit Parent1 Parent2 ...\n" */
- struct commit_graft *graft = read_graft_line(buf.buf, buf.len);
+ struct commit_graft *graft = read_graft_line(&buf);
if (!graft)
continue;
if (register_commit_graft(graft, 1))
};
typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
-struct commit_graft *read_graft_line(char *buf, int len);
+struct commit_graft *read_graft_line(struct strbuf *line);
int register_commit_graft(struct commit_graft *, int);
struct commit_graft *lookup_commit_graft(const struct object_id *oid);
{
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;
* 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);
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;
}
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);
return ret;
write_err_out:
- ret = write_error(get_lock_file_path(lock));
+ ret = write_error(get_lock_file_path(&lock));
goto out_free;
}
test: all
$(MAKE) -C t
+clean:
+ $(RM) diff-highlight
+
.PHONY: FORCE
?,1,"$1",*)
# If head can reach all the merge then we are up to date.
# but first the most common case of merging one remote.
- finish_up_to_date "Already up-to-date."
+ finish_up_to_date "Already up to date."
exit 0
;;
t,1,"$head",*)
done
if test "$up_to_date" = t
then
- finish_up_to_date "Already up-to-date. Yeeah!"
+ finish_up_to_date "Already up to date. Yeeah!"
exit 0
fi
;;
case "$common" in
"$merge")
- echo "Already up-to-date. Yeeah!"
+ echo "Already up to date. Yeeah!"
dropheads
exit 0
;;
# this shouldn not actually do anything, since FETCH_HEAD
# is already a parent
result=$(git merge -s ours -m "merge -s -ours" FETCH_HEAD) &&
- check_equal "${result}" "Already up-to-date."
+ check_equal "${result}" "Already up to date."
)
'
ca->crlf_action = git_path_check_crlf(ccheck + 4);
if (ca->crlf_action == CRLF_UNDEFINED)
ca->crlf_action = git_path_check_crlf(ccheck + 0);
- ca->attr_action = ca->crlf_action;
ca->ident = git_path_check_ident(ccheck + 1);
ca->drv = git_path_check_convert(ccheck + 2);
if (ca->crlf_action != CRLF_BINARY) {
else if (eol_attr == EOL_CRLF)
ca->crlf_action = CRLF_TEXT_CRLF;
}
- ca->attr_action = ca->crlf_action;
} else {
ca->drv = NULL;
ca->crlf_action = CRLF_UNDEFINED;
ca->ident = 0;
}
+
+ /* Save attr and make a decision for action */
+ ca->attr_action = ca->crlf_action;
if (ca->crlf_action == CRLF_TEXT)
ca->crlf_action = text_eol_is_crlf() ? CRLF_TEXT_CRLF : CRLF_TEXT_INPUT;
if (ca->crlf_action == CRLF_UNDEFINED && auto_crlf == AUTO_CRLF_FALSE)
#include "unix-socket.h"
#include "parse-options.h"
-static struct tempfile socket_file;
-
struct credential_cache_entry {
struct credential item;
timestamp_t expiration;
int cmd_main(int argc, const char **argv)
{
+ struct tempfile *socket_file;
const char *socket_path;
int ignore_sighup = 0;
static const char *usage[] = {
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);
* 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 {
{
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;
}
const struct object_id *oid,
int mode)
{
- int fd;
struct strbuf buf = STRBUF_INIT;
struct strbuf template = STRBUF_INIT;
char *path_dup = xstrdup(path);
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) != size ||
+ 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);
#include "dir.h"
#include "streaming.h"
#include "submodule.h"
+#include "progress.h"
static void create_directories(const char *path, int path_len,
const struct checkout *state)
int finish_delayed_checkout(struct checkout *state)
{
int errs = 0;
+ unsigned delayed_object_count;
+ off_t filtered_bytes = 0;
struct string_list_item *filter, *path;
+ struct progress *progress;
struct delayed_checkout *dco = state->delayed_checkout;
if (!state->delayed_checkout)
return errs;
dco->state = CE_RETRY;
+ delayed_object_count = dco->paths.nr;
+ progress = start_delayed_progress(_("Filtering content"), delayed_object_count);
while (dco->filters.nr > 0) {
for_each_string_list_item(filter, &dco->filters) {
struct string_list available_paths = STRING_LIST_INIT_NODUP;
+ display_progress(progress, delayed_object_count - dco->paths.nr);
if (!async_query_available_blobs(filter->string, &available_paths)) {
/* Filter reported an error */
}
ce = index_file_exists(state->istate, path->string,
strlen(path->string), 0);
- errs |= (ce ? checkout_entry(ce, state, NULL) : 1);
+ if (ce) {
+ errs |= checkout_entry(ce, state, NULL);
+ filtered_bytes += ce->ce_stat_data.sd_size;
+ display_throughput(progress, filtered_bytes);
+ } else
+ errs = 1;
}
}
string_list_remove_empty_items(&dco->filters, 0);
}
+ stop_progress(&progress);
string_list_clear(&dco->filters, 0);
/* At this point we should not have any delayed paths anymore. */
the translation of existing messages, or because the git-gui software
itself was updated and there are new messages that need translation.
-In any case, make sure you are up-to-date before starting your work:
+In any case, make sure you are up to date before starting your work:
$ git checkout master
$ git pull
case "$LF$common$LF" in
*"$LF$SHA1$LF"*)
- eval_gettextln "Already up-to-date with \$pretty_name"
+ eval_gettextln "Already up to date with \$pretty_name"
continue
;;
esac
def rebase(self):
if os.system("git update-index --refresh") != 0:
- die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
+ die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash.");
if len(read_pipe("git diff-index HEAD --")) > 0:
die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
}
my $have_email_valid = eval { require Email::Valid; 1 };
-my $have_mail_address = eval { require Mail::Address; 1 };
my $smtp;
my $auth;
my $num_sent = 0;
($repocommitter) = Git::ident_person(@repo, 'committer');
sub parse_address_line {
- if ($have_mail_address) {
- return map { $_->format } Mail::Address->parse($_[0]);
- } else {
- return Git::parse_mailboxes($_[0]);
- }
+ return Git::parse_mailboxes($_[0]);
}
sub split_addrs {
}
+sub strip_garbage_one_address {
+ my ($addr) = @_;
+ chomp $addr;
+ if ($addr =~ /^(("[^"]*"|[^"<]*)? *<[^>]*>).*/) {
+ # "Foo Bar" <foobar@example.com> [possibly garbage here]
+ # Foo Bar <foobar@example.com> [possibly garbage here]
+ return $1;
+ }
+ if ($addr =~ /^(<[^>]*>).*/) {
+ # <foo@example.com> [possibly garbage here]
+ # if garbage contains other addresses, they are ignored.
+ return $1;
+ }
+ if ($addr =~ /^([^"#,\s]*)/) {
+ # address without quoting: remove anything after the address
+ return $1;
+ }
+ return $addr;
+}
+
sub sanitize_address_list {
return (map { sanitize_address($_) } @_);
}
# Now parse the message body
while(<$fh>) {
$message .= $_;
- if (/^(Signed-off-by|Cc): ([^>]*>?)/i) {
+ if (/^(Signed-off-by|Cc): (.*)/i) {
chomp;
my ($what, $c) = ($1, $2);
- chomp $c;
+ # strip garbage for the address we'll use:
+ $c = strip_garbage_one_address($c);
+ # sanitize a bit more to decide whether to suppress the address:
my $sc = sanitize_address($c);
if ($sc eq $sender) {
next if ($suppress_cc{'self'});
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)
#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
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 */
/* 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);
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;
}
/*
{
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");
*
* 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()`.
*
* `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
*
* 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
*
* 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
*/
static inline int is_lock_file_locked(struct lock_file *lk)
{
- return is_tempfile_active(&lk->tempfile);
+ return is_tempfile_active(lk->tempfile);
}
/*
/*
* 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);
}
/*
*/
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);
}
/*
* 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
*/
static inline int reopen_lock_file(struct lock_file *lk)
{
- return reopen_tempfile(&lk->tempfile);
+ return reopen_tempfile(lk->tempfile);
}
/*
}
if (oid_eq(&common->object.oid, &merge->object.oid)) {
- output(o, 0, _("Already up-to-date!"));
+ output(o, 0, _("Already up to date!"));
*result = head;
return 1;
}
if (!oidcmp(&remote->object.oid, base_oid)) {
/* Already merged; result == local commit */
if (o->verbosity >= 2)
- printf("Already up-to-date!\n");
+ printf("Already up to date!\n");
oidcpy(result_oid, &local->object.oid);
goto found_result;
}
#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)
}
/*
- * 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)
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)
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++);
{
struct pathspec_item *item;
const char *entry = argv ? *argv : NULL;
- int i, n, prefixlen, warn_empty_string, nr_exclude = 0;
+ int i, n, prefixlen, nr_exclude = 0;
memset(pathspec, 0, sizeof(*pathspec));
}
n = 0;
- warn_empty_string = 1;
while (argv[n]) {
- if (*argv[n] == '\0' && warn_empty_string) {
- warning(_("empty strings as pathspecs will be made invalid in upcoming releases. "
- "please use . instead if you meant to match all paths"));
- warn_empty_string = 0;
- }
+ if (*argv[n] == '\0')
+ die("empty string is not a valid pathspec. "
+ "please use . instead if you meant to match all paths");
n++;
}
static int packet_write_fmt_1(int fd, int gently,
const char *fmt, va_list args)
{
- struct strbuf buf = STRBUF_INIT;
+ static struct strbuf buf = STRBUF_INIT;
ssize_t count;
+ strbuf_reset(&buf);
format_packet(&buf, fmt, args);
count = write_in_full(fd, buf.buf, buf.len);
if (count == buf.len)
#include "progress.h"
#include "list-objects.h"
#include "packfile.h"
+#include "worktree.h"
struct connectivity_progress {
struct progress *progress;
/* 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)
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;
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)) !=
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;
}
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);
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);
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);
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;
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;
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(
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)
{
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)
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;
{
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,
register_ref_store_map(&submodule_ref_stores, "submodule",
refs, submodule);
+done:
strbuf_release(&submodule_sb);
+ free(to_free);
+
return refs;
}
* 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,
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);
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);
struct ref_lock {
char *ref_name;
- struct lock_file *lk;
+ struct lock_file lk;
struct object_id old_oid;
};
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:
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);
}
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) {
/*
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;
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)) {
free(path);
}
- if (commit_lock_file(lock->lk))
+ if (commit_lock_file(&lock->lk))
return -1;
return 0;
}
unlock_ref(lock);
return -1;
}
- fd = get_lock_file_fd(lock->lk);
+ 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) {
+ 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;
}
{
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);
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));
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.
/*
* 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 "
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;
/*
* 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",
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;
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");
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,
strbuf_addf(err, "cannot lock ref '%s': %s",
original_update_refname(update), reason);
free(reason);
- return ret;
+ goto out;
}
update->backend_data = lock;
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 {
/*
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
"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;
}
* 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;
}
/*
!(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),
+ (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_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)",
* "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,
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;
}
*/
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) {
+ 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);
}
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));
if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0) {
strbuf_addf(err, "error writing to %s: %s",
- get_tempfile_path(&refs->tempfile), strerror(errno));
+ get_tempfile_path(refs->tempfile), strerror(errno));
goto error;
}
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),
+ get_tempfile_path(refs->tempfile),
strerror(errno));
ref_iterator_abort(iter);
goto error;
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!ours && !theirs) {
strbuf_addf(sb,
- _("Your branch is up-to-date with '%s'.\n"),
+ _("Your branch is up to date with '%s'.\n"),
base);
} else if (!theirs) {
strbuf_addf(sb,
#include "cache-tree.h"
#include "bisect.h"
#include "packfile.h"
+#include "worktree.h"
volatile show_early_output_fn_t show_early_output;
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)
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)
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)
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,
}
-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))
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)
{
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,
{
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!
*
* 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;
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;
}
topo_order:1,
simplify_merges:1,
simplify_by_decoration:1,
+ single_worktree:1,
tag_objects:1,
tree_objects:1,
blob_objects:1,
void *table,
size_t nr,
sha1_access_fn fn);
-
-extern int sha1_entry_pos(const void *table,
- size_t elem_size,
- size_t key_offset,
- unsigned lo, unsigned hi, unsigned nr,
- const unsigned char *key);
#endif
#include "quote.h"
#include "packfile.h"
-const unsigned char null_sha1[20];
+const unsigned char null_sha1[GIT_MAX_RAWSZ];
const struct object_id null_oid;
const struct object_id empty_tree_oid = {
EMPTY_TREE_SHA1_BIN_LITERAL
+#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))
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;
-/*
- * 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
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) != sb.len ||
+ 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,
if (len > (sb->alloc ? sb->alloc - 1 : 0))
die("BUG: strbuf_setlen() beyond buffer");
sb->len = len;
- sb->buf[len] = '\0';
+ if (sb->buf != strbuf_slopbuf)
+ sb->buf[len] = '\0';
+ else
+ assert(!strbuf_slopbuf[0]);
}
/**
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
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 */
echo >.gitattributes &&
git checkout -b master &&
git add .gitattributes &&
- git commit -m "add .gitattributes" "" &&
+ git commit -m "add .gitattributes" . &&
printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\nLINETWO\nLINETHREE" >LF &&
printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\r\nLINETHREE" >CRLF &&
printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\r\nLINETWO\nLINETHREE" >CRLF_mix_LF &&
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
grep "^0\{40\}.*$msg$" .git/logs/HEAD
'
+test_expect_success 'git branch -M should leave orphaned HEAD alone' '
+ git init orphan &&
+ (
+ cd orphan &&
+ test_commit initial &&
+ git checkout --orphan lonely &&
+ grep lonely .git/HEAD &&
+ test_path_is_missing .git/refs/head/lonely &&
+ git branch -M master mistress &&
+ grep lonely .git/HEAD
+ )
+'
+
test_expect_success 'resulting reflog can be shown by log -g' '
oid=$(git rev-parse HEAD) &&
cat >expect <<-EOF &&
test_expect_success 'use --set-upstream-to modify a particular branch' '
git branch my13 &&
git branch --set-upstream-to master my13 &&
+ test_when_finished "git branch --unset-upstream my13" &&
test "$(git config branch.my13.remote)" = "." &&
test "$(git config branch.my13.merge)" = "refs/heads/master"
'
test_must_fail git config branch.my14.merge
'
-test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' '
- git update-ref refs/remotes/origin/master HEAD &&
- git branch --set-upstream origin/master 2>actual &&
- test_when_finished git update-ref -d refs/remotes/origin/master &&
- test_when_finished git branch -d origin/master &&
- cat >expected <<EOF &&
-The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
-
-If you wanted to make '"'master'"' track '"'origin/master'"', do this:
-
- git branch -d origin/master
- git branch --set-upstream-to origin/master
-EOF
- test_i18ncmp expected actual
-'
-
-test_expect_success '--set-upstream with two args only shows the deprecation message' '
- git branch --set-upstream master my13 2>actual &&
- test_when_finished git branch --unset-upstream master &&
- cat >expected <<EOF &&
-The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
-EOF
- test_i18ncmp expected actual
-'
-
-test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' '
- git branch --set-upstream my13 2>actual &&
- test_when_finished git branch --unset-upstream my13 &&
- cat >expected <<EOF &&
-The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
-EOF
- test_i18ncmp expected actual
+test_expect_success '--set-upstream fails' '
+ test_must_fail git branch --set-upstream origin/master
'
test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
test_must_fail git branch -d my10
'
-test_expect_success 'use set-upstream on the current branch' '
- git checkout master &&
- git --bare init myupstream.git &&
- git push myupstream.git master:refs/heads/frotz &&
- git remote add origin myupstream.git &&
- git fetch &&
- git branch --set-upstream master origin/frotz &&
-
- test "z$(git config branch.master.remote)" = "zorigin" &&
- test "z$(git config branch.master.merge)" = "zrefs/heads/frotz"
-
-'
-
test_expect_success 'use --edit-description' '
write_script editor <<-\EOF &&
echo "New contents" >"$1"
test_i18ncmp expect actual
'
-test_expect_success 'rm empty string should invoke warning' '
- git rm -rf "" 2>output &&
- test_i18ngrep "warning: empty strings" output
+test_expect_success 'rm empty string should fail' '
+ test_must_fail git rm -rf ""
'
test_done
test_i18ncmp expect.err actual.err
'
-test_expect_success 'git add empty string should invoke warning' '
- git add "" 2>output &&
- test_i18ngrep "warning: empty strings" output
+test_expect_success 'git add empty string should fail' '
+ test_must_fail git add ""
'
test_expect_success 'git add --chmod=[+-]x stages correctly' '
SUBSTFORMAT='%H (%h)%n'
test_expect_exists() {
- test_expect_success " $1 exists" "test -e $1"
+ test_expect_${2:-success} " $1 exists" "test -e $1"
}
test_expect_missing() {
- test_expect_success " $1 does not exist" "test ! -e $1"
+ test_expect_${2:-success} " $1 does not exist" "test ! -e $1"
+}
+
+extract_tar_to_dir () {
+ (mkdir "$1" && cd "$1" && "$TAR" xf -) <"$1.tar"
}
test_expect_success 'setup' '
echo ignored by tree >ignored-by-tree &&
echo ignored-by-tree export-ignore >.gitattributes &&
- git add ignored-by-tree .gitattributes &&
+ mkdir ignored-by-tree.d &&
+ >ignored-by-tree.d/file &&
+ echo ignored-by-tree.d export-ignore >>.gitattributes &&
+ git add ignored-by-tree ignored-by-tree.d .gitattributes &&
echo ignored by worktree >ignored-by-worktree &&
echo ignored-by-worktree export-ignore >.gitattributes &&
git add ignored-by-worktree &&
+ mkdir excluded-by-pathspec.d &&
+ >excluded-by-pathspec.d/file &&
+ git add excluded-by-pathspec.d &&
+
printf "A\$Format:%s\$O" "$SUBSTFORMAT" >nosubstfile &&
printf "A\$Format:%s\$O" "$SUBSTFORMAT" >substfile1 &&
printf "A not substituted O" >substfile2 &&
test_expect_missing archive/ignored
test_expect_missing archive/ignored-by-tree
+test_expect_missing archive/ignored-by-tree.d
+test_expect_missing archive/ignored-by-tree.d/file
test_expect_exists archive/ignored-by-worktree
+test_expect_exists archive/excluded-by-pathspec.d
+test_expect_exists archive/excluded-by-pathspec.d/file
+
+test_expect_success 'git archive with pathspec' '
+ git archive HEAD ":!excluded-by-pathspec.d" >archive-pathspec.tar &&
+ extract_tar_to_dir archive-pathspec
+'
+
+test_expect_missing archive-pathspec/ignored
+test_expect_missing archive-pathspec/ignored-by-tree
+test_expect_missing archive-pathspec/ignored-by-tree.d
+test_expect_missing archive-pathspec/ignored-by-tree.d/file
+test_expect_exists archive-pathspec/ignored-by-worktree
+test_expect_missing archive-pathspec/excluded-by-pathspec.d failure
+test_expect_missing archive-pathspec/excluded-by-pathspec.d/file
+
+test_expect_success 'git archive with wildcard pathspec' '
+ git archive HEAD ":!excluded-by-p*" >archive-pathspec-wildcard.tar &&
+ extract_tar_to_dir archive-pathspec-wildcard
+'
+
+test_expect_missing archive-pathspec-wildcard/ignored
+test_expect_missing archive-pathspec-wildcard/ignored-by-tree
+test_expect_missing archive-pathspec-wildcard/ignored-by-tree.d
+test_expect_missing archive-pathspec-wildcard/ignored-by-tree.d/file
+test_expect_exists archive-pathspec-wildcard/ignored-by-worktree
+test_expect_missing archive-pathspec-wildcard/excluded-by-pathspec.d
+test_expect_missing archive-pathspec-wildcard/excluded-by-pathspec.d/file
test_expect_success 'git archive with worktree attributes' '
git archive --worktree-attributes HEAD >worktree.tar &&
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
(
cd test && git checkout b6
) >actual &&
- test_i18ngrep "Your branch is up-to-date with .origin/master" actual
+ test_i18ngrep "Your branch is up to date with .origin/master" actual
'
test_expect_success 'status (diverged from upstream)' '
# reports nothing to commit
test_must_fail git commit --dry-run
) >actual &&
- test_i18ngrep "Your branch is up-to-date with .origin/master" actual
+ test_i18ngrep "Your branch is up to date with .origin/master" actual
'
cat >expect <<\EOF
test_must_fail git checkout heavytrack
'
-test_expect_success 'setup tracking with branch --set-upstream on existing branch' '
+test_expect_success '--set-upstream-to does not change branch' '
git branch from-master master &&
- test_must_fail git config branch.from-master.merge > actual &&
- git branch --set-upstream from-master master &&
- git config branch.from-master.merge > actual &&
- grep -q "^refs/heads/master$" actual
-'
-
-test_expect_success '--set-upstream does not change branch' '
+ git branch --set-upstream-to master from-master &&
git branch from-master2 master &&
test_must_fail git config branch.from-master2.merge > actual &&
git rev-list from-master2 &&
git update-ref refs/heads/from-master2 from-master2^ &&
git rev-parse from-master2 >expect2 &&
- git branch --set-upstream from-master2 master &&
+ git branch --set-upstream-to master from-master2 &&
git config branch.from-master.merge > actual &&
git rev-parse from-master2 >actual2 &&
grep -q "^refs/heads/master$" actual &&
cmp expect2 actual2
'
-test_expect_success '--set-upstream @{-1}' '
- git checkout from-master &&
+test_expect_success '--set-upstream-to @{-1}' '
+ git checkout follower &&
git checkout from-master2 &&
git config branch.from-master2.merge > expect2 &&
- git branch --set-upstream @{-1} follower &&
+ git branch --set-upstream-to @{-1} from-master &&
git config branch.from-master.merge > actual &&
git config branch.from-master2.merge > actual2 &&
- git branch --set-upstream from-master follower &&
+ git branch --set-upstream-to follower from-master &&
git config branch.from-master.merge > expect &&
test_cmp expect2 actual2 &&
test_cmp expect actual
!two@example.com!
!three@example.com!
!four@example.com!
+!five@example.com!
+!six@example.com!
EOF
"
Cc: <two@example.com> # trailing comments are ignored
Cc: <three@example.com>, <not.four@example.com> one address per line
Cc: "Some # Body" <four@example.com> [ <also.a.comment> ]
+ Cc: five@example.com # not.six@example.com
+ Cc: six@example.com, not.seven@example.com
EOF
clean_fake_sendmail &&
git send-email -1 --to=recipient@example.com \
* `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
#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;
}
}
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,
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";
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;
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;
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;
}
#ifndef TEMPFILE_H
#define TEMPFILE_H
+#include "list.h"
+
/*
* Handle temporary files.
*
*
* 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.
*
* 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,
* 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
* 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);
/*
* 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;
}
/*
* 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
* `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
* `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 */
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
- echo >&2 "$topic is already up-to-date with master"
+ echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
}
}
-static struct tempfile trailers_tempfile;
+static struct tempfile *trailers_tempfile;
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"));
__attribute__((format (printf, 1, 2)))
static void transfer_debug(const char *fmt, ...)
{
+ /*
+ * NEEDSWORK: This function is sometimes used from multiple threads, and
+ * we end up using debug_enabled racily. That "should not matter" since
+ * we always write the same value, but it's still wrong. This function
+ * is listed in .tsan-suppressions for the time being.
+ */
+
va_list args;
char msgbuf[PBUFFERSIZE];
static int debug_enabled = -1;
msgs[ERROR_BIND_OVERLAP] = _("Entry '%s' overlaps with '%s'. Cannot bind.");
msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
- _("Cannot update sparse checkout: the following entries are not up-to-date:\n%s");
+ _("Cannot update sparse checkout: the following entries are not up to date:\n%s");
msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
_("The following working tree files would be overwritten by sparse checkout update:\n%s");
msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
}
}
}
- errs |= finish_delayed_checkout(&state);
stop_progress(&progress);
+ errs |= finish_delayed_checkout(&state);
if (o->update)
git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
return errs != 0;
target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
"HEAD",
- RESOLVE_REF_READING,
+ 0,
wt->head_sha1, &flags);
if (!target)
return;
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;
+}
#ifndef WORKTREE_H
#define WORKTREE_H
+#include "refs.h"
+
struct worktree {
char *path;
char *id;
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);