aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml13
-rw-r--r--Documentation/RelNotes/2.43.0.txt67
-rw-r--r--Documentation/config/rebase.txt6
-rw-r--r--Documentation/git-push.txt2
-rw-r--r--Documentation/git-rev-parse.txt10
-rw-r--r--Documentation/scalar.txt8
-rwxr-xr-xGIT-VERSION-GEN2
l---------RelNotes2
-rw-r--r--attr.c57
-rw-r--r--branch.c2
-rw-r--r--builtin/branch.c2
-rw-r--r--builtin/check-attr.c3
-rw-r--r--builtin/gc.c291
-rw-r--r--builtin/mv.c2
-rw-r--r--builtin/receive-pack.c6
-rw-r--r--builtin/repack.c62
-rw-r--r--builtin/worktree.c2
-rw-r--r--commit-graph.c38
-rw-r--r--compat/terminal.c10
-rw-r--r--contrib/credential/libsecret/git-credential-libsecret.c95
-rw-r--r--contrib/credential/wincred/git-credential-wincred.c20
-rwxr-xr-xcontrib/subtree/git-subtree.sh10
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh2
-rw-r--r--diff.c40
-rw-r--r--fetch-pack.c6
-rw-r--r--git-compat-util.h4
-rw-r--r--git-gui/Makefile21
-rw-r--r--git-gui/README.md2
-rwxr-xr-xgit-gui/git-gui.sh309
-rw-r--r--git-gui/lib/choose_repository.tcl27
-rw-r--r--git-gui/lib/shortcut.tcl31
-rw-r--r--parse-options.c69
-rw-r--r--range-diff.c11
-rw-r--r--scalar.c70
-rw-r--r--sequencer.c58
-rw-r--r--setup.c34
-rw-r--r--setup.h35
-rw-r--r--t/lib-credential.sh77
-rw-r--r--t/lib-gpg.sh1
-rw-r--r--t/lib-rebase.sh25
-rwxr-xr-xt/perf/p2000-sparse-operations.sh1
-rwxr-xr-xt/t0040-parse-options.sh44
-rwxr-xr-xt/t0301-credential-cache.sh1
-rwxr-xr-xt/t0303-credential-external.sh2
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh72
-rwxr-xr-xt/t1502-rev-parse-parseopt.sh131
-rw-r--r--t/t1502/.gitattributes1
-rw-r--r--t/t1502/optionspec-neg8
-rw-r--r--t/t1502/optionspec-neg.help12
-rwxr-xr-xt/t1502/optionspec.help36
-rwxr-xr-xt/t2400-worktree-add.sh3
-rwxr-xr-xt/t3200-branch.sh14
-rwxr-xr-xt/t3400-rebase.sh2
-rwxr-xr-xt/t3430-rebase-merges.sh11
-rwxr-xr-xt/t4015-diff-whitespace.sh39
-rwxr-xr-xt/t4040-whitespace-status.sh3
-rwxr-xr-xt/t5318-commit-graph.sh18
-rwxr-xr-xt/t5510-fetch.sh46
-rwxr-xr-xt/t5546-receive-limits.sh35
-rwxr-xr-xt/t6300-for-each-ref.sh5
-rwxr-xr-xt/t7900-maintenance.sh28
-rwxr-xr-xt/t9001-send-email.sh4
-rwxr-xr-xt/t9211-scalar-clone.sh12
-rw-r--r--upload-pack.c5
-rw-r--r--wrapper.c10
-rw-r--r--wrapper.h6
-rw-r--r--wt-status.c6
67 files changed, 1441 insertions, 646 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 079645b776..1b41278a7f 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -5,6 +5,19 @@ on: [push, pull_request]
env:
DEVELOPER: 1
+# If more than one workflow run is triggered for the very same commit hash
+# (which happens when multiple branches pointing to the same commit), only
+# the first one is allowed to run, the second will be kept in the "queued"
+# state. This allows a successful completion of the first run to be reused
+# in the second run via the `skip-if-redundant` logic in the `config` job.
+#
+# The only caveat is that if a workflow run is triggered for the same commit
+# hash that another run is already being held, that latter run will be
+# canceled. For more details about the `concurrency` attribute, see:
+# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency
+concurrency:
+ group: ${{ github.sha }}
+
jobs:
ci-config:
name: config
diff --git a/Documentation/RelNotes/2.43.0.txt b/Documentation/RelNotes/2.43.0.txt
new file mode 100644
index 0000000000..e04b18cbcb
--- /dev/null
+++ b/Documentation/RelNotes/2.43.0.txt
@@ -0,0 +1,67 @@
+Git v2.43 Release Notes
+=======================
+
+UI, Workflows & Features
+
+ * A message written in olden time prevented a branch from getting
+ checked out saying it is already checked out elsewhere, but these
+ days, we treat a branch that is being bisected or rebased just like
+ a branch that is checked out and protect it. Rephrase the message
+ to say that the branch is in use.
+
+ * Hourly and other schedule of "git maintenance" jobs are randomly
+ distributed now.
+
+ * "git cmd -h" learned to signal which options can be negated by
+ listing such options like "--[no-]opt".
+
+ * The way authentication related data other than passwords (e.g.
+ oath token and password expiration data) are stored in libsecret
+ keyrings has been rethought.
+
+ * Update two credential helpers to correctly match which credential
+ to erase; they dropped not the ones with stale password.
+
+ * Git GUI updates.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * "git check-attr" has been taught to work better with sparse-index.
+
+
+
+Fixes since v2.42
+-----------------
+
+ * Overly long label names used in the sequencer machinery are now
+ chopped to fit under filesystem limitation.
+ (merge ac300bda10 mp/rebase-label-length-limit later to maint).
+
+ * Scalar updates.
+ (merge f9a547d3a7 ds/scalar-updates later to maint).
+
+ * Tweak GitHub Actions CI so that pushing the same commit to multiple
+ branch tips at the same time will not waste building and testing
+ the same thing twice.
+ (merge 99fe06cbfd jc/ci-skip-same-commit later to maint).
+
+ * The commit-graph verification code that detects mixture of zero and
+ non-zero generation numbers has been updated.
+ (merge db6044d762 tb/commit-graph-verify-fix later to maint).
+
+ * "git diff -w --exit-code" with various options did not work
+ correctly, which is being addressed.
+ (merge a64f8b2595 jc/diff-exit-code-with-w-fixes later to maint).
+
+ * transfer.unpackLimit ought to be used as a fallback, but overrode
+ fetch.unpackLimit and receive.unpackLimit instead.
+ (merge f3d33f8cfe ts/unpacklimit-config-fix later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge fd3ba590d8 ws/git-push-doc-grammofix later to maint).
+ (merge 5f33a843de ds/upload-pack-error-sequence-fix later to maint).
+ (merge beaa1d952b jk/function-pointer-mismatches-fix later to maint).
+ (merge b46d806ea5 ob/t9001-indent-fix later to maint).
+ (merge fdc9914c28 ja/worktree-orphan later to maint).
+ (merge c2cbefc510 jc/mv-d-to-d-error-message-fix later to maint).
diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index afaf6dad99..9c248accec 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -77,3 +77,9 @@ rebase.rebaseMerges::
equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
command line, with or without an argument, overrides any
`rebase.rebaseMerges` configuration.
+
+rebase.maxLabelLength::
+ When generating label names from commit subjects, truncate the names to
+ this length. By default, the names are truncated to a little less than
+ `NAME_MAX` (to allow e.g. `.lock` files to be written for the
+ corresponding loose refs).
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 297927d866..5b4edaf4a8 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -37,7 +37,7 @@ the default `<refspec>` by consulting `remote.*.push` configuration,
and if it is not found, honors `push.default` configuration to decide
what to push (See linkgit:git-config[1] for the meaning of `push.default`).
-When neither the command-line nor the configuration specify what to
+When neither the command-line nor the configuration specifies what to
push, the default behavior is used, which corresponds to the `simple`
value for `push.default`: the current branch is pushed to the
corresponding upstream branch, but as a safety measure, the push is
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index f26a7591e3..6a4968f68a 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -398,7 +398,7 @@ some-command [<options>] <args>...
some-command does foo and bar!
--
-h,help show the help
+h,help! show the help
foo some nifty option --foo
bar= some cool option --bar with an argument
@@ -424,10 +424,10 @@ usage: some-command [<options>] <args>...
some-command does foo and bar!
-h, --help show the help
- --foo some nifty option --foo
- --bar ... some cool option --bar with an argument
- --baz <arg> another cool option --baz with a named argument
- --qux[=<path>] qux may take a path argument but has meaning by itself
+ --[no-]foo some nifty option --foo
+ --[no-]bar ... some cool option --bar with an argument
+ --[no-]baz <arg> another cool option --baz with a named argument
+ --[no-]qux[=<path>] qux may take a path argument but has meaning by itself
An option group Header
-C[...] option C with an optional argument
diff --git a/Documentation/scalar.txt b/Documentation/scalar.txt
index f33436c7f6..361f51a647 100644
--- a/Documentation/scalar.txt
+++ b/Documentation/scalar.txt
@@ -8,7 +8,8 @@ scalar - A tool for managing large Git repositories
SYNOPSIS
--------
[verse]
-scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<enlistment>]
+scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]
+ [--[no-]src] <url> [<enlistment>]
scalar list
scalar register [<enlistment>]
scalar unregister [<enlistment>]
@@ -80,6 +81,11 @@ remote-tracking branch for the branch this option was used for the initial
cloning. If the HEAD at the remote did not point at any branch when
`--single-branch` clone was made, no remote-tracking branch is created.
+--[no-]src::
+ By default, `scalar clone` places the cloned repository within a
+ `<entlistment>/src` directory. Use `--no-src` to place the cloned
+ repository directly in the `<enlistment>` directory.
+
--[no-]full-clone::
A sparse-checkout is initialized by default. This behavior can be
turned off via `--full-clone`.
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 2c8dae398f..9f0307d364 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.42.0
+DEF_VER=v2.42.GIT
LF='
'
diff --git a/RelNotes b/RelNotes
index 0527f30c2d..278cea33c4 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.42.0.txt \ No newline at end of file
+Documentation/RelNotes/2.43.0.txt \ No newline at end of file
diff --git a/attr.c b/attr.c
index ff0a3e7b61..71c84fbcf8 100644
--- a/attr.c
+++ b/attr.c
@@ -807,35 +807,56 @@ static struct attr_stack *read_attr_from_blob(struct index_state *istate,
static struct attr_stack *read_attr_from_index(struct index_state *istate,
const char *path, unsigned flags)
{
+ struct attr_stack *stack = NULL;
char *buf;
unsigned long size;
+ int sparse_dir_pos = -1;
if (!istate)
return NULL;
/*
- * The .gitattributes file only applies to files within its
- * parent directory. In the case of cone-mode sparse-checkout,
- * the .gitattributes file is sparse if and only if all paths
- * within that directory are also sparse. Thus, don't load the
- * .gitattributes file since it will not matter.
- *
- * In the case of a sparse index, it is critical that we don't go
- * looking for a .gitattributes file, as doing so would cause the
- * index to expand.
+ * When handling sparse-checkouts, .gitattributes files
+ * may reside within a sparse directory. We distinguish
+ * whether a path exists directly in the index or not by
+ * evaluating if 'pos' is negative.
+ * If 'pos' is negative, the path is not directly present
+ * in the index and is likely within a sparse directory.
+ * For paths not in the index, The absolute value of 'pos'
+ * minus 1 gives us the position where the path would be
+ * inserted in lexicographic order within the index.
+ * We then subtract another 1 from this value
+ * (sparse_dir_pos = -pos - 2) to find the position of the
+ * last index entry which is lexicographically smaller than
+ * the path. This would be the sparse directory containing
+ * the path. By identifying the sparse directory containing
+ * the path, we can correctly read the attributes specified
+ * in the .gitattributes file from the tree object of the
+ * sparse directory.
*/
- if (!path_in_cone_mode_sparse_checkout(path, istate))
- return NULL;
+ if (!path_in_cone_mode_sparse_checkout(path, istate)) {
+ int pos = index_name_pos_sparse(istate, path, strlen(path));
- buf = read_blob_data_from_index(istate, path, &size);
- if (!buf)
- return NULL;
- if (size >= ATTR_MAX_FILE_SIZE) {
- warning(_("ignoring overly large gitattributes blob '%s'"), path);
- return NULL;
+ if (pos < 0)
+ sparse_dir_pos = -pos - 2;
}
- return read_attr_from_buf(buf, path, flags);
+ if (sparse_dir_pos >= 0 &&
+ S_ISSPARSEDIR(istate->cache[sparse_dir_pos]->ce_mode) &&
+ !strncmp(istate->cache[sparse_dir_pos]->name, path, ce_namelen(istate->cache[sparse_dir_pos]))) {
+ const char *relative_path = path + ce_namelen(istate->cache[sparse_dir_pos]);
+ stack = read_attr_from_blob(istate, &istate->cache[sparse_dir_pos]->oid, relative_path, flags);
+ } else {
+ buf = read_blob_data_from_index(istate, path, &size);
+ if (!buf)
+ return NULL;
+ if (size >= ATTR_MAX_FILE_SIZE) {
+ warning(_("ignoring overly large gitattributes blob '%s'"), path);
+ return NULL;
+ }
+ stack = read_attr_from_buf(buf, path, flags);
+ }
+ return stack;
}
static struct attr_stack *read_attr(struct index_state *istate,
diff --git a/branch.c b/branch.c
index 3e4684f79f..06f7af9dd4 100644
--- a/branch.c
+++ b/branch.c
@@ -838,7 +838,7 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree)
if (is_shared_symref(worktrees[i], "HEAD", branch)) {
skip_prefix(branch, "refs/heads/", &branch);
- die(_("'%s' is already checked out at '%s'"),
+ die(_("'%s' is already used by worktree at '%s'"),
branch, worktrees[i]->path);
}
}
diff --git a/builtin/branch.c b/builtin/branch.c
index 08da650516..2ec190b14a 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -261,7 +261,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
const char *path;
if ((path = branch_checked_out(name))) {
error(_("Cannot delete branch '%s' "
- "checked out at '%s'"),
+ "used by worktree at '%s'"),
bname.buf, path);
ret = 1;
continue;
diff --git a/builtin/check-attr.c b/builtin/check-attr.c
index b22ff748c3..c1da1d184e 100644
--- a/builtin/check-attr.c
+++ b/builtin/check-attr.c
@@ -122,6 +122,9 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, check_attr_options,
check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
if (repo_read_index(the_repository) < 0) {
die("invalid cache");
}
diff --git a/builtin/gc.c b/builtin/gc.c
index 5c4315f0d8..1f53b66c7b 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -1708,6 +1708,15 @@ static int get_schedule_cmd(const char **cmd, int *is_available)
return 1;
}
+static int get_random_minute(void)
+{
+ /* Use a static value when under tests. */
+ if (getenv("GIT_TEST_MAINT_SCHEDULER"))
+ return 13;
+
+ return git_rand() % 60;
+}
+
static int is_launchctl_available(void)
{
const char *cmd = "launchctl";
@@ -1820,6 +1829,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
struct stat st;
const char *cmd = "launchctl";
+ int minute = get_random_minute();
get_schedule_cmd(&cmd, NULL);
preamble = "<?xml version=\"1.0\"?>\n"
@@ -1845,29 +1855,30 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
case SCHEDULE_HOURLY:
repeat = "<dict>\n"
"<key>Hour</key><integer>%d</integer>\n"
- "<key>Minute</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>%d</integer>\n"
"</dict>\n";
for (i = 1; i <= 23; i++)
- strbuf_addf(&plist, repeat, i);
+ strbuf_addf(&plist, repeat, i, minute);
break;
case SCHEDULE_DAILY:
repeat = "<dict>\n"
"<key>Day</key><integer>%d</integer>\n"
"<key>Hour</key><integer>0</integer>\n"
- "<key>Minute</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>%d</integer>\n"
"</dict>\n";
for (i = 1; i <= 6; i++)
- strbuf_addf(&plist, repeat, i);
+ strbuf_addf(&plist, repeat, i, minute);
break;
case SCHEDULE_WEEKLY:
- strbuf_addstr(&plist,
- "<dict>\n"
- "<key>Day</key><integer>0</integer>\n"
- "<key>Hour</key><integer>0</integer>\n"
- "<key>Minute</key><integer>0</integer>\n"
- "</dict>\n");
+ strbuf_addf(&plist,
+ "<dict>\n"
+ "<key>Day</key><integer>0</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>%d</integer>\n"
+ "</dict>\n",
+ minute);
break;
default:
@@ -1984,6 +1995,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
const char *frequency = get_frequency(schedule);
char *name = schtasks_task_name(frequency);
struct strbuf tfilename = STRBUF_INIT;
+ int minute = get_random_minute();
get_schedule_cmd(&cmd, NULL);
@@ -2004,7 +2016,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
switch (schedule) {
case SCHEDULE_HOURLY:
fprintf(tfile->fp,
- "<StartBoundary>2020-01-01T01:00:00</StartBoundary>\n"
+ "<StartBoundary>2020-01-01T01:%02d:00</StartBoundary>\n"
"<Enabled>true</Enabled>\n"
"<ScheduleByDay>\n"
"<DaysInterval>1</DaysInterval>\n"
@@ -2013,12 +2025,13 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
"<Interval>PT1H</Interval>\n"
"<Duration>PT23H</Duration>\n"
"<StopAtDurationEnd>false</StopAtDurationEnd>\n"
- "</Repetition>\n");
+ "</Repetition>\n",
+ minute);
break;
case SCHEDULE_DAILY:
fprintf(tfile->fp,
- "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n"
+ "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
"<Enabled>true</Enabled>\n"
"<ScheduleByWeek>\n"
"<DaysOfWeek>\n"
@@ -2030,19 +2043,21 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
"<Saturday />\n"
"</DaysOfWeek>\n"
"<WeeksInterval>1</WeeksInterval>\n"
- "</ScheduleByWeek>\n");
+ "</ScheduleByWeek>\n",
+ minute);
break;
case SCHEDULE_WEEKLY:
fprintf(tfile->fp,
- "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n"
+ "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
"<Enabled>true</Enabled>\n"
"<ScheduleByWeek>\n"
"<DaysOfWeek>\n"
"<Sunday />\n"
"</DaysOfWeek>\n"
"<WeeksInterval>1</WeeksInterval>\n"
- "</ScheduleByWeek>\n");
+ "</ScheduleByWeek>\n",
+ minute);
break;
default:
@@ -2159,6 +2174,7 @@ static int crontab_update_schedule(int run_maintenance, int fd)
FILE *cron_list, *cron_in;
struct strbuf line = STRBUF_INIT;
struct tempfile *tmpedit = NULL;
+ int minute = get_random_minute();
get_schedule_cmd(&cmd, NULL);
strvec_split(&crontab_list.args, cmd);
@@ -2213,11 +2229,11 @@ static int crontab_update_schedule(int run_maintenance, int fd)
"# replaced in the future by a Git command.\n\n");
strbuf_addf(&line_format,
- "%%s %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%s\n",
+ "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%s\n",
exec_path, exec_path);
- fprintf(cron_in, line_format.buf, "0", "1-23", "*", "hourly");
- fprintf(cron_in, line_format.buf, "0", "0", "1-6", "daily");
- fprintf(cron_in, line_format.buf, "0", "0", "0", "weekly");
+ fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly");
+ fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
+ fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly");
strbuf_release(&line_format);
fprintf(cron_in, "\n%s\n", END_LINE);
@@ -2276,77 +2292,54 @@ static char *xdg_config_home_systemd(const char *filename)
return xdg_config_home_for("systemd/user", filename);
}
-static int systemd_timer_enable_unit(int enable,
- enum schedule_priority schedule)
-{
- const char *cmd = "systemctl";
- struct child_process child = CHILD_PROCESS_INIT;
- const char *frequency = get_frequency(schedule);
-
- /*
- * Disabling the systemd unit while it is already disabled makes
- * systemctl print an error.
- * Let's ignore it since it means we already are in the expected state:
- * the unit is disabled.
- *
- * On the other hand, enabling a systemd unit which is already enabled
- * produces no error.
- */
- if (!enable)
- child.no_stderr = 1;
-
- get_schedule_cmd(&cmd, NULL);
- strvec_split(&child.args, cmd);
- strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
- "--now", NULL);
- strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency);
-
- if (start_command(&child))
- return error(_("failed to start systemctl"));
- if (finish_command(&child))
- /*
- * Disabling an already disabled systemd unit makes
- * systemctl fail.
- * Let's ignore this failure.
- *
- * Enabling an enabled systemd unit doesn't fail.
- */
- if (enable)
- return error(_("failed to run systemctl"));
- return 0;
-}
+#define SYSTEMD_UNIT_FORMAT "git-maintenance@%s.%s"
-static int systemd_timer_delete_unit_templates(void)
+static int systemd_timer_delete_timer_file(enum schedule_priority priority)
{
int ret = 0;
- char *filename = xdg_config_home_systemd("git-maintenance@.timer");
- if (unlink(filename) && !is_missing_file_error(errno))
- ret = error_errno(_("failed to delete '%s'"), filename);
- FREE_AND_NULL(filename);
+ const char *frequency = get_frequency(priority);
+ char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
+ char *filename = xdg_config_home_systemd(local_timer_name);
- filename = xdg_config_home_systemd("git-maintenance@.service");
if (unlink(filename) && !is_missing_file_error(errno))
ret = error_errno(_("failed to delete '%s'"), filename);
free(filename);
+ free(local_timer_name);
return ret;
}
-static int systemd_timer_delete_units(void)
+static int systemd_timer_delete_service_template(void)
{
- return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) ||
- systemd_timer_enable_unit(0, SCHEDULE_DAILY) ||
- systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) ||
- systemd_timer_delete_unit_templates();
+ int ret = 0;
+ char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
+ char *filename = xdg_config_home_systemd(local_service_name);
+ if (unlink(filename) && !is_missing_file_error(errno))
+ ret = error_errno(_("failed to delete '%s'"), filename);
+
+ free(filename);
+ free(local_service_name);
+ return ret;
}
-static int systemd_timer_write_unit_templates(const char *exec_path)
+/*
+ * Write the schedule information into a git-maintenance@<schedule>.timer
+ * file using a custom minute. This timer file cannot use the templating
+ * system, so we generate a specific file for each.
+ */
+static int systemd_timer_write_timer_file(enum schedule_priority schedule,
+ int minute)
{
+ int res = -1;
char *filename;
FILE *file;
const char *unit;
+ char *schedule_pattern = NULL;
+ const char *frequency = get_frequency(schedule);
+ char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
+
+ filename = xdg_config_home_systemd(local_timer_name);
- filename = xdg_config_home_systemd("git-maintenance@.timer");
if (safe_create_leading_directories(filename)) {
error(_("failed to create directories for '%s'"), filename);
goto error;
@@ -2355,6 +2348,23 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
if (!file)
goto error;
+ switch (schedule) {
+ case SCHEDULE_HOURLY:
+ schedule_pattern = xstrfmt("*-*-* 1..23:%02d:00", minute);
+ break;
+
+ case SCHEDULE_DAILY:
+ schedule_pattern = xstrfmt("Tue..Sun *-*-* 0:%02d:00", minute);
+ break;
+
+ case SCHEDULE_WEEKLY:
+ schedule_pattern = xstrfmt("Mon 0:%02d:00", minute);
+ break;
+
+ default:
+ BUG("Unhandled schedule_priority");
+ }
+
unit = "# This file was created and is maintained by Git.\n"
"# Any edits made in this file might be replaced in the future\n"
"# by a Git command.\n"
@@ -2363,12 +2373,12 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
"Description=Optimize Git repositories data\n"
"\n"
"[Timer]\n"
- "OnCalendar=%i\n"
+ "OnCalendar=%s\n"
"Persistent=true\n"
"\n"
"[Install]\n"
"WantedBy=timers.target\n";
- if (fputs(unit, file) == EOF) {
+ if (fprintf(file, unit, schedule_pattern) < 0) {
error(_("failed to write to '%s'"), filename);
fclose(file);
goto error;
@@ -2377,9 +2387,36 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
error_errno(_("failed to flush '%s'"), filename);
goto error;
}
+
+ res = 0;
+
+error:
+ free(schedule_pattern);
+ free(local_timer_name);
free(filename);
+ return res;
+}
- filename = xdg_config_home_systemd("git-maintenance@.service");
+/*
+ * No matter the schedule, we use the same service and can make use of the
+ * templating system. When installing git-maintenance@<schedule>.timer,
+ * systemd will notice that git-maintenance@.service exists as a template
+ * and will use this file and insert the <schedule> into the template at
+ * the position of "%i".
+ */
+static int systemd_timer_write_service_template(const char *exec_path)
+{
+ int res = -1;
+ char *filename;
+ FILE *file;
+ const char *unit;
+ char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
+
+ filename = xdg_config_home_systemd(local_service_name);
+ if (safe_create_leading_directories(filename)) {
+ error(_("failed to create directories for '%s'"), filename);
+ goto error;
+ }
file = fopen_or_warn(filename, "w");
if (!file)
goto error;
@@ -2412,25 +2449,110 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
error_errno(_("failed to flush '%s'"), filename);
goto error;
}
+
+ res = 0;
+
+error:
+ free(local_service_name);
free(filename);
+ return res;
+}
+
+static int systemd_timer_enable_unit(int enable,
+ enum schedule_priority schedule,
+ int minute)
+{
+ const char *cmd = "systemctl";
+ struct child_process child = CHILD_PROCESS_INIT;
+ const char *frequency = get_frequency(schedule);
+
+ /*
+ * Disabling the systemd unit while it is already disabled makes
+ * systemctl print an error.
+ * Let's ignore it since it means we already are in the expected state:
+ * the unit is disabled.
+ *
+ * On the other hand, enabling a systemd unit which is already enabled
+ * produces no error.
+ */
+ if (!enable)
+ child.no_stderr = 1;
+ else if (systemd_timer_write_timer_file(schedule, minute))
+ return -1;
+
+ get_schedule_cmd(&cmd, NULL);
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
+ "--now", NULL);
+ strvec_pushf(&child.args, SYSTEMD_UNIT_FORMAT, frequency, "timer");
+
+ if (start_command(&child))
+ return error(_("failed to start systemctl"));
+ if (finish_command(&child))
+ /*
+ * Disabling an already disabled systemd unit makes
+ * systemctl fail.
+ * Let's ignore this failure.
+ *
+ * Enabling an enabled systemd unit doesn't fail.
+ */
+ if (enable)
+ return error(_("failed to run systemctl"));
return 0;
+}
+
+/*
+ * A previous version of Git wrote the timer units as template files.
+ * Clean these up, if they exist.
+ */
+static void systemd_timer_delete_stale_timer_templates(void)
+{
+ char *timer_template_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "timer");
+ char *filename = xdg_config_home_systemd(timer_template_name);
+
+ if (unlink(filename) && !is_missing_file_error(errno))
+ warning(_("failed to delete '%s'"), filename);
-error:
free(filename);
- systemd_timer_delete_unit_templates();
- return -1;
+ free(timer_template_name);
+}
+
+static int systemd_timer_delete_unit_files(void)
+{
+ systemd_timer_delete_stale_timer_templates();
+
+ /* Purposefully not short-circuited to make sure all are called. */
+ return systemd_timer_delete_timer_file(SCHEDULE_HOURLY) |
+ systemd_timer_delete_timer_file(SCHEDULE_DAILY) |
+ systemd_timer_delete_timer_file(SCHEDULE_WEEKLY) |
+ systemd_timer_delete_service_template();
+}
+
+static int systemd_timer_delete_units(void)
+{
+ int minute = get_random_minute();
+ /* Purposefully not short-circuited to make sure all are called. */
+ return systemd_timer_enable_unit(0, SCHEDULE_HOURLY, minute) |
+ systemd_timer_enable_unit(0, SCHEDULE_DAILY, minute) |
+ systemd_timer_enable_unit(0, SCHEDULE_WEEKLY, minute) |
+ systemd_timer_delete_unit_files();
}
static int systemd_timer_setup_units(void)
{
+ int minute = get_random_minute();
const char *exec_path = git_exec_path();
- int ret = systemd_timer_write_unit_templates(exec_path) ||
- systemd_timer_enable_unit(1, SCHEDULE_HOURLY) ||
- systemd_timer_enable_unit(1, SCHEDULE_DAILY) ||
- systemd_timer_enable_unit(1, SCHEDULE_WEEKLY);
+ int ret = systemd_timer_write_service_template(exec_path) ||
+ systemd_timer_enable_unit(1, SCHEDULE_HOURLY, minute) ||
+ systemd_timer_enable_unit(1, SCHEDULE_DAILY, minute) ||
+ systemd_timer_enable_unit(1, SCHEDULE_WEEKLY, minute);
+
if (ret)
systemd_timer_delete_units();
+ else
+ systemd_timer_delete_stale_timer_templates();
+
return ret;
}
@@ -2606,9 +2728,12 @@ static int maintenance_start(int argc, const char **argv, const char *prefix)
opts.scheduler = resolve_scheduler(opts.scheduler);
validate_scheduler(opts.scheduler);
+ if (update_background_schedule(&opts, 1))
+ die(_("failed to set up maintenance schedule"));
+
if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL))
warning(_("failed to add repo to global config"));
- return update_background_schedule(&opts, 1);
+ return 0;
}
static const char *const builtin_maintenance_stop_usage[] = {
diff --git a/builtin/mv.c b/builtin/mv.c
index 05e7156034..c596515ad0 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -305,7 +305,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
}
if (S_ISDIR(st.st_mode)
&& lstat(dst, &dest_st) == 0) {
- bad = _("cannot move directory over file");
+ bad = _("destination already exists");
goto act_on_entry;
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index fb8e1549d1..8c4f0cb90a 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -2527,10 +2527,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if (cert_nonce_seed)
push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL));
- if (0 <= transfer_unpack_limit)
- unpack_limit = transfer_unpack_limit;
- else if (0 <= receive_unpack_limit)
+ if (0 <= receive_unpack_limit)
unpack_limit = receive_unpack_limit;
+ else if (0 <= transfer_unpack_limit)
+ unpack_limit = transfer_unpack_limit;
switch (determine_protocol_version_server()) {
case protocol_v2:
diff --git a/builtin/repack.c b/builtin/repack.c
index 97051479e4..2b43a5be08 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -303,6 +303,8 @@ struct pack_geometry {
struct packed_git **pack;
uint32_t pack_nr, pack_alloc;
uint32_t split;
+
+ int split_factor;
};
static uint32_t geometry_pack_weight(struct packed_git *p)
@@ -324,17 +326,13 @@ static int geometry_cmp(const void *va, const void *vb)
return 0;
}
-static void init_pack_geometry(struct pack_geometry **geometry_p,
+static void init_pack_geometry(struct pack_geometry *geometry,
struct string_list *existing_kept_packs,
const struct pack_objects_args *args)
{
struct packed_git *p;
- struct pack_geometry *geometry;
struct strbuf buf = STRBUF_INIT;
- *geometry_p = xcalloc(1, sizeof(struct pack_geometry));
- geometry = *geometry_p;
-
for (p = get_all_packs(the_repository); p; p = p->next) {
if (args->local && !p->pack_local)
/*
@@ -380,7 +378,7 @@ static void init_pack_geometry(struct pack_geometry **geometry_p,
strbuf_release(&buf);
}
-static void split_pack_geometry(struct pack_geometry *geometry, int factor)
+static void split_pack_geometry(struct pack_geometry *geometry)
{
uint32_t i;
uint32_t split;
@@ -399,12 +397,14 @@ static void split_pack_geometry(struct pack_geometry *geometry, int factor)
struct packed_git *ours = geometry->pack[i];
struct packed_git *prev = geometry->pack[i - 1];
- if (unsigned_mult_overflows(factor, geometry_pack_weight(prev)))
+ if (unsigned_mult_overflows(geometry->split_factor,
+ geometry_pack_weight(prev)))
die(_("pack %s too large to consider in geometric "
"progression"),
prev->pack_name);
- if (geometry_pack_weight(ours) < factor * geometry_pack_weight(prev))
+ if (geometry_pack_weight(ours) <
+ geometry->split_factor * geometry_pack_weight(prev))
break;
}
@@ -439,10 +439,12 @@ static void split_pack_geometry(struct pack_geometry *geometry, int factor)
for (i = split; i < geometry->pack_nr; i++) {
struct packed_git *ours = geometry->pack[i];
- if (unsigned_mult_overflows(factor, total_size))
+ if (unsigned_mult_overflows(geometry->split_factor,
+ total_size))
die(_("pack %s too large to roll up"), ours->pack_name);
- if (geometry_pack_weight(ours) < factor * total_size) {
+ if (geometry_pack_weight(ours) <
+ geometry->split_factor * total_size) {
if (unsigned_add_overflows(total_size,
geometry_pack_weight(ours)))
die(_("pack %s too large to roll up"),
@@ -498,7 +500,6 @@ static void free_pack_geometry(struct pack_geometry *geometry)
return;
free(geometry->pack);
- free(geometry);
}
struct midx_snapshot_ref_data {
@@ -575,7 +576,7 @@ static void midx_included_packs(struct string_list *include,
string_list_insert(include, xstrfmt("%s.idx", item->string));
for_each_string_list_item(item, names)
string_list_insert(include, xstrfmt("pack-%s.idx", item->string));
- if (geometry) {
+ if (geometry->split_factor) {
struct strbuf buf = STRBUF_INIT;
uint32_t i;
for (i = geometry->split; i < geometry->pack_nr; i++) {
@@ -779,7 +780,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
struct string_list names = STRING_LIST_INIT_DUP;
struct string_list existing_nonkept_packs = STRING_LIST_INIT_DUP;
struct string_list existing_kept_packs = STRING_LIST_INIT_DUP;
- struct pack_geometry *geometry = NULL;
+ struct pack_geometry geometry = { 0 };
struct strbuf line = STRBUF_INIT;
struct tempfile *refs_snapshot = NULL;
int i, ext, ret;
@@ -793,7 +794,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
struct pack_objects_args po_args = {NULL};
struct pack_objects_args cruft_po_args = {NULL};
- int geometric_factor = 0;
int write_midx = 0;
const char *cruft_expiration = NULL;
const char *expire_to = NULL;
@@ -842,7 +842,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
N_("repack objects in packs marked with .keep")),
OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
N_("do not repack this pack")),
- OPT_INTEGER('g', "geometric", &geometric_factor,
+ OPT_INTEGER('g', "geometric", &geometry.split_factor,
N_("find a geometric progression with factor <N>")),
OPT_BOOL('m', "write-midx", &write_midx,
N_("write a multi-pack index of the resulting packs")),
@@ -918,11 +918,11 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
collect_pack_filenames(&existing_nonkept_packs, &existing_kept_packs,
&keep_pack_list);
- if (geometric_factor) {
+ if (geometry.split_factor) {
if (pack_everything)
die(_("options '%s' and '%s' cannot be used together"), "--geometric", "-A/-a");
init_pack_geometry(&geometry, &existing_kept_packs, &po_args);
- split_pack_geometry(geometry, geometric_factor);
+ split_pack_geometry(&geometry);
}
prepare_pack_objects(&cmd, &po_args, packtmp);
@@ -936,7 +936,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_pushf(&cmd.args, "--keep-pack=%s",
keep_pack_list.items[i].string);
strvec_push(&cmd.args, "--non-empty");
- if (!geometry) {
+ if (!geometry.split_factor) {
/*
* We need to grab all reachable objects, including those that
* are reachable from reflogs and the index.
@@ -983,7 +983,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_push(&cmd.args, "--pack-loose-unreachable");
}
}
- } else if (geometry) {
+ } else if (geometry.split_factor) {
strvec_push(&cmd.args, "--stdin-packs");
strvec_push(&cmd.args, "--unpacked");
} else {
@@ -991,7 +991,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_push(&cmd.args, "--incremental");
}
- if (geometry)
+ if (geometry.split_factor)
cmd.in = -1;
else
cmd.no_stdin = 1;
@@ -1000,17 +1000,17 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (ret)
goto cleanup;
- if (geometry) {
+ if (geometry.split_factor) {
FILE *in = xfdopen(cmd.in, "w");
/*
* The resulting pack should contain all objects in packs that
* are going to be rolled up, but exclude objects in packs which
* are being left alone.
*/
- for (i = 0; i < geometry->split; i++)
- fprintf(in, "%s\n", pack_basename(geometry->pack[i]));
- for (i = geometry->split; i < geometry->pack_nr; i++)
- fprintf(in, "^%s\n", pack_basename(geometry->pack[i]));
+ for (i = 0; i < geometry.split; i++)
+ fprintf(in, "%s\n", pack_basename(geometry.pack[i]));
+ for (i = geometry.split; i < geometry.pack_nr; i++)
+ fprintf(in, "^%s\n", pack_basename(geometry.pack[i]));
fclose(in);
}
@@ -1153,9 +1153,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (write_midx) {
struct string_list include = STRING_LIST_INIT_NODUP;
midx_included_packs(&include, &existing_nonkept_packs,
- &existing_kept_packs, &names, geometry);
+ &existing_kept_packs, &names, &geometry);
- ret = write_midx_included_packs(&include, geometry,
+ ret = write_midx_included_packs(&include, &geometry,
refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,
show_progress, write_bitmaps > 0);
@@ -1178,12 +1178,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
remove_redundant_pack(packdir, item->string);
}
- if (geometry) {
+ if (geometry.split_factor) {
struct strbuf buf = STRBUF_INIT;
uint32_t i;
- for (i = 0; i < geometry->split; i++) {
- struct packed_git *p = geometry->pack[i];
+ for (i = 0; i < geometry.split; i++) {
+ struct packed_git *p = geometry.pack[i];
if (string_list_has_string(&names,
hash_to_hex(p->hash)))
continue;
@@ -1226,7 +1226,7 @@ cleanup:
string_list_clear(&names, 1);
string_list_clear(&existing_nonkept_packs, 0);
string_list_clear(&existing_kept_packs, 0);
- free_pack_geometry(geometry);
+ free_pack_geometry(&geometry);
return ret;
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 4cd01842de..10db70b7ec 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -696,7 +696,7 @@ static int can_use_remote_refs(const struct add_opts *opts)
return 1;
} else if (!opts->force && remote_get(NULL)) {
die(_("No local or remote refs exist despite at least one remote\n"
- "present, stopping; use 'add -f' to overide or fetch a remote first"));
+ "present, stopping; use 'add -f' to override or fetch a remote first"));
}
return 0;
}
diff --git a/commit-graph.c b/commit-graph.c
index 0aa1640d15..9e6eaa8a46 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -128,6 +128,16 @@ timestamp_t commit_graph_generation(const struct commit *c)
return GENERATION_NUMBER_INFINITY;
}
+static timestamp_t commit_graph_generation_from_graph(const struct commit *c)
+{
+ struct commit_graph_data *data =
+ commit_graph_data_slab_peek(&commit_graph_data_slab, c);
+
+ if (!data || data->graph_pos == COMMIT_NOT_FROM_GRAPH)
+ return GENERATION_NUMBER_INFINITY;
+ return data->generation;
+}
+
static struct commit_graph_data *commit_graph_data_at(const struct commit *c)
{
unsigned int i, nth_slab;
@@ -2550,9 +2560,6 @@ static void graph_report(const char *fmt, ...)
va_end(ap);
}
-#define GENERATION_ZERO_EXISTS 1
-#define GENERATION_NUMBER_EXISTS 2
-
static int commit_graph_checksum_valid(struct commit_graph *g)
{
return hashfile_checksum_valid(g->data, g->data_len);
@@ -2565,7 +2572,8 @@ static int verify_one_commit_graph(struct repository *r,
{
uint32_t i, cur_fanout_pos = 0;
struct object_id prev_oid, cur_oid;
- int generation_zero = 0;
+ struct commit *seen_gen_zero = NULL;
+ struct commit *seen_gen_non_zero = NULL;
verify_commit_graph_error = verify_commit_graph_lite(g);
if (verify_commit_graph_error)
@@ -2659,7 +2667,7 @@ static int verify_one_commit_graph(struct repository *r,
oid_to_hex(&graph_parents->item->object.oid),
oid_to_hex(&odb_parents->item->object.oid));
- generation = commit_graph_generation(graph_parents->item);
+ generation = commit_graph_generation_from_graph(graph_parents->item);
if (generation > max_generation)
max_generation = generation;
@@ -2671,16 +2679,12 @@ static int verify_one_commit_graph(struct repository *r,
graph_report(_("commit-graph parent list for commit %s terminates early"),
oid_to_hex(&cur_oid));
- if (!commit_graph_generation(graph_commit)) {
- if (generation_zero == GENERATION_NUMBER_EXISTS)
- graph_report(_("commit-graph has generation number zero for commit %s, but non-zero elsewhere"),
- oid_to_hex(&cur_oid));
- generation_zero = GENERATION_ZERO_EXISTS;
- } else if (generation_zero == GENERATION_ZERO_EXISTS)
- graph_report(_("commit-graph has non-zero generation number for commit %s, but zero elsewhere"),
- oid_to_hex(&cur_oid));
+ if (commit_graph_generation_from_graph(graph_commit))
+ seen_gen_non_zero = graph_commit;
+ else
+ seen_gen_zero = graph_commit;
- if (generation_zero == GENERATION_ZERO_EXISTS)
+ if (seen_gen_zero)
continue;
/*
@@ -2706,6 +2710,12 @@ static int verify_one_commit_graph(struct repository *r,
odb_commit->date);
}
+ if (seen_gen_zero && seen_gen_non_zero)
+ graph_report(_("commit-graph has both zero and non-zero "
+ "generations (e.g., commits '%s' and '%s')"),
+ oid_to_hex(&seen_gen_zero->object.oid),
+ oid_to_hex(&seen_gen_non_zero->object.oid));
+
return verify_commit_graph_error;
}
diff --git a/compat/terminal.c b/compat/terminal.c
index 83d95e8656..0afda730f2 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -479,10 +479,13 @@ struct escape_sequence_entry {
};
static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED,
- const struct escape_sequence_entry *e1,
- const struct escape_sequence_entry *e2,
+ const struct hashmap_entry *he1,
+ const struct hashmap_entry *he2,
const void *keydata)
{
+ const struct escape_sequence_entry
+ *e1 = container_of(he1, const struct escape_sequence_entry, entry),
+ *e2 = container_of(he2, const struct escape_sequence_entry, entry);
return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
}
@@ -496,8 +499,7 @@ static int is_known_escape_sequence(const char *sequence)
struct strbuf buf = STRBUF_INIT;
char *p, *eol;
- hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
- NULL, 0);
+ hashmap_init(&sequences, sequence_entry_cmp, NULL, 0);
strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
index ef681f29d5..215a81d8ba 100644
--- a/contrib/credential/libsecret/git-credential-libsecret.c
+++ b/contrib/credential/libsecret/git-credential-libsecret.c
@@ -39,6 +39,8 @@ struct credential {
char *path;
char *username;
char *password;
+ char *password_expiry_utc;
+ char *oauth_refresh_token;
};
#define CREDENTIAL_INIT { 0 }
@@ -52,8 +54,29 @@ struct credential_operation {
#define CREDENTIAL_OP_END { NULL, NULL }
+static void credential_clear(struct credential *c);
+
/* ----------------- Secret Service functions ----------------- */
+static const SecretSchema schema = {
+ "org.git.Password",
+ /* Ignore schema name during search for backwards compatibility */
+ SECRET_SCHEMA_DONT_MATCH_NAME,
+ {
+ /*
+ * libsecret assumes attribute values are non-confidential and
+ * unchanging, so we can't include oauth_refresh_token or
+ * password_expiry_utc.
+ */
+ { "user", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { "object", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
+ { "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { NULL, 0 },
+ }
+};
+
static char *make_label(struct credential *c)
{
if (c->port)
@@ -101,7 +124,7 @@ static int keyring_get(struct credential *c)
attributes = make_attr_list(c);
items = secret_service_search_sync(service,
- SECRET_SCHEMA_COMPAT_NETWORK,
+ &schema,
attributes,
SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
NULL,
@@ -117,6 +140,7 @@ static int keyring_get(struct credential *c)
SecretItem *item;
SecretValue *secret;
const char *s;
+ gchar **parts;
item = items->data;
secret = secret_item_get_secret(item);
@@ -130,8 +154,27 @@ static int keyring_get(struct credential *c)
s = secret_value_get_text(secret);
if (s) {
- g_free(c->password);
- c->password = g_strdup(s);
+ /*
+ * Passwords and other attributes encoded in following format:
+ * hunter2
+ * password_expiry_utc=1684189401
+ * oauth_refresh_token=xyzzy
+ */
+ parts = g_strsplit(s, "\n", 0);
+ if (g_strv_length(parts) >= 1) {
+ g_free(c->password);
+ c->password = g_strdup(parts[0]);
+ }
+ for (int i = 1; i < g_strv_length(parts); i++) {
+ if (g_str_has_prefix(parts[i], "password_expiry_utc=")) {
+ g_free(c->password_expiry_utc);
+ c->password_expiry_utc = g_strdup(&parts[i][20]);
+ } else if (g_str_has_prefix(parts[i], "oauth_refresh_token=")) {
+ g_free(c->oauth_refresh_token);
+ c->oauth_refresh_token = g_strdup(&parts[i][20]);
+ }
+ }
+ g_strfreev(parts);
}
g_hash_table_unref(attributes);
@@ -148,6 +191,7 @@ static int keyring_store(struct credential *c)
char *label = NULL;
GHashTable *attributes = NULL;
GError *error = NULL;
+ GString *secret = NULL;
/*
* Sanity check that what we are storing is actually sensible.
@@ -162,13 +206,23 @@ static int keyring_store(struct credential *c)
label = make_label(c);
attributes = make_attr_list(c);
- secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ secret = g_string_new(c->password);
+ if (c->password_expiry_utc) {
+ g_string_append_printf(secret, "\npassword_expiry_utc=%s",
+ c->password_expiry_utc);
+ }
+ if (c->oauth_refresh_token) {
+ g_string_append_printf(secret, "\noauth_refresh_token=%s",
+ c->oauth_refresh_token);
+ }
+ secret_password_storev_sync(&schema,
attributes,
NULL,
label,
- c->password,
+ secret->str,
NULL,
&error);
+ g_string_free(secret, TRUE);
g_free(label);
g_hash_table_unref(attributes);
@@ -185,6 +239,7 @@ static int keyring_erase(struct credential *c)
{
GHashTable *attributes = NULL;
GError *error = NULL;
+ struct credential existing = CREDENTIAL_INIT;
/*
* Sanity check that we actually have something to match
@@ -197,8 +252,22 @@ static int keyring_erase(struct credential *c)
if (!c->protocol && !c->host && !c->path && !c->username)
return EXIT_FAILURE;
+ if (c->password) {
+ existing.host = g_strdup(c->host);
+ existing.path = g_strdup(c->path);
+ existing.port = c->port;
+ existing.protocol = g_strdup(c->protocol);
+ existing.username = g_strdup(c->username);
+ keyring_get(&existing);
+ if (existing.password && strcmp(c->password, existing.password)) {
+ credential_clear(&existing);
+ return EXIT_SUCCESS;
+ }
+ credential_clear(&existing);
+ }
+
attributes = make_attr_list(c);
- secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ secret_password_clearv_sync(&schema,
attributes,
NULL,
&error);
@@ -238,6 +307,8 @@ static void credential_clear(struct credential *c)
g_free(c->path);
g_free(c->username);
g_free(c->password);
+ g_free(c->password_expiry_utc);
+ g_free(c->oauth_refresh_token);
credential_init(c);
}
@@ -284,11 +355,19 @@ static int credential_read(struct credential *c)
} else if (!strcmp(key, "username")) {
g_free(c->username);
c->username = g_strdup(value);
+ } else if (!strcmp(key, "password_expiry_utc")) {
+ g_free(c->password_expiry_utc);
+ c->password_expiry_utc = g_strdup(value);
} else if (!strcmp(key, "password")) {
g_free(c->password);
c->password = g_strdup(value);
while (*value)
*value++ = '\0';
+ } else if (!strcmp(key, "oauth_refresh_token")) {
+ g_free(c->oauth_refresh_token);
+ c->oauth_refresh_token = g_strdup(value);
+ while (*value)
+ *value++ = '\0';
}
/*
* Ignore other lines; we don't know what they mean, but
@@ -314,6 +393,10 @@ static void credential_write(const struct credential *c)
/* only write username/password, if set */
credential_write_item(stdout, "username", c->username);
credential_write_item(stdout, "password", c->password);
+ credential_write_item(stdout, "password_expiry_utc",
+ c->password_expiry_utc);
+ credential_write_item(stdout, "oauth_refresh_token",
+ c->oauth_refresh_token);
}
static void usage(const char *name)
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index 96f10613ae..4cd56c42e2 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -109,7 +109,18 @@ static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
return match_part_with_last(ptarget, want, delim, 1);
}
-static int match_cred(const CREDENTIALW *cred)
+static int match_cred_password(const CREDENTIALW *cred) {
+ int ret;
+ WCHAR *cred_password = xmalloc(cred->CredentialBlobSize);
+ wcsncpy_s(cred_password, cred->CredentialBlobSize,
+ (LPCWSTR)cred->CredentialBlob,
+ cred->CredentialBlobSize / sizeof(WCHAR));
+ ret = !wcscmp(cred_password, password);
+ free(cred_password);
+ return ret;
+}
+
+static int match_cred(const CREDENTIALW *cred, int match_password)
{
LPCWSTR target = cred->TargetName;
if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L""))
@@ -119,7 +130,8 @@ static int match_cred(const CREDENTIALW *cred)
match_part(&target, protocol, L"://") &&
match_part_last(&target, wusername, L"@") &&
match_part(&target, host, L"/") &&
- match_part(&target, path, L"");
+ match_part(&target, path, L"") &&
+ (!match_password || match_cred_password(cred));
}
static void get_credential(void)
@@ -134,7 +146,7 @@ static void get_credential(void)
/* search for the first credential that matches username */
for (i = 0; i < num_creds; ++i)
- if (match_cred(creds[i])) {
+ if (match_cred(creds[i], 0)) {
write_item("username", creds[i]->UserName,
creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
write_item("password",
@@ -196,7 +208,7 @@ static void erase_credential(void)
return;
for (i = 0; i < num_creds; ++i) {
- if (match_cred(creds[i]))
+ if (match_cred(creds[i], password != NULL))
CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
}
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 7db4c45676..e0c5d3b0de 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -33,19 +33,19 @@ git subtree split --prefix=<prefix> [<commit>]
git subtree pull --prefix=<prefix> <repository> <ref>
git subtree push --prefix=<prefix> <repository> <refspec>
--
-h,help show the help
-q,quiet quiet
-d,debug show debug messages
+h,help! show the help
+q,quiet! quiet
+d,debug! show debug messages
P,prefix= the name of the subdir to split out
options for 'split' (also: 'push')
annotate= add a prefix to commit message of new commits
-b,branch= create a new branch from the split subtree
+b,branch!= create a new branch from the split subtree
ignore-joins ignore prior --rejoin commits
onto= try connecting new tree to an existing one
rejoin merge the new branch back into HEAD
options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin')
squash merge subtree changes as a single commit
-m,message= use the given message as the commit message for the merge commit
+m,message!= use the given message as the commit message for the merge commit
"
indent=0
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 341c169eca..49a21dd7c9 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -71,7 +71,7 @@ test_expect_success 'shows short help text for -h' '
test_expect_code 129 git subtree -h >out 2>err &&
test_must_be_empty err &&
grep -e "^ *or: git subtree pull" out &&
- grep -e --annotate out
+ grep -F -e "--[no-]annotate" out
'
#
diff --git a/diff.c b/diff.c
index 2de5d3d098..bccb018da4 100644
--- a/diff.c
+++ b/diff.c
@@ -3563,18 +3563,21 @@ static void builtin_diff(const char *name_a,
strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, meta, two->mode, reset);
if (xfrm_msg)
strbuf_addstr(&header, xfrm_msg);
+ o->found_changes = 1;
must_show_header = 1;
}
else if (lbl[1][0] == '/') {
strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, meta, one->mode, reset);
if (xfrm_msg)
strbuf_addstr(&header, xfrm_msg);
+ o->found_changes = 1;
must_show_header = 1;
}
else {
if (one->mode != two->mode) {
strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, meta, one->mode, reset);
strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, meta, two->mode, reset);
+ o->found_changes = 1;
must_show_header = 1;
}
if (xfrm_msg)
@@ -4832,6 +4835,10 @@ void diff_setup_done(struct diff_options *options)
else
options->prefix_length = 0;
+ /*
+ * --name-only, --name-status, --checkdiff, and -s
+ * turn other output format off.
+ */
if (options->output_format & (DIFF_FORMAT_NAME |
DIFF_FORMAT_NAME_STATUS |
DIFF_FORMAT_CHECKDIFF |
@@ -6206,6 +6213,8 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
fprintf(opt->file, "%s", diff_line_prefix(opt));
write_name_quoted(name_a, opt->file, opt->line_termination);
}
+
+ opt->found_changes = 1;
}
static void show_file_mode_name(struct diff_options *opt, const char *newdelete, struct diff_filespec *fs)
@@ -6684,6 +6693,21 @@ void diff_flush(struct diff_options *options)
separator++;
}
+ if (output_format & DIFF_FORMAT_PATCH) {
+ if (separator) {
+ emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0);
+ if (options->stat_sep)
+ /* attach patch instead of inline */
+ emit_diff_symbol(options, DIFF_SYMBOL_STAT_SEP,
+ NULL, 0, 0);
+ }
+
+ diff_flush_patch_all_file_pairs(options);
+ }
+
+ if (output_format & DIFF_FORMAT_CALLBACK)
+ options->format_callback(q, options, options->format_callback_data);
+
if (output_format & DIFF_FORMAT_NO_OUTPUT &&
options->flags.exit_with_status &&
options->flags.diff_from_contents) {
@@ -6705,21 +6729,6 @@ void diff_flush(struct diff_options *options)
}
}
- if (output_format & DIFF_FORMAT_PATCH) {
- if (separator) {
- emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0);
- if (options->stat_sep)
- /* attach patch instead of inline */
- emit_diff_symbol(options, DIFF_SYMBOL_STAT_SEP,
- NULL, 0, 0);
- }
-
- diff_flush_patch_all_file_pairs(options);
- }
-
- if (output_format & DIFF_FORMAT_CALLBACK)
- options->format_callback(q, options, options->format_callback_data);
-
free_queue:
diff_free_queue(q);
DIFF_QUEUE_CLEAR(q);
@@ -7027,6 +7036,7 @@ void compute_diffstat(struct diff_options *options,
if (check_pair_status(p))
diff_flush_stat(p, options, diffstat);
}
+ options->found_changes = !!diffstat->nr;
}
void diff_addremove(struct diff_options *options,
diff --git a/fetch-pack.c b/fetch-pack.c
index 65c1ff4bb4..26999e3b65 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1911,10 +1911,10 @@ static void fetch_pack_setup(void)
if (did_setup)
return;
fetch_pack_config();
- if (0 <= transfer_unpack_limit)
- unpack_limit = transfer_unpack_limit;
- else if (0 <= fetch_unpack_limit)
+ if (0 <= fetch_unpack_limit)
unpack_limit = fetch_unpack_limit;
+ else if (0 <= transfer_unpack_limit)
+ unpack_limit = transfer_unpack_limit;
did_setup = 1;
}
diff --git a/git-compat-util.h b/git-compat-util.h
index d32aa754ae..3e7a59b5ff 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -422,6 +422,10 @@ char *gitdirname(char *);
#define PATH_MAX 4096
#endif
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
+
typedef uintmax_t timestamp_t;
#define PRItime PRIuMAX
#define parse_timestamp strtoumax
diff --git a/git-gui/Makefile b/git-gui/Makefile
index a0d5a4b28e..3f80435436 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -138,25 +138,10 @@ GITGUI_SCRIPT := $$0
GITGUI_RELATIVE :=
GITGUI_MACOSXAPP :=
-ifeq ($(uname_O),Cygwin)
- GITGUI_SCRIPT := `cygpath --windows --absolute "$(GITGUI_SCRIPT)"`
-
- # Is this a Cygwin Tcl/Tk binary? If so it knows how to do
- # POSIX path translation just like cygpath does and we must
- # keep libdir in POSIX format so Cygwin packages of git-gui
- # work no matter where the user installs them.
- #
- ifeq ($(shell echo 'puts [file normalize /]' | '$(TCL_PATH_SQ)'),$(shell cygpath --mixed --absolute /))
- gg_libdir_sed_in := $(gg_libdir)
- else
- gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)")
- endif
-else
- ifeq ($(exedir),$(gg_libdir))
- GITGUI_RELATIVE := 1
- endif
- gg_libdir_sed_in := $(gg_libdir)
+ifeq ($(exedir),$(gg_libdir))
+ GITGUI_RELATIVE := 1
endif
+gg_libdir_sed_in := $(gg_libdir)
ifeq ($(uname_S),Darwin)
ifeq ($(shell test -d $(TKFRAMEWORK) && echo y),y)
GITGUI_MACOSXAPP := YesPlease
diff --git a/git-gui/README.md b/git-gui/README.md
index 5ce2122fbc..b460b649a8 100644
--- a/git-gui/README.md
+++ b/git-gui/README.md
@@ -88,7 +88,7 @@ that you first use `git-format-patch` to generate the emails, audit them, and
then send them via `git-send-email`.
A pretty good guide to configuring and using `git-send-email` can be found
-[here](https://www.freedesktop.org/wiki/Software/PulseAudio/HowToUseGitSendEmail/)
+[here](https://www.freedesktop.org/wiki/Software/PulseAudio/HowToUseGitSendEmail/).
### Using your email client
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 201524c34e..8bc8892c40 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -46,6 +46,132 @@ catch {rename send {}} ; # What an evil concept...
######################################################################
##
+## Enabling platform-specific code paths
+
+proc is_MacOSX {} {
+ if {[tk windowingsystem] eq {aqua}} {
+ return 1
+ }
+ return 0
+}
+
+proc is_Windows {} {
+ if {$::tcl_platform(platform) eq {windows}} {
+ return 1
+ }
+ return 0
+}
+
+set _iscygwin {}
+proc is_Cygwin {} {
+ global _iscygwin
+ if {$_iscygwin eq {}} {
+ if {[string match "CYGWIN_*" $::tcl_platform(os)]} {
+ set _iscygwin 1
+ } else {
+ set _iscygwin 0
+ }
+ }
+ return $_iscygwin
+}
+
+######################################################################
+##
+## PATH lookup
+
+set _search_path {}
+proc _which {what args} {
+ global env _search_exe _search_path
+
+ if {$_search_path eq {}} {
+ if {[is_Windows]} {
+ set gitguidir [file dirname [info script]]
+ regsub -all ";" $gitguidir "\\;" gitguidir
+ set env(PATH) "$gitguidir;$env(PATH)"
+ set _search_path [split $env(PATH) {;}]
+ # Skip empty `PATH` elements
+ set _search_path [lsearch -all -inline -not -exact \
+ $_search_path ""]
+ set _search_exe .exe
+ } else {
+ set _search_path [split $env(PATH) :]
+ set _search_exe {}
+ }
+ }
+
+ if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
+ set suffix {}
+ } else {
+ set suffix $_search_exe
+ }
+
+ foreach p $_search_path {
+ set p [file join $p $what$suffix]
+ if {[file exists $p]} {
+ return [file normalize $p]
+ }
+ }
+ return {}
+}
+
+proc sanitize_command_line {command_line from_index} {
+ set i $from_index
+ while {$i < [llength $command_line]} {
+ set cmd [lindex $command_line $i]
+ if {[file pathtype $cmd] ne "absolute"} {
+ set fullpath [_which $cmd]
+ if {$fullpath eq ""} {
+ throw {NOT-FOUND} "$cmd not found in PATH"
+ }
+ lset command_line $i $fullpath
+ }
+
+ # handle piped commands, e.g. `exec A | B`
+ for {incr i} {$i < [llength $command_line]} {incr i} {
+ if {[lindex $command_line $i] eq "|"} {
+ incr i
+ break
+ }
+ }
+ }
+ return $command_line
+}
+
+# Override `exec` to avoid unsafe PATH lookup
+
+rename exec real_exec
+
+proc exec {args} {
+ # skip options
+ for {set i 0} {$i < [llength $args]} {incr i} {
+ set arg [lindex $args $i]
+ if {$arg eq "--"} {
+ incr i
+ break
+ }
+ if {[string range $arg 0 0] ne "-"} {
+ break
+ }
+ }
+ set args [sanitize_command_line $args $i]
+ uplevel 1 real_exec $args
+}
+
+# Override `open` to avoid unsafe PATH lookup
+
+rename open real_open
+
+proc open {args} {
+ set arg0 [lindex $args 0]
+ if {[string range $arg0 0 0] eq "|"} {
+ set command_line [string trim [string range $arg0 1 end]]
+ lset args 0 "| [sanitize_command_line $command_line 0]"
+ }
+ uplevel 1 real_open $args
+}
+
+######################################################################
+##
## locate our library
if { [info exists ::env(GIT_GUI_LIB_DIR) ] } {
@@ -163,8 +289,6 @@ set _isbare {}
set _gitexec {}
set _githtmldir {}
set _reponame {}
-set _iscygwin {}
-set _search_path {}
set _shellpath {@@SHELL_PATH@@}
set _trace [lsearch -exact $argv --trace]
@@ -211,14 +335,7 @@ proc gitexec {args} {
if {[catch {set _gitexec [git --exec-path]} err]} {
error "Git not installed?\n\n$err"
}
- if {[is_Cygwin]} {
- set _gitexec [exec cygpath \
- --windows \
- --absolute \
- $_gitexec]
- } else {
- set _gitexec [file normalize $_gitexec]
- }
+ set _gitexec [file normalize $_gitexec]
}
if {$args eq {}} {
return $_gitexec
@@ -233,14 +350,7 @@ proc githtmldir {args} {
# Git not installed or option not yet supported
return {}
}
- if {[is_Cygwin]} {
- set _githtmldir [exec cygpath \
- --windows \
- --absolute \
- $_githtmldir]
- } else {
- set _githtmldir [file normalize $_githtmldir]
- }
+ set _githtmldir [file normalize $_githtmldir]
}
if {$args eq {}} {
return $_githtmldir
@@ -252,40 +362,6 @@ proc reponame {} {
return $::_reponame
}
-proc is_MacOSX {} {
- if {[tk windowingsystem] eq {aqua}} {
- return 1
- }
- return 0
-}
-
-proc is_Windows {} {
- if {$::tcl_platform(platform) eq {windows}} {
- return 1
- }
- return 0
-}
-
-proc is_Cygwin {} {
- global _iscygwin
- if {$_iscygwin eq {}} {
- if {$::tcl_platform(platform) eq {windows}} {
- if {[catch {set p [exec cygpath --windir]} err]} {
- set _iscygwin 0
- } else {
- set _iscygwin 1
- # Handle MSys2 which is only cygwin when MSYSTEM is MSYS.
- if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} {
- set _iscygwin 0
- }
- }
- } else {
- set _iscygwin 0
- }
- }
- return $_iscygwin
-}
-
proc is_enabled {option} {
global enabled_options
if {[catch {set on $enabled_options($option)}]} {return 0}
@@ -448,44 +524,6 @@ proc _git_cmd {name} {
return $v
}
-proc _which {what args} {
- global env _search_exe _search_path
-
- if {$_search_path eq {}} {
- if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
- set _search_path [split [exec cygpath \
- --windows \
- --path \
- --absolute \
- $env(PATH)] {;}]
- set _search_exe .exe
- } elseif {[is_Windows]} {
- set gitguidir [file dirname [info script]]
- regsub -all ";" $gitguidir "\\;" gitguidir
- set env(PATH) "$gitguidir;$env(PATH)"
- set _search_path [split $env(PATH) {;}]
- set _search_exe .exe
- } else {
- set _search_path [split $env(PATH) :]
- set _search_exe {}
- }
- }
-
- if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
- set suffix {}
- } else {
- set suffix $_search_exe
- }
-
- foreach p $_search_path {
- set p [file join $p $what$suffix]
- if {[file exists $p]} {
- return [file normalize $p]
- }
- }
- return {}
-}
-
# Test a file for a hashbang to identify executable scripts on Windows.
proc is_shellscript {filename} {
if {![file exists $filename]} {return 0}
@@ -1259,9 +1297,6 @@ if {$_gitdir eq "."} {
set _gitdir [pwd]
}
-if {![file isdirectory $_gitdir] && [is_Cygwin]} {
- catch {set _gitdir [exec cygpath --windows $_gitdir]}
-}
if {![file isdirectory $_gitdir]} {
catch {wm withdraw .}
error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
@@ -1273,11 +1308,7 @@ apply_config
# v1.7.0 introduced --show-toplevel to return the canonical work-tree
if {[package vcompare $_git_version 1.7.0] >= 0} {
- if { [is_Cygwin] } {
- catch {set _gitworktree [exec cygpath --windows [git rev-parse --show-toplevel]]}
- } else {
- set _gitworktree [git rev-parse --show-toplevel]
- }
+ set _gitworktree [git rev-parse --show-toplevel]
} else {
# try to set work tree from environment, core.worktree or use
# cdup to obtain a relative path to the top of the worktree. If
@@ -1502,24 +1533,8 @@ proc rescan {after {honor_trustmtime 1}} {
}
}
-if {[is_Cygwin]} {
- set is_git_info_exclude {}
- proc have_info_exclude {} {
- global is_git_info_exclude
-
- if {$is_git_info_exclude eq {}} {
- if {[catch {exec test -f [gitdir info exclude]}]} {
- set is_git_info_exclude 0
- } else {
- set is_git_info_exclude 1
- }
- }
- return $is_git_info_exclude
- }
-} else {
- proc have_info_exclude {} {
- return [file readable [gitdir info exclude]]
- }
+proc have_info_exclude {} {
+ return [file readable [gitdir info exclude]]
}
proc rescan_stage2 {fd after} {
@@ -2259,7 +2274,9 @@ proc do_git_gui {} {
# Get the system-specific explorer app/command.
proc get_explorer {} {
- if {[is_Cygwin] || [is_Windows]} {
+ if {[is_Cygwin]} {
+ set explorer "/bin/cygstart.exe --explore"
+ } elseif {[is_Windows]} {
set explorer "explorer.exe"
} elseif {[is_MacOSX]} {
set explorer "open"
@@ -3053,10 +3070,6 @@ if {[is_MacOSX]} {
set doc_path [githtmldir]
if {$doc_path ne {}} {
set doc_path [file join $doc_path index.html]
-
- if {[is_Cygwin]} {
- set doc_path [exec cygpath --mixed $doc_path]
- }
}
if {[file isfile $doc_path]} {
@@ -4028,60 +4041,6 @@ set file_lists($ui_workdir) [list]
wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
focus -force $ui_comm
-# -- Warn the user about environmental problems. Cygwin's Tcl
-# does *not* pass its env array onto any processes it spawns.
-# This means that git processes get none of our environment.
-#
-if {[is_Cygwin]} {
- set ignored_env 0
- set suggest_user {}
- set msg [mc "Possible environment issues exist.
-
-The following environment variables are probably
-going to be ignored by any Git subprocess run
-by %s:
-
-" [appname]]
- foreach name [array names env] {
- switch -regexp -- $name {
- {^GIT_INDEX_FILE$} -
- {^GIT_OBJECT_DIRECTORY$} -
- {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
- {^GIT_DIFF_OPTS$} -
- {^GIT_EXTERNAL_DIFF$} -
- {^GIT_PAGER$} -
- {^GIT_TRACE$} -
- {^GIT_CONFIG$} -
- {^GIT_(AUTHOR|COMMITTER)_DATE$} {
- append msg " - $name\n"
- incr ignored_env
- }
- {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
- append msg " - $name\n"
- incr ignored_env
- set suggest_user $name
- }
- }
- }
- if {$ignored_env > 0} {
- append msg [mc "
-This is due to a known issue with the
-Tcl binary distributed by Cygwin."]
-
- if {$suggest_user ne {}} {
- append msg [mc "
-
-A good replacement for %s
-is placing values for the user.name and
-user.email settings into your personal
-~/.gitconfig file.
-" $suggest_user]
- }
- warn_popup $msg
- }
- unset ignored_env msg suggest_user name
-}
-
# -- Only initialize complex UI if we are going to stay running.
#
if {[is_enabled transport]} {
diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
index af1fee7c75..d23abedcb3 100644
--- a/git-gui/lib/choose_repository.tcl
+++ b/git-gui/lib/choose_repository.tcl
@@ -174,9 +174,6 @@ constructor pick {} {
-foreground blue \
-underline 1
set home $::env(HOME)
- if {[is_Cygwin]} {
- set home [exec cygpath --windows --absolute $home]
- }
set home "[file normalize $home]/"
set hlen [string length $home]
foreach p $sorted_recent {
@@ -374,18 +371,6 @@ proc _objdir {path} {
return $objdir
}
- if {[is_Cygwin]} {
- set objdir [file join $path .git objects.lnk]
- if {[file isfile $objdir]} {
- return [win32_read_lnk $objdir]
- }
-
- set objdir [file join $path objects.lnk]
- if {[file isfile $objdir]} {
- return [win32_read_lnk $objdir]
- }
- }
-
return {}
}
@@ -623,12 +608,6 @@ method _do_clone2 {} {
}
set giturl $origin_url
- if {[is_Cygwin] && [file isdirectory $giturl]} {
- set giturl [exec cygpath --unix --absolute $giturl]
- if {$clone_type eq {shared}} {
- set objdir [exec cygpath --unix --absolute $objdir]
- }
- }
if {[file exists $local_path]} {
error_popup [mc "Location %s already exists." $local_path]
@@ -668,11 +647,7 @@ method _do_clone2 {} {
fconfigure $f_cp -translation binary -encoding binary
cd $objdir
while {[gets $f_in line] >= 0} {
- if {[is_Cygwin]} {
- puts $f_cp [exec cygpath --unix --absolute $line]
- } else {
- puts $f_cp [file normalize $line]
- }
+ puts $f_cp [file normalize $line]
}
close $f_in
close $f_cp
diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl
index 97d1d7aa02..674a41f5e0 100644
--- a/git-gui/lib/shortcut.tcl
+++ b/git-gui/lib/shortcut.tcl
@@ -27,13 +27,10 @@ proc do_windows_shortcut {} {
}
proc do_cygwin_shortcut {} {
- global argv0 _gitworktree
+ global argv0 _gitworktree oguilib
if {[catch {
set desktop [exec cygpath \
- --windows \
- --absolute \
- --long-name \
--desktop]
}]} {
set desktop .
@@ -48,19 +45,19 @@ proc do_cygwin_shortcut {} {
set fn ${fn}.lnk
}
if {[catch {
- set sh [exec cygpath \
- --windows \
- --absolute \
- /bin/sh.exe]
- set me [exec cygpath \
- --unix \
- --absolute \
- $argv0]
- win32_create_lnk $fn [list \
- $sh -c \
- "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \
- ] \
- [file normalize $_gitworktree]
+ set repodir [file normalize $_gitworktree]
+ set shargs {-c \
+ "CHERE_INVOKING=1 \
+ source /etc/profile; \
+ git gui"}
+ exec /bin/mkshortcut.exe \
+ --arguments $shargs \
+ --desc "git-gui on $repodir" \
+ --icon $oguilib/git-gui.ico \
+ --name $fn \
+ --show min \
+ --workingdir $repodir \
+ /bin/sh.exe
} err]} {
error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
}
diff --git a/parse-options.c b/parse-options.c
index 60224cf8d0..76d2e76b49 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -1023,14 +1023,37 @@ static int usage_argh(const struct option *opts, FILE *outfile)
return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
}
-#define USAGE_OPTS_WIDTH 24
-#define USAGE_GAP 2
+static int usage_indent(FILE *outfile)
+{
+ return fprintf(outfile, " ");
+}
+
+#define USAGE_OPTS_WIDTH 26
+
+static void usage_padding(FILE *outfile, size_t pos)
+{
+ if (pos < USAGE_OPTS_WIDTH)
+ fprintf(outfile, "%*s", USAGE_OPTS_WIDTH - (int)pos, "");
+ else
+ fprintf(outfile, "\n%*s", USAGE_OPTS_WIDTH, "");
+}
+
+static const struct option *find_option_by_long_name(const struct option *opts,
+ const char *long_name)
+{
+ for (; opts->type != OPTION_END; opts++) {
+ if (opts->long_name && !strcmp(opts->long_name, long_name))
+ return opts;
+ }
+ return NULL;
+}
static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx,
const char * const *usagestr,
const struct option *opts,
int full, int err)
{
+ const struct option *all_opts = opts;
FILE *outfile = err ? stderr : stdout;
int need_newline;
@@ -1111,8 +1134,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
for (; opts->type != OPTION_END; opts++) {
size_t pos;
- int pad;
const char *cp, *np;
+ const char *positive_name = NULL;
if (opts->type == OPTION_SUBCOMMAND)
continue;
@@ -1131,7 +1154,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
need_newline = 0;
}
- pos = fprintf(outfile, " ");
+ pos = usage_indent(outfile);
if (opts->short_name) {
if (opts->flags & PARSE_OPT_NODASH)
pos += fprintf(outfile, "%c", opts->short_name);
@@ -1140,8 +1163,15 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
}
if (opts->long_name && opts->short_name)
pos += fprintf(outfile, ", ");
- if (opts->long_name)
- pos += fprintf(outfile, "--%s", opts->long_name);
+ if (opts->long_name) {
+ const char *long_name = opts->long_name;
+ if ((opts->flags & PARSE_OPT_NONEG) ||
+ skip_prefix(long_name, "no-", &positive_name))
+ pos += fprintf(outfile, "--%s", long_name);
+ else
+ pos += fprintf(outfile, "--[no-]%s", long_name);
+ }
+
if (opts->type == OPTION_NUMBER)
pos += utf8_fprintf(outfile, _("-NUM"));
@@ -1149,16 +1179,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
!(opts->flags & PARSE_OPT_NOARG))
pos += usage_argh(opts, outfile);
- if (pos == USAGE_OPTS_WIDTH + 1)
- pad = -1;
- else if (pos <= USAGE_OPTS_WIDTH)
- pad = USAGE_OPTS_WIDTH - pos;
- else {
- fputc('\n', outfile);
- pad = USAGE_OPTS_WIDTH;
- }
if (opts->type == OPTION_ALIAS) {
- fprintf(outfile, "%*s", pad + USAGE_GAP, "");
+ usage_padding(outfile, pos);
fprintf_ln(outfile, _("alias of --%s"),
(const char *)opts->value);
continue;
@@ -1166,12 +1188,21 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
for (cp = _(opts->help); *cp; cp = np) {
np = strchrnul(cp, '\n');
- fprintf(outfile,
- "%*s%.*s\n", pad + USAGE_GAP, "",
- (int)(np - cp), cp);
+ usage_padding(outfile, pos);
+ fprintf(outfile, "%.*s\n", (int)(np - cp), cp);
if (*np)
np++;
- pad = USAGE_OPTS_WIDTH;
+ pos = 0;
+ }
+
+ if (positive_name) {
+ if (find_option_by_long_name(all_opts, positive_name))
+ continue;
+ pos = usage_indent(outfile);
+ pos += fprintf(outfile, "--%s", positive_name);
+ usage_padding(outfile, pos);
+ fprintf_ln(outfile, _("opposite of --no-%s"),
+ positive_name);
}
}
fputc('\n', outfile);
diff --git a/range-diff.c b/range-diff.c
index 2e86063491..ca5493984a 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -230,16 +230,19 @@ cleanup:
}
static int patch_util_cmp(const void *cmp_data UNUSED,
- const struct patch_util *a,
- const struct patch_util *b,
- const char *keydata)
+ const struct hashmap_entry *ha,
+ const struct hashmap_entry *hb,
+ const void *keydata)
{
+ const struct patch_util
+ *a = container_of(ha, const struct patch_util, e),
+ *b = container_of(hb, const struct patch_util, e);
return strcmp(a->diff, keydata ? keydata : b->diff);
}
static void find_exact_matches(struct string_list *a, struct string_list *b)
{
- struct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL);
+ struct hashmap map = HASHMAP_INIT(patch_util_cmp, NULL);
int i;
/* First, add the patches of a to a hash map */
diff --git a/scalar.c b/scalar.c
index df7358f481..fb2940c2a0 100644
--- a/scalar.c
+++ b/scalar.c
@@ -409,6 +409,7 @@ static int cmd_clone(int argc, const char **argv)
{
const char *branch = NULL;
int full_clone = 0, single_branch = 0, show_progress = isatty(2);
+ int src = 1;
struct option clone_options[] = {
OPT_STRING('b', "branch", &branch, N_("<branch>"),
N_("branch to checkout after clone")),
@@ -417,10 +418,13 @@ static int cmd_clone(int argc, const char **argv)
OPT_BOOL(0, "single-branch", &single_branch,
N_("only download metadata for the branch that will "
"be checked out")),
+ OPT_BOOL(0, "src", &src,
+ N_("create repository within 'src' directory")),
OPT_END(),
};
const char * const clone_usage[] = {
- N_("scalar clone [<options>] [--] <repo> [<dir>]"),
+ N_("scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]\n"
+ "\t[--[no-]src] <url> [<enlistment>]"),
NULL
};
const char *url;
@@ -456,7 +460,10 @@ static int cmd_clone(int argc, const char **argv)
if (is_directory(enlistment))
die(_("directory '%s' exists already"), enlistment);
- dir = xstrfmt("%s/src", enlistment);
+ if (src)
+ dir = xstrfmt("%s/src", enlistment);
+ else
+ dir = xstrdup(enlistment);
strbuf_reset(&buf);
if (branch)
@@ -657,6 +664,7 @@ static int cmd_reconfigure(int argc, const char **argv)
git_config(get_scalar_repos, &scalar_repos);
for (i = 0; i < scalar_repos.nr; i++) {
+ int succeeded = 0;
const char *dir = scalar_repos.items[i].string;
strbuf_reset(&commondir);
@@ -667,30 +675,56 @@ static int cmd_reconfigure(int argc, const char **argv)
if (errno != ENOENT) {
warning_errno(_("could not switch to '%s'"), dir);
- res = -1;
- continue;
+ goto loop_end;
}
strbuf_addstr(&buf, dir);
if (remove_deleted_enlistment(&buf))
- res = error(_("could not remove stale "
- "scalar.repo '%s'"), dir);
- else
- warning(_("removing stale scalar.repo '%s'"),
+ error(_("could not remove stale "
+ "scalar.repo '%s'"), dir);
+ else {
+ warning(_("removed stale scalar.repo '%s'"),
dir);
+ succeeded = 1;
+ }
strbuf_release(&buf);
- } else if (discover_git_directory(&commondir, &gitdir) < 0) {
- warning_errno(_("git repository gone in '%s'"), dir);
- res = -1;
- } else {
- git_config_clear();
+ goto loop_end;
+ }
+
+ switch (discover_git_directory_reason(&commondir, &gitdir)) {
+ case GIT_DIR_INVALID_OWNERSHIP:
+ warning(_("repository at '%s' has different owner"), dir);
+ goto loop_end;
+
+ case GIT_DIR_INVALID_GITFILE:
+ case GIT_DIR_INVALID_FORMAT:
+ warning(_("repository at '%s' has a format issue"), dir);
+ goto loop_end;
+
+ case GIT_DIR_DISCOVERED:
+ succeeded = 1;
+ break;
+
+ default:
+ warning(_("repository not found in '%s'"), dir);
+ break;
+ }
- the_repository = &r;
- r.commondir = commondir.buf;
- r.gitdir = gitdir.buf;
+ git_config_clear();
- if (set_recommended_config(1) < 0)
- res = -1;
+ the_repository = &r;
+ r.commondir = commondir.buf;
+ r.gitdir = gitdir.buf;
+
+ if (set_recommended_config(1) >= 0)
+ succeeded = 1;
+
+loop_end:
+ if (!succeeded) {
+ res = -1;
+ warning(_("to unregister this repository from Scalar, run\n"
+ "\tgit config --global --unset --fixed-value scalar.repo \"%s\""),
+ dir);
}
}
diff --git a/sequencer.c b/sequencer.c
index 5e0c15a16b..a66dcf8ab2 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -51,6 +51,15 @@
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+/*
+ * To accommodate common filesystem limitations, where the loose refs' file
+ * names must not exceed `NAME_MAX`, the labels generated by `git rebase
+ * --rebase-merges` need to be truncated if the corresponding commit subjects
+ * are too long.
+ * Add some margin to stay clear from reaching `NAME_MAX`.
+ */
+#define GIT_MAX_LABEL_LENGTH ((NAME_MAX) - (LOCK_SUFFIX_LEN) - 16)
+
static const char sign_off_header[] = "Signed-off-by: ";
static const char cherry_picked_prefix[] = "(cherry picked from commit ";
@@ -5352,6 +5361,7 @@ struct label_state {
struct oidmap commit2label;
struct hashmap labels;
struct strbuf buf;
+ int max_label_length;
};
static const char *label_oid(struct object_id *oid, const char *label,
@@ -5408,6 +5418,8 @@ static const char *label_oid(struct object_id *oid, const char *label,
}
} else {
struct strbuf *buf = &state->buf;
+ int label_is_utf8 = 1; /* start with this assumption */
+ size_t max_len = buf->len + state->max_label_length;
/*
* Sanitize labels by replacing non-alpha-numeric characters
@@ -5416,14 +5428,34 @@ static const char *label_oid(struct object_id *oid, const char *label,
*
* Note that we retain non-ASCII UTF-8 characters (identified
* via the most significant bit). They should be all acceptable
- * in file names. We do not validate the UTF-8 here, that's not
- * the job of this function.
+ * in file names.
+ *
+ * As we will use the labels as names of (loose) refs, it is
+ * vital that the name not be longer than the maximum component
+ * size of the file system (`NAME_MAX`). We are careful to
+ * truncate the label accordingly, allowing for the `.lock`
+ * suffix and for the label to be UTF-8 encoded (i.e. we avoid
+ * truncating in the middle of a character).
*/
- for (; *label; label++)
- if ((*label & 0x80) || isalnum(*label))
+ for (; *label && buf->len + 1 < max_len; label++)
+ if (isalnum(*label) ||
+ (!label_is_utf8 && (*label & 0x80)))
strbuf_addch(buf, *label);
+ else if (*label & 0x80) {
+ const char *p = label;
+
+ utf8_width(&p, NULL);
+ if (p) {
+ if (buf->len + (p - label) > max_len)
+ break;
+ strbuf_add(buf, label, p - label);
+ label = p - 1;
+ } else {
+ label_is_utf8 = 0;
+ strbuf_addch(buf, *label);
+ }
/* avoid leading dash and double-dashes */
- else if (buf->len && buf->buf[buf->len - 1] != '-')
+ } else if (buf->len && buf->buf[buf->len - 1] != '-')
strbuf_addch(buf, '-');
if (!buf->len) {
strbuf_addstr(buf, "rev-");
@@ -5485,7 +5517,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
struct string_entry *entry;
struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
shown = OIDSET_INIT;
- struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
+ struct label_state state =
+ { OIDMAP_INIT, { NULL }, STRBUF_INIT, GIT_MAX_LABEL_LENGTH };
int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
const char *cmd_pick = abbr ? "p" : "pick",
@@ -5493,6 +5526,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
*cmd_reset = abbr ? "t" : "reset",
*cmd_merge = abbr ? "m" : "merge";
+ git_config_get_int("rebase.maxlabellength", &state.max_label_length);
+
oidmap_init(&commit2todo, 0);
oidmap_init(&state.commit2label, 0);
hashmap_init(&state.labels, labels_cmp, NULL, 0);
@@ -6194,7 +6229,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
if (checkout_onto(r, opts, onto_name, &oid, orig_head))
goto cleanup;
- if (require_clean_work_tree(r, "rebase", "", 1, 1))
+ if (require_clean_work_tree(r, "rebase", NULL, 1, 1))
goto cleanup;
todo_list_write_total_nr(&new_todo);
@@ -6245,7 +6280,7 @@ static int skip_fixupish(const char *subject, const char **p) {
int todo_list_rearrange_squash(struct todo_list *todo_list)
{
struct hashmap subject2item;
- int rearranged = 0, *next, *tail, i, nr = 0, alloc = 0;
+ int rearranged = 0, *next, *tail, i, nr = 0;
char **subjects;
struct commit_todo_item commit_todo;
struct todo_item *items = NULL;
@@ -6357,6 +6392,8 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
}
if (rearranged) {
+ ALLOC_ARRAY(items, todo_list->nr);
+
for (i = 0; i < todo_list->nr; i++) {
enum todo_command command = todo_list->items[i].command;
int cur = i;
@@ -6369,16 +6406,15 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
continue;
while (cur >= 0) {
- ALLOC_GROW(items, nr + 1, alloc);
items[nr++] = todo_list->items[cur];
cur = next[cur];
}
}
+ assert(nr == todo_list->nr);
+ todo_list->alloc = nr;
FREE_AND_NULL(todo_list->items);
todo_list->items = items;
- todo_list->nr = nr;
- todo_list->alloc = alloc;
}
free(next);
diff --git a/setup.c b/setup.c
index 18927a847b..2e607632db 100644
--- a/setup.c
+++ b/setup.c
@@ -1221,19 +1221,6 @@ static const char *allowed_bare_repo_to_string(
return NULL;
}
-enum discovery_result {
- GIT_DIR_NONE = 0,
- GIT_DIR_EXPLICIT,
- GIT_DIR_DISCOVERED,
- GIT_DIR_BARE,
- /* these are errors */
- GIT_DIR_HIT_CEILING = -1,
- GIT_DIR_HIT_MOUNT_POINT = -2,
- GIT_DIR_INVALID_GITFILE = -3,
- GIT_DIR_INVALID_OWNERSHIP = -4,
- GIT_DIR_DISALLOWED_BARE = -5,
-};
-
/*
* We cannot decide in this function whether we are in the work tree or
* not, since the config can only be read _after_ this function was called.
@@ -1385,21 +1372,23 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
}
}
-int discover_git_directory(struct strbuf *commondir,
- struct strbuf *gitdir)
+enum discovery_result discover_git_directory_reason(struct strbuf *commondir,
+ struct strbuf *gitdir)
{
struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;
size_t gitdir_offset = gitdir->len, cwd_len;
size_t commondir_offset = commondir->len;
struct repository_format candidate = REPOSITORY_FORMAT_INIT;
+ enum discovery_result result;
if (strbuf_getcwd(&dir))
- return -1;
+ return GIT_DIR_CWD_FAILURE;
cwd_len = dir.len;
- if (setup_git_directory_gently_1(&dir, gitdir, NULL, 0) <= 0) {
+ result = setup_git_directory_gently_1(&dir, gitdir, NULL, 0);
+ if (result <= 0) {
strbuf_release(&dir);
- return -1;
+ return result;
}
/*
@@ -1429,11 +1418,11 @@ int discover_git_directory(struct strbuf *commondir,
strbuf_setlen(commondir, commondir_offset);
strbuf_setlen(gitdir, gitdir_offset);
clear_repository_format(&candidate);
- return -1;
+ return GIT_DIR_INVALID_FORMAT;
}
clear_repository_format(&candidate);
- return 0;
+ return result;
}
const char *setup_git_directory_gently(int *nongit_ok)
@@ -1515,10 +1504,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
}
*nongit_ok = 1;
break;
- case GIT_DIR_NONE:
+ case GIT_DIR_CWD_FAILURE:
+ case GIT_DIR_INVALID_FORMAT:
/*
* As a safeguard against setup_git_directory_gently_1 returning
- * this value, fallthrough to BUG. Otherwise it is possible to
+ * these values, fallthrough to BUG. Otherwise it is possible to
* set startup_info->have_repository to 1 when we did nothing to
* find a repository.
*/
diff --git a/setup.h b/setup.h
index 58fd2605dd..b48cf1c43b 100644
--- a/setup.h
+++ b/setup.h
@@ -42,16 +42,45 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)
void setup_work_tree(void);
+
+/*
+ * discover_git_directory_reason() is similar to discover_git_directory(),
+ * except it returns an enum value instead. It is important to note that
+ * a zero-valued return here is actually GIT_DIR_NONE, which is different
+ * from discover_git_directory.
+ */
+enum discovery_result {
+ GIT_DIR_EXPLICIT = 1,
+ GIT_DIR_DISCOVERED = 2,
+ GIT_DIR_BARE = 3,
+ /* these are errors */
+ GIT_DIR_HIT_CEILING = -1,
+ GIT_DIR_HIT_MOUNT_POINT = -2,
+ GIT_DIR_INVALID_GITFILE = -3,
+ GIT_DIR_INVALID_OWNERSHIP = -4,
+ GIT_DIR_DISALLOWED_BARE = -5,
+ GIT_DIR_INVALID_FORMAT = -6,
+ GIT_DIR_CWD_FAILURE = -7,
+};
+enum discovery_result discover_git_directory_reason(struct strbuf *commondir,
+ struct strbuf *gitdir);
+
/*
* Find the commondir and gitdir of the repository that contains the current
* working directory, without changing the working directory or other global
* state. The result is appended to commondir and gitdir. If the discovered
* gitdir does not correspond to a worktree, then 'commondir' and 'gitdir' will
* both have the same result appended to the buffer. The return value is
- * either 0 upon success and non-zero if no repository was found.
+ * either 0 upon success and -1 if no repository was found.
*/
-int discover_git_directory(struct strbuf *commondir,
- struct strbuf *gitdir);
+static inline int discover_git_directory(struct strbuf *commondir,
+ struct strbuf *gitdir)
+{
+ if (discover_git_directory_reason(commondir, gitdir) <= 0)
+ return -1;
+ return 0;
+}
+
const char *setup_git_directory_gently(int *);
const char *setup_git_directory(void);
char *prefix_path(const char *prefix, int len, const char *path);
diff --git a/t/lib-credential.sh b/t/lib-credential.sh
index 032b2d8fcc..15fc9a31e2 100644
--- a/t/lib-credential.sh
+++ b/t/lib-credential.sh
@@ -43,6 +43,8 @@ helper_test_clean() {
reject $1 https example.com store-user
reject $1 https example.com user1
reject $1 https example.com user2
+ reject $1 https example.com user-expiry
+ reject $1 https example.com user-expiry-overwrite
reject $1 https example.com user4
reject $1 https example.com user-distinct-pass
reject $1 https example.com user-overwrite
@@ -431,6 +433,81 @@ helper_test_timeout() {
'
}
+helper_test_password_expiry_utc() {
+ HELPER=$1
+
+ test_expect_success "helper ($HELPER) stores password_expiry_utc" '
+ check approve $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=user-expiry
+ password=pass
+ password_expiry_utc=9999999999
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) gets password_expiry_utc" '
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=user-expiry
+ --
+ protocol=https
+ host=example.com
+ username=user-expiry
+ password=pass
+ password_expiry_utc=9999999999
+ --
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) overwrites when password_expiry_utc changes" '
+ check approve $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user-expiry-overwrite
+ password=pass1
+ password_expiry_utc=9999999998
+ EOF
+ check approve $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user-expiry-overwrite
+ password=pass2
+ password_expiry_utc=9999999999
+ EOF
+ check fill $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user-expiry-overwrite
+ --
+ protocol=https
+ host=example.com
+ username=user-expiry-overwrite
+ password=pass2
+ password_expiry_utc=9999999999
+ EOF
+ check reject $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user-expiry-overwrite
+ password=pass2
+ EOF
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=user-expiry-overwrite
+ --
+ protocol=https
+ host=example.com
+ username=user-expiry-overwrite
+ password=askpass-password
+ --
+ askpass: Password for '\''https://user-expiry-overwrite@example.com'\'':
+ EOF
+ '
+}
+
helper_test_oauth_refresh_token() {
HELPER=$1
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
index 4eebd9c2b5..83b83c9abb 100644
--- a/t/lib-gpg.sh
+++ b/t/lib-gpg.sh
@@ -45,6 +45,7 @@ test_lazy_prereq GPG '
"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
gpg --homedir "${GNUPGHOME}" --import-ownertrust \
"$TEST_DIRECTORY"/lib-gpg/ownertrust &&
+ gpg --homedir "${GNUPGHOME}" --update-trustdb &&
gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null \
--sign -u committer@example.com
;;
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index 7ca5b918f0..11d2dc9fe3 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -8,18 +8,21 @@
# - check that non-commit messages have a certain line count with $EXPECT_COUNT
# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT
# - rewrite a rebase -i script as directed by $FAKE_LINES.
-# $FAKE_LINES consists of a sequence of words separated by spaces.
-# The following word combinations are possible:
+# $FAKE_LINES consists of a sequence of words separated by spaces;
+# spaces inside the words are encoded as underscores.
+# The following words are possible:
#
-# "<lineno>" -- add a "pick" line with the SHA1 taken from the
-# specified line.
+# "<cmd>" -- override the command for the next line specification. Can be
+# "pick", "squash", "fixup[_-(c|C)]", "edit", "reword", "drop",
+# "merge[_-(c|C)_<rev>]", or "bad" for an invalid command.
#
-# "<cmd> <lineno>" -- add a line with the specified command
-# ("pick", "squash", "fixup"|"fixup_-C"|"fixup_-c", "edit", "reword" or "drop")
-# and the SHA1 taken from the specified line.
+# "<lineno>" -- add a command, using the specified line as a template.
+# If the command has not been overridden, the line will be copied
+# verbatim, usually resulting in a "pick" line.
#
-# "_" -- add a space, like "fixup_-C" implies "fixup -C" and
-# "exec_cmd_with_args" add an "exec cmd with args" line.
+# "fakesha" -- add a command ("pick" by default), using a fake SHA1.
+#
+# "exec_[command...]", "break" -- add the specified command.
#
# "#" -- Add a comment line.
#
@@ -49,7 +52,7 @@ set_fake_editor () {
action=\&
for line in $FAKE_LINES; do
case $line in
- pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d|label|l|reset|r|merge|m)
+ pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d|label|l|reset|t|merge|m)
action="$line";;
exec_*|x_*|break|b)
echo "$line" | sed 's/_/ /g' >> "$1";;
@@ -64,7 +67,7 @@ set_fake_editor () {
fakesha)
test \& != "$action" || action=pick
echo "$action XXXXXXX False commit" >> "$1"
- action=pick;;
+ action=\&;;
*)
sed -n "${line}s/^[a-z][a-z]*/$action/p" < "$1".tmp >> "$1"
action=\&;;
diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh
index 96ed3e1d69..39e92b0841 100755
--- a/t/perf/p2000-sparse-operations.sh
+++ b/t/perf/p2000-sparse-operations.sh
@@ -134,5 +134,6 @@ test_perf_on_all git diff-files -- $SPARSE_CONE/a
test_perf_on_all git diff-tree HEAD
test_perf_on_all git diff-tree HEAD -- $SPARSE_CONE/a
test_perf_on_all "git worktree add ../temp && git worktree remove ../temp"
+test_perf_on_all git check-attr -a -- $SPARSE_CONE/a
test_done
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index e19a199636..a0ad6192d6 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -13,30 +13,35 @@ usage: test-tool parse-options <options>
A helper function for the parse-options API.
- --yes get a boolean
+ --[no-]yes get a boolean
-D, --no-doubt begins with 'no-'
+ --doubt opposite of --no-doubt
-B, --no-fear be brave
- -b, --boolean increment by one
- -4, --or4 bitwise-or boolean with ...0100
- --neg-or4 same as --no-or4
+ -b, --[no-]boolean increment by one
+ -4, --[no-]or4 bitwise-or boolean with ...0100
+ --[no-]neg-or4 same as --no-or4
- -i, --integer <n> get a integer
+ -i, --[no-]integer <n>
+ get a integer
-j <n> get a integer, too
-m, --magnitude <n> get a magnitude
- --set23 set integer to 23
+ --[no-]set23 set integer to 23
--mode1 set integer to 1 (cmdmode option)
--mode2 set integer to 2 (cmdmode option)
- -L, --length <str> get length of <str>
- -F, --file <file> set file to <file>
+ -L, --[no-]length <str>
+ get length of <str>
+ -F, --[no-]file <file>
+ set file to <file>
String options
- -s, --string <string> get a string
- --string2 <str> get another string
- --st <st> get another string (pervert ordering)
+ -s, --[no-]string <string>
+ get a string
+ --[no-]string2 <str> get another string
+ --[no-]st <st> get another string (pervert ordering)
-o <str> get another string
--longhelp help text of this entry
spans multiple lines
- --list <str> add str to list
+ --[no-]list <str> add str to list
Magic arguments
-NUM set integer to NUM
@@ -45,16 +50,17 @@ Magic arguments
--no-ambiguous negative ambiguity
Standard options
- --abbrev[=<n>] use <n> digits to display object names
- -v, --verbose be verbose
- -n, --dry-run dry run
- -q, --quiet be quiet
- --expect <string> expected output in the variable dump
+ --[no-]abbrev[=<n>] use <n> digits to display object names
+ -v, --[no-]verbose be verbose
+ -n, --[no-]dry-run dry run
+ -q, --[no-]quiet be quiet
+ --[no-]expect <string>
+ expected output in the variable dump
Alias
- -A, --alias-source <string>
+ -A, --[no-]alias-source <string>
get a string
- -Z, --alias-target <string>
+ -Z, --[no-]alias-target <string>
alias of --alias-source
EOF
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
index c02a3b5969..8300faadea 100755
--- a/t/t0301-credential-cache.sh
+++ b/t/t0301-credential-cache.sh
@@ -29,6 +29,7 @@ test_atexit 'git credential-cache exit'
# test that the daemon works with no special setup
helper_test cache
+helper_test_password_expiry_utc cache
helper_test_oauth_refresh_token cache
test_expect_success 'socket defaults to ~/.cache/git/credential/socket' '
diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh
index f028fd1418..095574bfc6 100755
--- a/t/t0303-credential-external.sh
+++ b/t/t0303-credential-external.sh
@@ -45,6 +45,8 @@ test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
helper_test "$GIT_TEST_CREDENTIAL_HELPER"
+helper_test_password_expiry_utc "$GIT_TEST_CREDENTIAL_HELPER"
+helper_test_oauth_refresh_token "$GIT_TEST_CREDENTIAL_HELPER"
if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
say "# skipping timeout tests (GIT_TEST_CREDENTIAL_HELPER_TIMEOUT not set)"
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index 8a95adf4b5..2a4f35e984 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -2259,4 +2259,76 @@ test_expect_success 'worktree is not expanded' '
ensure_not_expanded worktree remove .worktrees/hotfix
'
+test_expect_success 'check-attr with pathspec inside sparse definition' '
+ init_repos &&
+
+ echo "a -crlf myAttr" >>.gitattributes &&
+ run_on_all cp ../.gitattributes ./deep &&
+
+ test_all_match git check-attr -a -- deep/a &&
+
+ test_all_match git add deep/.gitattributes &&
+ test_all_match git check-attr -a --cached -- deep/a
+'
+
+test_expect_success 'check-attr with pathspec outside sparse definition' '
+ init_repos &&
+
+ echo "a -crlf myAttr" >>.gitattributes &&
+ run_on_sparse mkdir folder1 &&
+ run_on_all cp ../.gitattributes ./folder1 &&
+ run_on_all cp a folder1/a &&
+
+ test_all_match git check-attr -a -- folder1/a &&
+
+ git -C full-checkout add folder1/.gitattributes &&
+ test_sparse_match git add --sparse folder1/.gitattributes &&
+ test_all_match git commit -m "add .gitattributes" &&
+ test_sparse_match git sparse-checkout reapply &&
+ test_all_match git check-attr -a --cached -- folder1/a
+'
+
+# NEEDSWORK: The 'diff --check' test is left as 'test_expect_failure' due
+# to an underlying issue in oneway_diff() within diff-lib.c.
+# 'do_oneway_diff()' is not called as expected for paths that could match
+# inside of a sparse directory. Specifically, the 'ce_path_match()' function
+# fails to recognize files inside a sparse directory (e.g., when 'folder1/'
+# is a sparse directory, 'folder1/a' cannot be recognized). The goal is to
+# proceed with 'do_oneway_diff()' if the pathspec could match inside of a
+# sparse directory.
+test_expect_failure 'diff --check with pathspec outside sparse definition' '
+ init_repos &&
+
+ write_script edit-contents <<-\EOF &&
+ echo "a " >"$1"
+ EOF
+
+ test_all_match git config core.whitespace -trailing-space,-space-before-tab &&
+
+ echo "a whitespace=trailing-space,space-before-tab" >>.gitattributes &&
+ run_on_all mkdir -p folder1 &&
+ run_on_all cp ../.gitattributes ./folder1 &&
+ test_all_match git add --sparse folder1/.gitattributes &&
+ run_on_all ../edit-contents folder1/a &&
+ test_all_match git add --sparse folder1/a &&
+
+ test_sparse_match git sparse-checkout reapply &&
+ test_all_match test_must_fail git diff --check --cached -- folder1/a
+'
+
+test_expect_success 'sparse-index is not expanded: check-attr' '
+ init_repos &&
+
+ echo "a -crlf myAttr" >>.gitattributes &&
+ mkdir ./sparse-index/folder1 &&
+ cp ./sparse-index/a ./sparse-index/folder1/a &&
+ cp .gitattributes ./sparse-index/deep &&
+ cp .gitattributes ./sparse-index/folder1 &&
+
+ git -C sparse-index add deep/.gitattributes &&
+ git -C sparse-index add --sparse folder1/.gitattributes &&
+ ensure_not_expanded check-attr -a --cached -- deep/a &&
+ ensure_not_expanded check-attr -a --cached -- folder1/a
+'
+
test_done
diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh
index dd811b7fb4..f0737593c3 100755
--- a/t/t1502-rev-parse-parseopt.sh
+++ b/t/t1502-rev-parse-parseopt.sh
@@ -3,13 +3,29 @@
test_description='test git rev-parse --parseopt'
. ./test-lib.sh
+check_invalid_long_option () {
+ spec="$1"
+ opt="$2"
+ test_expect_success "test --parseopt invalid switch $opt help output for $spec" '
+ {
+ cat <<-\EOF &&
+ error: unknown option `'${opt#--}\''
+ EOF
+ sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/$spec.help"
+ } >expect &&
+ test_expect_code 129 git rev-parse --parseopt -- $opt \
+ 2>output <"$TEST_DIRECTORY/t1502/$spec" &&
+ test_cmp expect output
+ '
+}
+
test_expect_success 'setup optionspec' '
sed -e "s/^|//" >optionspec <<\EOF
|some-command [options] <args>...
|
|some-command does foo and bar!
|--
-|h,help show the help
+|h,help! show the help
|
|foo some nifty option --foo
|bar= some cool option --bar with an argument
@@ -58,44 +74,8 @@ EOF
'
test_expect_success 'test --parseopt help output' '
- sed -e "s/^|//" >expect <<\END_EXPECT &&
-|cat <<\EOF
-|usage: some-command [options] <args>...
-|
-| some-command does foo and bar!
-|
-| -h, --help show the help
-| --foo some nifty option --foo
-| --bar ... some cool option --bar with an argument
-| -b, --baz a short and long option
-|
-|An option group Header
-| -C[...] option C with an optional argument
-| -d, --data[=...] short and long option with an optional argument
-|
-|Argument hints
-| -B <arg> short option required argument
-| --bar2 <arg> long option required argument
-| -e, --fuz <with-space>
-| short and long option required argument
-| -s[<some>] short option optional argument
-| --long[=<data>] long option optional argument
-| -g, --fluf[=<path>] short and long option optional argument
-| --longest <very-long-argument-hint>
-| a very long argument hint
-| --pair <key=value> with an equals sign in the hint
-| --aswitch help te=t contains? fl*g characters!`
-| --bswitch <hint> hint has trailing tab character
-| --cswitch switch has trailing tab character
-| --short-hint <a> with a one symbol hint
-|
-|Extras
-| --extra1 line above used to cause a segfault but no longer does
-|
-|EOF
-END_EXPECT
test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec &&
- test_cmp expect output
+ test_cmp "$TEST_DIRECTORY/t1502/optionspec.help" output
'
test_expect_success 'test --parseopt help output no switches' '
@@ -131,7 +111,7 @@ test_expect_success 'test --parseopt help-all output hidden switches' '
|
| some-command does foo and bar!
|
-| --hidden1 A hidden switch
+| --[no-]hidden1 A hidden switch
|
|EOF
END_EXPECT
@@ -140,41 +120,12 @@ END_EXPECT
'
test_expect_success 'test --parseopt invalid switch help output' '
- sed -e "s/^|//" >expect <<\END_EXPECT &&
-|error: unknown option `does-not-exist'\''
-|usage: some-command [options] <args>...
-|
-| some-command does foo and bar!
-|
-| -h, --help show the help
-| --foo some nifty option --foo
-| --bar ... some cool option --bar with an argument
-| -b, --baz a short and long option
-|
-|An option group Header
-| -C[...] option C with an optional argument
-| -d, --data[=...] short and long option with an optional argument
-|
-|Argument hints
-| -B <arg> short option required argument
-| --bar2 <arg> long option required argument
-| -e, --fuz <with-space>
-| short and long option required argument
-| -s[<some>] short option optional argument
-| --long[=<data>] long option optional argument
-| -g, --fluf[=<path>] short and long option optional argument
-| --longest <very-long-argument-hint>
-| a very long argument hint
-| --pair <key=value> with an equals sign in the hint
-| --aswitch help te=t contains? fl*g characters!`
-| --bswitch <hint> hint has trailing tab character
-| --cswitch switch has trailing tab character
-| --short-hint <a> with a one symbol hint
-|
-|Extras
-| --extra1 line above used to cause a segfault but no longer does
-|
-END_EXPECT
+ {
+ cat <<-\EOF &&
+ error: unknown option `does-not-exist'\''
+ EOF
+ sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/optionspec.help"
+ } >expect &&
test_expect_code 129 git rev-parse --parseopt -- --does-not-exist 1>/dev/null 2>output < optionspec &&
test_cmp expect output
'
@@ -288,7 +239,7 @@ test_expect_success 'test --parseopt help output: "wrapped" options normal "or:"
| [--another-option]
|cmd [--yet-another-option]
|--
- |h,help show the help
+ |h,help! show the help
EOF
sed -e "s/^|//" >expect <<-\END_EXPECT &&
@@ -322,7 +273,7 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l
|line
|blurb
|--
- |h,help show the help
+ |h,help! show the help
EOF
sed -e "s/^|//" >expect <<-\END_EXPECT &&
@@ -343,4 +294,32 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l
test_cmp expect actual
'
+test_expect_success 'test --parseopt help output for optionspec-neg' '
+ test_expect_code 129 git rev-parse --parseopt -- \
+ -h >output <"$TEST_DIRECTORY/t1502/optionspec-neg" &&
+ test_cmp "$TEST_DIRECTORY/t1502/optionspec-neg.help" output
+'
+
+test_expect_success 'test --parseopt valid options for optionspec-neg' '
+ cat >expect <<-\EOF &&
+ set -- --foo --no-foo --no-bar --positive-only --no-negative --
+ EOF
+ git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \
+ --foo --no-foo --no-bar --positive-only --no-negative &&
+ test_cmp expect output
+'
+
+test_expect_success 'test --parseopt positivated option for optionspec-neg' '
+ cat >expect <<-\EOF &&
+ set -- --no-no-bar --no-no-bar --
+ EOF
+ git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \
+ --no-no-bar --bar &&
+ test_cmp expect output
+'
+
+check_invalid_long_option optionspec-neg --no-positive-only
+check_invalid_long_option optionspec-neg --negative
+check_invalid_long_option optionspec-neg --no-no-negative
+
test_done
diff --git a/t/t1502/.gitattributes b/t/t1502/.gitattributes
new file mode 100644
index 0000000000..562b12e16e
--- /dev/null
+++ b/t/t1502/.gitattributes
@@ -0,0 +1 @@
+* -whitespace
diff --git a/t/t1502/optionspec-neg b/t/t1502/optionspec-neg
new file mode 100644
index 0000000000..392f43eb0b
--- /dev/null
+++ b/t/t1502/optionspec-neg
@@ -0,0 +1,8 @@
+some-command [options] <args>...
+
+some-command does foo and bar!
+--
+foo can be negated
+no-bar can be positivated
+positive-only! cannot be negated
+no-negative! cannot be positivated
diff --git a/t/t1502/optionspec-neg.help b/t/t1502/optionspec-neg.help
new file mode 100644
index 0000000000..7a29f8cb03
--- /dev/null
+++ b/t/t1502/optionspec-neg.help
@@ -0,0 +1,12 @@
+cat <<\EOF
+usage: some-command [options] <args>...
+
+ some-command does foo and bar!
+
+ --[no-]foo can be negated
+ --no-bar can be positivated
+ --bar opposite of --no-bar
+ --positive-only cannot be negated
+ --no-negative cannot be positivated
+
+EOF
diff --git a/t/t1502/optionspec.help b/t/t1502/optionspec.help
new file mode 100755
index 0000000000..cbdd54d41b
--- /dev/null
+++ b/t/t1502/optionspec.help
@@ -0,0 +1,36 @@
+cat <<\EOF
+usage: some-command [options] <args>...
+
+ some-command does foo and bar!
+
+ -h, --help show the help
+ --[no-]foo some nifty option --foo
+ --[no-]bar ... some cool option --bar with an argument
+ -b, --[no-]baz a short and long option
+
+An option group Header
+ -C[...] option C with an optional argument
+ -d, --[no-]data[=...] short and long option with an optional argument
+
+Argument hints
+ -B <arg> short option required argument
+ --[no-]bar2 <arg> long option required argument
+ -e, --[no-]fuz <with-space>
+ short and long option required argument
+ -s[<some>] short option optional argument
+ --[no-]long[=<data>] long option optional argument
+ -g, --[no-]fluf[=<path>]
+ short and long option optional argument
+ --[no-]longest <very-long-argument-hint>
+ a very long argument hint
+ --[no-]pair <key=value>
+ with an equals sign in the hint
+ --[no-]aswitch help te=t contains? fl*g characters!`
+ --[no-]bswitch <hint> hint has trailing tab character
+ --[no-]cswitch switch has trailing tab character
+ --[no-]short-hint <a> with a one symbol hint
+
+Extras
+ --[no-]extra1 line above used to cause a segfault but no longer does
+
+EOF
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index 051363acbb..df4aff7825 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -121,7 +121,8 @@ test_expect_success '"add" worktree creating new branch' '
test_expect_success 'die the same branch is already checked out' '
(
cd here &&
- test_must_fail git checkout newmain
+ test_must_fail git checkout newmain 2>actual &&
+ grep "already used by worktree at" actual
)
'
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index daf1666df7..080e4f24a6 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -942,7 +942,19 @@ test_expect_success 'test deleting branch without config' '
test_expect_success 'deleting currently checked out branch fails' '
git worktree add -b my7 my7 &&
test_must_fail git -C my7 branch -d my7 &&
- test_must_fail git branch -d my7 &&
+ test_must_fail git branch -d my7 2>actual &&
+ grep "^error: Cannot delete branch .my7. used by worktree at " actual &&
+ rm -r my7 &&
+ git worktree prune
+'
+
+test_expect_success 'deleting in-use branch fails' '
+ git worktree add my7 &&
+ test_commit -C my7 bt7 &&
+ git -C my7 bisect start HEAD HEAD~2 &&
+ test_must_fail git -C my7 branch -d my7 &&
+ test_must_fail git branch -d my7 2>actual &&
+ grep "^error: Cannot delete branch .my7. used by worktree at " actual &&
rm -r my7 &&
git worktree prune
'
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 3ce918fdb8..d3df19a51f 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -421,7 +421,7 @@ test_expect_success 'refuse to switch to branch checked out elsewhere' '
git checkout main &&
git worktree add wt &&
test_must_fail git -C wt rebase main main 2>err &&
- test_i18ngrep "already checked out" err
+ test_i18ngrep "already used by worktree at" err
'
test_expect_success MINGW,SYMLINKS_WINDOWS 'rebase when .git/logs is a symlink' '
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index 96ae0edf1e..ac5c390652 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -586,4 +586,15 @@ test_expect_success 'progress shows the correct total' '
test_line_count = 14 progress
'
+test_expect_success 'truncate label names' '
+ commit=$(git commit-tree -p HEAD^ -p HEAD -m "0123456789 我 123" HEAD^{tree}) &&
+ git merge --ff-only $commit &&
+
+ done="$(git rev-parse --git-path rebase-merge/done)" &&
+ git -c rebase.maxLabelLength=14 rebase --rebase-merges -x "cp \"$done\" out" --root &&
+ grep "label 0123456789-我$" out &&
+ git -c rebase.maxLabelLength=13 rebase --rebase-merges -x "cp \"$done\" out" --root &&
+ grep "label 0123456789-$" out
+'
+
test_done
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index b298f220e0..fcd2473e52 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright (c) 2006 Johannes E. Schindelin
-#
+# Copyright (c) 2023 Google LLC
test_description='Test special whitespace in diff engine.
@@ -11,6 +11,43 @@ TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-diff.sh
+for opt_res in --patch --quiet -s --stat --shortstat --dirstat=lines \
+ --raw! --name-only! --name-status!
+do
+ opts=${opt_res%!} expect_failure=
+ test "$opts" = "$opt_res" ||
+ expect_failure="test_expect_code 1"
+
+ test_expect_success "status with $opts (different)" '
+ echo foo >x &&
+ git add x &&
+ echo bar >x &&
+ test_expect_code 1 git diff -w $opts --exit-code x
+ '
+
+ test_expect_success POSIXPERM "status with $opts (mode differs)" '
+ test_when_finished "git update-index --chmod=-x x" &&
+ echo foo >x &&
+ git add x &&
+ git update-index --chmod=+x x &&
+ test_expect_code 1 git diff -w $opts --exit-code x
+ '
+
+ test_expect_success "status with $opts (removing an empty file)" '
+ : >x &&
+ git add x &&
+ rm x &&
+ test_expect_code 1 git diff -w $opts --exit-code -- x
+ '
+
+ test_expect_success "status with $opts (different but equivalent)" '
+ echo foo >x &&
+ git add x &&
+ echo " foo" >x &&
+ $expect_failure git diff -w $opts --exit-code x
+ '
+done
+
test_expect_success "Ray Lehtiniemi's example" '
cat <<-\EOF >x &&
do {
diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh
index e70e020ae9..eec3d73dc2 100755
--- a/t/t4040-whitespace-status.sh
+++ b/t/t4040-whitespace-status.sh
@@ -28,8 +28,7 @@ test_expect_success 'diff-tree --exit-code' '
test_expect_success 'diff-tree -b --exit-code' '
git diff -b --exit-code HEAD^ HEAD &&
- git diff-tree -b -p --exit-code HEAD^ HEAD &&
- git diff-tree -b --exit-code HEAD^ HEAD
+ git diff-tree -b -p --exit-code HEAD^ HEAD
'
test_expect_success 'diff-index --cached --exit-code' '
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index 4df76173a8..ba65f17dd9 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -450,14 +450,15 @@ GRAPH_BYTE_FANOUT2=$(($GRAPH_FANOUT_OFFSET + 4 * 255))
GRAPH_OID_LOOKUP_OFFSET=$(($GRAPH_FANOUT_OFFSET + 4 * 256))
GRAPH_BYTE_OID_LOOKUP_ORDER=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * 8))
GRAPH_BYTE_OID_LOOKUP_MISSING=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * 4 + 10))
+GRAPH_COMMIT_DATA_WIDTH=$(($HASH_LEN + 16))
GRAPH_COMMIT_DATA_OFFSET=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * $NUM_COMMITS))
GRAPH_BYTE_COMMIT_TREE=$GRAPH_COMMIT_DATA_OFFSET
GRAPH_BYTE_COMMIT_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN))
GRAPH_BYTE_COMMIT_EXTRA_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 4))
GRAPH_BYTE_COMMIT_WRONG_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 3))
GRAPH_BYTE_COMMIT_GENERATION=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 11))
+GRAPH_BYTE_COMMIT_GENERATION_LAST=$(($GRAPH_BYTE_COMMIT_GENERATION + $(($NUM_COMMITS - 1)) * $GRAPH_COMMIT_DATA_WIDTH))
GRAPH_BYTE_COMMIT_DATE=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 12))
-GRAPH_COMMIT_DATA_WIDTH=$(($HASH_LEN + 16))
GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \
$GRAPH_COMMIT_DATA_WIDTH * $NUM_COMMITS))
GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4))
@@ -596,11 +597,6 @@ test_expect_success 'detect incorrect generation number' '
"generation for commit"
'
-test_expect_success 'detect incorrect generation number' '
- corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION "\01" \
- "commit-graph generation for commit"
-'
-
test_expect_success 'detect incorrect commit date' '
corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_DATE "\01" \
"commit date"
@@ -622,6 +618,16 @@ test_expect_success 'detect incorrect chunk count' '
$GRAPH_CHUNK_LOOKUP_OFFSET
'
+test_expect_success 'detect mixed generation numbers (non-zero to zero)' '
+ corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION_LAST "\0\0\0\0" \
+ "both zero and non-zero generations"
+'
+
+test_expect_success 'detect mixed generation numbers (zero to non-zero)' '
+ corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION "\0\0\0\0" \
+ "both zero and non-zero generations"
+'
+
test_expect_success 'git fsck (checks commit-graph when config set to true)' '
git -C full fsck &&
corrupt_graph_and_verify $GRAPH_BYTE_FOOTER "\00" \
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 4f289063ce..19c36b57f4 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -1127,6 +1127,52 @@ do
'
done
+test_expect_success 'prepare source branch' '
+ echo one >onebranch &&
+ git checkout --orphan onebranch &&
+ git rm --cached -r . &&
+ git add onebranch &&
+ git commit -m onebranch &&
+ git rev-list --objects onebranch -- >actual &&
+ # 3 objects should be created, at least ...
+ test 3 -le $(wc -l <actual)
+'
+
+validate_store_type () {
+ git -C dest count-objects -v >actual &&
+ case "$store_type" in
+ packed)
+ grep "^count: 0$" actual ;;
+ loose)
+ grep "^packs: 0$" actual ;;
+ esac || {
+ echo "store_type is $store_type"
+ cat actual
+ false
+ }
+}
+
+test_unpack_limit () {
+ store_type=$1
+
+ case "$store_type" in
+ packed) fetch_limit=1 transfer_limit=10000 ;;
+ loose) fetch_limit=10000 transfer_limit=1 ;;
+ esac
+
+ test_expect_success "fetch trumps transfer limit" '
+ rm -fr dest &&
+ git --bare init dest &&
+ git -C dest config fetch.unpacklimit $fetch_limit &&
+ git -C dest config transfer.unpacklimit $transfer_limit &&
+ git -C dest fetch .. onebranch &&
+ validate_store_type
+ '
+}
+
+test_unpack_limit packed
+test_unpack_limit loose
+
setup_negotiation_tip () {
SERVER="$1"
URL="$2"
diff --git a/t/t5546-receive-limits.sh b/t/t5546-receive-limits.sh
index eed3c9d81a..9fc9ba552f 100755
--- a/t/t5546-receive-limits.sh
+++ b/t/t5546-receive-limits.sh
@@ -9,10 +9,26 @@ TEST_PASSES_SANITIZE_LEAK=true
# When the limit is 1, `git receive-pack` will call `git index-pack`.
# When the limit is 10000, `git receive-pack` will call `git unpack-objects`.
+validate_store_type () {
+ git -C dest count-objects -v >actual &&
+ case "$store_type" in
+ index)
+ grep "^count: 0$" actual ;;
+ unpack)
+ grep "^packs: 0$" actual ;;
+ esac || {
+ echo "store_type is $store_type"
+ cat actual
+ false;
+ }
+}
+
test_pack_input_limit () {
- case "$1" in
- index) unpack_limit=1 ;;
- unpack) unpack_limit=10000 ;;
+ store_type=$1
+
+ case "$store_type" in
+ index) unpack_limit=1 other_limit=10000 ;;
+ unpack) unpack_limit=10000 other_limit=1 ;;
esac
test_expect_success 'prepare destination repository' '
@@ -43,6 +59,19 @@ test_pack_input_limit () {
git --git-dir=dest config receive.maxInputSize 0 &&
git push dest HEAD
'
+
+ test_expect_success 'prepare destination repository (once more)' '
+ rm -fr dest &&
+ git --bare init dest
+ '
+
+ test_expect_success 'receive trumps transfer' '
+ git --git-dir=dest config receive.unpacklimit "$unpack_limit" &&
+ git --git-dir=dest config transfer.unpacklimit "$other_limit" &&
+ git push dest HEAD &&
+ validate_store_type
+ '
+
}
test_expect_success "create known-size (1024 bytes) commit" '
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 5b434ab451..aa3c7c03c4 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -1763,10 +1763,7 @@ test_expect_success GPGSSH 'setup for signature atom using ssh' '
'
test_expect_success GPG2 'bare signature atom' '
- git verify-commit first-signed 2>out.raw &&
- grep -Ev "checking the trustdb|PGP trust model" out.raw >out &&
- head -3 out >expect &&
- tail -1 out >>expect &&
+ git verify-commit first-signed 2>expect &&
echo >>expect &&
git for-each-ref refs/tags/first-signed \
--format="%(signature)" >actual &&
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 487e326b3f..e56f5980dc 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -744,7 +744,15 @@ test_expect_success 'start and stop Linux/systemd maintenance' '
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
- test_systemd_analyze_verify "systemd/user/git-maintenance@.service" &&
+ for schedule in hourly daily weekly
+ do
+ test_path_is_file "systemd/user/git-maintenance@$schedule.timer" || return 1
+ done &&
+ test_path_is_file "systemd/user/git-maintenance@.service" &&
+
+ test_systemd_analyze_verify "systemd/user/git-maintenance@hourly.service" &&
+ test_systemd_analyze_verify "systemd/user/git-maintenance@daily.service" &&
+ test_systemd_analyze_verify "systemd/user/git-maintenance@weekly.service" &&
printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
test_cmp expect args &&
@@ -755,7 +763,10 @@ test_expect_success 'start and stop Linux/systemd maintenance' '
# stop does not unregister the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
- test_path_is_missing "systemd/user/git-maintenance@.timer" &&
+ for schedule in hourly daily weekly
+ do
+ test_path_is_missing "systemd/user/git-maintenance@$schedule.timer" || return 1
+ done &&
test_path_is_missing "systemd/user/git-maintenance@.service" &&
printf -- "--user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
@@ -838,4 +849,17 @@ test_expect_success 'register and unregister bare repo' '
)
'
+test_expect_success 'failed schedule prevents config change' '
+ git init --bare failcase &&
+
+ for scheduler in crontab launchctl schtasks systemctl
+ do
+ GIT_TEST_MAINT_SCHEDULER="$scheduler:false" &&
+ export GIT_TEST_MAINT_SCHEDULER &&
+ test_must_fail \
+ git -C failcase maintenance start &&
+ test_must_fail git -C failcase config maintenance.auto || return 1
+ done
+'
+
test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index a60b05ad3f..263db3ad17 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -61,8 +61,8 @@ test_no_confirm () {
--smtp-server="$(pwd)/fake.sendmail" \
$@ \
$patches >stdout &&
- ! grep "Send this email" stdout &&
- >no_confirm_okay
+ ! grep "Send this email" stdout &&
+ >no_confirm_okay
}
# Exit immediately to prevent hang if a no-confirm test fails
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index 872ad1c9c2..7869f45ee6 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -180,4 +180,16 @@ test_expect_success 'scalar clone warns when background maintenance fails' '
grep "could not turn on maintenance" err
'
+test_expect_success '`scalar clone --no-src`' '
+ scalar clone --src "file://$(pwd)/to-clone" with-src &&
+ scalar clone --no-src "file://$(pwd)/to-clone" without-src &&
+
+ test_path_is_dir with-src/src &&
+ test_path_is_missing without-src/src &&
+
+ (cd with-src/src && ls ?*) >with &&
+ (cd without-src && ls ?*) >without &&
+ test_cmp with without
+'
+
test_done
diff --git a/upload-pack.c b/upload-pack.c
index 94751477ab..83f3d2651a 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -801,11 +801,12 @@ error:
for (i = 0; i < data->want_obj.nr; i++) {
struct object *o = data->want_obj.objects[i].item;
if (!is_our_ref(o, data->allow_uor)) {
+ error("git upload-pack: not our ref %s",
+ oid_to_hex(&o->oid));
packet_writer_error(&data->writer,
"upload-pack: not our ref %s",
oid_to_hex(&o->oid));
- die("git upload-pack: not our ref %s",
- oid_to_hex(&o->oid));
+ exit(128);
}
}
}
diff --git a/wrapper.c b/wrapper.c
index 5160c9e28d..48065c4f53 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -819,3 +819,13 @@ int csprng_bytes(void *buf, size_t len)
return 0;
#endif
}
+
+uint32_t git_rand(void)
+{
+ uint32_t result;
+
+ if (csprng_bytes(&result, sizeof(result)) < 0)
+ die(_("unable to get random bytes"));
+
+ return result;
+}
diff --git a/wrapper.h b/wrapper.h
index 79a9c1b507..79c7321bb3 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -139,4 +139,10 @@ void sleep_millisec(int millisec);
*/
int csprng_bytes(void *buf, size_t len);
+/*
+ * Returns a random uint32_t, uniformly distributed across all possible
+ * values.
+ */
+uint32_t git_rand(void);
+
#endif /* WRAPPER_H */
diff --git a/wt-status.c b/wt-status.c
index 981adb09f3..d03dfab9e4 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2655,8 +2655,12 @@ int require_clean_work_tree(struct repository *r,
}
if (err) {
- if (hint)
+ if (hint) {
+ if (!*hint)
+ BUG("empty hint passed to require_clean_work_tree();"
+ " use NULL instead");
error("%s", hint);
+ }
if (!gently)
exit(128);
}