summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/2.29.0.txt33
-rw-r--r--Documentation/config/fmt-merge-msg.txt2
-rw-r--r--Documentation/config/uploadpack.txt2
-rw-r--r--Documentation/git-mailinfo.txt7
-rw-r--r--Documentation/git-shortlog.txt31
-rw-r--r--Makefile67
-rw-r--r--bisect.c13
-rw-r--r--builtin/bisect--helper.c282
-rw-r--r--builtin/blame.c27
-rw-r--r--builtin/fast-export.c8
-rw-r--r--builtin/log.c1
-rw-r--r--builtin/shortlog.c213
-rwxr-xr-xci/lib.sh1
-rw-r--r--commit-reach.c8
-rw-r--r--compat/bswap.h24
-rw-r--r--contrib/completion/git-completion.bash73
-rwxr-xr-xcontrib/mw-to-git/git-mw.perl2
-rwxr-xr-xcontrib/mw-to-git/git-remote-mediawiki.perl80
-rw-r--r--contrib/mw-to-git/git-remote-mediawiki.txt2
-rw-r--r--contrib/mw-to-git/t/.gitignore2
-rw-r--r--contrib/mw-to-git/t/README10
-rw-r--r--contrib/mw-to-git/t/install-wiki/.gitignore1
-rw-r--r--contrib/mw-to-git/t/install-wiki/LocalSettings.php129
-rw-r--r--contrib/mw-to-git/t/install-wiki/db_install.php120
-rwxr-xr-xcontrib/mw-to-git/t/t9360-mw-to-git-clone.sh8
-rwxr-xr-xcontrib/mw-to-git/t/t9363-mw-to-git-export-import.sh9
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw-lib.sh162
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw.pl22
-rw-r--r--contrib/mw-to-git/t/test.config23
-rwxr-xr-xgit-bisect.sh70
-rw-r--r--oidset.c9
-rw-r--r--oidset.h9
-rw-r--r--packfile.c48
-rw-r--r--ref-filter.c4
-rw-r--r--sequencer.c11
-rw-r--r--shortlog.h8
-rw-r--r--t/helper/test-reach.c2
-rwxr-xr-xt/t4201-shortlog.sh141
-rwxr-xr-xt/t6030-bisect-porcelain.sh7
-rwxr-xr-xt/t6600-test-reach.sh30
-rwxr-xr-xt/t8013-blame-ignore-revs.sh61
-rw-r--r--trailer.c36
-rw-r--r--trailer.h45
43 files changed, 1176 insertions, 667 deletions
diff --git a/Documentation/RelNotes/2.29.0.txt b/Documentation/RelNotes/2.29.0.txt
index e430392340..41a78a4ce8 100644
--- a/Documentation/RelNotes/2.29.0.txt
+++ b/Documentation/RelNotes/2.29.0.txt
@@ -98,6 +98,18 @@ UI, Workflows & Features
* The transport protocol v2 has become the default again.
+ * The installation procedure learned to optionally omit "git-foo"
+ executable files for each 'foo' built-in subcommand, which are only
+ required by old timers that still rely on the age old promise that
+ prepending "git --exec-path" output to PATH early in their script
+ will keep the "git-foo" calls they wrote working.
+
+ * The command line completion (in contrib/) learned that "git restore
+ -s <TAB>" is often followed by a refname.
+
+ * "git shortlog" has been taught to group commits by the contents of
+ the trailer lines, like "Reviewed-by:", "Coauthored-by:", etc.
+
Performance, Internal Implementation, Development Support etc.
@@ -409,6 +421,26 @@ Fixes since v2.28
pull.ff configuration variable.
(merge 54200cef86 ah/pull later to maint).
+ * Compilation fix around type punning.
+ (merge 176380fd11 jk/drop-unaligned-loads later to maint).
+
+ * "git blame --ignore-rev/--ignore-revs-file" failed to validate
+ their input are valid revision, and failed to take into account
+ that the user may want to give an annotated tag instead of a
+ commit, which has been corrected.
+ (merge 610e2b9240 jc/blame-ignore-fix later to maint).
+
+ * "git bisect start X Y", when X and Y are not valid committish
+ object names, should take X and Y as pathspec, but didn't.
+ (merge 73c6de06af cc/bisect-start-fix later to maint).
+
+ * The explanation of the "scissors line" has been clarified.
+ (merge 287416dba6 eg/mailinfo-doc-scissors later to maint).
+
+ * A race that leads to an access to a free'd data was corrected in
+ the codepath that reads pack files.
+ (merge bda959c476 mt/delta-base-cache-races later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 84544f2ea3 sk/typofixes later to maint).
(merge b17f411ab5 ar/help-guides-doc later to maint).
@@ -440,3 +472,4 @@ Fixes since v2.28
(merge 324efcf6b6 pw/add-p-leakfix later to maint).
(merge 1c6ffb546b jk/add-i-fixes later to maint).
(merge e40e936551 cd/commit-graph-doc later to maint).
+ (merge 0512eabd91 jc/sequencer-stopped-sha-simplify later to maint).
diff --git a/Documentation/config/fmt-merge-msg.txt b/Documentation/config/fmt-merge-msg.txt
index a8e8f74d0a..3fbf40e24f 100644
--- a/Documentation/config/fmt-merge-msg.txt
+++ b/Documentation/config/fmt-merge-msg.txt
@@ -13,7 +13,7 @@ merge.suppressDest::
By adding a glob that matches the names of integration
branches to this multi-valued configuration variable, the
default merge message computed for merges into these
- integration branches will omit " into <branch name>" from
+ integration branches will omit "into <branch name>" from
its title.
+
An element with an empty value can be used to clear the list
diff --git a/Documentation/config/uploadpack.txt b/Documentation/config/uploadpack.txt
index ee7b3ac94f..b0d761282c 100644
--- a/Documentation/config/uploadpack.txt
+++ b/Documentation/config/uploadpack.txt
@@ -70,7 +70,7 @@ uploadpackfilter.<filter>.allow::
kinds must be allowed. Defaults to `uploadpackfilter.allow`.
uploadpackfilter.tree.maxDepth::
- Only allow `--filter=tree=<n>` when `n` is no more than the value of
+ Only allow `--filter=tree:<n>` when `<n>` is no more than the value of
`uploadpackfilter.tree.maxDepth`. If set, this also implies
`uploadpackfilter.tree.allow=true`, unless this configuration
variable had already been set. Has no effect if unset.
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 3bbc731f67..7a6aed0e30 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -72,10 +72,9 @@ conversion, even with this flag.
is useful in order to associate commits with mailing list discussions.
--scissors::
- Remove everything in body before a scissors line. A line that
- mainly consists of scissors (either ">8" or "8<") and perforation
- (dash "-") marks is called a scissors line, and is used to request
- the reader to cut the message at that line. If such a line
+ Remove everything in body before a scissors line (e.g. "-- >8 --").
+ The line represents scissors and perforation marks, and is used to
+ request the reader to cut the message at that line. If that line
appears in the body of the message before the patch, everything
before it (including the scissors line itself) is ignored when
this option is used.
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index a72ea7f7ba..fd93cd41e9 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -47,9 +47,38 @@ OPTIONS
Each pretty-printed commit will be rewrapped before it is shown.
+--group=<type>::
+ Group commits based on `<type>`. If no `--group` option is
+ specified, the default is `author`. `<type>` is one of:
++
+--
+ - `author`, commits are grouped by author
+ - `committer`, commits are grouped by committer (the same as `-c`)
+ - `trailer:<field>`, the `<field>` is interpreted as a case-insensitive
+ commit message trailer (see linkgit:git-interpret-trailers[1]). For
+ example, if your project uses `Reviewed-by` trailers, you might want
+ to see who has been reviewing with
+ `git shortlog -ns --group=trailer:reviewed-by`.
++
+Note that commits that do not include the trailer will not be counted.
+Likewise, commits with multiple trailers (e.g., multiple signoffs) may
+be counted more than once (but only once per unique trailer value in
+that commit).
++
+Shortlog will attempt to parse each trailer value as a `name <email>`
+identity. If successful, the mailmap is applied and the email is omitted
+unless the `--email` option is specified. If the value cannot be parsed
+as an identity, it will be taken literally and completely.
+--
++
+If `--group` is specified multiple times, commits are counted under each
+value (but again, only once per unique value in that commit). For
+example, `git shortlog --group=author --group=trailer:co-authored-by`
+counts both authors and co-authors.
+
-c::
--committer::
- Collect and show committer identities instead of authors.
+ This is an alias for `--group=committer`.
-w[<width>[,<indent1>[,<indent2>]]]::
Linewrap the output by wrapping each line at `width`. The first
diff --git a/Makefile b/Makefile
index de53954590..fb521dad71 100644
--- a/Makefile
+++ b/Makefile
@@ -348,6 +348,9 @@ all::
# Define NO_INSTALL_HARDLINKS if you prefer to use either symbolic links or
# copies to install built-in git commands e.g. git-cat-file.
#
+# Define SKIP_DASHED_BUILT_INS if you do not need the dashed versions of the
+# built-ins to be linked/copied at all.
+#
# Define USE_NED_ALLOCATOR if you want to replace the platforms default
# memory allocators with the nedmalloc allocator written by Niall Douglas.
#
@@ -775,6 +778,16 @@ BUILT_INS += git-whatchanged$X
# what 'all' will build and 'install' will install in gitexecdir,
# excluding programs for built-in commands
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
+ALL_COMMANDS_TO_INSTALL = $(ALL_PROGRAMS)
+ifeq (,$(SKIP_DASHED_BUILT_INS))
+ALL_COMMANDS_TO_INSTALL += $(BUILT_INS)
+else
+# git-upload-pack, git-receive-pack and git-upload-archive are special: they
+# are _expected_ to be present in the `bin/` directory in their dashed form.
+ALL_COMMANDS_TO_INSTALL += git-receive-pack$(X)
+ALL_COMMANDS_TO_INSTALL += git-upload-archive$(X)
+ALL_COMMANDS_TO_INSTALL += git-upload-pack$(X)
+endif
# what 'all' will build but not install in gitexecdir
OTHER_PROGRAMS = git$X
@@ -1219,7 +1232,6 @@ SANITIZERS := $(foreach flag,$(subst $(comma),$(space),$(SANITIZE)),$(flag))
BASIC_CFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize-recover=$(SANITIZE)
BASIC_CFLAGS += -fno-omit-frame-pointer
ifneq ($(filter undefined,$(SANITIZERS)),)
-BASIC_CFLAGS += -DNO_UNALIGNED_LOADS
BASIC_CFLAGS += -DSHA1DC_FORCE_ALIGNED_ACCESS
endif
ifneq ($(filter leak,$(SANITIZERS)),)
@@ -2089,9 +2101,9 @@ profile-fast: profile-clean
$(MAKE) PROFILE=USE all
-all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
+all:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
ifneq (,$X)
- $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
+ $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
endif
all::
@@ -2922,15 +2934,8 @@ ifdef MSVC
# have already been rolled up into the exe's pdb file.
# We DO NOT have pdb files for the builtin commands (like git-status.exe)
# because it is just a copy/hardlink of git.exe, rather than a unique binary.
- $(INSTALL) git.pdb '$(DESTDIR_SQ)$(bindir_SQ)'
- $(INSTALL) git-shell.pdb '$(DESTDIR_SQ)$(bindir_SQ)'
- $(INSTALL) git-daemon.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-http-backend.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-http-fetch.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-http-push.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-imap-send.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-remote-http.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-sh-i18n--envsubst.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+ $(INSTALL) $(patsubst %.exe,%.pdb,$(filter-out $(BUILT_INS),$(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)))) '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) $(patsubst %.exe,%.pdb,$(filter-out $(BUILT_INS) $(REMOTE_CURL_ALIASES),$(PROGRAMS))) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
ifndef DEBUG
$(INSTALL) $(vcpkg_rel_bin)/*.dll '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) $(vcpkg_rel_bin)/*.pdb '$(DESTDIR_SQ)$(bindir_SQ)'
@@ -2958,7 +2963,7 @@ ifndef NO_TCLTK
$(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
endif
ifneq (,$X)
- $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
+ $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
endif
bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
@@ -2976,21 +2981,27 @@ endif
} && \
for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
$(RM) "$$bindir/$$p" && \
- test -n "$(INSTALL_SYMLINKS)" && \
- ln -s "git$X" "$$bindir/$$p" || \
- { test -z "$(NO_INSTALL_HARDLINKS)" && \
- ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
- ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
- cp "$$bindir/git$X" "$$bindir/$$p" || exit; } \
+ if test -z "$(SKIP_DASHED_BUILT_INS)"; \
+ then \
+ test -n "$(INSTALL_SYMLINKS)" && \
+ ln -s "git$X" "$$bindir/$$p" || \
+ { test -z "$(NO_INSTALL_HARDLINKS)" && \
+ ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
+ ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
+ cp "$$bindir/git$X" "$$bindir/$$p" || exit; }; \
+ fi \
done && \
for p in $(BUILT_INS); do \
$(RM) "$$execdir/$$p" && \
- test -n "$(INSTALL_SYMLINKS)" && \
- ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/git$X" "$$execdir/$$p" || \
- { test -z "$(NO_INSTALL_HARDLINKS)" && \
- ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
- ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
- cp "$$execdir/git$X" "$$execdir/$$p" || exit; } \
+ if test -z "$(SKIP_DASHED_BUILT_INS)"; \
+ then \
+ test -n "$(INSTALL_SYMLINKS)" && \
+ ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/git$X" "$$execdir/$$p" || \
+ { test -z "$(NO_INSTALL_HARDLINKS)" && \
+ ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
+ ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
+ cp "$$execdir/git$X" "$$execdir/$$p" || exit; }; \
+ fi \
done && \
remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
for p in $$remote_curl_aliases; do \
@@ -3084,7 +3095,7 @@ ifneq ($(INCLUDE_DLLS_IN_ARTIFACTS),)
OTHER_PROGRAMS += $(shell echo *.dll t/helper/*.dll)
endif
-artifacts-tar:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) \
+artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
$(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
@@ -3179,7 +3190,7 @@ endif
### Check documentation
#
-ALL_COMMANDS = $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS)
+ALL_COMMANDS = $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB)
ALL_COMMANDS += git
ALL_COMMANDS += git-citool
ALL_COMMANDS += git-gui
@@ -3219,7 +3230,7 @@ check-docs::
-e 's/\.txt//'; \
) | while read how cmd; \
do \
- case " $(patsubst %$X,%,$(ALL_COMMANDS) $(EXCLUDED_PROGRAMS)) " in \
+ case " $(patsubst %$X,%,$(ALL_COMMANDS) $(BUILT_INS) $(EXCLUDED_PROGRAMS)) " in \
*" $$cmd "*) ;; \
*) echo "removed but $$how: $$cmd" ;; \
esac; \
diff --git a/bisect.c b/bisect.c
index d42a3a3767..f5b1368128 100644
--- a/bisect.c
+++ b/bisect.c
@@ -988,8 +988,11 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
* the bisection process finished successfully.
* In this case the calling function or command should not turn a
* BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND return code into an error or a non zero exit code.
- * If no_checkout is non-zero, the bisection process does not
- * checkout the trial commit but instead simply updates BISECT_HEAD.
+ *
+ * Checking BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND
+ * in bisect_helper::bisect_next() and only transforming it to 0 at
+ * the end of bisect_helper::cmd_bisect__helper() helps bypassing
+ * all the code related to finding a commit to test.
*/
enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
{
@@ -999,6 +1002,10 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
enum bisect_error res = BISECT_OK;
struct object_id *bisect_rev;
char *steps_msg;
+ /*
+ * If no_checkout is non-zero, the bisection process does not
+ * checkout the trial commit but instead simply updates BISECT_HEAD.
+ */
int no_checkout = ref_exists("BISECT_HEAD");
unsigned bisect_flags = 0;
@@ -1082,6 +1089,8 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
"Bisecting: %d revisions left to test after this %s\n",
nr), nr, steps_msg);
free(steps_msg);
+ /* Clean up objects used, as they will be reused. */
+ clear_commit_marks_all(ALL_REV_FLAGS);
return bisect_checkout(bisect_rev, no_checkout);
}
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 7dcc1b5188..7512b880f0 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -8,6 +8,7 @@
#include "run-command.h"
#include "prompt.h"
#include "quote.h"
+#include "revision.h"
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
@@ -29,9 +30,17 @@ static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]"
" [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
+ N_("git bisect--helper --bisect-next"),
+ N_("git bisect--helper --bisect-auto-next"),
+ N_("git bisect--helper --bisect-autostart"),
NULL
};
+struct add_bisect_ref_data {
+ struct rev_info *revs;
+ unsigned int object_flags;
+};
+
struct bisect_terms {
char *term_good;
char *term_bad;
@@ -55,6 +64,8 @@ static void set_terms(struct bisect_terms *terms, const char *bad,
static const char vocab_bad[] = "bad|new";
static const char vocab_good[] = "good|old";
+static int bisect_autostart(struct bisect_terms *terms);
+
/*
* Check whether the string `term` belongs to the set of strings
* included in the variable arguments.
@@ -74,6 +85,52 @@ static int one_of(const char *term, ...)
return res;
}
+static int write_in_file(const char *path, const char *mode, const char *format, va_list args)
+{
+ FILE *fp = NULL;
+ int res = 0;
+
+ if (strcmp(mode, "w") && strcmp(mode, "a"))
+ BUG("write-in-file does not support '%s' mode", mode);
+ fp = fopen(path, mode);
+ if (!fp)
+ return error_errno(_("cannot open file '%s' in mode '%s'"), path, mode);
+ res = vfprintf(fp, format, args);
+
+ if (res < 0) {
+ int saved_errno = errno;
+ fclose(fp);
+ errno = saved_errno;
+ return error_errno(_("could not write to file '%s'"), path);
+ }
+
+ return fclose(fp);
+}
+
+static int write_to_file(const char *path, const char *format, ...)
+{
+ int res;
+ va_list args;
+
+ va_start(args, format);
+ res = write_in_file(path, "w", format, args);
+ va_end(args);
+
+ return res;
+}
+
+static int append_to_file(const char *path, const char *format, ...)
+{
+ int res;
+ va_list args;
+
+ va_start(args, format);
+ res = write_in_file(path, "a", format, args);
+ va_end(args);
+
+ return res;
+}
+
static int check_term_format(const char *term, const char *orig_term)
{
int res;
@@ -104,7 +161,6 @@ static int check_term_format(const char *term, const char *orig_term)
static int write_terms(const char *bad, const char *good)
{
- FILE *fp = NULL;
int res;
if (!strcmp(bad, good))
@@ -113,13 +169,9 @@ static int write_terms(const char *bad, const char *good)
if (check_term_format(bad, "bad") || check_term_format(good, "good"))
return -1;
- fp = fopen(git_path_bisect_terms(), "w");
- if (!fp)
- return error_errno(_("could not open the file BISECT_TERMS"));
+ res = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good);
- res = fprintf(fp, "%s\n%s\n", bad, good);
- res |= fclose(fp);
- return (res < 0) ? -1 : 0;
+ return res;
}
static int is_expected_rev(const char *expected_hex)
@@ -421,6 +473,142 @@ finish:
return res;
}
+static int add_bisect_ref(const char *refname, const struct object_id *oid,
+ int flags, void *cb)
+{
+ struct add_bisect_ref_data *data = cb;
+
+ add_pending_oid(data->revs, refname, oid, data->object_flags);
+
+ return 0;
+}
+
+static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
+{
+ int res = 0;
+ struct add_bisect_ref_data cb = { revs };
+ char *good = xstrfmt("%s-*", terms->term_good);
+
+ /*
+ * We cannot use terms->term_bad directly in
+ * for_each_glob_ref_in() and we have to append a '*' to it,
+ * otherwise for_each_glob_ref_in() will append '/' and '*'.
+ */
+ char *bad = xstrfmt("%s*", terms->term_bad);
+
+ /*
+ * It is important to reset the flags used by revision walks
+ * as the previous call to bisect_next_all() in turn
+ * sets up a revision walk.
+ */
+ reset_revision_walk();
+ init_revisions(revs, NULL);
+ setup_revisions(0, NULL, revs, NULL);
+ for_each_glob_ref_in(add_bisect_ref, bad, "refs/bisect/", &cb);
+ cb.object_flags = UNINTERESTING;
+ for_each_glob_ref_in(add_bisect_ref, good, "refs/bisect/", &cb);
+ if (prepare_revision_walk(revs))
+ res = error(_("revision walk setup failed\n"));
+
+ free(good);
+ free(bad);
+ return res;
+}
+
+static int bisect_skipped_commits(struct bisect_terms *terms)
+{
+ int res;
+ FILE *fp = NULL;
+ struct rev_info revs;
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_name = STRBUF_INIT;
+
+ res = prepare_revs(terms, &revs);
+ if (res)
+ return res;
+
+ fp = fopen(git_path_bisect_log(), "a");
+ if (!fp)
+ return error_errno(_("could not open '%s' for appending"),
+ git_path_bisect_log());
+
+ if (fprintf(fp, "# only skipped commits left to test\n") < 0)
+ return error_errno(_("failed to write to '%s'"), git_path_bisect_log());
+
+ while ((commit = get_revision(&revs)) != NULL) {
+ strbuf_reset(&commit_name);
+ format_commit_message(commit, "%s",
+ &commit_name, &pp);
+ fprintf(fp, "# possible first %s commit: [%s] %s\n",
+ terms->term_bad, oid_to_hex(&commit->object.oid),
+ commit_name.buf);
+ }
+
+ /*
+ * Reset the flags used by revision walks in case
+ * there is another revision walk after this one.
+ */
+ reset_revision_walk();
+
+ strbuf_release(&commit_name);
+ fclose(fp);
+ return 0;
+}
+
+static int bisect_successful(struct bisect_terms *terms)
+{
+ struct object_id oid;
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_name = STRBUF_INIT;
+ char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad);
+ int res;
+
+ read_ref(bad_ref, &oid);
+ commit = lookup_commit_reference_by_name(bad_ref);
+ format_commit_message(commit, "%s", &commit_name, &pp);
+
+ res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n",
+ terms->term_bad, oid_to_hex(&commit->object.oid),
+ commit_name.buf);
+
+ strbuf_release(&commit_name);
+ free(bad_ref);
+ return res;
+}
+
+static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)
+{
+ enum bisect_error res;
+
+ if (bisect_autostart(terms))
+ return BISECT_FAILED;
+
+ if (bisect_next_check(terms, terms->term_good))
+ return BISECT_FAILED;
+
+ /* Perform all bisection computation */
+ res = bisect_next_all(the_repository, prefix);
+
+ if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
+ res = bisect_successful(terms);
+ return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
+ } else if (res == BISECT_ONLY_SKIPPED_LEFT) {
+ res = bisect_skipped_commits(terms);
+ return res ? res : BISECT_ONLY_SKIPPED_LEFT;
+ }
+ return res;
+}
+
+static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)
+{
+ if (bisect_next_check(terms, NULL))
+ return BISECT_OK;
+
+ return bisect_next(terms, prefix);
+}
+
static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
{
int no_checkout = 0;
@@ -484,14 +672,13 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
terms->term_bad = xstrdup(arg);
} else if (starts_with(arg, "--")) {
return error(_("unrecognized option: '%s'"), arg);
- } else {
- char *commit_id = xstrfmt("%s^{commit}", arg);
- if (get_oid(commit_id, &oid) && has_double_dash)
- die(_("'%s' does not appear to be a valid "
- "revision"), arg);
-
+ } else if (!get_oidf(&oid, "%s^{commit}", arg)) {
string_list_append(&revs, oid_to_hex(&oid));
- free(commit_id);
+ } else if (has_double_dash) {
+ die(_("'%s' does not appear to be a valid "
+ "revision"), arg);
+ } else {
+ break;
}
}
pathspec_pos = i;
@@ -624,6 +811,38 @@ finish:
return res;
}
+static inline int file_is_not_empty(const char *path)
+{
+ return !is_empty_or_missing_file(path);
+}
+
+static int bisect_autostart(struct bisect_terms *terms)
+{
+ int res;
+ const char *yesno;
+
+ if (file_is_not_empty(git_path_bisect_start()))
+ return 0;
+
+ fprintf_ln(stderr, _("You need to start by \"git bisect "
+ "start\"\n"));
+
+ if (!isatty(STDIN_FILENO))
+ return -1;
+
+ /*
+ * TRANSLATORS: Make sure to include [Y] and [n] in your
+ * translation. The program will only accept English input
+ * at this point.
+ */
+ yesno = git_prompt(_("Do you want me to do it for you "
+ "[Y/n]? "), PROMPT_ECHO);
+ res = tolower(*yesno) == 'n' ?
+ -1 : bisect_start(terms, empty_strvec, 0);
+
+ return res;
+}
+
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{
enum {
@@ -636,7 +855,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
CHECK_AND_SET_TERMS,
BISECT_NEXT_CHECK,
BISECT_TERMS,
- BISECT_START
+ BISECT_START,
+ BISECT_AUTOSTART,
+ BISECT_NEXT,
+ BISECT_AUTO_NEXT
} cmdmode = 0;
int res = 0, nolog = 0;
struct option options[] = {
@@ -660,6 +882,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
N_("print out the bisect terms"), BISECT_TERMS),
OPT_CMDMODE(0, "bisect-start", &cmdmode,
N_("start the bisect session"), BISECT_START),
+ OPT_CMDMODE(0, "bisect-next", &cmdmode,
+ N_("find the next bisection commit"), BISECT_NEXT),
+ OPT_CMDMODE(0, "bisect-auto-next", &cmdmode,
+ N_("verify the next bisection state then checkout the next bisection commit"), BISECT_AUTO_NEXT),
+ OPT_CMDMODE(0, "bisect-autostart", &cmdmode,
+ N_("start the bisection if it has not yet been started"), BISECT_AUTOSTART),
OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")),
OPT_END()
@@ -719,8 +947,26 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
set_terms(&terms, "bad", "good");
res = bisect_start(&terms, argv, argc);
break;
+ case BISECT_NEXT:
+ if (argc)
+ return error(_("--bisect-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_next(&terms, prefix);
+ break;
+ case BISECT_AUTO_NEXT:
+ if (argc)
+ return error(_("--bisect-auto-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_auto_next(&terms, prefix);
+ break;
+ case BISECT_AUTOSTART:
+ if (argc)
+ return error(_("--bisect-autostart does not accept arguments"));
+ set_terms(&terms, "bad", "good");
+ res = bisect_autostart(&terms);
+ break;
default:
- return error("BUG: unknown subcommand '%d'", cmdmode);
+ BUG("unknown subcommand %d", cmdmode);
}
free_terms(&terms);
@@ -728,8 +974,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
* Handle early success
* From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all
*/
- if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE)
+ if ((res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) || (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND))
res = BISECT_OK;
- return abs(res);
+ return -res;
}
diff --git a/builtin/blame.c b/builtin/blame.c
index eb513fbe60..bb0f29300e 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -27,6 +27,7 @@
#include "object-store.h"
#include "blame.h"
#include "refs.h"
+#include "tag.h"
static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
@@ -803,6 +804,26 @@ static int is_a_rev(const char *name)
return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
}
+static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata)
+{
+ struct repository *r = ((struct blame_scoreboard *)cbdata)->repo;
+ struct object_id oid;
+
+ oidcpy(&oid, oid_ret);
+ while (1) {
+ struct object *obj;
+ int kind = oid_object_info(r, &oid, NULL);
+ if (kind == OBJ_COMMIT) {
+ oidcpy(oid_ret, &oid);
+ return 0;
+ }
+ if (kind != OBJ_TAG)
+ return -1;
+ obj = deref_tag(r, parse_object(r, &oid), NULL, 0);
+ oidcpy(&oid, &obj->oid);
+ }
+}
+
static void build_ignorelist(struct blame_scoreboard *sb,
struct string_list *ignore_revs_file_list,
struct string_list *ignore_rev_list)
@@ -815,10 +836,12 @@ static void build_ignorelist(struct blame_scoreboard *sb,
if (!strcmp(i->string, ""))
oidset_clear(&sb->ignore_list);
else
- oidset_parse_file(&sb->ignore_list, i->string);
+ oidset_parse_file_carefully(&sb->ignore_list, i->string,
+ peel_to_commit_oid, sb);
}
for_each_string_list_item(i, ignore_rev_list) {
- if (get_oid_committish(i->string, &oid))
+ if (get_oid_committish(i->string, &oid) ||
+ peel_to_commit_oid(&oid, sb))
die(_("cannot find revision %s to ignore"), i->string);
oidset_insert(&sb->ignore_list, &oid);
}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 1b8fca3ee0..e266d90a81 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -405,12 +405,12 @@ static char *generate_fake_oid(void *data)
{
static uint32_t counter = 1; /* avoid null oid */
const unsigned hashsz = the_hash_algo->rawsz;
- unsigned char out[GIT_MAX_RAWSZ];
+ struct object_id oid;
char *hex = xmallocz(GIT_MAX_HEXSZ);
- hashclr(out);
- put_be32(out + hashsz - 4, counter++);
- return hash_to_hex_algop_r(hex, out, the_hash_algo);
+ oidclr(&oid);
+ put_be32(oid.hash + hashsz - 4, counter++);
+ return oid_to_hex_r(hex, &oid);
}
static const char *anonymize_oid(const char *oid_hex)
diff --git a/builtin/log.c b/builtin/log.c
index b8824d898f..7f27e9eca1 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1195,6 +1195,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
log.in1 = 2;
log.in2 = 4;
log.file = rev->diffopt.file;
+ log.groups = SHORTLOG_GROUP_AUTHOR;
for (i = 0; i < nr; i++)
shortlog_add_commit(&log, list[i]);
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index c856c58bb5..0a5c4968f6 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -9,6 +9,7 @@
#include "mailmap.h"
#include "shortlog.h"
#include "parse-options.h"
+#include "trailer.h"
static char const * const shortlog_usage[] = {
N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"),
@@ -49,12 +50,12 @@ static int compare_by_list(const void *a1, const void *a2)
}
static void insert_one_record(struct shortlog *log,
- const char *author,
+ const char *ident,
const char *oneline)
{
struct string_list_item *item;
- item = string_list_insert(&log->list, author);
+ item = string_list_insert(&log->list, ident);
if (log->summary)
item->util = (void *)(UTIL_TO_INT(item) + 1);
@@ -97,8 +98,8 @@ static void insert_one_record(struct shortlog *log,
}
}
-static int parse_stdin_author(struct shortlog *log,
- struct strbuf *out, const char *in)
+static int parse_ident(struct shortlog *log,
+ struct strbuf *out, const char *in)
{
const char *mailbuf, *namebuf;
size_t namelen, maillen;
@@ -122,18 +123,33 @@ static int parse_stdin_author(struct shortlog *log,
static void read_from_stdin(struct shortlog *log)
{
- struct strbuf author = STRBUF_INIT;
- struct strbuf mapped_author = STRBUF_INIT;
+ struct strbuf ident = STRBUF_INIT;
+ struct strbuf mapped_ident = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
static const char *author_match[2] = { "Author: ", "author " };
static const char *committer_match[2] = { "Commit: ", "committer " };
const char **match;
- match = log->committer ? committer_match : author_match;
- while (strbuf_getline_lf(&author, stdin) != EOF) {
+ if (HAS_MULTI_BITS(log->groups))
+ die(_("using multiple --group options with stdin is not supported"));
+
+ switch (log->groups) {
+ case SHORTLOG_GROUP_AUTHOR:
+ match = author_match;
+ break;
+ case SHORTLOG_GROUP_COMMITTER:
+ match = committer_match;
+ break;
+ case SHORTLOG_GROUP_TRAILER:
+ die(_("using --group=trailer with stdin is not supported"));
+ default:
+ BUG("unhandled shortlog group");
+ }
+
+ while (strbuf_getline_lf(&ident, stdin) != EOF) {
const char *v;
- if (!skip_prefix(author.buf, match[0], &v) &&
- !skip_prefix(author.buf, match[1], &v))
+ if (!skip_prefix(ident.buf, match[0], &v) &&
+ !skip_prefix(ident.buf, match[1], &v))
continue;
while (strbuf_getline_lf(&oneline, stdin) != EOF &&
oneline.len)
@@ -142,23 +158,118 @@ static void read_from_stdin(struct shortlog *log)
!oneline.len)
; /* discard blanks */
- strbuf_reset(&mapped_author);
- if (parse_stdin_author(log, &mapped_author, v) < 0)
+ strbuf_reset(&mapped_ident);
+ if (parse_ident(log, &mapped_ident, v) < 0)
continue;
- insert_one_record(log, mapped_author.buf, oneline.buf);
+ insert_one_record(log, mapped_ident.buf, oneline.buf);
}
- strbuf_release(&author);
- strbuf_release(&mapped_author);
+ strbuf_release(&ident);
+ strbuf_release(&mapped_ident);
strbuf_release(&oneline);
}
+struct strset_item {
+ struct hashmap_entry ent;
+ char value[FLEX_ARRAY];
+};
+
+struct strset {
+ struct hashmap map;
+};
+
+#define STRSET_INIT { { NULL } }
+
+static int strset_item_hashcmp(const void *hash_data,
+ const struct hashmap_entry *entry,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata)
+{
+ const struct strset_item *a, *b;
+
+ a = container_of(entry, const struct strset_item, ent);
+ if (keydata)
+ return strcmp(a->value, keydata);
+
+ b = container_of(entry_or_key, const struct strset_item, ent);
+ return strcmp(a->value, b->value);
+}
+
+/*
+ * Adds "str" to the set if it was not already present; returns true if it was
+ * already there.
+ */
+static int strset_check_and_add(struct strset *ss, const char *str)
+{
+ unsigned int hash = strhash(str);
+ struct strset_item *item;
+
+ if (!ss->map.table)
+ hashmap_init(&ss->map, strset_item_hashcmp, NULL, 0);
+
+ if (hashmap_get_from_hash(&ss->map, hash, str))
+ return 1;
+
+ FLEX_ALLOC_STR(item, value, str);
+ hashmap_entry_init(&item->ent, hash);
+ hashmap_add(&ss->map, &item->ent);
+ return 0;
+}
+
+static void strset_clear(struct strset *ss)
+{
+ if (!ss->map.table)
+ return;
+ hashmap_free_entries(&ss->map, struct strset_item, ent);
+}
+
+static void insert_records_from_trailers(struct shortlog *log,
+ struct strset *dups,
+ struct commit *commit,
+ struct pretty_print_context *ctx,
+ const char *oneline)
+{
+ struct trailer_iterator iter;
+ const char *commit_buffer, *body;
+ struct strbuf ident = STRBUF_INIT;
+
+ /*
+ * Using format_commit_message("%B") would be simpler here, but
+ * this saves us copying the message.
+ */
+ commit_buffer = logmsg_reencode(commit, NULL, ctx->output_encoding);
+ body = strstr(commit_buffer, "\n\n");
+ if (!body)
+ return;
+
+ trailer_iterator_init(&iter, body);
+ while (trailer_iterator_advance(&iter)) {
+ const char *value = iter.val.buf;
+
+ if (!string_list_has_string(&log->trailers, iter.key.buf))
+ continue;
+
+ strbuf_reset(&ident);
+ if (!parse_ident(log, &ident, value))
+ value = ident.buf;
+
+ if (strset_check_and_add(dups, value))
+ continue;
+ insert_one_record(log, value, oneline);
+ }
+ trailer_iterator_release(&iter);
+
+ strbuf_release(&ident);
+ unuse_commit_buffer(commit, commit_buffer);
+}
+
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
- struct strbuf author = STRBUF_INIT;
+ struct strbuf ident = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
+ struct strset dups = STRSET_INIT;
struct pretty_print_context ctx = {0};
- const char *fmt;
+ const char *oneline_str;
ctx.fmt = CMIT_FMT_USERFORMAT;
ctx.abbrev = log->abbrev;
@@ -166,21 +277,38 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
ctx.date_mode.type = DATE_NORMAL;
ctx.output_encoding = get_log_output_encoding();
- fmt = log->committer ?
- (log->email ? "%cN <%cE>" : "%cN") :
- (log->email ? "%aN <%aE>" : "%aN");
-
- format_commit_message(commit, fmt, &author, &ctx);
if (!log->summary) {
if (log->user_format)
pretty_print_commit(&ctx, commit, &oneline);
else
format_commit_message(commit, "%s", &oneline, &ctx);
}
+ oneline_str = oneline.len ? oneline.buf : "<none>";
+
+ if (log->groups & SHORTLOG_GROUP_AUTHOR) {
+ strbuf_reset(&ident);
+ format_commit_message(commit,
+ log->email ? "%aN <%aE>" : "%aN",
+ &ident, &ctx);
+ if (!HAS_MULTI_BITS(log->groups) ||
+ !strset_check_and_add(&dups, ident.buf))
+ insert_one_record(log, ident.buf, oneline_str);
+ }
+ if (log->groups & SHORTLOG_GROUP_COMMITTER) {
+ strbuf_reset(&ident);
+ format_commit_message(commit,
+ log->email ? "%cN <%cE>" : "%cN",
+ &ident, &ctx);
+ if (!HAS_MULTI_BITS(log->groups) ||
+ !strset_check_and_add(&dups, ident.buf))
+ insert_one_record(log, ident.buf, oneline_str);
+ }
+ if (log->groups & SHORTLOG_GROUP_TRAILER) {
+ insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
+ }
- insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>");
-
- strbuf_release(&author);
+ strset_clear(&dups);
+ strbuf_release(&ident);
strbuf_release(&oneline);
}
@@ -241,6 +369,28 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int parse_group_option(const struct option *opt, const char *arg, int unset)
+{
+ struct shortlog *log = opt->value;
+ const char *field;
+
+ if (unset) {
+ log->groups = 0;
+ string_list_clear(&log->trailers, 0);
+ } else if (!strcasecmp(arg, "author"))
+ log->groups |= SHORTLOG_GROUP_AUTHOR;
+ else if (!strcasecmp(arg, "committer"))
+ log->groups |= SHORTLOG_GROUP_COMMITTER;
+ else if (skip_prefix(arg, "trailer:", &field)) {
+ log->groups |= SHORTLOG_GROUP_TRAILER;
+ string_list_append(&log->trailers, field);
+ } else
+ return error(_("unknown group type: %s"), arg);
+
+ return 0;
+}
+
+
void shortlog_init(struct shortlog *log)
{
memset(log, 0, sizeof(*log));
@@ -251,6 +401,8 @@ void shortlog_init(struct shortlog *log)
log->wrap = DEFAULT_WRAPLEN;
log->in1 = DEFAULT_INDENT1;
log->in2 = DEFAULT_INDENT2;
+ log->trailers.strdup_strings = 1;
+ log->trailers.cmp = strcasecmp;
}
int cmd_shortlog(int argc, const char **argv, const char *prefix)
@@ -260,8 +412,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
int nongit = !startup_info->have_repository;
const struct option options[] = {
- OPT_BOOL('c', "committer", &log.committer,
- N_("Group by committer rather than author")),
+ OPT_BIT('c', "committer", &log.groups,
+ N_("Group by committer rather than author"),
+ SHORTLOG_GROUP_COMMITTER),
OPT_BOOL('n', "numbered", &log.sort_by_number,
N_("sort output according to the number of commits per author")),
OPT_BOOL('s', "summary", &log.summary,
@@ -271,6 +424,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
N_("Linewrap output"), PARSE_OPT_OPTARG,
&parse_wrap_args),
+ OPT_CALLBACK(0, "group", &log, N_("field"),
+ N_("Group by field"), parse_group_option),
OPT_END(),
};
@@ -311,6 +466,10 @@ parse_done:
log.abbrev = rev.abbrev;
log.file = rev.diffopt.file;
+ if (!log.groups)
+ log.groups = SHORTLOG_GROUP_AUTHOR;
+ string_list_sort(&log.trailers);
+
/* assume HEAD if from a tty */
if (!nongit && !rev.pending.nr && isatty(0))
add_head_to_pending(&rev);
diff --git a/ci/lib.sh b/ci/lib.sh
index 3eefec500d..821e3660d6 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -178,6 +178,7 @@ fi
export DEVELOPER=1
export DEFAULT_TEST_TARGET=prove
export GIT_TEST_CLONE_2GB=true
+export SKIP_DASHED_BUILT_INS=YesPlease
case "$jobname" in
linux-clang|linux-gcc)
diff --git a/commit-reach.c b/commit-reach.c
index efd5925cbb..50175b159e 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -321,7 +321,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,
{
struct commit_list *bases;
int ret = 0, i;
- uint32_t generation, min_generation = GENERATION_NUMBER_INFINITY;
+ uint32_t generation, max_generation = GENERATION_NUMBER_ZERO;
if (repo_parse_commit(r, commit))
return ret;
@@ -330,12 +330,12 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,
return ret;
generation = commit_graph_generation(reference[i]);
- if (generation < min_generation)
- min_generation = generation;
+ if (generation > max_generation)
+ max_generation = generation;
}
generation = commit_graph_generation(commit);
- if (generation > min_generation)
+ if (generation > max_generation)
return ret;
bases = paint_down_to_common(r, commit,
diff --git a/compat/bswap.h b/compat/bswap.h
index e4e25735ce..c0bb744adc 100644
--- a/compat/bswap.h
+++ b/compat/bswap.h
@@ -145,28 +145,6 @@ static inline uint64_t git_bswap64(uint64_t x)
#endif
-/*
- * Performance might be improved if the CPU architecture is OK with
- * unaligned 32-bit loads and a fast ntohl() is available.
- * Otherwise fall back to byte loads and shifts which is portable,
- * and is faster on architectures with memory alignment issues.
- */
-
-#if !defined(NO_UNALIGNED_LOADS) && ( \
- defined(__i386__) || defined(__x86_64__) || \
- defined(_M_IX86) || defined(_M_X64) || \
- defined(__ppc__) || defined(__ppc64__) || \
- defined(__powerpc__) || defined(__powerpc64__) || \
- defined(__s390__) || defined(__s390x__))
-
-#define get_be16(p) ntohs(*(unsigned short *)(p))
-#define get_be32(p) ntohl(*(unsigned int *)(p))
-#define get_be64(p) ntohll(*(uint64_t *)(p))
-#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
-#define put_be64(p, v) do { *(uint64_t *)(p) = htonll(v); } while (0)
-
-#else
-
static inline uint16_t get_be16(const void *ptr)
{
const unsigned char *p = ptr;
@@ -212,6 +190,4 @@ static inline void put_be64(void *ptr, uint64_t value)
p[7] = value >> 0;
}
-#endif
-
#endif /* COMPAT_BSWAP_H */
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 8be4a0316e..0a96ad87e7 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1508,6 +1508,22 @@ _git_checkout ()
{
__git_has_doubledash && return
+ local dwim_opt="$(__git_checkout_default_dwim_mode)"
+
+ case "$prev" in
+ -b|-B|--orphan)
+ # Complete local branches (and DWIM branch
+ # remote branch names) for an option argument
+ # specifying a new branch name. This is for
+ # convenience, assuming new branches are
+ # possibly based on pre-existing branch names.
+ __git_complete_refs $dwim_opt --mode="heads"
+ return
+ ;;
+ *)
+ ;;
+ esac
+
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
@@ -1516,23 +1532,6 @@ _git_checkout ()
__gitcomp_builtin checkout
;;
*)
- local dwim_opt="$(__git_checkout_default_dwim_mode)"
- local prevword prevword="${words[cword-1]}"
-
- case "$prevword" in
- -b|-B|--orphan)
- # Complete local branches (and DWIM branch
- # remote branch names) for an option argument
- # specifying a new branch name. This is for
- # convenience, assuming new branches are
- # possibly based on pre-existing branch names.
- __git_complete_refs $dwim_opt --mode="heads"
- return
- ;;
- *)
- ;;
- esac
-
# At this point, we've already handled special completion for
# the arguments to -b/-B, and --orphan. There are 3 main
# things left we can possibly complete:
@@ -2392,6 +2391,22 @@ _git_status ()
_git_switch ()
{
+ local dwim_opt="$(__git_checkout_default_dwim_mode)"
+
+ case "$prev" in
+ -c|-C|--orphan)
+ # Complete local branches (and DWIM branch
+ # remote branch names) for an option argument
+ # specifying a new branch name. This is for
+ # convenience, assuming new branches are
+ # possibly based on pre-existing branch names.
+ __git_complete_refs $dwim_opt --mode="heads"
+ return
+ ;;
+ *)
+ ;;
+ esac
+
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
@@ -2400,23 +2415,6 @@ _git_switch ()
__gitcomp_builtin switch
;;
*)
- local dwim_opt="$(__git_checkout_default_dwim_mode)"
- local prevword prevword="${words[cword-1]}"
-
- case "$prevword" in
- -c|-C|--orphan)
- # Complete local branches (and DWIM branch
- # remote branch names) for an option argument
- # specifying a new branch name. This is for
- # convenience, assuming new branches are
- # possibly based on pre-existing branch names.
- __git_complete_refs $dwim_opt --mode="heads"
- return
- ;;
- *)
- ;;
- esac
-
# Unlike in git checkout, git switch --orphan does not take
# a start point. Thus we really have nothing to complete after
# the branch name.
@@ -2843,6 +2841,13 @@ _git_reset ()
_git_restore ()
{
+ case "$prev" in
+ -s)
+ __git_complete_refs
+ return
+ ;;
+ esac
+
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
diff --git a/contrib/mw-to-git/git-mw.perl b/contrib/mw-to-git/git-mw.perl
index 28df3ee321..eb52a53d32 100755
--- a/contrib/mw-to-git/git-mw.perl
+++ b/contrib/mw-to-git/git-mw.perl
@@ -6,7 +6,7 @@
# License: GPL v2 or later
# Set of tools for git repo with a mediawiki remote.
-# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
+# Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki
use strict;
use warnings;
diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl
index d8ff2e69c4..a5624413dc 100755
--- a/contrib/mw-to-git/git-remote-mediawiki.perl
+++ b/contrib/mw-to-git/git-remote-mediawiki.perl
@@ -9,7 +9,7 @@
# License: GPL v2 or later
# Gateway between Git and MediaWiki.
-# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
+# Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki
use strict;
use MediaWiki::API;
@@ -56,38 +56,38 @@ my $url = $ARGV[1];
# Accept both space-separated and multiple keys in config file.
# Spaces should be written as _ anyway because we'll use chomp.
-my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.${remotename}.pages"));
+my @tracked_pages = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.pages"]));
chomp(@tracked_pages);
# Just like @tracked_pages, but for MediaWiki categories.
-my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.${remotename}.categories"));
+my @tracked_categories = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.categories"]));
chomp(@tracked_categories);
# Just like @tracked_categories, but for MediaWiki namespaces.
-my @tracked_namespaces = split(/[ \n]/, run_git("config --get-all remote.${remotename}.namespaces"));
+my @tracked_namespaces = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.namespaces"]));
for (@tracked_namespaces) { s/_/ /g; }
chomp(@tracked_namespaces);
# Import media files on pull
-my $import_media = run_git("config --get --bool remote.${remotename}.mediaimport");
+my $import_media = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.mediaimport"]);
chomp($import_media);
$import_media = ($import_media eq 'true');
# Export media files on push
-my $export_media = run_git("config --get --bool remote.${remotename}.mediaexport");
+my $export_media = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.mediaexport"]);
chomp($export_media);
$export_media = !($export_media eq 'false');
-my $wiki_login = run_git("config --get remote.${remotename}.mwLogin");
+my $wiki_login = run_git_quoted(["config", "--get", "remote.${remotename}.mwLogin"]);
# Note: mwPassword is discouraged. Use the credential system instead.
-my $wiki_passwd = run_git("config --get remote.${remotename}.mwPassword");
-my $wiki_domain = run_git("config --get remote.${remotename}.mwDomain");
+my $wiki_passwd = run_git_quoted(["config", "--get", "remote.${remotename}.mwPassword"]);
+my $wiki_domain = run_git_quoted(["config", "--get", "remote.${remotename}.mwDomain"]);
chomp($wiki_login);
chomp($wiki_passwd);
chomp($wiki_domain);
# Import only last revisions (both for clone and fetch)
-my $shallow_import = run_git("config --get --bool remote.${remotename}.shallow");
+my $shallow_import = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.shallow"]);
chomp($shallow_import);
$shallow_import = ($shallow_import eq 'true');
@@ -97,9 +97,9 @@ $shallow_import = ($shallow_import eq 'true');
# Possible values:
# - by_rev: perform one query per new revision on the remote wiki
# - by_page: query each tracked page for new revision
-my $fetch_strategy = run_git("config --get remote.${remotename}.fetchStrategy");
+my $fetch_strategy = run_git_quoted(["config", "--get", "remote.${remotename}.fetchStrategy"]);
if (!$fetch_strategy) {
- $fetch_strategy = run_git('config --get mediawiki.fetchStrategy');
+ $fetch_strategy = run_git_quoted(["config", "--get", "mediawiki.fetchStrategy"]);
}
chomp($fetch_strategy);
if (!$fetch_strategy) {
@@ -123,9 +123,9 @@ my %basetimestamps;
# will get the history with information lost). If the import is
# deterministic, this means everybody gets the same sha1 for each
# MediaWiki revision.
-my $dumb_push = run_git("config --get --bool remote.${remotename}.dumbPush");
+my $dumb_push = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.dumbPush"]);
if (!$dumb_push) {
- $dumb_push = run_git('config --get --bool mediawiki.dumbPush');
+ $dumb_push = run_git_quoted(["config", "--get", "--bool", "mediawiki.dumbPush"]);
}
chomp($dumb_push);
$dumb_push = ($dumb_push eq 'true');
@@ -369,12 +369,14 @@ sub get_mw_pages {
return %pages;
}
-# usage: $out = run_git("command args");
-# $out = run_git("command args", "raw"); # don't interpret output as UTF-8.
-sub run_git {
+# usage: $out = run_git_quoted(["command", "args", ...]);
+# $out = run_git_quoted(["command", "args", ...], "raw"); # don't interpret output as UTF-8.
+# $out = run_git_quoted_nostderr(["command", "args", ...]); # discard stderr
+# $out = run_git_quoted_nostderr(["command", "args", ...], "raw"); # ditto but raw instead of UTF-8 as above
+sub _run_git {
my $args = shift;
my $encoding = (shift || 'encoding(UTF-8)');
- open(my $git, "-|:${encoding}", "git ${args}")
+ open(my $git, "-|:${encoding}", @$args)
or die "Unable to fork: $!\n";
my $res = do {
local $/ = undef;
@@ -385,6 +387,13 @@ sub run_git {
return $res;
}
+sub run_git_quoted {
+ _run_git(["git", @{$_[0]}], $_[1]);
+}
+
+sub run_git_quoted_nostderr {
+ _run_git(['sh', '-c', 'git "$@" 2>/dev/null', '--', @{$_[0]}], $_[1]);
+}
sub get_all_mediafiles {
my $pages = shift;
@@ -511,8 +520,9 @@ sub download_mw_mediafile {
}
sub get_last_local_revision {
- # Get note regarding last mediawiki revision
- my $note = run_git("notes --ref=${remotename}/mediawiki show refs/mediawiki/${remotename}/master 2>/dev/null");
+ # Get note regarding last mediawiki revision.
+ my $note = run_git_quoted_nostderr(["notes", "--ref=${remotename}/mediawiki",
+ "show", "refs/mediawiki/${remotename}/master"]);
my @note_info = split(/ /, $note);
my $lastrevision_number;
@@ -807,7 +817,10 @@ sub get_more_refs {
sub mw_import {
# multiple import commands can follow each other.
my @refs = (shift, get_more_refs('import'));
+ my $processedRefs;
foreach my $ref (@refs) {
+ next if $processedRefs->{$ref}; # skip duplicates: "import refs/heads/master" being issued twice; TODO: why?
+ $processedRefs->{$ref} = 1;
mw_import_ref($ref);
}
print {*STDOUT} "done\n";
@@ -970,7 +983,7 @@ sub mw_import_revids {
}
sub error_non_fast_forward {
- my $advice = run_git('config --bool advice.pushNonFastForward');
+ my $advice = run_git_quoted(["config", "--bool", "advice.pushNonFastForward"]);
chomp($advice);
if ($advice ne 'false') {
# Native git-push would show this after the summary.
@@ -1014,7 +1027,7 @@ sub mw_upload_file {
}
} else {
# Don't let perl try to interpret file content as UTF-8 => use "raw"
- my $content = run_git("cat-file blob ${new_sha1}", 'raw');
+ my $content = run_git_quoted(["cat-file", "blob", $new_sha1], 'raw');
if ($content ne EMPTY) {
$mediawiki = connect_maybe($mediawiki, $remotename, $url);
$mediawiki->{config}->{upload_url} =
@@ -1084,7 +1097,7 @@ sub mw_push_file {
# with this content instead:
$file_content = DELETED_CONTENT;
} else {
- $file_content = run_git("cat-file blob ${new_sha1}");
+ $file_content = run_git_quoted(["cat-file", "blob", $new_sha1]);
}
$mediawiki = connect_maybe($mediawiki, $remotename, $url);
@@ -1174,10 +1187,10 @@ sub mw_push_revision {
my $mw_revision = $last_remote_revid;
# Get sha1 of commit pointed by local HEAD
- my $HEAD_sha1 = run_git("rev-parse ${local} 2>/dev/null");
+ my $HEAD_sha1 = run_git_quoted_nostderr(["rev-parse", $local]);
chomp($HEAD_sha1);
# Get sha1 of commit pointed by remotes/$remotename/master
- my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/${remotename}/master 2>/dev/null");
+ my $remoteorigin_sha1 = run_git_quoted_nostderr(["rev-parse", "refs/remotes/${remotename}/master"]);
chomp($remoteorigin_sha1);
if ($last_local_revid > 0 &&
@@ -1197,7 +1210,7 @@ sub mw_push_revision {
my $parsed_sha1 = $remoteorigin_sha1;
# Find a path from last MediaWiki commit to pushed commit
print {*STDERR} "Computing path from local to remote ...\n";
- my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents ${local} ^${parsed_sha1}"));
+ my @local_ancestry = split(/\n/, run_git_quoted(["rev-list", "--boundary", "--parents", $local, "^${parsed_sha1}"]));
my %local_ancestry;
foreach my $line (@local_ancestry) {
if (my ($child, $parents) = $line =~ /^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
@@ -1221,7 +1234,7 @@ sub mw_push_revision {
# No remote mediawiki revision. Export the whole
# history (linearized with --first-parent)
print {*STDERR} "Warning: no common ancestor, pushing complete history\n";
- my $history = run_git("rev-list --first-parent --children ${local}");
+ my $history = run_git_quoted(["rev-list", "--first-parent", "--children", $local]);
my @history = split(/\n/, $history);
@history = @history[1..$#history];
foreach my $line (reverse @history) {
@@ -1233,12 +1246,12 @@ sub mw_push_revision {
foreach my $commit_info_split (@commit_pairs) {
my $sha1_child = @{$commit_info_split}[0];
my $sha1_commit = @{$commit_info_split}[1];
- my $diff_infos = run_git("diff-tree -r --raw -z ${sha1_child} ${sha1_commit}");
+ my $diff_infos = run_git_quoted(["diff-tree", "-r", "--raw", "-z", $sha1_child, $sha1_commit]);
# TODO: we could detect rename, and encode them with a #redirect on the wiki.
# TODO: for now, it's just a delete+add
my @diff_info_list = split(/\0/, $diff_infos);
# Keep the subject line of the commit message as mediawiki comment for the revision
- my $commit_msg = run_git(qq(log --no-walk --format="%s" ${sha1_commit}));
+ my $commit_msg = run_git_quoted(["log", "--no-walk", '--format="%s"', $sha1_commit]);
chomp($commit_msg);
# Push every blob
while (@diff_info_list) {
@@ -1263,7 +1276,10 @@ sub mw_push_revision {
}
}
if (!$dumb_push) {
- run_git(qq(notes --ref=${remotename}/mediawiki add -f -m "mediawiki_revision: ${mw_revision}" ${sha1_commit}));
+ run_git_quoted(["notes", "--ref=${remotename}/mediawiki",
+ "add", "-f", "-m",
+ "mediawiki_revision: ${mw_revision}",
+ $sha1_commit]);
}
}
@@ -1304,7 +1320,7 @@ sub get_mw_namespace_id {
# already cached. Namespaces are stored in form:
# "Name_of_namespace:Id_namespace", ex.: "File:6".
my @temp = split(/\n/,
- run_git("config --get-all remote.${remotename}.namespaceCache"));
+ run_git_quoted(["config", "--get-all", "remote.${remotename}.namespaceCache"]));
chomp(@temp);
foreach my $ns (@temp) {
my ($n, $id) = split(/:/, $ns);
@@ -1358,7 +1374,7 @@ sub get_mw_namespace_id {
# Store explicitly requested namespaces on disk
if (!exists $cached_mw_namespace_id{$name}) {
- run_git(qq(config --add remote.${remotename}.namespaceCache "${name}:${store_id}"));
+ run_git_quoted(["config", "--add", "remote.${remotename}.namespaceCache", "${name}:${store_id}"]);
$cached_mw_namespace_id{$name} = 1;
}
return $id;
diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt
index 23b7ef9f62..5da825f61e 100644
--- a/contrib/mw-to-git/git-remote-mediawiki.txt
+++ b/contrib/mw-to-git/git-remote-mediawiki.txt
@@ -4,4 +4,4 @@ objects from mediawiki just as one would do with a classic git
repository thanks to remote-helpers.
For more information, visit the wiki at
-https://github.com/moy/Git-Mediawiki/wiki
+https://github.com/Git-Mediawiki/Git-Mediawiki
diff --git a/contrib/mw-to-git/t/.gitignore b/contrib/mw-to-git/t/.gitignore
index a7a40b4964..2b8dc30c6d 100644
--- a/contrib/mw-to-git/t/.gitignore
+++ b/contrib/mw-to-git/t/.gitignore
@@ -1,4 +1,4 @@
WEB/
-wiki/
+mediawiki/
trash directory.t*/
test-results/
diff --git a/contrib/mw-to-git/t/README b/contrib/mw-to-git/t/README
index 2ee34be7e4..72c4889db7 100644
--- a/contrib/mw-to-git/t/README
+++ b/contrib/mw-to-git/t/README
@@ -14,11 +14,11 @@ install the following packages (Debian/Ubuntu names, may need to be
adapted for another distribution):
* lighttpd
-* php5
-* php5-cgi
-* php5-cli
-* php5-curl
-* php5-sqlite
+* php
+* php-cgi
+* php-cli
+* php-curl
+* php-sqlite
Principles and Technical Choices
--------------------------------
diff --git a/contrib/mw-to-git/t/install-wiki/.gitignore b/contrib/mw-to-git/t/install-wiki/.gitignore
deleted file mode 100644
index b5a2a4408c..0000000000
--- a/contrib/mw-to-git/t/install-wiki/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-wikidb.sqlite
diff --git a/contrib/mw-to-git/t/install-wiki/LocalSettings.php b/contrib/mw-to-git/t/install-wiki/LocalSettings.php
deleted file mode 100644
index 745e47e881..0000000000
--- a/contrib/mw-to-git/t/install-wiki/LocalSettings.php
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-# This file was automatically generated by the MediaWiki 1.19.0
-# installer. If you make manual changes, please keep track in case you
-# need to recreate them later.
-#
-# See includes/DefaultSettings.php for all configurable settings
-# and their default values, but don't forget to make changes in _this_
-# file, not there.
-#
-# Further documentation for configuration settings may be found at:
-# http://www.mediawiki.org/wiki/Manual:Configuration_settings
-
-# Protect against web entry
-if ( !defined( 'MEDIAWIKI' ) ) {
- exit;
-}
-
-## Uncomment this to disable output compression
-# $wgDisableOutputCompression = true;
-
-$wgSitename = "Git-MediaWiki-Test";
-$wgMetaNamespace = "Git-MediaWiki-Test";
-
-## The URL base path to the directory containing the wiki;
-## defaults for all runtime URL paths are based off of this.
-## For more information on customizing the URLs please see:
-## http://www.mediawiki.org/wiki/Manual:Short_URL
-$wgScriptPath = "@WG_SCRIPT_PATH@";
-$wgScriptExtension = ".php";
-
-## The protocol and server name to use in fully-qualified URLs
-$wgServer = "@WG_SERVER@";
-
-## The relative URL path to the skins directory
-$wgStylePath = "$wgScriptPath/skins";
-
-## The relative URL path to the logo. Make sure you change this from the default,
-## or else you'll overwrite your logo when you upgrade!
-$wgLogo = "$wgStylePath/common/images/wiki.png";
-
-## UPO means: this is also a user preference option
-
-$wgEnableEmail = true;
-$wgEnableUserEmail = true; # UPO
-
-$wgEmergencyContact = "apache@localhost";
-$wgPasswordSender = "apache@localhost";
-
-$wgEnotifUserTalk = false; # UPO
-$wgEnotifWatchlist = false; # UPO
-$wgEmailAuthentication = true;
-
-## Database settings
-$wgDBtype = "sqlite";
-$wgDBserver = "";
-$wgDBname = "@WG_SQLITE_DATAFILE@";
-$wgDBuser = "";
-$wgDBpassword = "";
-
-# SQLite-specific settings
-$wgSQLiteDataDir = "@WG_SQLITE_DATADIR@";
-
-
-## Shared memory settings
-$wgMainCacheType = CACHE_NONE;
-$wgMemCachedServers = array();
-
-## To enable image uploads, make sure the 'images' directory
-## is writable, then set this to true:
-$wgEnableUploads = true;
-$wgUseImageMagick = true;
-$wgImageMagickConvertCommand ="@CONVERT@";
-$wgFileExtensions[] = 'txt';
-
-# InstantCommons allows wiki to use images from http://commons.wikimedia.org
-$wgUseInstantCommons = false;
-
-## If you use ImageMagick (or any other shell command) on a
-## Linux server, this will need to be set to the name of an
-## available UTF-8 locale
-$wgShellLocale = "en_US.utf8";
-
-## If you want to use image uploads under safe mode,
-## create the directories images/archive, images/thumb and
-## images/temp, and make them all writable. Then uncomment
-## this, if it's not already uncommented:
-#$wgHashedUploadDirectory = false;
-
-## Set $wgCacheDirectory to a writable directory on the web server
-## to make your wiki go slightly faster. The directory should not
-## be publicly accessible from the web.
-#$wgCacheDirectory = "$IP/cache";
-
-# Site language code, should be one of the list in ./languages/Names.php
-$wgLanguageCode = "en";
-
-$wgSecretKey = "1c912bfe3519fb70f5dc523ecc698111cd43d81a11c585b3eefb28f29c2699b7";
-#$wgSecretKey = "@SECRETKEY@";
-
-
-# Site upgrade key. Must be set to a string (default provided) to turn on the
-# web installer while LocalSettings.php is in place
-$wgUpgradeKey = "ddae7dc87cd0a645";
-
-## Default skin: you can change the default skin. Use the internal symbolic
-## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
-$wgDefaultSkin = "vector";
-
-## For attaching licensing metadata to pages, and displaying an
-## appropriate copyright notice / icon. GNU Free Documentation
-## License and Creative Commons licenses are supported so far.
-$wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
-$wgRightsUrl = "";
-$wgRightsText = "";
-$wgRightsIcon = "";
-
-# Path to the GNU diff3 utility. Used for conflict resolution.
-$wgDiff3 = "/usr/bin/diff3";
-
-# Query string length limit for ResourceLoader. You should only set this if
-# your web server has a query string length limit (then set it to that limit),
-# or if you have suhosin.get.max_value_length set in php.ini (then set it to
-# that value)
-$wgResourceLoaderMaxQueryLength = -1;
-
-
-
-# End of automatically generated settings.
-# Add more configuration options below.
diff --git a/contrib/mw-to-git/t/install-wiki/db_install.php b/contrib/mw-to-git/t/install-wiki/db_install.php
deleted file mode 100644
index b033849800..0000000000
--- a/contrib/mw-to-git/t/install-wiki/db_install.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-/**
- * This script generates a SQLite database for a MediaWiki version 1.19.0
- * You must specify the login of the admin (argument 1) and its
- * password (argument 2) and the folder where the database file
- * is located (absolute path in argument 3).
- * It is used by the script install-wiki.sh in order to make easy the
- * installation of a MediaWiki.
- *
- * In order to generate a SQLite database file, MediaWiki ask the user
- * to submit some forms in its web browser. This script simulates this
- * behavior though the functions <get> and <submit>
- *
- */
-$argc = $_SERVER['argc'];
-$argv = $_SERVER['argv'];
-
-$login = $argv[2];
-$pass = $argv[3];
-$tmp = $argv[4];
-$port = $argv[5];
-
-$url = 'http://localhost:'.$port.'/wiki/mw-config/index.php';
-$db_dir = urlencode($tmp);
-$tmp_cookie = tempnam($tmp, "COOKIE_");
-/*
- * Fetches a page with cURL.
- */
-function get($page_name = "") {
- $curl = curl_init();
- $page_name_add = "";
- if ($page_name != "") {
- $page_name_add = '?page='.$page_name;
- }
- $url = $GLOBALS['url'].$page_name_add;
- $tmp_cookie = $GLOBALS['tmp_cookie'];
- curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
- curl_setopt($curl, CURLOPT_HEADER, true);
- curl_setopt($curl, CURLOPT_URL, $url);
-
- $page = curl_exec($curl);
- if (!$page) {
- die("Could not get page: $url\n");
- }
- curl_close($curl);
- return $page;
-}
-
-/*
- * Submits a form with cURL.
- */
-function submit($page_name, $option = "") {
- $curl = curl_init();
- $datapost = 'submit-continue=Continue+%E2%86%92';
- if ($option != "") {
- $datapost = $option.'&'.$datapost;
- }
- $url = $GLOBALS['url'].'?page='.$page_name;
- $tmp_cookie = $GLOBALS['tmp_cookie'];
- curl_setopt($curl, CURLOPT_URL, $url);
- curl_setopt($curl, CURLOPT_POST, true);
- curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($curl, CURLOPT_POSTFIELDS, $datapost);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
- curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
-
- $page = curl_exec($curl);
- if (!$page) {
- die("Could not get page: $url\n");
- }
- curl_close($curl);
- return "$page";
-}
-
-/*
- * Here starts this script: simulates the behavior of the user
- * submitting forms to generates the database file.
- * Note this simulation was made for the MediaWiki version 1.19.0,
- * we can't assume it works with other versions.
- *
- */
-
-$page = get();
-if (!preg_match('/input type="hidden" value="([0-9]+)" name="LanguageRequestTime"/',
- $page, $matches)) {
- echo "Unexpected content for page downloaded:\n";
- echo "$page";
- die;
-};
-$timestamp = $matches[1];
-$language = "LanguageRequestTime=$timestamp&uselang=en&ContLang=en";
-$page = submit('Language', $language);
-
-submit('Welcome');
-
-$db_config = 'DBType=sqlite';
-$db_config = $db_config.'&sqlite_wgSQLiteDataDir='.$db_dir;
-$db_config = $db_config.'&sqlite_wgDBname='.$argv[1];
-submit('DBConnect', $db_config);
-
-$wiki_config = 'config_wgSitename=TEST';
-$wiki_config = $wiki_config.'&config__NamespaceType=site-name';
-$wiki_config = $wiki_config.'&config_wgMetaNamespace=MyWiki';
-$wiki_config = $wiki_config.'&config__AdminName='.$login;
-
-$wiki_config = $wiki_config.'&config__AdminPassword='.$pass;
-$wiki_config = $wiki_config.'&config__AdminPassword2='.$pass;
-
-$wiki_config = $wiki_config.'&wiki__configEmail=email%40email.org';
-$wiki_config = $wiki_config.'&config__SkipOptional=skip';
-submit('Name', $wiki_config);
-submit('Install');
-submit('Install');
-
-unlink($tmp_cookie);
-?>
diff --git a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
index 9106833578..4c39bda7bf 100755
--- a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
+++ b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
@@ -28,7 +28,7 @@ test_expect_success 'Git clone creates the expected git log with one file' '
git log --format=%s HEAD^..HEAD >log.tmp
) &&
echo "this must be the same" >msg.tmp &&
- diff -b mw_dir_1/log.tmp msg.tmp
+ test_cmp msg.tmp mw_dir_1/log.tmp
'
@@ -50,8 +50,8 @@ test_expect_success 'Git clone creates the expected git log with multiple files'
echo "this must be the same" >>msgDaddy.tmp &&
echo "identical too" >msgDj.tmp &&
echo "identical" >>msgDj.tmp &&
- diff -b mw_dir_2/logDaddy.tmp msgDaddy.tmp &&
- diff -b mw_dir_2/logDj.tmp msgDj.tmp
+ test_cmp msgDaddy.tmp mw_dir_2/logDaddy.tmp &&
+ test_cmp msgDj.tmp mw_dir_2/logDj.tmp
'
@@ -135,7 +135,7 @@ test_expect_success 'Git clone works with one specific page cloned ' '
cd mw_dir_8 &&
echo "this log must stay" >msg.tmp &&
git log --format=%s >log.tmp &&
- diff -b msg.tmp log.tmp
+ test_cmp msg.tmp log.tmp
) &&
wiki_check_content mw_dir_8/Namnam.mw Namnam
'
diff --git a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
index 3ff3a09567..6187ec67fa 100755
--- a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
+++ b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
@@ -27,12 +27,12 @@ test_git_reimport () {
# Don't bother with permissions, be administrator by default
test_expect_success 'setup config' '
- git config --global remote.origin.mwLogin WikiAdmin &&
- git config --global remote.origin.mwPassword AdminPass &&
+ git config --global remote.origin.mwLogin "$WIKI_ADMIN" &&
+ git config --global remote.origin.mwPassword "$WIKI_PASSW" &&
test_might_fail git config --global --unset remote.origin.mediaImport
'
-test_expect_success 'git push can upload media (File:) files' '
+test_expect_failure 'git push can upload media (File:) files' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir &&
(
@@ -48,13 +48,14 @@ test_expect_success 'git push can upload media (File:) files' '
)
'
-test_expect_success 'git clone works on previously created wiki with media files' '
+test_expect_failure 'git clone works on previously created wiki with media files' '
test_when_finished "rm -rf mw_dir mw_dir_clone" &&
git clone -c remote.origin.mediaimport=true \
mediawiki::'"$WIKI_URL"' mw_dir_clone &&
test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt &&
(cd mw_dir_clone && git checkout HEAD^) &&
(cd mw_dir && git checkout HEAD^) &&
+ test_path_is_file mw_dir_clone/Foo.txt &&
test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt
'
diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh
index 3948a00282..64e46c1671 100755
--- a/contrib/mw-to-git/t/test-gitmw-lib.sh
+++ b/contrib/mw-to-git/t/test-gitmw-lib.sh
@@ -13,7 +13,8 @@
. ./test.config
-WIKI_URL=http://"$SERVER_ADDR:$PORT/$WIKI_DIR_NAME"
+WIKI_BASE_URL=http://$SERVER_ADDR:$PORT
+WIKI_URL=$WIKI_BASE_URL/$WIKI_DIR_NAME
CURR_DIR=$(pwd)
TEST_OUTPUT_DIRECTORY=$(pwd)
TEST_DIRECTORY="$CURR_DIR"/../../../t
@@ -65,7 +66,7 @@ test_check_precond () {
GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd "../.." && pwd)
PATH="$GIT_EXEC_PATH"'/bin-wrapper:'"$PATH"
- if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ];
+ if ! test -d "$WIKI_DIR_INST/$WIKI_DIR_NAME"
then
skip_all='skipping gateway git-mw tests, no mediawiki found'
test_done
@@ -291,27 +292,59 @@ stop_lighttpd () {
test -f "$WEB_TMP/pid" && kill $(cat "$WEB_TMP/pid")
}
-# Create the SQLite database of the MediaWiki. If the database file already
-# exists, it will be deleted.
-# This script should be runned from the directory where $FILES_FOLDER is
-# located.
-create_db () {
- rm -f "$TMP/$DB_FILE"
-
- echo "Generating the SQLite database file. It can take some time ..."
- # Run the php script to generate the SQLite database file
- # with cURL calls.
- php "$FILES_FOLDER/$DB_INSTALL_SCRIPT" $(basename "$DB_FILE" .sqlite) \
- "$WIKI_ADMIN" "$WIKI_PASSW" "$TMP" "$PORT"
-
- if [ ! -f "$TMP/$DB_FILE" ] ; then
- error "Can't create database file $TMP/$DB_FILE. Try to run ./install-wiki.sh delete first."
+wiki_delete_db () {
+ rm -rf \
+ "$FILES_FOLDER_DB"/* || error "Couldn't delete $FILES_FOLDER_DB/"
+}
+
+wiki_delete_db_backup () {
+ rm -rf \
+ "$FILES_FOLDER_POST_INSTALL_DB"/* || error "Couldn't delete $FILES_FOLDER_POST_INSTALL_DB/"
+}
+
+# Install MediaWiki using its install.php script. If the database file
+# already exists, it will be deleted.
+install_mediawiki () {
+
+ localsettings="$WIKI_DIR_INST/$WIKI_DIR_NAME/LocalSettings.php"
+ if test -f "$localsettings"
+ then
+ error "We already installed the wiki, since $localsettings exists" \
+ "perhaps you wanted to run 'delete' first?"
fi
- # Copy the generated database file into the directory the
- # user indicated.
- cp "$TMP/$DB_FILE" "$FILES_FOLDER" ||
- error "Unable to copy $TMP/$DB_FILE to $FILES_FOLDER"
+ wiki_delete_db
+ wiki_delete_db_backup
+ mkdir \
+ "$FILES_FOLDER_DB/" \
+ "$FILES_FOLDER_POST_INSTALL_DB/"
+
+ install_script="$WIKI_DIR_INST/$WIKI_DIR_NAME/maintenance/install.php"
+ echo "Installing MediaWiki using $install_script. This may take some time ..."
+
+ php "$WIKI_DIR_INST/$WIKI_DIR_NAME/maintenance/install.php" \
+ --server $WIKI_BASE_URL \
+ --scriptpath /wiki \
+ --lang en \
+ --dbtype sqlite \
+ --dbpath $PWD/$FILES_FOLDER_DB/ \
+ --pass "$WIKI_PASSW" \
+ Git-MediaWiki-Test \
+ "$WIKI_ADMIN" ||
+ error "Couldn't run $install_script, see errors above. Try to run ./install-wiki.sh delete first."
+ cat <<-'EOF' >>$localsettings
+# Custom settings added by test-gitmw-lib.sh
+#
+# Uploading text files is needed for
+# t9363-mw-to-git-export-import.sh
+$wgEnableUploads = true;
+$wgFileExtensions[] = 'txt';
+EOF
+
+ # Copy the initially generated database file into our backup
+ # folder
+ cp -R "$FILES_FOLDER_DB/"* "$FILES_FOLDER_POST_INSTALL_DB/" ||
+ error "Unable to copy $FILES_FOLDER_DB/* to $FILES_FOLDER_POST_INSTALL_DB/*"
}
# Install a wiki in your web server directory.
@@ -320,30 +353,33 @@ wiki_install () {
start_lighttpd
fi
- SERVER_ADDR=$SERVER_ADDR:$PORT
# In this part, we change directory to $TMP in order to download,
# unpack and copy the files of MediaWiki
(
mkdir -p "$WIKI_DIR_INST/$WIKI_DIR_NAME"
- if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ] ; then
+ if ! test -d "$WIKI_DIR_INST/$WIKI_DIR_NAME"
+ then
error "Folder $WIKI_DIR_INST/$WIKI_DIR_NAME doesn't exist.
Please create it and launch the script again."
fi
- # Fetch MediaWiki's archive if not already present in the TMP directory
+ # Fetch MediaWiki's archive if not already present in the
+ # download directory
+ mkdir -p "$FILES_FOLDER_DOWNLOAD"
MW_FILENAME="mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
- cd "$TMP"
- if [ ! -f $MW_FILENAME ] ; then
+ cd "$FILES_FOLDER_DOWNLOAD"
+ if ! test -f $MW_FILENAME
+ then
echo "Downloading $MW_VERSION_MAJOR.$MW_VERSION_MINOR sources ..."
wget "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/$MW_FILENAME" ||
error "Unable to download "\
"http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\
"$MW_FILENAME. "\
"Please fix your connection and launch the script again."
- echo "$MW_FILENAME downloaded in $(pwd). "\
- "You can delete it later if you want."
+ echo "$MW_FILENAME downloaded in $(pwd)/;" \
+ "you can delete it later if you want."
else
- echo "Reusing existing $MW_FILENAME downloaded in $(pwd)."
+ echo "Reusing existing $MW_FILENAME downloaded in $(pwd)/"
fi
archive_abs_path=$(pwd)/$MW_FILENAME
cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" ||
@@ -352,48 +388,12 @@ wiki_install () {
error "Unable to extract WikiMedia's files from $archive_abs_path to "\
"$WIKI_DIR_INST/$WIKI_DIR_NAME"
) || exit 1
+ echo Extracted in "$WIKI_DIR_INST/$WIKI_DIR_NAME"
- create_db
-
- # Copy the generic LocalSettings.php in the web server's directory
- # And modify parameters according to the ones set at the top
- # of this script.
- # Note that LocalSettings.php is never modified.
- if [ ! -f "$FILES_FOLDER/LocalSettings.php" ] ; then
- error "Can't find $FILES_FOLDER/LocalSettings.php " \
- "in the current folder. "\
- "Please run the script inside its folder."
- fi
- cp "$FILES_FOLDER/LocalSettings.php" \
- "$FILES_FOLDER/LocalSettings-tmp.php" ||
- error "Unable to copy $FILES_FOLDER/LocalSettings.php " \
- "to $FILES_FOLDER/LocalSettings-tmp.php"
-
- # Parse and set the LocalSettings file of the user according to the
- # CONFIGURATION VARIABLES section at the beginning of this script
- file_swap="$FILES_FOLDER/LocalSettings-swap.php"
- sed "s,@WG_SCRIPT_PATH@,/$WIKI_DIR_NAME," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SERVER@,http://$SERVER_ADDR," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SQLITE_DATADIR@,$TMP," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SQLITE_DATAFILE@,$( basename $DB_FILE .sqlite)," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
-
- mv "$FILES_FOLDER/LocalSettings-tmp.php" \
- "$WIKI_DIR_INST/$WIKI_DIR_NAME/LocalSettings.php" ||
- error "Unable to move $FILES_FOLDER/LocalSettings-tmp.php" \
- "in $WIKI_DIR_INST/$WIKI_DIR_NAME"
- echo "File $FILES_FOLDER/LocalSettings.php is set in" \
- " $WIKI_DIR_INST/$WIKI_DIR_NAME"
+ install_mediawiki
echo "Your wiki has been installed. You can check it at
- http://$SERVER_ADDR/$WIKI_DIR_NAME"
+ $WIKI_URL"
}
# Reset the database of the wiki and the password of the admin
@@ -401,12 +401,18 @@ wiki_install () {
# Warning: This function must be called only in a subdirectory of t/ directory
wiki_reset () {
# Copy initial database of the wiki
- if [ ! -f "../$FILES_FOLDER/$DB_FILE" ] ; then
- error "Can't find ../$FILES_FOLDER/$DB_FILE in the current folder."
+ if ! test -d "../$FILES_FOLDER_DB"
+ then
+ error "No wiki database at ../$FILES_FOLDER_DB, not installed yet?"
+ fi
+ if ! test -d "../$FILES_FOLDER_POST_INSTALL_DB"
+ then
+ error "No wiki backup database at ../$FILES_FOLDER_POST_INSTALL_DB, failed installation?"
fi
- cp "../$FILES_FOLDER/$DB_FILE" "$TMP" ||
- error "Can't copy ../$FILES_FOLDER/$DB_FILE in $TMP"
- echo "File $FILES_FOLDER/$DB_FILE is set in $TMP"
+ wiki_delete_db
+ cp -R "../$FILES_FOLDER_POST_INSTALL_DB/"* "../$FILES_FOLDER_DB/" ||
+ error "Can't copy ../$FILES_FOLDER_POST_INSTALL_DB/* to ../$FILES_FOLDER_DB/*"
+ echo "File $FILES_FOLDER_DB/* has been reset"
}
# Delete the wiki created in the web server's directory and all its content
@@ -420,13 +426,7 @@ wiki_delete () {
rm -rf "$WIKI_DIR_INST/$WIKI_DIR_NAME" ||
error "Wiki's directory $WIKI_DIR_INST/" \
"$WIKI_DIR_NAME could not be deleted"
- # Delete the wiki's SQLite database.
- rm -f "$TMP/$DB_FILE" ||
- error "Database $TMP/$DB_FILE could not be deleted."
fi
-
- # Delete the wiki's SQLite database
- rm -f "$TMP/$DB_FILE" || error "Database $TMP/$DB_FILE could not be deleted."
- rm -f "$FILES_FOLDER/$DB_FILE"
- rm -rf "$TMP/mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
+ wiki_delete_db
+ wiki_delete_db_backup
}
diff --git a/contrib/mw-to-git/t/test-gitmw.pl b/contrib/mw-to-git/t/test-gitmw.pl
index 0ff76259fa..c5d687f078 100755
--- a/contrib/mw-to-git/t/test-gitmw.pl
+++ b/contrib/mw-to-git/t/test-gitmw.pl
@@ -24,9 +24,7 @@
use MediaWiki::API;
use Getopt::Long;
-use encoding 'utf8';
use DateTime::Format::ISO8601;
-use open ':encoding(utf8)';
use constant SLASH_REPLACEMENT => "%2F";
#Parsing of the config file
@@ -87,7 +85,7 @@ sub wiki_getpage {
# Replace spaces by underscore in the page name
$pagename =~ s/ /_/g;
$pagename =~ s/\//%2F/g;
- open(my $file, ">$destdir/$pagename.mw");
+ open(my $file, ">:encoding(UTF-8)", "$destdir/$pagename.mw");
print $file "$content";
close ($file);
@@ -172,7 +170,7 @@ sub wiki_getallpagename {
cmlimit => 500 },
)
|| die $mw->{error}->{code}.": ".$mw->{error}->{details};
- open(my $file, ">all.txt");
+ open(my $file, ">:encoding(UTF-8)", "all.txt");
foreach my $page (@{$mw_pages}) {
print $file "$page->{title}\n";
}
@@ -185,7 +183,7 @@ sub wiki_getallpagename {
aplimit => 500,
})
|| die $mw->{error}->{code}.": ".$mw->{error}->{details};
- open(my $file, ">all.txt");
+ open(my $file, ">:encoding(UTF-8)", "all.txt");
foreach my $page (@{$mw_pages}) {
print $file "$page->{title}\n";
}
@@ -214,12 +212,12 @@ my $fct_to_call = shift;
wiki_login($wiki_admin, $wiki_admin_pass);
-my %functions_to_call = qw(
- upload_file wiki_upload_file
- get_page wiki_getpage
- delete_page wiki_delete_page
- edit_page wiki_editpage
- getallpagename wiki_getallpagename
+my %functions_to_call = (
+ upload_file => \&wiki_upload_file,
+ get_page => \&wiki_getpage,
+ delete_page => \&wiki_delete_page,
+ edit_page => \&wiki_editpage,
+ getallpagename => \&wiki_getallpagename,
);
die "$0 ERROR: wrong argument" unless exists $functions_to_call{$fct_to_call};
-&{$functions_to_call{$fct_to_call}}(@ARGV);
+$functions_to_call{$fct_to_call}->(map { utf8::decode($_); $_ } @ARGV);
diff --git a/contrib/mw-to-git/t/test.config b/contrib/mw-to-git/t/test.config
index 5ba0684162..ed10b3e4a4 100644
--- a/contrib/mw-to-git/t/test.config
+++ b/contrib/mw-to-git/t/test.config
@@ -3,15 +3,11 @@ WIKI_DIR_NAME=wiki
# Login and password of the wiki's admin
WIKI_ADMIN=WikiAdmin
-WIKI_PASSW=AdminPass
+WIKI_PASSW=AdminPass1
# Address of the web server
SERVER_ADDR=localhost
-# SQLite database of the wiki, named DB_FILE, is located in TMP
-TMP=/tmp
-DB_FILE=wikidb.sqlite
-
# If LIGHTTPD is not set to true, the script will use the default
# web server running in WIKI_DIR_INST.
WIKI_DIR_INST=/var/www
@@ -28,10 +24,17 @@ WEB=WEB
WEB_TMP=$WEB/tmp
WEB_WWW=$WEB/www
+# Where our configuration for the wiki is located
+FILES_FOLDER=mediawiki
+FILES_FOLDER_DOWNLOAD=$FILES_FOLDER/download
+FILES_FOLDER_DB=$FILES_FOLDER/db
+FILES_FOLDER_POST_INSTALL_DB=$FILES_FOLDER/post-install-db
+
# The variables below are used by the script to install a wiki.
# You should not modify these unless you are modifying the script itself.
-# tested versions: 1.19.X -> 1.21.1
-MW_VERSION_MAJOR=1.21
-MW_VERSION_MINOR=1
-FILES_FOLDER=install-wiki
-DB_INSTALL_SCRIPT=db_install.php
+# tested versions: 1.19.X -> 1.21.1 -> 1.34.2
+#
+# See https://www.mediawiki.org/wiki/Download for what the latest
+# version is.
+MW_VERSION_MAJOR=1.34
+MW_VERSION_MINOR=2
diff --git a/git-bisect.sh b/git-bisect.sh
index 2f60fefcfa..ea7e684ebb 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -49,27 +49,6 @@ bisect_head()
fi
}
-bisect_autostart() {
- test -s "$GIT_DIR/BISECT_START" || {
- gettextln "You need to start by \"git bisect start\"" >&2
- if test -t 0
- then
- # TRANSLATORS: Make sure to include [Y] and [n] in your
- # translation. The program will only accept English input
- # at this point.
- gettext "Do you want me to do it for you [Y/n]? " >&2
- read yesno
- case "$yesno" in
- [Nn]*)
- exit ;;
- esac
- bisect_start
- else
- exit 1
- fi
- }
-}
-
bisect_start() {
git bisect--helper --bisect-start $@ || exit
@@ -86,8 +65,7 @@ bisect_start() {
#
# Check if we can proceed to the next bisect state.
#
- get_terms
- bisect_auto_next
+ git bisect--helper --bisect-auto-next || exit
trap '-' 0
}
@@ -108,7 +86,7 @@ bisect_skip() {
}
bisect_state() {
- bisect_autostart
+ git bisect--helper --bisect-autostart || exit
state=$1
git bisect--helper --check-and-set-terms $state $TERM_GOOD $TERM_BAD || exit
get_terms
@@ -140,45 +118,7 @@ bisect_state() {
*)
usage ;;
esac
- bisect_auto_next
-}
-
-bisect_auto_next() {
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD && bisect_next || :
-}
-
-bisect_next() {
- case "$#" in 0) ;; *) usage ;; esac
- bisect_autostart
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit
-
- # Perform all bisection computation, display and checkout
- git bisect--helper --next-all
- res=$?
-
- # Check if we should exit because bisection is finished
- if test $res -eq 10
- then
- bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD)
- bad_commit=$(git show-branch $bad_rev)
- echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
- exit 0
- elif test $res -eq 2
- then
- echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
- good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*")
- for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs)
- do
- skipped_commit=$(git show-branch $skipped)
- echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
- done
- exit $res
- fi
-
- # Check for an error in the bisection process
- test $res -ne 0 && exit $res
-
- return 0
+ git bisect--helper --bisect-auto-next
}
bisect_visualize() {
@@ -234,7 +174,7 @@ bisect_replay () {
esac
done <"$file"
IFS="$oIFS"
- bisect_auto_next
+ git bisect--helper --bisect-auto-next || exit
}
bisect_run () {
@@ -331,7 +271,7 @@ case "$#" in
bisect_skip "$@" ;;
next)
# Not sure we want "next" at the UI level anymore.
- bisect_next "$@" ;;
+ git bisect--helper --bisect-next "$@" || exit ;;
visualize|view)
bisect_visualize "$@" ;;
reset)
diff --git a/oidset.c b/oidset.c
index 15d4e18c37..2d0ab76fb5 100644
--- a/oidset.c
+++ b/oidset.c
@@ -43,6 +43,12 @@ int oidset_size(struct oidset *set)
void oidset_parse_file(struct oidset *set, const char *path)
{
+ oidset_parse_file_carefully(set, path, NULL, NULL);
+}
+
+void oidset_parse_file_carefully(struct oidset *set, const char *path,
+ oidset_parse_tweak_fn fn, void *cbdata)
+{
FILE *fp;
struct strbuf sb = STRBUF_INIT;
struct object_id oid;
@@ -66,7 +72,8 @@ void oidset_parse_file(struct oidset *set, const char *path)
if (!sb.len)
continue;
- if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0')
+ if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0' ||
+ (fn && fn(&oid, cbdata)))
die("invalid object name: %s", sb.buf);
oidset_insert(set, &oid);
}
diff --git a/oidset.h b/oidset.h
index 209ae7a173..01f6560283 100644
--- a/oidset.h
+++ b/oidset.h
@@ -73,6 +73,15 @@ void oidset_clear(struct oidset *set);
*/
void oidset_parse_file(struct oidset *set, const char *path);
+/*
+ * Similar to the above, but with a callback which can (1) return non-zero to
+ * signal displeasure with the object and (2) replace object ID with something
+ * else (meant to be used to "peel").
+ */
+typedef int (*oidset_parse_tweak_fn)(struct object_id *, void *);
+void oidset_parse_file_carefully(struct oidset *set, const char *path,
+ oidset_parse_tweak_fn fn, void *cbdata);
+
struct oidset_iter {
kh_oid_set_t *set;
khiter_t iter;
diff --git a/packfile.c b/packfile.c
index e69012e7f2..0929ebe4fc 100644
--- a/packfile.c
+++ b/packfile.c
@@ -1475,7 +1475,7 @@ void clear_delta_base_cache(void)
static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
void *base, unsigned long base_size, enum object_type type)
{
- struct delta_base_cache_entry *ent = xmalloc(sizeof(*ent));
+ struct delta_base_cache_entry *ent;
struct list_head *lru, *tmp;
/*
@@ -1483,8 +1483,10 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
* is unpacking the same object, in unpack_entry() (since its phases I
* and III might run concurrently across multiple threads).
*/
- if (in_delta_base_cache(p, base_offset))
+ if (in_delta_base_cache(p, base_offset)) {
+ free(base);
return;
+ }
delta_base_cached += base_size;
@@ -1496,6 +1498,7 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
release_delta_base_cache(f);
}
+ ent = xmalloc(sizeof(*ent));
ent->key.p = p;
ent->key.base_offset = base_offset;
ent->type = type;
@@ -1776,12 +1779,10 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
void *external_base = NULL;
unsigned long delta_size, base_size = size;
int i;
+ off_t base_obj_offset = obj_offset;
data = NULL;
- if (base)
- add_delta_base_cache(p, obj_offset, base, base_size, type);
-
if (!base) {
/*
* We're probably in deep shit, but let's try to fetch
@@ -1819,24 +1820,33 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
"at offset %"PRIuMAX" from %s",
(uintmax_t)curpos, p->pack_name);
data = NULL;
- free(external_base);
- continue;
- }
+ } else {
+ data = patch_delta(base, base_size, delta_data,
+ delta_size, &size);
- data = patch_delta(base, base_size,
- delta_data, delta_size,
- &size);
+ /*
+ * We could not apply the delta; warn the user, but
+ * keep going. Our failure will be noticed either in
+ * the next iteration of the loop, or if this is the
+ * final delta, in the caller when we return NULL.
+ * Those code paths will take care of making a more
+ * explicit warning and retrying with another copy of
+ * the object.
+ */
+ if (!data)
+ error("failed to apply delta");
+ }
/*
- * We could not apply the delta; warn the user, but keep going.
- * Our failure will be noticed either in the next iteration of
- * the loop, or if this is the final delta, in the caller when
- * we return NULL. Those code paths will take care of making
- * a more explicit warning and retrying with another copy of
- * the object.
+ * We delay adding `base` to the cache until the end of the loop
+ * because unpack_compressed_entry() momentarily releases the
+ * obj_read_mutex, giving another thread the chance to access
+ * the cache. Therefore, if `base` was already there, this other
+ * thread could free() it (e.g. to make space for another entry)
+ * before we are done using it.
*/
- if (!data)
- error("failed to apply delta");
+ if (!external_base)
+ add_delta_base_cache(p, base_obj_offset, base, base_size, type);
free(delta_data);
free(external_base);
diff --git a/ref-filter.c b/ref-filter.c
index 5550a0d34c..e0b8cd3ed8 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2239,12 +2239,14 @@ static void reach_filter(struct ref_array *array,
{
struct rev_info revs;
int i, old_nr;
- struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
+ struct commit **to_clear;
struct commit_list *cr;
if (!check_reachable)
return;
+ to_clear = xcalloc(sizeof(struct commit *), array->nr);
+
repo_init_revisions(the_repository, &revs, NULL);
for (i = 0; i < array->nr; i++) {
diff --git a/sequencer.c b/sequencer.c
index e8676e965f..3367eb5fbf 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -120,7 +120,7 @@ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
/*
* When we stop at a given patch via the "edit" command, this file contains
- * the abbreviated commit name of the corresponding patch.
+ * the commit object name of the corresponding patch.
*/
static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
/*
@@ -3111,11 +3111,12 @@ static int make_patch(struct repository *r,
{
struct strbuf buf = STRBUF_INIT;
struct rev_info log_tree_opt;
- const char *subject, *p;
+ const char *subject;
+ char hex[GIT_MAX_HEXSZ + 1];
int res = 0;
- p = short_commit_name(commit);
- if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
+ oid_to_hex_r(hex, &commit->object.oid);
+ if (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)
return -1;
res |= write_rebase_head(&commit->object.oid);
@@ -4527,7 +4528,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
if (read_oneliner(&buf, rebase_path_stopped_sha(),
READ_ONELINER_SKIP_IF_EMPTY) &&
- !get_oid_committish(buf.buf, &oid))
+ !get_oid_hex(buf.buf, &oid))
record_in_rewritten(&oid, peek_command(&todo_list, 0));
strbuf_release(&buf);
}
diff --git a/shortlog.h b/shortlog.h
index 2fa61c4294..64be879b24 100644
--- a/shortlog.h
+++ b/shortlog.h
@@ -15,7 +15,13 @@ struct shortlog {
int in2;
int user_format;
int abbrev;
- int committer;
+
+ enum {
+ SHORTLOG_GROUP_AUTHOR = (1 << 0),
+ SHORTLOG_GROUP_COMMITTER = (1 << 1),
+ SHORTLOG_GROUP_TRAILER = (1 << 2),
+ } groups;
+ struct string_list trailers;
char *common_repo_prefix;
int email;
diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c
index 14a3655442..cda804ed79 100644
--- a/t/helper/test-reach.c
+++ b/t/helper/test-reach.c
@@ -107,6 +107,8 @@ int cmd__reach(int ac, const char **av)
printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B));
else if (!strcmp(av[1], "in_merge_bases"))
printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B));
+ else if (!strcmp(av[1], "in_merge_bases_many"))
+ printf("%s(A,X):%d\n", av[1], in_merge_bases_many(A, X_nr, X_array));
else if (!strcmp(av[1], "is_descendant_of"))
printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X));
else if (!strcmp(av[1], "get_merge_bases_many")) {
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index d3a7ce6bbb..3d5c4a2086 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -215,4 +215,145 @@ test_expect_success 'shortlog --committer (external)' '
test_cmp expect actual
'
+test_expect_success '--group=committer is the same as --committer' '
+ git shortlog -ns --group=committer HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'shortlog --group=trailer:signed-off-by' '
+ git commit --allow-empty -m foo -s &&
+ GIT_COMMITTER_NAME="SOB One" \
+ GIT_COMMITTER_EMAIL=sob@example.com \
+ git commit --allow-empty -m foo -s &&
+ git commit --allow-empty --amend --no-edit -s &&
+ cat >expect <<-\EOF &&
+ 2 C O Mitter <committer@example.com>
+ 1 SOB One <sob@example.com>
+ EOF
+ git shortlog -nse --group=trailer:signed-off-by HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'trailer idents are split' '
+ cat >expect <<-\EOF &&
+ 2 C O Mitter
+ 1 SOB One
+ EOF
+ git shortlog -ns --group=trailer:signed-off-by HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'trailer idents are mailmapped' '
+ cat >expect <<-\EOF &&
+ 2 C O Mitter
+ 1 Another Name
+ EOF
+ echo "Another Name <sob@example.com>" >mail.map &&
+ git -c mailmap.file=mail.map shortlog -ns \
+ --group=trailer:signed-off-by HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'shortlog de-duplicates trailers in a single commit' '
+ git commit --allow-empty -F - <<-\EOF &&
+ subject one
+
+ this message has two distinct values, plus a repeat
+
+ Repeated-trailer: Foo
+ Repeated-trailer: Bar
+ Repeated-trailer: Foo
+ EOF
+
+ git commit --allow-empty -F - <<-\EOF &&
+ subject two
+
+ similar to the previous, but without the second distinct value
+
+ Repeated-trailer: Foo
+ Repeated-trailer: Foo
+ EOF
+
+ cat >expect <<-\EOF &&
+ 2 Foo
+ 1 Bar
+ EOF
+ git shortlog -ns --group=trailer:repeated-trailer -2 HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'shortlog can match multiple groups' '
+ git commit --allow-empty -F - <<-\EOF &&
+ subject one
+
+ this has two trailers that are distinct from the author; it will count
+ 3 times in the output
+
+ Some-trailer: User A <a@example.com>
+ Another-trailer: User B <b@example.com>
+ EOF
+
+ git commit --allow-empty -F - <<-\EOF &&
+ subject two
+
+ this one has two trailers, one of which is a duplicate with the author;
+ it will only be counted once for them
+
+ Another-trailer: A U Thor <author@example.com>
+ Some-trailer: User B <b@example.com>
+ EOF
+
+ cat >expect <<-\EOF &&
+ 2 A U Thor
+ 2 User B
+ 1 User A
+ EOF
+ git shortlog -ns \
+ --group=author \
+ --group=trailer:some-trailer \
+ --group=trailer:another-trailer \
+ -2 HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'set up option selection tests' '
+ git commit --allow-empty -F - <<-\EOF
+ subject
+
+ body
+
+ Trailer-one: value-one
+ Trailer-two: value-two
+ EOF
+'
+
+test_expect_success '--no-group resets group list to author' '
+ cat >expect <<-\EOF &&
+ 1 A U Thor
+ EOF
+ git shortlog -ns \
+ --group=committer \
+ --group=trailer:trailer-one \
+ --no-group \
+ -1 HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-group resets trailer list' '
+ cat >expect <<-\EOF &&
+ 1 value-two
+ EOF
+ git shortlog -ns \
+ --group=trailer:trailer-one \
+ --no-group \
+ --group=trailer:trailer-two \
+ -1 HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin with multiple groups reports error' '
+ git log >log &&
+ test_must_fail git shortlog --group=author --group=committer <log
+'
+
test_done
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index b886529e59..aa226381be 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -82,6 +82,13 @@ test_expect_success 'bisect fails if given any junk instead of revs' '
git bisect bad $HASH4
'
+test_expect_success 'bisect start without -- takes unknown arg as pathspec' '
+ git bisect reset &&
+ git bisect start foo bar &&
+ grep foo ".git/BISECT_NAMES" &&
+ grep bar ".git/BISECT_NAMES"
+'
+
test_expect_success 'bisect reset: back in the master branch' '
git bisect reset &&
echo "* master" > branch.expect &&
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh
index 475564bee7..f807276337 100755
--- a/t/t6600-test-reach.sh
+++ b/t/t6600-test-reach.sh
@@ -110,6 +110,36 @@ test_expect_success 'in_merge_bases:miss' '
test_three_modes in_merge_bases
'
+test_expect_success 'in_merge_bases_many:hit' '
+ cat >input <<-\EOF &&
+ A:commit-6-8
+ X:commit-6-9
+ X:commit-5-7
+ EOF
+ echo "in_merge_bases_many(A,X):1" >expect &&
+ test_three_modes in_merge_bases_many
+'
+
+test_expect_success 'in_merge_bases_many:miss' '
+ cat >input <<-\EOF &&
+ A:commit-6-8
+ X:commit-7-7
+ X:commit-8-6
+ EOF
+ echo "in_merge_bases_many(A,X):0" >expect &&
+ test_three_modes in_merge_bases_many
+'
+
+test_expect_success 'in_merge_bases_many:miss-heuristic' '
+ cat >input <<-\EOF &&
+ A:commit-6-8
+ X:commit-7-5
+ X:commit-6-6
+ EOF
+ echo "in_merge_bases_many(A,X):0" >expect &&
+ test_three_modes in_merge_bases_many
+'
+
test_expect_success 'is_descendant_of:hit' '
cat >input <<-\EOF &&
A:commit-5-7
diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh
index 36dc31eb39..24ae5018e8 100755
--- a/t/t8013-blame-ignore-revs.sh
+++ b/t/t8013-blame-ignore-revs.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
test_tick &&
git commit -m X &&
git tag X &&
+ git tag -a -m "X (annotated)" XT &&
git blame --line-porcelain file >blame_raw &&
@@ -31,20 +32,36 @@ test_expect_success setup '
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse X >expect &&
test_cmp expect actual
+'
+
+# Ensure bogus --ignore-rev requests are caught
+test_expect_success 'validate --ignore-rev' '
+ test_must_fail git blame --ignore-rev X^{tree} file
+'
+
+# Ensure bogus --ignore-revs-file requests are caught
+test_expect_success 'validate --ignore-revs-file' '
+ git rev-parse X^{tree} >ignore_x &&
+ test_must_fail git blame --ignore-revs-file ignore_x file
+'
+
+for I in X XT
+do
+ # Ignore X (or XT), make sure A is blamed for line 1 and B for line 2.
+ # Giving X (i.e. commit) and XT (i.e. annotated tag to commit) should
+ # produce the same result.
+ test_expect_success "ignore_rev_changing_lines ($I)" '
+ git blame --line-porcelain --ignore-rev $I file >blame_raw &&
+
+ grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
+ git rev-parse A >expect &&
+ test_cmp expect actual &&
+
+ grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
+ git rev-parse B >expect &&
+ test_cmp expect actual
'
-
-# Ignore X, make sure A is blamed for line 1 and B for line 2.
-test_expect_success ignore_rev_changing_lines '
- git blame --line-porcelain --ignore-rev X file >blame_raw &&
-
- grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
- git rev-parse A >expect &&
- test_cmp expect actual &&
-
- grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
- git rev-parse B >expect &&
- test_cmp expect actual
- '
+done
# For ignored revs that have added 'unblamable' lines, attribute those to the
# ignored commit.
@@ -67,7 +84,7 @@ test_expect_success ignore_rev_adding_unblamable_lines '
grep -E "^[0-9a-f]+ [0-9]+ 4" blame_raw | sed -e "s/ .*//" >actual &&
test_cmp expect actual
- '
+'
# Ignore X and Y, both in separate files. Lines 1 == A, 2 == B.
test_expect_success ignore_revs_from_files '
@@ -82,7 +99,7 @@ test_expect_success ignore_revs_from_files '
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual
- '
+'
# Ignore X from the config option, Y from a file.
test_expect_success ignore_revs_from_configs_and_files '
@@ -96,7 +113,7 @@ test_expect_success ignore_revs_from_configs_and_files '
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual
- '
+'
# Override blame.ignoreRevsFile (ignore_x) with an empty string. X should be
# blamed now for lines 1 and 2, since we are no longer ignoring X.
@@ -120,7 +137,7 @@ test_expect_success bad_files_and_revs '
echo NOREV >ignore_norev &&
test_must_fail git blame file --ignore-revs-file ignore_norev 2>err &&
test_i18ngrep "invalid object name: NOREV" err
- '
+'
# For ignored revs that have added 'unblamable' lines, mark those lines with a
# '*'
@@ -138,7 +155,7 @@ test_expect_success mark_unblamable_lines '
sed -n "4p" blame_raw | cut -c1 >actual &&
test_cmp expect actual
- '
+'
# Commit Z will touch the first two lines. Y touched all four.
# A--B--X--Y--Z
@@ -171,7 +188,7 @@ test_expect_success mark_ignored_lines '
sed -n "4p" blame_raw | cut -c1 >actual &&
! test_cmp expect actual
- '
+'
# For ignored revs that added 'unblamable' lines and more recent commits changed
# the blamable lines, mark the unblamable lines with a
@@ -190,7 +207,7 @@ test_expect_success mark_unblamable_lines_intermediate '
sed -n "4p" blame_raw | cut -c1 >actual &&
test_cmp expect actual
- '
+'
# The heuristic called by guess_line_blames() tries to find the size of a
# blame_entry 'e' in the parent's address space. Those calculations need to
@@ -227,7 +244,7 @@ test_expect_success ignored_chunk_negative_parent_size '
git tag C &&
git blame file --ignore-rev B >blame_raw
- '
+'
# Resetting the repo and creating:
#
@@ -269,6 +286,6 @@ test_expect_success ignore_merge '
grep -E "^[0-9a-f]+ [0-9]+ 9" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse C >expect &&
test_cmp expect actual
- '
+'
test_done
diff --git a/trailer.c b/trailer.c
index 68dabc2556..3f7391d793 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1183,3 +1183,39 @@ void format_trailers_from_commit(struct strbuf *out, const char *msg,
format_trailer_info(out, &info, opts);
trailer_info_release(&info);
}
+
+void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)
+{
+ struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
+ strbuf_init(&iter->key, 0);
+ strbuf_init(&iter->val, 0);
+ opts.no_divider = 1;
+ trailer_info_get(&iter->info, msg, &opts);
+ iter->cur = 0;
+}
+
+int trailer_iterator_advance(struct trailer_iterator *iter)
+{
+ while (iter->cur < iter->info.trailer_nr) {
+ char *trailer = iter->info.trailers[iter->cur++];
+ int separator_pos = find_separator(trailer, separators);
+
+ if (separator_pos < 1)
+ continue; /* not a real trailer */
+
+ strbuf_reset(&iter->key);
+ strbuf_reset(&iter->val);
+ parse_trailer(&iter->key, &iter->val, NULL,
+ trailer, separator_pos);
+ unfold_value(&iter->val);
+ return 1;
+ }
+ return 0;
+}
+
+void trailer_iterator_release(struct trailer_iterator *iter)
+{
+ trailer_info_release(&iter->info);
+ strbuf_release(&iter->val);
+ strbuf_release(&iter->key);
+}
diff --git a/trailer.h b/trailer.h
index 203acf4ee1..cd93e7ddea 100644
--- a/trailer.h
+++ b/trailer.h
@@ -2,8 +2,7 @@
#define TRAILER_H
#include "list.h"
-
-struct strbuf;
+#include "strbuf.h"
enum trailer_where {
WHERE_DEFAULT,
@@ -103,4 +102,46 @@ void trailer_info_release(struct trailer_info *info);
void format_trailers_from_commit(struct strbuf *out, const char *msg,
const struct process_trailer_options *opts);
+/*
+ * An interface for iterating over the trailers found in a particular commit
+ * message. Use like:
+ *
+ * struct trailer_iterator iter;
+ * trailer_iterator_init(&iter, msg);
+ * while (trailer_iterator_advance(&iter))
+ * ... do something with iter.key and iter.val ...
+ * trailer_iterator_release(&iter);
+ */
+struct trailer_iterator {
+ struct strbuf key;
+ struct strbuf val;
+
+ /* private */
+ struct trailer_info info;
+ size_t cur;
+};
+
+/*
+ * Initialize "iter" in preparation for walking over the trailers in the commit
+ * message "msg". The "msg" pointer must remain valid until the iterator is
+ * released.
+ *
+ * After initializing, note that key/val will not yet point to any trailer.
+ * Call advance() to parse the first one (if any).
+ */
+void trailer_iterator_init(struct trailer_iterator *iter, const char *msg);
+
+/*
+ * Advance to the next trailer of the iterator. Returns 0 if there is no such
+ * trailer, and 1 otherwise. The key and value of the trailer can be
+ * fetched from the iter->key and iter->value fields (which are valid
+ * only until the next advance).
+ */
+int trailer_iterator_advance(struct trailer_iterator *iter);
+
+/*
+ * Release all resources associated with the trailer iteration.
+ */
+void trailer_iterator_release(struct trailer_iterator *iter);
+
#endif /* TRAILER_H */