Merge branch 'hn/url-push-tracking'
When the push remote is specified as a URL, the fetch refspec of a
uniquely matching configured remote is now used to find and update
the remote-tracking branch (e.g., '@{push}').
* hn/url-push-tracking:
remote: find tracking branches for URL push destinations
remote: pass repository to push tracking helper
diff --git a/.b4-cover-template b/.b4-cover-template
index ab86493..8168d8a 100644
--- a/.b4-cover-template
+++ b/.b4-cover-template
@@ -8,4 +8,5 @@
${range_diff}
---
base-commit: ${base_commit}
+change-id: ${change_id}
${prerequisites}
diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc
index 5c6bfe1..6cee8bb 100644
--- a/Documentation/RelNotes/2.56.0.adoc
+++ b/Documentation/RelNotes/2.56.0.adoc
@@ -48,6 +48,34 @@
help option ('-h' or '--help') is requested directly by the user,
aligning with standard Unix convention.
+ * The '[includeIf "condition"]' conditional inclusion facility for
+ configuration files has been taught to use the location of the
+ worktree in its condition.
+
+ * The usage string and SYNOPSIS for 'git fast-export' have been
+ standardized to make them consistent with each other and with other
+ commands.
+
+ * 'git log --graph' has been modified to visually distinguish parentless
+ 'root' commits (and commits that become roots due to history
+ simplification) by indenting them, preventing them from appearing
+ falsely related to unrelated commits rendered immediately above them.
+
+ * Userdiff patterns for Swift have been added, with support for
+ Swift-specific constructs such as attributes, modifiers, failable
+ initializers, and generics.
+
+ * Configuration file locking has been updated to retry for a short
+ period, avoiding failures when multiple processes attempt to update
+ the configuration simultaneously.
+
+ * The 'remote-object-info' command has been added to 'git cat-file
+ --batch-command', allowing clients to request object metadata
+ (currently size) from a remote server via protocol v2 without
+ downloading the entire object. Format placeholders are dynamically
+ filtered on the client based on server-advertised capabilities,
+ returning empty strings for inapplicable or unsupported fields.
+
Performance, Internal Implementation, Development Support etc.
--------------------------------------------------------------
@@ -163,6 +191,95 @@
dereferences and invalid file descriptor accesses flagged by
Coverity.
+ * The in-tree 'b4' cover letter template has been updated to include the
+ 'change-id' trailer, ensuring that sent tags generated by 'b4' contain
+ the required tracking information for subsequent runs.
+
+ * 'git receive-pack' has been refactored to use ODB transaction
+ interfaces instead of directly managing 'tmp_objdir' for staging
+ incoming objects, bringing it closer to being ODB backend agnostic.
+
+ * The test script 't/t9811-git-p4-label-import.sh' has been
+ modernized to use 'test_path_is_file' and 'test_path_is_missing'
+ instead of raw 'test -f' and '! test -f' calls.
+
+ * A redundant strbuf_reset() call in the 'HAVE_GETDELIM' path of
+ strbuf_getwholeline() has been removed, as getdelim() overwrites the
+ buffer and the length is updated afterward.
+
+ * The object database enumeration interface odb_for_each_object() has
+ been taught to accept object filters, allowing the underlying backends
+ to optimize the traversal by using reachability bitmaps when
+ available. 'git cat-file --batch-all-objects' has been updated to use
+ this generic interface, simplifying its code and avoiding direct
+ access to ODB backend internals.
+
+ * The test script 't/t1100-commit-tree-options.sh' has been modernized
+ by converting test cases to the modern style (using single quotes and
+ tab indentation) and moving the creation of the expected file inside
+ the setup test so it runs under the protection of the test harness.
+
+ * The test script 't/t7614-merge-signoff.sh' has been updated to avoid
+ suppressing the exit code of 'git' commands in a pipe.
+
+ * The 'git rev-list --no-walk' command has been corrected to restore
+ pathspec filtering, which was lost when the streaming walk was
+ refactored.
+
+ * The ref subsystem and the worktree API have been refactored to pass a
+ repository pointer down the call chain, allowing them to drop
+ references to the global 'the_repository' variable. As part of this,
+ the handling of the 'core.packedRefsTimeout' configuration has been
+ moved into the per-repository ref store structure.
+
+ * 'git branch --contains' and 'git for-each-ref --contains' have been
+ optimized to use the memoized commit traversal previously used only by
+ 'git tag --contains', significantly speeding up connectivity checks
+ across many candidate refs with shared history.
+
+ * The passing of push destination specifications in the 'remote-curl'
+ helper has been simplified by removing the explicit 'count' parameter
+ and relying on the NULL-termination of the array.
+
+ * The dependency on the global 'the_repository' variable in the
+ 'refspec.c' API has been removed by passing the hash algorithm
+ explicitly to refspec-parsing functions and storing it in 'struct
+ refspec'.
+
+ * The enumeration of untracked and ignored files in 'git status' has
+ been optimized by avoiding quadratic complexity when inserting into
+ string lists, reducing the construction cost from O(n^2) to O(n log
+ n).
+
+ * The copy_file() and copy_file_with_time() functions have been
+ refactored to take a repository parameter, allowing the removal of the
+ implicit dependency on the global 'the_repository' variable in
+ 'copy.c'.
+
+ * The tempfile and lockfile APIs have been refactored to stop depending
+ on the 'the_repository' global variable, and their callers have been
+ updated to use the repository-aware variants.
+
+ * The 'trust_executable_bit' (coming from the 'core.filemode'
+ configuration) has been migrated into 'struct repo_config_values' to
+ tie it to a specific repository instance.
+
+ * The 'excludes_file' and various other global configuration variables
+ (including 'editor_program', 'pager_program', 'askpass_program', and
+ 'push_default') have been migrated into the per-repository structure.
+
+ * The 'git stash push' command has been optimized to avoid unnecessary
+ sparse index expansion when pathspecs are wholly inside the
+ sparse-checkout cone. Also, a potential out-of-bounds read in the
+ sparse-index expansion check helper pathspec_needs_expanded_index()
+ has been fixed by consistently using the parsed, prefixed path.
+
+ * The logic to write loose objects has been refactored and moved from
+ 'object-file.c' to the loose backend source file 'odb/source-loose.c',
+ making the loose backend more self-contained. This is achieved by
+ first refactoring force_object_loose() to use generic ODB write
+ interfaces instead of loose-backend internals.
+
Fixes since v2.55
-----------------
@@ -298,3 +415,33 @@
split commit-graph chain, causing topological levels for commits in
base layers to be recomputed during incremental writes. This has been
corrected.
+
+ * The stream-based object signature verification path has been
+ corrected to avoid double-closing the stream on read errors.
+ (merge cfd52a74a0 ps/odb-stream-double-close-fix later to maint).
+
+ * The '-i' shorthand for the '--init' option, which was accepted by the
+ 'git submodule update' command until it was broken in a modernization
+ of the option-parsing code, has been restored.
+ (merge ff1da37f58 dm/submodule-update-i-shorthand later to maint).
+
+ * An accidental use of the '%zu' format specifier in 'git
+ submodule--helper' has been corrected to use 'PRIuMAX' and cast the
+ value to 'uintmax_t' to avoid portability issues.
+ (merge 3279c13c00 jc/submodule-helper-avoid-zu later to maint).
+
+ * The rebase post-rewrite notes-copying logic has been corrected. When
+ a commit is dropped during rebase (e.g., because its changes are
+ already upstream), it is no longer recorded as rewritten, preventing
+ its notes from being copied to an unrelated commit.
+ (merge 42554b78fd pw/rebase-drop-notes-with-commit later to maint).
+
+ * A few memory problems in the Rust interface to C hash functions have
+ been corrected. The 'Clone' implementation of 'CryptoHasher' now
+ properly initializes the context before cloning, and its 'Drop'
+ implementation now discards the context to prevent leaks.
+
+ * The object ID shortening and linking in the 'commitdiff' view of
+ 'gitweb' has been corrected to work even when the index line carries
+ a trailing file mode.
+ (merge fda513d6fe tl/gitweb-shorten-hashes-with-modes later to maint).
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index 15b1a4d..1ef72de 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -146,6 +146,51 @@
This is the same as `gitdir` except that matching is done
case-insensitively (e.g. on case-insensitive file systems)
+`worktree`::
+ The data that follows the keyword `worktree` and a colon is used as a
+ glob pattern. If the working directory of the current worktree matches
+ the pattern, the include condition is met.
++
+The worktree location is the path where files are checked out (as returned
+by `git rev-parse --show-toplevel`). This is different from `gitdir`, which
+matches the `.git` directory path. In a linked worktree, the worktree path
+is the directory where that worktree's files are located, not the main
+repository's `.git` directory.
++
+The pattern uses the same glob syntax as `gitdir` (including `~/`, `./`,
+`**/`, and trailing-`/` prefix matching). This condition will never match
+in a bare repository (which has no worktree).
++
+Unlike `gitdir`, the `worktree` condition currently matches only the
+realpath-resolved worktree location. If the working tree was entered via a
+symbolic link, a pattern that uses the symbolic-link spelling may not match;
+use the real path instead.
++
+This is useful when you want to apply configuration based on where the
+working tree is located on the filesystem. For example, a contributor who
+works on the same project both personally and as an employee can use
+different `user.name` and `user.email` values depending on which directory
+the worktree is checked out under:
++
+----
+[includeIf "worktree:/home/user/work/"]
+ path = ~/.config/git/work.inc
+[includeIf "worktree:/home/user/personal/"]
+ path = ~/.config/git/personal.inc
+----
++
+While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports
+per-worktree configuration, it stores the config inside each repository's
+`.git/config.worktree` file and requires running `git config --worktree`
+inside each worktree individually. In contrast, `includeIf "worktree:..."`
+can be set once in a global or system-level configuration file (e.g.
+`~/.config/git/config`) and applies to all repositories at once based on
+their worktree location.
+
+`worktree/i`::
+ This is the same as `worktree` except that matching is done
+ case-insensitively (e.g. on case-insensitive file systems)
+
`onbranch`::
The data that follows the keyword `onbranch` and a colon is taken to be a
pattern with standard globbing wildcards and two additional
@@ -244,6 +289,14 @@
[includeIf "gitdir:~/to/group/"]
path = /path/to/foo.inc
+; include if the worktree is at /path/to/project-build
+[includeIf "worktree:/path/to/project-build"]
+ path = build-config.inc
+
+; include for all worktrees inside /path/to/group
+[includeIf "worktree:/path/to/group/"]
+ path = group-config.inc
+
; relative paths are always relative to the including
; file (if the condition is true); their location is not
; affected by the condition
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc
index a0ebf03..340329e 100644
--- a/Documentation/config/core.adoc
+++ b/Documentation/config/core.adoc
@@ -589,6 +589,14 @@
all; -1 means to try indefinitely. Default is 1000 (i.e.,
retry for 1 second).
+core.configLockTimeout::
+ The length of time, in milliseconds, to retry when trying to
+ lock a configuration file for writing. Value 0 means not to
+ retry at all; -1 means to try indefinitely. Default is 1000
+ (i.e., retry for 1 second). This is read from the configuration
+ that is already on disk before the lock is taken, so it can be
+ set persistently like any other option.
+
core.pager::
Text viewer for use by Git commands (e.g., 'less'). The value
is meant to be interpreted by the shell. The order of preference
diff --git a/Documentation/config/log.adoc b/Documentation/config/log.adoc
index 757a7be..f7dfce6 100644
--- a/Documentation/config/log.adoc
+++ b/Documentation/config/log.adoc
@@ -59,6 +59,10 @@
A list of colors, separated by commas, that can be used to draw
history lines in `git log --graph`.
+`log.graphIndent`::
+ If `true`, indent visual roots when rendering the graphs with `--graph`.
+ Set true by default. It can be overriden with `--[no-]graph-indent`.
+
`log.showRoot`::
If true, the initial commit will be shown as a big creation event.
This is equivalent to a diff against an empty tree.
diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc
index 86b9181..ac3b528 100644
--- a/Documentation/git-cat-file.adoc
+++ b/Documentation/git-cat-file.adoc
@@ -169,6 +169,13 @@
Print object info for object reference `<object>`. This corresponds to the
output of `--batch-check`.
+remote-object-info <remote> <object>...::
+ Print object info for object references `<object>` at specified
+ `<remote>` without downloading objects from the remote.
+ Raise an error when the `object-info` capability is not supported by the remote.
+ Raise an error when no object references are provided.
+ This command may be combined with `--buffer`.
+
flush::
Used with `--buffer` to execute all preceding commands that were issued
since the beginning or since the last flush was issued. When `--buffer`
@@ -301,7 +308,8 @@
`--batch-command`, the `info` command followed by an object will print
information about the object the same way `--batch-check` would, and the
`contents` command followed by an object prints contents in the same way
-`--batch` would.
+`--batch` would. The `remote-object-info` command followed by a remote and
+object IDs prints object info from the remote without downloading the objects.
You can specify the information shown for each object by using a custom
`<format>`. The `<format>` is copied literally to stdout for each
@@ -340,8 +348,15 @@
after that first run of whitespace (i.e., the "rest" of the
line) are output in place of the `%(rest)` atom.
+The command `remote-object-info` only supports the `%(objectname)` and
+`%(objectsize)` placeholders. See `CAVEATS` below for more information.
+
If no format is specified, the default format is `%(objectname)
-%(objecttype) %(objectsize)`.
+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which
+use `%(objectname) %(objectsize)` because `%(objecttype)` is not supported yet.
+
+WARNING: When "%(objecttype)" is supported, the default format WILL be unified,
+so DO NOT RELY on the current default format to stay the same!!!
If `--batch` is specified, or if `--batch-command` is used with the `contents`
command, the object information is followed by the object contents (consisting
@@ -438,6 +453,10 @@
CAVEATS
-------
+Note that only `%(objectname)` and `%(objectsize)` are currently
+supported by the `remote-object-info` command. Using any other placeholder in
+the format string will return an empty string in its position.
+
Note that the sizes of objects on disk are reported accurately, but care
should be taken in drawing conclusions about which refs or objects are
responsible for disk usage. The size of a packed non-delta object may be
diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc
index 297b57b..719aeca 100644
--- a/Documentation/git-fast-export.adoc
+++ b/Documentation/git-fast-export.adoc
@@ -9,7 +9,7 @@
SYNOPSIS
--------
[verse]
-'git fast-export' [<options>] | 'git fast-import'
+'git fast-export' [<options>] [<revision-range>] [[--] <path>...]
DESCRIPTION
-----------
diff --git a/Documentation/gitattributes.adoc b/Documentation/gitattributes.adoc
index bd76167..9fea75f 100644
--- a/Documentation/gitattributes.adoc
+++ b/Documentation/gitattributes.adoc
@@ -914,6 +914,8 @@
- `scheme` suitable for source code in most Lisp dialects,
including Scheme, Emacs Lisp, Common Lisp, and Clojure.
+- `swift` suitable for source code in the Swift language.
+
- `tex` suitable for source code for LaTeX documents.
diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc
index 2beb705..7bf6201 100644
--- a/Documentation/gitprotocol-v2.adoc
+++ b/Documentation/gitprotocol-v2.adoc
@@ -568,21 +568,26 @@
oid <oid>
Indicates to the server an object which the client wants to obtain
- information for.
+ information for. They must be full OIDs.
-The response of `object-info` is a list of the requested object ids
-and associated requested information, each separated by a single space.
+The response of `object-info` consists of one pkt-line per requested attribute,
+echoing the attributes the server will report, followed by one pkt-line per
+requested object id with its information, each field separated by a single
+space.
output = info flush-pkt
- info = PKT-LINE(attrs) LF)
- *PKT-LINE(obj-info LF)
-
- attrs = attr | attrs SP attrs
+ info = *PKT-LINE(attr LF)
+ *PKT-LINE(obj-info LF)
attr = "size"
- obj-info = obj-id SP obj-size
+ obj-size = 1*DIGIT
+
+ obj-info = obj-id [SP [obj-size]]
+
+If the server does not recognize the OID, the response will be `<oid> SP`
+regardless of the number of attributes requested.
bundle-uri
~~~~~~~~~~
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index eaee6ee..fd831f0 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -1269,6 +1269,14 @@
By default it is set to 0 (no limit), zero and negative values
are ignored and treated as no limit.
+`--no-graph-indent`::
+`--graph-indent`::
+ When used with `--graph`, indent visual roots (commits with no parents
+ or whose parents are not shown) to differentiate them from commits that
+ are vertically adjacent but unrelated. Enabled by default. Use
+ `--no-graph-indent` to disable or set `log.graphIndent` to set a
+ default preference.
+
ifdef::git-rev-list[]
`--count`::
Print a number stating how many commits would have been
diff --git a/Makefile b/Makefile
index 98e995e..fac3e88 100644
--- a/Makefile
+++ b/Makefile
@@ -1169,6 +1169,7 @@
LIB_OBJS += ewah/ewah_rlw.o
LIB_OBJS += exec-cmd.o
LIB_OBJS += fetch-negotiator.o
+LIB_OBJS += fetch-object-info.o
LIB_OBJS += fetch-pack.o
LIB_OBJS += fmt-merge-msg.o
LIB_OBJS += fsck.o
diff --git a/apply.c b/apply.c
index 5e87ba7..f00b7ba 100644
--- a/apply.c
+++ b/apply.c
@@ -47,11 +47,17 @@ struct gitdiff_data {
int p_value;
};
-static void git_apply_config(void)
+static void git_apply_config(struct repository *repo)
{
- repo_config_get_string(the_repository, "apply.whitespace", &apply_default_whitespace);
- repo_config_get_string(the_repository, "apply.ignorewhitespace", &apply_default_ignorewhitespace);
- repo_config(the_repository, git_xmerge_config, NULL);
+ struct repo_config_values *cfg = repo_config_values(repo);
+
+ FREE_AND_NULL(cfg->apply_default_whitespace);
+ repo_config_get_string(repo, "apply.whitespace",
+ &cfg->apply_default_whitespace);
+ FREE_AND_NULL(cfg->apply_default_ignorewhitespace);
+ repo_config_get_string(repo, "apply.ignorewhitespace",
+ &cfg->apply_default_ignorewhitespace);
+ repo_config(repo, git_xmerge_config, NULL);
}
static int parse_whitespace_option(struct apply_state *state, const char *option)
@@ -109,6 +115,8 @@ int init_apply_state(struct apply_state *state,
struct repository *repo,
const char *prefix)
{
+ struct repo_config_values *cfg = repo_config_values(repo);
+
memset(state, 0, sizeof(*state));
state->prefix = prefix;
state->repo = repo;
@@ -126,10 +134,13 @@ int init_apply_state(struct apply_state *state,
strset_init(&state->kept_symlinks);
strbuf_init(&state->root, 0);
- git_apply_config();
- if (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))
+ git_apply_config(repo);
+
+ if (cfg->apply_default_whitespace &&
+ parse_whitespace_option(state, cfg->apply_default_whitespace))
return -1;
- if (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))
+ if (cfg->apply_default_ignorewhitespace &&
+ parse_ignorewhitespace_option(state, cfg->apply_default_ignorewhitespace))
return -1;
return 0;
}
@@ -192,7 +203,8 @@ int check_apply_state(struct apply_state *state, int force_apply)
static void set_default_whitespace_mode(struct apply_state *state)
{
- if (!state->whitespace_option && !apply_default_whitespace)
+ if (!state->whitespace_option &&
+ !repo_config_values(state->repo)->apply_default_whitespace)
state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);
}
@@ -3893,8 +3905,8 @@ static int check_preimage(struct apply_state *state,
if (*ce && !(*ce)->ce_mode)
BUG("ce_mode == 0 for path '%s'", old_name);
- if (trust_executable_bit || !S_ISREG(st->st_mode))
- st_mode = ce_mode_from_stat(*ce, st->st_mode);
+ if (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode))
+ st_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode);
else if (*ce)
st_mode = (*ce)->ce_mode;
else
@@ -4287,7 +4299,8 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)
}
}
- hold_lock_file_for_update(&lock, state->fake_ancestor, LOCK_DIE_ON_ERROR);
+ repo_hold_lock_file_for_update(state->repo, &lock, state->fake_ancestor,
+ LOCK_DIE_ON_ERROR);
res = write_locked_index(&result, &lock, COMMIT_LOCK);
discard_index(&result);
@@ -4511,7 +4524,7 @@ static int try_create_file(struct apply_state *state, const char *path,
return !!mkdir(path, 0777);
}
- if (has_symlinks && S_ISLNK(mode))
+ if (repo_has_symlinks(state->repo) && S_ISLNK(mode))
/* Although buf:size is counted string, it also is NUL
* terminated.
*/
@@ -4945,9 +4958,10 @@ static int apply_patch(struct apply_state *state,
state->update_index = (state->check_index || state->ita_only) && state->apply;
if (state->update_index && !is_lock_file_locked(&state->lock_file)) {
if (state->index_file)
- hold_lock_file_for_update(&state->lock_file,
- state->index_file,
- LOCK_DIE_ON_ERROR);
+ repo_hold_lock_file_for_update(state->repo,
+ &state->lock_file,
+ state->index_file,
+ LOCK_DIE_ON_ERROR);
else
repo_hold_locked_index(state->repo, &state->lock_file,
LOCK_DIE_ON_ERROR);
diff --git a/branch.c b/branch.c
index 243db7d..3a12e2a 100644
--- a/branch.c
+++ b/branch.c
@@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)
static int should_setup_rebase(const char *origin)
{
- switch (autorebase) {
+ switch (repo_config_values(the_repository)->autorebase) {
case AUTOREBASE_NEVER:
return 0;
case AUTOREBASE_LOCAL:
@@ -372,7 +372,7 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
*/
int validate_branchname(const char *name, struct strbuf *ref)
{
- if (check_branch_ref(ref, name)) {
+ if (check_branch_ref(the_repository, ref, name)) {
int code = die_message(_("'%s' is not a valid branch name"), name);
advise_if_enabled(ADVICE_REF_SYNTAX,
_("See 'git help check-ref-format'"));
@@ -394,7 +394,7 @@ static void prepare_checked_out_branches(void)
return;
initialized_checked_out_branches = 1;
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
while (worktrees[i]) {
char *old, *wt_gitdir;
@@ -846,7 +846,7 @@ void remove_branch_state(struct repository *r, int verbose)
void die_if_checked_out(const char *branch, int ignore_current_worktree)
{
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees(the_repository);
for (int i = 0; worktrees[i]; i++) {
if (worktrees[i]->is_current && ignore_current_worktree)
diff --git a/builtin/add.c b/builtin/add.c
index c859f66..60ffbed 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -581,7 +581,7 @@ int cmd_add(int argc,
string_list_clear(&only_match_skip_worktree, 0);
}
- transaction = odb_transaction_begin(repo->objects);
+ odb_transaction_begin_or_die(repo->objects, &transaction, 0);
ps_matched = xcalloc(pathspec.nr, 1);
if (add_renormalize)
diff --git a/builtin/branch.c b/builtin/branch.c
index dede60d..031a4a9 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -259,7 +259,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
char *target = NULL;
int flags = 0;
- copy_branchname(&bname, argv[i], allowed_interpret);
+ copy_branchname(the_repository, &bname,
+ argv[i], allowed_interpret);
free(name);
name = mkpathdup(fmt, bname.buf);
@@ -579,9 +580,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
const char *interpreted_oldname = NULL;
const char *interpreted_newname = NULL;
int recovery = 0, oldref_usage = 0;
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees(the_repository);
- if (check_branch_ref(&oldref, oldname)) {
+ if (check_branch_ref(the_repository, &oldref, oldname)) {
/*
* Bad name --- this could be an attempt to rename a
* ref that we used to allow to be created by accident.
@@ -921,7 +922,8 @@ int cmd_branch(int argc,
die(_("cannot give description to detached HEAD"));
branch_name = head;
} else if (argc == 1) {
- copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
+ copy_branchname(the_repository, &buf, argv[0],
+ INTERPRET_BRANCH_LOCAL);
branch_name = buf.buf;
} else {
die(_("cannot edit description of more than one branch"));
@@ -964,7 +966,8 @@ int cmd_branch(int argc,
if (!argc)
branch = branch_get(NULL);
else if (argc == 1) {
- copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
+ copy_branchname(the_repository, &buf, argv[0],
+ INTERPRET_BRANCH_LOCAL);
branch = branch_get(buf.buf);
} else
die(_("too many arguments to set new upstream"));
@@ -1003,7 +1006,8 @@ int cmd_branch(int argc,
if (!argc)
branch = branch_get(NULL);
else if (argc == 1) {
- copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
+ copy_branchname(the_repository, &buf, argv[0],
+ INTERPRET_BRANCH_LOCAL);
branch = branch_get(buf.buf);
} else
die(_("too many arguments to unset upstream"));
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index b4b99a7..884b6d5 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -20,7 +20,6 @@
#include "userdiff.h"
#include "oid-array.h"
#include "packfile.h"
-#include "pack-bitmap.h"
#include "object-file.h"
#include "object-name.h"
#include "odb.h"
@@ -29,6 +28,22 @@
#include "promisor-remote.h"
#include "mailmap.h"
#include "write-or-die.h"
+#include "alias.h"
+#include "remote.h"
+#include "transport.h"
+
+/*
+ * Maximum length for a remote URL. While no universal standard exists,
+ * 8K is assumed to be a reasonable limit.
+ */
+#define MAX_REMOTE_URL_LEN (8 * 1024)
+
+/* Maximum number of objects allowed in a single remote-object-info request. */
+#define MAX_ALLOWED_OBJ_LIMIT 10000
+
+/* Maximum input size permitted for the remote-object-info command. */
+#define MAX_REMOTE_OBJ_INFO_LINE \
+ (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))
enum batch_mode {
BATCH_MODE_CONTENTS,
@@ -317,8 +332,24 @@ struct expand_data {
* optimized out.
*/
unsigned skip_object_info : 1;
+
+ /*
+ * Flags about when an object info is being fetched from remote.
+ */
+ unsigned is_remote:1;
+
+ /*
+ * List of atoms (i.e. "objectsize") that the server supports. Built
+ * from the server's object-info advertised capabilities.
+ */
+ struct string_list remote_allowed_atoms;
};
-#define EXPAND_DATA_INIT { .mode = S_IFINVALID }
+
+#define EXPAND_DATA_INIT { \
+ .mode = S_IFINVALID, \
+ .type = OBJ_BAD, \
+ .remote_allowed_atoms = STRING_LIST_INIT_NODUP, \
+}
static int is_atom(const char *atom, const char *s, int slen)
{
@@ -329,14 +360,26 @@ static int is_atom(const char *atom, const char *s, int slen)
static int expand_atom(struct strbuf *sb, const char *atom, int len,
struct expand_data *data)
{
+ if (data->is_remote) {
+ size_t i;
+ for (i = 0; i < data->remote_allowed_atoms.nr; i++)
+ if (is_atom(data->remote_allowed_atoms.items[i].string,
+ atom, len))
+ break;
+ if (i == data->remote_allowed_atoms.nr)
+ return 1;
+ }
+
if (is_atom("objectname", atom, len)) {
if (!data->mark_query)
strbuf_add_oid_hex(sb, &data->oid);
} else if (is_atom("objecttype", atom, len)) {
- if (data->mark_query)
+ if (data->mark_query) {
data->info.typep = &data->type;
- else
- strbuf_addstr(sb, type_name(data->type));
+ } else {
+ const char *t = type_name(data->type);
+ strbuf_addstr(sb, t ? t : "");
+ }
} else if (is_atom("objectsize", atom, len)) {
if (data->mark_query)
data->info.sizep = &data->size;
@@ -636,6 +679,62 @@ static void batch_one_object(const char *obj_name,
object_context_release(&ctx);
}
+static int get_remote_info(int argc,
+ const char **argv,
+ struct object_info **remote_object_info,
+ struct oid_array *object_info_oids,
+ struct string_list *object_info_options)
+{
+ int retval = 0;
+ struct remote *remote = NULL;
+ struct object_id oid;
+ struct transport *gtransport;
+
+ remote = remote_get(argv[0]);
+ if (!remote)
+ die(_("must supply valid remote when using remote-object-info"));
+
+ oid_array_clear(object_info_oids);
+ for (size_t i = 1; i < argc; i++) {
+ if (get_oid_hex(argv[i], &oid)) {
+ size_t len = strlen(argv[i]);
+
+ if (len < the_hash_algo->hexsz && len >= 4) {
+ size_t j;
+ for (j = 0; j < len; j++)
+ if (!isxdigit(argv[i][j]))
+ break;
+ if (j == len)
+ die(_("remote-object-info does not support "
+ "short oids, %d characters required"),
+ (int)the_hash_algo->hexsz);
+ }
+ die(_("not a valid object name '%s'"), argv[i]);
+ }
+ oid_array_append(object_info_oids, &oid);
+ }
+
+ if (!object_info_oids->nr)
+ die(_("remote-object-info requires objects"));
+
+ gtransport = transport_get(remote, NULL);
+
+ if (!gtransport->smart_options) {
+ retval = -1;
+ goto cleanup;
+ }
+
+ CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);
+ gtransport->smart_options->object_info_oids = object_info_oids;
+
+ gtransport->smart_options->object_info_options = object_info_options;
+ gtransport->smart_options->object_info_data = *remote_object_info;
+ retval = transport_fetch_object_info(gtransport);
+cleanup:
+ transport_disconnect(gtransport);
+ return retval;
+}
+
struct object_cb_data {
struct batch_options *opt;
struct expand_data *expand;
@@ -717,18 +816,115 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
load_mailmap();
}
+struct protocol_placeholder_entry {
+ const char *option;
+ const char *atom;
+};
+
+static const struct protocol_placeholder_entry remote_atom_map[] = {
+ {"size", "objectsize"},
+ {"type", "objecttype"},
+ /*
+ * Add new protocol options here. Even if the server doesn't support
+ * them the allow_list will drop them if the server doesn't advertise
+ * them.
+ */
+};
+
+static void parse_cmd_remote_object_info(struct batch_options *opt,
+ const char *line, struct strbuf *output,
+ struct expand_data *data)
+{
+ int count;
+ const char **argv;
+ char *line_to_split;
+ struct object_info *remote_object_info = NULL;
+ struct oid_array object_info_oids = OID_ARRAY_INIT;
+ struct string_list object_info_options = STRING_LIST_INIT_NODUP;
+ const char *saved_format = opt->format;
+
+ if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
+ die(_("remote-object-info command too long"));
+ /*
+ * TODO: Use the default format once %(objecttype) is supported.
+ */
+ if (!opt->format)
+ opt->format = "%(objectname) %(objectsize)";
+
+ line_to_split = xstrdup(line);
+ count = split_cmdline(line_to_split, &argv);
+ if (count < 0)
+ die(_("remote-object-info: failed to parse command line: %s"),
+ split_cmdline_strerror(count));
+ if (count - 1 > MAX_ALLOWED_OBJ_LIMIT)
+ die(_("remote-object-info supports at most %d objects"),
+ MAX_ALLOWED_OBJ_LIMIT);
+
+ if (data->info.sizep)
+ string_list_append(&object_info_options, "size");
+ if (data->info.typep)
+ string_list_append(&object_info_options, "type");
+
+ if (get_remote_info(count, argv, &remote_object_info,
+ &object_info_oids, &object_info_options))
+ die(_("failed to get object info from the remote: %s"), argv[0]);
+
+ string_list_clear(&data->remote_allowed_atoms, 0);
+ string_list_append(&data->remote_allowed_atoms, "objectname");
+ for (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)
+ if (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))
+ string_list_append(&data->remote_allowed_atoms,
+ remote_atom_map[i].atom);
+
+ data->skip_object_info = 1;
+ for (size_t i = 0; i < object_info_oids.nr; i++) {
+ data->oid = object_info_oids.oid[i];
+
+ if (remote_object_info[i].unrecognized) {
+ report_object_status(opt, oid_to_hex(&data->oid),
+ &data->oid, "missing");
+ continue;
+ }
+
+ /*
+ * When reaching here, it means remote-object-info can retrieve
+ * information from server without downloading them.
+ */
+ if (remote_object_info[i].sizep) {
+ data->size = *remote_object_info[i].sizep;
+ }
+
+ if (remote_object_info[i].typep) {
+ data->type = *remote_object_info[i].typep;
+ }
+
+ opt->batch_mode = BATCH_MODE_INFO;
+ data->is_remote = 1;
+ batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
+ data->is_remote = 0;
+ }
+ data->skip_object_info = 0;
+ opt->format = saved_format;
+
+ for (size_t i = 0; i < object_info_oids.nr; i++)
+ free_object_info_contents(&remote_object_info[i]);
+ string_list_clear(&object_info_options, 0);
+ free(line_to_split);
+ free(argv);
+ free(remote_object_info);
+ oid_array_clear(&object_info_oids);
+}
+
static void dispatch_calls(struct batch_options *opt,
struct strbuf *output,
struct expand_data *data,
struct queued_cmd *cmd,
- int nr)
+ size_t nr)
{
- int i;
-
if (!opt->buffer_output)
die(_("flush is only for --buffer mode"));
- for (i = 0; i < nr; i++)
+ for (size_t i = 0; i < nr; i++)
cmd[i].fn(opt, cmd[i].line, output, data);
fflush(stdout);
@@ -736,9 +932,7 @@ static void dispatch_calls(struct batch_options *opt,
static void free_cmds(struct queued_cmd *cmd, size_t *nr)
{
- size_t i;
-
- for (i = 0; i < *nr; i++)
+ for (size_t i = 0; i < *nr; i++)
FREE_AND_NULL(cmd[i].line);
*nr = 0;
@@ -751,9 +945,10 @@ static const struct parse_cmd {
unsigned takes_args;
} commands[] = {
{ "contents", parse_cmd_contents, 1 },
- { "info", parse_cmd_info, 1 },
{ "flush", NULL, 0 },
+ { "info", parse_cmd_info, 1 },
{ "mailmap", parse_cmd_mailmap, 1 },
+ { "remote-object-info", parse_cmd_remote_object_info, 1 },
};
static void batch_objects_command(struct batch_options *opt,
@@ -765,7 +960,6 @@ static void batch_objects_command(struct batch_options *opt,
size_t alloc = 0, nr = 0;
while (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {
- int i;
const struct parse_cmd *cmd = NULL;
const char *p = NULL, *cmd_end;
struct queued_cmd call = {0};
@@ -775,7 +969,7 @@ static void batch_objects_command(struct batch_options *opt,
if (isspace(*input.buf))
die(_("whitespace before command: '%s'"), input.buf);
- for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
if (!skip_prefix(input.buf, commands[i].name, &cmd_end))
continue;
@@ -844,28 +1038,6 @@ static int batch_one_object_oi(const struct object_id *oid,
return payload->callback(oid, NULL, 0, payload->payload);
}
-static int batch_one_object_packed(const struct object_id *oid,
- struct packed_git *pack,
- uint32_t pos,
- void *_payload)
-{
- struct for_each_object_payload *payload = _payload;
- return payload->callback(oid, pack, nth_packed_object_offset(pack, pos),
- payload->payload);
-}
-
-static int batch_one_object_bitmapped(const struct object_id *oid,
- enum object_type type UNUSED,
- int flags UNUSED,
- uint32_t hash UNUSED,
- struct packed_git *pack,
- off_t offset,
- void *_payload)
-{
- struct for_each_object_payload *payload = _payload;
- return payload->callback(oid, pack, offset, payload->payload);
-}
-
static void batch_each_object(struct batch_options *opt,
for_each_object_fn callback,
unsigned flags,
@@ -875,56 +1047,17 @@ static void batch_each_object(struct batch_options *opt,
.callback = callback,
.payload = _payload,
};
+ struct odb_source_info source_info;
+ struct object_info oi = {
+ .source_infop = &source_info,
+ };
struct odb_for_each_object_options opts = {
.flags = flags,
+ .filter = &opt->objects_filter,
};
- struct bitmap_index *bitmap = NULL;
- struct odb_source *source;
- /*
- * TODO: we still need to tap into implementation details of the object
- * database sources. Ideally, we should extend `odb_for_each_object()`
- * to handle object filters itself so that we can move the filtering
- * logic into the individual sources.
- */
- odb_prepare_alternates(the_repository->objects);
- for (source = the_repository->objects->sources; source; source = source->next) {
- struct odb_source_files *files = odb_source_files_downcast(source);
- int ret = odb_source_for_each_object(&files->loose->base, NULL, batch_one_object_oi,
- &payload, &opts);
- if (ret)
- break;
- }
-
- if (opt->objects_filter.choice != LOFC_DISABLED &&
- (bitmap = prepare_bitmap_git(the_repository)) &&
- !for_each_bitmapped_object(bitmap, &opt->objects_filter,
- batch_one_object_bitmapped, &payload)) {
- struct packed_git *pack;
-
- repo_for_each_pack(the_repository, pack) {
- if (bitmap_index_contains_pack(bitmap, pack) ||
- open_pack_index(pack))
- continue;
- for_each_object_in_pack(pack, batch_one_object_packed,
- &payload, flags);
- }
- } else {
- struct odb_source_info source_info;
- struct object_info oi = {
- .source_infop = &source_info,
- };
-
- for (source = the_repository->objects->sources; source; source = source->next) {
- struct odb_source_files *files = odb_source_files_downcast(source);
- int ret = odb_source_for_each_object(&files->packed->base, &oi,
- batch_one_object_oi, &payload, &opts);
- if (ret)
- break;
- }
- }
-
- free_bitmap_index(bitmap);
+ odb_for_each_object_ext(the_repository->objects, &oi,
+ batch_one_object_oi, &payload, &opts);
}
static int batch_objects(struct batch_options *opt)
@@ -1034,6 +1167,7 @@ static int batch_objects(struct batch_options *opt)
cleanup:
strbuf_release(&input);
strbuf_release(&output);
+ string_list_clear(&data.remote_allowed_atoms, 0);
cfg->warn_on_object_refname_ambiguity = save_warning;
return retval;
}
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c
index e42b044..fd1c9c0 100644
--- a/builtin/check-ref-format.c
+++ b/builtin/check-ref-format.c
@@ -45,7 +45,7 @@ static int check_ref_format_branch(const char *arg)
int nongit;
setup_git_directory_gently(the_repository, &nongit);
- if (check_branch_ref(&sb, arg) ||
+ if (check_branch_ref(the_repository, &sb, arg) ||
!skip_prefix(sb.buf, "refs/heads/", &name))
die("'%s' is not a valid branch name", arg);
printf("%s\n", name);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index aee84ca..55e3a89 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -805,7 +805,7 @@ static void setup_branch_path(struct branch_info *branch)
&branch->oid, &branch->refname, 0))
repo_get_oid_committish(the_repository, branch->name, &branch->oid);
- copy_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
+ copy_branchname(the_repository, &buf, branch->name, INTERPRET_BRANCH_LOCAL);
if (strcmp(buf.buf, branch->name)) {
free(branch->name);
branch->name = xstrdup(buf.buf);
diff --git a/builtin/clone.c b/builtin/clone.c
index 9d08cd8..c649a9f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -335,7 +335,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
die_errno(_("failed to create link '%s'"), dest->buf);
option_no_hardlinks = 1;
}
- if (copy_file_with_time(dest->buf, src->buf, 0666))
+ if (copy_file_with_time(the_repository, dest->buf, src->buf, 0666))
die_errno(_("failed to copy file to '%s'"), dest->buf);
}
diff --git a/builtin/config.c b/builtin/config.c
index 8d8ec0b..0882899 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -974,7 +974,7 @@ static void location_options_init(struct config_location_options *opts,
opts->source.file = opts->file_to_free = repo_git_path(the_repository, "config");
opts->source.scope = CONFIG_SCOPE_LOCAL;
} else if (opts->use_worktree_config) {
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees(the_repository);
if (the_repository->repository_format_worktree_config)
opts->source.file = opts->file_to_free =
repo_git_path(the_repository, "config.worktree");
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 26778f8..bc7b2ea 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -552,7 +552,7 @@ static int run_dir_diff(struct repository *repo,
struct stat st;
if (stat(wtdir.buf, &st))
st.st_mode = 0644;
- if (copy_file(rdir.buf, wtdir.buf,
+ if (copy_file(repo, rdir.buf, wtdir.buf,
st.st_mode)) {
ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
goto finish;
@@ -636,7 +636,7 @@ static int run_dir_diff(struct repository *repo,
struct lock_file lock = LOCK_INIT;
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/wtindex", tmpdir.buf);
- if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+ if (repo_hold_lock_file_for_update(repo, &lock, buf.buf, 0) < 0 ||
write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
ret = error("could not write %s", buf.buf);
goto finish;
@@ -658,7 +658,7 @@ static int run_dir_diff(struct repository *repo,
warning("%s", "");
err = 1;
} else if (unlink(wtdir.buf) ||
- copy_file(wtdir.buf, rdir.buf, st.st_mode))
+ copy_file(repo, wtdir.buf, rdir.buf, st.st_mode))
warning_errno(_("could not copy '%s' to '%s'"),
rdir.buf, wtdir.buf);
}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 0be4310..50278be 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -33,7 +33,7 @@
#include "gpg-interface.h"
static const char *const fast_export_usage[] = {
- N_("git fast-export [<rev-list-opts>]"),
+ N_("git fast-export [<options>] [<revision-range>] [[--] <path>...]"),
NULL
};
@@ -51,7 +51,7 @@ static int show_original_ids;
static int mark_tags;
static struct string_list extra_refs = STRING_LIST_INIT_DUP;
static struct string_list tag_refs = STRING_LIST_INIT_DUP;
-static struct refspec refspecs = REFSPEC_INIT_FETCH;
+static struct refspec refspecs;
static int anonymize;
static struct hashmap anonymized_seeds;
static struct revision_sources revision_sources;
@@ -1372,6 +1372,8 @@ int cmd_fast_export(int argc,
/* we handle encodings */
repo_config(the_repository, git_default_config, NULL);
+ refspec_init_fetch(&refspecs, the_hash_algo);
+
repo_init_revisions(the_repository, &revs, prefix);
init_revision_sources(&revision_sources);
revs.topo_order = 1;
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 775a797..ab7db2b 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -96,7 +96,7 @@ static struct string_list deepen_not = STRING_LIST_INIT_NODUP;
static struct strbuf default_rla = STRBUF_INIT;
static struct transport *gtransport;
static struct transport *gsecondary;
-static struct refspec refmap = REFSPEC_INIT_FETCH;
+static struct refspec refmap;
static struct string_list server_options = STRING_LIST_INIT_DUP;
static struct string_list negotiation_restrict = STRING_LIST_INIT_NODUP;
static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
@@ -601,7 +601,8 @@ static struct ref *get_ref_map(struct remote *remote,
struct refspec_item tag_refspec;
/* also fetch all tags */
- refspec_item_init_push(&tag_refspec, TAG_REFSPEC);
+ refspec_item_init_push(&tag_refspec, TAG_REFSPEC,
+ the_hash_algo);
get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
refspec_item_clear(&tag_refspec);
} else if (tags == TAGS_DEFAULT && *autotags) {
@@ -2428,7 +2429,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
const struct fetch_config *config,
struct list_objects_filter_options *filter_options)
{
- struct refspec rs = REFSPEC_INIT_FETCH;
+ struct refspec rs = REFSPEC_INIT_FETCH(the_hash_algo);
int i;
int exit_code;
int maybe_prune_tags;
@@ -2630,6 +2631,8 @@ int cmd_fetch(int argc,
filter_options.allow_auto_filter = 1;
+ refspec_init_fetch(&refmap, the_hash_algo);
+
packet_trace_identity("fetch");
/* Record the command line for the reflog */
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 76b723f..a6c054e 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -632,7 +632,7 @@ static void snapshot_refs(struct repository *repo,
refs_for_each_ref_ext(get_main_ref_store(repo),
snapshot_ref, &data, &opts);
- worktrees = get_worktrees();
+ worktrees = get_worktrees(repo);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
struct strbuf refname = STRBUF_INIT;
@@ -685,7 +685,7 @@ static void process_refs(struct repository *repo, struct snapshot *snap)
}
if (include_reflogs) {
- worktrees = get_worktrees();
+ worktrees = get_worktrees(repo);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
@@ -1121,7 +1121,7 @@ int cmd_fsck(int argc,
verify_index_checksum = 1;
verify_ce_order = 1;
- worktrees = get_worktrees();
+ worktrees = get_worktrees(repo);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
struct index_state istate =
diff --git a/builtin/gc.c b/builtin/gc.c
index d32af42..49c8474 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -412,7 +412,7 @@ static int worktree_prune_condition(struct gc_config *cfg)
while (limit && (d = readdir_skip_dot_and_dotdot(dir))) {
char *wtpath;
strbuf_reset(&buf);
- if (should_prune_worktree(d->d_name, &buf, &wtpath, expiry_date))
+ if (should_prune_worktree(the_repository, d->d_name, &buf, &wtpath, expiry_date))
limit--;
free(wtpath);
}
@@ -1790,7 +1790,7 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
struct repository *r = the_repository;
char *lock_path = xstrfmt("%s/maintenance", r->objects->sources->path);
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ if (repo_hold_lock_file_for_update(r, &lk, lock_path, LOCK_NO_DEREF) < 0) {
/*
* Another maintenance command is running.
*
diff --git a/builtin/history.c b/builtin/history.c
index d28c1f0..000155a 100644
--- a/builtin/history.c
+++ b/builtin/history.c
@@ -795,7 +795,7 @@ static int write_ondisk_index(struct repository *repo,
prime_cache_tree(repo, &index, tree);
- if (hold_lock_file_for_update(&lock, path, 0) < 0) {
+ if (repo_hold_lock_file_for_update(repo, &lock, path, 0) < 0) {
ret = error_errno(_("unable to acquire index lock"));
goto out;
}
diff --git a/builtin/merge.c b/builtin/merge.c
index 5b46a59..58d1b7b 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -553,7 +553,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
char *found_ref = NULL;
int len, early;
- copy_branchname(&bname, remote, 0);
+ copy_branchname(the_repository, &bname, remote, 0);
remote = bname.buf;
oidclr(&branch_head, the_repository->hash_algo);
diff --git a/builtin/notes.c b/builtin/notes.c
index 962df86..9f1f0ec 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -989,7 +989,7 @@ static int merge(int argc, const char **argv, const char *prefix,
"NOTES_MERGE_PARTIAL", &result_oid, NULL,
0, UPDATE_REFS_DIE_ON_ERR);
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
wt = find_shared_symref(worktrees, "NOTES_MERGE_REF",
notes_ref);
if (wt)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index ea5eab4..1ec5b6f 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -32,6 +32,7 @@
#include "list.h"
#include "packfile.h"
#include "object-file.h"
+#include "object-file-convert.h"
#include "odb.h"
#include "odb/streaming.h"
#include "replace-object.h"
@@ -1867,8 +1868,8 @@ static const char no_closure_warning[] = N_(
"disabling bitmap writing, as some objects are not being packed"
);
-static int add_object_entry(const struct object_id *oid, enum object_type type,
- const char *name, int exclude)
+static void add_object_entry(const struct object_id *oid, enum object_type type,
+ const char *name, int exclude)
{
struct packed_git *found_pack = NULL;
off_t found_offset = 0;
@@ -1876,7 +1877,7 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
display_progress(progress_state, ++nr_seen);
if (have_duplicate_entry(oid, exclude))
- return 0;
+ return;
if (!want_object_in_pack(oid, exclude, &found_pack, &found_offset)) {
/* The pack is missing an object, so it will not have closure */
@@ -1885,13 +1886,12 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
warning(_(no_closure_warning));
write_bitmap_index = 0;
}
- return 0;
+ return;
}
create_object_entry(oid, type, pack_name_hash_fn(name),
exclude, name && no_try_delta(name),
found_pack, found_offset);
- return 1;
}
static int add_object_entry_from_bitmap(const struct object_id *oid,
@@ -1909,7 +1909,7 @@ static int add_object_entry_from_bitmap(const struct object_id *oid,
return 0;
create_object_entry(oid, type, name_hash, 0, 0, pack, offset);
- return 1;
+ return 0;
}
struct pbase_tree_cache {
@@ -4622,6 +4622,51 @@ static int loosened_object_can_be_discarded(const struct object_id *oid,
return 1;
}
+static int force_object_loose(struct odb_source *source,
+ const struct object_id *oid,
+ const time_t *mtime)
+{
+ struct odb_source_files *files = odb_source_files_downcast(source);
+ const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
+ struct object_info oi = OBJECT_INFO_INIT;
+ struct object_id compat_oid, *compat_oid_p = NULL;
+ enum object_type type;
+ void *buf = NULL;
+ size_t len;
+ int ret;
+
+ for (struct odb_source *s = source->odb->sources; s; s = s->next) {
+ struct odb_source_files *files = odb_source_files_downcast(s);
+ if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0))
+ return 0;
+ }
+
+ oi.typep = &type;
+ oi.sizep = &len;
+ oi.contentp = &buf;
+ if (odb_read_object_info_extended(source->odb, oid, &oi, 0)) {
+ ret = error(_("cannot read object for %s"), oid_to_hex(oid));
+ goto out;
+ }
+
+ if (compat) {
+ if (repo_oid_to_algop(source->odb->repo, oid, compat, &compat_oid)) {
+ ret = error(_("cannot map object %s to %s"),
+ oid_to_hex(oid), compat->name);
+ goto out;
+ }
+
+ compat_oid_p = &compat_oid;
+ }
+
+ ret = odb_source_write_object(&files->loose->base, buf, len, type, oid,
+ compat_oid_p, mtime, 0);
+
+out:
+ free(buf);
+ return ret;
+}
+
static void loosen_unused_packed_objects(void)
{
struct packed_git *p;
@@ -4642,7 +4687,7 @@ static void loosen_unused_packed_objects(void)
!has_sha1_pack_kept_or_nonlocal(&oid) &&
!loosened_object_can_be_discarded(&oid, p->mtime)) {
if (force_object_loose(the_repository->objects->sources,
- &oid, p->mtime))
+ &oid, &p->mtime))
die(_("unable to force loose object"));
loosened_objects_nr++;
}
diff --git a/builtin/pull.c b/builtin/pull.c
index d49b091..db3ee0a 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -612,7 +612,7 @@ static const char *get_tracking_branch(const char *remote, const char *refspec)
const char *spec_src;
const char *merge_branch;
- if (!refspec_item_init_fetch(&spec, refspec))
+ if (!refspec_item_init_fetch(&spec, refspec, the_hash_algo))
die(_("invalid refspec '%s'"), refspec);
spec_src = spec.src;
if (!*spec_src || !strcmp(spec_src, "HEAD"))
diff --git a/builtin/push.c b/builtin/push.c
index 1b2ad3b..2377b5a 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -66,7 +66,7 @@ static enum transport_family family;
static struct push_cas_option cas;
-static struct refspec rs = REFSPEC_INIT_PUSH;
+static struct refspec rs;
static struct string_list push_options_config = STRING_LIST_INIT_DUP;
@@ -74,6 +74,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,
struct remote *remote, struct ref *matched)
{
const char *branch_name;
+ struct repo_config_values *cfg = repo_config_values(the_repository);
if (remote->push.nr) {
struct refspec_item query = {
@@ -89,7 +90,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,
}
}
- if (push_default == PUSH_DEFAULT_UPSTREAM &&
+ if (cfg->push_default == PUSH_DEFAULT_UPSTREAM &&
skip_prefix(matched->name, "refs/heads/", &branch_name)) {
struct branch *branch = branch_get(branch_name);
if (branch->merge_nr == 1 && branch->merge[0]->src) {
@@ -161,7 +162,7 @@ static NORETURN void die_push_simple(struct branch *branch,
* Don't show advice for people who explicitly set
* push.default.
*/
- if (push_default == PUSH_DEFAULT_UNSPECIFIED)
+ if (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)
advice_pushdefault_maybe = _("\n"
"To choose either option permanently, "
"see push.default in 'git help config'.\n");
@@ -232,8 +233,9 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)
struct branch *branch;
const char *dst;
int same_remote;
+ struct repo_config_values *cfg = repo_config_values(the_repository);
- switch (push_default) {
+ switch (cfg->push_default) {
case PUSH_DEFAULT_MATCHING:
refspec_append(&rs, ":");
return;
@@ -253,7 +255,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)
dst = branch->refname;
same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));
- switch (push_default) {
+ switch (cfg->push_default) {
default:
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
@@ -749,6 +751,8 @@ int cmd_push(int argc,
: &push_options_config);
set_push_cert_flags(&flags, push_cert);
+ refspec_init_push(&rs, the_hash_algo);
+
die_for_incompatible_opt4(deleterefs, "--delete",
tags, "--tags",
flags & TRANSPORT_PUSH_ALL, "--all/--branches",
@@ -855,7 +859,7 @@ int cmd_push(int argc,
}
refspec_clear(&rs);
- rs = (struct refspec) REFSPEC_INIT_PUSH;
+ rs = (struct refspec) REFSPEC_INIT_PUSH(the_hash_algo);
if (tags)
refspec_append(&rs, "refs/tags/*");
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index faf0f12..86933d8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -37,7 +37,6 @@
#include "sigchain.h"
#include "string-list.h"
#include "strvec.h"
-#include "tmp-objdir.h"
#include "trace.h"
#include "trace2.h"
#include "version.h"
@@ -112,8 +111,6 @@ static enum {
} use_keepalive;
static int keepalive_in_sec = 5;
-static struct tmp_objdir *tmp_objdir;
-
static struct proc_receive_ref {
unsigned int want_add:1,
want_delete:1,
@@ -926,6 +923,7 @@ static void receive_hook_feed_state_free(void *data)
static int run_receive_hook(struct command *commands,
const char *hook_name,
int skip_broken,
+ struct odb_transaction *transaction,
const struct string_list *push_options)
{
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
@@ -959,8 +957,8 @@ static int run_receive_hook(struct command *commands,
strvec_push(&opt.env, "GIT_PUSH_OPTION_COUNT");
}
- if (tmp_objdir)
- strvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir));
+ if (transaction)
+ odb_transaction_env(transaction, &opt.env);
prepare_push_cert_sha1(&opt);
@@ -1363,7 +1361,6 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
!delayed_reachability_test(si, i))
oid_array_append(&extra, &si->shallow->oid[i]);
- opt.env = tmp_objdir_env(tmp_objdir);
setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
if (check_connected(command_singleton_iterator, cmd, &opt)) {
rollback_shallow_file(the_repository, &shallow_lock);
@@ -1503,7 +1500,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
struct object_id *old_oid = &cmd->old_oid;
struct object_id *new_oid = &cmd->new_oid;
int do_update_worktree = 0;
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees(the_repository);
const struct worktree *worktree =
find_shared_symref(worktrees, "HEAD", name);
@@ -1790,24 +1787,30 @@ static const struct object_id *command_singleton_iterator(void *cb_data)
}
static void set_connectivity_errors(struct command *commands,
- struct shallow_info *si)
+ struct shallow_info *si,
+ struct odb_transaction *transaction)
{
struct command *cmd;
for (cmd = commands; cmd; cmd = cmd->next) {
struct command *singleton = cmd;
struct check_connected_options opt = CHECK_CONNECTED_INIT;
+ struct strvec env = STRVEC_INIT;
if (shallow_update && si->shallow_ref[cmd->index])
/* to be checked in update_shallow_ref() */
continue;
- opt.env = tmp_objdir_env(tmp_objdir);
+ odb_transaction_env(transaction, &env);
+ opt.env = env.v;
+
if (!check_connected(command_singleton_iterator, &singleton,
&opt))
continue;
cmd->error_string = "missing necessary objects";
+
+ strvec_clear(&env);
}
}
@@ -2028,6 +2031,7 @@ static void execute_commands_atomic(struct command *commands,
static void execute_commands(struct command *commands,
const char *unpacker_error,
struct shallow_info *si,
+ struct odb_transaction *transaction,
const struct string_list *push_options)
{
struct check_connected_options opt = CHECK_CONNECTED_INIT;
@@ -2044,6 +2048,8 @@ static void execute_commands(struct command *commands,
}
if (!skip_connectivity_check) {
+ struct strvec env = STRVEC_INIT;
+
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
@@ -2057,14 +2063,17 @@ static void execute_commands(struct command *commands,
data.si = si;
opt.err_fd = err_fd;
opt.progress = err_fd && !quiet;
- opt.env = tmp_objdir_env(tmp_objdir);
+ odb_transaction_env(transaction, &env);
+ opt.env = env.v;
opt.exclude_hidden_refs_section = "receive";
if (check_connected(iterate_receive_command_list, &data, &opt))
- set_connectivity_errors(commands, si);
+ set_connectivity_errors(commands, si, transaction);
if (use_sideband)
finish_async(&muxer);
+
+ strvec_clear(&env);
}
reject_updates_to_hidden(commands);
@@ -2085,7 +2094,7 @@ static void execute_commands(struct command *commands,
}
}
- if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
+ if (run_receive_hook(commands, "pre-receive", 0, transaction, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "pre-receive hook declined";
@@ -2106,14 +2115,13 @@ static void execute_commands(struct command *commands,
* Now we'll start writing out refs, which means the objects need
* to be in their final positions so that other processes can see them.
*/
- if (tmp_objdir_migrate(tmp_objdir) < 0) {
+ if (odb_transaction_commit(transaction)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "unable to migrate objects to permanent storage";
}
return;
}
- tmp_objdir = NULL;
check_aliased_updates(commands);
@@ -2326,7 +2334,8 @@ static void push_header_arg(struct strvec *args, struct pack_header *hdr)
ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries));
}
-static const char *unpack(int err_fd, struct shallow_info *si)
+static const char *unpack(int err_fd, struct shallow_info *si,
+ struct odb_transaction *transaction)
{
struct pack_header hdr;
const char *hdr_err;
@@ -2351,20 +2360,7 @@ static const char *unpack(int err_fd, struct shallow_info *si)
strvec_push(&child.args, alt_shallow_file);
}
- tmp_objdir = tmp_objdir_create(the_repository, "incoming");
- if (!tmp_objdir) {
- if (err_fd > 0)
- close(err_fd);
- return "unable to create temporary object directory";
- }
- strvec_pushv(&child.env, tmp_objdir_env(tmp_objdir));
-
- /*
- * Normally we just pass the tmp_objdir environment to the child
- * processes that do the heavy lifting, but we may need to see these
- * objects ourselves to set up shallow information.
- */
- tmp_objdir_add_as_alternate(tmp_objdir);
+ odb_transaction_env(transaction, &child.env);
if (ntohl(hdr.hdr_entries) < unpack_limit) {
strvec_push(&child.args, "unpack-objects");
@@ -2431,13 +2427,14 @@ static const char *unpack(int err_fd, struct shallow_info *si)
return NULL;
}
-static const char *unpack_with_sideband(struct shallow_info *si)
+static const char *unpack_with_sideband(struct shallow_info *si,
+ struct odb_transaction *transaction)
{
struct async muxer;
const char *ret;
if (!use_sideband)
- return unpack(0, si);
+ return unpack(0, si, transaction);
use_keepalive = KEEPALIVE_AFTER_NUL;
memset(&muxer, 0, sizeof(muxer));
@@ -2446,7 +2443,7 @@ static const char *unpack_with_sideband(struct shallow_info *si)
if (start_async(&muxer))
return NULL;
- ret = unpack(muxer.in, si);
+ ret = unpack(muxer.in, si, transaction);
finish_async(&muxer);
return ret;
@@ -2623,6 +2620,7 @@ int cmd_receive_pack(int argc,
struct oid_array ref = OID_ARRAY_INIT;
struct shallow_info si;
struct packet_reader reader;
+ struct odb_transaction *transaction = NULL;
struct option options[] = {
OPT__QUIET(&quiet, N_("quiet")),
@@ -2707,11 +2705,14 @@ int cmd_receive_pack(int argc,
if (!si.nr_ours && !si.nr_theirs)
shallow_update = 0;
if (!delete_only(commands)) {
- unpack_status = unpack_with_sideband(&si);
+ if (odb_transaction_begin(the_repository->objects, &transaction, ODB_TRANSACTION_RECEIVE))
+ unpack_status = "unable to start object transaction";
+ else
+ unpack_status = unpack_with_sideband(&si, transaction);
update_shallow_info(commands, &si, &ref);
}
use_keepalive = KEEPALIVE_ALWAYS;
- execute_commands(commands, unpack_status, &si,
+ execute_commands(commands, unpack_status, &si, transaction,
&push_options);
delete_tempfile(&pack_lockfile);
sigchain_push(SIGPIPE, SIG_IGN);
@@ -2720,7 +2721,7 @@ int cmd_receive_pack(int argc,
else if (report_status)
report(commands, unpack_status);
sigchain_pop(SIGPIPE);
- run_receive_hook(commands, "post-receive", 1,
+ run_receive_hook(commands, "post-receive", 1, NULL,
&push_options);
run_update_post_hook(commands);
free_commands(commands);
diff --git a/builtin/reflog.c b/builtin/reflog.c
index dcbfe89..1211c58 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -250,7 +250,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix,
struct string_list_item *item;
struct worktree **worktrees, **p;
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
for (p = worktrees; *p; p++) {
if (single_worktree && !(*p)->is_current)
continue;
@@ -374,7 +374,7 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix,
struct string_list_item *item;
struct worktree **worktrees, **p;
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
for (p = worktrees; *p; p++) {
if (single_worktree && !(*p)->is_current)
continue;
diff --git a/builtin/refs.c b/builtin/refs.c
index a9ca205..5cd21c2 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -113,7 +113,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
repo_config(repo, git_fsck_config, &fsck_refs_options);
prepare_repo_settings(repo);
- worktrees = get_worktrees_without_reading_head();
+ worktrees = get_worktrees_without_reading_head(repo);
for (size_t i = 0; worktrees[i]; i++)
ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
&fsck_refs_options, worktrees[i]);
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 8f63003..02818b8 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -486,7 +486,7 @@ static int show_object_fast(
void *payload UNUSED)
{
fprintf(stdout, "%s\n", oid_to_hex(oid));
- return 1;
+ return 0;
}
static void print_disk_usage(off_t size)
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 1412b49..d6cdbae 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -153,7 +153,7 @@ int cmd_send_pack(int argc,
const char *prefix,
struct repository *repo)
{
- struct refspec rs = REFSPEC_INIT_PUSH;
+ struct refspec rs;
const char *remote_name = NULL;
struct remote *remote = NULL;
const char *dest = NULL;
@@ -214,6 +214,9 @@ int cmd_send_pack(int argc,
repo_config(repo, send_pack_config, NULL);
argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0);
+
+ refspec_init_push(&rs, repo->hash_algo);
+
if (argc > 0) {
dest = argv[0];
refspec_appendn(&rs, argv + 1, argc - 1);
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 0863d0f..cb4a037 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -341,7 +341,8 @@ static int write_patterns_and_update(struct repository *repo,
if (safe_create_leading_directories(repo, sparse_filename))
die(_("failed to create directory for sparse-checkout file"));
- hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR);
+ repo_hold_lock_file_for_update(repo, &lk, sparse_filename,
+ LOCK_DIE_ON_ERROR);
result = update_working_directory(repo, pl);
if (result) {
diff --git a/builtin/stash.c b/builtin/stash.c
index c4809f2..72c5257 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1702,8 +1702,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
if (!include_untracked && ps->nr) {
char *ps_matched = xcalloc(ps->nr, 1);
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(the_repository->index);
+ if (pathspec_needs_expanded_index(the_repository->index, ps))
+ ensure_full_index(the_repository->index);
for (size_t i = 0; i < the_repository->index->cache_nr; i++)
ce_path_match(the_repository->index, the_repository->index->cache[i], ps,
ps_matched);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 510f193..e7cd322 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -549,7 +549,8 @@ static void create_default_gitdir_config(const char *submodule_name)
}
/* Case 2.4: If all the above failed, try a hash of the name as a last resort */
- header_len = snprintf(header, sizeof(header), "blob %zu", strlen(submodule_name));
+ header_len = snprintf(header, sizeof(header),
+ "blob %"PRIuMAX, (uintmax_t)strlen(submodule_name));
git_hash_init(&ctx, the_hash_algo);
git_hash_update(&ctx, header, header_len);
git_hash_update(&ctx, "\0", 1);
@@ -2990,7 +2991,7 @@ static int module_update(int argc, const char **argv, const char *prefix,
struct option module_update_options[] = {
OPT__SUPER_PREFIX(&opt.super_prefix),
OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
- OPT_BOOL(0, "init", &opt.init,
+ OPT_BOOL('i', "init", &opt.init,
N_("initialize uninitialized submodules before update")),
OPT_BOOL(0, "remote", &opt.remote,
N_("use SHA-1 of submodule's remote tracking branch")),
@@ -3150,7 +3151,7 @@ static int push_check(int argc, const char **argv, const char *prefix UNUSED,
if (argc > 2) {
int i;
struct ref *local_refs = get_local_heads();
- struct refspec refspec = REFSPEC_INIT_PUSH;
+ struct refspec refspec = REFSPEC_INIT_PUSH(the_hash_algo);
refspec_appendn(&refspec, argv + 2, argc - 2);
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 93a9caa..4263edf 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -598,7 +598,7 @@ static void unpack_all(void)
progress = start_progress(the_repository,
_("Unpacking objects"), nr_objects);
CALLOC_ARRAY(obj_list, nr_objects);
- transaction = odb_transaction_begin(the_repository->objects);
+ odb_transaction_begin_or_die(the_repository->objects, &transaction, 0);
for (i = 0; i < nr_objects; i++) {
unpack_one(i);
display_progress(progress, i + 1);
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 5d80d48..241abd4 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -294,7 +294,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len
ce->ce_flags = create_ce_flags(0);
ce->ce_namelen = len;
fill_stat_cache_info(the_repository->index, ce, st);
- ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
+ ce->ce_mode = ce_mode_from_stat(the_repository, old, st->st_mode);
if (index_path(the_repository->index, &ce->oid, path, st,
info_only ? 0 : INDEX_WRITE_OBJECT)) {
@@ -1124,7 +1124,7 @@ int cmd_update_index(int argc,
* Allow the object layer to optimize adding multiple objects in
* a batch.
*/
- transaction = odb_transaction_begin(the_repository->objects);
+ odb_transaction_begin_or_die(the_repository->objects, &transaction, 0);
while (ctx.argc) {
if (parseopt_state != PARSE_OPT_DONE)
parseopt_state = parse_options_step(&ctx, options,
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 4bc7b4f..654d27c 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -226,7 +226,7 @@ static void prune_worktrees(void)
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
char *path;
strbuf_reset(&reason);
- if (should_prune_worktree(d->d_name, &reason, &path, expire))
+ if (should_prune_worktree(the_repository, d->d_name, &reason, &path, expire))
prune_worktree(d->d_name, reason.buf);
else if (path)
string_list_append_nodup(&kept, path)->util = xstrdup(d->d_name);
@@ -349,7 +349,7 @@ static void copy_sparse_checkout(const char *worktree_git_dir)
if (file_exists(from_file)) {
if (safe_create_leading_directories(the_repository, to_file) ||
- copy_file(to_file, from_file, 0666))
+ copy_file(the_repository, to_file, from_file, 0666))
error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"),
from_file, to_file);
}
@@ -368,7 +368,7 @@ static void copy_filtered_worktree_config(const char *worktree_git_dir)
int bare;
if (safe_create_leading_directories(the_repository, to_file) ||
- copy_file(to_file, from_file, 0666)) {
+ copy_file(the_repository, to_file, from_file, 0666)) {
error(_("failed to copy worktree config from '%s' to '%s'"),
from_file, to_file);
goto worktree_copy_cleanup;
@@ -475,13 +475,13 @@ static int add_worktree(const char *path, const char *refname,
struct ref_store *wt_refs;
struct repo_config_values *cfg = repo_config_values(the_repository);
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
check_candidate_path(path, opts->force, worktrees, "add");
free_worktrees(worktrees);
worktrees = NULL;
/* is 'refname' a branch or commit? */
- if (!opts->detach && !check_branch_ref(&symref, refname) &&
+ if (!opts->detach && !check_branch_ref(the_repository, &symref, refname) &&
refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) {
is_branch = 1;
if (!opts->force)
@@ -539,7 +539,8 @@ static int add_worktree(const char *path, const char *refname,
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
- write_worktree_linking_files(sb_git.buf, sb.buf, opts->relative_paths);
+ write_worktree_linking_files(the_repository, sb_git.buf,
+ sb.buf, opts->relative_paths);
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
@@ -547,7 +548,7 @@ static int add_worktree(const char *path, const char *refname,
/*
* Set up the ref store of the worktree and create the HEAD reference.
*/
- wt = get_linked_worktree(name, 1);
+ wt = get_linked_worktree(the_repository, name, 1);
if (!wt) {
ret = error(_("could not find created worktree '%s'"), name);
goto done;
@@ -649,7 +650,7 @@ static void print_preparing_worktree_line(int detach,
fprintf_ln(stderr, _("Preparing worktree (new branch '%s')"), new_branch);
} else {
struct strbuf s = STRBUF_INIT;
- if (!detach && !check_branch_ref(&s, branch) &&
+ if (!detach && !check_branch_ref(the_repository, &s, branch) &&
refs_ref_exists(get_main_ref_store(the_repository), s.buf))
fprintf_ln(stderr, _("Preparing worktree (checking out '%s')"),
branch);
@@ -771,7 +772,7 @@ static char *dwim_branch(const char *path, char **new_branch)
char *branchname = xstrndup(s, n);
struct strbuf ref = STRBUF_INIT;
- branch_exists = !check_branch_ref(&ref, branchname) &&
+ branch_exists = !check_branch_ref(the_repository, &ref, branchname) &&
refs_ref_exists(get_main_ref_store(the_repository),
ref.buf);
strbuf_release(&ref);
@@ -868,7 +869,7 @@ static int add(int ac, const char **av, const char *prefix,
new_branch = new_branch_force;
if (!opts.force &&
- !check_branch_ref(&symref, new_branch) &&
+ !check_branch_ref(the_repository, &symref, new_branch) &&
refs_ref_exists(get_main_ref_store(the_repository), symref.buf))
die_if_checked_out(symref.buf, 0);
strbuf_release(&symref);
@@ -1106,7 +1107,7 @@ static int list(int ac, const char **av, const char *prefix,
else if (!line_terminator && !porcelain)
die(_("the option '%s' requires '%s'"), "-z", "--porcelain");
else {
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees(the_repository);
int path_maxwidth = 0, abbrev = DEFAULT_ABBREV, i;
struct worktree_display *display = NULL;
@@ -1149,7 +1150,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix,
if (ac != 1)
usage_with_options(git_worktree_lock_usage, options);
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -1186,7 +1187,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix,
if (ac != 1)
usage_with_options(git_worktree_unlock_usage, options);
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -1272,7 +1273,7 @@ static int move_worktree(int ac, const char **av, const char *prefix,
strbuf_addstr(&dst, path);
free(path);
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -1397,7 +1398,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix,
if (ac != 1)
usage_with_options(git_worktree_remove_usage, options);
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -1459,8 +1460,9 @@ static int repair(int ac, const char **av, const char *prefix,
ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0);
p = ac > 0 ? av : self;
for (; *p; p++)
- repair_worktree_at_path(*p, report_repair, &rc, use_relative_paths);
- repair_worktrees(report_repair, &rc, use_relative_paths);
+ repair_worktree_at_path(the_repository, *p, report_repair,
+ &rc, use_relative_paths);
+ repair_worktrees(the_repository, report_repair, &rc, use_relative_paths);
return rc;
}
diff --git a/bundle-uri.c b/bundle-uri.c
index 9ca2060..2bb2eb9 100644
--- a/bundle-uri.c
+++ b/bundle-uri.c
@@ -396,7 +396,7 @@ static int copy_uri_to_file(const char *filename, const char *uri)
uri = out;
/* Copy as a file */
- return copy_file(filename, uri, 0);
+ return copy_file(the_repository, filename, uri, 0);
}
static int unbundle_from_file(struct repository *r, const char *file)
diff --git a/bundle.c b/bundle.c
index fd2db2c..b64716f 100644
--- a/bundle.c
+++ b/bundle.c
@@ -519,8 +519,8 @@ int create_bundle(struct repository *r, const char *path,
if (bundle_to_stdout)
bundle_fd = 1;
else
- bundle_fd = hold_lock_file_for_update(&lock, path,
- LOCK_DIE_ON_ERROR);
+ bundle_fd = repo_hold_lock_file_for_update(r, &lock, path,
+ LOCK_DIE_ON_ERROR);
if (version == -1)
version = min_version;
diff --git a/cache-tree.c b/cache-tree.c
index 9204eb3..d92f513 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -516,6 +516,7 @@ static int update_one(struct cache_tree *it,
int cache_tree_update(struct index_state *istate, int flags)
{
+ int inflight = !!the_repository->objects->transaction;
struct odb_transaction *transaction;
int skip, i;
@@ -532,10 +533,12 @@ int cache_tree_update(struct index_state *istate, int flags)
trace_performance_enter();
trace2_region_enter("cache_tree", "update", istate->repo);
- transaction = odb_transaction_begin(the_repository->objects);
+ if (!inflight)
+ odb_transaction_begin_or_die(the_repository->objects, &transaction, 0);
i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
"", 0, &skip, flags);
- odb_transaction_commit(transaction);
+ if (!inflight)
+ odb_transaction_commit(transaction);
trace2_region_leave("cache_tree", "update", istate->repo);
trace_performance_leave("cache_tree_update");
if (i < 0)
diff --git a/combine-diff.c b/combine-diff.c
index fb72174..717d537 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -1080,7 +1080,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
/* if symlinks don't work, assume symlink if all parents
* are symlinks
*/
- is_file = has_symlinks;
+ is_file = repo_has_symlinks(rev->repo);
for (i = 0; !is_file && i < num_parent; i++)
is_file = !S_ISLNK(elem->parent[i].mode);
if (!is_file)
diff --git a/commit-graph.c b/commit-graph.c
index ce2e020..49e8f63 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -2127,8 +2127,8 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
if (ctx->split) {
char *lock_name = get_commit_graph_chain_filename(ctx->odb_source);
- hold_lock_file_for_update_mode(&lk, lock_name,
- LOCK_DIE_ON_ERROR, 0444);
+ repo_hold_lock_file_for_update_mode(ctx->r, &lk, lock_name,
+ LOCK_DIE_ON_ERROR, 0444);
free(lock_name);
graph_layer = mks_tempfile_m(ctx->graph_name, 0444);
@@ -2146,8 +2146,9 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
f = hashfd(ctx->r->hash_algo,
get_tempfile_fd(graph_layer), get_tempfile_path(graph_layer));
} else {
- hold_lock_file_for_update_mode(&lk, ctx->graph_name,
- LOCK_DIE_ON_ERROR, 0444);
+ repo_hold_lock_file_for_update_mode(ctx->r, &lk,
+ ctx->graph_name,
+ LOCK_DIE_ON_ERROR, 0444);
f = hashfd(ctx->r->hash_algo,
get_lock_file_fd(&lk), get_lock_file_path(&lk));
}
diff --git a/commit-reach.c b/commit-reach.c
index d7221fe..b53c6b1 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -761,7 +761,8 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
/*
* Test whether the candidate is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
+ * Do not recurse to find out, though, but return CONTAINS_UNKNOWN if
+ * inconclusive.
*/
static enum contains_result contains_test(struct commit *candidate,
const struct commit_list *want,
@@ -818,6 +819,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
if (result != CONTAINS_UNKNOWN)
return result;
+ *contains_cache_at(cache, candidate) = CONTAINS_IN_PROGRESS;
push_to_contains_stack(candidate, &contains_stack);
while (contains_stack.nr) {
struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
@@ -829,8 +831,8 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
contains_stack.nr--;
}
/*
- * If we just popped the stack, parents->item has been marked,
- * therefore contains_test will return a meaningful yes/no.
+ * A parent may have just been popped and marked, or may still
+ * be active when replacement refs create a cycle.
*/
else switch (contains_test(parents->item, want, cache, cutoff)) {
case CONTAINS_YES:
@@ -840,7 +842,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
case CONTAINS_NO:
entry->parents = parents->next;
break;
+ case CONTAINS_IN_PROGRESS:
+ die(_("commit ancestry contains a cycle"));
case CONTAINS_UNKNOWN:
+ *contains_cache_at(cache, parents->item) =
+ CONTAINS_IN_PROGRESS;
push_to_contains_stack(parents->item, &contains_stack);
break;
}
@@ -852,9 +858,16 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
int commit_contains(struct ref_filter *filter, struct commit *commit,
struct commit_list *list, struct contains_cache *cache)
{
- if (filter->with_commit_tag_algo)
+ int result;
+
+ if (filter->with_commit_tag_algo ||
+ generation_numbers_enabled(the_repository))
return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
- return repo_is_descendant_of(the_repository, commit, list);
+
+ result = repo_is_descendant_of(the_repository, commit, list);
+ if (result < 0)
+ die(_("failed to check reachability"));
+ return result;
}
int can_all_from_reach_with_flag(struct object_array *from,
diff --git a/commit-reach.h b/commit-reach.h
index 3f3a563..f908d30 100644
--- a/commit-reach.h
+++ b/commit-reach.h
@@ -73,7 +73,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
enum contains_result {
CONTAINS_UNKNOWN = 0,
CONTAINS_NO,
- CONTAINS_YES
+ CONTAINS_YES,
+ CONTAINS_IN_PROGRESS
};
define_commit_slab(contains_cache, enum contains_result);
diff --git a/compat/mingw.c b/compat/mingw.c
index 3eca3a7..e0fbd2c 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -7,6 +7,7 @@
#include "config.h"
#include "dir.h"
#include "environment.h"
+#include "repository.h"
#include "gettext.h"
#include "run-command.h"
#include "strbuf.h"
@@ -1044,7 +1045,7 @@ int mingw_chdir(const char *dirname)
if (xutftowcs_path(wdirname, dirname) < 0)
return -1;
- if (has_symlinks) {
+ if (repo_has_symlinks(the_repository)) {
HANDLE hnd = CreateFileW(wdirname, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
@@ -2914,7 +2915,7 @@ int symlink(const char *target, const char *link)
int len;
/* fail if symlinks are disabled or API is not supported (WinXP) */
- if (!has_symlinks) {
+ if (!repo_has_symlinks(the_repository)) {
errno = ENOSYS;
return -1;
}
@@ -3184,15 +3185,23 @@ static void setup_windows_environment(void)
if (!tmp && (tmp = getenv("USERPROFILE")))
setenv("HOME", tmp, 1);
}
+}
+int mingw_platform_has_symlinks(void)
+{
+ static int has_symlinks = -1;
/*
* Change 'core.symlinks' default to false, unless native symlinks are
* enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
* run the test suite (which doesn't obey config files) with or without
* symlink support.
*/
- if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
- has_symlinks = 0;
+ if (has_symlinks < 0) {
+ const char *tmp = getenv("MSYS");
+ has_symlinks = (tmp && strstr(tmp, "winsymlinks:nativestrict")) ? 1 : 0;
+ }
+
+ return has_symlinks;
}
static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
diff --git a/compat/mingw.h b/compat/mingw.h
index 444daed..df02aeb 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -208,6 +208,9 @@ void open_in_gdb(void);
*/
int err_win_to_posix(DWORD winerr);
+int mingw_platform_has_symlinks(void);
+#define platform_has_symlinks() mingw_platform_has_symlinks()
+
#ifndef NO_UNIX_SOCKETS
int mingw_have_unix_sockets(void);
#undef have_unix_sockets
diff --git a/config.c b/config.c
index 6a0de86..1bdd702 100644
--- a/config.c
+++ b/config.c
@@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi,
return 0;
}
-static int include_by_gitdir(const struct key_value_info *kvi,
- const struct config_options *opts,
- const char *cond, size_t cond_len, int icase)
+static int include_by_path(const struct key_value_info *kvi,
+ const char *path,
+ const char *cond, size_t cond_len, int icase)
{
struct strbuf text = STRBUF_INIT;
struct strbuf pattern = STRBUF_INIT;
size_t prefix;
int ret = 0;
- const char *git_dir;
int already_tried_absolute = 0;
- if (opts->git_dir)
- git_dir = opts->git_dir;
- else
+ if (!path)
goto done;
- strbuf_realpath(&text, git_dir, 1);
+ strbuf_realpath(&text, path, 1);
strbuf_add(&pattern, cond, cond_len);
ret = prepare_include_condition_pattern(kvi, &pattern, &prefix);
if (ret < 0)
@@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi,
* which'll do the right thing
*/
strbuf_reset(&text);
- strbuf_add_absolute_path(&text, git_dir);
+ strbuf_add_absolute_path(&text, path);
already_tried_absolute = 1;
goto again;
}
@@ -400,9 +397,15 @@ static int include_condition_is_true(const struct key_value_info *kvi,
const struct config_options *opts = inc->opts;
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
- return include_by_gitdir(kvi, opts, cond, cond_len, 0);
+ return include_by_path(kvi, opts->git_dir, cond, cond_len, 0);
else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
- return include_by_gitdir(kvi, opts, cond, cond_len, 1);
+ return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
+ else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len))
+ return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
+ cond, cond_len, 0);
+ else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len))
+ return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
+ cond, cond_len, 1);
else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
return include_by_branch(inc, cond, cond_len);
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -2951,6 +2954,24 @@ char *git_config_prepare_comment_string(const char *comment)
return prepared;
}
+/*
+ * How long to retry acquiring config.lock when another process holds
+ * it. Default matches core.packedRefsTimeout; override via
+ * core.configLockTimeout.
+ */
+static long config_lock_timeout_ms(struct repository *r)
+{
+ static int configured;
+ static int timeout_ms = 1000;
+
+ if (!configured) {
+ repo_config_get_int(r, "core.configlocktimeout", &timeout_ms);
+ configured = 1;
+ }
+
+ return timeout_ms;
+}
+
static void validate_comment_string(const char *comment)
{
size_t leading_blanks;
@@ -3034,7 +3055,8 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
* The lock serves a purpose in addition to locking: the new
* contents of .git/config will be written into it.
*/
- fd = hold_lock_file_for_update(&lock, config_filename, 0);
+ fd = repo_hold_lock_file_for_update_timeout(r, &lock, config_filename, 0,
+ config_lock_timeout_ms(r));
if (fd < 0) {
error_errno(_("could not lock config file %s"), config_filename);
ret = CONFIG_NO_LOCK;
@@ -3379,7 +3401,9 @@ static int repo_config_copy_or_rename_section_in_file(
if (!config_filename)
config_filename = filename_buf = repo_git_path(r, "config");
- out_fd = hold_lock_file_for_update(&lock, config_filename, 0);
+ out_fd = repo_hold_lock_file_for_update_timeout(r, &lock,
+ config_filename, 0,
+ config_lock_timeout_ms(r));
if (out_fd < 0) {
ret = error(_("could not lock config file %s"), config_filename);
goto out;
diff --git a/connect.c b/connect.c
index 1d74c1e..feb0420 100644
--- a/connect.c
+++ b/connect.c
@@ -709,6 +709,40 @@ int server_supports(const char *feature)
return !!server_feature_value(feature, NULL);
}
+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,
+ const struct string_list *server_options)
+{
+ const char *hash_name;
+ int advertise_sid = 0;
+
+ repo_config_get_bool(the_repository, "transfer.advertisesid", &advertise_sid);
+
+ ensure_server_supports_v2(command);
+ packet_buf_write(req_buf, "command=%s", command);
+ if (server_supports_v2("agent"))
+ packet_buf_write(req_buf, "agent=%s", git_user_agent_sanitized());
+ if (advertise_sid && server_supports_v2("session-id"))
+ packet_buf_write(req_buf, "session-id=%s", trace2_session_id());
+ if (server_options && server_options->nr) {
+ ensure_server_supports_v2("server-option");
+ for (size_t i = 0; i < server_options->nr; i++)
+ packet_buf_write(req_buf, "server-option=%s",
+ server_options->items[i].string);
+ }
+
+ if (server_feature_v2("object-format", &hash_name)) {
+ const unsigned int hash_algo = hash_algo_by_name(hash_name);
+ if (hash_algo_by_ptr(the_hash_algo) != hash_algo)
+ die(_("mismatched algorithms: client %s; server %s"),
+ the_hash_algo->name, hash_name);
+ packet_buf_write(req_buf, "object-format=%s", the_hash_algo->name);
+ } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {
+ die(_("the server does not support algorithm '%s'"),
+ the_hash_algo->name);
+ }
+ packet_buf_delim(req_buf);
+}
+
static const char *url_scheme_name(enum url_scheme scheme)
{
switch (scheme) {
diff --git a/connect.h b/connect.h
index aa482a3..957e5fe 100644
--- a/connect.h
+++ b/connect.h
@@ -34,4 +34,12 @@ void check_stateless_delimiter(int stateless_rpc,
struct packet_reader *reader,
const char *error);
+struct string_list;
+/*
+ * Write a protocol v2 command request, along with the capability
+ * advertisements, into req_buf.
+ */
+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,
+ const struct string_list *server_options);
+
#endif
diff --git a/copy.c b/copy.c
index b668209..6074132 100644
--- a/copy.c
+++ b/copy.c
@@ -1,5 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
#include "git-compat-util.h"
#include "copy.h"
#include "path.h"
@@ -35,7 +33,8 @@ static int copy_times(const char *dst, const char *src)
return 0;
}
-int copy_file(const char *dst, const char *src, int mode)
+int copy_file(struct repository *repo,
+ const char *dst, const char *src, int mode)
{
int fdi, fdo, status;
@@ -59,15 +58,16 @@ int copy_file(const char *dst, const char *src, int mode)
if (close(fdo) != 0)
return error_errno("%s: close error", dst);
- if (!status && adjust_shared_perm(the_repository, dst))
+ if (!status && adjust_shared_perm(repo, dst))
return -1;
return status;
}
-int copy_file_with_time(const char *dst, const char *src, int mode)
+int copy_file_with_time(struct repository *repo,
+ const char *dst, const char *src, int mode)
{
- int status = copy_file(dst, src, mode);
+ int status = copy_file(repo, dst, src, mode);
if (!status)
return copy_times(dst, src);
return status;
diff --git a/copy.h b/copy.h
index 2af77cb..1059b11 100644
--- a/copy.h
+++ b/copy.h
@@ -1,10 +1,14 @@
#ifndef COPY_H
#define COPY_H
+struct repository;
+
#define COPY_READ_ERROR (-2)
#define COPY_WRITE_ERROR (-3)
int copy_fd(int ifd, int ofd);
-int copy_file(const char *dst, const char *src, int mode);
-int copy_file_with_time(const char *dst, const char *src, int mode);
+int copy_file(struct repository *repo,
+ const char *dst, const char *src, int mode);
+int copy_file_with_time(struct repository *repo,
+ const char *dst, const char *src, int mode);
#endif /* COPY_H */
diff --git a/diff-lib.c b/diff-lib.c
index ae91027..46cae63 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -160,7 +160,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
changed = check_removed(ce, &st);
if (!changed)
- wt_mode = ce_mode_from_stat(ce, st.st_mode);
+ wt_mode = ce_mode_from_stat(revs->repo, ce, st.st_mode);
else {
if (changed < 0) {
perror(ce->name);
@@ -193,7 +193,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
num_compare_stages++;
oidcpy(&dpath->parent[stage - 2].oid,
&nce->oid);
- dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
+ dpath->parent[stage-2].mode = ce_mode_from_stat(revs->repo, nce, mode);
dpath->parent[stage-2].status =
DIFF_STATUS_MODIFIED;
}
@@ -262,7 +262,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
continue;
} else if (revs->diffopt.ita_invisible_in_index &&
ce_intent_to_add(ce)) {
- newmode = ce_mode_from_stat(ce, st.st_mode);
+ newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);
diff_addremove(&revs->diffopt, '+', newmode,
null_oid(the_hash_algo), 0, ce->name, 0);
continue;
@@ -270,7 +270,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
ce_option, &dirty_submodule);
- newmode = ce_mode_from_stat(ce, st.st_mode);
+ newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);
}
if (!changed && !dirty_submodule) {
@@ -338,7 +338,7 @@ static int get_stat_data(const struct cache_entry *ce,
changed = match_stat_with_submodule(diffopt, ce, &st,
0, dirty_submodule);
if (changed) {
- mode = ce_mode_from_stat(ce, st.st_mode);
+ mode = ce_mode_from_stat(diffopt->repo, ce, st.st_mode);
oid = null_oid(the_hash_algo);
}
}
diff --git a/dir.c b/dir.c
index 1094d7b..95d8a1c 100644
--- a/dir.c
+++ b/dir.c
@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude")
void setup_standard_excludes(struct dir_struct *dir)
{
+ const char *excludes_file = repo_excludes_file(the_repository);
+
dir->exclude_per_dir = ".gitignore";
/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */
- if (!excludes_file)
- excludes_file = xdg_config_home("ignore");
if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
add_patterns_from_file_1(dir, excludes_file,
dir->untracked ? &dir->internal.ss_excludes_file : NULL);
diff --git a/editor.c b/editor.c
index fd174e6a..0d1cb87 100644
--- a/editor.c
+++ b/editor.c
@@ -29,8 +29,8 @@ const char *git_editor(void)
const char *editor = getenv("GIT_EDITOR");
int terminal_is_dumb = is_terminal_dumb();
- if (!editor && editor_program)
- editor = editor_program;
+ if (!editor)
+ editor = repo_config_values(the_repository)->editor_program;
if (!editor && !terminal_is_dumb)
editor = getenv("VISUAL");
if (!editor)
diff --git a/entry.c b/entry.c
index 6b79884..1c4f0f4 100644
--- a/entry.c
+++ b/entry.c
@@ -319,7 +319,8 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca
* We can't make a real symlink; write out a regular file entry
* with the symlink destination as its contents.
*/
- if (!has_symlinks || to_tempfile)
+ if (!repo_has_symlinks(state->istate && state->istate->repo ?
+ state->istate->repo : the_repository) || to_tempfile)
goto write_file_entry;
ret = symlink(new_blob, path);
diff --git a/environment.c b/environment.c
index c663113..76ee65e 100644
--- a/environment.c
+++ b/environment.c
@@ -41,31 +41,21 @@
static int pack_compression_seen;
static int zlib_compression_seen;
-int trust_executable_bit = 1;
-int has_symlinks = 1;
int minimum_abbrev = 4, default_abbrev = -1;
int assume_unchanged;
char *git_commit_encoding;
char *git_log_output_encoding;
-char *apply_default_whitespace;
-char *apply_default_ignorewhitespace;
int fsync_object_files = -1;
int use_fsync = -1;
enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;
enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;
-char *editor_program;
-char *askpass_program;
-char *excludes_file;
enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
enum eol core_eol = EOL_UNSET;
int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;
char *check_roundtrip_encoding;
-enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
-enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
#ifndef OBJECT_CREATION_MODE
#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
#endif
-enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
int grafts_keep_true_parents;
unsigned long pack_size_limit_cfg;
@@ -148,6 +138,30 @@ int repo_ignore_case(struct repository *repo)
0;
}
+int repo_trust_executable_bit(struct repository *repo)
+{
+ return repo->initialized
+ ? repo_config_values(repo)->trust_executable_bit
+ : 1;
+}
+
+int repo_has_symlinks(struct repository *repo)
+{
+ return repo->initialized
+ ? repo_config_values(repo)->has_symlinks
+ : platform_has_symlinks();
+}
+
+const char *repo_excludes_file(struct repository *repo)
+{
+ struct repo_config_values *cfg = repo_config_values(repo);
+
+ if (!cfg->excludes_file)
+ cfg->excludes_file = xdg_config_home("ignore");
+
+ return cfg->excludes_file;
+}
+
int have_git_dir(void)
{
return startup_info->have_repository
@@ -311,7 +325,7 @@ int git_default_core_config(const char *var, const char *value,
/* This needs a better name */
if (!strcmp(var, "core.filemode")) {
- trust_executable_bit = git_config_bool(var, value);
+ cfg->trust_executable_bit = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.trustctime")) {
@@ -336,7 +350,8 @@ int git_default_core_config(const char *var, const char *value,
}
if (!strcmp(var, "core.symlinks")) {
- has_symlinks = git_config_bool(var, value);
+ struct repo_config_values *cfg = repo_config_values(the_repository);
+ cfg->has_symlinks = git_config_bool(var, value);
return 0;
}
@@ -442,8 +457,8 @@ int git_default_core_config(const char *var, const char *value,
}
if (!strcmp(var, "core.editor")) {
- FREE_AND_NULL(editor_program);
- return git_config_string(&editor_program, var, value);
+ FREE_AND_NULL(cfg->editor_program);
+ return git_config_string(&cfg->editor_program, var, value);
}
if (!strcmp(var, "core.commentchar") ||
@@ -470,13 +485,13 @@ int git_default_core_config(const char *var, const char *value,
}
if (!strcmp(var, "core.askpass")) {
- FREE_AND_NULL(askpass_program);
- return git_config_string(&askpass_program, var, value);
+ FREE_AND_NULL(cfg->askpass_program);
+ return git_config_string(&cfg->askpass_program, var, value);
}
if (!strcmp(var, "core.excludesfile")) {
- FREE_AND_NULL(excludes_file);
- return git_config_pathname(&excludes_file, var, value);
+ FREE_AND_NULL(cfg->excludes_file);
+ return git_config_pathname(&cfg->excludes_file, var, value);
}
if (!strcmp(var, "core.whitespace")) {
@@ -523,9 +538,9 @@ int git_default_core_config(const char *var, const char *value,
if (!value)
return config_error_nonbool(var);
if (!strcmp(value, "rename"))
- object_creation_mode = OBJECT_CREATION_USES_RENAMES;
+ cfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES;
else if (!strcmp(value, "link"))
- object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;
+ cfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;
else
die(_("invalid mode for object creation: %s"), value);
return 0;
@@ -611,13 +626,13 @@ static int git_default_branch_config(const char *var, const char *value)
if (!value)
return config_error_nonbool(var);
else if (!strcmp(value, "never"))
- autorebase = AUTOREBASE_NEVER;
+ cfg->autorebase = AUTOREBASE_NEVER;
else if (!strcmp(value, "local"))
- autorebase = AUTOREBASE_LOCAL;
+ cfg->autorebase = AUTOREBASE_LOCAL;
else if (!strcmp(value, "remote"))
- autorebase = AUTOREBASE_REMOTE;
+ cfg->autorebase = AUTOREBASE_REMOTE;
else if (!strcmp(value, "always"))
- autorebase = AUTOREBASE_ALWAYS;
+ cfg->autorebase = AUTOREBASE_ALWAYS;
else
return error(_("malformed value for %s"), var);
return 0;
@@ -629,21 +644,23 @@ static int git_default_branch_config(const char *var, const char *value)
static int git_default_push_config(const char *var, const char *value)
{
+ struct repo_config_values *cfg = repo_config_values(the_repository);
+
if (!strcmp(var, "push.default")) {
if (!value)
return config_error_nonbool(var);
else if (!strcmp(value, "nothing"))
- push_default = PUSH_DEFAULT_NOTHING;
+ cfg->push_default = PUSH_DEFAULT_NOTHING;
else if (!strcmp(value, "matching"))
- push_default = PUSH_DEFAULT_MATCHING;
+ cfg->push_default = PUSH_DEFAULT_MATCHING;
else if (!strcmp(value, "simple"))
- push_default = PUSH_DEFAULT_SIMPLE;
+ cfg->push_default = PUSH_DEFAULT_SIMPLE;
else if (!strcmp(value, "upstream"))
- push_default = PUSH_DEFAULT_UPSTREAM;
+ cfg->push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "tracking")) /* deprecated */
- push_default = PUSH_DEFAULT_UPSTREAM;
+ cfg->push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "current"))
- push_default = PUSH_DEFAULT_CURRENT;
+ cfg->push_default = PUSH_DEFAULT_CURRENT;
else {
error(_("malformed value for %s: %s"), var, value);
return error(_("must be one of nothing, matching, simple, "
@@ -729,10 +746,21 @@ int git_default_config(const char *var, const char *value,
void repo_config_values_init(struct repo_config_values *cfg)
{
cfg->attributes_file = NULL;
+ cfg->excludes_file = NULL;
+ cfg->editor_program = NULL;
+ cfg->pager_program = NULL;
+ cfg->askpass_program = NULL;
+ cfg->apply_default_whitespace = NULL;
+ cfg->apply_default_ignorewhitespace = NULL;
+ cfg->push_default = PUSH_DEFAULT_UNSPECIFIED;
+ cfg->autorebase = AUTOREBASE_NEVER;
+ cfg->object_creation_mode = OBJECT_CREATION_MODE;
cfg->apply_sparse_checkout = 0;
cfg->protect_hfs = PROTECT_HFS_DEFAULT;
cfg->protect_ntfs = PROTECT_NTFS_DEFAULT;
cfg->ignore_case = 0;
+ cfg->trust_executable_bit = 1;
+ cfg->has_symlinks = platform_has_symlinks();
cfg->branch_track = BRANCH_TRACK_REMOTE;
cfg->trust_ctime = 1;
cfg->check_stat = 1;
@@ -743,3 +771,14 @@ void repo_config_values_init(struct repo_config_values *cfg)
cfg->sparse_expect_files_outside_of_patterns = 0;
cfg->warn_on_object_refname_ambiguity = 1;
}
+
+void repo_config_values_clear(struct repo_config_values *cfg)
+{
+ FREE_AND_NULL(cfg->attributes_file);
+ FREE_AND_NULL(cfg->excludes_file);
+ FREE_AND_NULL(cfg->editor_program);
+ FREE_AND_NULL(cfg->pager_program);
+ FREE_AND_NULL(cfg->askpass_program);
+ FREE_AND_NULL(cfg->apply_default_whitespace);
+ FREE_AND_NULL(cfg->apply_default_ignorewhitespace);
+}
diff --git a/environment.h b/environment.h
index acfb670..e7ec5b0 100644
--- a/environment.h
+++ b/environment.h
@@ -87,9 +87,45 @@ extern const char * const local_repo_env[];
struct strvec;
struct repository;
+
+/*
+ * NEEDSWORK: It would be better if these definitions could be moved to
+ * other more specific files, but care is needed to avoid circular
+ * inclusion issues.
+ */
+enum push_default_type {
+ PUSH_DEFAULT_NOTHING = 0,
+ PUSH_DEFAULT_MATCHING,
+ PUSH_DEFAULT_SIMPLE,
+ PUSH_DEFAULT_UPSTREAM,
+ PUSH_DEFAULT_CURRENT,
+ PUSH_DEFAULT_UNSPECIFIED
+};
+
+enum rebase_setup_type {
+ AUTOREBASE_NEVER = 0,
+ AUTOREBASE_LOCAL,
+ AUTOREBASE_REMOTE,
+ AUTOREBASE_ALWAYS
+};
+
+enum object_creation_mode {
+ OBJECT_CREATION_USES_HARDLINKS = 0,
+ OBJECT_CREATION_USES_RENAMES = 1
+};
+
struct repo_config_values {
/* section "core" config values */
char *attributes_file;
+ char *excludes_file;
+ char *editor_program;
+ char *pager_program;
+ char *askpass_program;
+ char *apply_default_whitespace;
+ char *apply_default_ignorewhitespace;
+ enum push_default_type push_default;
+ enum rebase_setup_type autorebase;
+ enum object_creation_mode object_creation_mode;
int apply_sparse_checkout;
int trust_ctime;
int check_stat;
@@ -101,6 +137,8 @@ struct repo_config_values {
int protect_hfs;
int protect_ntfs;
int ignore_case;
+ int trust_executable_bit;
+ int has_symlinks;
/* section "sparse" config values */
int sparse_expect_files_outside_of_patterns;
@@ -151,11 +189,26 @@ int repo_protect_ntfs(struct repository *repo);
*/
int repo_ignore_case(struct repository *repo);
+int repo_trust_executable_bit(struct repository *repo);
+
+int repo_has_symlinks(struct repository *repo);
+
+const char *repo_excludes_file(struct repository *repo);
+
void repo_config_values_init(struct repo_config_values *cfg);
int is_bare_repository(struct repository *repo);
/*
+ * Frees memory allocated for dynamically loaded configuration values
+ * inside `repo_config_values`.
+ *
+ * As dynamically allocated variables are migrated into this struct,
+ * their FREE_AND_NULL() calls should be appended here.
+ */
+void repo_config_values_clear(struct repo_config_values *cfg);
+
+/*
* TODO: All the below state either explicitly or implicitly relies on
* `the_repository`. We should eventually get rid of these and make the
* dependency on a repository explicit:
@@ -178,38 +231,10 @@ int is_bare_repository(struct repository *repo);
int have_git_dir(void);
/* Environment bits from configuration mechanism */
-extern int trust_executable_bit;
-extern int has_symlinks;
extern int minimum_abbrev, default_abbrev;
extern int assume_unchanged;
-extern char *apply_default_whitespace;
-extern char *apply_default_ignorewhitespace;
extern unsigned long pack_size_limit_cfg;
-enum rebase_setup_type {
- AUTOREBASE_NEVER = 0,
- AUTOREBASE_LOCAL,
- AUTOREBASE_REMOTE,
- AUTOREBASE_ALWAYS
-};
-extern enum rebase_setup_type autorebase;
-
-enum push_default_type {
- PUSH_DEFAULT_NOTHING = 0,
- PUSH_DEFAULT_MATCHING,
- PUSH_DEFAULT_SIMPLE,
- PUSH_DEFAULT_UPSTREAM,
- PUSH_DEFAULT_CURRENT,
- PUSH_DEFAULT_UNSPECIFIED
-};
-extern enum push_default_type push_default;
-
-enum object_creation_mode {
- OBJECT_CREATION_USES_HARDLINKS = 0,
- OBJECT_CREATION_USES_RENAMES = 1
-};
-extern enum object_creation_mode object_creation_mode;
-
extern int grafts_keep_true_parents;
const char *get_log_output_encoding(void);
@@ -218,10 +243,6 @@ const char *get_commit_output_encoding(void);
extern char *git_commit_encoding;
extern char *git_log_output_encoding;
-extern char *editor_program;
-extern char *askpass_program;
-extern char *excludes_file;
-
/*
* The character that begins a commented line in user-editable file
* that is subject to stripspace.
diff --git a/fetch-object-info.c b/fetch-object-info.c
new file mode 100644
index 0000000..ba7e179
--- /dev/null
+++ b/fetch-object-info.c
@@ -0,0 +1,156 @@
+#include "git-compat-util.h"
+#include "gettext.h"
+#include "hex.h"
+#include "pkt-line.h"
+#include "connect.h"
+#include "oid-array.h"
+#include "odb.h"
+#include "fetch-object-info.h"
+#include "string-list.h"
+
+/* Sends object-info command and its arguments into the request buffer. */
+static void send_object_info_request(const int fd_out, struct object_info_args *args)
+{
+ struct strbuf req_buf = STRBUF_INIT;
+
+ write_command_and_capabilities(&req_buf, "object-info", args->server_options);
+
+ if (unsorted_string_list_has_string(args->object_info_options, "size"))
+ packet_buf_write(&req_buf, "size");
+ else if (args->object_info_options->nr)
+ BUG("only size should be in object_info_options");
+
+ if (args->oids)
+ for (size_t i = 0; i < args->oids->nr; i++)
+ packet_buf_write(&req_buf, "oid %s", oid_to_hex(&args->oids->oid[i]));
+
+ packet_buf_flush(&req_buf);
+ if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)
+ die_errno(_("unable to write request to remote"));
+
+ strbuf_release(&req_buf);
+}
+
+static int parse_object_size(const char *s, size_t *res)
+{
+ uintmax_t uim;
+
+ if (!s[0] || s[strspn(s, "0123456789")])
+ return -1;
+ errno = 0;
+ uim = strtoumax(s, NULL, 10);
+ if (errno || uim > SIZE_MAX)
+ return -1;
+ *res = uim;
+ return 0;
+}
+
+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,
+ struct packet_reader *reader, struct object_info *object_info_data,
+ const int stateless_rpc, const int fd_out)
+{
+ int size_index = -1;
+
+ switch (version) {
+ case protocol_v2:
+ if (!server_supports_v2("object-info"))
+ die(_("object-info capability is not enabled on the server"));
+ /*
+ * When removing an element from the list it gets swapped by the
+ * last element, iterate backwards to prevent elements skipping
+ * evaluation.
+ *
+ * object_info_options->nr can be safely casted without overflow
+ * because the number of options is a small known number (the
+ * supported placeholders which currently are size and type).
+ */
+ for (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)
+ if (!server_supports_feature("object-info",
+ args->object_info_options->items[i].string, 0))
+ unsorted_string_list_delete_item(args->object_info_options, i, 0);
+
+ /*
+ * Even if no options are left, we still send the oid so we get
+ * at least an existence check.
+ */
+ send_object_info_request(fd_out, args);
+ break;
+ case protocol_v1:
+ case protocol_v0:
+ die(_("object-info requires protocol v2"));
+ case protocol_unknown_version:
+ BUG("unknown protocol version");
+ }
+
+ for (size_t i = 0; i < args->object_info_options->nr; i++) {
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL) {
+ check_stateless_delimiter(stateless_rpc, reader,
+ "stateless delimiter expected");
+ return -1;
+ }
+
+ if (!unsorted_string_list_has_string(args->object_info_options, reader->line))
+ return -1;
+
+ if (!strcmp(reader->line, "size")) {
+ /*
+ * i is the number of supported options which currently
+ * is only size. No risk of overflow.
+ */
+ size_index = (int)i;
+ for (size_t j = 0; j < args->oids->nr; j++)
+ object_info_data[j].sizep =
+ xcalloc(1, sizeof(*object_info_data[j].sizep));
+ } else {
+ BUG("only size is supported");
+ }
+ }
+
+ for (size_t i = 0;
+ packet_reader_read(reader) == PACKET_READ_NORMAL &&
+ i < args->oids->nr;
+ i++) {
+ struct string_list object_info_values = STRING_LIST_INIT_DUP;
+
+ string_list_split(&object_info_values, reader->line, " ", -1);
+
+ if (strcmp(object_info_values.items[0].string,
+ oid_to_hex(&args->oids->oid[i])))
+ die(_("object-info: expected OID: %s, got %s"),
+ oid_to_hex(&args->oids->oid[i]),
+ object_info_values.items[0].string);
+
+ /*
+ * If the response is two elements but the second one is an
+ * empty string, that means that the OID is unrecognized by the
+ * server.
+ */
+ if (object_info_values.nr >= 2 &&
+ !strcmp(object_info_values.items[1].string, "")) {
+ object_info_data[i].unrecognized = 1;
+ string_list_clear(&object_info_values, 0);
+ continue;
+ }
+
+ /*
+ * Because we filter the options to be only the supported by
+ * the server we expect the server to answer with the same
+ * number of attributes requested.
+ */
+ if (args->object_info_options->nr + 1 != object_info_values.nr)
+ die("object-info: unexpected number of attributes: %s",
+ reader->line);
+
+ if (size_index >= 0 &&
+ parse_object_size(object_info_values.items[size_index + 1].string,
+ object_info_data[i].sizep))
+ die("object-info: ref %s has invalid size %s",
+ object_info_values.items[0].string,
+ object_info_values.items[size_index + 1].string);
+
+ string_list_clear(&object_info_values, 0);
+ }
+ check_stateless_delimiter(stateless_rpc, reader, "stateless delimiter expected");
+
+ return 0;
+}
diff --git a/fetch-object-info.h b/fetch-object-info.h
new file mode 100644
index 0000000..269cebb
--- /dev/null
+++ b/fetch-object-info.h
@@ -0,0 +1,25 @@
+#ifndef FETCH_OBJECT_INFO_H
+#define FETCH_OBJECT_INFO_H
+
+#include "pkt-line.h"
+#include "protocol.h"
+
+struct object_info_args {
+ struct string_list *object_info_options;
+ const struct string_list *server_options;
+ struct oid_array *oids;
+};
+
+struct object_info;
+/*
+ * Sends git-cat-file object-info command into the request buf and read the
+ * results from packets.
+ *
+ * Modifies args->object_info_options, on return it contains only the supported
+ * options by the server.
+ */
+int fetch_object_info(enum protocol_version version, struct object_info_args *args,
+ struct packet_reader *reader, struct object_info *object_info_data,
+ int stateless_rpc, int fd_out);
+
+#endif /* FETCH_OBJECT_INFO_H */
diff --git a/fetch-pack.c b/fetch-pack.c
index 29c4113..922a9b2 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;
static int transfer_fsck_objects = -1;
static int agent_supported;
static int server_supports_filtering;
-static int advertise_sid;
static struct shallow_lock shallow_lock;
static const char *alternate_shallow_file;
static struct strbuf fsck_msg_types = STRBUF_INIT;
@@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,
size_t state_len = 0;
struct packet_reader reader;
struct oidset negotiation_include_oids = OIDSET_INIT;
+ int advertise_sid = 0;
+
+ repo_config_get_bool(the_repository, "transfer.advertisesid", &advertise_sid);
if (args->stateless_rpc && multi_ack == 1)
die(_("the option '%s' requires '%s'"), "--stateless-rpc", "multi_ack_detailed");
@@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,
if (deepen_not_ok) strbuf_addstr(&c, " deepen-not");
if (agent_supported) strbuf_addf(&c, " agent=%s",
git_user_agent_sanitized());
- if (advertise_sid)
+ if (advertise_sid && server_supports("session-id"))
strbuf_addf(&c, " session-id=%s", trace2_session_id());
if (args->filter_options.choice)
strbuf_addstr(&c, " filter");
@@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
(int)agent_len, agent_feature);
}
- if (!server_supports("session-id"))
- advertise_sid = 0;
-
if (server_supports("shallow"))
print_verbose(args, _("Server supports %s"), "shallow");
else if (args->depth > 0 || is_repository_shallow(r))
@@ -1376,38 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,
return haves_added;
}
-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,
- const struct string_list *server_options)
-{
- const char *hash_name;
-
- ensure_server_supports_v2("fetch");
- packet_buf_write(req_buf, "command=fetch");
- if (server_supports_v2("agent"))
- packet_buf_write(req_buf, "agent=%s", git_user_agent_sanitized());
- if (advertise_sid && server_supports_v2("session-id"))
- packet_buf_write(req_buf, "session-id=%s", trace2_session_id());
- if (server_options && server_options->nr) {
- int i;
- ensure_server_supports_v2("server-option");
- for (i = 0; i < server_options->nr; i++)
- packet_buf_write(req_buf, "server-option=%s",
- server_options->items[i].string);
- }
-
- if (server_feature_v2("object-format", &hash_name)) {
- int hash_algo = hash_algo_by_name(hash_name);
- if (hash_algo_by_ptr(the_hash_algo) != hash_algo)
- die(_("mismatched algorithms: client %s; server %s"),
- the_hash_algo->name, hash_name);
- packet_buf_write(req_buf, "object-format=%s", the_hash_algo->name);
- } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {
- die(_("the server does not support algorithm '%s'"),
- the_hash_algo->name);
- }
- packet_buf_delim(req_buf);
-}
-
static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
struct fetch_pack_args *args,
const struct ref *wants, struct oidset *common,
@@ -1419,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
int done_sent = 0;
struct strbuf req_buf = STRBUF_INIT;
- write_fetch_command_and_capabilities(&req_buf, args->server_options);
+ write_command_and_capabilities(&req_buf, "fetch", args->server_options);
if (args->use_thin_pack)
packet_buf_write(&req_buf, "thin-pack");
@@ -1768,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
reader.me = "fetch-pack";
}
+ /* v2 supports these by default */
+ allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
+ use_sideband = 2;
+ if (args->depth > 0 || args->deepen_since || args->deepen_not)
+ args->deepen = 1;
+
while (state != FETCH_DONE) {
switch (state) {
case FETCH_CHECK_LOCAL:
sort_ref_list(&ref, ref_compare_name);
QSORT(sought, nr_sought, cmp_ref_by_name);
- /* v2 supports these by default */
- allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
- use_sideband = 2;
- if (args->depth > 0 || args->deepen_since || args->deepen_not)
- args->deepen = 1;
-
/* Filter 'ref' by 'sought' and those that aren't local */
mark_complete_and_common_ref(negotiator, args, &ref);
filter_refs(args, &ref, sought, nr_sought);
@@ -1999,7 +1966,6 @@ static void fetch_pack_config(void)
repo_config_get_bool(the_repository, "repack.usedeltabaseoffset", &prefer_ofs_delta);
repo_config_get_bool(the_repository, "fetch.fsckobjects", &fetch_fsck_objects);
repo_config_get_bool(the_repository, "transfer.fsckobjects", &transfer_fsck_objects);
- repo_config_get_bool(the_repository, "transfer.advertisesid", &advertise_sid);
if (!uri_protocols.nr) {
char *str;
@@ -2287,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
the_repository, "%d",
negotiation_round);
strbuf_reset(&req_buf);
- write_fetch_command_and_capabilities(&req_buf, server_options);
+ write_command_and_capabilities(&req_buf, "fetch", server_options);
packet_buf_write(&req_buf, "wait-for-done");
diff --git a/git-compat-util.h b/git-compat-util.h
index 8809776..a0f901c 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c)
#define is_dir_sep git_is_dir_sep
#endif
+#ifndef platform_has_symlinks
+#define platform_has_symlinks() 1
+#endif
+
#ifndef offset_1st_component
static inline int git_offset_1st_component(const char *path)
{
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index fde8045..8c2d9b8 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -2339,12 +2339,14 @@
$line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
esc_path($to->{'file'}));
}
- # match single <mode>
- if ($line =~ m/\s(\d{6})$/) {
- $line .= '<span class="info"> (' .
- file_type_long($1) .
- ')</span>';
+
+ # Temporarily remove a trailing <mode> so an index line ends with its
+ # object IDs and can be shortened below.
+ my $mode;
+ if ($line =~ s/\s(\d{6})$//) {
+ $mode = $1;
}
+
# match <hash>
if ($line =~ oid_nlen_prefix_infix_regex($sha1_len, "index ", ",") |
$line =~ oid_nlen_prefix_infix_regex($sha256_len, "index ", ",")) {
@@ -2388,6 +2390,12 @@
my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
$line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
}
+ if (defined $mode) {
+ $line .= " $mode" .
+ '<span class="info"> (' .
+ file_type_long($mode) .
+ ')</span>';
+ }
return $line . "<br/>\n";
}
diff --git a/graph.c b/graph.c
index 8422826..28bef1b 100644
--- a/graph.c
+++ b/graph.c
@@ -60,12 +60,23 @@ struct column {
* index into column_colors.
*/
unsigned short color;
+ /*
+ * Marks if a commit is a non-first parent of a merge. These columns are
+ * already visually connected to the merge commit and do not need
+ * indentation.
+ *
+ * The first parent is the one that inherits the column and it can need
+ * indentation if turns out to be a visual root and there's still
+ * commits to render.
+ */
+ unsigned int is_merge_parent:1;
};
enum graph_state {
GRAPH_PADDING,
GRAPH_SKIP,
GRAPH_PRE_COMMIT,
+ GRAPH_PRE_ROOT,
GRAPH_COMMIT,
GRAPH_POST_MERGE,
GRAPH_COLLAPSING
@@ -315,6 +326,59 @@ struct git_graph {
* diff_output_prefix_callback().
*/
struct strbuf prefix_buf;
+
+ /*
+ * Lookahead buffer: up to 2 pre-fetched commits that will be shown.
+ * Populated by get_revision() so graph_peek_next_visible() can use
+ * actual walk results instead of peeking at rev_info internals.
+ */
+ struct commit *lookahead[2];
+ int lookahead_nr;
+
+ /*
+ * If a commit is a visual root, we need to indent it to prevent
+ * unrelated commits from being vertically adjacent to it.
+ */
+ unsigned int is_visual_root:1;
+
+ /*
+ * Indentation increases for each visual root adjacent to another visual
+ * root, making visual root commits indentation cascade.
+ */
+ unsigned int visual_root_depth;
+
+ /*
+ * When a visual root is adjacent to other visual roots, the first one
+ * can avoid indentation and the rest cascades, increasing the indentation
+ * for each one.
+ */
+ unsigned int visual_root_cascade:1;
+
+ /*
+ * Set when the current commit was already present in graph->columns
+ * before being processed.
+ */
+ unsigned int commit_in_columns:1;
+};
+
+struct graph_lookahead_flags {
+
+ /*
+ * Set when there will be a commit after the current one that will be
+ * rendered.
+ */
+ unsigned int is_next_visible:1;
+
+ /*
+ * Set when the next visible commit is candidate to be a visual root.
+ */
+ unsigned int is_next_visual_root:1;
+
+ /*
+ * Set when the next visible commit will be rendered under the current
+ * commit.
+ */
+ unsigned int next_has_column:1;
};
static inline int graph_needs_truncation(struct git_graph *graph, int lane)
@@ -353,13 +417,13 @@ void graph_setup_line_prefix(struct diff_options *diffopt)
diffopt->output_prefix = diff_output_prefix_callback;
}
-struct git_graph *graph_init(struct rev_info *opt)
+static void graph_read_config(struct rev_info *revs)
{
- struct git_graph *graph = xmalloc(sizeof(struct git_graph));
+ int val;
if (!column_colors) {
char *string;
- if (repo_config_get_string(opt->repo, "log.graphcolors", &string)) {
+ if (repo_config_get_string(revs->repo, "log.graphcolors", &string)) {
/* not configured -- use default */
graph_set_column_colors(column_colors_ansi,
column_colors_ansi_max);
@@ -374,6 +438,16 @@ struct git_graph *graph_init(struct rev_info *opt)
}
}
+ if (!repo_config_get_bool(revs->repo, "log.graphIndent", &val))
+ revs->no_graph_indent = !val;
+}
+
+struct git_graph *graph_init(struct rev_info *opt)
+{
+ struct git_graph *graph = xmalloc(sizeof(struct git_graph));
+
+ graph_read_config(opt);
+
graph->commit = NULL;
graph->revs = opt;
graph->num_parents = 0;
@@ -388,6 +462,11 @@ struct git_graph *graph_init(struct rev_info *opt)
graph->num_columns = 0;
graph->num_new_columns = 0;
graph->mapping_size = 0;
+ graph->lookahead[0] = NULL;
+ graph->lookahead[1] = NULL;
+ graph->lookahead_nr = 0;
+ graph->visual_root_depth = 0;
+ graph->visual_root_cascade = 0;
/*
* Start the column color at the maximum value, since we'll
* always increment it for the first commit we output.
@@ -457,6 +536,15 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns)
static int graph_is_interesting(struct git_graph *graph, struct commit *commit)
{
/*
+ * Commits in the lookahead buffer have been pre-fetched by
+ * get_revision() and will be shown in the future. They already have
+ * the SHOWN flag set when they were pre-fetched but the graph still
+ * needs to treat them as interesting parents.
+ */
+ for (int i = 0; i < graph->lookahead_nr; i++)
+ if (graph->lookahead[i] == commit)
+ return 1;
+ /*
* If revs->boundary is set, commits whose children have
* been shown are always interesting, even if they have the
* UNINTERESTING or TREESAME flags set.
@@ -561,6 +649,11 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
struct commit *commit,
int idx)
{
+ /*
+ * Get the initial merge_layout before it's modified to know if this
+ * is a merge.
+ */
+ int initial_merge_layout = graph->merge_layout;
int i = graph_find_new_column_by_commit(graph, commit);
int mapping_idx;
@@ -572,6 +665,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
i = graph->num_new_columns++;
graph->new_columns[i].commit = commit;
graph->new_columns[i].color = graph_find_commit_color(graph, commit);
+ graph->new_columns[i].is_merge_parent = 0;
}
if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {
@@ -610,6 +704,12 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
}
graph->mapping[mapping_idx] = i;
+
+ /*
+ * Mark non-first parents of a merge.
+ */
+ if (graph->num_parents > 1 && initial_merge_layout >= 0 && idx > -1)
+ graph->new_columns[i].is_merge_parent = 1;
}
static void graph_update_columns(struct git_graph *graph)
@@ -701,10 +801,20 @@ static void graph_update_columns(struct git_graph *graph)
if (graph->num_parents == 0)
graph->width += 2;
} else {
+ int j;
graph_insert_into_new_columns(graph, col_commit, -1);
+ /*
+ * This column is not the current commit, but we need to
+ * propagate the flag until the commit is processed.
+ */
+ j = graph_find_new_column_by_commit(graph, col_commit);
+ if (j >= 0 && graph->columns[i].is_merge_parent)
+ graph->new_columns[j].is_merge_parent = 1;
}
}
+ graph->commit_in_columns = is_commit_in_columns;
+
/*
* If graph_max_lanes is set, cap the width
*/
@@ -763,9 +873,145 @@ static int graph_needs_pre_commit_line(struct git_graph *graph)
graph->expansion_row < graph_num_expansion_rows(graph);
}
+struct commit *graph_pop_lookahead(struct git_graph *graph)
+{
+ struct commit *c;
+
+ if (!graph->lookahead_nr)
+ return NULL;
+
+ c = graph->lookahead[0];
+ if (!c)
+ BUG("lookahead buffer has %d entries but the first one is NULL",
+ graph->lookahead_nr);
+
+ graph->lookahead[0] = graph->lookahead[1];
+ graph->lookahead[1] = NULL;
+ graph->lookahead_nr--;
+ return c;
+}
+
+int graph_get_lookahead_room(struct git_graph *graph)
+{
+ return (int)ARRAY_SIZE(graph->lookahead) - graph->lookahead_nr;
+}
+
+void graph_push_lookahead(struct git_graph *graph, struct commit *c)
+{
+ if (!graph_get_lookahead_room(graph))
+ BUG("pushing into lookahead buffer when it is already full");
+
+ graph->lookahead[graph->lookahead_nr++] = c;
+}
+
+/*
+ * A commit can be a visual root when:
+ *
+ * - It has no parents.
+ *
+ * - It has parents but they are all filtered out and
+ * commit->parents arrives NULL.
+ *
+ * - Its parents are uninteresting.
+ *
+ * - It is not a boundary commit. Boundary commits also have no visible
+ * parents, but they are not selected as visual roots because they cannot
+ * cause the ambiguity of being vertically adjacent because:
+ *
+ * 1. A boundary only appears because an included commit is its child.
+ * Children are always above, and the renderer draws an edge down to
+ * the boundary from that child. Rather than starting a column like a
+ * visual root would do, it inherits its child column.
+ *
+ * 2. Included commits cannot appear below a boundary. Boundaries are
+ * ancestors of the exclusion point; if an included commit were an
+ * ancestor of the boundary it would be excluded and not rendered.
+ * Boundaries therefore always sink to the bottom.
+ */
+static int graph_is_visual_root_candidate(struct commit *c, struct git_graph *graph)
+{
+ struct commit_list *p;
+
+ if (c->object.flags & BOUNDARY)
+ return 0;
+ for (p = c->parents; p; p = p->next)
+ if (graph_is_interesting(graph, p->item))
+ return 0;
+ return 1;
+}
+
+static int graph_is_visual_root(struct git_graph *graph,
+ struct graph_lookahead_flags *flags)
+{
+ /*
+ * This must be only called for the current commit as graph contains
+ * the state for the current commit only.
+ *
+ * To check if a commit is a visual root, call graph_is_visual_root_candidate()
+ * but we won't know if it is really a visual root until we get to the
+ * next commit state.
+ *
+ * The current commit is an actual visual root if it is a candidate and
+ * the commit is not a non-first parent of a merge.
+ *
+ * *
+ * |\
+ * | * <- it is a visual root candidate but it shouldn't be indented
+ * * because it is already connected by an edge.
+ * ^ if commit_in_columns && is_merge_parent means the commit
+ * | was put by a merge and is connected.
+ * |
+ * `-------- if !is_next_visible means we're on the last commit, avoid
+ * indentation unless the one before is a visual root, then
+ * we need to differentiate from the one above.
+ *
+ * If next_has_columns means that the next commit has
+ * already a column, so it will not be rendered below, the
+ * current commit has to act as the last commit and omit
+ * indentation.
+ */
+ return graph_is_visual_root_candidate(graph->commit, graph) &&
+ !(graph->commit_in_columns &&
+ graph->columns[graph->commit_index].is_merge_parent) &&
+ flags->is_next_visible &&
+ (!flags->next_has_column || graph->visual_root_depth > 0);
+}
+
+/*
+ * Peeks the next commits via the lookahead buffer and sets the lookahead flags.
+ */
+static void graph_peek_next_visible(struct git_graph *graph,
+ struct graph_lookahead_flags *flags)
+{
+ flags->is_next_visible = 0;
+ flags->is_next_visual_root = 0;
+ flags->next_has_column = 0;
+
+ if (!graph->lookahead_nr)
+ return;
+
+ flags->is_next_visible = 1;
+ flags->next_has_column =
+ graph_find_new_column_by_commit(graph, graph->lookahead[0]) >= 0;
+
+ if (!graph_is_visual_root_candidate(graph->lookahead[0], graph))
+ return;
+
+ if (graph->lookahead_nr >= 2)
+ flags->is_next_visual_root = 1;
+}
+
+static int graph_needs_pre_root_line(struct git_graph *graph)
+{
+ return graph->commit_in_columns && graph->is_visual_root &&
+ graph->num_columns > 0 && !graph->visual_root_cascade &&
+ !graph->revs->no_graph_indent;
+}
+
void graph_update(struct git_graph *graph, struct commit *commit)
{
struct commit_list *parent;
+ struct graph_lookahead_flags flags;
/*
* Set the new commit
@@ -796,6 +1042,40 @@ void graph_update(struct git_graph *graph, struct commit *commit)
*/
graph_update_columns(graph);
+ graph_peek_next_visible(graph, &flags);
+
+ graph->is_visual_root = graph_is_visual_root(graph, &flags);
+
+ if (graph->is_visual_root) {
+ /*
+ * If next is a visual root we can omit the indent for the first
+ * visual root and start cascading.
+ */
+ if (!graph->visual_root_depth && flags.is_next_visual_root)
+ graph->visual_root_cascade = 1;
+
+ /*
+ * We wrap the cascading at a max of four columns at most, after
+ * that we wrap it back to the initial column.
+ *
+ * This could cause ambiguity in case of the next commit not
+ * being a visual root and be at the initial column after the
+ * first wrap.
+ *
+ * In case of being a non-visual-root the next, stop the
+ * cascading to get the commit indented.
+ */
+ if (!flags.is_next_visual_root &&
+ graph->visual_root_depth &&
+ !(graph->visual_root_depth % 4))
+ graph->visual_root_cascade = 0;
+
+ graph->visual_root_depth++;
+ } else {
+ graph->visual_root_depth = 0;
+ graph->visual_root_cascade = 0;
+ }
+
graph->expansion_row = 0;
/*
@@ -813,11 +1093,16 @@ void graph_update(struct git_graph *graph, struct commit *commit)
* room for it. We need to do this only if there is a branch row
* (or more) to the right of this commit.
*
+ * If it is a visual root, we need to print an extra row to
+ * connect the indentation.
+ *
* If there are less than 3 parents, we can immediately print the
* commit line.
*/
if (graph->state != GRAPH_PADDING)
graph->state = GRAPH_SKIP;
+ else if (graph_needs_pre_root_line(graph))
+ graph->state = GRAPH_PRE_ROOT;
else if (graph_needs_pre_commit_line(graph))
graph->state = GRAPH_PRE_COMMIT;
else
@@ -1065,6 +1350,20 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
if (col_commit == graph->commit) {
seen_this = 1;
+ if (graph->is_visual_root && !graph->revs->no_graph_indent) {
+ int depth = graph->visual_root_depth;
+ /*
+ * Each visual column is 2 characters wide.
+ * Omit the indentation for the first visual
+ * root in cascade mode.
+ *
+ * Have a max of 4 columns when cascading, after
+ * that wrap it and repeat.
+ */
+ int padding = ((depth - graph->visual_root_cascade) % 4) * 2;
+ graph_line_addchars(line, ' ', padding);
+ graph->width += padding;
+ }
graph_output_commit_char(graph, line);
if (graph_needs_truncation(graph, i)) {
@@ -1436,6 +1735,30 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
graph_update_state(graph, GRAPH_PADDING);
}
+static void graph_output_pre_root_line(struct git_graph *graph, struct graph_line *line)
+{
+ /*
+ * This function adds a row before a visual root, to connect the
+ * branch to the indented commit. It must only be called on a
+ * visual root.
+ */
+ if (!graph->is_visual_root)
+ BUG("commit must be a visual root to call pre_root_line");
+
+ for (int i = 0; i < graph->num_columns; i++) {
+ struct column *col = &graph->columns[i];
+ if (col->commit == graph->commit) {
+ graph_line_addch(line, ' ');
+ graph_line_write_column(line, col, '\\');
+ } else {
+ graph_line_write_column(line, col, '|');
+ }
+ graph_line_addch(line, ' ');
+ }
+
+ graph_update_state(graph, GRAPH_COMMIT);
+}
+
int graph_next_line(struct git_graph *graph, struct strbuf *sb)
{
int shown_commit_line = 0;
@@ -1461,6 +1784,9 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)
case GRAPH_PRE_COMMIT:
graph_output_pre_commit_line(graph, &line);
break;
+ case GRAPH_PRE_ROOT:
+ graph_output_pre_root_line(graph, &line);
+ break;
case GRAPH_COMMIT:
graph_output_commit_line(graph, &line);
shown_commit_line = 1;
diff --git a/graph.h b/graph.h
index 3fd1dcb..1193711 100644
--- a/graph.h
+++ b/graph.h
@@ -262,4 +262,21 @@ void graph_show_commit_msg(struct git_graph *graph,
FILE *file,
struct strbuf const *sb);
+/*
+ * Pop the first commit from the graph's lookahead buffer.
+ * Returns NULL if the buffer is empty.
+ */
+struct commit *graph_pop_lookahead(struct git_graph *graph);
+
+/*
+ * Returns how many more commits can be added to the lookahead buffer.
+ */
+int graph_get_lookahead_room(struct git_graph *graph);
+
+/*
+ * Push a commit into the lookahead buffer. Must only be called when
+ * graph_get_lookahead_room() returns > 0.
+ */
+void graph_push_lookahead(struct git_graph *graph, struct commit *c);
+
#endif /* GRAPH_H */
diff --git a/http-push.c b/http-push.c
index 60f6f8f..94a1fac 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1716,7 +1716,7 @@ int cmd_main(int argc, const char **argv)
{
struct transfer_request *request;
struct transfer_request *next_request;
- struct refspec rs = REFSPEC_INIT_PUSH;
+ struct refspec rs = REFSPEC_INIT_PUSH(the_hash_algo);
struct remote_lock *ref_lock = NULL;
struct remote_lock *info_ref_lock = NULL;
int delete_branch = 0;
diff --git a/lockfile.c b/lockfile.c
index 7add2f1..100f603 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -2,11 +2,14 @@
* Copyright (c) 2005, Junio C Hamano
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "git-compat-util.h"
#include "abspath.h"
#include "gettext.h"
#include "lockfile.h"
#include "parse.h"
+#include "repository.h"
#include "strbuf.h"
#include "wrapper.h"
@@ -162,8 +165,8 @@ static int read_lock_pid(const char *pid_path, uintmax_t *pid_out)
}
/* Make sure errno contains a meaningful value on error */
-static int lock_file(struct lock_file *lk, const char *path, int flags,
- int mode)
+static int lock_file(struct repository *r, struct lock_file *lk,
+ const char *path, int flags, int mode)
{
struct strbuf base_path = STRBUF_INIT;
struct strbuf lock_path = STRBUF_INIT;
@@ -176,7 +179,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags,
get_lock_path(&lock_path, base_path.buf);
get_pid_path(&pid_path, base_path.buf);
- lk->tempfile = create_tempfile_mode(lock_path.buf, mode);
+ lk->tempfile = repo_create_tempfile_mode(r, lock_path.buf, mode);
if (lk->tempfile)
lk->pid_tempfile = create_lock_pid_file(pid_path.buf, mode);
@@ -200,8 +203,9 @@ static int lock_file(struct lock_file *lk, const char *path, int flags,
* timeout_ms milliseconds. If timeout_ms is 0, try locking the file
* exactly once. If timeout_ms is -1, try indefinitely.
*/
-static int lock_file_timeout(struct lock_file *lk, const char *path,
- int flags, long timeout_ms, int mode)
+static int lock_file_timeout(struct repository *r, struct lock_file *lk,
+ const char *path, int flags, long timeout_ms,
+ int mode)
{
int n = 1;
int multiplier = 1;
@@ -209,7 +213,7 @@ static int lock_file_timeout(struct lock_file *lk, const char *path,
static int random_initialized = 0;
if (timeout_ms == 0)
- return lock_file(lk, path, flags, mode);
+ return lock_file(r, lk, path, flags, mode);
if (!random_initialized) {
srand((unsigned int)getpid());
@@ -223,7 +227,7 @@ static int lock_file_timeout(struct lock_file *lk, const char *path,
long backoff_ms, wait_ms;
int fd;
- fd = lock_file(lk, path, flags, mode);
+ fd = lock_file(r, lk, path, flags, mode);
if (fd >= 0)
return fd; /* success */
@@ -308,7 +312,17 @@ int hold_lock_file_for_update_timeout_mode(struct lock_file *lk,
const char *path, int flags,
long timeout_ms, int mode)
{
- int fd = lock_file_timeout(lk, path, flags, timeout_ms, mode);
+ return repo_hold_lock_file_for_update_timeout_mode(the_repository,
+ lk, path, flags,
+ timeout_ms, mode);
+}
+
+int repo_hold_lock_file_for_update_timeout_mode(struct repository *r,
+ struct lock_file *lk,
+ const char *path, int flags,
+ long timeout_ms, int mode)
+{
+ int fd = lock_file_timeout(r, lk, path, flags, timeout_ms, mode);
if (fd < 0) {
if (flags & LOCK_DIE_ON_ERROR)
unable_to_lock_die(path, errno);
diff --git a/lockfile.h b/lockfile.h
index e7233f2..1667612 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -189,6 +189,11 @@ int hold_lock_file_for_update_timeout_mode(
struct lock_file *lk, const char *path,
int flags, long timeout_ms, int mode);
+int repo_hold_lock_file_for_update_timeout_mode(struct repository *r,
+ struct lock_file *lk,
+ const char *path, int flags,
+ long timeout_ms, int mode);
+
static inline int hold_lock_file_for_update_timeout(
struct lock_file *lk, const char *path,
int flags, long timeout_ms)
@@ -197,6 +202,16 @@ static inline int hold_lock_file_for_update_timeout(
timeout_ms, 0666);
}
+static inline int repo_hold_lock_file_for_update_timeout(struct repository *r,
+ struct lock_file *lk,
+ const char *path,
+ int flags,
+ long timeout_ms)
+{
+ return repo_hold_lock_file_for_update_timeout_mode(r, lk, path, flags,
+ timeout_ms, 0666);
+}
+
/*
* Attempt to create a lockfile for the file at `path` and return a
* file descriptor for writing to it, or -1 on error. The flags
@@ -208,6 +223,13 @@ static inline int hold_lock_file_for_update(
return hold_lock_file_for_update_timeout(lk, path, flags, 0);
}
+static inline int repo_hold_lock_file_for_update(struct repository *r,
+ struct lock_file *lk,
+ const char *path, int flags)
+{
+ return repo_hold_lock_file_for_update_timeout(r, lk, path, flags, 0);
+}
+
static inline int hold_lock_file_for_update_mode(
struct lock_file *lk, const char *path,
int flags, int mode)
@@ -215,6 +237,15 @@ static inline int hold_lock_file_for_update_mode(
return hold_lock_file_for_update_timeout_mode(lk, path, flags, 0, mode);
}
+static inline int repo_hold_lock_file_for_update_mode(struct repository *r,
+ struct lock_file *lk,
+ const char *path,
+ int flags, int mode)
+{
+ return repo_hold_lock_file_for_update_timeout_mode(r, lk, path, flags,
+ 0, mode);
+}
+
/*
* Return a nonzero value iff `lk` is currently locked.
*/
diff --git a/loose.c b/loose.c
index bf01d3e..e97c3bb 100644
--- a/loose.c
+++ b/loose.c
@@ -137,7 +137,8 @@ int repo_write_loose_object_map(struct repository *repo)
return 0;
repo_common_path_replace(repo, &path, "objects/loose-object-idx");
- fd = hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1);
+ fd = repo_hold_lock_file_for_update_timeout(repo, &lock, path.buf,
+ LOCK_DIE_ON_ERROR, -1);
iter = kh_begin(map);
if (write_in_full(fd, loose_object_header, strlen(loose_object_header)) < 0)
goto errout;
@@ -179,7 +180,8 @@ static int write_one_object(struct odb_source_loose *loose,
struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
strbuf_addf(&path, "%s/loose-object-idx", loose->base.path);
- hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1);
+ repo_hold_lock_file_for_update_timeout(loose->base.odb->repo, &lock,
+ path.buf, LOCK_DIE_ON_ERROR, -1);
fd = open(path.buf, O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0)
diff --git a/meson.build b/meson.build
index f7c40ea..7073d58 100644
--- a/meson.build
+++ b/meson.build
@@ -374,6 +374,7 @@
'ewah/ewah_rlw.c',
'exec-cmd.c',
'fetch-negotiator.c',
+ 'fetch-object-info.c',
'fetch-pack.c',
'fmt-merge-msg.c',
'fsck.c',
diff --git a/midx-write.c b/midx-write.c
index 8c1837f..580724d 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -1627,8 +1627,8 @@ static int write_midx_internal(struct write_midx_opts *opts)
struct strbuf lock_name = STRBUF_INIT;
get_midx_chain_filename(opts->source, &lock_name);
- hold_lock_file_for_update(&lk, lock_name.buf,
- LOCK_DIE_ON_ERROR);
+ repo_hold_lock_file_for_update(r, &lk, lock_name.buf,
+ LOCK_DIE_ON_ERROR);
strbuf_release(&lock_name);
}
@@ -1647,7 +1647,8 @@ static int write_midx_internal(struct write_midx_opts *opts)
f = hashfd(r->hash_algo, get_tempfile_fd(incr),
get_tempfile_path(incr));
} else {
- hold_lock_file_for_update(&lk, midx_name.buf, LOCK_DIE_ON_ERROR);
+ repo_hold_lock_file_for_update(r, &lk, midx_name.buf,
+ LOCK_DIE_ON_ERROR);
f = hashfd(r->hash_algo, get_lock_file_fd(&lk),
get_lock_file_path(&lk));
}
diff --git a/object-file.c b/object-file.c
index 93602f8..ec35c31 100644
--- a/object-file.c
+++ b/object-file.c
@@ -27,6 +27,7 @@
#include "path.h"
#include "read-cache-ll.h"
#include "setup.h"
+#include "strvec.h"
#include "tempfile.h"
#include "tmp-objdir.h"
@@ -66,9 +67,17 @@ const char *odb_loose_path(struct odb_source_loose *loose,
}
/* Returns 1 if we have successfully freshened the file, 0 otherwise. */
-static int freshen_file(const char *fn)
+static int freshen_file(const char *fn, const time_t *mtime)
{
- return !utime(fn, NULL);
+ struct utimbuf times, *timesp = NULL;
+
+ if (mtime) {
+ times.actime = *mtime;
+ times.modtime = *mtime;
+ timesp = ×
+ }
+
+ return !utime(fn, timesp);
}
/*
@@ -78,11 +87,12 @@ static int freshen_file(const char *fn)
* either does not exist on disk, or has a stale mtime and may be subject to
* pruning).
*/
-int check_and_freshen_file(const char *fn, int freshen)
+int check_and_freshen_file(const char *fn, int freshen,
+ const time_t *mtime)
{
if (access(fn, F_OK))
return 0;
- if (freshen && !freshen_file(fn))
+ if (freshen && !freshen_file(fn, mtime))
return 0;
return 1;
}
@@ -129,11 +139,8 @@ int stream_object_signature(struct repository *r,
for (;;) {
char buf[1024 * 16];
ssize_t readlen = odb_read_stream_read(st, buf, sizeof(buf));
-
- if (readlen < 0) {
- odb_read_stream_close(st);
+ if (readlen < 0)
return -1;
- }
if (!readlen)
break;
git_hash_update(&c, buf, readlen);
@@ -315,31 +322,6 @@ int parse_loose_header(const char *hdr, struct object_info *oi)
return 0;
}
-static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,
- const void *buf, size_t len,
- struct object_id *oid,
- char *hdr, size_t *hdrlen)
-{
- git_hash_init(c, algo);
- git_hash_update(c, hdr, *hdrlen);
- git_hash_update(c, buf, len);
- git_hash_final_oid(oid, c);
-}
-
-void write_object_file_prepare(const struct git_hash_algo *algo,
- const void *buf, size_t len,
- enum object_type type, struct object_id *oid,
- char *hdr, size_t *hdrlen)
-{
- struct git_hash_ctx c;
-
- /* Generate the header */
- *hdrlen = format_object_header(hdr, *hdrlen, type, len);
-
- /* Hash (function pointers) computation */
- hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen);
-}
-
#define CHECK_COLLISION_DEST_VANISHED -2
static int check_collision(const char *source, const char *dest)
@@ -411,11 +393,12 @@ int finalize_object_file_flags(struct repository *repo,
{
unsigned retries = 0;
int ret;
+ struct repo_config_values *cfg = repo_config_values(repo);
retry:
ret = 0;
- if (object_creation_mode == OBJECT_CREATION_USES_RENAMES)
+ if (cfg->object_creation_mode == OBJECT_CREATION_USES_RENAMES)
goto try_rename;
else if (link(tmpfile, filename))
ret = errno;
@@ -475,10 +458,16 @@ void hash_object_file(const struct git_hash_algo *algo, const void *buf,
size_t len, enum object_type type,
struct object_id *oid)
{
+ struct git_hash_ctx c;
char hdr[MAX_HEADER_LEN];
- size_t hdrlen = sizeof(hdr);
+ int hdrlen;
- write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);
+ hdrlen = format_object_header(hdr, sizeof(hdr), type, len);
+
+ git_hash_init(&c, algo);
+ git_hash_update(&c, hdr, hdrlen);
+ git_hash_update(&c, buf, len);
+ git_hash_final_oid(oid, &c);
}
struct transaction_packfile {
@@ -497,9 +486,10 @@ struct odb_transaction_files {
struct tmp_objdir *objdir;
struct transaction_packfile packfile;
+ const char *prefix;
};
-static void prepare_loose_object_transaction(struct odb_transaction *base)
+int odb_transaction_files_prepare(struct odb_transaction *base)
{
struct odb_transaction_files *transaction =
container_of_or_null(base, struct odb_transaction_files, base);
@@ -511,19 +501,28 @@ static void prepare_loose_object_transaction(struct odb_transaction *base)
* added at the time they call odb_transaction_files_begin.
*/
if (!transaction || transaction->objdir)
- return;
+ return 0;
- transaction->objdir = tmp_objdir_create(base->source->odb->repo, "bulk-fsync");
- if (transaction->objdir)
- tmp_objdir_replace_primary_odb(transaction->objdir, 0);
+ transaction->objdir = tmp_objdir_create(base->source->odb->repo, transaction->prefix);
+ if (!transaction->objdir)
+ return error(_("unable to create temporary object directory"));
+
+ tmp_objdir_replace_primary_odb(transaction->objdir, 0);
+
+ return 0;
}
-static void fsync_loose_object_transaction(struct odb_transaction *base,
- int fd, const char *filename)
+void odb_transaction_files_fsync(struct odb_transaction *base,
+ int fd, const char *filename)
{
struct odb_transaction_files *transaction =
container_of_or_null(base, struct odb_transaction_files, base);
+ if (!transaction || !transaction->objdir) {
+ fsync_or_die(fd, filename);
+ return;
+ }
+
/*
* If we have an active ODB transaction, we issue a call that
* cleans the filesystem page cache but avoids a hardware flush
@@ -531,8 +530,7 @@ static void fsync_loose_object_transaction(struct odb_transaction *base,
* before renaming the objects to their final names as part of
* flush_batch_fsync.
*/
- if (!transaction || !transaction->objdir ||
- git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
+ if (git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
if (errno == ENOSYS)
warning(_("core.fsyncMethod = batch is unsupported on this platform"));
fsync_or_die(fd, filename);
@@ -540,432 +538,6 @@ static void fsync_loose_object_transaction(struct odb_transaction *base,
}
/*
- * Cleanup after batch-mode fsync_object_files.
- */
-static void flush_loose_object_transaction(struct odb_transaction_files *transaction)
-{
- struct strbuf temp_path = STRBUF_INIT;
- struct tempfile *temp;
-
- if (!transaction->objdir)
- return;
-
- /*
- * Issue a full hardware flush against a temporary file to ensure
- * that all objects are durable before any renames occur. The code in
- * fsync_loose_object_transaction has already issued a writeout
- * request, but it has not flushed any writeback cache in the storage
- * hardware or any filesystem logs. This fsync call acts as a barrier
- * to ensure that the data in each new object file is durable before
- * the final name is visible.
- */
- strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX",
- repo_get_object_directory(transaction->base.source->odb->repo));
- temp = xmks_tempfile(temp_path.buf);
- fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
- delete_tempfile(&temp);
- strbuf_release(&temp_path);
-
- /*
- * Make the object files visible in the primary ODB after their data is
- * fully durable.
- */
- tmp_objdir_migrate(transaction->objdir);
- transaction->objdir = NULL;
-}
-
-/* Finalize a file on disk, and close it. */
-static void close_loose_object(struct odb_source_loose *loose,
- int fd, const char *filename)
-{
- if (loose->base.will_destroy)
- goto out;
-
- if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
- fsync_loose_object_transaction(loose->base.odb->transaction, fd, filename);
- else if (fsync_object_files > 0)
- fsync_or_die(fd, filename);
- else
- fsync_component_or_die(FSYNC_COMPONENT_LOOSE_OBJECT, fd,
- filename);
-
-out:
- if (close(fd) != 0)
- die_errno(_("error when closing loose object file"));
-}
-
-/* Size of directory component, including the ending '/' */
-static inline int directory_size(const char *filename)
-{
- const char *s = strrchr(filename, '/');
- if (!s)
- return 0;
- return s - filename + 1;
-}
-
-/*
- * This creates a temporary file in the same directory as the final
- * 'filename'
- *
- * We want to avoid cross-directory filename renames, because those
- * can have problems on various filesystems (FAT, NFS, Coda).
- */
-static int create_tmpfile(struct repository *repo,
- struct strbuf *tmp, const char *filename)
-{
- int fd, dirlen = directory_size(filename);
-
- strbuf_reset(tmp);
- strbuf_add(tmp, filename, dirlen);
- strbuf_addstr(tmp, "tmp_obj_XXXXXX");
- fd = git_mkstemp_mode(tmp->buf, 0444);
- if (fd < 0 && dirlen && errno == ENOENT) {
- /*
- * Make sure the directory exists; note that the contents
- * of the buffer are undefined after mkstemp returns an
- * error, so we have to rewrite the whole buffer from
- * scratch.
- */
- strbuf_reset(tmp);
- strbuf_add(tmp, filename, dirlen - 1);
- if (mkdir(tmp->buf, 0777) && errno != EEXIST)
- return -1;
- if (adjust_shared_perm(repo, tmp->buf))
- return -1;
-
- /* Try again */
- strbuf_addstr(tmp, "/tmp_obj_XXXXXX");
- fd = git_mkstemp_mode(tmp->buf, 0444);
- }
- return fd;
-}
-
-/**
- * Common steps for loose object writers to start writing loose
- * objects:
- *
- * - Create tmpfile for the loose object.
- * - Setup zlib stream for compression.
- * - Start to feed header to zlib stream.
- *
- * Returns a "fd", which should later be provided to
- * end_loose_object_common().
- */
-static int start_loose_object_common(struct odb_source_loose *loose,
- struct strbuf *tmp_file,
- const char *filename, unsigned flags,
- git_zstream *stream,
- unsigned char *buf, size_t buflen,
- struct git_hash_ctx *c, struct git_hash_ctx *compat_c,
- char *hdr, int hdrlen)
-{
- const struct git_hash_algo *algo = loose->base.odb->repo->hash_algo;
- const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
- int fd;
- struct repo_config_values *cfg = repo_config_values(the_repository);
-
- fd = create_tmpfile(loose->base.odb->repo, tmp_file, filename);
- if (fd < 0) {
- if (flags & ODB_WRITE_OBJECT_SILENT)
- return -1;
- else if (errno == EACCES)
- return error(_("insufficient permission for adding "
- "an object to repository database %s"),
- loose->base.path);
- else
- return error_errno(
- _("unable to create temporary file"));
- }
-
- /* Setup zlib stream for compression */
- git_deflate_init(stream, cfg->zlib_compression_level);
- stream->next_out = buf;
- stream->avail_out = buflen;
- git_hash_init(c, algo);
- if (compat && compat_c)
- git_hash_init(compat_c, compat);
-
- /* Start to feed header to zlib stream */
- stream->next_in = (unsigned char *)hdr;
- stream->avail_in = hdrlen;
- while (git_deflate(stream, 0) == Z_OK)
- ; /* nothing */
- git_hash_update(c, hdr, hdrlen);
- if (compat && compat_c)
- git_hash_update(compat_c, hdr, hdrlen);
-
- return fd;
-}
-
-/**
- * Common steps for the inner git_deflate() loop for writing loose
- * objects. Returns what git_deflate() returns.
- */
-static int write_loose_object_common(struct odb_source_loose *loose,
- struct git_hash_ctx *c, struct git_hash_ctx *compat_c,
- git_zstream *stream, const int flush,
- unsigned char *in0, const int fd,
- unsigned char *compressed,
- const size_t compressed_len)
-{
- const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
- int ret;
-
- ret = git_deflate(stream, flush ? Z_FINISH : 0);
- git_hash_update(c, in0, stream->next_in - in0);
- if (compat && compat_c)
- git_hash_update(compat_c, in0, stream->next_in - in0);
- if (write_in_full(fd, compressed, stream->next_out - compressed) < 0)
- die_errno(_("unable to write loose object file"));
- stream->next_out = compressed;
- stream->avail_out = compressed_len;
-
- return ret;
-}
-
-/**
- * Common steps for loose object writers to end writing loose objects:
- *
- * - End the compression of zlib stream.
- * - Get the calculated oid to "oid".
- */
-static int end_loose_object_common(struct odb_source_loose *loose,
- struct git_hash_ctx *c, struct git_hash_ctx *compat_c,
- git_zstream *stream, struct object_id *oid,
- struct object_id *compat_oid)
-{
- const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
- int ret;
-
- ret = git_deflate_end_gently(stream);
- if (ret != Z_OK)
- return ret;
- git_hash_final_oid(oid, c);
- if (compat && compat_c)
- git_hash_final_oid(compat_oid, compat_c);
-
- return Z_OK;
-}
-
-int write_loose_object(struct odb_source_loose *loose,
- const struct object_id *oid, char *hdr,
- int hdrlen, const void *buf, unsigned long len,
- time_t mtime, unsigned flags)
-{
- int fd, ret;
- unsigned char compressed[4096];
- git_zstream stream;
- struct git_hash_ctx c;
- struct object_id parano_oid;
- static struct strbuf tmp_file = STRBUF_INIT;
- static struct strbuf filename = STRBUF_INIT;
-
- if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
- prepare_loose_object_transaction(loose->base.odb->transaction);
-
- odb_loose_path(loose, &filename, oid);
-
- fd = start_loose_object_common(loose, &tmp_file, filename.buf, flags,
- &stream, compressed, sizeof(compressed),
- &c, NULL, hdr, hdrlen);
- if (fd < 0)
- return -1;
-
- /* Then the data itself.. */
- stream.next_in = (void *)buf;
- stream.avail_in = len;
- do {
- unsigned char *in0 = stream.next_in;
-
- ret = write_loose_object_common(loose, &c, NULL, &stream, 1, in0, fd,
- compressed, sizeof(compressed));
- } while (ret == Z_OK);
-
- if (ret != Z_STREAM_END)
- die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid),
- ret);
- ret = end_loose_object_common(loose, &c, NULL, &stream, ¶no_oid, NULL);
- if (ret != Z_OK)
- die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid),
- ret);
- if (!oideq(oid, ¶no_oid))
- die(_("confused by unstable object source data for %s"),
- oid_to_hex(oid));
-
- close_loose_object(loose, fd, tmp_file.buf);
-
- if (mtime) {
- struct utimbuf utb;
- utb.actime = mtime;
- utb.modtime = mtime;
- if (utime(tmp_file.buf, &utb) < 0 &&
- !(flags & ODB_WRITE_OBJECT_SILENT))
- warning_errno(_("failed utime() on %s"), tmp_file.buf);
- }
-
- return finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf,
- FOF_SKIP_COLLISION_CHECK);
-}
-
-int odb_source_loose_write_stream(struct odb_source_loose *loose,
- struct odb_write_stream *in_stream, size_t len,
- struct object_id *oid)
-{
- const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
- struct object_id compat_oid;
- int fd, ret, err = 0, flush = 0;
- unsigned char compressed[4096];
- git_zstream stream;
- struct git_hash_ctx c, compat_c;
- struct strbuf tmp_file = STRBUF_INIT;
- struct strbuf filename = STRBUF_INIT;
- unsigned char buf[8192];
- int dirlen;
- char hdr[MAX_HEADER_LEN];
- int hdrlen;
-
- if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
- prepare_loose_object_transaction(loose->base.odb->transaction);
-
- /* Since oid is not determined, save tmp file to odb path. */
- strbuf_addf(&filename, "%s/", loose->base.path);
- hdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len);
-
- /*
- * Common steps for write_loose_object and stream_loose_object to
- * start writing loose objects:
- *
- * - Create tmpfile for the loose object.
- * - Setup zlib stream for compression.
- * - Start to feed header to zlib stream.
- */
- fd = start_loose_object_common(loose, &tmp_file, filename.buf, 0,
- &stream, compressed, sizeof(compressed),
- &c, &compat_c, hdr, hdrlen);
- if (fd < 0) {
- err = -1;
- goto cleanup;
- }
-
- /* Then the data itself.. */
- do {
- unsigned char *in0 = stream.next_in;
-
- if (!stream.avail_in && !in_stream->is_finished) {
- ssize_t read_len = odb_write_stream_read(in_stream, buf,
- sizeof(buf));
- if (read_len < 0) {
- close(fd);
- err = -1;
- goto cleanup;
- }
-
- stream.avail_in = read_len;
- stream.next_in = buf;
- in0 = buf;
- /* All data has been read. */
- if (in_stream->is_finished)
- flush = 1;
- }
- ret = write_loose_object_common(loose, &c, &compat_c, &stream, flush, in0, fd,
- compressed, sizeof(compressed));
- /*
- * Unlike write_loose_object(), we do not have the entire
- * buffer. If we get Z_BUF_ERROR due to too few input bytes,
- * then we'll replenish them in the next input_stream->read()
- * call when we loop.
- */
- } while (ret == Z_OK || ret == Z_BUF_ERROR);
-
- if (stream.total_in != len + hdrlen)
- die(_("write stream object %"PRIuMAX" != %"PRIuMAX), (uintmax_t)stream.total_in,
- (uintmax_t)len + hdrlen);
-
- /*
- * Common steps for write_loose_object and stream_loose_object to
- * end writing loose object:
- *
- * - End the compression of zlib stream.
- * - Get the calculated oid.
- */
- if (ret != Z_STREAM_END)
- die(_("unable to stream deflate new object (%d)"), ret);
- ret = end_loose_object_common(loose, &c, &compat_c, &stream, oid, &compat_oid);
- if (ret != Z_OK)
- die(_("deflateEnd on stream object failed (%d)"), ret);
- close_loose_object(loose, fd, tmp_file.buf);
-
- if (odb_freshen_object(loose->base.odb, oid)) {
- unlink_or_warn(tmp_file.buf);
- goto cleanup;
- }
- odb_loose_path(loose, &filename, oid);
-
- /* We finally know the object path, and create the missing dir. */
- dirlen = directory_size(filename.buf);
- if (dirlen) {
- struct strbuf dir = STRBUF_INIT;
- strbuf_add(&dir, filename.buf, dirlen);
-
- if (safe_create_dir_in_gitdir(loose->base.odb->repo, dir.buf) &&
- errno != EEXIST) {
- err = error_errno(_("unable to create directory %s"), dir.buf);
- strbuf_release(&dir);
- goto cleanup;
- }
- strbuf_release(&dir);
- }
-
- err = finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf,
- FOF_SKIP_COLLISION_CHECK);
- if (!err && compat)
- err = repo_add_loose_object_map(loose, oid, &compat_oid);
-cleanup:
- strbuf_release(&tmp_file);
- strbuf_release(&filename);
- return err;
-}
-
-int force_object_loose(struct odb_source *source,
- const struct object_id *oid, time_t mtime)
-{
- struct odb_source_files *files = odb_source_files_downcast(source);
- const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
- void *buf;
- size_t len;
- struct object_info oi = OBJECT_INFO_INIT;
- struct object_id compat_oid;
- enum object_type type;
- char hdr[MAX_HEADER_LEN];
- int hdrlen;
- int ret;
-
- for (struct odb_source *s = source->odb->sources; s; s = s->next) {
- struct odb_source_files *files = odb_source_files_downcast(s);
- if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0))
- return 0;
- }
-
- oi.typep = &type;
- oi.sizep = &len;
- oi.contentp = &buf;
- if (odb_read_object_info_extended(source->odb, oid, &oi, 0))
- return error(_("cannot read object for %s"), oid_to_hex(oid));
- if (compat) {
- if (repo_oid_to_algop(source->odb->repo, oid, compat, &compat_oid))
- return error(_("cannot map object %s to %s"),
- oid_to_hex(oid), compat->name);
- }
- hdrlen = format_object_header(hdr, sizeof(hdr), type, len);
- ret = write_loose_object(files->loose, oid, hdr, hdrlen, buf, len, mtime, 0);
- if (!ret && compat)
- ret = repo_add_loose_object_map(files->loose, oid, &compat_oid);
- free(buf);
-
- return ret;
-}
-
-/*
* We can't use the normal fsck_error_function() for index_mem(),
* because we don't yet have a valid oid for it to report. Instead,
* report the minimal fsck error here, and rely on the caller to
@@ -1383,13 +955,17 @@ int index_fd(struct index_state *istate, struct object_id *oid,
if (flags & INDEX_WRITE_OBJECT) {
struct object_database *odb = the_repository->objects;
- struct odb_transaction *transaction = odb_transaction_begin(odb);
+ struct odb_transaction *transaction = odb->transaction;
+ int inflight = !!transaction;
- ret = odb_transaction_write_object_stream(odb->transaction,
+ if (!inflight)
+ odb_transaction_begin_or_die(odb, &transaction, 0);
+ ret = odb_transaction_write_object_stream(transaction,
&stream,
xsize_t(st->st_size),
oid);
- odb_transaction_commit(transaction);
+ if (!inflight)
+ odb_transaction_commit(transaction);
} else {
ret = hash_blob_stream(&stream,
the_repository->hash_algo, oid,
@@ -1674,27 +1250,103 @@ int read_loose_object(struct repository *repo,
return ret;
}
-static void odb_transaction_files_commit(struct odb_transaction *base)
+static int odb_transaction_files_commit(struct odb_transaction *base)
{
struct odb_transaction_files *transaction =
container_of(base, struct odb_transaction_files, base);
- flush_loose_object_transaction(transaction);
+ if (transaction->objdir) {
+ struct strbuf temp_path = STRBUF_INIT;
+ struct tempfile *temp;
+
+ /*
+ * Issue a full hardware flush against a temporary file to ensure
+ * that all objects are durable before any renames occur. The code in
+ * odb_transaction_files_fsync has already issued a writeout
+ * request, but it has not flushed any writeback cache in the storage
+ * hardware or any filesystem logs. This fsync call acts as a barrier
+ * to ensure that the data in each new object file is durable before
+ * the final name is visible.
+ */
+ strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX",
+ repo_get_object_directory(transaction->base.source->odb->repo));
+ temp = xmks_tempfile(temp_path.buf);
+ fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
+ delete_tempfile(&temp);
+ strbuf_release(&temp_path);
+
+ /*
+ * Make the object files visible in the primary ODB after their data is
+ * fully durable.
+ */
+ if (tmp_objdir_migrate(transaction->objdir))
+ return error(_("unable to migrate temporary objects"));
+
+ transaction->objdir = NULL;
+ }
+
flush_packfile_transaction(transaction);
+
+ return 0;
}
-struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
+static int odb_transaction_files_env(struct odb_transaction *base,
+ struct strvec *env)
+{
+ struct odb_transaction_files *transaction =
+ container_of(base, struct odb_transaction_files, base);
+ int ret;
+
+ ret = odb_transaction_files_prepare(&transaction->base);
+ if (!ret)
+ strvec_pushv(env, tmp_objdir_env(transaction->objdir));
+
+ return ret;
+}
+
+int odb_transaction_files_begin(struct odb_source *source,
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags)
{
struct odb_transaction_files *transaction;
- struct object_database *odb = source->odb;
-
- if (odb->transaction)
- return NULL;
transaction = xcalloc(1, sizeof(*transaction));
transaction->base.source = source;
transaction->base.commit = odb_transaction_files_commit;
transaction->base.write_object_stream = odb_transaction_files_write_object_stream;
+ transaction->base.env = odb_transaction_files_env;
- return &transaction->base;
+ transaction->prefix = "bulk-fsync";
+ if (flags & ODB_TRANSACTION_RECEIVE) {
+ /*
+ * ODB transactions for git-receive-pack(1) eagerly create a
+ * temporary directory and use a different temporary directory
+ * prefix.
+ *
+ * NEEDSWORK: This transaction flag is only used by the "files"
+ * backend to special case temporary directory set up and
+ * handling. Ideally transaction users should not have to care
+ * though. To avoid this, we could eagerly create the temporary
+ * directory and use the same prefix name for all transactions.
+ */
+ transaction->prefix = "incoming";
+ if (odb_transaction_files_prepare(&transaction->base)) {
+ free(transaction);
+ return -1;
+ }
+ }
+
+ *out = &transaction->base;
+
+ return 0;
+}
+
+void free_object_info_contents(struct object_info *object_info)
+{
+ if (!object_info)
+ return;
+ free(object_info->typep);
+ free(object_info->sizep);
+ free(object_info->disk_sizep);
+ free(object_info->delta_base_oid);
}
diff --git a/object-file.h b/object-file.h
index 4c87cd1..805f2cf 100644
--- a/object-file.h
+++ b/object-file.h
@@ -5,6 +5,7 @@
#include "object.h"
#include "odb.h"
#include "odb/source-loose.h"
+#include "odb/transaction.h"
/* The maximum size for an object header. */
#define MAX_HEADER_LEN 32
@@ -24,20 +25,6 @@ struct object_info;
struct odb_source;
/*
- * Write the given stream into the loose object source. The only difference
- * from the generic implementation of this function is that we don't perform an
- * object existence check here.
- *
- * TODO: We should stop exposing this function altogether and move it into
- * "odb/source-loose.c". This requires a couple of refactorings though to make
- * `force_object_loose()` generic and is thus postponed to a later point in
- * time.
- */
-int odb_source_loose_write_stream(struct odb_source_loose *source,
- struct odb_write_stream *stream, size_t len,
- struct object_id *oid);
-
-/*
* Put in `buf` the name of the file in the local object database that
* would be used to store a loose object with the specified oid.
*/
@@ -97,9 +84,6 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
int format_object_header(char *str, size_t size, enum object_type type,
size_t objsize);
-int force_object_loose(struct odb_source *source,
- const struct object_id *oid, time_t mtime);
-
/**
* With in-core object data in "buf", rehash it to make sure the
* object name actually matches "oid" to detect object corruption.
@@ -133,17 +117,10 @@ int finalize_object_file_flags(struct repository *repo,
void hash_object_file(const struct git_hash_algo *algo, const void *buf,
size_t len, enum object_type type,
struct object_id *oid);
-void write_object_file_prepare(const struct git_hash_algo *algo,
- const void *buf, size_t len,
- enum object_type type, struct object_id *oid,
- char *hdr, size_t *hdrlen);
-int write_loose_object(struct odb_source_loose *loose,
- const struct object_id *oid, char *hdr,
- int hdrlen, const void *buf, unsigned long len,
- time_t mtime, unsigned flags);
/* Helper to check and "touch" a file */
-int check_and_freshen_file(const char *fn, int freshen);
+int check_and_freshen_file(const char *fn, int freshen,
+ const time_t *mtime);
/*
* Open the loose object at path, check its hash, and return the contents,
@@ -194,9 +171,14 @@ struct odb_transaction;
/*
* Tell the object database to optimize for adding
* multiple objects. odb_transaction_files_commit must be called
- * to make new objects visible. If a transaction is already
- * pending, NULL is returned.
+ * to make new objects visible.
*/
-struct odb_transaction *odb_transaction_files_begin(struct odb_source *source);
+int odb_transaction_files_begin(struct odb_source *source,
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags);
+
+int odb_transaction_files_prepare(struct odb_transaction *base);
+void odb_transaction_files_fsync(struct odb_transaction *base,
+ int fd, const char *filename);
#endif /* OBJECT_FILE_H */
diff --git a/odb.c b/odb.c
index cf6e793..dabd481 100644
--- a/odb.c
+++ b/odb.c
@@ -738,7 +738,7 @@ int odb_pretend_object(struct object_database *odb,
return 0;
return odb_source_write_object(odb->inmemory_objects,
- buf, len, type, oid, NULL, 0);
+ buf, len, type, oid, NULL, NULL, 0);
}
void *odb_read_object(struct object_database *odb,
@@ -829,7 +829,7 @@ int odb_freshen_object(struct object_database *odb,
struct odb_source *source;
odb_prepare_alternates(odb);
for (source = odb->sources; source; source = source->next)
- if (odb_source_freshen_object(source, oid))
+ if (odb_source_freshen_object(source, oid, NULL))
return 1;
return 0;
}
@@ -989,11 +989,42 @@ int odb_write_object_ext(struct object_database *odb,
const void *buf, unsigned long len,
enum object_type type,
struct object_id *oid,
- struct object_id *compat_oid,
+ const struct object_id *compat_oid_in,
enum odb_write_object_flags flags)
{
+ const struct git_hash_algo *compat = odb->repo->compat_hash_algo;
+ struct object_id compat_oid, *compat_oid_p = NULL;
+
+ hash_object_file(odb->repo->hash_algo, buf, len, type, oid);
+
+ /*
+ * We can skip the write in case we already have the object available.
+ * In that case, we only freshen its mtime.
+ */
+ if (odb_freshen_object(odb, oid))
+ return 0;
+
+ if (compat) {
+ const struct git_hash_algo *algo = odb->repo->hash_algo;
+
+ if (compat_oid_in) {
+ oidcpy(&compat_oid, compat_oid_in);
+ } else if (type == OBJ_BLOB) {
+ hash_object_file(compat, buf, len, type, &compat_oid);
+ } else {
+ struct strbuf converted = STRBUF_INIT;
+ convert_object_file(odb->repo, &converted, algo, compat,
+ buf, len, type, 0);
+ hash_object_file(compat, converted.buf, converted.len,
+ type, &compat_oid);
+ strbuf_release(&converted);
+ }
+
+ compat_oid_p = &compat_oid;
+ }
+
return odb_source_write_object(odb->sources, buf, len, type,
- oid, compat_oid, flags);
+ oid, compat_oid_p, NULL, flags);
}
int odb_write_object_stream(struct object_database *odb,
diff --git a/odb.h b/odb.h
index 9475464..cbc2f9c 100644
--- a/odb.h
+++ b/odb.h
@@ -8,6 +8,7 @@
#include "thread-utils.h"
struct cached_object_entry;
+struct list_objects_filter_options;
struct odb_source_inmemory;
struct packed_git;
struct repository;
@@ -339,6 +340,12 @@ struct object_info {
* or multiple times in the same source.
*/
struct odb_source_info *source_infop;
+
+ /*
+ * object-info protocol specific. Set by the protocol when the remote
+ * does not recognize the requested object.
+ */
+ unsigned int unrecognized:1;
};
/*
@@ -502,6 +509,17 @@ struct odb_for_each_object_options {
*/
const struct object_id *prefix;
size_t prefix_hex_len;
+
+ /*
+ * Optional object filter that allows backends to skip yielding
+ * objects that are excluded by the filter as an optimization. The
+ * filter is a best-effort hint: backends may use it to skip
+ * excluded objects (e.g. by consulting a reachability bitmap), but
+ * are also free to ignore it entirely and yield every object. As a
+ * consequence, callers must re-apply the filter on yielded objects
+ * if they require strict filtering semantics.
+ */
+ const struct list_objects_filter_options *filter;
};
/*
@@ -585,9 +603,11 @@ enum odb_write_object_flags {
/*
* Write an object into the object database. The object is being written into
- * the local alternate of the repository. If provided, the converted object ID
- * as well as the compatibility object ID are written to the respective
- * pointers.
+ * the local alternate of the repository. If provided, the object ID of the
+ * final object is written into `oid`.
+ *
+ * If the caller provides a `compat_oid`, then this compatibility object hash
+ * will be stored instead of computing the compatibility hash ad-hoc.
*
* Returns 0 on success, a negative error code otherwise.
*/
@@ -595,7 +615,7 @@ int odb_write_object_ext(struct object_database *odb,
const void *buf, unsigned long len,
enum object_type type,
struct object_id *oid,
- struct object_id *compat_oid,
+ const struct object_id *compat_oid,
enum odb_write_object_flags flags);
static inline int odb_write_object(struct object_database *odb,
@@ -617,4 +637,7 @@ void parse_alternates(const char *string,
const char *relative_base,
struct strvec *out);
+/* Free pointers inside of object_info, but not object_info itself */
+void free_object_info_contents(struct object_info *object_info);
+
#endif /* ODB_H */
diff --git a/odb/source-files.c b/odb/source-files.c
index 6c8e935..5e086d2 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -150,11 +150,12 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source,
}
static int odb_source_files_freshen_object(struct odb_source *source,
- const struct object_id *oid)
+ const struct object_id *oid,
+ const time_t *mtime)
{
struct odb_source_files *files = odb_source_files_downcast(source);
- if (odb_source_freshen_object(&files->packed->base, oid) ||
- odb_source_freshen_object(&files->loose->base, oid))
+ if (odb_source_freshen_object(&files->packed->base, oid, mtime) ||
+ odb_source_freshen_object(&files->loose->base, oid, mtime))
return 1;
return 0;
}
@@ -162,13 +163,14 @@ static int odb_source_files_freshen_object(struct odb_source *source,
static int odb_source_files_write_object(struct odb_source *source,
const void *buf, size_t len,
enum object_type type,
- struct object_id *oid,
- struct object_id *compat_oid,
+ const struct object_id *oid,
+ const struct object_id *compat_oid,
+ const time_t *mtime,
enum odb_write_object_flags flags)
{
struct odb_source_files *files = odb_source_files_downcast(source);
return odb_source_write_object(&files->loose->base, buf, len, type,
- oid, compat_oid, flags);
+ oid, compat_oid, mtime, flags);
}
static int odb_source_files_write_object_stream(struct odb_source *source,
@@ -181,13 +183,10 @@ static int odb_source_files_write_object_stream(struct odb_source *source,
}
static int odb_source_files_begin_transaction(struct odb_source *source,
- struct odb_transaction **out)
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags)
{
- struct odb_transaction *tx = odb_transaction_files_begin(source);
- if (!tx)
- return -1;
- *out = tx;
- return 0;
+ return odb_transaction_files_begin(source, out, flags);
}
static int odb_source_files_read_alternates(struct odb_source *source,
@@ -218,7 +217,8 @@ static int odb_source_files_write_alternate(struct odb_source *source,
int found = 0;
int ret;
- hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR);
+ repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
+ LOCK_DIE_ON_ERROR);
out = fdopen_lock_file(&lock, "w");
if (!out) {
ret = error_errno(_("unable to fdopen alternates lockfile"));
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index f41a602..3e71611 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -230,15 +230,14 @@ static int odb_source_inmemory_count_objects(struct odb_source *source,
static int odb_source_inmemory_write_object(struct odb_source *source,
const void *buf, size_t len,
enum object_type type,
- struct object_id *oid,
- struct object_id *compat_oid UNUSED,
+ const struct object_id *oid,
+ const struct object_id *compat_oid UNUSED,
+ const time_t *mtime UNUSED,
enum odb_write_object_flags flags UNUSED)
{
struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
struct inmemory_object *object;
- hash_object_file(source->odb->repo->hash_algo, buf, len, type, oid);
-
if (!inmemory->objects) {
CALLOC_ARRAY(inmemory->objects, 1);
oidtree_init(inmemory->objects);
@@ -285,8 +284,10 @@ static int odb_source_inmemory_write_object_stream(struct odb_source *source,
goto out;
}
+ hash_object_file(source->odb->repo->hash_algo, data, total_read, OBJ_BLOB, oid);
+
ret = odb_source_inmemory_write_object(source, data, len, OBJ_BLOB, oid,
- NULL, 0);
+ NULL, NULL, 0);
if (ret < 0)
goto out;
@@ -296,7 +297,8 @@ static int odb_source_inmemory_write_object_stream(struct odb_source *source,
}
static int odb_source_inmemory_freshen_object(struct odb_source *source,
- const struct object_id *oid)
+ const struct object_id *oid,
+ const time_t *mtime UNUSED)
{
struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
if (find_cached_object(inmemory, oid))
@@ -305,7 +307,8 @@ static int odb_source_inmemory_freshen_object(struct odb_source *source,
}
static int odb_source_inmemory_begin_transaction(struct odb_source *source UNUSED,
- struct odb_transaction **out UNUSED)
+ struct odb_transaction **out UNUSED,
+ enum odb_transaction_flags flags UNUSED)
{
return error("in-memory source does not support transactions");
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 091e8e0..ef0e919 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -11,8 +11,11 @@
#include "odb/source-loose.h"
#include "odb/streaming.h"
#include "oidtree.h"
+#include "path.h"
#include "repository.h"
#include "strbuf.h"
+#include "tempfile.h"
+#include "write-or-die.h"
static int append_loose_object(const struct object_id *oid,
const char *path UNUSED,
@@ -574,53 +577,270 @@ static int odb_source_loose_count_objects(struct odb_source *source,
}
static int odb_source_loose_freshen_object(struct odb_source *source,
- const struct object_id *oid)
+ const struct object_id *oid,
+ const time_t *mtime)
{
struct odb_source_loose *loose = odb_source_loose_downcast(source);
static struct strbuf path = STRBUF_INIT;
odb_loose_path(loose, &path, oid);
- return !!check_and_freshen_file(path.buf, 1);
+ return !!check_and_freshen_file(path.buf, 1, mtime);
+}
+
+/* Finalize a file on disk, and close it. */
+static void close_loose_object(struct odb_source_loose *loose,
+ int fd, const char *filename)
+{
+ if (loose->base.will_destroy)
+ goto out;
+
+ if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
+ odb_transaction_files_fsync(loose->base.odb->transaction, fd, filename);
+ else if (fsync_object_files > 0)
+ fsync_or_die(fd, filename);
+ else
+ fsync_component_or_die(FSYNC_COMPONENT_LOOSE_OBJECT, fd,
+ filename);
+
+out:
+ if (close(fd) != 0)
+ die_errno(_("error when closing loose object file"));
+}
+
+/* Size of directory component, including the ending '/' */
+static inline int directory_size(const char *filename)
+{
+ const char *s = strrchr(filename, '/');
+ if (!s)
+ return 0;
+ return s - filename + 1;
+}
+
+/*
+ * This creates a temporary file in the same directory as the final
+ * 'filename'
+ *
+ * We want to avoid cross-directory filename renames, because those
+ * can have problems on various filesystems (FAT, NFS, Coda).
+ */
+static int create_tmpfile(struct repository *repo,
+ struct strbuf *tmp, const char *filename)
+{
+ int fd, dirlen = directory_size(filename);
+
+ strbuf_reset(tmp);
+ strbuf_add(tmp, filename, dirlen);
+ strbuf_addstr(tmp, "tmp_obj_XXXXXX");
+ fd = git_mkstemp_mode(tmp->buf, 0444);
+ if (fd < 0 && dirlen && errno == ENOENT) {
+ /*
+ * Make sure the directory exists; note that the contents
+ * of the buffer are undefined after mkstemp returns an
+ * error, so we have to rewrite the whole buffer from
+ * scratch.
+ */
+ strbuf_reset(tmp);
+ strbuf_add(tmp, filename, dirlen - 1);
+ if (mkdir(tmp->buf, 0777) && errno != EEXIST)
+ return -1;
+ if (adjust_shared_perm(repo, tmp->buf))
+ return -1;
+
+ /* Try again */
+ strbuf_addstr(tmp, "/tmp_obj_XXXXXX");
+ fd = git_mkstemp_mode(tmp->buf, 0444);
+ }
+ return fd;
+}
+
+/**
+ * Common steps for loose object writers to start writing loose
+ * objects:
+ *
+ * - Create tmpfile for the loose object.
+ * - Setup zlib stream for compression.
+ * - Start to feed header to zlib stream.
+ *
+ * Returns a "fd", which should later be provided to
+ * end_loose_object_common().
+ */
+static int start_loose_object_common(struct odb_source_loose *loose,
+ struct strbuf *tmp_file,
+ const char *filename, unsigned flags,
+ git_zstream *stream,
+ unsigned char *buf, size_t buflen,
+ struct git_hash_ctx *c, struct git_hash_ctx *compat_c,
+ char *hdr, int hdrlen)
+{
+ const struct git_hash_algo *algo = loose->base.odb->repo->hash_algo;
+ const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
+ int fd;
+ struct repo_config_values *cfg = repo_config_values(loose->base.odb->repo);
+
+ fd = create_tmpfile(loose->base.odb->repo, tmp_file, filename);
+ if (fd < 0) {
+ if (flags & ODB_WRITE_OBJECT_SILENT)
+ return -1;
+ else if (errno == EACCES)
+ return error(_("insufficient permission for adding "
+ "an object to repository database %s"),
+ loose->base.path);
+ else
+ return error_errno(
+ _("unable to create temporary file"));
+ }
+
+ /* Setup zlib stream for compression */
+ git_deflate_init(stream, cfg->zlib_compression_level);
+ stream->next_out = buf;
+ stream->avail_out = buflen;
+ git_hash_init(c, algo);
+ if (compat && compat_c)
+ git_hash_init(compat_c, compat);
+
+ /* Start to feed header to zlib stream */
+ stream->next_in = (unsigned char *)hdr;
+ stream->avail_in = hdrlen;
+ while (git_deflate(stream, 0) == Z_OK)
+ ; /* nothing */
+ git_hash_update(c, hdr, hdrlen);
+ if (compat && compat_c)
+ git_hash_update(compat_c, hdr, hdrlen);
+
+ return fd;
+}
+
+/**
+ * Common steps for the inner git_deflate() loop for writing loose
+ * objects. Returns what git_deflate() returns.
+ */
+static int write_loose_object_common(struct odb_source_loose *loose,
+ struct git_hash_ctx *c, struct git_hash_ctx *compat_c,
+ git_zstream *stream, const int flush,
+ unsigned char *in0, const int fd,
+ unsigned char *compressed,
+ const size_t compressed_len)
+{
+ const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
+ int ret;
+
+ ret = git_deflate(stream, flush ? Z_FINISH : 0);
+ git_hash_update(c, in0, stream->next_in - in0);
+ if (compat && compat_c)
+ git_hash_update(compat_c, in0, stream->next_in - in0);
+ if (write_in_full(fd, compressed, stream->next_out - compressed) < 0)
+ die_errno(_("unable to write loose object file"));
+ stream->next_out = compressed;
+ stream->avail_out = compressed_len;
+
+ return ret;
+}
+
+/**
+ * Common steps for loose object writers to end writing loose objects:
+ *
+ * - End the compression of zlib stream.
+ * - Get the calculated oid to "oid".
+ */
+static int end_loose_object_common(struct odb_source_loose *loose,
+ struct git_hash_ctx *c, struct git_hash_ctx *compat_c,
+ git_zstream *stream, struct object_id *oid,
+ struct object_id *compat_oid)
+{
+ const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
+ int ret;
+
+ ret = git_deflate_end_gently(stream);
+ if (ret != Z_OK)
+ return ret;
+ git_hash_final_oid(oid, c);
+ if (compat && compat_c)
+ git_hash_final_oid(compat_oid, compat_c);
+
+ return Z_OK;
+}
+
+static int write_loose_object(struct odb_source_loose *loose,
+ const struct object_id *oid, char *hdr,
+ int hdrlen, const void *buf, unsigned long len,
+ const time_t *mtime, unsigned flags)
+{
+ int fd, ret;
+ unsigned char compressed[4096];
+ git_zstream stream;
+ struct git_hash_ctx c;
+ struct object_id parano_oid;
+ static struct strbuf tmp_file = STRBUF_INIT;
+ static struct strbuf filename = STRBUF_INIT;
+
+ if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
+ odb_transaction_files_prepare(loose->base.odb->transaction);
+
+ odb_loose_path(loose, &filename, oid);
+
+ fd = start_loose_object_common(loose, &tmp_file, filename.buf, flags,
+ &stream, compressed, sizeof(compressed),
+ &c, NULL, hdr, hdrlen);
+ if (fd < 0)
+ return -1;
+
+ /* Then the data itself.. */
+ stream.next_in = (void *)buf;
+ stream.avail_in = len;
+ do {
+ unsigned char *in0 = stream.next_in;
+
+ ret = write_loose_object_common(loose, &c, NULL, &stream, 1, in0, fd,
+ compressed, sizeof(compressed));
+ } while (ret == Z_OK);
+
+ if (ret != Z_STREAM_END)
+ die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid),
+ ret);
+ ret = end_loose_object_common(loose, &c, NULL, &stream, ¶no_oid, NULL);
+ if (ret != Z_OK)
+ die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid),
+ ret);
+ if (!oideq(oid, ¶no_oid))
+ die(_("confused by unstable object source data for %s"),
+ oid_to_hex(oid));
+
+ close_loose_object(loose, fd, tmp_file.buf);
+
+ if (mtime) {
+ struct utimbuf utb = {
+ .actime = *mtime,
+ .modtime = *mtime,
+ };
+
+ if (utime(tmp_file.buf, &utb) < 0 &&
+ !(flags & ODB_WRITE_OBJECT_SILENT))
+ warning_errno(_("failed utime() on %s"), tmp_file.buf);
+ }
+
+ return finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf,
+ FOF_SKIP_COLLISION_CHECK);
}
static int odb_source_loose_write_object(struct odb_source *source,
const void *buf, size_t len,
- enum object_type type, struct object_id *oid,
- struct object_id *compat_oid_in,
+ enum object_type type,
+ const struct object_id *oid,
+ const struct object_id *compat_oid,
+ const time_t *mtime,
enum odb_write_object_flags flags)
{
struct odb_source_loose *loose = odb_source_loose_downcast(source);
- const struct git_hash_algo *algo = source->odb->repo->hash_algo;
- const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
- struct object_id compat_oid;
char hdr[MAX_HEADER_LEN];
- size_t hdrlen = sizeof(hdr);
+ int hdrlen;
- /* Generate compat_oid */
- if (compat) {
- if (compat_oid_in)
- oidcpy(&compat_oid, compat_oid_in);
- else if (type == OBJ_BLOB)
- hash_object_file(compat, buf, len, type, &compat_oid);
- else {
- struct strbuf converted = STRBUF_INIT;
- convert_object_file(source->odb->repo, &converted, algo, compat,
- buf, len, type, 0);
- hash_object_file(compat, converted.buf, converted.len,
- type, &compat_oid);
- strbuf_release(&converted);
- }
- }
+ hdrlen = format_object_header(hdr, sizeof(hdr), type, len);
- /* Normally if we have it in the pack then we do not bother writing
- * it out into .git/objects/??/?{38} file.
- */
- write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);
- if (odb_freshen_object(source->odb, oid))
- return 0;
- if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, 0, flags))
+ if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, mtime, flags))
return -1;
- if (compat)
- return repo_add_loose_object_map(loose, oid, &compat_oid);
+
+ if (compat_oid)
+ return repo_add_loose_object_map(loose, oid, compat_oid);
+
return 0;
}
@@ -629,16 +849,125 @@ static int odb_source_loose_write_object_stream(struct odb_source *source,
size_t len,
struct object_id *oid)
{
- /*
- * TODO: the implementation should be moved here, see the comment on
- * the called function in "object-file.h".
- */
struct odb_source_loose *loose = odb_source_loose_downcast(source);
- return odb_source_loose_write_stream(loose, in_stream, len, oid);
+ const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo;
+ struct object_id compat_oid;
+ int fd, ret, err = 0, flush = 0;
+ unsigned char compressed[4096];
+ git_zstream stream;
+ struct git_hash_ctx c, compat_c;
+ struct strbuf tmp_file = STRBUF_INIT;
+ struct strbuf filename = STRBUF_INIT;
+ unsigned char buf[8192];
+ int dirlen;
+ char hdr[MAX_HEADER_LEN];
+ int hdrlen;
+
+ if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
+ odb_transaction_files_prepare(loose->base.odb->transaction);
+
+ /* Since oid is not determined, save tmp file to odb path. */
+ strbuf_addf(&filename, "%s/", loose->base.path);
+ hdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len);
+
+ /*
+ * Common steps for write_loose_object and stream_loose_object to
+ * start writing loose objects:
+ *
+ * - Create tmpfile for the loose object.
+ * - Setup zlib stream for compression.
+ * - Start to feed header to zlib stream.
+ */
+ fd = start_loose_object_common(loose, &tmp_file, filename.buf, 0,
+ &stream, compressed, sizeof(compressed),
+ &c, &compat_c, hdr, hdrlen);
+ if (fd < 0) {
+ err = -1;
+ goto cleanup;
+ }
+
+ /* Then the data itself.. */
+ do {
+ unsigned char *in0 = stream.next_in;
+
+ if (!stream.avail_in && !in_stream->is_finished) {
+ ssize_t read_len = odb_write_stream_read(in_stream, buf,
+ sizeof(buf));
+ if (read_len < 0) {
+ close(fd);
+ err = -1;
+ goto cleanup;
+ }
+
+ stream.avail_in = read_len;
+ stream.next_in = buf;
+ in0 = buf;
+ /* All data has been read. */
+ if (in_stream->is_finished)
+ flush = 1;
+ }
+ ret = write_loose_object_common(loose, &c, &compat_c, &stream, flush, in0, fd,
+ compressed, sizeof(compressed));
+ /*
+ * Unlike write_loose_object(), we do not have the entire
+ * buffer. If we get Z_BUF_ERROR due to too few input bytes,
+ * then we'll replenish them in the next input_stream->read()
+ * call when we loop.
+ */
+ } while (ret == Z_OK || ret == Z_BUF_ERROR);
+
+ if (stream.total_in != len + hdrlen)
+ die(_("write stream object %"PRIuMAX" != %"PRIuMAX), (uintmax_t)stream.total_in,
+ (uintmax_t)len + hdrlen);
+
+ /*
+ * Common steps for write_loose_object and stream_loose_object to
+ * end writing loose object:
+ *
+ * - End the compression of zlib stream.
+ * - Get the calculated oid.
+ */
+ if (ret != Z_STREAM_END)
+ die(_("unable to stream deflate new object (%d)"), ret);
+ ret = end_loose_object_common(loose, &c, &compat_c, &stream, oid, &compat_oid);
+ if (ret != Z_OK)
+ die(_("deflateEnd on stream object failed (%d)"), ret);
+ close_loose_object(loose, fd, tmp_file.buf);
+
+ if (odb_freshen_object(loose->base.odb, oid)) {
+ unlink_or_warn(tmp_file.buf);
+ goto cleanup;
+ }
+ odb_loose_path(loose, &filename, oid);
+
+ /* We finally know the object path, and create the missing dir. */
+ dirlen = directory_size(filename.buf);
+ if (dirlen) {
+ struct strbuf dir = STRBUF_INIT;
+ strbuf_add(&dir, filename.buf, dirlen);
+
+ if (safe_create_dir_in_gitdir(loose->base.odb->repo, dir.buf) &&
+ errno != EEXIST) {
+ err = error_errno(_("unable to create directory %s"), dir.buf);
+ strbuf_release(&dir);
+ goto cleanup;
+ }
+ strbuf_release(&dir);
+ }
+
+ err = finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf,
+ FOF_SKIP_COLLISION_CHECK);
+ if (!err && compat)
+ err = repo_add_loose_object_map(loose, oid, &compat_oid);
+cleanup:
+ strbuf_release(&tmp_file);
+ strbuf_release(&filename);
+ return err;
}
static int odb_source_loose_begin_transaction(struct odb_source *source UNUSED,
- struct odb_transaction **out UNUSED)
+ struct odb_transaction **out UNUSED,
+ enum odb_transaction_flags flags UNUSED)
{
/* TODO: this is a known omission that we'll want to address eventually. */
return error("loose source does not support transactions");
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 647f23c..0890704 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -3,11 +3,13 @@
#include "chdir-notify.h"
#include "dir.h"
#include "git-zlib.h"
+#include "list-objects-filter-options.h"
#include "mergesort.h"
#include "midx.h"
#include "odb/source-packed.h"
#include "odb/streaming.h"
#include "packfile.h"
+#include "pack-bitmap.h"
static int find_pack_entry(struct odb_source_packed *store,
const struct object_id *oid,
@@ -143,7 +145,7 @@ static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_f
}
static int for_each_prefixed_object_in_midx(
- struct odb_source_packed *store,
+ struct odb_source_packed *source,
struct multi_pack_index *m,
const struct odb_for_each_object_options *opts,
struct odb_source_packed_for_each_object_wrapper_data *data)
@@ -170,6 +172,7 @@ static int for_each_prefixed_object_in_midx(
*/
for (i = first; i < num; i++) {
const struct object_id *current = NULL;
+ struct packed_git *pack;
struct object_id oid;
current = nth_midxed_object_oid(&oid, m, i);
@@ -177,9 +180,8 @@ static int for_each_prefixed_object_in_midx(
if (!match_hash(len, opts->prefix->hash, current->hash))
break;
- if (opts->flags) {
+ if (opts->flags || data->request) {
uint32_t pack_id = nth_midxed_pack_int_id(m, i);
- struct packed_git *pack;
if (prepare_midx_pack(m, pack_id)) {
pack_errors = true;
@@ -193,9 +195,9 @@ static int for_each_prefixed_object_in_midx(
if (data->request) {
struct object_info oi = *data->request;
+ off_t offset = nth_midxed_offset(m, i);
- ret = odb_source_read_object_info(&store->base, current,
- &oi, 0);
+ ret = packed_object_info(source, pack, offset, &oi);
if (ret)
goto out;
@@ -219,7 +221,7 @@ static int for_each_prefixed_object_in_midx(
}
static int for_each_prefixed_object_in_pack(
- struct odb_source_packed *store,
+ struct odb_source_packed *source,
struct packed_git *p,
const struct odb_for_each_object_options *opts,
struct odb_source_packed_for_each_object_wrapper_data *data)
@@ -246,8 +248,9 @@ static int for_each_prefixed_object_in_pack(
if (data->request) {
struct object_info oi = *data->request;
+ off_t offset = nth_packed_object_offset(p, i);
- ret = odb_source_read_object_info(&store->base, &oid, &oi, 0);
+ ret = packed_object_info(source, p, offset, &oi);
if (ret)
goto out;
@@ -314,6 +317,37 @@ static int odb_source_packed_for_each_prefixed_object(
return ret;
}
+struct bitmapped_for_each_object_data {
+ struct odb_source_packed *packed;
+ const struct object_info *request;
+ const struct odb_for_each_object_options *opts;
+ odb_for_each_object_cb cb;
+ void *cb_data;
+};
+
+static int bitmapped_for_each_object(const struct object_id *oid,
+ enum object_type type UNUSED,
+ int flags UNUSED,
+ uint32_t hash UNUSED,
+ struct packed_git *pack,
+ off_t offset,
+ void *cb_data)
+{
+ struct bitmapped_for_each_object_data *data = cb_data;
+
+ if (should_exclude_pack(pack, data->opts->flags))
+ return 0;
+
+ if (data->request) {
+ struct object_info oi = *data->request;
+ if (packed_object_info(data->packed, pack, offset, &oi) < 0)
+ return -1;
+ return data->cb(oid, &oi, data->cb_data);
+ }
+
+ return data->cb(oid, NULL, data->cb_data);
+}
+
static int odb_source_packed_for_each_object(struct odb_source *source,
const struct object_info *request,
odb_for_each_object_cb cb,
@@ -327,12 +361,33 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
.cb = cb,
.cb_data = cb_data,
};
+ struct bitmap_index *bitmap = NULL;
struct packfile_list_entry *e;
int pack_errors = 0, ret;
if (opts->prefix)
return odb_source_packed_for_each_prefixed_object(packed, opts, &data);
+ if (opts->filter &&
+ opts->filter->choice != LOFC_DISABLED &&
+ can_filter_bitmap(opts->filter))
+ bitmap = prepare_bitmap_git_for_source(packed);
+ if (bitmap) {
+ struct bitmapped_for_each_object_data bitmap_data = {
+ .packed = packed,
+ .request = request,
+ .opts = opts,
+ .cb = cb,
+ .cb_data = cb_data,
+ };
+
+ ret = for_each_bitmapped_object(bitmap, opts->filter,
+ bitmapped_for_each_object,
+ &bitmap_data);
+ if (ret)
+ goto out;
+ }
+
packed->skip_mru_updates = true;
for (e = packfile_store_get_packs(packed); e; e = e->next) {
@@ -341,6 +396,13 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
if (should_exclude_pack(p, opts->flags))
continue;
+ /*
+ * Objects covered by the bitmap have already been yielded
+ * above; skip them here to avoid duplicates.
+ */
+ if (bitmap && bitmap_index_contains_pack(bitmap, p))
+ continue;
+
if (open_pack_index(p)) {
pack_errors = 1;
continue;
@@ -356,6 +418,7 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
out:
packed->skip_mru_updates = false;
+ free_bitmap_index(bitmap);
if (!ret && pack_errors)
ret = -1;
@@ -507,18 +570,26 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,
}
static int odb_source_packed_freshen_object(struct odb_source *source,
- const struct object_id *oid)
+ const struct object_id *oid,
+ const time_t *mtime)
{
struct odb_source_packed *packed = odb_source_packed_downcast(source);
+ struct utimbuf times, *timesp = NULL;
struct pack_entry e;
+ if (mtime) {
+ times.actime = *mtime;
+ times.modtime = *mtime;
+ timesp = ×
+ }
+
if (!find_pack_entry(packed, oid, &e))
return 0;
if (e.p->is_cruft)
return 0;
if (e.p->freshened)
return 1;
- if (utime(e.p->pack_name, NULL))
+ if (utime(e.p->pack_name, timesp))
return 0;
e.p->freshened = 1;
@@ -529,8 +600,9 @@ static int odb_source_packed_write_object(struct odb_source *source UNUSED,
const void *buf UNUSED,
size_t len UNUSED,
enum object_type type UNUSED,
- struct object_id *oid UNUSED,
- struct object_id *compat_oid UNUSED,
+ const struct object_id *oid UNUSED,
+ const struct object_id *compat_oid UNUSED,
+ const time_t *mtime UNUSED,
unsigned flags UNUSED)
{
return error("packed backend cannot write objects");
@@ -545,7 +617,8 @@ static int odb_source_packed_write_object_stream(struct odb_source *source UNUSE
}
static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED,
- struct odb_transaction **out UNUSED)
+ struct odb_transaction **out UNUSED,
+ enum odb_transaction_flags flags UNUSED)
{
return error("packed backend cannot begin transactions");
}
diff --git a/odb/source.h b/odb/source.h
index efeff7e..fc04dd5 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -3,6 +3,7 @@
#include "object.h"
#include "odb.h"
+#include "odb/transaction.h"
enum odb_source_type {
/*
@@ -189,7 +190,8 @@ struct odb_source {
* has been freshened.
*/
int (*freshen_object)(struct odb_source *source,
- const struct object_id *oid);
+ const struct object_id *oid,
+ const time_t *mtime);
/*
* This callback is expected to persist the given object into the
@@ -205,8 +207,9 @@ struct odb_source {
int (*write_object)(struct odb_source *source,
const void *buf, size_t len,
enum object_type type,
- struct object_id *oid,
- struct object_id *compat_oid,
+ const struct object_id *oid,
+ const struct object_id *compat_oid,
+ const time_t *mtime,
enum odb_write_object_flags flags);
/*
@@ -232,7 +235,8 @@ struct odb_source {
* negative error code otherwise.
*/
int (*begin_transaction)(struct odb_source *source,
- struct odb_transaction **out);
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags);
/*
* This callback is expected to read the list of alternate object
@@ -401,9 +405,10 @@ static inline int odb_source_find_abbrev_len(struct odb_source *source,
* not exist.
*/
static inline int odb_source_freshen_object(struct odb_source *source,
- const struct object_id *oid)
+ const struct object_id *oid,
+ const time_t *mtime)
{
- return source->freshen_object(source, oid);
+ return source->freshen_object(source, oid, mtime);
}
/*
@@ -414,12 +419,13 @@ static inline int odb_source_freshen_object(struct odb_source *source,
static inline int odb_source_write_object(struct odb_source *source,
const void *buf, unsigned long len,
enum object_type type,
- struct object_id *oid,
- struct object_id *compat_oid,
+ const struct object_id *oid,
+ const struct object_id *compat_oid,
+ const time_t *mtime,
enum odb_write_object_flags flags)
{
return source->write_object(source, buf, len, type, oid,
- compat_oid, flags);
+ compat_oid, mtime, flags);
}
/*
@@ -472,9 +478,10 @@ static inline int odb_source_write_alternate(struct odb_source *source,
* Returns 0 on success, a negative error code otherwise.
*/
static inline int odb_source_begin_transaction(struct odb_source *source,
- struct odb_transaction **out)
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags)
{
- return source->begin_transaction(source, out);
+ return source->begin_transaction(source, out, flags);
}
#endif
diff --git a/odb/transaction.c b/odb/transaction.c
index b16e07a..dab7da6 100644
--- a/odb/transaction.c
+++ b/odb/transaction.c
@@ -1,30 +1,41 @@
#include "git-compat-util.h"
+#include "gettext.h"
#include "odb/source.h"
#include "odb/transaction.h"
-struct odb_transaction *odb_transaction_begin(struct object_database *odb)
+int odb_transaction_begin(struct object_database *odb,
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags)
{
+ int ret;
+
if (odb->transaction)
- return NULL;
+ return error(_("object database transaction already pending"));
- odb_source_begin_transaction(odb->sources, &odb->transaction);
+ ret = odb_source_begin_transaction(odb->sources, out, flags);
+ if (!ret)
+ odb->transaction = *out;
- return odb->transaction;
+ return ret;
}
-void odb_transaction_commit(struct odb_transaction *transaction)
+int odb_transaction_commit(struct odb_transaction *transaction)
{
+ int ret;
+
if (!transaction)
- return;
+ return 0;
/*
* Ensure the transaction ending matches the pending transaction.
*/
ASSERT(transaction == transaction->source->odb->transaction);
- transaction->commit(transaction);
+ ret = transaction->commit(transaction);
transaction->source->odb->transaction = NULL;
free(transaction);
+
+ return ret;
}
int odb_transaction_write_object_stream(struct odb_transaction *transaction,
@@ -33,3 +44,11 @@ int odb_transaction_write_object_stream(struct odb_transaction *transaction,
{
return transaction->write_object_stream(transaction, stream, len, oid);
}
+
+int odb_transaction_env(struct odb_transaction *transaction, struct strvec *env)
+{
+ if (!transaction)
+ return 0;
+
+ return transaction->env(transaction, env);
+}
diff --git a/odb/transaction.h b/odb/transaction.h
index 854fda0..4cb2eaf 100644
--- a/odb/transaction.h
+++ b/odb/transaction.h
@@ -1,8 +1,8 @@
#ifndef ODB_TRANSACTION_H
#define ODB_TRANSACTION_H
+#include "gettext.h"
#include "odb.h"
-#include "odb/source.h"
/*
* A transaction may be started for an object database prior to writing new
@@ -16,8 +16,11 @@ struct odb_transaction {
/* The ODB source the transaction is opened against. */
struct odb_source *source;
- /* The ODB source specific callback invoked to commit a transaction. */
- void (*commit)(struct odb_transaction *transaction);
+ /*
+ * The ODB source specific callback invoked to commit a transaction.
+ * Returns 0 on success, a negative error code otherwise.
+ */
+ int (*commit)(struct odb_transaction *transaction);
/*
* This callback is expected to write the given object stream into
@@ -30,20 +33,47 @@ struct odb_transaction {
int (*write_object_stream)(struct odb_transaction *transaction,
struct odb_write_stream *stream, size_t len,
struct object_id *oid);
+
+ /*
+ * This callback is expected to populate the provided strvec with the
+ * environment variables that a child process should inherit so that its
+ * object writes participate in the transaction. Returns 0 on success, a
+ * negative error code otherwise.
+ */
+ int (*env)(struct odb_transaction *transaction, struct strvec *env);
+};
+
+/* Flags used to configure an ODB transaction. */
+enum odb_transaction_flags {
+ /* Configures the transaction for use with git-receive-pack(1). */
+ ODB_TRANSACTION_RECEIVE = (1 << 0),
};
/*
- * Starts an ODB transaction. Subsequent objects are written to the transaction
- * and not committed until odb_transaction_commit() is invoked on the
- * transaction. If the ODB already has a pending transaction, NULL is returned.
+ * Starts an ODB transaction and returns it via `out`. Subsequent objects are
+ * written to the transaction and not committed until odb_transaction_commit()
+ * is invoked on the transaction. Returns 0 on success and a negative value on
+ * error. Note that it is considered an error to start a new transaction if the
+ * ODB already has an inflight transaction pending.
*/
-struct odb_transaction *odb_transaction_begin(struct object_database *odb);
+int odb_transaction_begin(struct object_database *odb,
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags);
+
+static inline void odb_transaction_begin_or_die(struct object_database *odb,
+ struct odb_transaction **out,
+ enum odb_transaction_flags flags)
+{
+ if (odb_transaction_begin(odb, out, flags))
+ die(_("failed to start ODB transaction"));
+}
/*
- * Commits an ODB transaction making the written objects visible. If the
- * specified transaction is NULL, the function is a no-op.
+ * Commits an ODB transaction making the written objects visible. Returns 0 on
+ * success, a negative error code otherwise. Note that, if the specified
+ * transaction is NULL, the function is a no-op and no error is returned.
*/
-void odb_transaction_commit(struct odb_transaction *transaction);
+int odb_transaction_commit(struct odb_transaction *transaction);
/*
* Writes the object in the provided stream into the transaction. The resulting
@@ -54,4 +84,13 @@ int odb_transaction_write_object_stream(struct odb_transaction *transaction,
struct odb_write_stream *stream,
size_t len, struct object_id *oid);
+/*
+ * Populates the provided strvec with the environment variables that a child
+ * process should inherit so that its object writes participate in the
+ * transaction, suitable for using via child_process.env. Returns 0 on success,
+ * a negative error code otherwise. Note that, if the specified transaction is
+ * NULL, the function is a no-op and no error is returned.
+ */
+int odb_transaction_env(struct odb_transaction *transaction, struct strvec *env);
+
#endif
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 1fc3e62..d8dc4ae 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -460,8 +460,8 @@ char *pack_bitmap_filename(struct packed_git *p)
return xstrfmt("%.*s.bitmap", (int)len, p->pack_name);
}
-static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
- struct multi_pack_index *midx)
+static int open_midx_bitmap(struct bitmap_index *bitmap_git,
+ struct multi_pack_index *midx)
{
struct stat st;
char *bitmap_name = midx_bitmap_filename(midx);
@@ -543,7 +543,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
return -1;
}
-static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git *packfile)
+static int open_pack_bitmap(struct bitmap_index *bitmap_git, struct packed_git *packfile)
{
int fd;
struct stat st;
@@ -607,7 +607,7 @@ static int load_reverse_index(struct repository *r, struct bitmap_index *bitmap_
/*
* The multi-pack-index's .rev file is already loaded via
- * open_pack_bitmap_1().
+ * open_pack_bitmap().
*
* But we still need to open the individual pack .rev files,
* since we will need to make use of them in pack-objects.
@@ -684,60 +684,53 @@ static int load_bitmap(struct repository *r, struct bitmap_index *bitmap_git,
return 0;
}
-static int open_pack_bitmap(struct repository *r,
- struct bitmap_index *bitmap_git)
+static int open_bitmap_for_source(struct odb_source_packed *source,
+ struct bitmap_index *bitmap_git)
{
- struct packed_git *p;
- int ret = -1;
+ struct multi_pack_index *midx = get_multi_pack_index(source);
+ struct packfile_list_entry *e;
+ bool found = false;
- repo_for_each_pack(r, p) {
- if (open_pack_bitmap_1(bitmap_git, p) == 0) {
- ret = 0;
- /*
- * The only reason to keep looking is to report
- * duplicates.
- */
- if (!trace2_is_enabled())
- break;
- }
+ if (midx && !open_midx_bitmap(bitmap_git, midx))
+ found = true;
+
+ for (e = packfile_store_get_packs(source); e; e = e->next) {
+ /*
+ * When tracing is enabled we want to keep looking to report
+ * duplicates even if we have already found a bitmap.
+ */
+ if (found && !trace2_is_enabled())
+ break;
+
+ if (!open_pack_bitmap(bitmap_git, e->pack))
+ found = true;
}
- return ret;
+ return found ? 0 : -1;
}
-static int open_midx_bitmap(struct repository *r,
- struct bitmap_index *bitmap_git)
+static int open_bitmap(struct repository *r,
+ struct bitmap_index *bitmap_git)
{
struct odb_source *source;
- int ret = -1;
+ bool found = false;
assert(!bitmap_git->map);
odb_prepare_alternates(r->objects);
for (source = r->objects->sources; source; source = source->next) {
struct odb_source_files *files = odb_source_files_downcast(source);
- struct multi_pack_index *midx = get_multi_pack_index(files->packed);
- if (midx && !open_midx_bitmap_1(bitmap_git, midx))
- ret = 0;
+
+ if (!open_bitmap_for_source(files->packed, bitmap_git))
+ found = true;
+
+ /*
+ * The only reason to keep looking after having found a bitmap
+ * is to report duplicates.
+ */
+ if (found && !trace2_is_enabled())
+ break;
}
- return ret;
-}
-
-static int open_bitmap(struct repository *r,
- struct bitmap_index *bitmap_git)
-{
- int found;
-
- assert(!bitmap_git->map);
-
- found = !open_midx_bitmap(r, bitmap_git);
-
- /*
- * these will all be skipped if we opened a midx bitmap; but run it
- * anyway if tracing is enabled to report the duplicates
- */
- if (!found || trace2_is_enabled())
- found |= !open_pack_bitmap(r, bitmap_git);
return found ? 0 : -1;
}
@@ -757,7 +750,19 @@ struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx)
{
struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
- if (!open_midx_bitmap_1(bitmap_git, midx))
+ if (!open_midx_bitmap(bitmap_git, midx))
+ return bitmap_git;
+
+ free_bitmap_index(bitmap_git);
+ return NULL;
+}
+
+struct bitmap_index *prepare_bitmap_git_for_source(struct odb_source_packed *source)
+{
+ struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
+
+ if (!open_bitmap_for_source(source, bitmap_git) &&
+ !load_bitmap(source->base.odb->repo, bitmap_git, 0))
return bitmap_git;
free_bitmap_index(bitmap_git);
@@ -1699,7 +1704,7 @@ static void init_type_iterator(struct ewah_or_iterator *it,
}
}
-static void show_objects_for_type(
+static int show_objects_for_type(
struct bitmap_index *bitmap_git,
struct bitmap *objects,
enum object_type object_type,
@@ -1708,6 +1713,7 @@ static void show_objects_for_type(
{
size_t i = 0;
uint32_t offset;
+ int ret;
struct ewah_or_iterator it;
eword_t filter;
@@ -1753,11 +1759,17 @@ static void show_objects_for_type(
hash = bitmap_name_hash(bitmap_git, index_pos);
- show_reach(&oid, object_type, 0, hash, pack, ofs, payload);
+ ret = show_reach(&oid, object_type, 0, hash, pack, ofs, payload);
+ if (ret)
+ goto out;
}
}
+ ret = 0;
+
+out:
ewah_or_iterator_release(&it);
+ return ret;
}
static int in_bitmapped_pack(struct bitmap_index *bitmap_git,
@@ -1980,7 +1992,7 @@ static void filter_bitmap_object_type(struct bitmap_index *bitmap_git,
static int filter_bitmap(struct bitmap_index *bitmap_git,
struct object_list *tip_objects,
struct bitmap *to_filter,
- struct list_objects_filter_options *filter)
+ const struct list_objects_filter_options *filter)
{
if (!filter || filter->choice == LOFC_DISABLED)
return 0;
@@ -2031,12 +2043,11 @@ static int filter_bitmap(struct bitmap_index *bitmap_git,
return -1;
}
-static int can_filter_bitmap(struct list_objects_filter_options *filter)
+bool can_filter_bitmap(const struct list_objects_filter_options *filter)
{
return !filter_bitmap(NULL, NULL, NULL, filter);
}
-
static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git,
struct bitmap *result)
{
@@ -2062,10 +2073,16 @@ static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git,
}
int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
- struct list_objects_filter_options *filter,
+ const struct list_objects_filter_options *filter,
show_reachable_fn show_reach,
void *payload)
{
+ const enum object_type types[] = {
+ OBJ_COMMIT,
+ OBJ_TREE,
+ OBJ_BLOB,
+ OBJ_TAG,
+ };
struct bitmap *filtered_bitmap = NULL;
uint32_t objects_nr;
size_t full_word_count;
@@ -2090,14 +2107,12 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
goto out;
}
- show_objects_for_type(bitmap_git, filtered_bitmap,
- OBJ_COMMIT, show_reach, payload);
- show_objects_for_type(bitmap_git, filtered_bitmap,
- OBJ_TREE, show_reach, payload);
- show_objects_for_type(bitmap_git, filtered_bitmap,
- OBJ_BLOB, show_reach, payload);
- show_objects_for_type(bitmap_git, filtered_bitmap,
- OBJ_TAG, show_reach, payload);
+ for (size_t i = 0; i < ARRAY_SIZE(types); i++) {
+ ret = show_objects_for_type(bitmap_git, filtered_bitmap,
+ types[i], show_reach, payload);
+ if (ret)
+ goto out;
+ }
ret = 0;
out:
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 19a8655..1385027 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -9,6 +9,7 @@
#include "string-list.h"
struct commit;
+struct odb_source_packed;
struct repository;
struct rev_info;
@@ -68,6 +69,7 @@ struct bitmapped_pack {
struct bitmap_index *prepare_bitmap_git(struct repository *r);
struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx);
+struct bitmap_index *prepare_bitmap_git_for_source(struct odb_source_packed *source);
/*
* Given a bitmap index, determine whether it contains the pack either directly
@@ -90,13 +92,17 @@ int test_bitmap_pseudo_merge_objects(struct repository *r, uint32_t n);
struct list_objects_filter_options;
+/* Check whether the filter can be computed via the bitmap. */
+bool can_filter_bitmap(const struct list_objects_filter_options *filter);
+
/*
* Filter bitmapped objects and iterate through all resulting objects,
* executing `show_reach` for each of them. Returns `-1` in case the filter is
- * not supported, `0` otherwise.
+ * not supported, `0` otherwise. Aborts iteration and bubbles up the return
+ * value in case `show_reach()` returns non-zero.
*/
int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
- struct list_objects_filter_options *filter,
+ const struct list_objects_filter_options *filter,
show_reachable_fn show_reach,
void *payload);
diff --git a/pager.c b/pager.c
index 35b210e..543ef12 100644
--- a/pager.c
+++ b/pager.c
@@ -5,6 +5,8 @@
#include "run-command.h"
#include "sigchain.h"
#include "alias.h"
+#include "repository.h"
+#include "environment.h"
int pager_use_color = 1;
@@ -13,7 +15,6 @@ int pager_use_color = 1;
#endif
static struct child_process pager_process;
-static char *pager_program;
static int old_fd1 = -1, old_fd2 = -1;
/* Is the value coming back from term_columns() just a guess? */
@@ -75,10 +76,17 @@ static void wait_for_pager_signal(int signo)
static int core_pager_config(const char *var, const char *value,
const struct config_context *ctx UNUSED,
- void *data UNUSED)
+ void *data)
{
- if (!strcmp(var, "core.pager"))
- return git_config_string(&pager_program, var, value);
+ struct repository *r = data;
+
+ if (!strcmp(var, "core.pager")) {
+ struct repo_config_values *cfg = repo_config_values(r);
+
+ FREE_AND_NULL(cfg->pager_program);
+ return git_config_string(&cfg->pager_program, var, value);
+ }
+
return 0;
}
@@ -91,10 +99,12 @@ const char *git_pager(struct repository *r, int stdout_is_tty)
pager = getenv("GIT_PAGER");
if (!pager) {
- if (!pager_program)
+ struct repo_config_values *cfg = repo_config_values(r);
+
+ if (!cfg->pager_program)
read_early_config(r,
- core_pager_config, NULL);
- pager = pager_program;
+ core_pager_config, r);
+ pager = cfg->pager_program;
}
if (!pager)
pager = getenv("PAGER");
@@ -302,7 +312,11 @@ int check_pager_config(struct repository *r, const char *cmd)
read_early_config(r, pager_command_config, &data);
- if (data.value)
- pager_program = data.value;
+ if (data.value) {
+ struct repo_config_values *cfg = repo_config_values(r);
+
+ free(cfg->pager_program);
+ cfg->pager_program = data.value;
+ }
return data.want;
}
diff --git a/pathspec.c b/pathspec.c
index f78b227..281858f 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -847,9 +847,9 @@ int pathspec_needs_expanded_index(struct index_state *istate,
* - not-in-cone/bar*: may need expanded index
* - **.c: may need expanded index
*/
- if (strspn(item.original + item.nowildcard_len, "*") ==
+ if (strspn(item.match + item.nowildcard_len, "*") ==
(unsigned int)(item.len - item.nowildcard_len) &&
- path_in_cone_mode_sparse_checkout(item.original, istate))
+ path_in_cone_mode_sparse_checkout(item.match, istate))
continue;
for (pos = 0; pos < istate->cache_nr; pos++) {
@@ -865,7 +865,7 @@ int pathspec_needs_expanded_index(struct index_state *istate,
*/
if ((unsigned int)item.nowildcard_len >
ce_namelen(ce) &&
- !strncmp(item.original, ce->name,
+ !strncmp(item.match, ce->name,
ce_namelen(ce))) {
res = 1;
break;
@@ -876,13 +876,13 @@ int pathspec_needs_expanded_index(struct index_state *istate,
* directory and the pathspec does not match the whole
* directory, need to expand the index.
*/
- if (!strncmp(item.original, ce->name, item.nowildcard_len) &&
- wildmatch(item.original, ce->name, 0)) {
+ if (!strncmp(item.match, ce->name, item.nowildcard_len) &&
+ wildmatch(item.match, ce->name, 0)) {
res = 1;
break;
}
}
- } else if (!path_in_cone_mode_sparse_checkout(item.original, istate) &&
+ } else if (!path_in_cone_mode_sparse_checkout(item.match, istate) &&
!matches_skip_worktree(pathspec, i, &skip_worktree_seen))
res = 1;
diff --git a/prompt.c b/prompt.c
index 706fba2..d8d74c7 100644
--- a/prompt.c
+++ b/prompt.c
@@ -3,6 +3,7 @@
#include "git-compat-util.h"
#include "parse.h"
#include "environment.h"
+#include "repository.h"
#include "run-command.h"
#include "strbuf.h"
#include "prompt.h"
@@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)
askpass = getenv("GIT_ASKPASS");
if (!askpass)
- askpass = askpass_program;
+ askpass = repo_config_values(the_repository)->askpass_program;
if (!askpass)
askpass = getenv("SSH_ASKPASS");
if (askpass && *askpass)
diff --git a/protocol-caps.c b/protocol-caps.c
index 8858ea4..02261be 100644
--- a/protocol-caps.c
+++ b/protocol-caps.c
@@ -31,6 +31,32 @@ static int parse_oid(const char *line, struct string_list *oid_str_list)
}
/*
+ * odb_read_object_info_extended() wrapper. Similar to odb_read_object_info()
+ * but uses the flags:
+ *
+ * - OBJECT_INFO_SKIP_FETCH_OBJECT so a server won't fetch an object when a
+ * object-info request asks for an OID that it doesn't have.
+ *
+ * - OBJECT_INFO_QUICK to avoid re-scanning packs when the object is not found.
+ */
+static enum object_type get_object_info(struct object_database *odb,
+ const struct object_id *oid,
+ size_t *sizep)
+{
+ enum object_type type;
+ struct object_info oi = OBJECT_INFO_INIT;
+
+ oi.typep = &type;
+ oi.sizep = sizep;
+ if (odb_read_object_info_extended(odb, oid, &oi,
+ OBJECT_INFO_LOOKUP_REPLACE |
+ OBJECT_INFO_SKIP_FETCH_OBJECT |
+ OBJECT_INFO_QUICK) < 0)
+ return OBJ_BAD;
+ return type;
+}
+
+/*
* Validates and send requested info back to the client. Any errors detected
* are returned as they are detected.
*/
@@ -62,15 +88,22 @@ static void send_info(struct repository *r, struct packet_writer *writer,
strbuf_addstr(&send_buffer, oid_str);
- if (info->size) {
- if (odb_read_object_info(r->objects, &oid, &object_size) < 0) {
- strbuf_addstr(&send_buffer, " ");
- } else {
- strbuf_addf(&send_buffer, " %"PRIuMAX,
- (uintmax_t)object_size);
- }
+ /*
+ * Check the existence of the object first.
+ * If an object is not recognized by the server append SP to
+ * the response.
+ */
+ if (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {
+ strbuf_addstr(&send_buffer, " ");
+ goto write;
}
+ if (info->size) {
+ strbuf_addf(&send_buffer, " %"PRIuMAX,
+ (uintmax_t)object_size);
+ }
+
+write:
packet_writer_write(writer, "%s", send_buffer.buf);
strbuf_reset(&send_buffer);
}
diff --git a/reachable.c b/reachable.c
index caadacc..3079d2c 100644
--- a/reachable.c
+++ b/reachable.c
@@ -62,7 +62,7 @@ static void add_rebase_files(struct rev_info *revs)
"rebase-merge/autostash",
"rebase-merge/orig-head",
};
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees(the_repository);
for (struct worktree **wt = worktrees; *wt; wt++) {
char *wt_gitdir = get_worktree_git_dir(*wt);
@@ -322,7 +322,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
/* detached HEAD is not included in the list above */
refs_head_ref(get_main_ref_store(the_repository), add_one_ref, revs);
- other_head_refs(add_one_ref, revs);
+ other_head_refs(the_repository, add_one_ref, revs);
/* rebase autostash and orig-head */
add_rebase_files(revs);
diff --git a/read-cache.c b/read-cache.c
index 797fdfc..6c449f3 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -205,13 +205,11 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st
static unsigned int st_mode_from_ce(const struct cache_entry *ce)
{
- extern int trust_executable_bit, has_symlinks;
-
switch (ce->ce_mode & S_IFMT) {
case S_IFLNK:
- return has_symlinks ? S_IFLNK : (S_IFREG | 0644);
+ return repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644);
case S_IFREG:
- return (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;
+ return (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;
case S_IFGITLINK:
return S_IFDIR | 0755;
case S_IFDIR:
@@ -321,13 +319,13 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
/* We consider only the owner x bit to be relevant for
* "mode changes"
*/
- if (trust_executable_bit &&
+ if (repo_trust_executable_bit(the_repository) &&
(0100 & (ce->ce_mode ^ st->st_mode)))
changed |= MODE_CHANGED;
break;
case S_IFLNK:
if (!S_ISLNK(st->st_mode) &&
- (has_symlinks || !S_ISREG(st->st_mode)))
+ (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode)))
changed |= TYPE_CHANGED;
break;
case S_IFGITLINK:
@@ -742,7 +740,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
ce->ce_flags |= CE_INTENT_TO_ADD;
- if (trust_executable_bit && has_symlinks) {
+ if (repo_trust_executable_bit(istate->repo) &&
+ repo_has_symlinks(istate->repo)) {
ce->ce_mode = create_ce_mode(st_mode);
} else {
/* If there is an existing entry, pick the mode bits and type
@@ -752,7 +751,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
int pos = index_name_pos_also_unmerged(istate, path, namelen);
ent = (0 <= pos) ? istate->cache[pos] : NULL;
- ce->ce_mode = ce_mode_from_stat(ent, st_mode);
+ ce->ce_mode = ce_mode_from_stat(istate->repo, ent, st_mode);
}
/* When core.ignorecase=true, determine if a directory of the same name but differing
@@ -2342,7 +2341,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
*/
static void freshen_shared_index(const char *shared_index, int warn)
{
- if (!check_and_freshen_file(shared_index, 1) && warn)
+ if (!check_and_freshen_file(shared_index, 1, NULL) && warn)
warning(_("could not freshen shared index '%s'"), shared_index);
}
@@ -4015,6 +4014,7 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
const struct pathspec *pathspec, char *ps_matched,
int include_sparse, int flags, int ignored_too )
{
+ int inflight = !!repo->objects->transaction;
struct odb_transaction *transaction;
struct update_callback_data data;
struct rev_info rev;
@@ -4045,9 +4045,11 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
* This function is invoked from commands other than 'add', which
* may not have their own transaction active.
*/
- transaction = odb_transaction_begin(repo->objects);
+ if (!inflight)
+ odb_transaction_begin_or_die(repo->objects, &transaction, 0);
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
- odb_transaction_commit(transaction);
+ if (!inflight)
+ odb_transaction_commit(transaction);
release_revisions(&rev);
return !!data.add_errors;
diff --git a/read-cache.h b/read-cache.h
index 043da1f..ab9d40a 100644
--- a/read-cache.h
+++ b/read-cache.h
@@ -4,15 +4,23 @@
#include "read-cache-ll.h"
#include "object.h"
#include "pathspec.h"
+#include "environment.h"
-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
+/*
+ * Determine the appropriate index mode for a file based on its stat()
+ * information and the existing cache entry (if any).
+ *
+ * This function handles degradation for filesystems that lack
+ * symlink support or reliable executable bits.
+ */
+static inline unsigned int ce_mode_from_stat(struct repository *repo,
+ const struct cache_entry *ce,
unsigned int mode)
{
- extern int trust_executable_bit, has_symlinks;
- if (!has_symlinks && S_ISREG(mode) &&
+ if (S_ISREG(mode) && !repo_has_symlinks(repo) &&
ce && S_ISLNK(ce->ce_mode))
return ce->ce_mode;
- if (!trust_executable_bit && S_ISREG(mode)) {
+ if (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {
if (ce && S_ISREG(ce->ce_mode))
return ce->ce_mode;
return create_ce_mode(0666);
diff --git a/ref-filter.c b/ref-filter.c
index 284796c..29aca08 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2402,7 +2402,7 @@ static void lazy_init_worktree_map(void)
if (ref_to_worktree_map.worktrees)
return;
- ref_to_worktree_map.worktrees = get_worktrees();
+ ref_to_worktree_map.worktrees = get_worktrees(the_repository);
hashmap_init(&(ref_to_worktree_map.map), ref_to_worktree_map_cmpfnc, NULL, 0);
populate_worktree_map(&(ref_to_worktree_map.map), ref_to_worktree_map.worktrees);
}
diff --git a/refs.c b/refs.c
index 1d24637..92d5df5 100644
--- a/refs.c
+++ b/refs.c
@@ -2,8 +2,6 @@
* The backend-independent part of the reference module.
*/
-#define USE_THE_REPOSITORY_VARIABLE
-
#include "git-compat-util.h"
#include "abspath.h"
#include "advice.h"
@@ -744,14 +742,15 @@ static char *substitute_branch_name(struct repository *r,
return NULL;
}
-void copy_branchname(struct strbuf *sb, const char *name,
+void copy_branchname(struct repository *repo,
+ struct strbuf *sb, const char *name,
enum interpret_branch_kind allowed)
{
int len = strlen(name);
struct interpret_branch_name_options options = {
.allowed = allowed
};
- int used = repo_interpret_branch_name(the_repository, name, len, sb,
+ int used = repo_interpret_branch_name(repo, name, len, sb,
&options);
if (used < 0)
@@ -759,10 +758,10 @@ void copy_branchname(struct strbuf *sb, const char *name,
strbuf_add(sb, name + used, len - used);
}
-int check_branch_ref(struct strbuf *sb, const char *name)
+int check_branch_ref(struct repository *repo, struct strbuf *sb, const char *name)
{
if (startup_info->have_repository)
- copy_branchname(sb, name, INTERPRET_BRANCH_LOCAL);
+ copy_branchname(repo, sb, name, INTERPRET_BRANCH_LOCAL);
else
strbuf_addstr(sb, name);
@@ -3326,9 +3325,9 @@ static int move_files(const char *from_path, const char *to_path, struct strbuf
return ret;
}
-static int has_worktrees(void)
+static int has_worktrees(struct repository *repo)
{
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees(repo);
int ret = 0;
size_t i;
@@ -3373,12 +3372,8 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* Worktrees complicate the migration because every worktree has a
* separate ref storage. While it should be feasible to implement, this
* is pushed out to a future iteration.
- *
- * TODO: we should really be passing the caller-provided repository to
- * `has_worktrees()`, but our worktree subsystem doesn't yet support
- * that.
*/
- if (has_worktrees()) {
+ if (has_worktrees(repo)) {
strbuf_addstr(errbuf, "migrating repositories with worktrees is not supported yet");
ret = -1;
goto done;
@@ -3503,7 +3498,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* repository format so that clients will use the new ref store.
* We also need to swap out the repository's main ref store.
*/
- initialize_repository_version(the_repository, hash_algo_by_ptr(repo->hash_algo), format, 1);
+ initialize_repository_version(repo, hash_algo_by_ptr(repo->hash_algo), format, 1);
/*
* Unset the old ref store and release it. `get_main_ref_store()` will
diff --git a/refs.h b/refs.h
index a381022..9979446 100644
--- a/refs.h
+++ b/refs.h
@@ -234,7 +234,8 @@ char *repo_default_branch_name(struct repository *r, int quiet);
* If "allowed" is non-zero, restrict the set of allowed expansions. See
* repo_interpret_branch_name() for details.
*/
-void copy_branchname(struct strbuf *sb, const char *name,
+void copy_branchname(struct repository *repo,
+ struct strbuf *sb, const char *name,
enum interpret_branch_kind allowed);
/*
@@ -243,7 +244,7 @@ void copy_branchname(struct strbuf *sb, const char *name,
*
* The return value is "0" if the result is valid, and "-1" otherwise.
*/
-int check_branch_ref(struct strbuf *sb, const char *name);
+int check_branch_ref(struct repository *repo, struct strbuf *sb, const char *name);
/*
* Similar for a tag name in refs/tags/.
diff --git a/refs/files-backend.c b/refs/files-backend.c
index e1d963a..1cc20aa 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "../git-compat-util.h"
@@ -29,6 +28,9 @@
#include "../revision.h"
#include <wildmatch.h>
+/* So that we can drop `USE_THE_REPOSITORY_VARIABLE`. */
+extern int ignore_case;
+
/*
* This backend uses the following flags in `ref_update::flags` for
* internal bookkeeping purposes. Their numerical values must not
@@ -788,7 +790,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
files_ref_path(refs, &ref_file, refname);
retry:
- switch (safe_create_leading_directories(the_repository, ref_file.buf)) {
+ switch (safe_create_leading_directories(refs->base.repo, ref_file.buf)) {
case SCLD_OK:
break; /* success */
case SCLD_EXISTS:
@@ -842,7 +844,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
goto error_return;
}
- if (hold_lock_file_for_update_timeout(
+ if (repo_hold_lock_file_for_update_timeout(refs->base.repo,
&lock->lk, ref_file.buf, LOCK_NO_DEREF,
get_files_ref_lock_timeout_ms(transaction->ref_store->repo)) < 0) {
int myerr = errno;
@@ -857,7 +859,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
} else {
unable_to_lock_message(ref_file.buf, myerr, err);
if (myerr == EEXIST) {
- if (repo_ignore_case(the_repository) &&
+ if (repo_ignore_case(refs->base.repo) &&
transaction_has_case_conflicting_update(transaction, update)) {
/*
* In case-insensitive filesystems, ensure that conflicts within a
@@ -971,7 +973,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
* conflicts between 'foo' and 'Foo/bar'. So let's lowercase
* the refname.
*/
- if (repo_ignore_case(the_repository)) {
+ if (repo_ignore_case(refs->base.repo)) {
struct strbuf lower = STRBUF_INIT;
strbuf_addstr(&lower, refname);
@@ -1164,7 +1166,8 @@ typedef int create_file_fn(const char *path, void *cb);
* recent call of fn. fn is always called at least once, and will be
* called more than once if it returns ENOENT or EISDIR.
*/
-static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
+static int raceproof_create_file(struct files_ref_store *refs,
+ const char *path, create_file_fn fn, void *cb)
{
/*
* The number of times we will try to remove empty directories
@@ -1220,7 +1223,7 @@ static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
strbuf_addstr(&path_copy, path);
do {
- scld_result = safe_create_leading_directories(the_repository, path_copy.buf);
+ scld_result = safe_create_leading_directories(refs->base.repo, path_copy.buf);
if (scld_result == SCLD_OK)
goto retry_fn;
} while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
@@ -1250,8 +1253,8 @@ struct create_reflock_cb {
static int create_reflock(const char *path, void *cb)
{
struct create_reflock_cb *data = cb;
- return hold_lock_file_for_update_timeout(
- data->lk, path, LOCK_NO_DEREF,
+ return repo_hold_lock_file_for_update_timeout(
+ data->repo, data->lk, path, LOCK_NO_DEREF,
get_files_ref_lock_timeout_ms(data->repo)) < 0 ? -1 : 0;
}
@@ -1289,7 +1292,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
cb_data.lk = &lock->lk;
cb_data.repo = refs->base.repo;
- if (raceproof_create_file(ref_file.buf, create_reflock, &cb_data)) {
+ if (raceproof_create_file(refs, ref_file.buf, create_reflock, &cb_data)) {
unable_to_lock_message(ref_file.buf, errno, err);
goto error_return;
}
@@ -1383,7 +1386,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,
+ null_oid(refs->base.repo->hash_algo), &r->oid, NULL, NULL, NULL,
NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1629,7 +1632,7 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
files_reflog_path(refs, &path, newrefname);
files_reflog_path(refs, &tmp, TMP_RENAMED_LOG);
cb.tmp_renamed_log = tmp.buf;
- ret = raceproof_create_file(path.buf, rename_tmp_log_callback, &cb);
+ ret = raceproof_create_file(refs, path.buf, rename_tmp_log_callback, &cb);
if (ret) {
if (errno == EISDIR)
error("directory not empty: %s", path.buf);
@@ -1736,7 +1739,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
goto out;
}
- if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) {
+ if (copy && log && copy_file(refs->base.repo, tmp_renamed_log.buf, sb_oldref.buf, 0644)) {
ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
oldrefname, strerror(errno));
goto out;
@@ -1916,13 +1919,13 @@ static int log_ref_setup(struct files_ref_store *refs,
char *logfile;
if (log_refs_cfg == LOG_REFS_UNSET)
- log_refs_cfg = is_bare_repository(the_repository) ? LOG_REFS_NONE : LOG_REFS_NORMAL;
+ log_refs_cfg = is_bare_repository(refs->base.repo) ? LOG_REFS_NONE : LOG_REFS_NORMAL;
files_reflog_path(refs, &logfile_sb, refname);
logfile = strbuf_detach(&logfile_sb, NULL);
if (force_create || should_autocreate_reflog(log_refs_cfg, refname)) {
- if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) {
+ if (raceproof_create_file(refs, logfile, open_or_create_logfile, logfd)) {
if (errno == ENOENT)
strbuf_addf(err, "unable to create directory for '%s': "
"%s", logfile, strerror(errno));
@@ -1955,7 +1958,7 @@ static int log_ref_setup(struct files_ref_store *refs,
}
if (*logfd >= 0)
- adjust_shared_perm(the_repository, logfile);
+ adjust_shared_perm(refs->base.repo, logfile);
free(logfile);
return 0;
@@ -3581,7 +3584,9 @@ static int files_reflog_expire(struct ref_store *ref_store,
* work we need, including cleaning up if the program
* exits unexpectedly.
*/
- if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) {
+ if (repo_hold_lock_file_for_update(ref_store->repo,
+ &reflog_lock, log_file,
+ 0) < 0) {
struct strbuf err = STRBUF_INIT;
unable_to_lock_message(log_file, errno, &err);
error("%s", err.buf);
@@ -3672,8 +3677,8 @@ static int files_ref_store_create_on_disk(struct ref_store *ref_store,
* they do not understand the reference format extension.
*/
strbuf_addf(&sb, "%s/refs", ref_store->gitdir);
- safe_create_dir(the_repository, sb.buf, 1);
- adjust_shared_perm(the_repository, sb.buf);
+ safe_create_dir(refs->base.repo, sb.buf, 1);
+ adjust_shared_perm(refs->base.repo, sb.buf);
/*
* There is no need to create directories for common refs when creating
@@ -3685,11 +3690,11 @@ static int files_ref_store_create_on_disk(struct ref_store *ref_store,
*/
strbuf_reset(&sb);
files_ref_path(refs, &sb, "refs/heads");
- safe_create_dir(the_repository, sb.buf, 1);
+ safe_create_dir(refs->base.repo, sb.buf, 1);
strbuf_reset(&sb);
files_ref_path(refs, &sb, "refs/tags");
- safe_create_dir(the_repository, sb.buf, 1);
+ safe_create_dir(refs->base.repo, sb.buf, 1);
}
strbuf_release(&sb);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 499cb55..b9b04b7 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "../git-compat-util.h"
@@ -162,6 +161,13 @@ struct packed_ref_store {
* `packed_ref_store`) must not be freed.
*/
struct tempfile *tempfile;
+
+ /*
+ * Timeout when taking the "packed-refs.lock" file. configurable via
+ * "core.packedRefsTimeout".
+ */
+ bool timeout_configured;
+ int timeout_value;
};
/*
@@ -1233,12 +1239,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
struct packed_ref_store *refs =
packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN,
"packed_refs_lock");
- static int timeout_configured = 0;
- static int timeout_value = 1000;
- if (!timeout_configured) {
- repo_config_get_int(the_repository, "core.packedrefstimeout", &timeout_value);
- timeout_configured = 1;
+ if (!refs->timeout_configured) {
+ if (repo_config_get_int(ref_store->repo, "core.packedrefstimeout",
+ &refs->timeout_value))
+ refs->timeout_value = 1000;
+ refs->timeout_configured = true;
}
/*
@@ -1246,10 +1252,9 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
* don't write new content to it, but rather to a separate
* tempfile.
*/
- if (hold_lock_file_for_update_timeout(
- &refs->lock,
- refs->path,
- flags, timeout_value) < 0) {
+ if (repo_hold_lock_file_for_update_timeout(ref_store->repo, &refs->lock,
+ refs->path, flags,
+ refs->timeout_value) < 0) {
unable_to_lock_message(refs->path, errno, err);
return -1;
}
@@ -1393,7 +1398,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
packed_refs_path = get_locked_file_path(&refs->lock);
strbuf_addf(&sb, "%s.new", packed_refs_path);
free(packed_refs_path);
- refs->tempfile = create_tempfile(sb.buf);
+ refs->tempfile = repo_create_tempfile(refs->base.repo, sb.buf);
if (!refs->tempfile) {
strbuf_addf(err, "unable to create file %s: %s",
sb.buf, strerror(errno));
diff --git a/refs/packed-backend.h b/refs/packed-backend.h
index 1db48e8..8a7b323 100644
--- a/refs/packed-backend.h
+++ b/refs/packed-backend.h
@@ -21,7 +21,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo,
/*
* Lock the packed-refs file for writing. Flags is passed to
- * hold_lock_file_for_update(). Return 0 on success. On errors, write
+ * repo_hold_lock_file_for_update(). Return 0 on success. On errors, write
* an error message to `err` and return a nonzero value.
*/
int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err);
diff --git a/refspec.c b/refspec.c
index fb89bce..7cb4799 100644
--- a/refspec.c
+++ b/refspec.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
@@ -16,7 +15,8 @@
* Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
* Returns 1 if successful and 0 if the refspec is invalid.
*/
-static int parse_refspec(struct refspec_item *item, const char *refspec, int fetch)
+static int parse_refspec(struct refspec_item *item, const char *refspec,
+ const struct git_hash_algo *algo, int fetch)
{
size_t llen;
int is_glob;
@@ -84,7 +84,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
*/
if (!*item->src)
return 0; /* negative refspecs must not be empty */
- else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
+ else if (llen == algo->hexsz && !get_oid_hex_algop(item->src, &unused, algo))
return 0; /* negative refspecs cannot be exact sha1 */
else if (!check_refname_format(item->src, flags))
; /* valid looking ref is ok */
@@ -101,7 +101,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
/* LHS */
if (!*item->src)
; /* empty is ok; it means "HEAD" */
- else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
+ else if (llen == algo->hexsz && !get_oid_hex_algop(item->src, &unused, algo))
item->exact_sha1 = 1; /* ok */
else if (!check_refname_format(item->src, flags))
; /* valid looking ref is ok */
@@ -154,21 +154,23 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
}
static int refspec_item_init(struct refspec_item *item, const char *refspec,
- int fetch)
+ const struct git_hash_algo *algo, int fetch)
{
memset(item, 0, sizeof(*item));
item->raw = xstrdup(refspec);
- return parse_refspec(item, refspec, fetch);
+ return parse_refspec(item, refspec, algo, fetch);
}
-int refspec_item_init_fetch(struct refspec_item *item, const char *refspec)
+int refspec_item_init_fetch(struct refspec_item *item, const char *refspec,
+ const struct git_hash_algo *algo)
{
- return refspec_item_init(item, refspec, 1);
+ return refspec_item_init(item, refspec, algo, 1);
}
-int refspec_item_init_push(struct refspec_item *item, const char *refspec)
+int refspec_item_init_push(struct refspec_item *item, const char *refspec,
+ const struct git_hash_algo *algo)
{
- return refspec_item_init(item, refspec, 0);
+ return refspec_item_init(item, refspec, algo, 0);
}
void refspec_item_clear(struct refspec_item *item)
@@ -182,15 +184,15 @@ void refspec_item_clear(struct refspec_item *item)
item->exact_sha1 = 0;
}
-void refspec_init_fetch(struct refspec *rs)
+void refspec_init_fetch(struct refspec *rs, const struct git_hash_algo *algo)
{
- struct refspec blank = REFSPEC_INIT_FETCH;
+ struct refspec blank = REFSPEC_INIT_FETCH(algo);
memcpy(rs, &blank, sizeof(*rs));
}
-void refspec_init_push(struct refspec *rs)
+void refspec_init_push(struct refspec *rs, const struct git_hash_algo *algo)
{
- struct refspec blank = REFSPEC_INIT_PUSH;
+ struct refspec blank = REFSPEC_INIT_PUSH(algo);
memcpy(rs, &blank, sizeof(*rs));
}
@@ -200,9 +202,9 @@ void refspec_append(struct refspec *rs, const char *refspec)
int ret;
if (rs->fetch)
- ret = refspec_item_init_fetch(&item, refspec);
+ ret = refspec_item_init_fetch(&item, refspec, rs->hash_algo);
else
- ret = refspec_item_init_push(&item, refspec);
+ ret = refspec_item_init_push(&item, refspec, rs->hash_algo);
if (!ret)
die(_("invalid refspec '%s'"), refspec);
@@ -246,10 +248,11 @@ void refspec_clear(struct refspec *rs)
rs->fetch = 0;
}
-int valid_fetch_refspec(const char *fetch_refspec_str)
+int valid_fetch_refspec(const char *fetch_refspec_str,
+ const struct git_hash_algo *algo)
{
struct refspec_item refspec;
- int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str);
+ int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str, algo);
refspec_item_clear(&refspec);
return ret;
}
diff --git a/refspec.h b/refspec.h
index 8b04f99..fadef67 100644
--- a/refspec.h
+++ b/refspec.h
@@ -1,6 +1,10 @@
#ifndef REFSPEC_H
#define REFSPEC_H
+struct git_hash_algo;
+struct string_list;
+struct strvec;
+
#define TAG_REFSPEC "refs/tags/*:refs/tags/*"
/**
@@ -30,10 +34,11 @@ struct refspec_item {
char *raw;
};
-struct string_list;
-
-#define REFSPEC_INIT_FETCH { .fetch = 1 }
-#define REFSPEC_INIT_PUSH { .fetch = 0 }
+int refspec_item_init_fetch(struct refspec_item *item, const char *refspec,
+ const struct git_hash_algo *algo);
+int refspec_item_init_push(struct refspec_item *item, const char *refspec,
+ const struct git_hash_algo *algo);
+void refspec_item_clear(struct refspec_item *item);
/**
* An array of strings can be parsed into a struct refspec using
@@ -44,23 +49,30 @@ struct refspec {
int alloc;
int nr;
+ const struct git_hash_algo *hash_algo;
unsigned fetch : 1;
};
-int refspec_item_init_fetch(struct refspec_item *item, const char *refspec);
-int refspec_item_init_push(struct refspec_item *item, const char *refspec);
-void refspec_item_clear(struct refspec_item *item);
-void refspec_init_fetch(struct refspec *rs);
-void refspec_init_push(struct refspec *rs);
+#define REFSPEC_INIT_FETCH(algo) { \
+ .fetch = 1, \
+ .hash_algo = (algo), \
+}
+#define REFSPEC_INIT_PUSH(algo) { \
+ .fetch = 0, \
+ .hash_algo = (algo), \
+}
+
+void refspec_init_fetch(struct refspec *rs, const struct git_hash_algo *hash_algo);
+void refspec_init_push(struct refspec *rs, const struct git_hash_algo *hash_algo);
+void refspec_clear(struct refspec *rs);
+
void refspec_append(struct refspec *rs, const char *refspec);
__attribute__((format (printf,2,3)))
void refspec_appendf(struct refspec *rs, const char *fmt, ...);
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr);
-void refspec_clear(struct refspec *rs);
-int valid_fetch_refspec(const char *refspec);
+int valid_fetch_refspec(const char *refspec, const struct git_hash_algo *algo);
-struct strvec;
/*
* Determine what <prefix> values to pass to the peer in ref-prefix lines
* (see linkgit:gitprotocol-v2[5]).
@@ -76,7 +88,7 @@ int refname_matches_negative_refspec_item(const char *refname, struct refspec *r
* Returns 1 if refname matches pattern, 0 otherwise.
*/
int match_refname_with_pattern(const char *pattern, const char *refname,
- const char *replacement, char **result);
+ const char *replacement, char **result);
/*
* Queries a refspec for a match and updates the query item.
@@ -89,8 +101,8 @@ int refspec_find_match(struct refspec *rs, struct refspec_item *query);
* list.
*/
void refspec_find_all_matches(struct refspec *rs,
- struct refspec_item *query,
- struct string_list *results);
+ struct refspec_item *query,
+ struct string_list *results);
/*
* Remove all entries in the input list which match any negative refspec in
diff --git a/remote-curl.c b/remote-curl.c
index 9e614c5..2c35dd5 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -1340,10 +1340,9 @@ static void parse_get(const char *arg)
fflush(stdout);
}
-static int push_dav(int nr_spec, const char **specs)
+static int push_dav(const char **specs)
{
struct child_process child = CHILD_PROCESS_INIT;
- size_t i;
child.git_cmd = 1;
strvec_push(&child.args, "http-push");
@@ -1353,15 +1352,14 @@ static int push_dav(int nr_spec, const char **specs)
if (options.verbosity > 1)
strvec_push(&child.args, "--verbose");
strvec_push(&child.args, url.buf);
- for (i = 0; i < nr_spec; i++)
- strvec_push(&child.args, specs[i]);
+ strvec_pushv(&child.args, specs);
if (run_command(&child))
die(_("git-http-push failed"));
return 0;
}
-static int push_git(struct discovery *heads, int nr_spec, const char **specs)
+static int push_git(struct discovery *heads, const char **specs)
{
struct rpc_state rpc = RPC_STATE_INIT;
int i, err;
@@ -1400,8 +1398,8 @@ static int push_git(struct discovery *heads, int nr_spec, const char **specs)
strvec_push(&args, "--force-if-includes");
strvec_push(&args, "--stdin");
- for (i = 0; i < nr_spec; i++)
- packet_buf_write(&preamble, "%s\n", specs[i]);
+ for (; *specs; specs++)
+ packet_buf_write(&preamble, "%s\n", *specs);
packet_buf_flush(&preamble);
memset(&rpc, 0, sizeof(rpc));
@@ -1416,15 +1414,15 @@ static int push_git(struct discovery *heads, int nr_spec, const char **specs)
return err;
}
-static int push(int nr_spec, const char **specs)
+static int push(const char **specs)
{
struct discovery *heads = discover_refs("git-receive-pack", 1);
int ret;
if (heads->proto_git)
- ret = push_git(heads, nr_spec, specs);
+ ret = push_git(heads, specs);
else
- ret = push_dav(nr_spec, specs);
+ ret = push_dav(specs);
free_discovery(heads);
return ret;
}
@@ -1448,7 +1446,7 @@ static void parse_push(struct strbuf *buf)
break;
} while (1);
- ret = push(specs.nr, specs.v);
+ ret = push(specs.v);
printf("\n");
fflush(stdout);
diff --git a/remote.c b/remote.c
index 3a6abf1..a13d9a0 100644
--- a/remote.c
+++ b/remote.c
@@ -150,8 +150,8 @@ static struct remote *make_remote(struct remote_state *remote_state,
ret->prune = -1; /* unspecified */
ret->prune_tags = -1; /* unspecified */
ret->name = xstrndup(name, len);
- refspec_init_push(&ret->push);
- refspec_init_fetch(&ret->fetch);
+ refspec_init_push(&ret->push, the_hash_algo);
+ refspec_init_fetch(&ret->fetch, the_hash_algo);
string_list_init_dup(&ret->server_options);
string_list_init_dup(&ret->negotiation_restrict);
string_list_init_dup(&ret->negotiation_include);
@@ -1977,7 +1977,7 @@ static char *branch_get_push_1(struct repository *repo,
if (remote->mirror)
return tracking_for_push_dest(repo, remote, branch->refname, err);
- switch (push_default) {
+ switch (repo_config_values(repo)->push_default) {
case PUSH_DEFAULT_NOTHING:
return error_buf(err, _("push has no destination (push.default is 'nothing')"));
@@ -3085,7 +3085,7 @@ int valid_remote_name(const char *name)
int result;
struct strbuf refspec = STRBUF_INIT;
strbuf_addf(&refspec, "refs/heads/test:refs/remotes/%s/test", name);
- result = valid_fetch_refspec(refspec.buf);
+ result = valid_fetch_refspec(refspec.buf, the_hash_algo);
strbuf_release(&refspec);
return result;
}
diff --git a/repack-midx.c b/repack-midx.c
index 7c7c362..64c7f8d 100644
--- a/repack-midx.c
+++ b/repack-midx.c
@@ -954,7 +954,8 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts)
lock_name.buf))
die_errno(_("unable to create leading directories of %s"),
lock_name.buf);
- hold_lock_file_for_update(&lf, lock_name.buf, LOCK_DIE_ON_ERROR);
+ repo_hold_lock_file_for_update(opts->existing->repo, &lf, lock_name.buf,
+ LOCK_DIE_ON_ERROR);
if (!fdopen_lock_file(&lf, "w")) {
ret = error_errno(_("unable to open multi-pack-index chain file"));
diff --git a/repository.c b/repository.c
index 2ef0778..651b0f6 100644
--- a/repository.c
+++ b/repository.c
@@ -390,6 +390,7 @@ void repo_clear(struct repository *repo)
FREE_AND_NULL(repo->parsed_objects);
repo_settings_clear(repo);
+ repo_config_values_clear(&repo->config_values_private_);
if (repo->config) {
git_configset_clear(repo->config);
@@ -473,5 +474,5 @@ int repo_hold_locked_index(struct repository *repo,
{
if (!repo->index_file)
BUG("the repo hasn't been setup");
- return hold_lock_file_for_update(lf, repo->index_file, flags);
+ return repo_hold_lock_file_for_update(repo, lf, repo->index_file, flags);
}
diff --git a/repository.h b/repository.h
index b767307..3b467a2 100644
--- a/repository.h
+++ b/repository.h
@@ -167,7 +167,7 @@ struct repository {
/* Repository's compatibility hash algorithm. */
const struct git_hash_algo *compat_hash_algo;
- /* Repository's config values parsed by git_default_config() */
+ /* Repository-specific configuration values. */
struct repo_config_values config_values_private_;
/* Repository's reference storage format, as serialized on disk. */
diff --git a/rerere.c b/rerere.c
index 2161009..0015e25 100644
--- a/rerere.c
+++ b/rerere.c
@@ -756,7 +756,7 @@ static void do_rerere_one_path(struct index_state *istate,
/* Has the user resolved it already? */
if (variant >= 0) {
if (!handle_file(istate, path, NULL, NULL)) {
- copy_file(rerere_path(&buf, id, "postimage"), path, 0666);
+ copy_file(the_repository, rerere_path(&buf, id, "postimage"), path, 0666);
id->collection->status[variant] |= RR_HAS_POSTIMAGE;
fprintf_ln(stderr, _("Recorded resolution for '%s'."), path);
free_rerere_id(rr_item);
@@ -911,9 +911,9 @@ int setup_rerere(struct repository *r, struct string_list *merge_rr, int flags)
if (flags & RERERE_READONLY)
fd = 0;
else
- fd = hold_lock_file_for_update(&write_lock,
- git_path_merge_rr(r),
- LOCK_DIE_ON_ERROR);
+ fd = repo_hold_lock_file_for_update(r, &write_lock,
+ git_path_merge_rr(r),
+ LOCK_DIE_ON_ERROR);
read_rr(r, merge_rr);
return fd;
}
diff --git a/revision.c b/revision.c
index 8425f67..8eb7cb9 100644
--- a/revision.c
+++ b/revision.c
@@ -1711,7 +1711,7 @@ static void add_other_reflogs_to_pending(struct all_refs_cb *cb)
{
struct worktree **worktrees, **p;
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
@@ -1837,7 +1837,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags)
if (revs->single_worktree)
return;
- worktrees = get_worktrees();
+ worktrees = get_worktrees(the_repository);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
struct index_state istate = INDEX_STATE_INIT(revs->repo);
@@ -2632,6 +2632,12 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->graph = NULL;
} else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) {
revs->graph_max_lanes = parse_count(optarg);
+ } else if (!strcmp(arg, "--graph-indent")) {
+ revs->no_graph_indent = 0;
+ revs->graph_indent_set = 1;
+ } else if (!strcmp(arg, "--no-graph-indent")) {
+ revs->no_graph_indent = 1;
+ revs->graph_indent_set = 1;
} else if (!strcmp(arg, "--encode-email-headers")) {
revs->encode_email_headers = 1;
} else if (!strcmp(arg, "--no-encode-email-headers")) {
@@ -2818,7 +2824,7 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags);
- other_head_refs(handle_one_ref, &cb);
+ other_head_refs(the_repository, handle_one_ref, &cb);
}
clear_ref_exclusions(&revs->ref_excludes);
} else if (!strcmp(arg, "--branches")) {
@@ -3206,6 +3212,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (revs->graph_max_lanes > 0 && !revs->graph)
die(_("the option '%s' requires '%s'"), "--graph-lane-limit", "--graph");
+ if (revs->graph_indent_set && !revs->graph)
+ die(_("the option '%s' requires '%s'"), "--[no-]graph-indent", "--graph");
+
if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs");
@@ -4424,6 +4433,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
switch (mode) {
case REV_WALK_REFLOG:
+ case REV_WALK_NO_WALK:
try_to_simplify_commit(revs, commit);
break;
case REV_WALK_TOPO:
@@ -4437,7 +4447,6 @@ static struct commit *get_revision_1(struct rev_info *revs)
oid_to_hex(&commit->object.oid));
}
break;
- case REV_WALK_NO_WALK:
case REV_WALK_LIMITED:
break;
}
@@ -4664,12 +4673,34 @@ static void retrieve_oldest_commits(struct rev_info *revs,
commit_list_insert(c, queue);
}
+/*
+ * Returns the next commit that will be shown, regardless of whether it comes
+ * directly from the revision walk or from the list saved by the staged output
+ * of --max-count-oldest.
+ */
+static struct commit *next_commit_to_show(struct rev_info *revs)
+{
+ struct commit *c;
+ struct commit_list *p;
+
+ if (!revs->max_count_stage)
+ return get_revision_internal(revs);
+
+ c = pop_commit(&revs->commits);
+ if (c) {
+ c->object.flags |= SHOWN;
+ if (!(c->object.flags & BOUNDARY))
+ for (p = c->parents; p; p = p->next)
+ p->item->object.flags |= CHILD_SHOWN;
+ }
+ return c;
+}
+
struct commit *get_revision(struct rev_info *revs)
{
struct commit *c;
struct commit_list *reversed;
struct commit_list *queue = NULL;
- struct commit_list *p;
if (revs->max_count_type == 1 && !revs->max_count_stage) {
retrieve_oldest_commits(revs, &queue);
@@ -4699,20 +4730,24 @@ struct commit *get_revision(struct rev_info *revs)
return c;
}
- if (revs->max_count_stage) {
- c = pop_commit(&revs->commits);
- if (c) {
- c->object.flags |= SHOWN;
- if (!(c->object.flags & BOUNDARY))
- for (p = c->parents; p; p = p->next)
- p->item->object.flags |= CHILD_SHOWN;
- }
+ if (revs->graph) {
+ c = graph_pop_lookahead(revs->graph);
+ if (!c)
+ c = next_commit_to_show(revs);
} else {
- c = get_revision_internal(revs);
+ c = next_commit_to_show(revs);
}
- if (c && revs->graph)
+ if (c && revs->graph) {
+ while (graph_get_lookahead_room(revs->graph)) {
+ struct commit *next = next_commit_to_show(revs);
+ if (!next)
+ break;
+ graph_push_lookahead(revs->graph, next);
+ }
graph_update(revs->graph, c);
+ }
+
if (!c) {
free_saved_parents(revs);
commit_list_free(revs->previous_parents);
diff --git a/revision.h b/revision.h
index 569b3fa..acf6d06 100644
--- a/revision.h
+++ b/revision.h
@@ -314,6 +314,8 @@ struct rev_info {
/* Display history graph */
struct git_graph *graph;
int graph_max_lanes;
+ unsigned int no_graph_indent:1;
+ unsigned int graph_indent_set:1;
/* special limits */
int skip_count;
diff --git a/sequencer.c b/sequencer.c
index 1355a99..82ab3c5 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2260,10 +2260,17 @@ static const char *reflog_message(struct replay_opts *opts,
return buf.buf;
}
-static int do_pick_commit(struct repository *r,
- struct todo_item *item,
- struct replay_opts *opts,
- int final_fixup, int *check_todo)
+enum pick_result {
+ PICK_RESULT_ERROR = -1,
+ PICK_RESULT_OK,
+ PICK_RESULT_CONFLICTS,
+ PICK_RESULT_DROPPED,
+};
+
+static enum pick_result do_pick_commit(struct repository *r,
+ struct todo_item *item,
+ struct replay_opts *opts,
+ int final_fixup, int *check_todo)
{
struct replay_ctx *ctx = opts->ctx;
unsigned int flags = should_edit(opts) ? EDIT_MSG : 0;
@@ -2273,7 +2280,7 @@ static int do_pick_commit(struct repository *r,
const char *base_label, *next_label, *reflog_action;
char *author = NULL;
struct commit_message msg = { NULL, NULL, NULL, NULL };
- int res, unborn = 0, reword = 0, allow, drop_commit;
+ int res, unborn = 0, reword = 0, allow, drop_commit = 0;
enum todo_command command = item->command;
struct commit *commit = item->commit;
@@ -2419,7 +2426,7 @@ static int do_pick_commit(struct repository *r,
} else {
const char *dest = git_path_squash_msg(r);
unlink(dest);
- if (copy_file(dest, rebase_path_squash_msg(), 0666)) {
+ if (copy_file(r, dest, rebase_path_squash_msg(), 0666)) {
res = error(_("could not copy '%s' to '%s'"),
rebase_path_squash_msg(), dest);
goto leave;
@@ -2453,14 +2460,25 @@ static int do_pick_commit(struct repository *r,
struct commit_list *common = NULL;
struct commit_list *remotes = NULL;
- res = write_message(ctx->message.buf, ctx->message.len,
- git_path_merge_msg(r), 0);
+ if (write_message(ctx->message.buf, ctx->message.len,
+ git_path_merge_msg(r), 0)) {
+ res = -1;
+ goto leave;
+ }
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
- res |= try_merge_command(r, opts->strategy,
- opts->xopts.nr, opts->xopts.v,
+ res = try_merge_command(r, opts->strategy,
+ opts->xopts.nr, opts->xopts.v,
common, oid_to_hex(&head), remotes);
+ /*
+ * If there were conflicts, try_merge_command() returns 1,
+ * any other no-zero return code means that either the merge
+ * command could not be run, or it failed to merge.
+ */
+ if (res && res != 1)
+ res = -1;
+
commit_list_free(common);
commit_list_free(remotes);
}
@@ -2492,7 +2510,6 @@ static int do_pick_commit(struct repository *r,
goto leave;
}
- drop_commit = 0;
allow = allow_empty(r, opts, commit);
if (allow < 0) {
res = allow;
@@ -2531,6 +2548,12 @@ static int do_pick_commit(struct repository *r,
res = run_git_commit(NULL, reflog_action, opts, flags);
*check_todo = 1;
}
+ /*
+ * If "git commit" failed to run then res == -1, but we don't
+ * want reschedule the last command because the picking the
+ * commit was successful.
+ */
+ res = !!res;
}
@@ -2547,7 +2570,14 @@ static int do_pick_commit(struct repository *r,
free(author);
update_abort_safety_file();
- return res;
+ if (res < 0)
+ return PICK_RESULT_ERROR;
+ else if (res > 0)
+ return PICK_RESULT_CONFLICTS;
+ else if (drop_commit)
+ return PICK_RESULT_DROPPED;
+ else
+ return PICK_RESULT_OK;
}
static int prepare_revs(struct replay_opts *opts)
@@ -3864,15 +3894,15 @@ static int error_failed_squash(struct repository *r,
int subject_len,
const char *subject)
{
- if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
+ if (copy_file(r, rebase_path_message(), rebase_path_squash_msg(), 0666))
return error(_("could not copy '%s' to '%s'"),
rebase_path_squash_msg(), rebase_path_message());
unlink(git_path_merge_msg(r));
- if (copy_file(git_path_merge_msg(r), rebase_path_message(), 0666))
+ if (copy_file(r, git_path_merge_msg(r), rebase_path_message(), 0666))
return error(_("could not copy '%s' to '%s'"),
rebase_path_message(),
git_path_merge_msg(r));
- return error_with_patch(r, commit, subject, subject_len, opts, 1, 0);
+ return error_with_patch(r, commit, subject, subject_len, opts, 1, 1);
}
static int do_exec(struct repository *r, const char *command_line, int quiet)
@@ -4963,37 +4993,59 @@ static int pick_one_commit(struct repository *r,
struct replay_opts *opts,
int *check_todo, int* reschedule)
{
- int res;
+ enum pick_result pick_res;
struct todo_item *item = todo_list->items + todo_list->current;
const char *arg = todo_item_get_arg(todo_list, item);
- res = do_pick_commit(r, item, opts, is_final_fixup(todo_list),
- check_todo);
- if (is_rebase_i(opts) && res < 0) {
+ pick_res = do_pick_commit(r, item, opts, is_final_fixup(todo_list),
+ check_todo);
+ if (!is_rebase_i(opts))
+ switch (pick_res) {
+ case PICK_RESULT_ERROR:
+ return -1;
+ case PICK_RESULT_CONFLICTS:
+ return 1;
+ default:
+ return 0;
+ }
+
+ if (pick_res == PICK_RESULT_ERROR) {
/* Reschedule */
*reschedule = 1;
return -1;
- }
- if (item->command == TODO_EDIT) {
+ } else if (item->command == TODO_EDIT) {
struct commit *commit = item->commit;
- if (!res) {
+ int res = pick_res == PICK_RESULT_CONFLICTS;
+ int to_amend = pick_res != PICK_RESULT_CONFLICTS &&
+ pick_res != PICK_RESULT_DROPPED;
+
+ /*
+ * NEEDSWORK: Do not record the commit as rewritten when
+ * continuing if it was dropped. Does it even make sense
+ * to stop if the commit was dropped?
+ */
+ if (pick_res == PICK_RESULT_OK ||
+ pick_res == PICK_RESULT_DROPPED) {
if (!opts->verbose)
term_clear_line();
fprintf(stderr, _("Stopped at %s... %.*s\n"),
short_commit_name(r, commit), item->arg_len, arg);
}
- return error_with_patch(r, commit,
- arg, item->arg_len, opts, res, !res);
- }
- if (is_rebase_i(opts) && !res)
+ return error_with_patch(r, commit, arg, item->arg_len, opts,
+ res, to_amend);
+ } else if (pick_res == PICK_RESULT_OK) {
record_in_rewritten(&item->commit->object.oid,
peek_command(todo_list, 1));
- if (res && is_fixup(item->command)) {
- if (res == 1)
- intend_to_amend();
+ return 0;
+ } else if (pick_res == PICK_RESULT_DROPPED) {
+ if (is_final_fixup(todo_list))
+ flush_rewritten_pending();
+ return 0;
+ } else if (pick_res == PICK_RESULT_CONFLICTS &&
+ is_fixup(item->command)) {
return error_failed_squash(r, item->commit, opts,
item->arg_len, arg);
- } else if (res && is_rebase_i(opts) && item->commit) {
+ } else if (pick_res == PICK_RESULT_CONFLICTS) {
int to_amend = 0;
struct object_id oid;
@@ -5010,11 +5062,11 @@ static int pick_one_commit(struct repository *r,
oideq(&opts->squash_onto, &oid))))
to_amend = 1;
- return res | error_with_patch(r, item->commit,
- arg, item->arg_len, opts,
- res, to_amend);
+ return error_with_patch(r, item->commit, arg, item->arg_len,
+ opts, 1, to_amend);
}
- return res;
+
+ BUG("Unhandled return value from do_pick_commit()");
}
static int pick_commits(struct repository *r,
@@ -5550,7 +5602,15 @@ static int single_pick(struct repository *r,
TODO_PICK : TODO_REVERT;
item.commit = cmit;
- return do_pick_commit(r, &item, opts, 0, &check_todo);
+ switch (do_pick_commit(r, &item, opts, 0, &check_todo)) {
+ case PICK_RESULT_ERROR:
+ return -1;
+ case PICK_RESULT_CONFLICTS:
+ return 1;
+ default:
+ return 0;
+ }
+
}
int sequencer_pick_revisions(struct repository *r,
diff --git a/serve.c b/serve.c
index 49a6e39..2b07d92 100644
--- a/serve.c
+++ b/serve.c
@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,
trace2_data_string("transfer", NULL, "client-sid", client_sid);
}
-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)
+static int object_info_advertise(struct repository *r, struct strbuf *value)
{
if (advertise_object_info == -1 &&
repo_config_get_bool(r, "transfer.advertiseobjectinfo",
@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS
/* disabled by default */
advertise_object_info = 0;
}
+ /* Currently only size is supported */
+ if (value && advertise_object_info)
+ strbuf_addstr(value, "size");
return advertise_object_info;
}
diff --git a/setup.c b/setup.c
index d318081..95909e9 100644
--- a/setup.c
+++ b/setup.c
@@ -2355,7 +2355,7 @@ static void copy_templates_1(struct repository *repo,
strbuf_release(&lnk);
}
else if (S_ISREG(st_template.st_mode)) {
- if (copy_file(path->buf, template_path->buf, st_template.st_mode))
+ if (copy_file(repo, path->buf, template_path->buf, st_template.st_mode))
die_errno(_("cannot copy '%s' to '%s'"),
template_path->buf, path->buf);
}
@@ -2674,7 +2674,8 @@ static void create_object_directory(struct repository *repo)
strbuf_release(&path);
}
-static void separate_git_dir(const char *git_dir, const char *git_link)
+static void separate_git_dir(struct repository *repo,
+ const char *git_dir, const char *git_link)
{
struct stat st;
@@ -2690,7 +2691,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
if (rename(src, git_dir))
die_errno(_("unable to move %s to %s"), src, git_dir);
- repair_worktrees_after_gitdir_move(src);
+ repair_worktrees_after_gitdir_move(repo, src);
}
write_file(git_link, "gitdir: %s", git_dir);
@@ -2849,7 +2850,7 @@ int init_db(struct repository *repo,
apply_and_export_relative_gitdir(repo, real_git_dir, 1);
git_dir = repo_get_git_dir(repo);
- separate_git_dir(git_dir, original_git_dir);
+ separate_git_dir(repo, git_dir, original_git_dir);
} else {
apply_and_export_relative_gitdir(repo, git_dir, 1);
git_dir = repo_get_git_dir(repo);
diff --git a/src/hash.rs b/src/hash.rs
index dea2998..e1f2d31 100644
--- a/src/hash.rs
+++ b/src/hash.rs
@@ -181,7 +181,10 @@
impl Clone for CryptoHasher {
fn clone(&self) -> Self {
let ctx = unsafe { c::git_hash_alloc() };
- unsafe { c::git_hash_clone(ctx, self.ctx) };
+ unsafe {
+ c::git_hash_init(ctx, self.algo.hash_algo_ptr());
+ c::git_hash_clone(ctx, self.ctx)
+ };
Self {
algo: self.algo,
ctx,
@@ -191,7 +194,10 @@
impl Drop for CryptoHasher {
fn drop(&mut self) {
- unsafe { c::git_hash_free(self.ctx) };
+ unsafe {
+ c::git_hash_discard(self.ctx);
+ c::git_hash_free(self.ctx);
+ };
}
}
@@ -353,6 +359,7 @@
pub fn git_hash_clone(dst: *mut c_void, src: *const c_void);
pub fn git_hash_update(ctx: *mut c_void, inp: *const c_void, len: usize);
pub fn git_hash_final(hash: *mut u8, ctx: *mut c_void);
+ pub fn git_hash_discard(ctx: *mut c_void);
pub fn git_hash_final_oid(hash: *mut c_void, ctx: *mut c_void);
}
}
@@ -447,6 +454,7 @@
h.update(&data[2..]);
let h2 = h.clone();
+ let h3 = h2.clone();
let actual_oid = h.into_oid();
assert_eq!(**oid, actual_oid);
@@ -460,6 +468,7 @@
let actual_oid = h.into_oid();
assert_eq!(**oid, actual_oid);
+ std::mem::drop(h3);
}
}
}
diff --git a/strbuf.c b/strbuf.c
index 764b629..4495566 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -646,8 +646,6 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
if (feof(fp))
return EOF;
- strbuf_reset(sb);
-
/* Translate slopbuf to NULL, as we cannot call realloc on it */
if (!sb->alloc)
sb->buf = NULL;
diff --git a/submodule.c b/submodule.c
index b30bb96..5c92575 100644
--- a/submodule.c
+++ b/submodule.c
@@ -2494,7 +2494,7 @@ static void relocate_single_git_dir_into_superproject(const char *path,
if (validate_submodule_path(path) < 0)
exit(128);
- if (submodule_uses_worktrees(path))
+ if (submodule_uses_worktrees(the_repository, path))
die(_("relocate_gitdir for submodule '%s' with "
"more than one worktree not supported"), path);
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 3866d0a..5a9a305 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -84,7 +84,7 @@ static const char **get_store(const char **argv, struct ref_store **refs)
*refs = repo_get_submodule_ref_store(the_repository, gitdir);
} else if (skip_prefix(argv[0], "worktree:", &gitdir)) {
- struct worktree **p, **worktrees = get_worktrees();
+ struct worktree **p, **worktrees = get_worktrees(the_repository);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh
new file mode 100644
index 0000000..7c2e877
--- /dev/null
+++ b/t/lib-cat-file.sh
@@ -0,0 +1,16 @@
+# Library of git-cat-file related test functions.
+
+# Print a string without a trailing newline.
+echo_without_newline () {
+ printf '%s' "$*"
+}
+
+# Print a string without newlines and replace them with a NUL character (\0).
+echo_without_newline_nul () {
+ echo_without_newline "$@" | tr '\n' '\0'
+}
+
+# Calculate the length of a string.
+strlen () {
+ echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
+}
diff --git a/t/lib-log-graph.sh b/t/lib-log-graph.sh
index bf952ef..1eae8f6 100644
--- a/t/lib-log-graph.sh
+++ b/t/lib-log-graph.sh
@@ -26,3 +26,8 @@
test_decode_color <output.colors.raw | sed "s/ *\$//" >output.colors &&
test_cmp expect.colors output.colors
}
+
+lib_test_check_graph () {
+ cat >expect &&
+ lib_test_cmp_graph --format=%s "$@"
+}
diff --git a/t/meson.build b/t/meson.build
index 8ae6ab6..a25f37d 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -171,6 +171,7 @@
't1014-read-tree-confusing.sh',
't1015-read-index-unmerged.sh',
't1016-compatObjectFormat.sh',
+ 't1017-cat-file-remote-object-info.sh',
't1020-subdirectory.sh',
't1022-read-tree-partial-clone.sh',
't1050-large.sh',
@@ -582,6 +583,7 @@
't4215-log-skewed-merges.sh',
't4216-log-bloom.sh',
't4217-log-limit.sh',
+ 't4218-log-graph-indentation.sh',
't4219-log-follow-merge.sh',
't4252-am-options.sh',
't4253-am-keep-cr-dos.sh',
diff --git a/t/perf/p1500-graph-walks.sh b/t/perf/p1500-graph-walks.sh
index 5b23ce5..d167b4f 100755
--- a/t/perf/p1500-graph-walks.sh
+++ b/t/perf/p1500-graph-walks.sh
@@ -32,7 +32,16 @@
echo "X:$line" >>test-tool-tags || return 1
done &&
- commit=$(git commit-tree $(git rev-parse HEAD^{tree})) &&
+ git rev-list --first-parent --max-count=8192 HEAD >contains-commits &&
+ test_file_not_empty contains-commits &&
+ git update-ref refs/contains-perf-base "$(tail -n 1 contains-commits)" &&
+ awk "{
+ printf \"update refs/contains-perf/%04d %s\\n\", NR, \$1
+ }" contains-commits |
+ git update-ref --stdin &&
+ git pack-refs --include "refs/contains-perf/*" &&
+
+ commit=$(git commit-tree HEAD^{tree}) &&
git update-ref refs/heads/disjoint-base $commit &&
git commit-graph write --reachable
@@ -62,6 +71,23 @@
xargs git tag --merged=HEAD <tags
'
+test_perf 'contains: git for-each-ref' '
+ git for-each-ref --contains=refs/contains-perf-base --stdin <refs
+'
+
+test_perf 'contains: git branch' '
+ xargs git branch --contains=refs/contains-perf-base <branches
+'
+
+test_perf 'contains: git tag' '
+ xargs git tag --contains=refs/contains-perf-base <tags
+'
+
+test_perf 'contains: synthetic shared history' '
+ git for-each-ref --contains=refs/contains-perf-base \
+ refs/contains-perf/ >/dev/null
+'
+
test_perf 'is-base check: test-tool reach (refs)' '
test-tool reach get_branch_base_for_tip <test-tool-refs
'
diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh
index aadf22b..548a61c 100755
--- a/t/perf/p2000-sparse-operations.sh
+++ b/t/perf/p2000-sparse-operations.sh
@@ -108,6 +108,7 @@
test_perf_on_all git status
test_perf_on_all 'git stash && git stash pop'
+test_perf_on_all "git stash push -- $SPARSE_CONE/a && git stash pop"
test_perf_on_all 'echo >>new && git stash -u && git stash pop'
test_perf_on_all git add -A
test_perf_on_all git add .
diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches
index e8d6c13..c4a55ff 100644
--- a/t/t0450/adoc-help-mismatches
+++ b/t/t0450/adoc-help-mismatches
@@ -12,7 +12,6 @@
credential
credential-cache
credential-store
-fast-export
fast-import
fetch-pack
fmt-merge-msg
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index 762c77c..0c1dc0f 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -3,7 +3,8 @@
test_description='git cat-file'
. ./test-lib.sh
-. "$TEST_DIRECTORY/lib-loose.sh"
+. "$TEST_DIRECTORY"/lib-loose.sh
+. "$TEST_DIRECTORY"/lib-cat-file.sh
test_cmdmode_usage () {
test_expect_code 129 "$@" 2>err &&
@@ -99,18 +100,6 @@
'
done
-echo_without_newline () {
- printf '%s' "$*"
-}
-
-echo_without_newline_nul () {
- echo_without_newline "$@" | tr '\n' '\0'
-}
-
-strlen () {
- echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
-}
-
run_tests () {
type=$1
object_name="$2"
diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh
new file mode 100755
index 0000000..116862f
--- /dev/null
+++ b/t/t1017-cat-file-remote-object-info.sh
@@ -0,0 +1,747 @@
+#!/bin/sh
+
+test_description='git cat-file --batch-command with remote-object-info command'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-cat-file.sh
+
+hello_content="Hello World"
+hello_size=$(strlen "$hello_content")
+hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin)
+hello_short_oid=$(git rev-parse --short "$hello_oid")
+
+unstored_content="Hello Git"
+unstored_oid=$(echo_without_newline "$unstored_content" | git hash-object --stdin)
+
+# This is how we get 13:
+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where
+# file mode is 100644, which is 6 characters;
+# file name is hello, which is 5 characters
+# a space is 1 character and a null is 1 character
+tree_size=$(($(test_oid rawsz) + 13))
+
+commit_message="Initial commit"
+
+# This is how we get 137:
+# 137 = <tree header> + <a_space> + <a newline> +
+# <Author line> + <a newline> +
+# <Committer line> + <a newline> +
+# <a newline> +
+# <commit message length>
+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,
+# to get 177, 2. then deduct 40 hex characters to get 137
+commit_size=$(($(test_oid hexsz) + 137))
+
+tag_header_without_oid="type blob
+tag hellotag
+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+tag_header_without_timestamp="object $hello_oid
+$tag_header_without_oid"
+tag_description="This is a tag"
+tag_content="$tag_header_without_timestamp 0 +0000
+
+$tag_description"
+
+tag_oid=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w)
+tag_size=$(strlen "$tag_content")
+
+set_transport_variables () {
+ hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin)
+ tree_oid=$(git -C "$1" write-tree)
+ commit_oid=$(echo_without_newline "$commit_message" | git -C "$1" commit-tree $tree_oid)
+ tag_oid=$(echo_without_newline "$tag_content" | git -C "$1" hash-object -t tag --stdin -w)
+ tag_size=$(strlen "$tag_content")
+}
+
+# This section tests --batch-command with remote-object-info command
+# Since "%(objecttype)" is currently not supported by the command remote-object-info ,
+# the filters are set to "%(objectname) %(objectsize)" in some test cases.
+
+# Test --batch-command remote-object-info with 'git://' transport with
+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon --export-all
+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
+
+test_expect_success 'create repo to be served by git-daemon' '
+ git init "$daemon_parent" &&
+ echo_without_newline "$hello_content" > $daemon_parent/hello &&
+ git -C "$daemon_parent" update-index --add hello &&
+ git -C "$daemon_parent" config transfer.advertiseobjectinfo true &&
+ git clone "$GIT_DAEMON_URL/parent" -n "$daemon_parent/daemon_client_empty"
+'
+
+test_expect_success 'batch-command remote-object-info git://' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $tree_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info git:// default filter' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info does not change the default format of info' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ local_content="local object" &&
+ local_oid=$(echo_without_newline "$local_content" | git hash-object -w --stdin) &&
+ local_size=$(strlen "$local_content") &&
+
+ echo "$local_oid blob $local_size" >expect &&
+ echo "$hello_oid $hello_size" >>expect &&
+ echo "$local_oid blob $local_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ info $local_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ info $local_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command --buffer remote-object-info git://' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ flush
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ printf "%s\0" "$hello_oid $hello_size" >expect &&
+ printf "%s\0" "$tree_oid $tree_size" >>expect &&
+ printf "%s\0" "$commit_oid $commit_size" >>expect &&
+ printf "%s\0" "$tag_oid $tag_size" >>expect &&
+
+ printf "%s\0" "$hello_oid missing" >>expect &&
+ printf "%s\0" "$tree_oid missing" >>expect &&
+ printf "%s\0" "$commit_oid missing" >>expect &&
+ printf "%s\0" "$tag_oid missing" >>expect &&
+
+ batch_input="remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid
+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid
+info $hello_oid
+info $tree_oid
+info $commit_oid
+info $tag_oid
+" &&
+ echo_without_newline_nul "$batch_input" >commands_null_delimited &&
+
+ git cat-file --batch-command -Z < commands_null_delimited >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info does not support short oids' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ test_must_fail git cat-file --batch-command 2>err <<-EOF &&
+ remote-object-info $GIT_DAEMON_URL/parent $hello_short_oid
+ EOF
+ test_grep "does not support short oids" err
+ )
+'
+
+test_expect_success 'remote-object-info does not die on missing oid like info' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ git cat-file --batch-command >local <<-EOF &&
+ info $unstored_oid
+ EOF
+ git cat-file --batch-command >remote <<-EOF &&
+ remote-object-info $GIT_DAEMON_URL/parent $unstored_oid
+ EOF
+ test_cmp local remote
+ )
+'
+
+# This tests depends on %(objecttype) not being supported yet, once supported
+# it needs to be updated.
+test_expect_success 'unsupported placeholder on remote returns empty string' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo "" >expect &&
+ git cat-file --batch-command="%(objecttype)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'requesting only objectname echoes back' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo $hello_oid >expect &&
+ git cat-file --batch-command="%(objectname)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'objectname goes through existence check' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo "$unstored_oid missing" >expect &&
+
+ git cat-file --batch-command="%(objectname)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $unstored_oid
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
+# Test --batch-command remote-object-info with 'git://' and
+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '
+ (
+ git -C "$daemon_parent" config transfer.advertiseobjectinfo false &&
+ set_transport_variables "$daemon_parent" &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid
+ EOF
+ test_grep "object-info capability is not enabled on the server" err &&
+
+ # revert server state back
+ git -C "$daemon_parent" config transfer.advertiseobjectinfo true
+
+ )
+'
+
+stop_git_daemon
+
+# Test --batch-command remote-object-info with 'file://' transport with
+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
+# shellcheck disable=SC2016
+test_expect_success 'create repo to be served by file:// transport' '
+ git init server &&
+ git -C server config protocol.version 2 &&
+ git -C server config transfer.advertiseobjectinfo true &&
+ echo_without_newline "$hello_content" > server/hello &&
+ git -C server update-index --add hello &&
+ git clone -n "file://$(pwd)/server" file_client_empty
+'
+
+test_expect_success 'batch-command remote-object-info file://' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid
+ remote-object-info "file://${server_path}" $tree_oid
+ remote-object-info "file://${server_path}" $commit_oid
+ remote-object-info "file://${server_path}" $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command --buffer remote-object-info file://' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid
+ remote-object-info "file://${server_path}" $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ flush
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info file:// default filter' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid
+ remote-object-info "file://${server_path}" $commit_oid $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ printf "%s\0" "$hello_oid $hello_size" >expect &&
+ printf "%s\0" "$tree_oid $tree_size" >>expect &&
+ printf "%s\0" "$commit_oid $commit_size" >>expect &&
+ printf "%s\0" "$tag_oid $tag_size" >>expect &&
+
+ printf "%s\0" "$hello_oid missing" >>expect &&
+ printf "%s\0" "$tree_oid missing" >>expect &&
+ printf "%s\0" "$commit_oid missing" >>expect &&
+ printf "%s\0" "$tag_oid missing" >>expect &&
+
+ batch_input="remote-object-info \"file://${server_path}\" $hello_oid $tree_oid
+remote-object-info \"file://${server_path}\" $commit_oid $tag_oid
+info $hello_oid
+info $tree_oid
+info $commit_oid
+info $tag_oid
+" &&
+ echo_without_newline_nul "$batch_input" >commands_null_delimited &&
+
+ git cat-file --batch-command -Z < commands_null_delimited >actual &&
+ test_cmp expect actual
+ )
+'
+
+# Test --batch-command remote-object-info with 'file://' and
+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ git -C "${server_path}" config transfer.advertiseobjectinfo false &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid
+ EOF
+ test_grep "object-info capability is not enabled on the server" err &&
+
+ # revert server state back
+ git -C "${server_path}" config transfer.advertiseobjectinfo true
+ )
+'
+
+# Test --batch-command remote-object-info with 'http://' transport with
+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
+
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'create repo to be served by http:// transport' '
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true &&
+ echo_without_newline "$hello_content" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" update-index --add hello &&
+ git clone "$HTTPD_URL/smart/http_parent" -n "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty"
+'
+
+test_expect_success 'batch-command remote-object-info http://' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $tree_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info http:// one line' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command --buffer remote-object-info http://' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ flush
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info http:// default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ printf "%s\0" "$hello_oid $hello_size" >expect &&
+ printf "%s\0" "$tree_oid $tree_size" >>expect &&
+ printf "%s\0" "$commit_oid $commit_size" >>expect &&
+ printf "%s\0" "$tag_oid $tag_size" >>expect &&
+
+ batch_input="remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid
+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid
+" &&
+ echo_without_newline_nul "$batch_input" >commands_null_delimited &&
+
+ git cat-file --batch-command -Z < commands_null_delimited >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ echo "$hello_oid " >expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize:disk)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ echo "" >expect &&
+
+ git cat-file --batch-command="%(deltabase)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info fails on server with legacy protocol' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ test_must_fail git -c protocol.version=0 cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_grep "object-info requires protocol v2" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ test_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_grep "object-info requires protocol v2" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on malformed OID' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ malformed_object_id="this_id_is_not_valid" &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id
+ EOF
+ test_grep "not a valid object name '$malformed_object_id'" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on malformed OID with default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ malformed_object_id="this_id_is_not_valid" &&
+
+ test_must_fail git cat-file --batch-command 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id
+ EOF
+ test_grep "not a valid object name '$malformed_object_id'" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on not providing OID' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent"
+ EOF
+ test_grep "remote-object-info requires objects" err
+ )
+'
+
+
+# Test --batch-command remote-object-info with 'http://' transport and
+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo false &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid
+ EOF
+ test_grep "object-info capability is not enabled on the server" err &&
+
+ # revert server state back
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true
+ )
+'
+
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
+test_done
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index 9814431..4140c4d 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -1598,6 +1598,61 @@
ensure_not_expanded stash pop
'
+test_expect_success 'sparse-index is not expanded: stash in-cone pathspec' '
+ init_repos &&
+
+ echo unrelated >>sparse-index/deep/e &&
+ echo literal >>sparse-index/deep/a &&
+ ensure_not_expanded stash push -- deep/a &&
+ test_grep ! literal sparse-index/deep/a &&
+ test_grep unrelated sparse-index/deep/e &&
+ ensure_not_expanded stash pop &&
+ test_grep literal sparse-index/deep/a &&
+
+ echo prefixed >>sparse-index/deep/a &&
+ ensure_not_expanded -C deep stash push -- a &&
+ test_grep ! prefixed sparse-index/deep/a &&
+ test_grep unrelated sparse-index/deep/e &&
+ ensure_not_expanded stash pop &&
+ test_grep prefixed sparse-index/deep/a &&
+
+ echo wildcard >>sparse-index/deep/a &&
+ ensure_not_expanded stash push -- "deep/a*" &&
+ test_grep ! wildcard sparse-index/deep/a &&
+ test_grep unrelated sparse-index/deep/e &&
+ ensure_not_expanded stash pop &&
+ test_grep wildcard sparse-index/deep/a &&
+
+ echo pathspec-file >>sparse-index/deep/a &&
+ echo deep/a >pathspec-file &&
+ ensure_not_expanded stash push --pathspec-from-file=../pathspec-file &&
+ test_grep ! pathspec-file sparse-index/deep/a &&
+ test_grep unrelated sparse-index/deep/e &&
+ ensure_not_expanded stash pop &&
+ test_grep pathspec-file sparse-index/deep/a &&
+
+ echo multiple-a >>sparse-index/deep/a &&
+ echo multiple-e >>sparse-index/deep/e &&
+ ensure_not_expanded stash push -- deep/a deep/e &&
+ test_grep ! multiple-a sparse-index/deep/a &&
+ test_grep ! multiple-e sparse-index/deep/e &&
+ ensure_not_expanded stash pop &&
+ test_grep multiple-a sparse-index/deep/a &&
+ test_grep multiple-e sparse-index/deep/e &&
+
+ echo staged >>sparse-index/deep/a &&
+ git -C sparse-index add deep/a &&
+ ensure_not_expanded stash push --staged -- deep/a &&
+ test_grep ! staged sparse-index/deep/a &&
+ test_grep unrelated sparse-index/deep/e &&
+ ensure_not_expanded stash pop --index &&
+ test_grep staged sparse-index/deep/a &&
+ test_must_fail git -C sparse-index diff --cached --quiet -- deep/a &&
+
+ ensure_not_expanded ! stash push -- deep/does-not-exist &&
+ test_grep "did not match any file" sparse-index-error
+'
+
test_expect_success 'describe tested on all' '
init_repos &&
@@ -2119,6 +2174,13 @@
ensure_not_expanded rm -r deep
'
+test_expect_success 'sparse index is not expanded: prefixed wildcard pathspec' '
+ init_repos &&
+
+ ensure_not_expanded -C deep rm --dry-run -- "a*" &&
+ ensure_not_expanded -C deep reset base -- "a*"
+'
+
test_expect_success 'grep with and --cached' '
init_repos &&
diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh
index ae66ba5..3e88890 100755
--- a/t/t1100-commit-tree-options.sh
+++ b/t/t1100-commit-tree-options.sh
@@ -14,37 +14,35 @@
. ./test-lib.sh
-cat >expected <<EOF
-tree $EMPTY_TREE
-author Author Name <author@email> 1117148400 +0000
-committer Committer Name <committer@email> 1117150200 +0000
+test_expect_success 'test preparation: write empty tree' '
+ cat >expected <<-EOF &&
+ tree $EMPTY_TREE
+ author Author Name <author@email> 1117148400 +0000
+ committer Committer Name <committer@email> 1117150200 +0000
-comment text
-EOF
+ comment text
+ EOF
+ git write-tree >treeid
+'
-test_expect_success \
- 'test preparation: write empty tree' \
- 'git write-tree >treeid'
+test_expect_success 'construct commit' '
+ echo comment text |
+ GIT_AUTHOR_NAME="Author Name" \
+ GIT_AUTHOR_EMAIL="author@email" \
+ GIT_AUTHOR_DATE="2005-05-26 23:00" \
+ GIT_COMMITTER_NAME="Committer Name" \
+ GIT_COMMITTER_EMAIL="committer@email" \
+ GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ TZ=GMT git commit-tree $(cat treeid) >commitid 2>/dev/null
+'
-test_expect_success \
- 'construct commit' \
- 'echo comment text |
- GIT_AUTHOR_NAME="Author Name" \
- GIT_AUTHOR_EMAIL="author@email" \
- GIT_AUTHOR_DATE="2005-05-26 23:00" \
- GIT_COMMITTER_NAME="Committer Name" \
- GIT_COMMITTER_EMAIL="committer@email" \
- GIT_COMMITTER_DATE="2005-05-26 23:30" \
- TZ=GMT git commit-tree $(cat treeid) >commitid 2>/dev/null'
+test_expect_success 'read commit' '
+ git cat-file commit $(cat commitid) >commit
+'
-test_expect_success \
- 'read commit' \
- 'git cat-file commit $(cat commitid) >commit'
-
-test_expect_success \
- 'compare commit' \
- 'test_cmp expected commit'
-
+test_expect_success 'compare commit' '
+ test_cmp expected commit
+'
test_expect_success 'flags and then non flags' '
test_tick &&
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index b99f782..e3f8064 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -3002,4 +3002,21 @@
test_cmp expect actual
'
+test_expect_success 'writing config fails immediately with core.configLockTimeout=0' '
+ test_when_finished "rm -f .git/config.lock" &&
+ >.git/config.lock &&
+ test_must_fail git -c core.configLockTimeout=0 config foo.bar baz 2>err &&
+ test_grep "could not lock config file" err
+'
+
+test_expect_success 'writing config retries until lock is released' '
+ test_when_finished "rm -f .git/config.lock" &&
+ >.git/config.lock &&
+ {
+ ( sleep 1 && rm -f .git/config.lock ) &
+ } &&
+ git -c core.configLockTimeout=5000 config retried.key value &&
+ test "$(git config retried.key)" = value
+'
+
test_done
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index f611526..507b8eb 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -396,4 +396,132 @@
test_must_fail nongit git --git-dir=nonexistent config get foo.bar
'
+# worktree: conditional include tests
+
+test_expect_success 'conditional include, worktree bare repo' '
+ git init --bare wt-bare &&
+ (
+ cd wt-bare &&
+ echo "[includeIf \"worktree:/\"]path=bar-bare" >>config &&
+ echo "[test]wtbare=1" >bar-bare &&
+ test_must_fail git config test.wtbare
+ )
+'
+
+test_expect_success 'conditional include, worktree multiple worktrees' '
+ git init wt-multi &&
+ (
+ cd wt-multi &&
+ test_commit initial &&
+ git worktree add -b linked-branch ../wt-linked HEAD &&
+ git worktree add -b prefix-branch ../wt-prefix/linked HEAD
+ ) &&
+ wt_main="$(cd wt-multi && pwd)" &&
+ wt_linked="$(cd wt-linked && pwd)" &&
+ wt_prefix_parent="$(cd wt-prefix && pwd)" &&
+ cat >>wt-multi/.git/config <<-EOF &&
+ [includeIf "worktree:$wt_main"]
+ path = main-config
+ [includeIf "worktree:$wt_linked"]
+ path = linked-config
+ [includeIf "worktree:$wt_prefix_parent/"]
+ path = prefix-config
+ EOF
+ echo "[test]mainvar=main" >wt-multi/.git/main-config &&
+ echo "[test]linkedvar=linked" >wt-multi/.git/linked-config &&
+ echo "[test]prefixvar=prefix" >wt-multi/.git/prefix-config &&
+ echo main >expect &&
+ git -C wt-multi config test.mainvar >actual &&
+ test_cmp expect actual &&
+ test_must_fail git -C wt-multi config test.linkedvar &&
+ test_must_fail git -C wt-multi config test.prefixvar &&
+ echo linked >expect &&
+ git -C wt-linked config test.linkedvar >actual &&
+ test_cmp expect actual &&
+ test_must_fail git -C wt-linked config test.mainvar &&
+ test_must_fail git -C wt-linked config test.prefixvar &&
+ echo prefix >expect &&
+ git -C wt-prefix/linked config test.prefixvar >actual &&
+ test_cmp expect actual &&
+ test_must_fail git -C wt-prefix/linked config test.mainvar &&
+ test_must_fail git -C wt-prefix/linked config test.linkedvar
+'
+
+test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '
+ mkdir real-wt &&
+ ln -s real-wt link-wt &&
+ git init link-wt/repo &&
+ (
+ cd link-wt/repo &&
+ # repo->worktree resolves symlinks, so use real path in pattern
+ echo "[includeIf \"worktree:**/real-wt/repo\"]path=bar-link" >>.git/config &&
+ echo "[test]wtlink=2" >.git/bar-link &&
+ echo 2 >expect &&
+ git config test.wtlink >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success !CASE_INSENSITIVE_FS 'conditional include, worktree, case sensitive' '
+ git init wt-case &&
+ (
+ cd wt-case &&
+ test_commit initial &&
+ wt_path="$(pwd)" &&
+ wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
+ echo "[includeIf \"worktree:$wt_upper\"]path=case-inc" >>.git/config &&
+ echo "[test]wtcase=1" >.git/case-inc &&
+ test_must_fail git config test.wtcase
+ )
+'
+
+test_expect_success 'conditional include, worktree, icase' '
+ git init wt-icase &&
+ (
+ cd wt-icase &&
+ test_commit initial &&
+ wt_path="$(pwd)" &&
+ wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
+ echo "[includeIf \"worktree/i:$wt_upper\"]path=icase-inc" >>.git/config &&
+ echo "[test]wticase=1" >.git/icase-inc &&
+ echo 1 >expect &&
+ git config test.wticase >actual &&
+ test_cmp expect actual
+ )
+'
+
+# The "worktree" condition cannot match during early config reading
+# because the repository object is not yet fully initialized and
+# repo_get_work_tree() returns NULL.
+test_expect_success 'conditional include, worktree does not match in early config' '
+ git init wt-early &&
+ (
+ cd wt-early &&
+ test_commit initial &&
+ wt_path="$(pwd)" &&
+ echo "[includeIf \"worktree:$wt_path\"]path=early-inc" >>.git/config &&
+ echo "[test]wtearly=1" >.git/early-inc &&
+ test-tool config read_early_config test.wtearly >actual &&
+ test_must_be_empty actual
+ )
+'
+
+# Use a loose pattern so the "present in non-worktree cases" check works
+# for Unix-style absolute paths and Windows paths like D:/a/git/...
+test_expect_success 'conditional include, worktree without repository' '
+ test_when_finished "rm -f .gitconfig config.inc" &&
+ git config set -f .gitconfig "includeIf.worktree:**.path" config.inc &&
+ git config set -f config.inc foo.bar baz &&
+ git config get foo.bar &&
+ test_must_fail nongit git config get foo.bar
+'
+
+test_expect_success 'conditional include, worktree without repository but explicit nonexistent Git directory' '
+ test_when_finished "rm -f .gitconfig config.inc" &&
+ git config set -f .gitconfig "includeIf.worktree:**.path" config.inc &&
+ git config set -f config.inc foo.bar baz &&
+ git config get foo.bar &&
+ test_must_fail nongit git --git-dir=nonexistent config get foo.bar
+'
+
test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 47d460a..77cd96d 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -538,6 +538,23 @@
test_grep -q "error: hash mismatch $(dirname $new)$(test_oid ff_2)" out
'
+test_expect_success 'rev-list --verify-objects with truncated loose blob' '
+ git init truncated-blob &&
+ (
+ cd truncated-blob &&
+ blob=$(test-tool genrandom one 5k | git hash-object -t blob -w --stdin) &&
+ obj=.git/objects/$(test_oid_to_path $blob) &&
+
+ # Truncate the loose blob such that its header can still be
+ # parsed, but reading the object data fails mid-stream.
+ test_copy_bytes 64 <"$obj" >obj.tmp &&
+ mv obj.tmp "$obj" &&
+
+ test_must_fail git rev-list --verify-objects "$blob" 2>err &&
+ test_grep "hash mismatch" err
+ )
+'
+
# An actual bit corruption is more likely than swapped commits, but
# this provides an easy way to have commits which don't match their purported
# hashes, but which aren't so broken we can't read them at all.
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 1ecbafb..7f7807e 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1075,7 +1075,8 @@
test_when_finished "rm -f .git/config.lock" &&
>.git/config.lock &&
git branch locked &&
- test_must_fail git branch --set-upstream-to locked 2>err &&
+ test_must_fail git -c core.configLockTimeout=0 \
+ branch --set-upstream-to locked 2>err &&
test_grep "could not lock config file .git/config" err
'
@@ -1106,7 +1107,8 @@
test_when_finished "rm -f .git/config.lock" &&
git branch --set-upstream-to locked &&
>.git/config.lock &&
- test_must_fail git branch --unset-upstream 2>err &&
+ test_must_fail git -c core.configLockTimeout=0 \
+ branch --unset-upstream 2>err &&
test_grep "could not lock config file .git/config" err
'
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index e62e07b..accaaba 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -270,12 +270,24 @@
test "a note" = "$(git notes show HEAD)"
'
-test_expect_success 'rebase -m can copy notes' '
+test_expect_success 'rebase --apply can copy notes' '
git reset --hard n3 &&
- git rebase -m --onto n1 n2 &&
+ git rebase --apply --onto n1 n2 &&
test "a note" = "$(git notes show HEAD)"
'
+test_expect_success 'rebase drops notes of dropped commits' '
+ git checkout n1 &&
+ echo n3 >n3.t &&
+ echo n4 >n4.t &&
+ git add n3.t n4.t &&
+ git commit -m n34 &&
+ git rebase HEAD n3 &&
+ test_commit_message HEAD -m n2 &&
+ test_must_fail git notes list HEAD >actual &&
+ test_must_be_empty actual
+'
+
test_expect_success 'rebase commit with an ancient timestamp' '
git reset --hard &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index e648167..9bf5351 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1251,6 +1251,17 @@
test $(cat file1) = Z
'
+test_expect_success 'failing pick with --strategy is rescheduled' '
+ test_when_finished "rm -rf bin; test_might_fail git rebase --abort" &&
+ mkdir bin &&
+ echo exit 2 | write_script bin/git-merge-fail &&
+ git log -1 --format="pick %H # %s" HEAD >expect &&
+ test_must_fail env PATH="$PWD/bin:$PATH" \
+ git rebase --no-ff --strategy fail HEAD^ &&
+ test_cmp expect .git/rebase-merge/git-rebase-todo &&
+ test_cmp expect .git/rebase-merge/done
+'
+
test_expect_success 'rebase -i error on commits with \ in message' '
current_head=$(git rev-parse HEAD) &&
test_when_finished "git rebase --abort; git reset --hard $current_head; rm -f error" &&
diff --git a/t/t4018/swift-actor b/t/t4018/swift-actor
new file mode 100644
index 0000000..e4852f4
--- /dev/null
+++ b/t/t4018/swift-actor
@@ -0,0 +1,5 @@
+actor RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/t/t4018/swift-attribute-with-args b/t/t4018/swift-attribute-with-args
new file mode 100644
index 0000000..22b1ee3
--- /dev/null
+++ b/t/t4018/swift-attribute-with-args
@@ -0,0 +1,7 @@
+struct View {
+ @available(iOS 13, *) public func RIGHT() {
+ let a = 1
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-class b/t/t4018/swift-class
new file mode 100644
index 0000000..c3a9336
--- /dev/null
+++ b/t/t4018/swift-class
@@ -0,0 +1,5 @@
+class RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/t/t4018/swift-enum b/t/t4018/swift-enum
new file mode 100644
index 0000000..0a84302
--- /dev/null
+++ b/t/t4018/swift-enum
@@ -0,0 +1,5 @@
+enum RIGHT {
+ case first
+ // a comment
+ case ChangeMe
+}
diff --git a/t/t4018/swift-extension b/t/t4018/swift-extension
new file mode 100644
index 0000000..cbc18ab
--- /dev/null
+++ b/t/t4018/swift-extension
@@ -0,0 +1,5 @@
+extension RIGHT {
+ static let a = 1
+ // a comment
+ static let b = ChangeMe
+}
diff --git a/t/t4018/swift-failable-init b/t/t4018/swift-failable-init
new file mode 100644
index 0000000..4bbd621
--- /dev/null
+++ b/t/t4018/swift-failable-init
@@ -0,0 +1,7 @@
+class Bar {
+ init?(RIGHT: Int) {
+ let x = 0
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-func b/t/t4018/swift-func
new file mode 100644
index 0000000..1fecae0
--- /dev/null
+++ b/t/t4018/swift-func
@@ -0,0 +1,5 @@
+func RIGHT(x: Int) -> Int {
+ let y = x
+ // a comment
+ return ChangeMe
+}
diff --git a/t/t4018/swift-generic-subscript b/t/t4018/swift-generic-subscript
new file mode 100644
index 0000000..423cb58
--- /dev/null
+++ b/t/t4018/swift-generic-subscript
@@ -0,0 +1,7 @@
+struct Container {
+ subscript<RIGHT>(index: Int) -> Int {
+ let a = 0
+ // a comment
+ return ChangeMe
+ }
+}
diff --git a/t/t4018/swift-init b/t/t4018/swift-init
new file mode 100644
index 0000000..dc7a298
--- /dev/null
+++ b/t/t4018/swift-init
@@ -0,0 +1,7 @@
+class Foo {
+ init(RIGHT: Int) {
+ let x = 0
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-inline-attribute b/t/t4018/swift-inline-attribute
new file mode 100644
index 0000000..2374c4b
--- /dev/null
+++ b/t/t4018/swift-inline-attribute
@@ -0,0 +1,7 @@
+class Service {
+ @objc func RIGHT() {
+ let path = "/api"
+ // a comment
+ log(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-modifiers b/t/t4018/swift-modifiers
new file mode 100644
index 0000000..9d80685
--- /dev/null
+++ b/t/t4018/swift-modifiers
@@ -0,0 +1,4 @@
+public static func RIGHT() -> Int {
+ // a comment
+ return ChangeMe
+}
diff --git a/t/t4018/swift-protocol b/t/t4018/swift-protocol
new file mode 100644
index 0000000..07c39ec
--- /dev/null
+++ b/t/t4018/swift-protocol
@@ -0,0 +1,5 @@
+protocol RIGHT {
+ var first: Int { get }
+ // a comment
+ var second: ChangeMe { get }
+}
diff --git a/t/t4018/swift-struct b/t/t4018/swift-struct
new file mode 100644
index 0000000..e399ed7
--- /dev/null
+++ b/t/t4018/swift-struct
@@ -0,0 +1,5 @@
+struct RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh
index 1612f05..eebab71 100755
--- a/t/t4215-log-skewed-merges.sh
+++ b/t/t4215-log-skewed-merges.sh
@@ -5,11 +5,6 @@
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-log-graph.sh
-check_graph () {
- cat >expect &&
- lib_test_cmp_graph --format=%s "$@"
-}
-
test_expect_success 'log --graph with merge fusing with its left and right neighbors' '
git checkout --orphan _p &&
test_commit A &&
@@ -21,7 +16,7 @@
git checkout _p && git merge --no-ff _r -m G &&
git checkout @^^ && git merge --no-ff _p -m H &&
- check_graph <<-\EOF
+ lib_test_check_graph <<-\EOF
* H
|\
| * G
@@ -49,7 +44,7 @@
git checkout 0_p && git merge --no-ff 0_s -m 0_G &&
git checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&
- check_graph <<-\EOF
+ lib_test_check_graph <<-\EOF
*-----. 0_H
|\ \ \ \
| | | | * 0_G
@@ -83,7 +78,7 @@
git checkout 1_p && git merge --no-ff 1_r -m 1_G &&
git checkout @^^ && git merge --no-ff 1_p -m 1_H &&
- check_graph <<-\EOF
+ lib_test_check_graph <<-\EOF
* 1_H
|\
| * 1_G
@@ -115,7 +110,7 @@
git checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&
git checkout 2_p && git merge --no-ff 2_s -m 2_K &&
- check_graph <<-\EOF
+ lib_test_check_graph <<-\EOF
* 2_K
|\
| * 2_J
@@ -151,7 +146,7 @@
git checkout 3_p && git merge --no-ff 3_r -m 3_H &&
git checkout @^^ && git merge --no-ff 3_p -m 3_J &&
- check_graph <<-\EOF
+ lib_test_check_graph <<-\EOF
* 3_J
|\
| * 3_H
@@ -182,7 +177,7 @@
git merge --no-ff 4_p -m 4_G &&
git checkout @^^ && git merge --no-ff 4_s -m 4_H &&
- check_graph --date-order <<-\EOF
+ lib_test_check_graph --date-order <<-\EOF
* 4_H
|\
| * 4_G
@@ -218,7 +213,7 @@
git checkout 5_r &&
git merge --no-ff 5_s -m 5_H &&
- check_graph <<-\EOF
+ lib_test_check_graph <<-\EOF
* 5_H
|\
| *-. 5_G
@@ -257,7 +252,7 @@
git checkout 6_1 &&
git merge --no-ff 6_2 -m 6_I &&
- check_graph 6_1 6_3 6_5 <<-\EOF
+ lib_test_check_graph 6_1 6_3 6_5 <<-\EOF
* 6_I
|\
| | * 6_H
@@ -334,7 +329,7 @@
git checkout -b M_7 7_1 &&
git merge --no-ff 7_2 7_3 -m 7_M4 &&
- check_graph M_1 M_3 M_5 M_7 <<-\EOF
+ lib_test_check_graph M_1 M_3 M_5 M_7 <<-\EOF
* 7_M1
|\
| | * 7_M2
@@ -371,7 +366,7 @@
'
test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' '
- check_graph --graph-lane-limit=2 M_7 <<-\EOF
+ lib_test_check_graph --graph-lane-limit=2 M_7 <<-\EOF
*-. 7_M4
|\ \
| | * 7_G
@@ -388,7 +383,7 @@
'
test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' '
- check_graph --graph-lane-limit=1 M_7 <<-\EOF
+ lib_test_check_graph --graph-lane-limit=1 M_7 <<-\EOF
*-~ 7_M4
|\~
| ~ 7_G
@@ -405,7 +400,7 @@
'
test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' '
- check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF
+ lib_test_check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF
* 7_M1
|\
| | * 7_M2
@@ -441,7 +436,7 @@
'
test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows first of 3 parent merge' '
- check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF
+ lib_test_check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF
* 7_M1
|\
| | * 7_M2
@@ -478,7 +473,7 @@
'
test_expect_success 'log --graph --graph-lane-limit=7 check if it shows all 3 parent merge' '
- check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF
+ lib_test_check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF
* 7_M1
|\
| | * 7_M2
diff --git a/t/t4218-log-graph-indentation.sh b/t/t4218-log-graph-indentation.sh
new file mode 100755
index 0000000..24dc9b4
--- /dev/null
+++ b/t/t4218-log-graph-indentation.sh
@@ -0,0 +1,596 @@
+#!/bin/sh
+
+test_description='git log --graph visual root indentations'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-log-graph.sh
+
+check_graph_with_description () {
+ cat >expect &&
+ lib_test_cmp_graph --format="%s%ndescription%nsecond-line" "$@"
+}
+
+create_orphan () {
+ git checkout --orphan "$1" &&
+ test_might_fail git rm -rf .
+}
+
+# disable commit-graph topo order to have the graph to render in different
+# ways (used in --first-parent tests to have multiple visual roots while a
+# column is active at the same time).
+unset_commit_graph () {
+ sane_unset GIT_TEST_COMMIT_GRAPH &&
+ rm -f .git/objects/info/commit-graph &&
+ rm -rf .git/objects/info/commit-graphs
+}
+
+test_expect_success 'single root commit is not indented' '
+ create_orphan _1 && test_commit 1_A &&
+ lib_test_check_graph _1 <<-\EOF
+ * 1_A
+ EOF
+'
+
+test_expect_success 'visual root indented before unrelated branch' '
+ create_orphan _2 && test_commit 2_A && test_commit 2_B &&
+ create_orphan _3 && test_commit 3_A &&
+ lib_test_check_graph _2 _3 <<-\EOF
+ * 3_A
+ * 2_B
+ * 2_A
+ EOF
+'
+
+test_expect_success 'visual root indentation with --left-right' '
+ lib_test_check_graph --left-right _2..._3 <<-\EOF
+ > 3_A
+ < 2_B
+ < 2_A
+ EOF
+'
+
+# A better case of why indentation is still needed with '--left-right' flag is
+# that unrelated branches can be on the same side, so it's needed to
+# differentiate visual roots on the same side.
+test_expect_success 'visual root indentation with --left-right having unrelated commits on the same side' '
+ lib_test_check_graph --left-right _2..._3 _1 <<-\EOF
+ > 3_A
+ < 2_B
+ \
+ < 2_A
+ > 1_A
+ EOF
+'
+
+test_expect_success 'visual root indents the description also' '
+ check_graph_with_description _2 _3 <<-\EOF
+ * 3_A
+ description
+ second-line
+ * 2_B
+ | description
+ | second-line
+ * 2_A
+ description
+ second-line
+ EOF
+'
+
+test_expect_success 'indented visual root parent gets connected to its child' '
+ create_orphan _4 && test_commit 4_A && test_commit 4_B &&
+ create_orphan _5 && test_commit 5_A && test_commit 5_B &&
+ lib_test_check_graph _4 _5 <<-\EOF
+ * 5_B
+ \
+ * 5_A
+ * 4_B
+ * 4_A
+ EOF
+'
+
+test_expect_success 'indented visual root parent gets connected to its child with description' '
+ check_graph_with_description _4 _5 <<-\EOF
+ * 5_B
+ | description
+ | second-line
+ \
+ * 5_A
+ description
+ second-line
+ * 4_B
+ | description
+ | second-line
+ * 4_A
+ description
+ second-line
+ EOF
+'
+
+test_expect_success 'visual roots cascade and last root does not' '
+ create_orphan _7 && test_commit 7_A && test_commit 7_B &&
+ create_orphan _8 && test_commit 8_A &&
+ create_orphan _9 && test_commit 9_A &&
+ create_orphan _10 && test_commit 10_A &&
+ lib_test_check_graph _7 _8 _9 _10 <<-\EOF
+ * 10_A
+ * 9_A
+ * 8_A
+ * 7_B
+ * 7_A
+ EOF
+'
+
+test_expect_success 'last root does not cascade' '
+ lib_test_check_graph _8 _9 _10 <<-\EOF
+ * 10_A
+ * 9_A
+ * 8_A
+ EOF
+'
+
+test_expect_success 'merge parents are roots between them but they do not indent' '
+ create_orphan _11 && test_commit 11_A &&
+ create_orphan _12 && test_commit 12_A &&
+ create_orphan _13 && test_commit 13_A &&
+ git checkout _11 &&
+ TREE=$(git write-tree) &&
+ MERGE=$(git commit-tree $TREE -p _11 -p _12 -p _13 -m 11_octopus) &&
+ git reset --hard $MERGE &&
+ lib_test_check_graph _11 <<-\EOF
+ *-. 11_octopus
+ |\ \
+ | | * 13_A
+ | * 12_A
+ * 11_A
+ EOF
+'
+
+# The last parent of a merge can be indented if nothing related to it needs to
+# be rendered after, if it's another visual root, merge parent must not get
+# indented but rather activate cascading.
+test_expect_success 'merge then unrelated visual root and unrelated branch' '
+ create_orphan _16 && test_commit 16_A && test_commit 16_B &&
+ create_orphan _17 && test_commit 17_A &&
+ create_orphan _18 && test_commit 18_A &&
+ create_orphan _19 && test_commit 19_A &&
+ create_orphan _20 && test_commit 20_A &&
+ git checkout _18 &&
+ TREE=$(git write-tree) &&
+ MERGE=$(git commit-tree $TREE -p _18 -p _19 -p _20 -m 18_octopus) &&
+ git reset --hard $MERGE &&
+ lib_test_check_graph _18 _17 _16 <<-\EOF
+ *-. 18_octopus
+ |\ \
+ | | * 20_A
+ | * 19_A
+ * 18_A
+ * 17_A
+ * 16_B
+ * 16_A
+ EOF
+'
+
+# The last commit root does not get indented, if the next thing after the root
+# merge parent is the last commit, indent the merge parent.
+test_expect_success 'merge then unrelated root indents merge parent' '
+ lib_test_check_graph _18 _17 <<-\EOF
+ *-. 18_octopus
+ |\ \
+ | | * 20_A
+ | * 19_A
+ \
+ * 18_A
+ * 17_A
+ EOF
+'
+
+test_expect_success 'merge then unrelated branch indents merge parent' '
+ lib_test_check_graph _18 _16 <<-\EOF
+ *-. 18_octopus
+ |\ \
+ | | * 20_A
+ | * 19_A
+ \
+ * 18_A
+ * 16_B
+ * 16_A
+ EOF
+'
+
+test_expect_success 'two-parent merge of orphans' '
+ create_orphan _21 && test_commit 21_A &&
+ create_orphan _22 && test_commit 22_A &&
+ git checkout _21 &&
+ TREE=$(git write-tree) &&
+ MERGE=$(git commit-tree $TREE -p _21 -p _22 -m 21_merge) &&
+ git reset --hard $MERGE &&
+ lib_test_check_graph _21 <<-\EOF
+ * 21_merge
+ |\
+ | * 22_A
+ * 21_A
+ EOF
+'
+
+test_expect_success 'commit with filtered parent becomes a visual root' '
+ create_orphan _23 &&
+ echo test >other.txt &&
+ git add other.txt &&
+ git commit -m "23_A" &&
+ echo test >foo.txt &&
+ git add foo.txt &&
+ git commit -m "23_B" &&
+ create_orphan _24 &&
+ echo test >foo.txt &&
+ git add foo.txt &&
+ git commit -m "24_A" &&
+ lib_test_check_graph _23 _24 -- foo.txt <<-\EOF
+ * 23_B
+ * 24_A
+ EOF
+'
+
+test_expect_success 'filtered parent cascading edge case' '
+ create_orphan _27 &&
+ echo test >foo.txt &&
+ git add foo.txt &&
+ test_tick &&
+ git commit -m "D (last)" &&
+
+ create_orphan _25 &&
+ echo test >other.txt &&
+ git add other.txt &&
+ test_tick &&
+ git commit -m "C-filtered" &&
+
+ echo test >foo.txt &&
+ git add foo.txt &&
+ test_tick &&
+ git commit -m "B (child of filtered)" &&
+
+ create_orphan _26 &&
+ echo test >foo.txt &&
+ git add foo.txt &&
+ test_tick &&
+ git commit -m "A (visual root)" &&
+
+ lib_test_check_graph _25 _26 _27 -- foo.txt <<-\EOF
+ * A (visual root)
+ * B (child of filtered)
+ * D (last)
+ EOF
+'
+
+test_expect_success 'multiple filtered parents in sequence' '
+ create_orphan _44 &&
+ echo a >other.txt && git add other.txt && git commit -m "44_F" &&
+ echo b >foo.txt && git add foo.txt && git commit -m "44_C" &&
+
+ create_orphan _45 &&
+ echo c >other.txt && git add other.txt && git commit -m "45_F" &&
+ echo d >foo.txt && git add foo.txt && git commit -m "45_C" &&
+
+ create_orphan _46 &&
+ echo e >foo.txt && git add foo.txt && git commit -m "46_A" &&
+
+ lib_test_check_graph _44 _45 _46 -- foo.txt <<-\EOF
+ * 44_C
+ * 45_C
+ * 46_A
+ EOF
+'
+
+# These tests prove why there is no need to have indentation for boundary
+# commits.
+#
+# Boundary commits rather than starting a column they 'inherit' the one of
+# its child so there will always be an edge that connects it removing the
+# ambiguity.
+test_expect_success 'unrelated boundaries are not ambiguous' '
+ create_orphan _28 && test_commit 28_A && test_commit 28_B &&
+ test_commit 28_C &&
+ create_orphan _29 && test_commit 29_A && test_commit 29_B &&
+ lib_test_check_graph --boundary 28_A.._28 29_A.._29 <<-\EOF
+ * 29_B
+ | * 28_C
+ | * 28_B
+ | o 28_A
+ o 29_A
+ EOF
+'
+
+# Same structure as t6016
+test_expect_success 'boundary commits big test' '
+ # 3 commits on branch _30
+ create_orphan _30 &&
+ test_commit 30_A &&
+ test_commit 30_B &&
+ test_commit 30_C &&
+
+ # 2 commits on branch _31, started from 30_A
+ git checkout -b _31 30_A &&
+ test_commit 31_A &&
+ test_commit 31_B &&
+
+ # 2 commits on branch _32, started from 30_B
+ git checkout -b _32 30_B &&
+ test_commit 32_A &&
+ test_commit 32_B &&
+
+ # Octopus merge _31 and _32 into -30
+ git checkout _30 &&
+ git merge _31 _32 -m 30_D &&
+ git tag 30_D &&
+ test_commit 30_E &&
+
+ # More commits on _32, then merge _32 into _30
+ git checkout _32 &&
+ test_commit 32_C &&
+ test_commit 32_D &&
+ git checkout _30 &&
+ git merge -s ours _32 -m 30_F &&
+ git tag 30_F &&
+ test_commit 30_G &&
+ lib_test_check_graph --boundary _30 _31 _32 ^32_C <<-\EOF
+ * 30_G
+ * 30_F
+ |\
+ | * 32_D
+ * | 30_E
+ | |
+ | \
+ *-. \ 30_D
+ |\ \ \
+ | * | | 31_B
+ | * | | 31_A
+ * | | | 30_C
+ o | | | 30_B
+ |/ / /
+ o / / 30_A
+ / /
+ | o 32_C
+ |/
+ o 32_B
+ EOF
+'
+
+# Filter by --first-parent and then forcing the filtered parents to be shown.
+test_expect_success '--first-parent flag with the filtered parents' '
+ (
+ unset_commit_graph &&
+ create_orphan _35 && test_commit 35_A && test_commit 35_B &&
+ create_orphan _36 && test_commit 36_A &&
+ create_orphan _37 && test_commit 37_A &&
+ git checkout _35 &&
+ TREE=$(git write-tree) &&
+ MERGE=$(git commit-tree $TREE -p _35 -p _36 -p _37 -m 35_octopus) &&
+ git reset --hard $MERGE &&
+ lib_test_check_graph --first-parent _35 _36 _37 <<-\EOF
+ * 35_octopus
+ | * 37_A
+ | * 36_A
+ * 35_B
+ * 35_A
+ EOF
+ )
+'
+
+test_expect_success '--first-parent with filtered parents but one has a child' '
+ (
+ unset_commit_graph &&
+ create_orphan _38 && test_commit 38_A && test_commit 38_B &&
+ create_orphan _39 && test_commit 39_A &&
+ create_orphan _40 && test_commit 40_A && test_commit 40_B &&
+ git checkout _38 &&
+ TREE=$(git write-tree) &&
+ MERGE=$(git commit-tree $TREE -p _38 -p _39 -p _40 -m 38_octopus) &&
+ git reset --hard $MERGE &&
+ lib_test_check_graph --first-parent _38 _39 _40 <<-\EOF
+ * 38_octopus
+ | * 40_B
+ | * 40_A
+ | * 39_A
+ * 38_B
+ * 38_A
+ EOF
+ )
+'
+
+test_expect_success '--first-parent with filtered parents but both have children' '
+ (
+ unset_commit_graph &&
+ create_orphan _41 && test_commit 41_A && test_commit 41_B &&
+ create_orphan _42 && test_commit 42_A && test_commit 42_B &&
+ create_orphan _43 && test_commit 43_A && test_commit 43_B &&
+ git checkout _41 &&
+ TREE=$(git write-tree) &&
+ MERGE=$(git commit-tree $TREE -p _41 -p _42 -p _43 -m 41_octopus) &&
+ git reset --hard $MERGE &&
+ lib_test_check_graph --first-parent _41 _42 _43 <<-\EOF
+ * 41_octopus
+ | * 43_B
+ | \
+ | * 43_A
+ | * 42_B
+ | * 42_A
+ * 41_B
+ * 41_A
+ EOF
+ )
+'
+
+test_expect_success 'two unrelated merges' '
+ create_orphan _50 && test_commit 50_A &&
+ git checkout -b _51 &&
+ test_commit 51_A && test_commit 51_B &&
+ git checkout _50 &&
+ git merge --no-ff _51 -m 50_B &&
+
+ create_orphan _52 && test_commit 52_A &&
+ git checkout -b _53 &&
+ test_commit 53_A && test_commit 53_B &&
+ git checkout _52 &&
+ git merge --no-ff _53 -m 52_B &&
+
+ lib_test_check_graph _52 _50 <<-\EOF
+ * 52_B
+ |\
+ | * 53_B
+ | * 53_A
+ |/
+ \
+ * 52_A
+ * 50_B
+ |\
+ | * 51_B
+ | * 51_A
+ |/
+ * 50_A
+ EOF
+'
+
+test_expect_success '--max-count treats the last visible commit as the last commit' '
+ lib_test_check_graph --max-count=2 _8 _9 _10 <<-\EOF
+ * 10_A
+ * 9_A
+ EOF
+'
+
+test_expect_success '--max-count=1 shows a single root without indentation' '
+ lib_test_check_graph --max-count=1 _8 _9 _10 <<-\EOF
+ * 10_A
+ EOF
+'
+
+test_expect_success '--max-count-oldest indents visual roots' '
+ lib_test_check_graph --max-count-oldest=3 _8 _9 _10 <<-\EOF
+ * 10_A
+ * 9_A
+ * 8_A
+ EOF
+'
+
+# when the graph commits are filtered with regex options like --author, the
+# commit parents do not come NULL so it is needed to check if the parents are
+# interesting.
+test_expect_success '--author skipped parent makes a visual root' '
+ create_orphan _55 &&
+ test_tick &&
+ git commit --allow-empty -m 55_A &&
+ create_orphan _54 &&
+ test_tick &&
+ git commit --allow-empty --author="Other <other@example.com>" -m 54_A &&
+ test_tick &&
+ git commit --allow-empty -m 54_B &&
+ test_tick &&
+ git commit --allow-empty -m 54_C &&
+ lib_test_check_graph --author="A U Thor" _54 _55 <<-\EOF
+ * 54_C
+ \
+ * 54_B
+ * 55_A
+ EOF
+'
+
+test_expect_success '--grep skipped parent makes a visual root' '
+ create_orphan _57 &&
+ test_tick &&
+ git commit --allow-empty -m 57_keep_A &&
+ create_orphan _56 &&
+ test_tick &&
+ git commit --allow-empty -m 56_skip &&
+ test_tick &&
+ git commit --allow-empty -m 56_keep_A &&
+ test_tick &&
+ git commit --allow-empty -m 56_keep_B &&
+ lib_test_check_graph --grep=keep _56 _57 <<-\EOF
+ * 56_keep_B
+ \
+ * 56_keep_A
+ * 57_keep_A
+ EOF
+'
+
+# The cascading wraps after 4 columns and when wraping (column % 4 == 0) if the
+# next is a non visual-root, force indentation to avoid an ambiguous graph
+# (commit 59_A is forcefully indented)
+test_expect_success 'visual root cascading gets wrapped after 4 columns' '
+ create_orphan _58 && test_commit 58_A && test_commit 58_B &&
+ create_orphan _59 && test_commit 59_A &&
+ create_orphan _60 && test_commit 60_A &&
+ create_orphan _61 && test_commit 61_A &&
+ create_orphan _62 && test_commit 62_A &&
+ create_orphan _63 && test_commit 63_A &&
+ create_orphan _64 && test_commit 64_A &&
+ create_orphan _65 && test_commit 65_A &&
+ create_orphan _66 && test_commit 66_A &&
+ create_orphan _67 && test_commit 67_A &&
+ lib_test_check_graph _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF
+ * 67_A
+ * 66_A
+ * 65_A
+ * 64_A
+ * 63_A
+ * 62_A
+ * 61_A
+ * 60_A
+ * 59_A
+ * 58_B
+ * 58_A
+ EOF
+'
+
+test_expect_success '--no-graph-indent disables indentation' '
+ lib_test_check_graph --no-graph-indent _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF
+ * 67_A
+ * 66_A
+ * 65_A
+ * 64_A
+ * 63_A
+ * 62_A
+ * 61_A
+ * 60_A
+ * 59_A
+ * 58_B
+ * 58_A
+ EOF
+'
+
+test_expect_success 'log.graphIndent config disables indentation' '
+ test_config log.graphIndent false &&
+ lib_test_check_graph _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF
+ * 67_A
+ * 66_A
+ * 65_A
+ * 64_A
+ * 63_A
+ * 62_A
+ * 61_A
+ * 60_A
+ * 59_A
+ * 58_B
+ * 58_A
+ EOF
+'
+
+test_expect_success '--graph-indent forces indentation when graph.indent is unset' '
+ test_config log.graphIndent false &&
+ lib_test_check_graph --graph-indent _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF
+ * 67_A
+ * 66_A
+ * 65_A
+ * 64_A
+ * 63_A
+ * 62_A
+ * 61_A
+ * 60_A
+ * 59_A
+ * 58_B
+ * 58_A
+ EOF
+'
+
+# log.graphIndent unset and no --option (which activates graph indentation) is
+# the default state.
+
+test_done
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index ed9896e..ca8a10f 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -310,4 +310,27 @@
verify_hook_input
'
+test_expect_success 'rebase with commits that become empty' '
+ cat >todo <<-\EOF &&
+ pick H
+ pick E
+ fixup I
+ fixup H
+ pick G
+ pick I
+ EOF
+ (
+ set_replace_editor todo &&
+ git rebase -i --empty=drop A A
+ ) &&
+ echo rebase >expected.args &&
+ cat >expected.data <<-EOF &&
+ $(git rev-parse H) $(git rev-parse HEAD~2)
+ $(git rev-parse E) $(git rev-parse HEAD~1)
+ $(git rev-parse I) $(git rev-parse HEAD~1)
+ $(git rev-parse G) $(git rev-parse HEAD)
+ EOF
+ verify_hook_input
+'
+
test_done
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 9c2f140..5fbfcb0 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -1471,7 +1471,8 @@
test_when_finished "rm -f .git/config.lock" &&
git config --get-all remote.someremote.url >expect &&
>.git/config.lock &&
- test_must_fail git remote set-url someremote baz &&
+ test_must_fail git -c core.configLockTimeout=0 \
+ remote set-url someremote baz &&
git config --get-all remote.someremote.url >actual &&
cmp expect actual
'
diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh
index 108eb30..9a575aa 100755
--- a/t/t5701-git-serve.sh
+++ b/t/t5701-git-serve.sh
@@ -7,6 +7,8 @@
. ./test-lib.sh
+unknown_oid=$(printf "test" | git hash-object --stdin)
+
test_expect_success 'setup to generate files with expected content' '
printf "agent=git/%s" "$(git version | cut -d" " -f3)" >agent_capability &&
@@ -364,6 +366,67 @@
test_cmp expect actual
'
+test_expect_success 'bare OID request' '
+ test_config transfer.advertiseObjectInfo true &&
+
+ test-tool pkt-line pack >in <<-EOF &&
+ command=object-info
+ object-format=$(test_oid algo)
+ 0001
+ oid $(git rev-parse two:two.t)
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse two:two.t)
+ 0000
+ EOF
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'object-info with bare unrecognized OID' '
+ test_config transfer.advertiseObjectInfo true &&
+
+ test-tool pkt-line pack >in <<-EOF &&
+ command=object-info
+ object-format=$(test_oid algo)
+ 0001
+ oid $unknown_oid
+ 0000
+ EOF
+
+ printf "%s \n" "$unknown_oid" >expect &&
+ printf "0000\n" >>expect &&
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'object-info with size for unrecognized OID' '
+ test_config transfer.advertiseObjectInfo true &&
+
+ test-tool pkt-line pack >in <<-EOF &&
+ command=object-info
+ object-format=$(test_oid algo)
+ 0001
+ size
+ oid $unknown_oid
+ 0000
+ EOF
+
+ printf "size\n" >expect &&
+ printf "%s \n" "$unknown_oid" >>expect &&
+ printf "0000\n" >>expect &&
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'test capability advertisement with uploadpack.advertiseBundleURIs' '
test_config uploadpack.advertiseBundleURIs true &&
diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh
index 54b0a6f..e0d9c3c 100755
--- a/t/t6016-rev-list-graph-simplify-history.sh
+++ b/t/t6016-rev-list-graph-simplify-history.sh
@@ -13,11 +13,6 @@
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-log-graph.sh
-check_graph () {
- cat >expect &&
- lib_test_cmp_graph --format=%s "$@"
-}
-
test_expect_success 'set up rev-list --graph test' '
# 3 commits on branch A
test_commit A1 foo.txt &&
@@ -54,7 +49,7 @@
'
test_expect_success '--graph --all' '
- check_graph --all <<-\EOF
+ lib_test_check_graph --all <<-\EOF
* A7
* A6
|\
@@ -82,7 +77,7 @@
# that undecorated merges are interesting, even with --simplify-by-decoration
test_expect_success '--graph --simplify-by-decoration' '
git tag -d A4 &&
- check_graph --all --simplify-by-decoration <<-\EOF
+ lib_test_check_graph --all --simplify-by-decoration <<-\EOF
* A7
* A6
|\
@@ -114,7 +109,7 @@
# Graph with branch B simplified away
test_expect_success '--graph --simplify-by-decoration prune branch B' '
- check_graph --simplify-by-decoration --all <<-\EOF
+ lib_test_check_graph --simplify-by-decoration --all <<-\EOF
* A7
* A6
|\
@@ -133,7 +128,7 @@
'
test_expect_success '--graph --full-history -- bar.txt' '
- check_graph --full-history --all -- bar.txt <<-\EOF
+ lib_test_check_graph --full-history --all -- bar.txt <<-\EOF
* A7
* A6
|\
@@ -148,7 +143,7 @@
'
test_expect_success '--graph --full-history --simplify-merges -- bar.txt' '
- check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF
+ lib_test_check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF
* A7
* A6
|\
@@ -161,7 +156,7 @@
'
test_expect_success '--graph -- bar.txt' '
- check_graph --all -- bar.txt <<-\EOF
+ lib_test_check_graph --all -- bar.txt <<-\EOF
* A7
* A5
* A3
@@ -172,7 +167,7 @@
'
test_expect_success '--graph --sparse -- bar.txt' '
- check_graph --sparse --all -- bar.txt <<-\EOF
+ lib_test_check_graph --sparse --all -- bar.txt <<-\EOF
* A7
* A6
* A5
@@ -189,7 +184,7 @@
'
test_expect_success '--graph ^C4' '
- check_graph --all ^C4 <<-\EOF
+ lib_test_check_graph --all ^C4 <<-\EOF
* A7
* A6
* A5
@@ -202,7 +197,7 @@
'
test_expect_success '--graph ^C3' '
- check_graph --all ^C3 <<-\EOF
+ lib_test_check_graph --all ^C3 <<-\EOF
* A7
* A6
|\
@@ -220,7 +215,7 @@
# that important, but this test depends on it. If the ordering ever changes
# in the code, we'll need to update this test.
test_expect_success '--graph --boundary ^C3' '
- check_graph --boundary --all ^C3 <<-\EOF
+ lib_test_check_graph --boundary --all ^C3 <<-\EOF
* A7
* A6
|\
diff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh
index 4821b90..32284f1 100755
--- a/t/t6017-rev-list-stdin.sh
+++ b/t/t6017-rev-list-stdin.sh
@@ -148,4 +148,22 @@
test_cmp expect actual
'
+test_expect_success '--no-walk filters by path (single commit, match)' '
+ git rev-parse side-1 >expect &&
+ git rev-list --no-walk side-1 -- file-1 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-walk filters by path (single commit, no match)' '
+ git rev-list --no-walk side-2 -- file-1 >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success '--no-walk with pathspec exclusion' '
+ git rev-parse side-3 side-2 >expect &&
+ git rev-parse side-1 side-2 side-3 >input &&
+ git rev-list --stdin --no-walk -- ":!file-1" <input >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh
index e06feb0..72b27c8 100755
--- a/t/t6301-for-each-ref-errors.sh
+++ b/t/t6301-for-each-ref-errors.sh
@@ -52,6 +52,28 @@
test_must_be_empty brief-err
'
+test_expect_success 'missing ancestors are reported by contains filters' '
+ test_when_finished "git update-ref -d refs/heads/missing-parent" &&
+ {
+ echo "tree $(git rev-parse HEAD^{tree})" &&
+ echo "parent $MISSING" &&
+ git cat-file commit HEAD |
+ sed -n -e "/^author /p" -e "/^committer /p" &&
+ echo &&
+ echo "missing parent"
+ } >commit &&
+ broken=$(git hash-object -t commit -w commit) &&
+ git update-ref refs/heads/missing-parent "$broken" &&
+ for option in --contains --no-contains
+ do
+ test_must_fail git for-each-ref "$option=HEAD" \
+ refs/heads/missing-parent >out 2>err &&
+ test_must_be_empty out &&
+ test_grep "parse commit $MISSING" err ||
+ return 1
+ done
+'
+
test_expect_success 'ahead-behind requires an argument' '
test_must_fail git for-each-ref \
--format="%(ahead-behind)" 2>err &&
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 2ad3004..8c795d7 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1611,6 +1611,24 @@
test_cmp expected actual
'
+test_expect_success 'tag --contains rejects cyclic replacement histories' '
+ first=$(git rev-parse HEAD~2) &&
+ second=$(git rev-parse HEAD~) &&
+ third=$(git rev-parse HEAD) &&
+ test_when_finished "
+ git replace -d $first &&
+ git replace -d $third &&
+ git tag -d cycle-a cycle-b
+ " &&
+ git tag cycle-a "$first" &&
+ git tag cycle-b "$third" &&
+ git replace --graft "$first" "$third" "$second" &&
+ git replace --graft "$third" "$first" &&
+ test_must_fail git tag --contains="$second" --list "cycle-*" \
+ >/dev/null 2>err &&
+ test_grep "fatal: commit ancestry contains a cycle" err
+'
+
# other ways of specifying the commit
test_expect_success 'checking that first commit is in all tags (tag)' '
cat >expected <<-\EOF &&
diff --git a/t/t7614-merge-signoff.sh b/t/t7614-merge-signoff.sh
index fee258d..e58bf07 100755
--- a/t/t7614-merge-signoff.sh
+++ b/t/t7614-merge-signoff.sh
@@ -45,7 +45,8 @@
test_commit main-branch-2 file2 2 &&
git checkout other-branch &&
git merge main --signoff --no-edit &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ git cat-file commit HEAD >commit &&
+ sed -e "1,/^\$/d" commit >actual &&
test_cmp expected-signed actual
'
@@ -55,7 +56,8 @@
test_commit main-branch-3 file3 3 &&
git checkout other-branch &&
git merge main --no-edit &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ git cat-file commit HEAD >commit &&
+ sed -e "1,/^\$/d" commit >actual &&
test_cmp expected-unsigned actual
'
@@ -65,7 +67,8 @@
test_commit main-branch-4 file4 4 &&
git checkout other-branch &&
git merge main --no-edit --signoff --no-signoff &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ git cat-file commit HEAD >commit &&
+ sed -e "1,/^\$/d" commit >actual &&
test_cmp expected-unsigned actual
'
diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh
index 9587ce2..00cf077 100755
--- a/t/t9502-gitweb-standalone-parse-output.sh
+++ b/t/t9502-gitweb-standalone-parse-output.sh
@@ -115,6 +115,19 @@
'
test_debug 'cat gitweb.headers'
+test_expect_success 'commitdiff: index line shortens hashes with mode' '
+ old_blob=$(git rev-parse HEAD:foo) &&
+ old_short=$(git rev-parse --short=7 HEAD:foo) &&
+ echo changed >foo &&
+ git commit -am "change foo" &&
+ new_blob=$(git rev-parse HEAD:foo) &&
+ new_short=$(git rev-parse --short=7 HEAD:foo) &&
+ gitweb_run "p=.git;a=commitdiff;h=HEAD" &&
+ test_grep ">${old_short}</a>\\.\\.<a [^>]*>${new_short}</a> 100644<span class=\"info\"> (file)</span>" \
+ gitweb.body &&
+ test_grep ! "index ${old_blob}\\.\\.${new_blob} 100644" gitweb.body
+'
+
# ----------------------------------------------------------------------
# forks of projects
diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh
index 7614dfb..ee25701 100755
--- a/t/t9811-git-p4-label-import.sh
+++ b/t/t9811-git-p4-label-import.sh
@@ -62,9 +62,11 @@
cd main &&
git checkout TAG_F1_ONLY &&
- ! test -f f2 &&
+ test_path_is_missing f2 &&
git checkout TAG_WITH\$_SHELL_CHAR &&
- test -f f1 && test -f f2 && test -f file_with_\$metachar &&
+ test_path_is_file f1 &&
+ test_path_is_file f2 &&
+ test_path_is_file file_with_\$metachar &&
git show TAG_LONG_LABEL | grep -q "A Label second line"
)
@@ -102,11 +104,11 @@
git checkout TAG_F1_1 &&
ls &&
- test -f f1 &&
+ test_path_is_file f1 &&
git checkout TAG_F1_2 &&
ls &&
- test -f f1
+ test_path_is_file f1
)
'
@@ -135,9 +137,9 @@
p4 labels ... | grep LIGHTWEIGHT_TAG &&
p4 label -o GIT_TAG_1 | grep "tag created in git:xyzzy" &&
p4 sync ...@GIT_TAG_1 &&
- ! test -f main/f10 &&
+ test_path_is_missing main/f10 &&
p4 sync ...@GIT_TAG_2 &&
- test -f main/f10
+ test_path_is_file main/f10
)
'
@@ -168,9 +170,9 @@
cd "$cli" &&
p4 sync ... &&
p4 sync ...@GIT_TAG_ON_DELETED &&
- test -f main/deleted_file &&
+ test_path_is_file main/deleted_file &&
p4 sync ...@GIT_TAG_AFTER_DELETION &&
- ! test -f main/deleted_file &&
+ test_path_is_missing main/deleted_file &&
echo "checking label contents" &&
p4 label -o GIT_TAG_ON_DELETED | grep "tag on deleted file"
)
@@ -231,17 +233,25 @@
P4CLIENT=missing-revision &&
client_view "//depot/missing-revision/... //missing-revision/..." &&
cd "$cli" &&
- >f1 && p4 add f1 && p4 submit -d "start" &&
+ >f1 &&
+ p4 add f1 &&
+ p4 submit -d "start" &&
p4 tag -l TAG_S0 ... &&
- >f2 && p4 add f2 && p4 submit -d "second" &&
+ >f2 &&
+ p4 add f2 &&
+ p4 submit -d "second" &&
startrev=$(p4_head_revision //depot/missing-revision/...) &&
- >f3 && p4 add f3 && p4 submit -d "third" &&
+ >f3 &&
+ p4 add f3 &&
+ p4 submit -d "third" &&
- p4 edit f2 && date >f2 && p4 submit -d "change" f2 &&
+ p4 edit f2 &&
+ date >f2 &&
+ p4 submit -d "change" f2 &&
endrev=$(p4_head_revision //depot/missing-revision/...) &&
diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c
index 6844bfc..ddf2db5 100644
--- a/t/unit-tests/u-odb-inmemory.c
+++ b/t/unit-tests/u-odb-inmemory.c
@@ -1,5 +1,6 @@
#include "unit-test.h"
#include "hex.h"
+#include "object-file.h"
#include "odb/source-inmemory.h"
#include "odb/streaming.h"
#include "oidset.h"
@@ -36,6 +37,17 @@ static void cl_assert_object_info(struct odb_source_inmemory *source,
free(actual_content);
}
+static void cl_assert_write_object(struct odb_source_inmemory *source,
+ const char *content,
+ enum object_type type,
+ struct object_id *oid)
+{
+ size_t content_len = strlen(content);
+ hash_object_file(repo.hash_algo, content, content_len, type, oid);
+ cl_must_pass(odb_source_write_object(&source->base, content, content_len,
+ type, oid, NULL, NULL, 0));
+}
+
void test_odb_inmemory__initialize(void)
{
odb = odb_new(&repo, "", "");
@@ -78,8 +90,7 @@ void test_odb_inmemory__read_written_object(void)
const char data[] = "foobar";
struct object_id written_oid;
- cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
- OBJ_BLOB, &written_oid, NULL, 0));
+ cl_assert_write_object(source, data, OBJ_BLOB, &written_oid);
cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
@@ -94,8 +105,7 @@ void test_odb_inmemory__read_stream_object(void)
const char data[] = "foobar";
char buf[3] = { 0 };
- cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
- OBJ_BLOB, &written_oid, NULL, 0));
+ cl_assert_write_object(source, data, OBJ_BLOB, &written_oid);
cl_must_pass(odb_source_read_object_stream(&stream, &source->base,
&written_oid));
@@ -141,8 +151,7 @@ void test_odb_inmemory__for_each_object(void)
strbuf_reset(&buf);
strbuf_addf(&buf, "%d", i);
- cl_must_pass(odb_source_write_object(&source->base, buf.buf, buf.len,
- OBJ_BLOB, &written_oid, NULL, 0));
+ cl_assert_write_object(source, buf.buf, OBJ_BLOB, &written_oid);
cl_must_pass(oidset_insert(&expected_oids, &written_oid));
}
@@ -174,12 +183,9 @@ void test_odb_inmemory__for_each_object_can_abort_iteration(void)
struct object_id written_oid;
unsigned counter = 0;
- cl_must_pass(odb_source_write_object(&source->base, "1", 1,
- OBJ_BLOB, &written_oid, NULL, 0));
- cl_must_pass(odb_source_write_object(&source->base, "2", 1,
- OBJ_BLOB, &written_oid, NULL, 0));
- cl_must_pass(odb_source_write_object(&source->base, "3", 1,
- OBJ_BLOB, &written_oid, NULL, 0));
+ cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid);
+ cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid);
+ cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid);
cl_assert_equal_i(odb_source_for_each_object(&source->base, NULL,
abort_after_two_objects,
@@ -199,12 +205,9 @@ void test_odb_inmemory__count_objects(void)
cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
cl_assert_equal_u(count, 0);
- cl_must_pass(odb_source_write_object(&source->base, "1", 1,
- OBJ_BLOB, &written_oid, NULL, 0));
- cl_must_pass(odb_source_write_object(&source->base, "2", 1,
- OBJ_BLOB, &written_oid, NULL, 0));
- cl_must_pass(odb_source_write_object(&source->base, "3", 1,
- OBJ_BLOB, &written_oid, NULL, 0));
+ cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid);
+ cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid);
+ cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid);
cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
cl_assert_equal_u(count, 3);
@@ -228,8 +231,7 @@ void test_odb_inmemory__find_abbrev_len(void)
*
* With only one blob written we expect a length of 4.
*/
- cl_must_pass(odb_source_write_object(&source->base, "368317", strlen("368317"),
- OBJ_BLOB, &oid1, NULL, 0));
+ cl_assert_write_object(source, "368317", OBJ_BLOB, &oid1);
cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
&abbrev_len));
cl_assert_equal_u(abbrev_len, 4);
@@ -238,8 +240,7 @@ void test_odb_inmemory__find_abbrev_len(void)
* With both objects present, the shared 10-character prefix means we
* need at least 11 characters to uniquely identify either object.
*/
- cl_must_pass(odb_source_write_object(&source->base, "514796", strlen("514796"),
- OBJ_BLOB, &oid2, NULL, 0));
+ cl_assert_write_object(source, "514796", OBJ_BLOB, &oid2);
cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
&abbrev_len));
cl_assert_equal_u(abbrev_len, 11);
@@ -255,13 +256,11 @@ void test_odb_inmemory__freshen_object(void)
const char *end;
cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
- cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid), 0);
+ cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid, NULL), 0);
- cl_must_pass(odb_source_write_object(&source->base, "foobar",
- strlen("foobar"), OBJ_BLOB,
- &written_oid, NULL, 0));
+ cl_assert_write_object(source, "foobar", OBJ_BLOB, &written_oid);
cl_assert_equal_i(odb_source_freshen_object(&source->base,
- &written_oid), 1);
+ &written_oid, NULL), 1);
odb_source_free(&source->base);
}
diff --git a/tempfile.c b/tempfile.c
index f0fdf58..dc9ca4e 100644
--- a/tempfile.c
+++ b/tempfile.c
@@ -42,8 +42,6 @@
* file created by its parent.
*/
-#define USE_THE_REPOSITORY_VARIABLE
-
#include "git-compat-util.h"
#include "abspath.h"
#include "path.h"
@@ -134,7 +132,8 @@ static void deactivate_tempfile(struct tempfile *tempfile)
}
/* Make sure errno contains a meaningful value on error */
-struct tempfile *create_tempfile_mode(const char *path, int mode)
+struct tempfile *repo_create_tempfile_mode(struct repository *r,
+ const char *path, int mode)
{
struct tempfile *tempfile = new_tempfile();
@@ -150,7 +149,7 @@ struct tempfile *create_tempfile_mode(const char *path, int mode)
return NULL;
}
activate_tempfile(tempfile);
- if (adjust_shared_perm(the_repository, tempfile->filename.buf)) {
+ if (adjust_shared_perm(r, tempfile->filename.buf)) {
int save_errno = errno;
error("cannot fix permission bits on %s", tempfile->filename.buf);
delete_tempfile(&tempfile);
diff --git a/tempfile.h b/tempfile.h
index 2227a09..f571f3c 100644
--- a/tempfile.h
+++ b/tempfile.h
@@ -4,6 +4,8 @@
#include "list.h"
#include "strbuf.h"
+struct repository;
+
/*
* Handle temporary files.
*
@@ -92,11 +94,13 @@ struct tempfile {
* `core.sharedRepository`, so it is not guaranteed to have the given
* mode.
*/
-struct tempfile *create_tempfile_mode(const char *path, int mode);
+struct tempfile *repo_create_tempfile_mode(struct repository *r,
+ const char *path, int mode);
-static inline struct tempfile *create_tempfile(const char *path)
+static inline struct tempfile *repo_create_tempfile(struct repository *r,
+ const char *path)
{
- return create_tempfile_mode(path, 0666);
+ return repo_create_tempfile_mode(r, path, 0666);
}
/*
diff --git a/transport-helper.c b/transport-helper.c
index 80f90eb..b109fbd 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -162,7 +162,7 @@ static struct child_process *get_helper(struct transport *transport)
data->helper = helper;
data->no_disconnect_req = 0;
- refspec_init_fetch(&data->rs);
+ refspec_init_fetch(&data->rs, the_hash_algo);
/*
* Open the output as FILE* so strbuf_getline_*() family of
@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)
close(data->helper->out);
fclose(data->out);
res = finish_command(data->helper);
- FREE_AND_NULL(data->name);
FREE_AND_NULL(data->helper);
}
+ FREE_AND_NULL(data->name);
return res;
}
@@ -784,6 +784,15 @@ static int fetch_refs(struct transport *transport,
return -1;
}
+static int fetch_object_info_helper(struct transport *transport)
+{
+ get_helper(transport);
+ if (process_connect(transport, 0))
+ return transport->vtable->fetch_object_info(transport);
+
+ die(_("object-info requires protocol v2"));
+}
+
struct push_update_ref_state {
struct ref *hint;
struct ref_push_report *report;
@@ -1330,6 +1339,7 @@ static struct transport_vtable vtable = {
.get_refs_list = get_refs_list,
.get_bundle_uri = get_bundle_uri,
.fetch_refs = fetch_refs,
+ .fetch_object_info = fetch_object_info_helper,
.push_refs = push_refs,
.connect = connect_helper,
.disconnect = release_helper
diff --git a/transport-internal.h b/transport-internal.h
index 051f3ab..60db0be 100644
--- a/transport-internal.h
+++ b/transport-internal.h
@@ -45,6 +45,14 @@ struct transport_vtable {
**/
int (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);
+ /*
+ * Fetch object info (only size currently) from remote without
+ * downloading the objects.
+ *
+ * Uses object-info capability of v2 protocol.
+ */
+ int (*fetch_object_info)(struct transport *transport);
+
/**
* Push the objects and refs. Send the necessary objects, and
* then, for any refs where peer_ref is set and
diff --git a/transport.c b/transport.c
index 30a4ab2..42b0a2d 100644
--- a/transport.c
+++ b/transport.c
@@ -9,6 +9,7 @@
#include "hook.h"
#include "pkt-line.h"
#include "fetch-pack.h"
+#include "fetch-object-info.h"
#include "remote.h"
#include "connect.h"
#include "send-pack.h"
@@ -432,6 +433,47 @@ static int get_bundle_uri(struct transport *transport)
transport->bundles, stateless_rpc);
}
+static int fetch_object_info_via_pack(struct transport *transport)
+{
+ int ret = 0;
+ struct git_transport_data *data = transport->data;
+ struct packet_reader reader;
+ struct object_info_args args = { 0 };
+
+ args.server_options = transport->server_options;
+ args.oids = transport->smart_options->object_info_oids;
+ args.object_info_options = transport->smart_options->object_info_options;
+
+ connect_setup(transport, 0);
+ packet_reader_init(&reader, data->fd[0], NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ data->version = discover_version(&reader);
+ transport->hash_algo = reader.hash_algo;
+
+ ret = fetch_object_info(data->version, &args, &reader,
+ data->options.object_info_data,
+ transport->stateless_rpc, data->fd[1]);
+
+ close(data->fd[0]);
+ if (data->fd[1] >= 0)
+ close(data->fd[1]);
+ if (finish_connect(data->conn))
+ ret = -1;
+ data->conn = NULL;
+
+ return ret;
+}
+
+int transport_fetch_object_info(struct transport *transport)
+{
+ if (!transport->vtable->fetch_object_info)
+ die(_("remote does not support object-info"));
+ return transport->vtable->fetch_object_info(transport);
+}
+
static int fetch_refs_via_pack(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
@@ -1004,6 +1046,7 @@ static struct transport_vtable taken_over_vtable = {
.get_refs_list = get_refs_via_connect,
.get_bundle_uri = get_bundle_uri,
.fetch_refs = fetch_refs_via_pack,
+ .fetch_object_info = fetch_object_info_via_pack,
.push_refs = git_transport_push,
.disconnect = disconnect_git
};
@@ -1169,6 +1212,7 @@ static struct transport_vtable builtin_smart_vtable = {
.get_refs_list = get_refs_via_connect,
.get_bundle_uri = get_bundle_uri,
.fetch_refs = fetch_refs_via_pack,
+ .fetch_object_info = fetch_object_info_via_pack,
.push_refs = git_transport_push,
.connect = connect_git,
.disconnect = disconnect_git
diff --git a/transport.h b/transport.h
index 7e5867c..a7869d1 100644
--- a/transport.h
+++ b/transport.h
@@ -55,6 +55,10 @@ struct git_transport_options {
* common commits to this oidset instead of fetching any packfiles.
*/
struct oidset *acked_commits;
+
+ struct oid_array *object_info_oids;
+ struct object_info *object_info_data;
+ struct string_list *object_info_options;
};
enum transport_family {
@@ -310,6 +314,11 @@ const struct git_hash_algo *transport_get_hash_algo(struct transport *transport)
int transport_fetch_refs(struct transport *transport, struct ref *refs);
/*
+ * Fetch the object info from remote
+ */
+int transport_fetch_object_info(struct transport *transport);
+
+/*
* If this flag is set, unlocking will avoid to call non-async-signal-safe
* functions. This will necessarily leave behind some data structures which
* cannot be cleaned up.
diff --git a/userdiff.c b/userdiff.c
index b5412e6..7129bf1 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -362,6 +362,16 @@ PATTERNS("scheme",
"\\|([^|\\\\]|\\\\.)*\\|"
/* All other words should be delimited by spaces or parentheses. */
"|([^][)(}{ \t])+"),
+PATTERNS("swift",
+ "^[ \t]*((@[A-Za-z_][A-Za-z0-9_]*(\\([^()]*\\))?[ \t]+)*([a-z]+[ \t]+)*(func|init|deinit|subscript|class|struct|enum|protocol|extension|actor)[ \t(?!<].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ /* hexadecimal, octal, and binary literals */
+ "|0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+"
+ /* integers and floating-point numbers */
+ "|[0-9][0-9_]*([.][0-9_]+)?([eE][-+]?[0-9]+)?"
+ /* unary and binary operators */
+ "|[-+*/%<>=!&|^~?]=|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"),
PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
"\\\\[a-zA-Z@]+|\\\\.|([a-zA-Z0-9]|[^\x01-\x7f])+"),
{ .name = "default", .binary = -1 },
diff --git a/worktree.c b/worktree.c
index 3012582..cbf9532 100644
--- a/worktree.c
+++ b/worktree.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
@@ -111,34 +110,36 @@ static int is_main_worktree_bare(struct repository *repo)
/**
* get the main worktree
*/
-static struct worktree *get_main_worktree(int skip_reading_head)
+static struct worktree *get_main_worktree(struct repository *repo,
+ int skip_reading_head)
{
struct worktree *worktree = NULL;
struct strbuf worktree_path = STRBUF_INIT;
- strbuf_add_real_path(&worktree_path, repo_get_common_dir(the_repository));
+ strbuf_add_real_path(&worktree_path, repo_get_common_dir(repo));
strbuf_strip_suffix(&worktree_path, "/.git");
CALLOC_ARRAY(worktree, 1);
- worktree->repo = the_repository;
+ worktree->repo = repo;
worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->is_current = is_current_worktree(worktree);
- worktree->is_bare = (the_repository->bare_cfg == 1) ||
- is_bare_repository(the_repository) ||
+ worktree->is_bare = (repo->bare_cfg == 1) ||
+ is_bare_repository(repo) ||
/*
* When in a secondary worktree we have to also verify if the main
* worktree is bare in $commondir/config.worktree.
* This check is unnecessary if we're currently in the main worktree,
* as prior checks already consulted all configs of the current worktree.
*/
- (!worktree->is_current && is_main_worktree_bare(the_repository));
+ (!worktree->is_current && is_main_worktree_bare(repo));
if (!skip_reading_head)
add_head_info(worktree);
return worktree;
}
-struct worktree *get_linked_worktree(const char *id,
+struct worktree *get_linked_worktree(struct repository *repo,
+ const char *id,
int skip_reading_head)
{
struct worktree *worktree = NULL;
@@ -148,7 +149,7 @@ struct worktree *get_linked_worktree(const char *id,
if (!id)
die("Missing linked worktree name");
- repo_common_path_append(the_repository, &path, "worktrees/%s/gitdir", id);
+ repo_common_path_append(repo, &path, "worktrees/%s/gitdir", id);
if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
/* invalid gitdir file */
goto done;
@@ -162,7 +163,7 @@ struct worktree *get_linked_worktree(const char *id,
}
CALLOC_ARRAY(worktree, 1);
- worktree->repo = the_repository;
+ worktree->repo = repo;
worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->id = xstrdup(id);
worktree->is_current = is_current_worktree(worktree);
@@ -182,7 +183,8 @@ struct worktree *get_linked_worktree(const char *id,
* retrieving worktree metadata that could be used when the worktree is known
* to not be in a healthy state, e.g. when creating or repairing it.
*/
-static struct worktree **get_worktrees_internal(int skip_reading_head)
+static struct worktree **get_worktrees_internal(struct repository *repo,
+ int skip_reading_head)
{
struct worktree **list = NULL;
struct strbuf path = STRBUF_INIT;
@@ -192,16 +194,16 @@ static struct worktree **get_worktrees_internal(int skip_reading_head)
ALLOC_ARRAY(list, alloc);
- list[counter++] = get_main_worktree(skip_reading_head);
+ list[counter++] = get_main_worktree(repo, skip_reading_head);
- strbuf_addf(&path, "%s/worktrees", repo_get_common_dir(the_repository));
+ strbuf_addf(&path, "%s/worktrees", repo_get_common_dir(repo));
dir = opendir(path.buf);
strbuf_release(&path);
if (dir) {
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct worktree *linked = NULL;
- if ((linked = get_linked_worktree(d->d_name, skip_reading_head))) {
+ if ((linked = get_linked_worktree(repo, d->d_name, skip_reading_head))) {
ALLOC_GROW(list, counter + 1, alloc);
list[counter++] = linked;
}
@@ -214,14 +216,14 @@ static struct worktree **get_worktrees_internal(int skip_reading_head)
return list;
}
-struct worktree **get_worktrees(void)
+struct worktree **get_worktrees(struct repository *repo)
{
- return get_worktrees_internal(0);
+ return get_worktrees_internal(repo, 0);
}
-struct worktree **get_worktrees_without_reading_head(void)
+struct worktree **get_worktrees_without_reading_head(struct repository *repo)
{
- return get_worktrees_internal(1);
+ return get_worktrees_internal(repo, 1);
}
char *get_worktree_git_dir(const struct worktree *wt)
@@ -334,7 +336,7 @@ const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire)
if (wt->prune_reason_valid)
return wt->prune_reason;
- if (should_prune_worktree(wt->id, &reason, &path, expire))
+ if (should_prune_worktree(wt->repo, wt->id, &reason, &path, expire))
wt->prune_reason = strbuf_detach(&reason, NULL);
wt->prune_reason_valid = 1;
@@ -392,7 +394,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
if (!is_absolute_path(wt->path)) {
strbuf_addf_gently(errmsg,
_("'%s' file does not contain absolute path to the working tree location"),
- repo_common_path_replace(the_repository, &buf, "worktrees/%s/gitdir", wt->id));
+ repo_common_path_replace(wt->repo, &buf, "worktrees/%s/gitdir", wt->id));
goto done;
}
@@ -414,12 +416,12 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
goto done;
}
- strbuf_realpath(&realpath, repo_common_path_replace(the_repository, &buf, "worktrees/%s", wt->id), 1);
+ strbuf_realpath(&realpath, repo_common_path_replace(wt->repo, &buf, "worktrees/%s", wt->id), 1);
ret = fspathcmp(path, realpath.buf);
if (ret)
strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
- wt->path, repo_common_path_replace(the_repository, &buf,
+ wt->path, repo_common_path_replace(wt->repo, &buf,
"worktrees/%s", wt->id));
done:
free(path);
@@ -440,12 +442,13 @@ void update_worktree_location(struct worktree *wt, const char *path_,
if (is_main_worktree(wt))
BUG("can't relocate main worktree");
- wt_gitdir = repo_common_path(the_repository, "worktrees/%s/gitdir", wt->id);
+ wt_gitdir = repo_common_path(wt->repo, "worktrees/%s/gitdir", wt->id);
strbuf_realpath(&gitdir, wt_gitdir, 1);
strbuf_realpath(&path, path_, 1);
strbuf_addf(&dotgit, "%s/.git", path.buf);
if (fspathcmp(wt->path, path.buf)) {
- write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths);
+ write_worktree_linking_files(wt->repo, dotgit.buf,
+ gitdir.buf, use_relative_paths);
free(wt->path);
wt->path = strbuf_detach(&path, NULL);
@@ -533,7 +536,8 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
return NULL;
}
-int submodule_uses_worktrees(const char *path)
+int submodule_uses_worktrees(struct repository *repo,
+ const char *path)
{
char *submodule_gitdir;
struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
@@ -542,7 +546,7 @@ int submodule_uses_worktrees(const char *path)
int ret = 0;
struct repository_format format = REPOSITORY_FORMAT_INIT;
- submodule_gitdir = repo_submodule_path(the_repository,
+ submodule_gitdir = repo_submodule_path(repo,
path, "%s", "");
if (!submodule_gitdir)
return 0;
@@ -595,13 +599,14 @@ void strbuf_worktree_ref(const struct worktree *wt,
strbuf_addstr(sb, refname);
}
-int other_head_refs(refs_for_each_cb fn, void *cb_data)
+int other_head_refs(struct repository *repo,
+ refs_for_each_cb fn, void *cb_data)
{
struct worktree **worktrees, **p;
struct strbuf refname = STRBUF_INIT;
int ret = 0;
- worktrees = get_worktrees();
+ worktrees = get_worktrees(repo);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
struct object_id oid;
@@ -612,7 +617,7 @@ int other_head_refs(refs_for_each_cb fn, void *cb_data)
strbuf_reset(&refname);
strbuf_worktree_ref(wt, &refname, "HEAD");
- if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ if (refs_resolve_ref_unsafe(get_main_ref_store(repo),
refname.buf,
RESOLVE_REF_READING,
&oid, &flag)) {
@@ -658,7 +663,7 @@ static void repair_gitfile(struct worktree *wt,
goto done;
}
- path = repo_common_path(the_repository, "worktrees/%s", wt->id);
+ path = repo_common_path(wt->repo, "worktrees/%s", wt->id);
strbuf_realpath(&repo, path, 1);
strbuf_addf(&dotgit, "%s/.git", wt->path);
strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
@@ -685,7 +690,8 @@ static void repair_gitfile(struct worktree *wt,
if (repair) {
fn(0, wt->path, repair, cb_data);
- write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths);
+ write_worktree_linking_files(wt->repo, dotgit.buf,
+ gitdir.buf, use_relative_paths);
}
done:
@@ -705,9 +711,10 @@ static void repair_noop(int iserr UNUSED,
/* nothing */
}
-void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths)
+void repair_worktrees(struct repository *repo, worktree_repair_fn fn,
+ void *cb_data, int use_relative_paths)
{
- struct worktree **worktrees = get_worktrees_internal(1);
+ struct worktree **worktrees = get_worktrees_internal(repo, 1);
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
if (!fn)
@@ -727,7 +734,7 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
if (is_main_worktree(wt))
goto done;
- path = repo_common_path(the_repository, "worktrees/%s/gitdir", wt->id);
+ path = repo_common_path(wt->repo, "worktrees/%s/gitdir", wt->id);
strbuf_realpath(&gitdir, path, 1);
if (strbuf_read_file(&dotgit, gitdir.buf, 0) < 0)
@@ -743,16 +750,17 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
if (!file_exists(dotgit.buf))
goto done;
- write_worktree_linking_files(dotgit.buf, gitdir.buf, is_relative_path);
+ write_worktree_linking_files(wt->repo, dotgit.buf,
+ gitdir.buf, is_relative_path);
done:
strbuf_release(&gitdir);
strbuf_release(&dotgit);
free(path);
}
-void repair_worktrees_after_gitdir_move(const char *old_path)
+void repair_worktrees_after_gitdir_move(struct repository *repo, const char *old_path)
{
- struct worktree **worktrees = get_worktrees_internal(1);
+ struct worktree **worktrees = get_worktrees_internal(repo, 1);
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
for (; *wt; wt++)
@@ -760,7 +768,7 @@ void repair_worktrees_after_gitdir_move(const char *old_path)
free_worktrees(worktrees);
}
-static int is_main_worktree_path(const char *path)
+static int is_main_worktree_path(struct repository *repo, const char *path)
{
struct strbuf target = STRBUF_INIT;
struct strbuf maindir = STRBUF_INIT;
@@ -768,7 +776,7 @@ static int is_main_worktree_path(const char *path)
strbuf_add_real_path(&target, path);
strbuf_strip_suffix(&target, "/.git");
- strbuf_add_real_path(&maindir, repo_get_common_dir(the_repository));
+ strbuf_add_real_path(&maindir, repo_get_common_dir(repo));
strbuf_strip_suffix(&maindir, "/.git");
cmp = fspathcmp(maindir.buf, target.buf);
@@ -786,7 +794,9 @@ static int is_main_worktree_path(const char *path)
*
* Returns -1 on failure and strbuf.len on success.
*/
-static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
+static ssize_t infer_backlink(struct repository *repo,
+ const char *gitfile,
+ struct strbuf *inferred)
{
struct strbuf actual = STRBUF_INIT;
const char *id;
@@ -801,7 +811,7 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
id++; /* advance past '/' to point at <id> */
if (!*id)
goto error;
- repo_common_path_replace(the_repository, inferred, "worktrees/%s", id);
+ repo_common_path_replace(repo, inferred, "worktrees/%s", id);
if (!is_directory(inferred->buf))
goto error;
@@ -817,7 +827,8 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
* Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
* the worktree's path.
*/
-void repair_worktree_at_path(const char *path,
+void repair_worktree_at_path(struct repository *repo,
+ const char *path,
worktree_repair_fn fn, void *cb_data,
int use_relative_paths)
{
@@ -833,7 +844,7 @@ void repair_worktree_at_path(const char *path,
if (!fn)
fn = repair_noop;
- if (is_main_worktree_path(path))
+ if (is_main_worktree_path(repo, path))
goto done;
strbuf_addf(&dotgit, "%s/.git", path);
@@ -842,7 +853,7 @@ void repair_worktree_at_path(const char *path,
goto done;
}
- infer_backlink(dotgit.buf, &inferred_backlink);
+ infer_backlink(repo, dotgit.buf, &inferred_backlink);
strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
if (dotgit_contents) {
@@ -915,7 +926,8 @@ void repair_worktree_at_path(const char *path,
if (repair) {
fn(0, gitdir.buf, repair, cb_data);
- write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths);
+ write_worktree_linking_files(repo, dotgit.buf,
+ gitdir.buf, use_relative_paths);
}
done:
free(dotgit_contents);
@@ -926,12 +938,16 @@ void repair_worktree_at_path(const char *path,
strbuf_release(&dotgit);
}
-int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
+int should_prune_worktree(struct repository *repo,
+ const char *id,
+ struct strbuf *reason,
+ char **wtpath,
+ timestamp_t expire)
{
struct stat st;
struct strbuf dotgit = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;
- struct strbuf repo = STRBUF_INIT;
+ struct strbuf repo_path = STRBUF_INIT;
struct strbuf file = STRBUF_INIT;
char *path = NULL;
int rc = 0;
@@ -941,17 +957,17 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
*wtpath = NULL;
- path = repo_common_path(the_repository, "worktrees/%s", id);
- strbuf_realpath(&repo, path, 1);
+ path = repo_common_path(repo, "worktrees/%s", id);
+ strbuf_realpath(&repo_path, path, 1);
FREE_AND_NULL(path);
- strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
- if (!is_directory(repo.buf)) {
+ strbuf_addf(&gitdir, "%s/gitdir", repo_path.buf);
+ if (!is_directory(repo_path.buf)) {
strbuf_addstr(reason, _("not a valid directory"));
rc = 1;
goto done;
}
- strbuf_addf(&file, "%s/locked", repo.buf);
+ strbuf_addf(&file, "%s/locked", repo_path.buf);
if (file_exists(file.buf)) {
goto done;
}
@@ -995,12 +1011,12 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
if (is_absolute_path(path)) {
strbuf_addstr(&dotgit, path);
} else {
- strbuf_addf(&dotgit, "%s/%s", repo.buf, path);
+ strbuf_addf(&dotgit, "%s/%s", repo_path.buf, path);
strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0);
}
if (!file_exists(dotgit.buf)) {
strbuf_reset(&file);
- strbuf_addf(&file, "%s/index", repo.buf);
+ strbuf_addf(&file, "%s/index", repo_path.buf);
if (stat(file.buf, &st) || st.st_mtime <= expire) {
strbuf_addstr(reason, _("gitdir file points to non-existent location"));
rc = 1;
@@ -1012,17 +1028,18 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
free(path);
strbuf_release(&dotgit);
strbuf_release(&gitdir);
- strbuf_release(&repo);
+ strbuf_release(&repo_path);
strbuf_release(&file);
return rc;
}
-static int move_config_setting(const char *key, const char *value,
+static int move_config_setting(struct repository *repo,
+ const char *key, const char *value,
const char *from_file, const char *to_file)
{
- if (repo_config_set_in_file_gently(the_repository, to_file, key, NULL, value))
+ if (repo_config_set_in_file_gently(repo, to_file, key, NULL, value))
return error(_("unable to set %s in '%s'"), key, to_file);
- if (repo_config_set_in_file_gently(the_repository, from_file, key, NULL, NULL))
+ if (repo_config_set_in_file_gently(repo, from_file, key, NULL, NULL))
return error(_("unable to unset %s in '%s'"), key, from_file);
return 0;
}
@@ -1042,7 +1059,7 @@ int init_worktree_config(struct repository *r)
*/
if (r->repository_format_worktree_config)
return 0;
- if ((res = repo_config_set_gently(the_repository, "extensions.worktreeConfig", "true")))
+ if ((res = repo_config_set_gently(r, "extensions.worktreeConfig", "true")))
return error(_("failed to set extensions.worktreeConfig setting"));
common_config_file = xstrfmt("%s/config", r->commondir);
@@ -1058,7 +1075,7 @@ int init_worktree_config(struct repository *r)
* _could_ be negating a global core.bare=true.
*/
if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
- if ((res = move_config_setting("core.bare", "true",
+ if ((res = move_config_setting(r, "core.bare", "true",
common_config_file,
main_worktree_file)))
goto cleanup;
@@ -1070,7 +1087,7 @@ int init_worktree_config(struct repository *r)
* upgrade to worktree config.
*/
if (!git_configset_get_value(&cs, "core.worktree", &core_worktree, NULL)) {
- if ((res = move_config_setting("core.worktree", core_worktree,
+ if ((res = move_config_setting(r, "core.worktree", core_worktree,
common_config_file,
main_worktree_file)))
goto cleanup;
@@ -1089,37 +1106,38 @@ int init_worktree_config(struct repository *r)
return res;
}
-void write_worktree_linking_files(const char *dotgit, const char *gitdir,
+void write_worktree_linking_files(struct repository *repo,
+ const char *dotgit, const char *gitdir,
int use_relative_paths)
{
struct strbuf path = STRBUF_INIT;
- struct strbuf repo = STRBUF_INIT;
+ struct strbuf repo_path = STRBUF_INIT;
struct strbuf tmp = STRBUF_INIT;
strbuf_addstr(&path, dotgit);
strbuf_strip_suffix(&path, "/.git");
strbuf_realpath(&path, path.buf, 1);
- strbuf_addstr(&repo, gitdir);
- strbuf_strip_suffix(&repo, "/gitdir");
- strbuf_realpath(&repo, repo.buf, 1);
+ strbuf_addstr(&repo_path, gitdir);
+ strbuf_strip_suffix(&repo_path, "/gitdir");
+ strbuf_realpath(&repo_path, repo_path.buf, 1);
- if (use_relative_paths && !the_repository->repository_format_relative_worktrees) {
- if (upgrade_repository_format(the_repository, 1) < 0)
+ if (use_relative_paths && !repo->repository_format_relative_worktrees) {
+ if (upgrade_repository_format(repo, 1) < 0)
die(_("unable to upgrade repository format to support relative worktrees"));
- if (repo_config_set_gently(the_repository, "extensions.relativeWorktrees", "true"))
+ if (repo_config_set_gently(repo, "extensions.relativeWorktrees", "true"))
die(_("unable to set extensions.relativeWorktrees setting"));
- the_repository->repository_format_relative_worktrees = 1;
+ repo->repository_format_relative_worktrees = 1;
}
if (use_relative_paths) {
- write_file(gitdir, "%s/.git", relative_path(path.buf, repo.buf, &tmp));
- write_file(dotgit, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp));
+ write_file(gitdir, "%s/.git", relative_path(path.buf, repo_path.buf, &tmp));
+ write_file(dotgit, "gitdir: %s", relative_path(repo_path.buf, path.buf, &tmp));
} else {
write_file(gitdir, "%s/.git", path.buf);
- write_file(dotgit, "gitdir: %s", repo.buf);
+ write_file(dotgit, "gitdir: %s", repo_path.buf);
}
strbuf_release(&path);
- strbuf_release(&repo);
+ strbuf_release(&repo_path);
strbuf_release(&tmp);
}
diff --git a/worktree.h b/worktree.h
index 1075409..fbb2757 100644
--- a/worktree.h
+++ b/worktree.h
@@ -28,7 +28,7 @@ struct worktree {
* The caller is responsible for freeing the memory from the returned
* worktrees by calling free_worktrees().
*/
-struct worktree **get_worktrees(void);
+struct worktree **get_worktrees(struct repository *repo);
/*
* Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
@@ -36,7 +36,7 @@ struct worktree **get_worktrees(void);
* the HEAD ref. This is useful in contexts where it is assumed that the
* refdb may not be in a consistent state.
*/
-struct worktree **get_worktrees_without_reading_head(void);
+struct worktree **get_worktrees_without_reading_head(struct repository *repo);
/*
* Construct a struct worktree corresponding to repo->gitdir and
@@ -47,7 +47,7 @@ struct worktree *get_current_worktree(struct repository *repo);
/*
* Returns 1 if linked worktrees exist, 0 otherwise.
*/
-int submodule_uses_worktrees(const char *path);
+int submodule_uses_worktrees(struct repository *repo, const char *path);
/*
* Return git dir of the worktree. Note that the path may be relative.
@@ -76,7 +76,8 @@ struct worktree *find_worktree(struct worktree **list,
* Look up the worktree corresponding to `id`, or NULL of no such worktree
* exists.
*/
-struct worktree *get_linked_worktree(const char *id,
+struct worktree *get_linked_worktree(struct repository *repo,
+ const char *id,
int skip_reading_head);
/*
@@ -112,7 +113,8 @@ const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire);
* `expire` defines a grace period to prune the worktree when its path
* does not exist.
*/
-int should_prune_worktree(const char *id,
+int should_prune_worktree(struct repository *repo,
+ const char *id,
struct strbuf *reason,
char **wtpath,
timestamp_t expire);
@@ -142,12 +144,14 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
* function, if non-NULL, is called with the path of the worktree and a
* description of the repair or error, along with the callback user-data.
*/
-void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths);
+void repair_worktrees(struct repository *repo, worktree_repair_fn,
+ void *cb_data, int use_relative_paths);
/*
* Repair the linked worktrees after the gitdir has been moved.
*/
-void repair_worktrees_after_gitdir_move(const char *old_path);
+void repair_worktrees_after_gitdir_move(struct repository *repo,
+ const char *old_path);
/*
* Repair the linked worktree after the gitdir has been moved.
@@ -164,7 +168,9 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
* worktree and a description of the repair or error, along with the callback
* user-data.
*/
-void repair_worktree_at_path(const char *, worktree_repair_fn,
+void repair_worktree_at_path(struct repository *repo,
+ const char *path,
+ worktree_repair_fn fn,
void *cb_data, int use_relative_paths);
/*
@@ -196,7 +202,7 @@ int is_shared_symref(const struct worktree *wt,
* Similar to head_ref() for all HEADs _except_ one from the current
* worktree, which is covered by head_ref().
*/
-int other_head_refs(refs_for_each_cb fn, void *cb_data);
+int other_head_refs(struct repository *repo, refs_for_each_cb 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);
@@ -239,7 +245,8 @@ int init_worktree_config(struct repository *r);
* dotgit: "/path/to/foo/.git"
* gitdir: "/path/to/repo/worktrees/foo/gitdir"
*/
-void write_worktree_linking_files(const char *dotgit, const char *gitdir,
+void write_worktree_linking_files(struct repository *repo,
+ const char *dotgit, const char *gitdir,
int use_relative_paths);
#endif
diff --git a/wt-status.c b/wt-status.c
index 58461e0..57772c7 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -832,14 +832,16 @@ static void wt_status_collect_untracked(struct wt_status *s)
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
if (index_name_is_other(istate, ent->name, ent->len))
- string_list_insert(&s->untracked, ent->name);
+ string_list_append(&s->untracked, ent->name);
}
+ string_list_sort_u(&s->untracked, 0);
for (i = 0; i < dir.ignored_nr; i++) {
struct dir_entry *ent = dir.ignored[i];
if (index_name_is_other(istate, ent->name, ent->len))
- string_list_insert(&s->ignored, ent->name);
+ string_list_append(&s->ignored, ent->name);
}
+ string_list_sort_u(&s->ignored, 0);
dir_clear(&dir);