diff options
96 files changed, 1262 insertions, 432 deletions
diff --git a/.gitignore b/.gitignore index 05cb58a3d4..6722f78f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -203,7 +203,6 @@ /config.mak.autogen /config.mak.append /configure -/unicode /tags /TAGS /cscope* diff --git a/.travis.yml b/.travis.yml index 0b2ea5c3e2..3843967a69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,8 @@ env: # The Linux build installs the defined dependency versions below. # The OS X build installs the latest available versions. Keep that # in mind when you encounter a broken OS X build! - - LINUX_P4_VERSION="16.1" - - LINUX_GIT_LFS_VERSION="1.2.0" + - LINUX_P4_VERSION="16.2" + - LINUX_GIT_LFS_VERSION="1.5.2" - DEFAULT_TEST_TARGET=prove - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" - GIT_TEST_OPTS="--verbose-log" diff --git a/Documentation/RelNotes/2.10.3.txt b/Documentation/RelNotes/2.10.3.txt new file mode 100644 index 0000000000..277a2a18a7 --- /dev/null +++ b/Documentation/RelNotes/2.10.3.txt @@ -0,0 +1,48 @@ +Git v2.10.3 Release Notes +========================= + +Fixes since v2.10.2 +------------------- + + * Extract a small helper out of the function that reads the authors + script file "git am" internally uses. + This by itself is not useful until a second caller appears in the + future for "rebase -i" helper. + + * The command-line completion script (in contrib/) learned to + complete "git cmd ^mas<HT>" to complete the negative end of + reference to "git cmd ^master". + + * "git send-email" attempts to pick up valid e-mails from the + trailers, but people in real world write non-addresses there, like + "Cc: Stable <add@re.ss> # 4.8+", which broke the output depending + on the availability and vintage of Mail::Address perl module. + + * The code that we have used for the past 10+ years to cycle + 4-element ring buffers turns out to be not quite portable in + theoretical world. + + * "git daemon" used fixed-length buffers to turn URL to the + repository the client asked for into the server side directory + path, using snprintf() to avoid overflowing these buffers, but + allowed possibly truncated paths to the directory. This has been + tightened to reject such a request that causes overlong path to be + required to serve. + + * Recent update to git-sh-setup (a library of shell functions that + are used by our in-tree scripted Porcelain commands) included + another shell library git-sh-i18n without specifying where it is, + relying on the $PATH. This has been fixed to be more explicit by + prefixing $(git --exec-path) output in front. + + * Fix for a racy false-positive test failure. + + * Portability update and workaround for builds on recent Mac OS X. + + * Update to the test framework made in 2.9 timeframe broke running + the tests under valgrind, which has been fixed. + + * Improve the rule to convert "unsigned char [20]" into "struct + object_id *" in contrib/coccinelle/ + +Also contains minor documentation updates and code clean-ups. diff --git a/Documentation/config.txt b/Documentation/config.txt index a0ab66aae7..d51182a060 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1891,6 +1891,16 @@ http.userAgent:: of common USER_AGENT strings (but not including those like git/1.7.1). Can be overridden by the `GIT_HTTP_USER_AGENT` environment variable. +http.followRedirects:: + Whether git should follow HTTP redirects. If set to `true`, git + will transparently follow any redirect issued by a server it + encounters. If set to `false`, git will treat all redirects as + errors. If set to `initial`, git will follow redirects only for + the initial request to a remote, but not for subsequent + follow-up HTTP requests. Since git uses the redirected URL as + the base for the follow-up requests, this is generally + sufficient. The default is `initial`. + http.<url>.*:: Any of the http.* options above can be applied selectively to some URLs. For a config key to match a URL, each element of the config key is diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index f2ab0ee2e7..4f8f20a360 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -265,7 +265,8 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) If this option is specified together with `--amend`, then no paths need to be specified, which can be used to amend the last commit without committing changes that have - already been staged. + already been staged. If used together with `--allow-empty` + paths are also not required, and an empty commit will be created. -u[<mode>]:: --untracked-files[=<mode>]:: diff --git a/Documentation/git.txt b/Documentation/git.txt index af191c51b1..98033302bb 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -871,6 +871,12 @@ Git so take care if using a foreign front-end. specifies a ":" separated (on Windows ";" separated) list of Git object directories which can be used to search for Git objects. New objects will not be written to these directories. ++ + Entries that begin with `"` (double-quote) will be interpreted + as C-style quoted paths, removing leading and trailing + double-quotes and respecting backslash escapes. E.g., the value + `"path-with-\"-and-:-in-it":vanilla-path` has two paths: + `path-with-"-and-:-in-it` and `vanilla-path`. `GIT_DIR`:: If the `GIT_DIR` environment variable is set then it @@ -348,7 +348,7 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) int replace_each_worktree_head_symref(const char *oldref, const char *newref) { int ret = 0; - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(0); int i; for (i = 0; worktrees[i]; i++) { diff --git a/builtin/am.c b/builtin/am.c index 6981f42ce9..826f18ba12 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2124,7 +2124,7 @@ static int safe_to_abort(const struct am_state *state) if (read_state_file(&sb, state, "abort-safety", 1) > 0) { if (get_oid_hex(sb.buf, &abort_safety)) - die(_("could not parse %s"), am_path(state, "abort_safety")); + die(_("could not parse %s"), am_path(state, "abort-safety")); } else oidclr(&abort_safety); @@ -2134,7 +2134,7 @@ static int safe_to_abort(const struct am_state *state) if (!oidcmp(&head, &abort_safety)) return 1; - error(_("You seem to have moved HEAD since the last 'am' failure.\n" + warning(_("You seem to have moved HEAD since the last 'am' failure.\n" "Not rewinding to ORIG_HEAD")); return 0; diff --git a/builtin/branch.c b/builtin/branch.c index 60cc5c8e8d..475707528a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -531,7 +531,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin static void reject_rebase_or_bisect_branch(const char *target) { - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(0); int i; for (i = 0; worktrees[i]; i++) { diff --git a/builtin/commit.c b/builtin/commit.c index 8976c3d29b..276c74034e 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1206,10 +1206,8 @@ static int parse_and_validate_options(int argc, const char *argv[], if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - if (argc == 0 && (also || (only && !amend))) + if (argc == 0 && (also || (only && !amend && !allow_empty))) die(_("No paths with --include/--only does not make sense.")); - if (argc == 0 && only && amend) - only_include_assumed = _("Clever... amending the last one with dirty index."); if (argc > 0 && !also && !only) only_include_assumed = _("Explicit paths specified without -i or -o; assuming --only paths..."); if (!cleanup_arg || !strcmp(cleanup_arg, "default")) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 0a27bab11b..f4b87c6c9f 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -787,13 +787,15 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, const unsigned char *sha1) { void *new_data = NULL; - int collision_test_needed; + int collision_test_needed = 0; assert(data || obj_entry); - read_lock(); - collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK); - read_unlock(); + if (startup_info->have_repository) { + read_lock(); + collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK); + read_unlock(); + } if (collision_test_needed && !data) { read_lock(); @@ -1730,6 +1732,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) usage(index_pack_usage); if (fix_thin_pack && !from_stdin) die(_("--fix-thin cannot be used without --stdin")); + if (from_stdin && !startup_info->have_repository) + die(_("--stdin requires a git repository")); if (!index_name && pack_name) index_name = derive_filename(pack_name, ".idx", &index_name_buf); if (keep_msg && !keep_name && pack_name) diff --git a/builtin/pull.c b/builtin/pull.c index d6e46ee6d0..3ecb881b0b 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -857,10 +857,24 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (merge_heads.nr > 1) die(_("Cannot merge multiple branches into empty head.")); return pull_into_void(*merge_heads.sha1, curr_head); - } else if (opt_rebase) { - if (merge_heads.nr > 1) - die(_("Cannot rebase onto multiple branches.")); + } + if (opt_rebase && merge_heads.nr > 1) + die(_("Cannot rebase onto multiple branches.")); + + if (opt_rebase) { + struct commit_list *list = NULL; + struct commit *merge_head, *head; + + head = lookup_commit_reference(orig_head); + commit_list_insert(head, &list); + merge_head = lookup_commit_reference(merge_heads.sha1[0]); + if (is_descendant_of(merge_head, list)) { + /* we can fast-forward this without invoking rebase */ + opt_ff = "--ff-only"; + return run_merge(); + } return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point); - } else + } else { return run_merge(); + } } diff --git a/builtin/push.c b/builtin/push.c index 3bb9d6b7e6..9307ad56a9 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -194,15 +194,18 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch, die_push_simple(branch, remote); } - strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); + strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src); add_refspec(refspec.buf); } static void setup_push_current(struct remote *remote, struct branch *branch) { + struct strbuf refspec = STRBUF_INIT; + if (!branch) die(_(message_detached_head_die), remote->name); - add_refspec(branch->name); + strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname); + add_refspec(refspec.buf); } static int is_workflow_triangular(struct remote *remote) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index cfb0f1510c..ff13e59e1d 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -342,11 +342,16 @@ static int try_parent_shorthands(const char *arg) for (parents = commit->parents, parent_number = 1; parents; parents = parents->next, parent_number++) { + char *name = NULL; + if (exclude_parent && parent_number != exclude_parent) continue; + if (symbolic) + name = xstrfmt("%s^%d", arg, parent_number); show_rev(include_parents ? NORMAL : REVERSED, - parents->item->object.oid.hash, arg); + parents->item->object.oid.hash, name); + free(name); } *dotdot = '^'; diff --git a/builtin/worktree.c b/builtin/worktree.c index 5c4854d3e4..9a97e37a3f 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -388,7 +388,7 @@ static void show_worktree_porcelain(struct worktree *wt) printf("HEAD %s\n", sha1_to_hex(wt->head_sha1)); if (wt->is_detached) printf("detached\n"); - else + else if (wt->head_ref) printf("branch %s\n", wt->head_ref); } printf("\n"); @@ -406,10 +406,12 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) else { strbuf_addf(&sb, "%-*s ", abbrev_len, find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV)); - if (!wt->is_detached) + if (wt->is_detached) + strbuf_addstr(&sb, "(detached HEAD)"); + else if (wt->head_ref) strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0)); else - strbuf_addstr(&sb, "(detached HEAD)"); + strbuf_addstr(&sb, "(error)"); } printf("%s\n", sb.buf); @@ -445,7 +447,7 @@ static int list(int ac, const char **av, const char *prefix) if (ac) usage_with_options(worktree_usage, options); else { - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(GWT_SORT_LINKED); int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; if (!porcelain) @@ -476,7 +478,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(); + worktrees = get_worktrees(0); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -509,7 +511,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(); + worktrees = get_worktrees(0); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); diff --git a/compat/mingw.h b/compat/mingw.h index 034fff9479..3350169555 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -384,6 +384,9 @@ int mingw_raise(int sig); * ANSI emulation wrappers */ +int winansi_isatty(int fd); +#define isatty winansi_isatty + void winansi_init(void); HANDLE winansi_get_osfhandle(int fd); diff --git a/compat/winansi.c b/compat/winansi.c index db4a5b0a37..477209fce7 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -6,6 +6,12 @@ #include "../git-compat-util.h" #include <wingdi.h> #include <winreg.h> +#include "win32.h" + +static int fd_is_interactive[3] = { 0, 0, 0 }; +#define FD_CONSOLE 0x1 +#define FD_SWAPPED 0x2 +#define FD_MSYS 0x4 /* ANSI codes used by git: m, K @@ -81,6 +87,7 @@ static void warn_if_raster_font(void) static int is_console(int fd) { CONSOLE_SCREEN_BUFFER_INFO sbi; + DWORD mode; HANDLE hcon; static int initialized = 0; @@ -95,9 +102,15 @@ static int is_console(int fd) return 0; /* check if its a handle to a console output screen buffer */ - if (!GetConsoleScreenBufferInfo(hcon, &sbi)) + if (!fd) { + if (!GetConsoleMode(hcon, &mode)) + return 0; + } else if (!GetConsoleScreenBufferInfo(hcon, &sbi)) return 0; + if (fd >= 0 && fd <= 2) + fd_is_interactive[fd] |= FD_CONSOLE; + /* initialize attributes */ if (!initialized) { console = hcon; @@ -459,76 +472,50 @@ static HANDLE duplicate_handle(HANDLE hnd) return hresult; } - -/* - * Make MSVCRT's internal file descriptor control structure accessible - * so that we can tweak OS handles and flags directly (we need MSVCRT - * to treat our pipe handle as if it were a console). - * - * We assume that the ioinfo structure (exposed by MSVCRT.dll via - * __pioinfo) starts with the OS handle and the flags. The exact size - * varies between MSVCRT versions, so we try different sizes until - * toggling the FDEV bit of _pioinfo(1)->osflags is reflected in - * isatty(1). - */ -typedef struct { - HANDLE osfhnd; - char osflags; -} ioinfo; - -extern __declspec(dllimport) ioinfo *__pioinfo[]; - -static size_t sizeof_ioinfo = 0; - -#define IOINFO_L2E 5 -#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E) - -#define FPIPE 0x08 -#define FDEV 0x40 - -static inline ioinfo* _pioinfo(int fd) -{ - return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] + - (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo); -} - -static int init_sizeof_ioinfo(void) -{ - int istty, wastty; - /* don't init twice */ - if (sizeof_ioinfo) - return sizeof_ioinfo >= 256; - - sizeof_ioinfo = sizeof(ioinfo); - wastty = isatty(1); - while (sizeof_ioinfo < 256) { - /* toggle FDEV flag, check isatty, then toggle back */ - _pioinfo(1)->osflags ^= FDEV; - istty = isatty(1); - _pioinfo(1)->osflags ^= FDEV; - /* return if we found the correct size */ - if (istty != wastty) - return 0; - sizeof_ioinfo += sizeof(void*); - } - error("Tweaking file descriptors doesn't work with this MSVCRT.dll"); - return 1; -} - static HANDLE swap_osfhnd(int fd, HANDLE new_handle) { - ioinfo *pioinfo; - HANDLE old_handle; - - /* init ioinfo size if we haven't done so */ - if (init_sizeof_ioinfo()) - return INVALID_HANDLE_VALUE; - - /* get ioinfo pointer and change the handles */ - pioinfo = _pioinfo(fd); - old_handle = pioinfo->osfhnd; - pioinfo->osfhnd = new_handle; - return old_handle; + /* + * Create a copy of the original handle associated with fd + * because the original will get closed when we dup2(). + */ + HANDLE handle = (HANDLE)_get_osfhandle(fd); + HANDLE duplicate = duplicate_handle(handle); + + /* Create a temp fd associated with the already open "new_handle". */ + int new_fd = _open_osfhandle((intptr_t)new_handle, O_BINARY); + + assert((fd == 1) || (fd == 2)); + + /* + * Use stock dup2() to re-bind fd to the new handle. Note that + * this will implicitly close(1) and close both fd=1 and the + * originally associated handle. It will open a new fd=1 and + * call DuplicateHandle() on the handle associated with new_fd. + * It is because of this implicit close() that we created the + * copy of the original. + * + * Note that the OS can recycle HANDLE (numbers) just like it + * recycles fd (numbers), so we must update the cached value + * of "console". You can use GetFileType() to see that + * handle and _get_osfhandle(fd) may have the same number + * value, but they refer to different actual files now. + * + * Note that dup2() when given target := {0,1,2} will also + * call SetStdHandle(), so we don't need to worry about that. + */ + dup2(new_fd, fd); + if (console == handle) + console = duplicate; + handle = INVALID_HANDLE_VALUE; + + /* Close the temp fd. This explicitly closes "new_handle" + * (because it has been associated with it). + */ + close(new_fd); + + fd_is_interactive[fd] |= FD_SWAPPED; + + return duplicate; } #ifdef DETECT_MSYS_TTY @@ -555,21 +542,35 @@ static void detect_msys_tty(int fd) name = nameinfo->Name.Buffer; name[nameinfo->Name.Length] = 0; - /* check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX') */ - if (!wcsstr(name, L"msys-") || !wcsstr(name, L"-pty")) + /* + * Check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX') + * or a cygwin pty pipe ('cygwin-XXXX-ptyN-XX') + */ + if ((!wcsstr(name, L"msys-") && !wcsstr(name, L"cygwin-")) || + !wcsstr(name, L"-pty")) return; - /* init ioinfo size if we haven't done so */ - if (init_sizeof_ioinfo()) - return; - - /* set FDEV flag, reset FPIPE flag */ - _pioinfo(fd)->osflags &= ~FPIPE; - _pioinfo(fd)->osflags |= FDEV; + fd_is_interactive[fd] |= FD_MSYS; } #endif +/* + * Wrapper for isatty(). Most calls in the main git code + * call isatty(1 or 2) to see if the instance is interactive + * and should: be colored, show progress, paginate output. + * We lie and give results for what the descriptor WAS at + * startup (and ignore any pipe redirection we internally + * do). + */ +#undef isatty +int winansi_isatty(int fd) +{ + if (fd >= 0 && fd <= 2) + return fd_is_interactive[fd] != 0; + return isatty(fd); +} + void winansi_init(void) { int con1, con2; @@ -578,6 +579,10 @@ void winansi_init(void) /* check if either stdout or stderr is a console output screen buffer */ con1 = is_console(1); con2 = is_console(2); + + /* Also compute console bit for fd 0 even though we don't need the result here. */ + is_console(0); + if (!con1 && !con2) { #ifdef DETECT_MSYS_TTY /* check if stdin / stdout / stderr are MSYS2 pty pipes */ @@ -621,12 +626,10 @@ void winansi_init(void) */ HANDLE winansi_get_osfhandle(int fd) { - HANDLE hnd = (HANDLE) _get_osfhandle(fd); - if (isatty(fd) && GetFileType(hnd) == FILE_TYPE_PIPE) { - if (fd == 1 && hconsole1) - return hconsole1; - else if (fd == 2 && hconsole2) - return hconsole2; - } - return hnd; + if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED)) + return hconsole1; + if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED)) + return hconsole2; + + return (HANDLE)_get_osfhandle(fd); } diff --git a/contrib/update-unicode/.gitignore b/contrib/update-unicode/.gitignore new file mode 100644 index 0000000000..b0ebc6aad2 --- /dev/null +++ b/contrib/update-unicode/.gitignore @@ -0,0 +1,3 @@ +uniset/ +UnicodeData.txt +EastAsianWidth.txt diff --git a/contrib/update-unicode/README b/contrib/update-unicode/README new file mode 100644 index 0000000000..b9e2fc8540 --- /dev/null +++ b/contrib/update-unicode/README @@ -0,0 +1,20 @@ +TL;DR: Run update_unicode.sh after the publication of a new Unicode +standard and commit the resulting unicode_widths.h file. + +The long version +================ + +The Git source code ships the file unicode_widths.h which contains +tables of zero and double width Unicode code points, respectively. +These tables are generated using update_unicode.sh in this directory. +update_unicode.sh itself uses a third-party tool, uniset, to query two +Unicode data files for the interesting code points. + +On first run, update_unicode.sh clones uniset from Github and builds it. +This requires a current-ish version of autoconf (2.69 works per December +2016). + +On each run, update_unicode.sh checks whether more recent Unicode data +files are available from the Unicode consortium, and rebuilds the header +unicode_widths.h with the new data. The new header can then be +committed. diff --git a/contrib/update-unicode/update_unicode.sh b/contrib/update-unicode/update_unicode.sh new file mode 100755 index 0000000000..e05db92d3f --- /dev/null +++ b/contrib/update-unicode/update_unicode.sh @@ -0,0 +1,33 @@ +#!/bin/sh +#See http://www.unicode.org/reports/tr44/ +# +#Me Enclosing_Mark an enclosing combining mark +#Mn Nonspacing_Mark a nonspacing combining mark (zero advance width) +#Cf Format a format control character +# +cd "$(dirname "$0")" +UNICODEWIDTH_H=$(git rev-parse --show-toplevel)/unicode_width.h + +wget -N http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt \ + http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt && +if ! test -d uniset; then + git clone https://github.com/depp/uniset.git && + ( cd uniset && git checkout 4b186196dd ) +fi && +( + cd uniset && + if ! test -x uniset; then + autoreconf -i && + ./configure --enable-warnings=-Werror CFLAGS='-O0 -ggdb' + fi && + make +) && +UNICODE_DIR=. && export UNICODE_DIR && +cat >$UNICODEWIDTH_H <<-EOF +static const struct interval zero_width[] = { + $(uniset/uniset --32 cat:Me,Mn,Cf + U+1160..U+11FF - U+00AD) +}; +static const struct interval double_width[] = { + $(uniset/uniset --32 eaw:F,W) +}; +EOF @@ -279,15 +279,16 @@ static int crlf_to_git(const char *path, const char *src, size_t len, if (convert_is_binary(len, &stats)) return 0; /* - * If the file in the index has any CR in it, do not convert. - * This is the new safer autocrlf handling. + * If the file in the index has any CR in it, do not + * convert. This is the new safer autocrlf handling, + * unless we want to renormalize in a merge or + * cherry-pick. */ - if (checksafe == SAFE_CRLF_RENORMALIZE) - checksafe = SAFE_CRLF_FALSE; - else if (has_cr_in_index(path)) + if ((checksafe != SAFE_CRLF_RENORMALIZE) && has_cr_in_index(path)) convert_crlf_into_lf = 0; } - if (checksafe && len) { + if ((checksafe == SAFE_CRLF_WARN || + (checksafe == SAFE_CRLF_FAIL)) && len) { struct text_stat new_stats; memcpy(&new_stats, &stats, sizeof(new_stats)); /* simulate "git add" */ @@ -3106,7 +3106,8 @@ static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev) abbrev = FALLBACK_DEFAULT_ABBREV; if (abbrev > GIT_SHA1_HEXSZ) die("BUG: oid abbreviation out of range: %d", abbrev); - hex[abbrev] = '\0'; + if (abbrev) + hex[abbrev] = '\0'; return hex; } } @@ -3364,6 +3365,7 @@ void diff_setup(struct diff_options *options) options->file = stdout; + options->abbrev = DEFAULT_ABBREV; options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; @@ -4024,6 +4026,8 @@ int diff_opt_parse(struct diff_options *options, offending, optarg); return argcount; } + else if (!strcmp(arg, "--no-abbrev")) + options->abbrev = 0; else if (!strcmp(arg, "--abbrev")) options->abbrev = DEFAULT_ABBREV; else if (skip_prefix(arg, "--abbrev=", &arg)) { diff --git a/git-difftool.perl b/git-difftool.perl index a5790d03a0..959822d5f3 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -182,6 +182,10 @@ EOF } } + # Go to the root of the worktree so that the left index files + # are properly setup -- the index is toplevel-relative. + chdir($workdir); + # Setup temp directories my $tmpdir = tempdir('git-difftool.XXXXX', CLEANUP => 0, TMPDIR => 1); my $ldir = "$tmpdir/left"; diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 9abd00be21..9a8b97a2ab 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -125,16 +125,7 @@ setup_user_tool () { } merge_cmd () { - trust_exit_code=$(git config --bool \ - "mergetool.$1.trustExitCode" || echo false) - if test "$trust_exit_code" = "false" - then - touch "$BACKUP" - ( eval $merge_tool_cmd ) - check_unchanged - else - ( eval $merge_tool_cmd ) - fi + ( eval $merge_tool_cmd ) } } @@ -162,6 +153,28 @@ setup_tool () { echo "$1" } + # Most tools' exit codes cannot be trusted, so By default we ignore + # their exit code and check the merged file's modification time in + # check_unchanged() to determine whether or not the merge was + # successful. The return value from run_merge_cmd, by default, is + # determined by check_unchanged(). + # + # When a tool's exit code can be trusted then the return value from + # run_merge_cmd is simply the tool's exit code, and check_unchanged() + # is not called. + # + # The return value of exit_code_trustable() tells us whether or not we + # can trust the tool's exit code. + # + # User-defined and built-in tools default to false. + # Built-in tools advertise that their exit code is trustable by + # redefining exit_code_trustable() to true. + + exit_code_trustable () { + false + } + + if ! test -f "$MERGE_TOOLS_DIR/$tool" then setup_user_tool @@ -197,6 +210,19 @@ get_merge_tool_cmd () { fi } +trust_exit_code () { + if git config --bool "mergetool.$1.trustExitCode" + then + :; # OK + elif exit_code_trustable + then + echo true + else + echo false + fi +} + + # Entry point for running tools run_merge_tool () { # If GIT_PREFIX is empty then we cannot use it in tools @@ -225,7 +251,15 @@ run_diff_cmd () { # Run a either a configured or built-in merge tool run_merge_cmd () { - merge_cmd "$1" + mergetool_trust_exit_code=$(trust_exit_code "$1") + if test "$mergetool_trust_exit_code" = "true" + then + merge_cmd "$1" + else + touch "$BACKUP" + merge_cmd "$1" + check_unchanged + fi } list_merge_tool_candidates () { @@ -1006,18 +1006,20 @@ class LargeFileSystem(object): steps.""" if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath): contentTempFile = self.generateTempFile(contents) - (git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile) - - # Move temp file to final location in large file system - largeFileDir = os.path.dirname(localLargeFile) - if not os.path.isdir(largeFileDir): - os.makedirs(largeFileDir) - shutil.move(contentTempFile, localLargeFile) - self.addLargeFile(relPath) - if gitConfigBool('git-p4.largeFilePush'): - self.pushFile(localLargeFile) - if verbose: - sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile)) + (pointer_git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile) + if pointer_git_mode: + git_mode = pointer_git_mode + if localLargeFile: + # Move temp file to final location in large file system + largeFileDir = os.path.dirname(localLargeFile) + if not os.path.isdir(largeFileDir): + os.makedirs(largeFileDir) + shutil.move(contentTempFile, localLargeFile) + self.addLargeFile(relPath) + if gitConfigBool('git-p4.largeFilePush'): + self.pushFile(localLargeFile) + if verbose: + sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile)) return (git_mode, contents) class MockLFS(LargeFileSystem): @@ -1057,6 +1059,9 @@ class GitLFS(LargeFileSystem): the actual content. Return also the new location of the actual content. """ + if os.path.getsize(contentFile) == 0: + return (None, '', None) + pointerProcess = subprocess.Popen( ['git', 'lfs', 'pointer', '--file=' + contentFile], stdout=subprocess.PIPE diff --git a/git-stash.sh b/git-stash.sh index 4546abaaef..10c284d1aa 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -115,7 +115,7 @@ create_stash () { git read-tree --index-output="$TMPindex" -m $i_tree && GIT_INDEX_FILE="$TMPindex" && export GIT_INDEX_FILE && - git diff --name-only -z HEAD -- >"$TMP-stagenames" && + git diff-index --name-only -z HEAD -- >"$TMP-stagenames" && git update-index -z --add --remove --stdin <"$TMP-stagenames" && git write-tree && rm -f "$TMPindex" diff --git a/http-walker.c b/http-walker.c index 0b2425531a..c2f81cd6af 100644 --- a/http-walker.c +++ b/http-walker.c @@ -274,9 +274,8 @@ static void process_alternates_response(void *callback_data) struct strbuf target = STRBUF_INIT; strbuf_add(&target, base, serverlen); strbuf_add(&target, data + i, posn - i - 7); - if (walker->get_verbosely) - fprintf(stderr, "Also look at %s\n", - target.buf); + warning("adding alternate object store: %s", + target.buf); newalt = xmalloc(sizeof(*newalt)); newalt->next = NULL; newalt->base = strbuf_detach(&target, NULL); @@ -302,6 +301,9 @@ static void fetch_alternates(struct walker *walker, const char *base) struct alternates_request alt_req; struct walker_data *cdata = walker->data; + if (http_follow_config != HTTP_FOLLOW_ALWAYS) + return; + /* * If another request has already started fetching alternates, * wait for them to arrive and return to processing this request's @@ -480,10 +482,13 @@ static int fetch_object(struct walker *walker, unsigned char *sha1) * we turned off CURLOPT_FAILONERROR to avoid losing a * persistent connection and got CURLE_OK. */ - if (req->http_code == 404 && req->curl_result == CURLE_OK && + if (req->http_code >= 300 && req->curl_result == CURLE_OK && (starts_with(req->url, "http://") || - starts_with(req->url, "https://"))) + starts_with(req->url, "https://"))) { req->curl_result = CURLE_HTTP_RETURNED_ERROR; + xsnprintf(req->errorstr, sizeof(req->errorstr), + "HTTP request failed"); + } if (obj_req->state == ABORTED) { ret = error("Request for %s aborted", hex); @@ -111,6 +111,8 @@ static int http_proactive_auth; static const char *user_agent; static int curl_empty_auth; +enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL; + #if LIBCURL_VERSION_NUM >= 0x071700 /* Use CURLOPT_KEYPASSWD as is */ #elif LIBCURL_VERSION_NUM >= 0x070903 @@ -366,6 +368,16 @@ static int http_options(const char *var, const char *value, void *cb) return 0; } + if (!strcmp("http.followredirects", var)) { + if (value && !strcmp(value, "initial")) + http_follow_config = HTTP_FOLLOW_INITIAL; + else if (git_config_bool(var, value)) + http_follow_config = HTTP_FOLLOW_ALWAYS; + else + http_follow_config = HTTP_FOLLOW_NONE; + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, cb); } @@ -717,7 +729,6 @@ static CURL *get_curl_handle(void) curl_low_speed_time); } - curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20); #if LIBCURL_VERSION_NUM >= 0x071301 curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); @@ -734,6 +745,7 @@ static CURL *get_curl_handle(void) if (is_transport_allowed("ftps")) allowed_protocols |= CURLPROTO_FTPS; curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols); + curl_easy_setopt(result, CURLOPT_PROTOCOLS, allowed_protocols); #else if (transport_restrict_protocols()) warning("protocol restrictions not applied to curl redirects because\n" @@ -1044,6 +1056,16 @@ struct active_request_slot *get_active_slot(void) curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(slot->curl, CURLOPT_RANGE, NULL); + /* + * Default following to off unless "ALWAYS" is configured; this gives + * callers a sane starting point, and they can tweak for individual + * HTTP_FOLLOW_* cases themselves. + */ + if (http_follow_config == HTTP_FOLLOW_ALWAYS) + curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 1); + else + curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 0); + #if LIBCURL_VERSION_NUM >= 0x070a08 curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve); #endif @@ -1286,9 +1308,12 @@ static int handle_curl_result(struct slot_results *results) * If we see a failing http code with CURLE_OK, we have turned off * FAILONERROR (to keep the server's custom error response), and should * translate the code into failure here. + * + * Likewise, if we see a redirect (30x code), that means we turned off + * redirect-following, and we should treat the result as an error. */ if (results->curl_result == CURLE_OK && - results->http_code >= 400) { + results->http_code >= 300) { results->curl_result = CURLE_HTTP_RETURNED_ERROR; /* * Normally curl will already have put the "reason phrase" @@ -1607,6 +1632,9 @@ static int http_request(const char *url, strbuf_addstr(&buf, " no-cache"); if (options && options->keep_error) curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0); + if (options && options->initial_request && + http_follow_config == HTTP_FOLLOW_INITIAL) + curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 1); headers = curl_slist_append(headers, buf.buf); @@ -1655,16 +1683,16 @@ static int http_request(const char *url, * * Note that this assumes a sane redirect scheme. It's entirely possible * in the example above to end up at a URL that does not even end in - * "info/refs". In such a case we simply punt, as there is not much we can - * do (and such a scheme is unlikely to represent a real git repository, - * which means we are likely about to abort anyway). + * "info/refs". In such a case we die. There's not much we can do, such a + * scheme is unlikely to represent a real git repository, and failing to + * rewrite the base opens options for malicious redirects to do funny things. */ static int update_url_from_redirect(struct strbuf *base, const char *asked, const struct strbuf *got) { const char *tail; - size_t tail_len; + size_t new_len; if (!strcmp(asked, got->buf)) return 0; @@ -1673,14 +1701,16 @@ static int update_url_from_redirect(struct strbuf *base, die("BUG: update_url_from_redirect: %s is not a superset of %s", asked, base->buf); - tail_len = strlen(tail); - - if (got->len < tail_len || - strcmp(tail, got->buf + got->len - tail_len)) - return 0; /* insane redirect scheme */ + new_len = got->len; + if (!strip_suffix_mem(got->buf, &new_len, tail)) + die(_("unable to update url base from redirection:\n" + " asked for: %s\n" + " redirect: %s"), + asked, got->buf); strbuf_reset(base); - strbuf_add(base, got->buf, got->len - tail_len); + strbuf_add(base, got->buf, new_len); + return 1; } @@ -2028,7 +2058,7 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb, if (c != CURLE_OK) die("BUG: curl_easy_getinfo for HTTP code failed: %s", curl_easy_strerror(c)); - if (slot->http_code >= 400) + if (slot->http_code >= 300) return size; } @@ -116,6 +116,13 @@ extern struct credential http_auth; extern char curl_errorstr[CURL_ERROR_SIZE]; +enum http_follow_config { + HTTP_FOLLOW_NONE, + HTTP_FOLLOW_ALWAYS, + HTTP_FOLLOW_INITIAL +}; +extern enum http_follow_config http_follow_config; + static inline int missing__target(int code, int result) { return /* file:// URL -- do we ever use one??? */ @@ -139,7 +146,8 @@ extern char *get_remote_object_url(const char *url, const char *hex, /* Options for http_get_*() */ struct http_get_options { unsigned no_cache:1, - keep_error:1; + keep_error:1, + initial_request:1; /* If non-NULL, returns the content-type of the response. */ struct strbuf *content_type; diff --git a/mailinfo.c b/mailinfo.c index 2fb3877ee4..a489d9d0fb 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -710,7 +710,8 @@ static void flush_inbody_header_accum(struct mailinfo *mi) { if (!mi->inbody_header_accum.len) return; - assert(check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0)); + if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0)) + die("BUG: inbody_header_accum, if not empty, must always contain a valid in-body header"); strbuf_reset(&mi->inbody_header_accum); } diff --git a/merge-recursive.c b/merge-recursive.c index 9041c2f149..214f5a693b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -235,6 +235,8 @@ static int add_cacheinfo(struct merge_options *o, struct cache_entry *nce; nce = refresh_cache_entry(ce, CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING); + if (!nce) + return err(o, _("addinfo_cache failed for path '%s'"), path); if (nce != ce) ret = add_cache_entry(nce, options); } @@ -664,7 +666,13 @@ static char *unique_path(struct merge_options *o, const char *path, const char * return strbuf_detach(&newpath, NULL); } -static int dir_in_way(const char *path, int check_working_copy) +/** + * Check whether a directory in the index is in the way of an incoming + * file. Return 1 if so. If check_working_copy is non-zero, also + * check the working directory. If empty_ok is non-zero, also return + * 0 in the case where the working-tree dir exists but is empty. + */ +static int dir_in_way(const char *path, int check_working_copy, int empty_ok) { int pos; struct strbuf dirpath = STRBUF_INIT; @@ -684,7 +692,8 @@ static int dir_in_way(const char *path, int check_working_copy) } strbuf_release(&dirpath); - return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode); + return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode) && + !(empty_ok && is_empty_dir(path)); } static int was_tracked(const char *path) @@ -1062,7 +1071,7 @@ static int handle_change_delete(struct merge_options *o, { char *renamed = NULL; int ret = 0; - if (dir_in_way(path, !o->call_depth)) { + if (dir_in_way(path, !o->call_depth, 0)) { renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2); } @@ -1195,7 +1204,7 @@ static int handle_file(struct merge_options *o, remove_file(o, 0, rename->path, 0); dst_name = unique_path(o, rename->path, cur_branch); } else { - if (dir_in_way(rename->path, !o->call_depth)) { + if (dir_in_way(rename->path, !o->call_depth, 0)) { dst_name = unique_path(o, rename->path, cur_branch); output(o, 1, _("%s is a directory in %s adding as %s instead"), rename->path, other_branch, dst_name); @@ -1704,7 +1713,8 @@ static int merge_content(struct merge_options *o, o->branch2 == rename_conflict_info->branch1) ? pair1->two->path : pair1->one->path; - if (dir_in_way(path, !o->call_depth)) + if (dir_in_way(path, !o->call_depth, + S_ISGITLINK(pair1->two->mode))) df_conflict_remains = 1; } if (merge_file_special_markers(o, &one, &a, &b, @@ -1862,7 +1872,8 @@ static int process_entry(struct merge_options *o, oid = b_oid; conf = _("directory/file"); } - if (dir_in_way(path, !o->call_depth)) { + if (dir_in_way(path, !o->call_depth, + S_ISGITLINK(a_mode))) { char *new_path = unique_path(o, path, add_branch); clean_merge = 0; output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. " diff --git a/mergetools/araxis b/mergetools/araxis index 64f97c5e97..e2407b65b7 100644 --- a/mergetools/araxis +++ b/mergetools/araxis @@ -3,7 +3,6 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" -wait -merge -3 -a1 \ @@ -12,7 +11,6 @@ merge_cmd () { "$merge_tool_path" -wait -2 \ "$LOCAL" "$REMOTE" "$MERGED" >/dev/null 2>&1 fi - check_unchanged } translate_merge_tool_path() { diff --git a/mergetools/bc b/mergetools/bc index b6319d206e..3a69e60faa 100644 --- a/mergetools/bc +++ b/mergetools/bc @@ -3,7 +3,6 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \ @@ -12,7 +11,6 @@ merge_cmd () { "$merge_tool_path" "$LOCAL" "$REMOTE" \ -mergeoutput="$MERGED" fi - check_unchanged } translate_merge_tool_path() { diff --git a/mergetools/codecompare b/mergetools/codecompare index 3f0486bc80..9f60e8da65 100644 --- a/mergetools/codecompare +++ b/mergetools/codecompare @@ -3,7 +3,6 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" -MF="$LOCAL" -TF="$REMOTE" -BF="$BASE" \ @@ -12,7 +11,6 @@ merge_cmd () { "$merge_tool_path" -MF="$LOCAL" -TF="$REMOTE" \ -RF="$MERGED" fi - check_unchanged } translate_merge_tool_path() { diff --git a/mergetools/deltawalker b/mergetools/deltawalker index b3c71b6236..ee6f374bce 100644 --- a/mergetools/deltawalker +++ b/mergetools/deltawalker @@ -16,6 +16,10 @@ merge_cmd () { fi >/dev/null 2>&1 } -translate_merge_tool_path() { +translate_merge_tool_path () { echo DeltaWalker } + +exit_code_trustable () { + true +} diff --git a/mergetools/diffmerge b/mergetools/diffmerge index f138cb4e73..9b6355b98a 100644 --- a/mergetools/diffmerge +++ b/mergetools/diffmerge @@ -12,3 +12,7 @@ merge_cmd () { --result="$MERGED" "$LOCAL" "$REMOTE" fi } + +exit_code_trustable () { + true +} diff --git a/mergetools/diffuse b/mergetools/diffuse index 02e0843f47..5a3ae8b569 100644 --- a/mergetools/diffuse +++ b/mergetools/diffuse @@ -3,7 +3,6 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" \ @@ -13,5 +12,4 @@ merge_cmd () { "$merge_tool_path" \ "$LOCAL" "$MERGED" "$REMOTE" | cat fi - check_unchanged } diff --git a/mergetools/ecmerge b/mergetools/ecmerge index 13c2e439de..6c5101c4f7 100644 --- a/mergetools/ecmerge +++ b/mergetools/ecmerge @@ -3,7 +3,6 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \ @@ -12,5 +11,4 @@ merge_cmd () { "$merge_tool_path" "$LOCAL" "$REMOTE" \ --default --mode=merge2 --to="$MERGED" fi - check_unchanged } diff --git a/mergetools/emerge b/mergetools/emerge index 7b895fdb1f..d1ce513ff5 100644 --- a/mergetools/emerge +++ b/mergetools/emerge @@ -20,3 +20,7 @@ merge_cmd () { translate_merge_tool_path() { echo emacs } + +exit_code_trustable () { + true +} diff --git a/mergetools/examdiff b/mergetools/examdiff index 7b524d4088..e72b06fc4d 100644 --- a/mergetools/examdiff +++ b/mergetools/examdiff @@ -3,14 +3,12 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" -merge "$LOCAL" "$BASE" "$REMOTE" -o:"$MERGED" -nh else "$merge_tool_path" -merge "$LOCAL" "$REMOTE" -o:"$MERGED" -nh fi - check_unchanged } translate_merge_tool_path() { diff --git a/mergetools/kdiff3 b/mergetools/kdiff3 index 793d1293b1..0264ed5b20 100644 --- a/mergetools/kdiff3 +++ b/mergetools/kdiff3 @@ -21,3 +21,7 @@ merge_cmd () { >/dev/null 2>&1 fi } + +exit_code_trustable () { + true +} diff --git a/mergetools/kompare b/mergetools/kompare index 433686c12a..e8c0bfa678 100644 --- a/mergetools/kompare +++ b/mergetools/kompare @@ -5,3 +5,7 @@ can_merge () { diff_cmd () { "$merge_tool_path" "$LOCAL" "$REMOTE" } + +exit_code_trustable () { + true +} diff --git a/mergetools/meld b/mergetools/meld index 83ebdfb4c3..bc178e8882 100644 --- a/mergetools/meld +++ b/mergetools/meld @@ -7,7 +7,7 @@ merge_cmd () { then check_meld_for_output_version fi - touch "$BACKUP" + if test "$meld_has_output_option" = true then "$merge_tool_path" --output "$MERGED" \ @@ -15,7 +15,6 @@ merge_cmd () { else "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE" fi - check_unchanged } # Check whether we should use 'meld --output <file>' diff --git a/mergetools/opendiff b/mergetools/opendiff index 0942b2a733..b608dd6de3 100644 --- a/mergetools/opendiff +++ b/mergetools/opendiff @@ -3,7 +3,6 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" "$LOCAL" "$REMOTE" \ @@ -12,5 +11,4 @@ merge_cmd () { "$merge_tool_path" "$LOCAL" "$REMOTE" \ -merge "$MERGED" | cat fi - check_unchanged } diff --git a/mergetools/p4merge b/mergetools/p4merge index 5a608abf9c..7a5b291dd2 100644 --- a/mergetools/p4merge +++ b/mergetools/p4merge @@ -20,14 +20,12 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" if ! $base_present then cp -- "$LOCAL" "$BASE" create_virtual_base "$BASE" "$REMOTE" fi "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED" - check_unchanged } create_empty_file () { diff --git a/mergetools/tkdiff b/mergetools/tkdiff index 618c438e87..eee5cb57e3 100644 --- a/mergetools/tkdiff +++ b/mergetools/tkdiff @@ -10,3 +10,7 @@ merge_cmd () { "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE" fi } + +exit_code_trustable () { + true +} diff --git a/mergetools/tortoisemerge b/mergetools/tortoisemerge index 3b89f1c82d..d7ab666a59 100644 --- a/mergetools/tortoisemerge +++ b/mergetools/tortoisemerge @@ -5,7 +5,6 @@ can_diff () { merge_cmd () { if $base_present then - touch "$BACKUP" basename="$(basename "$merge_tool_path" .exe)" if test "$basename" = "tortoisegitmerge" then @@ -17,7 +16,6 @@ merge_cmd () { -base:"$BASE" -mine:"$LOCAL" \ -theirs:"$REMOTE" -merged:"$MERGED" fi - check_unchanged else echo "$merge_tool_path cannot be used without a base" 1>&2 return 1 diff --git a/mergetools/vimdiff b/mergetools/vimdiff index 74ea6d5479..10d86f3e19 100644 --- a/mergetools/vimdiff +++ b/mergetools/vimdiff @@ -4,7 +4,6 @@ diff_cmd () { } merge_cmd () { - touch "$BACKUP" case "$1" in gvimdiff|vimdiff) if $base_present @@ -31,7 +30,6 @@ merge_cmd () { fi ;; esac - check_unchanged } translate_merge_tool_path() { @@ -44,3 +42,7 @@ translate_merge_tool_path() { ;; esac } + +exit_code_trustable () { + true +} diff --git a/mergetools/winmerge b/mergetools/winmerge index f3819d316a..74d03259fd 100644 --- a/mergetools/winmerge +++ b/mergetools/winmerge @@ -6,10 +6,8 @@ diff_cmd () { merge_cmd () { # mergetool.winmerge.trustExitCode is implicitly false. # touch $BACKUP so that we can check_unchanged. - touch "$BACKUP" "$merge_tool_path" -u -e -dl Local -dr Remote \ "$LOCAL" "$REMOTE" "$MERGED" - check_unchanged } translate_merge_tool_path() { diff --git a/mergetools/xxdiff b/mergetools/xxdiff index 05b443394b..ce5b8e9f29 100644 --- a/mergetools/xxdiff +++ b/mergetools/xxdiff @@ -1,25 +1,23 @@ diff_cmd () { "$merge_tool_path" \ -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ + -R 'Accel.SearchForward: "Ctrl+G"' \ "$LOCAL" "$REMOTE" } merge_cmd () { - touch "$BACKUP" if $base_present then "$merge_tool_path" -X --show-merged-pane \ - -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.SaveAsMerged: "Ctrl+S"' \ -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ + -R 'Accel.SearchForward: "Ctrl+G"' \ --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE" else "$merge_tool_path" -X $extra \ - -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.SaveAsMerged: "Ctrl+S"' \ -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ + -R 'Accel.SearchForward: "Ctrl+G"' \ --merged-file "$MERGED" "$LOCAL" "$REMOTE" fi - check_unchanged } diff --git a/parse-options.c b/parse-options.c index 312a85dbde..4fbe924a5d 100644 --- a/parse-options.c +++ b/parse-options.c @@ -661,7 +661,7 @@ void NORETURN usage_msg_opt(const char *msg, const char * const *usagestr, const struct option *options) { - fprintf(stderr, "%s\n\n", msg); + fprintf(stderr, "fatal: %s\n\n", msg); usage_with_options(usagestr, options); } @@ -991,7 +991,7 @@ const char *remove_leading_path(const char *in, const char *prefix) * * Performs the following normalizations on src, storing the result in dst: * - Ensures that components are separated by '/' (Windows only) - * - Squashes sequences of '/'. + * - Squashes sequences of '/' except "//server/share" on Windows * - Removes "." components. * - Removes ".." components, and the components the precede them. * Returns failure (non-zero) if a ".." component appears as first path @@ -1014,17 +1014,22 @@ const char *remove_leading_path(const char *in, const char *prefix) int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; - int i; + const char *end; - for (i = has_dos_drive_prefix(src); i > 0; i--) - *dst++ = *src++; + /* + * Copy initial part of absolute path: "/", "C:/", "//server/share/". + */ + end = src + offset_1st_component(src); + while (src < end) { + char c = *src++; + if (is_dir_sep(c)) + c = '/'; + *dst++ = c; + } dst0 = dst; - if (is_dir_sep(*src)) { - *dst++ = '/'; - while (is_dir_sep(*src)) - src++; - } + while (is_dir_sep(*src)) + src++; for (;;) { char c = *src; diff --git a/remote-curl.c b/remote-curl.c index f14c41f4c0..28d9d10638 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -274,7 +274,7 @@ static struct discovery *discover_refs(const char *service, int for_push) struct strbuf effective_url = STRBUF_INIT; struct discovery *last = last_discovery; int http_ret, maybe_smart = 0; - struct http_get_options options; + struct http_get_options http_options; if (last && !strcmp(service, last->service)) return last; @@ -291,15 +291,16 @@ static struct discovery *discover_refs(const char *service, int for_push) strbuf_addf(&refs_url, "service=%s", service); } - memset(&options, 0, sizeof(options)); - options.content_type = &type; - options.charset = &charset; - options.effective_url = &effective_url; - options.base_url = &url; - options.no_cache = 1; - options.keep_error = 1; + memset(&http_options, 0, sizeof(http_options)); + http_options.content_type = &type; + http_options.charset = &charset; + http_options.effective_url = &effective_url; + http_options.base_url = &url; + http_options.initial_request = 1; + http_options.no_cache = 1; + http_options.keep_error = 1; - http_ret = http_get_strbuf(refs_url.buf, &buffer, &options); + http_ret = http_get_strbuf(refs_url.buf, &buffer, &http_options); switch (http_ret) { case HTTP_OK: break; @@ -314,6 +315,9 @@ static struct discovery *discover_refs(const char *service, int for_push) die("unable to access '%s': %s", url.buf, curl_errorstr); } + if (options.verbosity && !starts_with(refs_url.buf, url.buf)) + warning(_("redirecting to %s"), url.buf); + last= xcalloc(1, sizeof(*last_discovery)); last->service = service; last->buf_alloc = strbuf_detach(&buffer, &last->len); diff --git a/sequencer.c b/sequencer.c index 30b10ba143..0b78f3149f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -27,6 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer") static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") +static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and @@ -310,6 +311,20 @@ static int error_dirty_index(struct replay_opts *opts) return -1; } +static void update_abort_safety_file(void) +{ + struct object_id head; + + /* Do nothing on a single-pick */ + if (!file_exists(git_path_seq_dir())) + return; + + if (!get_oid("HEAD", &head)) + write_file(git_path_abort_safety_file(), "%s", oid_to_hex(&head)); + else + write_file(git_path_abort_safety_file(), "%s", ""); +} + static int fast_forward_to(const unsigned char *to, const unsigned char *from, int unborn, struct replay_opts *opts) { @@ -339,6 +354,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, strbuf_release(&sb); strbuf_release(&err); ref_transaction_free(transaction); + update_abort_safety_file(); return 0; } @@ -813,6 +829,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, leave: free_message(commit, &msg); + update_abort_safety_file(); return res; } @@ -1132,9 +1149,34 @@ static int save_head(const char *head) return 0; } +static int rollback_is_safe(void) +{ + struct strbuf sb = STRBUF_INIT; + struct object_id expected_head, actual_head; + + if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) { + strbuf_trim(&sb); + if (get_oid_hex(sb.buf, &expected_head)) { + strbuf_release(&sb); + die(_("could not parse %s"), git_path_abort_safety_file()); + } + strbuf_release(&sb); + } + else if (errno == ENOENT) + oidclr(&expected_head); + else + die_errno(_("could not read '%s'"), git_path_abort_safety_file()); + + if (get_oid("HEAD", &actual_head)) + oidclr(&actual_head); + + return !oidcmp(&actual_head, &expected_head); +} + static int reset_for_rollback(const unsigned char *sha1) { const char *argv[4]; /* reset --merge <arg> + NULL */ + argv[0] = "reset"; argv[1] = "--merge"; argv[2] = sha1_to_hex(sha1); @@ -1189,6 +1231,12 @@ int sequencer_rollback(struct replay_opts *opts) error(_("cannot abort from a branch yet to be born")); goto fail; } + + if (!rollback_is_safe()) { + /* Do not error, just do not rollback */ + warning(_("You seem to have moved HEAD. " + "Not rewinding, check your HEAD!")); + } else if (reset_for_rollback(sha1)) goto fail; strbuf_release(&buf); @@ -1393,6 +1441,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) return -1; if (save_opts(opts)) return -1; + update_abort_safety_file(); res = pick_commits(&todo_list, opts); todo_list_release(&todo_list); return res; diff --git a/sha1_file.c b/sha1_file.c index 9c86d1924a..1173071859 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -26,6 +26,7 @@ #include "mru.h" #include "list.h" #include "mergesort.h" +#include "quote.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -329,13 +330,40 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base, return 0; } +static const char *parse_alt_odb_entry(const char *string, + int sep, + struct strbuf *out) +{ + const char *end; + + strbuf_reset(out); + + if (*string == '#') { + /* comment; consume up to next separator */ + end = strchrnul(string, sep); + } else if (*string == '"' && !unquote_c_style(out, string, &end)) { + /* + * quoted path; unquote_c_style has copied the + * data for us and set "end". Broken quoting (e.g., + * an entry that doesn't end with a quote) falls + * back to the unquoted case below. + */ + } else { + /* normal, unquoted path */ + end = strchrnul(string, sep); + strbuf_add(out, string, end - string); + } + + if (*end) + end++; + return end; +} + static void link_alt_odb_entries(const char *alt, int len, int sep, const char *relative_base, int depth) { - struct string_list entries = STRING_LIST_INIT_NODUP; - char *alt_copy; - int i; struct strbuf objdirbuf = STRBUF_INIT; + struct strbuf entry = STRBUF_INIT; if (depth > 5) { error("%s: ignoring alternate object stores, nesting too deep.", @@ -348,16 +376,13 @@ static void link_alt_odb_entries(const char *alt, int len, int sep, die("unable to normalize object directory: %s", objdirbuf.buf); - alt_copy = xmemdupz(alt, len); - string_list_split_in_place(&entries, alt_copy, sep, -1); - for (i = 0; i < entries.nr; i++) { - const char *entry = entries.items[i].string; - if (entry[0] == '\0' || entry[0] == '#') + while (*alt) { + alt = parse_alt_odb_entry(alt, sep, &entry); + if (!entry.len) continue; - link_alt_odb_entry(entry, relative_base, depth, objdirbuf.buf); + link_alt_odb_entry(entry.buf, relative_base, depth, objdirbuf.buf); } - string_list_clear(&entries, 0); - free(alt_copy); + strbuf_release(&entry); strbuf_release(&objdirbuf); } @@ -431,12 +431,14 @@ void remove_nonexistent_theirs_shallow(struct shallow_info *info) define_commit_slab(ref_bitmap, uint32_t *); +#define POOL_SIZE (512 * 1024) + struct paint_info { struct ref_bitmap ref_bitmap; unsigned nr_bits; - char **slab; + char **pools; char *free, *end; - unsigned slab_count; + unsigned pool_count; }; static uint32_t *paint_alloc(struct paint_info *info) @@ -444,12 +446,15 @@ static uint32_t *paint_alloc(struct paint_info *info) unsigned nr = (info->nr_bits + 31) / 32; unsigned size = nr * sizeof(uint32_t); void *p; - if (!info->slab_count || info->free + size > info->end) { - info->slab_count++; - REALLOC_ARRAY(info->slab, info->slab_count); - info->free = xmalloc(COMMIT_SLAB_SIZE); - info->slab[info->slab_count - 1] = info->free; - info->end = info->free + COMMIT_SLAB_SIZE; + if (!info->pool_count || size > info->end - info->free) { + if (size > POOL_SIZE) + die("BUG: pool size too small for %d in paint_alloc()", + size); + info->pool_count++; + REALLOC_ARRAY(info->pools, info->pool_count); + info->free = xmalloc(POOL_SIZE); + info->pools[info->pool_count - 1] = info->free; + info->end = info->free + POOL_SIZE; } p = info->free; info->free += size; @@ -462,7 +467,7 @@ static uint32_t *paint_alloc(struct paint_info *info) * all walked commits. */ static void paint_down(struct paint_info *info, const unsigned char *sha1, - int id) + unsigned int id) { unsigned int i, nr; struct commit_list *head = NULL; @@ -474,7 +479,7 @@ static void paint_down(struct paint_info *info, const unsigned char *sha1, if (!c) return; memset(bitmap, 0, bitmap_size); - bitmap[id / 32] |= (1 << (id % 32)); + bitmap[id / 32] |= (1U << (id % 32)); commit_list_insert(c, &head); while (head) { struct commit_list *p; @@ -507,12 +512,8 @@ static void paint_down(struct paint_info *info, const unsigned char *sha1, oid_to_hex(&c->object.oid)); for (p = c->parents; p; p = p->next) { - uint32_t **p_refs = ref_bitmap_at(&info->ref_bitmap, - p->item); if (p->item->object.flags & SEEN) continue; - if (*p_refs == NULL || *p_refs == *refs) - *p_refs = *refs; commit_list_insert(p->item, &head); } } @@ -624,9 +625,9 @@ void assign_shallow_commits_to_refs(struct shallow_info *info, post_assign_shallow(info, &pi.ref_bitmap, ref_status); clear_ref_bitmap(&pi.ref_bitmap); - for (i = 0; i < pi.slab_count; i++) - free(pi.slab[i]); - free(pi.slab); + for (i = 0; i < pi.pool_count; i++) + free(pi.pools[i]); + free(pi.pools); free(shallow); } @@ -648,11 +649,11 @@ static int add_ref(const char *refname, const struct object_id *oid, static void update_refstatus(int *ref_status, int nr, uint32_t *bitmap) { - int i; + unsigned int i; if (!ref_status) return; for (i = 0; i < nr; i++) - if (bitmap[i / 32] & (1 << (i % 32))) + if (bitmap[i / 32] & (1U << (i % 32))) ref_status[i]++; } diff --git a/submodule.c b/submodule.c index 6f7d883de9..ece17315d6 100644 --- a/submodule.c +++ b/submodule.c @@ -500,27 +500,67 @@ static int has_remote(const char *refname, const struct object_id *oid, return 1; } -static int submodule_needs_pushing(const char *path, const unsigned char sha1[20]) +static int append_sha1_to_argv(const unsigned char sha1[20], void *data) { - if (add_submodule_odb(path) || !lookup_commit_reference(sha1)) + struct argv_array *argv = data; + argv_array_push(argv, sha1_to_hex(sha1)); + return 0; +} + +static int check_has_commit(const unsigned char sha1[20], void *data) +{ + int *has_commit = data; + + if (!lookup_commit_reference(sha1)) + *has_commit = 0; + + return 0; +} + +static int submodule_has_commits(const char *path, struct sha1_array *commits) +{ + int has_commit = 1; + + if (add_submodule_odb(path)) + return 0; + + sha1_array_for_each_unique(commits, check_has_commit, &has_commit); + return has_commit; +} + +static int submodule_needs_pushing(const char *path, struct sha1_array *commits) +{ + if (!submodule_has_commits(path, commits)) + /* + * NOTE: We do consider it safe to return "no" here. The + * correct answer would be "We do not know" instead of + * "No push needed", but it is quite hard to change + * the submodule pointer without having the submodule + * around. If a user did however change the submodules + * without having the submodule around, this indicates + * an expert who knows what they are doing or a + * maintainer integrating work from other people. In + * both cases it should be safe to skip this check. + */ return 0; if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) { struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL}; struct strbuf buf = STRBUF_INIT; int needs_pushing = 0; - argv[1] = sha1_to_hex(sha1); - cp.argv = argv; + argv_array_push(&cp.args, "rev-list"); + sha1_array_for_each_unique(commits, append_sha1_to_argv, &cp.args); + argv_array_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; cp.dir = path; if (start_command(&cp)) - die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s", - sha1_to_hex(sha1), path); + die("Could not run 'git rev-list <commits> --not --remotes -n 1' command in submodule %s", + path); if (strbuf_read(&buf, cp.out, 41)) needs_pushing = 1; finish_command(&cp); @@ -532,19 +572,34 @@ static int submodule_needs_pushing(const char *path, const unsigned char sha1[20 return 0; } +static struct sha1_array *submodule_commits(struct string_list *submodules, + const char *path) +{ + struct string_list_item *item; + + item = string_list_insert(submodules, path); + if (item->util) + return (struct sha1_array *) item->util; + + /* NEEDSWORK: should we have sha1_array_init()? */ + item->util = xcalloc(1, sizeof(struct sha1_array)); + return (struct sha1_array *) item->util; +} + static void collect_submodules_from_diff(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; - struct string_list *needs_pushing = data; + struct string_list *submodules = data; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; + struct sha1_array *commits; if (!S_ISGITLINK(p->two->mode)) continue; - if (submodule_needs_pushing(p->two->path, p->two->oid.hash)) - string_list_insert(needs_pushing, p->two->path); + commits = submodule_commits(submodules, p->two->path); + sha1_array_append(commits, p->two->oid.hash); } } @@ -560,46 +615,63 @@ static void find_unpushed_submodule_commits(struct commit *commit, diff_tree_combined_merge(commit, 1, &rev); } -int find_unpushed_submodules(unsigned char new_sha1[20], +static void free_submodules_sha1s(struct string_list *submodules) +{ + struct string_list_item *item; + for_each_string_list_item(item, submodules) + sha1_array_clear((struct sha1_array *) item->util); + string_list_clear(submodules, 1); +} + +int find_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, struct string_list *needs_pushing) { struct rev_info rev; struct commit *commit; - const char *argv[] = {NULL, NULL, "--not", "NULL", NULL}; - int argc = ARRAY_SIZE(argv) - 1; - char *sha1_copy; - - struct strbuf remotes_arg = STRBUF_INIT; + struct string_list submodules = STRING_LIST_INIT_DUP; + struct string_list_item *submodule; + struct argv_array argv = ARGV_ARRAY_INIT; - strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name); init_revisions(&rev, NULL); - sha1_copy = xstrdup(sha1_to_hex(new_sha1)); - argv[1] = sha1_copy; - argv[3] = remotes_arg.buf; - setup_revisions(argc, argv, &rev, NULL); + + /* argv.argv[0] will be ignored by setup_revisions */ + argv_array_push(&argv, "find_unpushed_submodules"); + sha1_array_for_each_unique(commits, append_sha1_to_argv, &argv); + argv_array_push(&argv, "--not"); + argv_array_pushf(&argv, "--remotes=%s", remotes_name); + + setup_revisions(argv.argc, argv.argv, &rev, NULL); if (prepare_revision_walk(&rev)) die("revision walk setup failed"); while ((commit = get_revision(&rev)) != NULL) - find_unpushed_submodule_commits(commit, needs_pushing); + find_unpushed_submodule_commits(commit, &submodules); reset_revision_walk(); - free(sha1_copy); - strbuf_release(&remotes_arg); + argv_array_clear(&argv); + + for_each_string_list_item(submodule, &submodules) { + struct sha1_array *commits = (struct sha1_array *) submodule->util; + + if (submodule_needs_pushing(submodule->string, commits)) + string_list_insert(needs_pushing, submodule->string); + } + free_submodules_sha1s(&submodules); return needs_pushing->nr; } -static int push_submodule(const char *path) +static int push_submodule(const char *path, int dry_run) { if (add_submodule_odb(path)) return 1; if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) { struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = {"push", NULL}; + argv_array_push(&cp.args, "push"); + if (dry_run) + argv_array_push(&cp.args, "--dry-run"); - cp.argv = argv; prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@ -612,18 +684,20 @@ static int push_submodule(const char *path) return 1; } -int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name) +int push_unpushed_submodules(struct sha1_array *commits, + const char *remotes_name, + int dry_run) { int i, ret = 1; struct string_list needs_pushing = STRING_LIST_INIT_DUP; - if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing)) + if (!find_unpushed_submodules(commits, remotes_name, &needs_pushing)) return 1; for (i = 0; i < needs_pushing.nr; i++) { const char *path = needs_pushing.items[i].string; fprintf(stderr, "Pushing submodule '%s'\n", path); - if (!push_submodule(path)) { + if (!push_submodule(path, dry_run)) { fprintf(stderr, "Unable to push submodule '%s'\n", path); ret = 0; } diff --git a/submodule.h b/submodule.h index d9e197a948..23d76682b1 100644 --- a/submodule.h +++ b/submodule.h @@ -3,6 +3,7 @@ struct diff_options; struct argv_array; +struct sha1_array; enum { RECURSE_SUBMODULES_CHECK = -4, @@ -62,9 +63,11 @@ int submodule_uses_gitfile(const char *path); int ok_to_remove_submodule(const char *path); int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], const unsigned char a[20], const unsigned char b[20], int search); -int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name, +int find_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, struct string_list *needs_pushing); -int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); +extern int push_unpushed_submodules(struct sha1_array *commits, + const char *remotes_name, + int dry_run); void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); int parallel_submodules(void); diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index c3e631394f..69174c6e31 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -123,6 +123,7 @@ ScriptAlias /error/ error.sh/ </Files> RewriteEngine on +RewriteRule ^/dumb-redir/(.*)$ /dumb/$1 [R=301] RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301] RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302] RewriteRule ^/smart-redir-auth/(.*)$ /auth/smart/$1 [R=301] @@ -132,6 +133,19 @@ RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302] RewriteRule ^/loop-redir/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-(.*) /$1 [R=302] RewriteRule ^/loop-redir/(.*)$ /loop-redir/x-$1 [R=302] +# The first rule issues a client-side redirect to something +# that _doesn't_ look like a git repo. The second rule is a +# server-side rewrite, so that it turns out the odd-looking +# thing _is_ a git repo. The "[PT]" tells Apache to match +# the usual ScriptAlias rules for /smart. +RewriteRule ^/insane-redir/(.*)$ /intern-redir/$1/foo [R=301] +RewriteRule ^/intern-redir/(.*)/foo$ /smart/$1 [PT] + +# Serve info/refs internally without redirecting, but +# issue a redirect for any object requests. +RewriteRule ^/redir-objects/(.*/info/refs)$ /dumb/$1 [PT] +RewriteRule ^/redir-objects/(.*/objects/.*)$ /dumb/$1 [R=301] + # Apache 2.2 does not understand <RequireAll>, so we use RewriteCond. # And as RewriteCond does not allow testing for non-matches, we match # the desired case first (one has abra, two has cadabra), and let it diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh index 7655c94c28..ff50960cca 100755 --- a/t/t1308-config-set.sh +++ b/t/t1308-config-set.sh @@ -219,14 +219,8 @@ test_expect_success 'check line errors for malformed values' ' ' test_expect_success 'error on modifying repo config without repo' ' - mkdir no-repo && - ( - GIT_CEILING_DIRECTORIES=$(pwd) && - export GIT_CEILING_DIRECTORIES && - cd no-repo && - test_must_fail git config a.b c 2>err && - grep "not in a git directory" err - ) + nongit test_must_fail git config a.b c 2>err && + grep "not in a git directory" err ' cmdline_config="'foo.bar=from-cmdline'" diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh index 1b1b65a6b0..465eeeacd3 100755 --- a/t/t2027-worktree-list.sh +++ b/t/t2027-worktree-list.sh @@ -96,4 +96,44 @@ test_expect_success 'bare repo cleanup' ' rm -rf bare1 ' +test_expect_success 'broken main worktree still at the top' ' + git init broken-main && + ( + cd broken-main && + test_commit new && + git worktree add linked && + cat >expected <<-EOF && + worktree $(pwd) + HEAD $_z40 + + EOF + cd linked && + echo "worktree $(pwd)" >expected && + echo "ref: .broken" >../.git/HEAD && + git worktree list --porcelain | head -n 3 >actual && + test_cmp ../expected actual && + git worktree list | head -n 1 >actual.2 && + grep -F "(error)" actual.2 + ) +' + +test_expect_success 'linked worktrees are sorted' ' + mkdir sorted && + git init sorted/main && + ( + cd sorted/main && + test_tick && + test_commit new && + git worktree add ../first && + git worktree add ../second && + git worktree list --porcelain | grep ^worktree >actual + ) && + cat >expected <<-EOF && + worktree $(pwd)/sorted/main + worktree $(pwd)/sorted/first + worktree $(pwd)/sorted/second + EOF + test_cmp expected sorted/main/actual +' + test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 470f33466c..9a893b5fe7 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -575,13 +575,13 @@ test_expect_success 'merge removes empty directories' ' test_must_fail test -d d ' -test_expect_failure 'merge-recursive simple w/submodule' ' +test_expect_success 'merge-recursive simple w/submodule' ' git checkout submod && git merge remove ' -test_expect_failure 'merge-recursive simple w/submodule result' ' +test_expect_success 'merge-recursive simple w/submodule result' ' git ls-files -s >actual && ( diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh index d5b896d445..ebf4f5e4b2 100755 --- a/t/t3426-rebase-submodule.sh +++ b/t/t3426-rebase-submodule.sh @@ -38,9 +38,6 @@ git_rebase_interactive () { git rebase -i "$1" } -KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 -# The real reason "replace directory with submodule" fails is because a -# directory "sub1" exists, but we reuse the suppression added for merge here test_submodule_switch "git_rebase_interactive" test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 394f0005a1..4f2a263b63 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -141,4 +141,16 @@ test_expect_success 'cherry-pick "-" works with arguments' ' test_cmp expect actual ' +test_expect_success 'cherry-pick works with dirty renamed file' ' + test_commit to-rename && + git checkout -b unrelated && + test_commit unrelated && + git checkout @{-1} && + git mv to-rename.t renamed && + test_tick && + git commit -m renamed && + echo modified >renamed && + git cherry-pick refs/heads/unrelated +' + test_done diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 7b7a89dbd5..372307c21b 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -147,6 +147,16 @@ test_expect_success '--abort to cancel single cherry-pick' ' git diff-index --exit-code HEAD ' +test_expect_success '--abort does not unsafely change HEAD' ' + pristine_detach initial && + test_must_fail git cherry-pick picked anotherpick && + git reset --hard base && + test_must_fail git cherry-pick picked anotherpick && + git cherry-pick --abort 2>actual && + test_i18ngrep "You seem to have moved HEAD" actual && + test_cmp_rev base HEAD +' + test_expect_success 'cherry-pick --abort to cancel multiple revert' ' pristine_detach anotherpick && test_expect_code 1 git revert base..picked && diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index e1a6ccc00c..2de3e18ce6 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -766,4 +766,13 @@ test_expect_success 'stash list --cc shows combined diff' ' test_cmp expect actual ' +test_expect_success 'stash is not confused by partial renames' ' + mv file renamed && + git add renamed && + git stash && + git stash apply && + test_path_is_file renamed && + test_path_is_missing file +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 566817e2ef..d09acfe48e 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -311,6 +311,13 @@ diff --line-prefix=abc master master^ side diff --dirstat master~1 master~2 diff --dirstat initial rearrange diff --dirstat-by-file initial rearrange +# No-index --abbrev and --no-abbrev +diff --raw initial +diff --raw --abbrev=4 initial +diff --raw --no-abbrev initial +diff --no-index --raw dir2 dir +diff --no-index --raw --abbrev=4 dir2 dir +diff --no-index --raw --no-abbrev dir2 dir EOF test_expect_success 'log -S requires an argument' ' diff --git a/t/t4013/diff.diff_--no-index_--raw_--abbrev=4_dir2_dir b/t/t4013/diff.diff_--no-index_--raw_--abbrev=4_dir2_dir new file mode 100644 index 0000000000..a71b38a833 --- /dev/null +++ b/t/t4013/diff.diff_--no-index_--raw_--abbrev=4_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw --abbrev=4 dir2 dir +:000000 100644 0000... 0000... A dir/sub +$ diff --git a/t/t4013/diff.diff_--no-index_--raw_--no-abbrev_dir2_dir b/t/t4013/diff.diff_--no-index_--raw_--no-abbrev_dir2_dir new file mode 100644 index 0000000000..e0f00977c8 --- /dev/null +++ b/t/t4013/diff.diff_--no-index_--raw_--no-abbrev_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw --no-abbrev dir2 dir +:000000 100644 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 A dir/sub +$ diff --git a/t/t4013/diff.diff_--no-index_--raw_dir2_dir b/t/t4013/diff.diff_--no-index_--raw_dir2_dir new file mode 100644 index 0000000000..3cb4ee7a9a --- /dev/null +++ b/t/t4013/diff.diff_--no-index_--raw_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw dir2 dir +:000000 100644 0000000... 0000000... A dir/sub +$ diff --git a/t/t4013/diff.diff_--raw_--abbrev=4_initial b/t/t4013/diff.diff_--raw_--abbrev=4_initial new file mode 100644 index 0000000000..c3641db31d --- /dev/null +++ b/t/t4013/diff.diff_--raw_--abbrev=4_initial @@ -0,0 +1,6 @@ +$ git diff --raw --abbrev=4 initial +:100644 100644 35d2... 9929... M dir/sub +:100644 100644 01e7... 10a8... M file0 +:000000 100644 0000... b1e6... A file1 +:100644 000000 01e7... 0000... D file2 +$ diff --git a/t/t4013/diff.diff_--raw_--no-abbrev_initial b/t/t4013/diff.diff_--raw_--no-abbrev_initial new file mode 100644 index 0000000000..c87a1258e3 --- /dev/null +++ b/t/t4013/diff.diff_--raw_--no-abbrev_initial @@ -0,0 +1,6 @@ +$ git diff --raw --no-abbrev initial +:100644 100644 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e 992913c5aa0a5476d10c49ed0f21fc0c6d1aedf3 M dir/sub +:100644 100644 01e79c32a8c99c557f0757da7cb6d65b3414466d 10a8a9f3657f91a156b9f0184ed79a20adef9f7f M file0 +:000000 100644 0000000000000000000000000000000000000000 b1e67221afe8461efd244b487afca22d46b95eb8 A file1 +:100644 000000 01e79c32a8c99c557f0757da7cb6d65b3414466d 0000000000000000000000000000000000000000 D file2 +$ diff --git a/t/t4013/diff.diff_--raw_initial b/t/t4013/diff.diff_--raw_initial new file mode 100644 index 0000000000..a3e978040d --- /dev/null +++ b/t/t4013/diff.diff_--raw_initial @@ -0,0 +1,6 @@ +$ git diff --raw initial +:100644 100644 35d242b... 992913c... M dir/sub +:100644 100644 01e79c3... 10a8a9f... M file0 +:000000 100644 0000000... b1e6722... A file1 +:100644 000000 01e79c3... 0000000... D file2 +$ diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 830bf2a2f6..886b6953e4 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -94,20 +94,6 @@ check_tar() { ' } -# run "$@" inside a non-git directory -nongit () { - test -d non-repo || - mkdir non-repo || - return 1 - - ( - GIT_CEILING_DIRECTORIES=$(pwd) && - export GIT_CEILING_DIRECTORIES && - cd non-repo && - "$@" - ) -} - test_expect_success \ 'populate workdir' \ 'mkdir a && diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 899e52d50f..43a672c345 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -406,6 +406,21 @@ test_expect_success 'verify resulting packs' ' git verify-pack test-11-*.pack ' +test_expect_success 'set up pack for non-repo tests' ' + # make sure we have a pack with no matching index file + cp test-1-*.pack foo.pack +' + +test_expect_success 'index-pack --stdin complains of non-repo' ' + nongit test_must_fail git index-pack --stdin <foo.pack && + test_path_is_missing non-repo/.git +' + +test_expect_success 'index-pack <pack> works in non-repo' ' + nongit git index-pack ../foo.pack && + test_path_is_file foo.idx +' + # # WARNING! # diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 551844584f..17f4d0fe4e 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -255,6 +255,23 @@ test_expect_success '--rebase' ' test new = "$(git show HEAD:file2)" ' +test_expect_success '--rebase fast forward' ' + git reset --hard before-rebase && + git checkout -b ff && + echo another modification >file && + git commit -m third file && + + git checkout to-rebase && + git pull --rebase . ff && + test "$(git rev-parse HEAD)" = "$(git rev-parse ff)" && + + # The above only validates the result. Did we actually bypass rebase? + git reflog -1 >reflog.actual && + sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy && + echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected && + test_cmp reflog.expected reflog.fuzzy +' + test_expect_success '--rebase with conflicts shows advice' ' test_when_finished "git rebase --abort; git checkout -f to-rebase" && git checkout -b seq && diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh index 73f4bb6346..44309566f1 100755 --- a/t/t5528-push-default.sh +++ b/t/t5528-push-default.sh @@ -98,6 +98,16 @@ test_expect_success 'push from/to new branch with upstream, matching and simple' test_push_failure upstream ' +test_expect_success 'push ambiguously named branch with upstream, matching and simple' ' + git checkout -b ambiguous && + test_config branch.ambiguous.remote parent1 && + test_config branch.ambiguous.merge refs/heads/ambiguous && + git tag ambiguous && + test_push_success simple ambiguous && + test_push_success matching ambiguous && + test_push_success upstream ambiguous +' + test_expect_success 'push from/to new branch with current creates remote branch' ' test_config branch.new-branch.remote repo1 && git checkout new-branch && diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 198ce84754..1524ff5ba6 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -427,7 +427,31 @@ test_expect_success 'push unpushable submodule recursively fails' ' cd submodule.git && git rev-parse master >../actual ) && + test_when_finished git -C work reset --hard master^ && test_cmp expected actual ' +test_expect_success 'push --dry-run does not recursively update submodules' ' + ( + cd work/gar/bage && + git checkout master && + git rev-parse master >../../../expected_submodule && + > junk9 && + git add junk9 && + git commit -m "Ninth junk" && + + # Go up to 'work' directory + cd ../.. && + git checkout master && + git rev-parse master >../expected_pub && + git add gar/bage && + git commit -m "Ninth commit for gar/bage" && + git push --dry-run --recurse-submodules=on-demand ../pub.git master + ) && + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_pub actual_pub && + test_cmp expected_submodule actual_submodule +' + test_done diff --git a/t/t5547-push-quarantine.sh b/t/t5547-push-quarantine.sh index 1e5d32d068..af9fcd833a 100755 --- a/t/t5547-push-quarantine.sh +++ b/t/t5547-push-quarantine.sh @@ -33,4 +33,29 @@ test_expect_success 'rejected objects are removed' ' test_cmp expect actual ' +test_expect_success 'push to repo path with path separator (colon)' ' + # The interesting failure case here is when the + # receiving end cannot access its original object directory, + # so make it likely for us to generate a delta by having + # a non-trivial file with multiple versions. + + test-genrandom foo 4096 >file.bin && + git add file.bin && + git commit -m bin && + + if test_have_prereq MINGW + then + pathsep=";" + else + pathsep=":" + fi && + git clone --bare . "xxx${pathsep}yyy.git" && + + echo change >>file.bin && + git commit -am change && + # Note that we have to use the full path here, or it gets confused + # with the ssh host:path syntax. + git push "$(pwd)/xxx${pathsep}yyy.git" HEAD +' + test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 7641417b4a..264a1ab8b0 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -307,5 +307,66 @@ test_expect_success 'remote-http complains cleanly about malformed urls' ' test_must_fail git remote-http http::/example.com/repo.git ' +test_expect_success 'redirects can be forbidden/allowed' ' + test_must_fail git -c http.followRedirects=false \ + clone $HTTPD_URL/dumb-redir/repo.git dumb-redir && + git -c http.followRedirects=true \ + clone $HTTPD_URL/dumb-redir/repo.git dumb-redir 2>stderr +' + +test_expect_success 'redirects are reported to stderr' ' + # just look for a snippet of the redirected-to URL + test_i18ngrep /dumb/ stderr +' + +test_expect_success 'non-initial redirects can be forbidden' ' + test_must_fail git -c http.followRedirects=initial \ + clone $HTTPD_URL/redir-objects/repo.git redir-objects && + git -c http.followRedirects=true \ + clone $HTTPD_URL/redir-objects/repo.git redir-objects +' + +test_expect_success 'http.followRedirects defaults to "initial"' ' + test_must_fail git clone $HTTPD_URL/redir-objects/repo.git default +' + +# The goal is for a clone of the "evil" repository, which has no objects +# itself, to cause the client to fetch objects from the "victim" repository. +test_expect_success 'set up evil alternates scheme' ' + victim=$HTTPD_DOCUMENT_ROOT_PATH/victim.git && + git init --bare "$victim" && + git -C "$victim" --work-tree=. commit --allow-empty -m secret && + git -C "$victim" repack -ad && + git -C "$victim" update-server-info && + sha1=$(git -C "$victim" rev-parse HEAD) && + + evil=$HTTPD_DOCUMENT_ROOT_PATH/evil.git && + git init --bare "$evil" && + # do this by hand to avoid object existence check + printf "%s\\t%s\\n" $sha1 refs/heads/master >"$evil/info/refs" +' + +# Here we'll just redirect via HTTP. In a real-world attack these would be on +# different servers, but we should reject it either way. +test_expect_success 'http-alternates is a non-initial redirect' ' + echo "$HTTPD_URL/dumb/victim.git/objects" \ + >"$evil/objects/info/http-alternates" && + test_must_fail git -c http.followRedirects=initial \ + clone $HTTPD_URL/dumb/evil.git evil-initial && + git -c http.followRedirects=true \ + clone $HTTPD_URL/dumb/evil.git evil-initial +' + +# Curl supports a lot of protocols that we'd prefer not to allow +# http-alternates to use, but it's hard to test whether curl has +# accessed, say, the SMTP protocol, because we are not running an SMTP server. +# But we can check that it does not allow access to file://, which would +# otherwise allow this clone to complete. +test_expect_success 'http-alternates cannot point at funny protocols' ' + echo "file://$victim/objects" >"$evil/objects/info/http-alternates" && + test_must_fail git -c http.followRedirects=true \ + clone "$HTTPD_URL/dumb/evil.git" evil-file +' + stop_httpd test_done diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 1ec5b2747a..6e5b9e42fb 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -119,6 +119,10 @@ test_expect_success 'redirects re-root further requests' ' git clone $HTTPD_URL/smart-redir-limited/repo.git repo-redir-limited ' +test_expect_success 're-rooting dies on insane schemes' ' + test_must_fail git clone $HTTPD_URL/insane-redir/repo.git insane +' + test_expect_success 'clone from password-protected repository' ' echo two >expect && set_askpass user@host pass@host && diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh index eec4137ca5..26ebb0375d 100755 --- a/t/t5615-alternate-env.sh +++ b/t/t5615-alternate-env.sh @@ -68,4 +68,22 @@ test_expect_success 'access alternate via relative path (subdir)' ' EOF ' +# set variables outside test to avoid quote insanity; the \057 is '/', +# which doesn't need quoting, but just confirms that de-quoting +# is working. +quoted='"one.git\057objects"' +unquoted='two.git/objects' +test_expect_success 'mix of quoted and unquoted alternates' ' + check_obj "$quoted:$unquoted" <<-EOF + $one blob + $two blob +' + +test_expect_success !MINGW 'broken quoting falls back to interpreting raw' ' + mv one.git \"one.git && + check_obj \"one.git/objects <<-EOF + $one blob + EOF +' + test_done diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh index 0d105d5417..044cc152f8 100755 --- a/t/t5812-proto-disable-http.sh +++ b/t/t5812-proto-disable-http.sh @@ -18,6 +18,7 @@ test_proto "smart http" http "$HTTPD_URL/smart/repo.git" test_expect_success 'curl redirects respect whitelist' ' test_must_fail env GIT_ALLOW_PROTOCOL=http:https \ + GIT_SMART_HTTP=0 \ git clone "$HTTPD_URL/ftp-redir/repo.git" 2>stderr && { test_i18ngrep "ftp.*disabled" stderr || diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 64a9850e31..8c617981a3 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -83,12 +83,24 @@ test_expect_success 'final^1^@ = final^1^1 final^1^2' ' test_cmp expect actual ' +test_expect_success 'symbolic final^1^@ = final^1^1 final^1^2' ' + git rev-parse --symbolic final^1^1 final^1^2 >expect && + git rev-parse --symbolic final^1^@ >actual && + test_cmp expect actual +' + test_expect_success 'final^1^! = final^1 ^final^1^1 ^final^1^2' ' git rev-parse final^1 ^final^1^1 ^final^1^2 >expect && git rev-parse final^1^! >actual && test_cmp expect actual ' +test_expect_success 'symbolic final^1^! = final^1 ^final^1^1 ^final^1^2' ' + git rev-parse --symbolic final^1 ^final^1^1 ^final^1^2 >expect && + git rev-parse --symbolic final^1^! >actual && + test_cmp expect actual +' + test_expect_success 'large graft octopus' ' test_cmp_rev_output b31 "git rev-parse --verify b1^30" ' @@ -143,6 +155,12 @@ test_expect_success 'rev-parse merge^-2 = merge^2..merge' ' test_cmp expect actual ' +test_expect_success 'symbolic merge^-1 = merge^1..merge' ' + git rev-parse --symbolic merge^1..merge >expect && + git rev-parse --symbolic merge^-1 >actual && + test_cmp expect actual +' + test_expect_success 'rev-parse merge^-0 (invalid parent)' ' test_must_fail git rev-parse merge^-0 ' diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index d84897a67a..0d8d893090 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -155,6 +155,15 @@ test_expect_success 'amend --only ignores staged contents' ' git diff --exit-code ' +test_expect_success 'allow-empty --only ignores staged contents' ' + echo changed-again >file && + git add file && + git commit --allow-empty --only -m "empty" && + git cat-file blob HEAD:file >file.actual && + test_cmp file.expect file.actual && + git diff --exit-code +' + test_expect_success 'set up editor' ' cat >editor <<-\EOF && #!/bin/sh diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 70a2de461a..99d4123461 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -374,6 +374,7 @@ test_expect_success PERL 'setup change in subdirectory' ' echo master >sub/sub && git add sub/sub && git commit -m "added sub/sub" && + git tag v1 && echo test >>file && echo test >>sub/sub && git add file sub/sub && @@ -409,12 +410,49 @@ run_dir_diff_test 'difftool --dir-diff ignores --prompt' ' grep file output ' -run_dir_diff_test 'difftool --dir-diff from subdirectory' ' +run_dir_diff_test 'difftool --dir-diff branch from subdirectory' ' ( cd sub && git difftool --dir-diff $symlinks --extcmd ls branch >output && - grep sub output && - grep file output + # "sub" must only exist in "right" + # "file" and "file2" must be listed in both "left" and "right" + test "1" = $(grep sub output | wc -l) && + test "2" = $(grep file"$" output | wc -l) && + test "2" = $(grep file2 output | wc -l) + ) +' + +run_dir_diff_test 'difftool --dir-diff v1 from subdirectory' ' + ( + cd sub && + git difftool --dir-diff $symlinks --extcmd ls v1 >output && + # "sub" and "file" exist in both v1 and HEAD. + # "file2" is unchanged. + test "2" = $(grep sub output | wc -l) && + test "2" = $(grep file output | wc -l) && + test "0" = $(grep file2 output | wc -l) + ) +' + +run_dir_diff_test 'difftool --dir-diff branch from subdirectory w/ pathspec' ' + ( + cd sub && + git difftool --dir-diff $symlinks --extcmd ls branch -- .>output && + # "sub" only exists in "right" + # "file" and "file2" must not be listed + test "1" = $(grep sub output | wc -l) && + test "0" = $(grep file output | wc -l) + ) +' + +run_dir_diff_test 'difftool --dir-diff v1 from subdirectory w/ pathspec' ' + ( + cd sub && + git difftool --dir-diff $symlinks --extcmd ls v1 -- .>output && + # "sub" exists in v1 and HEAD + # "file" is filtered out by the pathspec + test "2" = $(grep sub output | wc -l) && + test "0" = $(grep file output | wc -l) ) ' diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 92a3aa8063..8a8ba65a2a 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -17,25 +17,12 @@ case "$GIT_SVN_LC_ALL" in ;; esac -deepdir=nothing-above -ceiling=$PWD - test_expect_success 'git svn --version works anywhere' ' - mkdir -p "$deepdir" && ( - GIT_CEILING_DIRECTORIES="$ceiling" && - export GIT_CEILING_DIRECTORIES && - cd "$deepdir" && - git svn --version - ) + nongit git svn --version ' test_expect_success 'git svn help works anywhere' ' - mkdir -p "$deepdir" && ( - GIT_CEILING_DIRECTORIES="$ceiling" && - export GIT_CEILING_DIRECTORIES && - cd "$deepdir" && - git svn help - ) + nongit git svn help ' test_expect_success \ diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh index 110a7e7924..734b8db4cb 100755 --- a/t/t9824-git-p4-git-lfs.sh +++ b/t/t9824-git-p4-git-lfs.sh @@ -42,6 +42,8 @@ test_expect_success 'Create repo with binary files' ' ( cd "$cli" && + >file0.dat && + p4 add file0.dat && echo "content 1 txt 23 bytes" >file1.txt && p4 add file1.txt && echo "content 2-3 bin 25 bytes" >file2.dat && diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 2ba62fbc17..a34e55f874 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -257,12 +257,7 @@ test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' ' ' test_expect_success '__gitdir - not a git repository' ' - ( - cd subdir/subsubdir && - GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" && - export GIT_CEILING_DIRECTORIES && - test_must_fail __gitdir - ) + nongit test_must_fail __gitdir ' test_expect_success '__gitcomp - trailing space - options' ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index fdaeb3a96b..adab7f51f4 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -994,3 +994,17 @@ test_copy_bytes () { } ' - "$1" } + +# run "$@" inside a non-git directory +nongit () { + test -d non-repo || + mkdir non-repo || + return 1 + + ( + GIT_CEILING_DIRECTORIES=$(pwd) && + export GIT_CEILING_DIRECTORIES && + cd non-repo && + "$@" + ) +} diff --git a/tmp-objdir.c b/tmp-objdir.c index 64435f23a4..b2d9280f10 100644 --- a/tmp-objdir.c +++ b/tmp-objdir.c @@ -5,6 +5,7 @@ #include "string-list.h" #include "strbuf.h" #include "argv-array.h" +#include "quote.h" struct tmp_objdir { struct strbuf path; @@ -79,12 +80,27 @@ static void remove_tmp_objdir_on_signal(int signo) */ static void env_append(struct argv_array *env, const char *key, const char *val) { - const char *old = getenv(key); + struct strbuf quoted = STRBUF_INIT; + const char *old; + /* + * Avoid quoting if it's not necessary, for maximum compatibility + * with older parsers which don't understand the quoting. + */ + if (*val == '"' || strchr(val, PATH_SEP)) { + strbuf_addch("ed, '"'); + quote_c_style(val, "ed, NULL, 1); + strbuf_addch("ed, '"'); + val = quoted.buf; + } + + old = getenv(key); if (!old) argv_array_pushf(env, "%s=%s", key, val); else argv_array_pushf(env, "%s=%s%c%s", key, old, PATH_SEP, val); + + strbuf_release("ed); } static void env_replace(struct argv_array *env, const char *key, const char *val) diff --git a/transport.c b/transport.c index d57e8dec28..04e5d6623e 100644 --- a/transport.c +++ b/transport.c @@ -949,23 +949,39 @@ int transport_push(struct transport *transport, if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) { struct ref *ref = remote_refs; + struct sha1_array commits = SHA1_ARRAY_INIT; + for (; ref; ref = ref->next) - if (!is_null_oid(&ref->new_oid) && - !push_unpushed_submodules(ref->new_oid.hash, - transport->remote->name)) - die ("Failed to push all needed submodules!"); + if (!is_null_oid(&ref->new_oid)) + sha1_array_append(&commits, ref->new_oid.hash); + + if (!push_unpushed_submodules(&commits, + transport->remote->name, + pretend)) { + sha1_array_clear(&commits); + die("Failed to push all needed submodules!"); + } + sha1_array_clear(&commits); } - if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | - TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) { + if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) || + ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && + !pretend)) && !is_bare_repository()) { struct ref *ref = remote_refs; struct string_list needs_pushing = STRING_LIST_INIT_DUP; + struct sha1_array commits = SHA1_ARRAY_INIT; for (; ref; ref = ref->next) - if (!is_null_oid(&ref->new_oid) && - find_unpushed_submodules(ref->new_oid.hash, - transport->remote->name, &needs_pushing)) - die_with_unpushed_submodules(&needs_pushing); + if (!is_null_oid(&ref->new_oid)) + sha1_array_append(&commits, ref->new_oid.hash); + + if (find_unpushed_submodules(&commits, transport->remote->name, + &needs_pushing)) { + sha1_array_clear(&commits); + die_with_unpushed_submodules(&needs_pushing); + } + string_list_clear(&needs_pushing, 0); + sha1_array_clear(&commits); } push_ret = transport->push_refs(transport, remote_refs, flags); diff --git a/unicode_width.h b/unicode_width.h index 47cdd2369d..02207be4fc 100644 --- a/unicode_width.h +++ b/unicode_width.h @@ -25,7 +25,7 @@ static const struct interval zero_width[] = { { 0x0825, 0x0827 }, { 0x0829, 0x082D }, { 0x0859, 0x085B }, -{ 0x08E4, 0x0902 }, +{ 0x08D4, 0x0902 }, { 0x093A, 0x093A }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, @@ -120,6 +120,7 @@ static const struct interval zero_width[] = { { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180E }, +{ 0x1885, 0x1886 }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, @@ -158,7 +159,7 @@ static const struct interval zero_width[] = { { 0x1CF4, 0x1CF4 }, { 0x1CF8, 0x1CF9 }, { 0x1DC0, 0x1DF5 }, -{ 0x1DFC, 0x1DFF }, +{ 0x1DFB, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2064 }, @@ -171,13 +172,13 @@ static const struct interval zero_width[] = { { 0x3099, 0x309A }, { 0xA66F, 0xA672 }, { 0xA674, 0xA67D }, -{ 0xA69F, 0xA69F }, +{ 0xA69E, 0xA69F }, { 0xA6F0, 0xA6F1 }, { 0xA802, 0xA802 }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, -{ 0xA8C4, 0xA8C4 }, +{ 0xA8C4, 0xA8C5 }, { 0xA8E0, 0xA8F1 }, { 0xA926, 0xA92D }, { 0xA947, 0xA951 }, @@ -204,7 +205,7 @@ static const struct interval zero_width[] = { { 0xABED, 0xABED }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, -{ 0xFE20, 0xFE2D }, +{ 0xFE20, 0xFE2F }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x101FD, 0x101FD }, @@ -228,16 +229,21 @@ static const struct interval zero_width[] = { { 0x11173, 0x11173 }, { 0x11180, 0x11181 }, { 0x111B6, 0x111BE }, +{ 0x111CA, 0x111CC }, { 0x1122F, 0x11231 }, { 0x11234, 0x11234 }, { 0x11236, 0x11237 }, +{ 0x1123E, 0x1123E }, { 0x112DF, 0x112DF }, { 0x112E3, 0x112EA }, -{ 0x11301, 0x11301 }, +{ 0x11300, 0x11301 }, { 0x1133C, 0x1133C }, { 0x11340, 0x11340 }, { 0x11366, 0x1136C }, { 0x11370, 0x11374 }, +{ 0x11438, 0x1143F }, +{ 0x11442, 0x11444 }, +{ 0x11446, 0x11446 }, { 0x114B3, 0x114B8 }, { 0x114BA, 0x114BA }, { 0x114BF, 0x114C0 }, @@ -245,6 +251,7 @@ static const struct interval zero_width[] = { { 0x115B2, 0x115B5 }, { 0x115BC, 0x115BD }, { 0x115BF, 0x115C0 }, +{ 0x115DC, 0x115DD }, { 0x11633, 0x1163A }, { 0x1163D, 0x1163D }, { 0x1163F, 0x11640 }, @@ -252,6 +259,16 @@ static const struct interval zero_width[] = { { 0x116AD, 0x116AD }, { 0x116B0, 0x116B5 }, { 0x116B7, 0x116B7 }, +{ 0x1171D, 0x1171F }, +{ 0x11722, 0x11725 }, +{ 0x11727, 0x1172B }, +{ 0x11C30, 0x11C36 }, +{ 0x11C38, 0x11C3D }, +{ 0x11C3F, 0x11C3F }, +{ 0x11C92, 0x11CA7 }, +{ 0x11CAA, 0x11CB0 }, +{ 0x11CB2, 0x11CB3 }, +{ 0x11CB5, 0x11CB6 }, { 0x16AF0, 0x16AF4 }, { 0x16B30, 0x16B36 }, { 0x16F8F, 0x16F92 }, @@ -262,31 +279,59 @@ static const struct interval zero_width[] = { { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, { 0x1D242, 0x1D244 }, +{ 0x1DA00, 0x1DA36 }, +{ 0x1DA3B, 0x1DA6C }, +{ 0x1DA75, 0x1DA75 }, +{ 0x1DA84, 0x1DA84 }, +{ 0x1DA9B, 0x1DA9F }, +{ 0x1DAA1, 0x1DAAF }, +{ 0x1E000, 0x1E006 }, +{ 0x1E008, 0x1E018 }, +{ 0x1E01B, 0x1E021 }, +{ 0x1E023, 0x1E024 }, +{ 0x1E026, 0x1E02A }, { 0x1E8D0, 0x1E8D6 }, +{ 0x1E944, 0x1E94A }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF } }; static const struct interval double_width[] = { -{ /* plane */ 0x0, 0x1C }, -{ /* plane */ 0x1C, 0x21 }, -{ /* plane */ 0x21, 0x22 }, -{ /* plane */ 0x22, 0x23 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, -{ /* plane */ 0x0, 0x0 }, { 0x1100, 0x115F }, +{ 0x231A, 0x231B }, { 0x2329, 0x232A }, +{ 0x23E9, 0x23EC }, +{ 0x23F0, 0x23F0 }, +{ 0x23F3, 0x23F3 }, +{ 0x25FD, 0x25FE }, +{ 0x2614, 0x2615 }, +{ 0x2648, 0x2653 }, +{ 0x267F, 0x267F }, +{ 0x2693, 0x2693 }, +{ 0x26A1, 0x26A1 }, +{ 0x26AA, 0x26AB }, +{ 0x26BD, 0x26BE }, +{ 0x26C4, 0x26C5 }, +{ 0x26CE, 0x26CE }, +{ 0x26D4, 0x26D4 }, +{ 0x26EA, 0x26EA }, +{ 0x26F2, 0x26F3 }, +{ 0x26F5, 0x26F5 }, +{ 0x26FA, 0x26FA }, +{ 0x26FD, 0x26FD }, +{ 0x2705, 0x2705 }, +{ 0x270A, 0x270B }, +{ 0x2728, 0x2728 }, +{ 0x274C, 0x274C }, +{ 0x274E, 0x274E }, +{ 0x2753, 0x2755 }, +{ 0x2757, 0x2757 }, +{ 0x2795, 0x2797 }, +{ 0x27B0, 0x27B0 }, +{ 0x27BF, 0x27BF }, +{ 0x2B1B, 0x2B1C }, +{ 0x2B50, 0x2B50 }, +{ 0x2B55, 0x2B55 }, { 0x2E80, 0x2E99 }, { 0x2E9B, 0x2EF3 }, { 0x2F00, 0x2FD5 }, @@ -313,11 +358,49 @@ static const struct interval double_width[] = { { 0xFE68, 0xFE6B }, { 0xFF01, 0xFF60 }, { 0xFFE0, 0xFFE6 }, +{ 0x16FE0, 0x16FE0 }, +{ 0x17000, 0x187EC }, +{ 0x18800, 0x18AF2 }, { 0x1B000, 0x1B001 }, +{ 0x1F004, 0x1F004 }, +{ 0x1F0CF, 0x1F0CF }, +{ 0x1F18E, 0x1F18E }, +{ 0x1F191, 0x1F19A }, { 0x1F200, 0x1F202 }, -{ 0x1F210, 0x1F23A }, +{ 0x1F210, 0x1F23B }, { 0x1F240, 0x1F248 }, { 0x1F250, 0x1F251 }, +{ 0x1F300, 0x1F320 }, +{ 0x1F32D, 0x1F335 }, +{ 0x1F337, 0x1F37C }, +{ 0x1F37E, 0x1F393 }, +{ 0x1F3A0, 0x1F3CA }, +{ 0x1F3CF, 0x1F3D3 }, +{ 0x1F3E0, 0x1F3F0 }, +{ 0x1F3F4, 0x1F3F4 }, +{ 0x1F3F8, 0x1F43E }, +{ 0x1F440, 0x1F440 }, +{ 0x1F442, 0x1F4FC }, +{ 0x1F4FF, 0x1F53D }, +{ 0x1F54B, 0x1F54E }, +{ 0x1F550, 0x1F567 }, +{ 0x1F57A, 0x1F57A }, +{ 0x1F595, 0x1F596 }, +{ 0x1F5A4, 0x1F5A4 }, +{ 0x1F5FB, 0x1F64F }, +{ 0x1F680, 0x1F6C5 }, +{ 0x1F6CC, 0x1F6CC }, +{ 0x1F6D0, 0x1F6D2 }, +{ 0x1F6EB, 0x1F6EC }, +{ 0x1F6F4, 0x1F6F6 }, +{ 0x1F910, 0x1F91E }, +{ 0x1F920, 0x1F927 }, +{ 0x1F930, 0x1F930 }, +{ 0x1F933, 0x1F93E }, +{ 0x1F940, 0x1F94B }, +{ 0x1F950, 0x1F95E }, +{ 0x1F980, 0x1F991 }, +{ 0x1F9C0, 0x1F9C0 }, { 0x20000, 0x2FFFD }, { 0x30000, 0x3FFFD } }; diff --git a/update_unicode.sh b/update_unicode.sh deleted file mode 100755 index 27af77c7df..0000000000 --- a/update_unicode.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh -#See http://www.unicode.org/reports/tr44/ -# -#Me Enclosing_Mark an enclosing combining mark -#Mn Nonspacing_Mark a nonspacing combining mark (zero advance width) -#Cf Format a format control character -# -UNICODEWIDTH_H=../unicode_width.h -if ! test -d unicode; then - mkdir unicode -fi && -( cd unicode && - if ! test -f UnicodeData.txt; then - wget http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt - fi && - if ! test -f EastAsianWidth.txt; then - wget http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt - fi && - if ! test -d uniset; then - git clone https://github.com/depp/uniset.git - fi && - ( - cd uniset && - if ! test -x uniset; then - autoreconf -i && - ./configure --enable-warnings=-Werror CFLAGS='-O0 -ggdb' - fi && - make - ) && - UNICODE_DIR=. && export UNICODE_DIR && - cat >$UNICODEWIDTH_H <<-EOF - static const struct interval zero_width[] = { - $(uniset/uniset --32 cat:Me,Mn,Cf + U+1160..U+11FF - U+00AD | - grep -v plane) - }; - static const struct interval double_width[] = { - $(uniset/uniset --32 eaw:F,W) - }; - EOF -) diff --git a/worktree.c b/worktree.c index f7869f8d60..eb6121263b 100644 --- a/worktree.c +++ b/worktree.c @@ -88,21 +88,13 @@ static struct worktree *get_main_worktree(void) strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); - if (parse_ref(path.buf, &head_ref, &is_detached) < 0) - goto done; - - worktree = xmalloc(sizeof(struct worktree)); + worktree = xcalloc(1, sizeof(*worktree)); worktree->path = strbuf_detach(&worktree_path, NULL); - worktree->id = NULL; worktree->is_bare = is_bare; - worktree->head_ref = NULL; worktree->is_detached = is_detached; - worktree->is_current = 0; - add_head_info(&head_ref, worktree); - worktree->lock_reason = NULL; - worktree->lock_reason_valid = 0; + if (!parse_ref(path.buf, &head_ref, &is_detached)) + add_head_info(&head_ref, worktree); -done: strbuf_release(&path); strbuf_release(&worktree_path); strbuf_release(&head_ref); @@ -138,16 +130,11 @@ static struct worktree *get_linked_worktree(const char *id) if (parse_ref(path.buf, &head_ref, &is_detached) < 0) goto done; - worktree = xmalloc(sizeof(struct worktree)); + worktree = xcalloc(1, sizeof(*worktree)); worktree->path = strbuf_detach(&worktree_path, NULL); worktree->id = xstrdup(id); - worktree->is_bare = 0; - worktree->head_ref = NULL; worktree->is_detached = is_detached; - worktree->is_current = 0; add_head_info(&head_ref, worktree); - worktree->lock_reason = NULL; - worktree->lock_reason_valid = 0; done: strbuf_release(&path); @@ -173,7 +160,14 @@ static void mark_current_worktree(struct worktree **worktrees) free(git_dir); } -struct worktree **get_worktrees(void) +static int compare_worktree(const void *a_, const void *b_) +{ + const struct worktree *const *a = a_; + const struct worktree *const *b = b_; + return fspathcmp((*a)->path, (*b)->path); +} + +struct worktree **get_worktrees(unsigned flags) { struct worktree **list = NULL; struct strbuf path = STRBUF_INIT; @@ -183,8 +177,7 @@ struct worktree **get_worktrees(void) list = xmalloc(alloc * sizeof(struct worktree *)); - if ((list[counter] = get_main_worktree())) - counter++; + list[counter++] = get_main_worktree(); strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); dir = opendir(path.buf); @@ -205,6 +198,13 @@ struct worktree **get_worktrees(void) ALLOC_GROW(list, counter + 1, alloc); list[counter] = NULL; + if (flags & GWT_SORT_LINKED) + /* + * don't sort the first item (main worktree), which will + * always be the first + */ + QSORT(list + 1, counter - 1, compare_worktree); + mark_current_worktree(list); return list; } @@ -341,7 +341,7 @@ const struct worktree *find_shared_symref(const char *symref, if (worktrees) free_worktrees(worktrees); - worktrees = get_worktrees(); + worktrees = get_worktrees(0); for (i = 0; worktrees[i]; i++) { struct worktree *wt = worktrees[i]; diff --git a/worktree.h b/worktree.h index 90e1311fa7..d59ce1fee8 100644 --- a/worktree.h +++ b/worktree.h @@ -15,6 +15,8 @@ struct worktree { /* Functions for acting on the information about worktrees. */ +#define GWT_SORT_LINKED (1 << 0) /* keeps linked worktrees sorted */ + /* * Get the worktrees. The primary worktree will always be the first returned, * and linked worktrees will be pointed to by 'next' in each subsequent @@ -23,7 +25,7 @@ struct worktree { * The caller is responsible for freeing the memory from the returned * worktree(s). */ -extern struct worktree **get_worktrees(void); +extern struct worktree **get_worktrees(unsigned flags); /* * Return git dir of the worktree. Note that the path may be relative. |