From: Junio C Hamano Date: Fri, 1 Jun 2018 06:06:37 +0000 (+0900) Subject: Merge branch 'nd/command-list' X-Git-Tag: v2.18.0-rc1~18 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/2289880f784326dc955f213072164539dcaf445e?ds=inline;hp=-c Merge branch 'nd/command-list' The list of commands with their various attributes were spread across a few places in the build procedure, but it now is getting a bit more consolidated to allow more automation. * nd/command-list: completion: allow to customize the completable command list completion: add and use --list-cmds=alias completion: add and use --list-cmds=nohelpers Move declaration for alias.c to alias.h completion: reduce completable command list completion: let git provide the completable command list command-list.txt: documentation and guide line help: use command-list.txt for the source of guides help: add "-a --verbose" to list all commands with synopsis git: support --list-cmds=list- completion: implement and use --list-cmds=main,others git --list-cmds: collect command list in a string_list git.c: convert --list-* to --list-cmds=* Remove common-cmds.h help: use command-list.h for common command list generate-cmds.sh: export all commands to command-list.h generate-cmds.sh: factor out synopsis extract code --- 2289880f784326dc955f213072164539dcaf445e diff --combined .gitignore index b2a1ae4a1d,0836083992..388cc4beee --- a/.gitignore +++ b/.gitignore @@@ -3,7 -3,6 +3,7 @@@ /GIT-LDFLAGS /GIT-PREFIX /GIT-PERL-DEFINES +/GIT-PERL-HEADER /GIT-PYTHON-VARS /GIT-SCRIPT-DEFINES /GIT-USER-AGENT @@@ -35,7 -34,6 +35,7 @@@ /git-clone /git-column /git-commit +/git-commit-graph /git-commit-tree /git-config /git-count-objects @@@ -142,7 -140,6 +142,7 @@@ /git-rm /git-send-email /git-send-pack +/git-serve /git-sh-i18n /git-sh-i18n--envsubst /git-sh-setup @@@ -182,7 -179,7 +182,7 @@@ /gitweb/gitweb.cgi /gitweb/static/gitweb.js /gitweb/static/gitweb.min.* - /common-cmds.h + /command-list.h *.tar.gz *.dsc *.deb diff --combined Documentation/config.txt index 7d8383433c,9e81dcf867..ab641bf5a9 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@@ -530,12 -530,6 +530,12 @@@ core.autocrlf: This variable can be set to 'input', in which case no output conversion is performed. +core.checkRoundtripEncoding:: + A comma and/or whitespace separated list of encodings that Git + performs UTF-8 round trip checks on if they are used in an + `working-tree-encoding` attribute (see linkgit:gitattributes[5]). + The default value is `SHIFT-JIS`. + core.symlinks:: If false, symbolic links are checked out as small plain files that contain the link text. linkgit:git-update-index[1] and @@@ -904,10 -898,6 +904,10 @@@ core.notesRef: This setting defaults to "refs/notes/commits", and it can be overridden by the `GIT_NOTES_REF` environment variable. See linkgit:git-notes[1]. +core.commitGraph:: + Enable git commit graph feature. Allows reading from the + commit-graph file. + core.sparseCheckout:: Enable "sparse checkout" feature. See section "Sparse checkout" in linkgit:git-read-tree[1] for more information. @@@ -1068,10 -1058,6 +1068,10 @@@ branch..rebase: "git pull" is run. See "pull.rebase" for doing this in a non branch-specific manner. + +When `merges`, pass the `--rebase-merges` option to 'git rebase' +so that the local merge commits are included in the rebase (see +linkgit:git-rebase[1] for details). ++ When preserve, also pass `--preserve-merges` along to 'git rebase' so that locally committed merge commits will not be flattened by running 'git pull'. @@@ -1102,16 -1088,6 +1102,16 @@@ clean.requireForce: A boolean to make git-clean do nothing unless given -f, -i or -n. Defaults to true. +color.advice:: + A boolean to enable/disable color in hints (e.g. when a push + failed, see `advice.*` for a list). May be set to `always`, + `false` (or `never`) or `auto` (or `true`), in which case colors + are used only when the error output goes to a terminal. If + unset, then the value of `color.ui` is used (`auto` by default). + +color.advice.hint:: + Use customized color for hints. + color.branch:: A boolean to enable/disable color in the output of linkgit:git-branch[1]. May be set to `always`, @@@ -1214,15 -1190,6 +1214,15 @@@ color.pager: A boolean to enable/disable colored output when the pager is in use (default is true). +color.push:: + A boolean to enable/disable color in push errors. May be set to + `always`, `false` (or `never`) or `auto` (or `true`), in which + case colors are used only when the error output goes to a terminal. + If unset, then the value of `color.ui` is used (`auto` by default). + +color.push.error:: + Use customized color for push errors. + color.showBranch:: A boolean to enable/disable color in the output of linkgit:git-show-branch[1]. May be set to `always`, @@@ -1251,42 -1218,6 +1251,42 @@@ color.status.: status short-format), or `unmerged` (files which have unmerged changes). +color.blame.repeatedLines:: + Use the customized color for the part of git-blame output that + is repeated meta information per line (such as commit id, + author name, date and timezone). Defaults to cyan. + +color.blame.highlightRecent:: + This can be used to color the metadata of a blame line depending + on age of the line. ++ +This setting should be set to a comma-separated list of color and date settings, +starting and ending with a color, the dates should be set from oldest to newest. +The metadata will be colored given the colors if the the line was introduced +before the given timestamp, overwriting older timestamped colors. ++ +Instead of an absolute timestamp relative timestamps work as well, e.g. +2.weeks.ago is valid to address anything older than 2 weeks. ++ +It defaults to 'blue,12 month ago,white,1 month ago,red', which colors +everything older than one year blue, recent changes between one month and +one year old are kept white, and lines introduced within the last month are +colored red. + +blame.coloring:: + This determines the coloring scheme to be applied to blame + output. It can be 'repeatedLines', 'highlightRecent', + or 'none' which is the default. + +color.transport:: + A boolean to enable/disable color when pushes are rejected. May be + set to `always`, `false` (or `never`) or `auto` (or `true`), in which + case colors are used only when the error output goes to a terminal. + If unset, then the value of `color.ui` is used (`auto` by default). + +color.transport.rejected:: + Use customized color when a push was rejected. + color.ui:: This variable determines the default value for variables such as `color.diff` and `color.grep` that control the use of color @@@ -1412,6 -1343,14 +1412,14 @@@ credential..*: credentialCache.ignoreSIGHUP:: Tell git-credential-cache--daemon to ignore SIGHUP, instead of quitting. + completion.commands:: + This is only used by git-completion.bash to add or remove + commands from the list of completed commands. Normally only + porcelain commands and a few select others are completed. You + can add more commands, separated by space, in this + variable. Prefixing the command with '-' will remove it from + the existing list. + include::diff-config.txt[] difftool..path:: @@@ -1627,18 -1566,6 +1635,18 @@@ gc.autoDetach: Make `git gc --auto` return immediately and run in background if the system supports it. Default is true. +gc.bigPackThreshold:: + If non-zero, all packs larger than this limit are kept when + `git gc` is run. This is very similar to `--keep-base-pack` + except that all packs that meet the threshold are kept, not + just the base pack. Defaults to zero. Common unit suffixes of + 'k', 'm', or 'g' are supported. ++ +Note that if the number of kept packs is more than gc.autoPackLimit, +this configuration variable is ignored, all packs except the base pack +will be repacked. After this the number of packs should go below +gc.autoPackLimit and gc.bigPackThreshold should be respected again. + gc.logExpiry:: If the file gc.log exists, then `git gc --auto` won't run unless that file is more than 'gc.logExpiry' old. Default is @@@ -2503,7 -2430,6 +2511,7 @@@ pack.window: pack.depth:: The maximum delta depth used by linkgit:git-pack-objects[1] when no maximum depth is given on the command line. Defaults to 50. + Maximum value is 4095. pack.windowMemory:: The maximum size of memory that is consumed by each thread @@@ -2540,8 -2466,7 +2548,8 @@@ pack.deltaCacheLimit: The maximum size of a delta, that is cached in linkgit:git-pack-objects[1]. This cache is used to speed up the writing object phase by not having to recompute the final delta - result once the best match for all objects is found. Defaults to 1000. + result once the best match for all objects is found. + Defaults to 1000. Maximum value is 65535. pack.threads:: Specifies the number of threads to spawn when searching for best @@@ -2700,10 -2625,6 +2708,10 @@@ pull.rebase: pull" is run. See "branch..rebase" for setting this on a per-branch basis. + +When `merges`, pass the `--rebase-merges` option to 'git rebase' +so that the local merge commits are included in the rebase (see +linkgit:git-rebase[1] for details). ++ When preserve, also pass `--preserve-merges` along to 'git rebase' so that locally committed merge commits will not be flattened by running 'git pull'. @@@ -3206,18 -3127,6 +3214,18 @@@ status.displayCommentPrefix: behavior of linkgit:git-status[1] in Git 1.8.4 and previous. Defaults to false. +status.renameLimit:: + The number of files to consider when performing rename detection + in linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to + the value of diff.renameLimit. + +status.renames:: + Whether and how Git detects renames in linkgit:git-status[1] and + linkgit:git-commit[1] . If set to "false", rename detection is + disabled. If set to "true", basic rename detection is enabled. + If set to "copies" or "copy", Git will detect copies, as well. + Defaults to the value of diff.renames. + status.showStash:: If set to true, linkgit:git-status[1] will display the number of entries currently stashed away. diff --combined Documentation/git.txt index c662f41c1d,6f7eddf847..dba7f0c18e --- a/Documentation/git.txt +++ b/Documentation/git.txt @@@ -11,7 -11,7 +11,7 @@@ SYNOPSI [verse] 'git' [--version] [--help] [-C ] [-c =] [--exec-path[=]] [--html-path] [--man-path] [--info-path] - [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] + [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare] [--git-dir=] [--work-tree=] [--namespace=] [--super-prefix=] [] @@@ -103,7 -103,6 +103,7 @@@ foo.bar= ...`) sets `foo.bar` to the em configuration options (see the "Configuration Mechanism" section below). +-P:: --no-pager:: Do not pipe Git output into a pager. @@@ -164,6 -163,16 +164,16 @@@ Do not perform optional operations that require locks. This is equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`. + --list-cmds=group[,group...]:: + List commands by group. This is an internal/experimental + option and may change or be removed in the future. Supported + groups are: builtins, parseopt (builtin commands that use + parse-options), main (all commands in libexec directory), + others (all other commands in `$PATH` that have git- prefix), + list- (see categories in command-list.txt), + nohelpers (exclude helper commands), alias and config + (retrieve command list from config variable completion.commands) + GIT COMMANDS ------------ diff --combined Documentation/gitattributes.txt index b72936a885,083c2f380d..92010b062e --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@@ -3,7 -3,7 +3,7 @@@ gitattributes(5 NAME ---- - gitattributes - defining attributes per path + gitattributes - Defining attributes per path SYNOPSIS -------- @@@ -279,94 -279,6 +279,94 @@@ few exceptions. Even though.. catch potential problems early, safety triggers. +`working-tree-encoding` +^^^^^^^^^^^^^^^^^^^^^^^ + +Git recognizes files encoded in ASCII or one of its supersets (e.g. +UTF-8, ISO-8859-1, ...) as text files. Files encoded in certain other +encodings (e.g. UTF-16) are interpreted as binary and consequently +built-in Git text processing tools (e.g. 'git diff') as well as most Git +web front ends do not visualize the contents of these files by default. + +In these cases you can tell Git the encoding of a file in the working +directory with the `working-tree-encoding` attribute. If a file with this +attribute is added to Git, then Git reencodes the content from the +specified encoding to UTF-8. Finally, Git stores the UTF-8 encoded +content in its internal data structure (called "the index"). On checkout +the content is reencoded back to the specified encoding. + +Please note that using the `working-tree-encoding` attribute may have a +number of pitfalls: + +- Alternative Git implementations (e.g. JGit or libgit2) and older Git + versions (as of March 2018) do not support the `working-tree-encoding` + attribute. If you decide to use the `working-tree-encoding` attribute + in your repository, then it is strongly recommended to ensure that all + clients working with the repository support it. + + For example, Microsoft Visual Studio resources files (`*.rc`) or + PowerShell script files (`*.ps1`) are sometimes encoded in UTF-16. + If you declare `*.ps1` as files as UTF-16 and you add `foo.ps1` with + a `working-tree-encoding` enabled Git client, then `foo.ps1` will be + stored as UTF-8 internally. A client without `working-tree-encoding` + support will checkout `foo.ps1` as UTF-8 encoded file. This will + typically cause trouble for the users of this file. + + If a Git client, that does not support the `working-tree-encoding` + attribute, adds a new file `bar.ps1`, then `bar.ps1` will be + stored "as-is" internally (in this example probably as UTF-16). + A client with `working-tree-encoding` support will interpret the + internal contents as UTF-8 and try to convert it to UTF-16 on checkout. + That operation will fail and cause an error. + +- Reencoding content to non-UTF encodings can cause errors as the + conversion might not be UTF-8 round trip safe. If you suspect your + encoding to not be round trip safe, then add it to + `core.checkRoundtripEncoding` to make Git check the round trip + encoding (see linkgit:git-config[1]). SHIFT-JIS (Japanese character + set) is known to have round trip issues with UTF-8 and is checked by + default. + +- Reencoding content requires resources that might slow down certain + Git operations (e.g 'git checkout' or 'git add'). + +Use the `working-tree-encoding` attribute only if you cannot store a file +in UTF-8 encoding and if you want Git to be able to process the content +as text. + +As an example, use the following attributes if your '*.ps1' files are +UTF-16 encoded with byte order mark (BOM) and you want Git to perform +automatic line ending conversion based on your platform. + +------------------------ +*.ps1 text working-tree-encoding=UTF-16 +------------------------ + +Use the following attributes if your '*.ps1' files are UTF-16 little +endian encoded without BOM and you want Git to use Windows line endings +in the working directory. Please note, it is highly recommended to +explicitly define the line endings with `eol` if the `working-tree-encoding` +attribute is used to avoid ambiguity. + +------------------------ +*.ps1 text working-tree-encoding=UTF-16LE eol=CRLF +------------------------ + +You can get a list of all available encodings on your platform with the +following command: + +------------------------ +iconv --list +------------------------ + +If you do not know the encoding of a file, then you can use the `file` +command to guess the encoding: + +------------------------ +file foo.ps1 +------------------------ + + `ident` ^^^^^^^ @@@ -1229,8 -1141,8 +1229,8 @@@ to ------------ -EXAMPLE -------- +EXAMPLES +-------- If you have these three `gitattributes` file: diff --combined Makefile index 4bca65383a,1efb751e46..1d27f36365 --- a/Makefile +++ b/Makefile @@@ -441,49 -441,6 +441,49 @@@ all: # # When cross-compiling, define HOST_CPU as the canonical name of the CPU on # which the built Git will run (for instance "x86_64"). +# +# Define RUNTIME_PREFIX to configure Git to resolve its ancillary tooling and +# support files relative to the location of the runtime binary, rather than +# hard-coding them into the binary. Git installations built with RUNTIME_PREFIX +# can be moved to arbitrary filesystem locations. RUNTIME_PREFIX also causes +# Perl scripts to use a modified entry point header allowing them to resolve +# support files at runtime. +# +# When using RUNTIME_PREFIX, define HAVE_BSD_KERN_PROC_SYSCTL if your platform +# supports the KERN_PROC BSD sysctl function. +# +# When using RUNTIME_PREFIX, define PROCFS_EXECUTABLE_PATH if your platform +# mounts a "procfs" filesystem capable of resolving the path of the current +# executable. If defined, this must be the canonical path for the "procfs" +# current executable path. +# +# When using RUNTIME_PREFIX, define HAVE_NS_GET_EXECUTABLE_PATH if your platform +# supports calling _NSGetExecutablePath to retrieve the path of the running +# executable. +# +# When using RUNTIME_PREFIX, define HAVE_WPGMPTR if your platform offers +# the global variable _wpgmptr containing the absolute path of the current +# executable (this is the case on Windows). +# +# Define DEVELOPER to enable more compiler warnings. Compiler version +# and family are auto detected, but could be overridden by defining +# COMPILER_FEATURES (see config.mak.dev) +# +# When DEVELOPER is set, DEVOPTS can be used to control compiler +# options. This variable contains keywords separated by +# whitespace. The following keywords are are recognized: +# +# no-error: +# +# suppresses the -Werror that implicitly comes with +# DEVELOPER=1. Useful for getting the full set of errors +# without immediately dying, or for logging them. +# +# extra-all: +# +# The DEVELOPER mode enables -Wextra with a few exceptions. By +# setting this flag the exceptions are removed, and all of +# -Wextra is used. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@@ -492,6 -449,15 +492,6 @@@ # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall -DEVELOPER_CFLAGS = -Werror \ - -Wdeclaration-after-statement \ - -Wno-format-zero-length \ - -Wold-style-definition \ - -Woverflow \ - -Wpointer-arith \ - -Wstrict-prototypes \ - -Wunused \ - -Wvla LDFLAGS = ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) @@@ -512,8 -478,6 +512,8 @@@ ARFLAGS = rc # mandir # infodir # htmldir +# localedir +# perllibdir # This can help installing the suite in a relocatable way. prefix = $(HOME) @@@ -538,9 -502,7 +538,9 @@@ bindir_relative = $(patsubst $(prefix)/ mandir_relative = $(patsubst $(prefix)/%,%,$(mandir)) infodir_relative = $(patsubst $(prefix)/%,%,$(infodir)) gitexecdir_relative = $(patsubst $(prefix)/%,%,$(gitexecdir)) +localedir_relative = $(patsubst $(prefix)/%,%,$(localedir)) htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir)) +perllibdir_relative = $(patsubst $(prefix)/%,%,$(perllibdir)) export prefix bindir sharedir sysconfdir gitwebdir perllibdir localedir @@@ -690,6 -652,7 +690,6 @@@ PROGRAM_OBJS += imap-send. PROGRAM_OBJS += sh-i18n--envsubst.o PROGRAM_OBJS += shell.o PROGRAM_OBJS += show-index.o -PROGRAM_OBJS += upload-pack.o PROGRAM_OBJS += remote-testsvn.o # Binary suffix, set to .exe for Windows builds @@@ -738,7 -701,6 +738,7 @@@ TEST_PROGRAMS_NEED_X += test-dump-untra TEST_PROGRAMS_NEED_X += test-fake-ssh TEST_PROGRAMS_NEED_X += test-line-buffer TEST_PROGRAMS_NEED_X += test-parse-options +TEST_PROGRAMS_NEED_X += test-pkt-line TEST_PROGRAMS_NEED_X += test-svn-fe TEST_PROGRAMS_NEED_X += test-tool @@@ -795,7 -757,7 +795,7 @@@ LIB_FILE = libgit. XDIFF_LIB = xdiff/lib.a VCSSVN_LIB = vcs-svn/lib.a - GENERATED_H += common-cmds.h + GENERATED_H += command-list.h LIB_H = $(shell $(FIND) . \ -name .git -prune -o \ @@@ -821,13 -783,11 +821,13 @@@ LIB_OBJS += branch. LIB_OBJS += bulk-checkin.o LIB_OBJS += bundle.o LIB_OBJS += cache-tree.o +LIB_OBJS += chdir-notify.o LIB_OBJS += checkout.o LIB_OBJS += color.o LIB_OBJS += column.o LIB_OBJS += combine-diff.o LIB_OBJS += commit.o +LIB_OBJS += commit-graph.o LIB_OBJS += compat/obstack.o LIB_OBJS += compat/terminal.o LIB_OBJS += config.o @@@ -858,7 -818,7 +858,7 @@@ LIB_OBJS += ewah/bitmap. LIB_OBJS += ewah/ewah_bitmap.o LIB_OBJS += ewah/ewah_io.o LIB_OBJS += ewah/ewah_rlw.o -LIB_OBJS += exec_cmd.o +LIB_OBJS += exec-cmd.o LIB_OBJS += fetch-object.o LIB_OBJS += fetch-pack.o LIB_OBJS += fsck.o @@@ -881,11 -841,9 +881,11 @@@ LIB_OBJS += list-objects-filter-options LIB_OBJS += ll-merge.o LIB_OBJS += lockfile.o LIB_OBJS += log-tree.o +LIB_OBJS += ls-refs.o LIB_OBJS += mailinfo.o LIB_OBJS += mailmap.o LIB_OBJS += match-trees.o +LIB_OBJS += mem-pool.o LIB_OBJS += merge.o LIB_OBJS += merge-blobs.o LIB_OBJS += merge-recursive.o @@@ -928,10 -886,9 +928,10 @@@ LIB_OBJS += refs/files-backend. LIB_OBJS += refs/iterator.o LIB_OBJS += refs/packed-backend.o LIB_OBJS += refs/ref-cache.o +LIB_OBJS += refspec.o LIB_OBJS += ref-filter.o LIB_OBJS += remote.o -LIB_OBJS += replace_object.o +LIB_OBJS += replace-object.o LIB_OBJS += repository.o LIB_OBJS += rerere.o LIB_OBJS += resolve-undo.o @@@ -939,13 -896,12 +939,13 @@@ LIB_OBJS += revision. LIB_OBJS += run-command.o LIB_OBJS += send-pack.o LIB_OBJS += sequencer.o +LIB_OBJS += serve.o LIB_OBJS += server-info.o LIB_OBJS += setup.o LIB_OBJS += sha1-array.o LIB_OBJS += sha1-lookup.o -LIB_OBJS += sha1_file.o -LIB_OBJS += sha1_name.o +LIB_OBJS += sha1-file.o +LIB_OBJS += sha1-name.o LIB_OBJS += shallow.o LIB_OBJS += sideband.o LIB_OBJS += sigchain.o @@@ -968,7 -924,6 +968,7 @@@ LIB_OBJS += tree-diff. LIB_OBJS += tree.o LIB_OBJS += tree-walk.o LIB_OBJS += unpack-trees.o +LIB_OBJS += upload-pack.o LIB_OBJS += url.o LIB_OBJS += urlmatch.o LIB_OBJS += usage.o @@@ -981,7 -936,7 +981,7 @@@ LIB_OBJS += walker. LIB_OBJS += wildmatch.o LIB_OBJS += worktree.o LIB_OBJS += wrapper.o -LIB_OBJS += write_or_die.o +LIB_OBJS += write-or-die.o LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o @@@ -1008,7 -963,6 +1008,7 @@@ BUILTIN_OBJS += builtin/clone. BUILTIN_OBJS += builtin/column.o BUILTIN_OBJS += builtin/commit-tree.o BUILTIN_OBJS += builtin/commit.o +BUILTIN_OBJS += builtin/commit-graph.o BUILTIN_OBJS += builtin/config.o BUILTIN_OBJS += builtin/count-objects.o BUILTIN_OBJS += builtin/credential.o @@@ -1074,7 -1028,6 +1074,7 @@@ BUILTIN_OBJS += builtin/rev-parse. BUILTIN_OBJS += builtin/revert.o BUILTIN_OBJS += builtin/rm.o BUILTIN_OBJS += builtin/send-pack.o +BUILTIN_OBJS += builtin/serve.o BUILTIN_OBJS += builtin/shortlog.o BUILTIN_OBJS += builtin/show-branch.o BUILTIN_OBJS += builtin/show-ref.o @@@ -1088,7 -1041,6 +1088,7 @@@ BUILTIN_OBJS += builtin/update-index. BUILTIN_OBJS += builtin/update-ref.o BUILTIN_OBJS += builtin/update-server-info.o BUILTIN_OBJS += builtin/upload-archive.o +BUILTIN_OBJS += builtin/upload-pack.o BUILTIN_OBJS += builtin/var.o BUILTIN_OBJS += builtin/verify-commit.o BUILTIN_OBJS += builtin/verify-pack.o @@@ -1110,7 -1062,7 +1110,7 @@@ include config.mak.unam -include config.mak ifdef DEVELOPER -CFLAGS += $(DEVELOPER_CFLAGS) +include config.mak.dev endif comma := , @@@ -1711,27 -1663,10 +1711,27 @@@ ifdef HAVE_BSD_SYSCT BASIC_CFLAGS += -DHAVE_BSD_SYSCTL endif +ifdef HAVE_BSD_KERN_PROC_SYSCTL + BASIC_CFLAGS += -DHAVE_BSD_KERN_PROC_SYSCTL +endif + ifdef HAVE_GETDELIM BASIC_CFLAGS += -DHAVE_GETDELIM endif +ifneq ($(PROCFS_EXECUTABLE_PATH),) + procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH)) + BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"' +endif + +ifdef HAVE_NS_GET_EXECUTABLE_PATH + BASIC_CFLAGS += -DHAVE_NS_GET_EXECUTABLE_PATH +endif + +ifdef HAVE_WPGMPTR + BASIC_CFLAGS += -DHAVE_WPGMPTR +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK = NoThanks endif @@@ -1816,13 -1751,11 +1816,13 @@@ mandir_relative_SQ = $(subst ','\'',$(m infodir_relative_SQ = $(subst ','\'',$(infodir_relative)) perllibdir_SQ = $(subst ','\'',$(perllibdir)) localedir_SQ = $(subst ','\'',$(localedir)) +localedir_relative_SQ = $(subst ','\'',$(localedir_relative)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) gitexecdir_relative_SQ = $(subst ','\'',$(gitexecdir_relative)) template_dir_SQ = $(subst ','\'',$(template_dir)) htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative)) prefix_SQ = $(subst ','\'',$(prefix)) +perllibdir_relative_SQ = $(subst ','\'',$(perllibdir_relative)) gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) @@@ -1833,31 -1766,6 +1833,31 @@@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_ DIFF_SQ = $(subst ','\'',$(DIFF)) PERLLIB_EXTRA_SQ = $(subst ','\'',$(PERLLIB_EXTRA)) +# RUNTIME_PREFIX's resolution logic requires resource paths to be expressed +# relative to each other and share an installation path. +# +# This is a dependency in: +# - Git's binary RUNTIME_PREFIX logic in (see "exec_cmd.c"). +# - The runtime prefix Perl header (see +# "perl/header_templates/runtime_prefix.template.pl"). +ifdef RUNTIME_PREFIX + +ifneq ($(filter /%,$(firstword $(gitexecdir_relative))),) +$(error RUNTIME_PREFIX requires a relative gitexecdir, not: $(gitexecdir)) +endif + +ifneq ($(filter /%,$(firstword $(localedir_relative))),) +$(error RUNTIME_PREFIX requires a relative localedir, not: $(localedir)) +endif + +ifndef NO_PERL +ifneq ($(filter /%,$(firstword $(perllibdir_relative))),) +$(error RUNTIME_PREFIX requires a relative perllibdir, not: $(perllibdir)) +endif +endif + +endif + # We must filter out any object files from $(GITLIBS), # as it is typically used like: # @@@ -2006,9 -1914,9 +2006,9 @@@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ $(filter %.o,$^) $(LIBS) - help.sp help.s help.o: common-cmds.h + help.sp help.s help.o: command-list.h - builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX + builtin/help.sp builtin/help.s builtin/help.o: command-list.h GIT-PREFIX builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \ @@@ -2027,9 -1935,9 +2027,9 @@@ $(BUILT_INS): git$ ln -s $< $@ 2>/dev/null || \ cp $< $@ - common-cmds.h: generate-cmdlist.sh command-list.txt + command-list.h: generate-cmdlist.sh command-list.txt - common-cmds.h: $(wildcard Documentation/git-*.txt) + command-list.h: $(wildcard Documentation/git*.txt) $(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh command-list.txt >$@+ && mv $@+ $@ SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\ @@@ -2078,44 -1986,27 +2078,44 @@@ git.res: git.rc GIT-VERSION-FIL # This makes sure we depend on the NO_PERL setting itself. $(SCRIPT_PERL_GEN): GIT-BUILD-OPTIONS -ifndef NO_PERL -$(SCRIPT_PERL_GEN): +# Used for substitution in Perl modules. Disabled when using RUNTIME_PREFIX +# since the locale directory is injected. +perl_localedir_SQ = $(localedir_SQ) +ifndef NO_PERL +PERL_HEADER_TEMPLATE = perl/header_templates/fixed_prefix.template.pl PERL_DEFINES = $(PERL_PATH_SQ):$(PERLLIB_EXTRA_SQ):$(perllibdir_SQ) -$(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-VERSION-FILE + +PERL_DEFINES := $(PERL_PATH_SQ) $(PERLLIB_EXTRA_SQ) $(perllibdir_SQ) +PERL_DEFINES += $(RUNTIME_PREFIX) + +# Support Perl runtime prefix. In this mode, a different header is installed +# into Perl scripts. +ifdef RUNTIME_PREFIX + +PERL_HEADER_TEMPLATE = perl/header_templates/runtime_prefix.template.pl + +# Don't export a fixed $(localedir) path; it will be resolved by the Perl header +# at runtime. +perl_localedir_SQ = + +endif + +PERL_DEFINES += $(gitexecdir) $(perllibdir) $(localedir) + +$(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-PERL-HEADER GIT-VERSION-FILE $(QUIET_GEN)$(RM) $@ $@+ && \ - INSTLIBDIR='$(perllibdir_SQ)' && \ - INSTLIBDIR_EXTRA='$(PERLLIB_EXTRA_SQ)' && \ - INSTLIBDIR="$$INSTLIBDIR$${INSTLIBDIR_EXTRA:+:$$INSTLIBDIR_EXTRA}" && \ sed -e '1{' \ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ - -e ' h' \ - -e ' s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "'"$$INSTLIBDIR"'"));=' \ - -e ' H' \ - -e ' x' \ + -e ' rGIT-PERL-HEADER' \ + -e ' G' \ -e '}' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ +PERL_DEFINES := $(subst $(space),:,$(PERL_DEFINES)) GIT-PERL-DEFINES: FORCE @FLAGS='$(PERL_DEFINES)'; \ if test x"$$FLAGS" != x"`cat $@ 2>/dev/null`" ; then \ @@@ -2123,22 -2014,6 +2123,22 @@@ echo "$$FLAGS" >$@; \ fi +GIT-PERL-HEADER: $(PERL_HEADER_TEMPLATE) GIT-PERL-DEFINES Makefile + $(QUIET_GEN)$(RM) $@ && \ + INSTLIBDIR='$(perllibdir_SQ)' && \ + INSTLIBDIR_EXTRA='$(PERLLIB_EXTRA_SQ)' && \ + INSTLIBDIR="$$INSTLIBDIR$${INSTLIBDIR_EXTRA:+:$$INSTLIBDIR_EXTRA}" && \ + sed -e 's=@@PATHSEP@@=$(pathsep)=g' \ + -e "s=@@INSTLIBDIR@@=$$INSTLIBDIR=g" \ + -e 's=@@PERLLIBDIR_REL@@=$(perllibdir_relative_SQ)=g' \ + -e 's=@@GITEXECDIR_REL@@=$(gitexecdir_relative_SQ)=g' \ + -e 's=@@LOCALEDIR_REL@@=$(localedir_relative_SQ)=g' \ + $< >$@+ && \ + mv $@+ $@ + +.PHONY: perllibdir +perllibdir: + @echo '$(perllibdir_SQ)' .PHONY: gitweb gitweb: @@@ -2273,19 -2148,18 +2273,19 @@@ els # Dependencies on header files, for platforms that do not support # the gcc -MMD option. # - # Dependencies on automatically generated headers such as common-cmds.h + # Dependencies on automatically generated headers such as command-list.h # should _not_ be included here, since they are necessary even when # building an object for the first time. $(OBJECTS): $(LIB_H) endif -exec_cmd.sp exec_cmd.s exec_cmd.o: GIT-PREFIX -exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ +exec-cmd.sp exec-cmd.s exec-cmd.o: GIT-PREFIX +exec-cmd.sp exec-cmd.s exec-cmd.o: EXTRA_CPPFLAGS = \ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ + '-DGIT_LOCALE_PATH="$(localedir_relative_SQ)"' \ '-DBINDIR="$(bindir_relative_SQ)"' \ - '-DPREFIX="$(prefix_SQ)"' + '-DFALLBACK_RUNTIME_PREFIX="$(prefix_SQ)"' builtin/init-db.sp builtin/init-db.s builtin/init-db.o: GIT-PREFIX builtin/init-db.sp builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \ @@@ -2301,7 -2175,7 +2301,7 @@@ attr.sp attr.s attr.o: EXTRA_CPPFLAGS gettext.sp gettext.s gettext.o: GIT-PREFIX gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \ - -DGIT_LOCALE_PATH='"$(localedir_SQ)"' + -DGIT_LOCALE_PATH='"$(localedir_relative_SQ)"' http-push.sp http.sp http-walker.sp remote-curl.sp imap-send.sp: SPARSE_FLAGS += \ -DCURL_DISABLE_TYPECHECK @@@ -2461,7 -2335,7 +2461,7 @@@ endi perl/build/lib/%.pm: perl/%.pm $(QUIET_GEN)mkdir -p $(dir $@) && \ - sed -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \ + sed -e 's|@@LOCALEDIR@@|$(perl_localedir_SQ)|g' \ -e 's|@@NO_PERL_CPAN_FALLBACKS@@|$(NO_PERL_CPAN_FALLBACKS_SQ)|g' \ < $< > $@ @@@ -2653,7 -2527,7 +2653,7 @@@ sparse: $(SP_OBJ style: git clang-format --style file --diff --extensions c,h - check: common-cmds.h + check: command-list.h @if sparse; \ then \ echo >&2 "Use 'make sparse' instead"; \ @@@ -2901,7 -2775,7 +2901,7 @@@ clean: profile-clean coverage-clea $(RM) $(TEST_PROGRAMS) $(NO_INSTALL) $(RM) -r bin-wrappers $(dep_dirs) $(RM) -r po/build/ - $(RM) *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope* + $(RM) *.pyc *.pyo */*.pyc */*.pyo command-list.h $(ETAGS_TARGET) tags cscope* $(RM) -r $(GIT_TARNAME) .doc-tmp-dir $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz $(RM) $(htmldocs).tar.gz $(manpages).tar.gz @@@ -2919,7 -2793,7 +2919,7 @@@ ifndef NO_TCLT endif $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-BUILD-OPTIONS $(RM) GIT-USER-AGENT GIT-PREFIX - $(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PYTHON-VARS + $(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS .PHONY: all install profile-clean clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell diff --combined builtin/help.c index 2d51071429,6b4b3df90d..58e0a5507f --- a/builtin/help.c +++ b/builtin/help.c @@@ -4,11 -4,12 +4,12 @@@ #include "cache.h" #include "config.h" #include "builtin.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "parse-options.h" #include "run-command.h" #include "column.h" #include "help.h" + #include "alias.h" #ifndef DEFAULT_HELP_FORMAT #define DEFAULT_HELP_FORMAT "man" @@@ -36,6 -37,7 +37,7 @@@ static const char *html_path static int show_all = 0; static int show_guides = 0; + static int verbose; static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; static int exclude_guides; @@@ -48,6 -50,7 +50,7 @@@ static struct option builtin_help_optio HELP_FORMAT_WEB), OPT_SET_INT('i', "info", &help_format, N_("show info page"), HELP_FORMAT_INFO), + OPT__VERBOSE(&verbose, N_("print command description")), OPT_END(), }; @@@ -400,38 -403,6 +403,6 @@@ static void show_html_page(const char * open_html(page_path.buf); } - static struct { - const char *name; - const char *help; - } common_guides[] = { - { "attributes", N_("Defining attributes per path") }, - { "everyday", N_("Everyday Git With 20 Commands Or So") }, - { "glossary", N_("A Git glossary") }, - { "ignore", N_("Specifies intentionally untracked files to ignore") }, - { "modules", N_("Defining submodule properties") }, - { "revisions", N_("Specifying revisions and ranges for Git") }, - { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") }, - { "workflows", N_("An overview of recommended workflows with Git") }, - }; - - static void list_common_guides_help(void) - { - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_guides); i++) { - if (longest < strlen(common_guides[i].name)) - longest = strlen(common_guides[i].name); - } - - puts(_("The common Git guides are:\n")); - for (i = 0; i < ARRAY_SIZE(common_guides); i++) { - printf(" %s ", common_guides[i].name); - mput_char(' ', longest - strlen(common_guides[i].name)); - puts(_(common_guides[i].help)); - } - putchar('\n'); - } - static const char *check_git_cmd(const char* cmd) { char *alias; @@@ -463,6 -434,11 +434,11 @@@ int cmd_help(int argc, const char **arg if (show_all) { git_config(git_help_config, NULL); + if (verbose) { + setup_pager(); + list_all_cmds_help(); + return 0; + } printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); load_command_list("git-", &main_cmds, &other_cmds); list_commands(colopts, &main_cmds, &other_cmds); diff --combined builtin/merge.c index d85f99b781,e3681cd850..6d7bbe8c9f --- a/builtin/merge.c +++ b/builtin/merge.c @@@ -14,7 -14,6 +14,7 @@@ #include "run-command.h" #include "diff.h" #include "refs.h" +#include "refspec.h" #include "commit.h" #include "diffcore.h" #include "revision.h" @@@ -35,6 -34,7 +35,7 @@@ #include "string-list.h" #include "packfile.h" #include "tag.h" + #include "alias.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@@ -281,7 -281,7 +282,7 @@@ out return rc; } -static void read_empty(unsigned const char *sha1, int verbose) +static void read_empty(const struct object_id *oid, int verbose) { int i = 0; const char *args[7]; @@@ -291,15 -291,15 +292,15 @@@ args[i++] = "-v"; args[i++] = "-m"; args[i++] = "-u"; - args[i++] = EMPTY_TREE_SHA1_HEX; - args[i++] = sha1_to_hex(sha1); + args[i++] = empty_tree_oid_hex(); + args[i++] = oid_to_hex(oid); args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) die(_("read-tree failed")); } -static void reset_hard(unsigned const char *sha1, int verbose) +static void reset_hard(const struct object_id *oid, int verbose) { int i = 0; const char *args[6]; @@@ -309,7 -309,7 +310,7 @@@ args[i++] = "-v"; args[i++] = "--reset"; args[i++] = "-u"; - args[i++] = sha1_to_hex(sha1); + args[i++] = oid_to_hex(oid); args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) @@@ -325,7 -325,7 +326,7 @@@ static void restore_state(const struct if (is_null_oid(stash)) return; - reset_hard(head->hash, 1); + reset_hard(head, 1); args[2] = oid_to_hex(stash); @@@ -648,7 -648,7 +649,7 @@@ static int try_merge_strategy(const cha struct commit_list *remoteheads, struct commit *head) { - static struct lock_file lock; + struct lock_file lock = LOCK_INIT; const char *head_arg = "HEAD"; hold_locked_index(&lock, LOCK_DIE_ON_ERROR); @@@ -806,7 -806,7 +807,7 @@@ static int merge_trivial(struct commit { struct object_id result_tree, result_commit; struct commit_list *parents, **pptr = &parents; - static struct lock_file lock; + struct lock_file lock = LOCK_INIT; hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); @@@ -1298,7 -1298,7 +1299,7 @@@ int cmd_merge(int argc, const char **ar if (remoteheads->next) die(_("Can merge only exactly one commit into empty head")); remote_head_oid = &remoteheads->item->object.oid; - read_empty(remote_head_oid->hash, 0); + read_empty(remote_head_oid, 0); update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; diff --combined cache.h index 08a48263b5,111116ea13..89a107a7f7 --- a/cache.h +++ b/cache.h @@@ -324,7 -324,7 +324,7 @@@ struct index_state drop_cache_tree : 1; struct hashmap name_hash; struct hashmap dir_hash; - unsigned char sha1[20]; + struct object_id oid; struct untracked_cache *untracked; uint64_t fsmonitor_last_update; struct ewah_bitmap *fsmonitor_dirty; @@@ -373,13 -373,6 +373,13 @@@ extern void free_name_hash(struct index #define read_blob_data_from_cache(path, sz) read_blob_data_from_index(&the_index, (path), (sz)) #endif +#define TYPE_BITS 3 + +/* + * Values in this enum (except those outside the 3 bit range) are part + * of pack file format. See Documentation/technical/pack-format.txt + * for more information. + */ enum object_type { OBJ_BAD = -1, OBJ_NONE = 0, @@@ -435,7 -428,6 +435,7 @@@ static inline enum object_type object_t #define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS" #define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH" #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS" +#define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR" /* * Environment variable used in handshaking the wire protocol. @@@ -485,7 -477,7 +485,7 @@@ extern const char *get_git_common_dir(v extern char *get_object_directory(void); extern char *get_index_file(void); extern char *get_graft_file(void); -extern int set_git_dir(const char *path); +extern void set_git_dir(const char *path); extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir); extern int get_common_dir(struct strbuf *sb, const char *gitdir); extern const char *get_git_namespace(void); @@@ -642,7 -634,7 +642,7 @@@ extern int unmerged_index(const struct */ extern int index_has_changes(struct strbuf *sb); -extern int verify_path(const char *path); +extern int verify_path(const char *path, unsigned mode); extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change); extern int index_dir_exists(struct index_state *istate, const char *name, int namelen); extern void adjust_dirname_case(struct index_state *istate, char *name); @@@ -813,7 -805,6 +813,7 @@@ extern char *git_replace_ref_base extern int fsync_object_files; extern int core_preload_index; +extern int core_commit_graph; extern int core_apply_sparse_checkout; extern int precomposed_unicode; extern int protect_hfs; @@@ -1017,10 -1008,21 +1017,10 @@@ static inline void oidclr(struct object memset(oid->hash, 0, GIT_MAX_RAWSZ); } - -#define EMPTY_TREE_SHA1_HEX \ - "4b825dc642cb6eb9a060e54bf8d69288fbee4904" -#define EMPTY_TREE_SHA1_BIN_LITERAL \ - "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \ - "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04" -extern const struct object_id empty_tree_oid; -#define EMPTY_TREE_SHA1_BIN (empty_tree_oid.hash) - -#define EMPTY_BLOB_SHA1_HEX \ - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" -#define EMPTY_BLOB_SHA1_BIN_LITERAL \ - "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \ - "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91" -extern const struct object_id empty_blob_oid; +static inline void oidread(struct object_id *oid, const unsigned char *hash) +{ + memcpy(oid->hash, hash, the_hash_algo->rawsz); +} static inline int is_empty_blob_sha1(const unsigned char *sha1) { @@@ -1042,9 -1044,6 +1042,9 @@@ static inline int is_empty_tree_oid(con return !oidcmp(oid, the_hash_algo->empty_tree); } +const char *empty_tree_oid_hex(void); +const char *empty_blob_oid_hex(void); + /* set default permissions by passing mode arguments to open(2) */ int git_mkstemps_mode(char *pattern, int suffix_len, int mode); int git_mkstemp_mode(char *pattern, int mode); @@@ -1160,15 -1159,7 +1160,15 @@@ int normalize_path_copy(char *dst, cons int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); int daemon_avoid_alias(const char *path); -extern int is_ntfs_dotgit(const char *name); + +/* + * These functions match their is_hfs_dotgit() counterparts; see utf8.h for + * details. + */ +int is_ntfs_dotgit(const char *name); +int is_ntfs_dotgitmodules(const char *name); +int is_ntfs_dotgitignore(const char *name); +int is_ntfs_dotgitattributes(const char *name); /* * Returns true iff "str" could be confused as a command-line option when @@@ -1200,8 -1191,27 +1200,8 @@@ static inline void *read_object_file(co return read_object_file_extended(oid, type, size, 1); } -/* - * This internal function is only declared here for the benefit of - * lookup_replace_object(). Please do not call it directly. - */ -extern const struct object_id *do_lookup_replace_object(const struct object_id *oid); - -/* - * If object sha1 should be replaced, return the replacement object's - * name (replaced recursively, if necessary). The return value is - * either sha1 or a pointer to a permanently-allocated value. When - * object replacement is suppressed, always return sha1. - */ -static inline const struct object_id *lookup_replace_object(const struct object_id *oid) -{ - if (!check_replace_refs) - return oid; - return do_lookup_replace_object(oid); -} - /* Read and unpack an object file into memory, write memory to an object file */ -extern int oid_object_info(const struct object_id *, unsigned long *); +int oid_object_info(struct repository *r, const struct object_id *, unsigned long *); extern int hash_object_file(const void *buf, unsigned long len, const char *type, struct object_id *oid); @@@ -1260,7 -1270,7 +1260,7 @@@ extern int has_object_file_with_flags(c * with the specified name. This function does not respect replace * references. */ -extern int has_loose_object_nonlocal(const unsigned char *sha1); +extern int has_loose_object_nonlocal(const struct object_id *oid); extern void assert_oid_type(const struct object_id *oid, enum object_type expect); @@@ -1291,6 -1301,7 +1291,6 @@@ static inline int hex2chr(const char *s #define FALLBACK_DEFAULT_ABBREV 7 struct object_context { - unsigned char tree[20]; unsigned mode; /* * symlink_path is only used by get_tree_entry_follow_symlinks, @@@ -1557,6 -1568,7 +1557,6 @@@ struct pack_window struct pack_entry { off_t offset; - unsigned char sha1[20]; struct packed_git *p; }; @@@ -1680,10 -1692,7 +1680,10 @@@ struct object_info #define OBJECT_INFO_QUICK 8 /* Do not check loose object */ #define OBJECT_INFO_IGNORE_LOOSE 16 -extern int oid_object_info_extended(const struct object_id *, struct object_info *, unsigned flags); + +int oid_object_info_extended(struct repository *r, + const struct object_id *, + struct object_info *, unsigned flags); /* * Set this to 0 to prevent sha1_object_info_extended() from fetching missing @@@ -1826,11 -1835,6 +1826,6 @@@ extern int ws_blank_line(const char *li void overlay_tree_on_index(struct index_state *istate, const char *tree_name, const char *prefix); - char *alias_lookup(const char *alias); - int split_cmdline(char *cmdline, const char ***argv); - /* Takes a negative value returned by split_cmdline */ - const char *split_cmdline_strerror(int cmdline_errno); - /* setup.c */ struct startup_info { int have_repository; diff --combined command-list.txt index 835c5890be,e505a1e34c..e1c26c1bb7 --- a/command-list.txt +++ b/command-list.txt @@@ -1,23 -1,58 +1,58 @@@ - # common commands are grouped by themes - # these groups are output by 'git help' in the order declared here. - # map each common command in the command list to one of these groups. - ### common groups (do not change this line) - init start a working area (see also: git help tutorial) - worktree work on the current change (see also: git help everyday) - info examine the history and state (see also: git help revisions) - history grow, mark and tweak your common history - remote collaborate (see also: git help workflows) - - ### command list (do not change this line) - # command name category [deprecated] [common] + # Command classification list + # --------------------------- + # All supported commands, builtin or external, must be described in + # here. This info is used to list commands in various places. Each + # command is on one line followed by one or more attributes. + # + # The first attribute group is mandatory and indicates the command + # type. This group includes: + # + # mainporcelain + # ancillarymanipulators + # ancillaryinterrogators + # foreignscminterface + # plumbingmanipulators + # plumbinginterrogators + # synchingrepositories + # synchelpers + # purehelpers + # + # The type names are self explanatory. But if you want to see what + # command belongs to what group to get a better picture, have a look + # at "git" man page, "GIT COMMANDS" section. + # + # Commands of type mainporcelain can also optionally have one of these + # attributes: + # + # init + # worktree + # info + # history + # remote + # + # These commands are considered "common" and will show up in "git + # help" output in groups. Uncommon porcelain commands must not + # specify any of these attributes. + # + # "complete" attribute is used to mark that the command should be + # completable by git-completion.bash. Note that by default, + # mainporcelain commands are completable so you don't need this + # attribute. + # + # As part of the Git man page list, the man(5/7) guides are also + # specified here, which can only have "guide" attribute and nothing + # else. + # + ### command list (do not change this line, also do not change alignment) + # command name category [category] [category] git-add mainporcelain worktree git-am mainporcelain git-annotate ancillaryinterrogators - git-apply plumbingmanipulators + git-apply plumbingmanipulators complete git-archimport foreignscminterface git-archive mainporcelain git-bisect mainporcelain info - git-blame ancillaryinterrogators + git-blame ancillaryinterrogators complete git-branch mainporcelain history git-bundle mainporcelain git-cat-file plumbinginterrogators @@@ -27,16 -62,15 +62,16 @@@ git-check-mailma git-checkout mainporcelain history git-checkout-index plumbingmanipulators git-check-ref-format purehelpers - git-cherry ancillaryinterrogators + git-cherry ancillaryinterrogators complete git-cherry-pick mainporcelain git-citool mainporcelain git-clean mainporcelain git-clone mainporcelain init git-column purehelpers git-commit mainporcelain history +git-commit-graph plumbingmanipulators git-commit-tree plumbingmanipulators - git-config ancillarymanipulators + git-config ancillarymanipulators complete git-count-objects ancillaryinterrogators git-credential purehelpers git-credential-cache purehelpers @@@ -50,7 -84,7 +85,7 @@@ git-dif git-diff-files plumbinginterrogators git-diff-index plumbinginterrogators git-diff-tree plumbinginterrogators - git-difftool ancillaryinterrogators + git-difftool ancillaryinterrogators complete git-fast-export ancillarymanipulators git-fast-import ancillarymanipulators git-fetch mainporcelain remote @@@ -59,20 -93,20 +94,20 @@@ git-filter-branc git-fmt-merge-msg purehelpers git-for-each-ref plumbinginterrogators git-format-patch mainporcelain - git-fsck ancillaryinterrogators + git-fsck ancillaryinterrogators complete git-gc mainporcelain git-get-tar-commit-id ancillaryinterrogators git-grep mainporcelain info git-gui mainporcelain git-hash-object plumbingmanipulators - git-help ancillaryinterrogators + git-help ancillaryinterrogators complete git-http-backend synchingrepositories git-http-fetch synchelpers git-http-push synchelpers git-imap-send foreignscminterface git-index-pack plumbingmanipulators git-init mainporcelain init - git-instaweb ancillaryinterrogators + git-instaweb ancillaryinterrogators complete git-interpret-trailers purehelpers gitk mainporcelain git-log mainporcelain info @@@ -86,7 -120,7 +121,7 @@@ git-merge-bas git-merge-file plumbingmanipulators git-merge-index plumbingmanipulators git-merge-one-file purehelpers - git-mergetool ancillarymanipulators + git-mergetool ancillarymanipulators complete git-merge-tree ancillaryinterrogators git-mktag plumbingmanipulators git-mktree plumbingmanipulators @@@ -107,28 -141,29 +142,29 @@@ git-quiltimpor git-read-tree plumbingmanipulators git-rebase mainporcelain history git-receive-pack synchelpers - git-reflog ancillarymanipulators - git-remote ancillarymanipulators - git-repack ancillarymanipulators - git-replace ancillarymanipulators - git-request-pull foreignscminterface + git-reflog ancillarymanipulators complete + git-remote ancillarymanipulators complete + git-repack ancillarymanipulators complete + git-replace ancillarymanipulators complete + git-request-pull foreignscminterface complete git-rerere ancillaryinterrogators git-reset mainporcelain worktree git-revert mainporcelain git-rev-list plumbinginterrogators git-rev-parse ancillaryinterrogators git-rm mainporcelain worktree - git-send-email foreignscminterface + git-send-email foreignscminterface complete git-send-pack synchingrepositories git-shell synchelpers git-shortlog mainporcelain git-show mainporcelain info - git-show-branch ancillaryinterrogators + git-show-branch ancillaryinterrogators complete git-show-index plumbinginterrogators git-show-ref plumbinginterrogators git-sh-i18n purehelpers git-sh-setup purehelpers git-stash mainporcelain + git-stage complete git-status mainporcelain info git-stripspace purehelpers git-submodule mainporcelain @@@ -147,6 -182,22 +183,22 @@@ git-verify-commi git-verify-pack plumbinginterrogators git-verify-tag ancillaryinterrogators gitweb ancillaryinterrogators - git-whatchanged ancillaryinterrogators + git-whatchanged ancillaryinterrogators complete git-worktree mainporcelain git-write-tree plumbingmanipulators + gitattributes guide + gitcli guide + gitcore-tutorial guide + gitcvs-migration guide + gitdiffcore guide + giteveryday guide + gitglossary guide + githooks guide + gitignore guide + gitmodules guide + gitnamespaces guide + gitrepository-layout guide + gitrevisions guide + gittutorial-2 guide + gittutorial guide + gitworkflows guide diff --combined connect.c index 31aa9c8433,ff078d28dc..968e91b18c --- a/connect.c +++ b/connect.c @@@ -12,11 -12,10 +12,12 @@@ #include "sha1-array.h" #include "transport.h" #include "strbuf.h" +#include "version.h" #include "protocol.h" + #include "alias.h" -static char *server_capabilities; +static char *server_capabilities_v1; +static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT; static const char *parse_feature_value(const char *, const char *, int *); static int check_ref(const char *name, unsigned int flags) @@@ -48,14 -47,8 +49,14 @@@ int check_ref_type(const struct ref *re return check_ref(ref->name, flags); } -static void die_initial_contact(int unexpected) +static NORETURN void die_initial_contact(int unexpected) { + /* + * A hang-up after seeing some response from the other end + * means that it is unexpected, as we know the other end is + * willing to talk to us. A hang-up before seeing any + * response does not necessarily mean an ACL problem, though. + */ if (unexpected) die(_("The remote end hung up upon initial contact")); else @@@ -64,92 -57,6 +65,92 @@@ "and the repository exists.")); } +/* Checks if the server supports the capability 'c' */ +int server_supports_v2(const char *c, int die_on_error) +{ + int i; + + for (i = 0; i < server_capabilities_v2.argc; i++) { + const char *out; + if (skip_prefix(server_capabilities_v2.argv[i], c, &out) && + (!*out || *out == '=')) + return 1; + } + + if (die_on_error) + die("server doesn't support '%s'", c); + + return 0; +} + +int server_supports_feature(const char *c, const char *feature, + int die_on_error) +{ + int i; + + for (i = 0; i < server_capabilities_v2.argc; i++) { + const char *out; + if (skip_prefix(server_capabilities_v2.argv[i], c, &out) && + (!*out || *(out++) == '=')) { + if (parse_feature_request(out, feature)) + return 1; + else + break; + } + } + + if (die_on_error) + die("server doesn't support feature '%s'", feature); + + return 0; +} + +static void process_capabilities_v2(struct packet_reader *reader) +{ + while (packet_reader_read(reader) == PACKET_READ_NORMAL) + argv_array_push(&server_capabilities_v2, reader->line); + + if (reader->status != PACKET_READ_FLUSH) + die("expected flush after capabilities"); +} + +enum protocol_version discover_version(struct packet_reader *reader) +{ + enum protocol_version version = protocol_unknown_version; + + /* + * Peek the first line of the server's response to + * determine the protocol version the server is speaking. + */ + switch (packet_reader_peek(reader)) { + case PACKET_READ_EOF: + die_initial_contact(0); + case PACKET_READ_FLUSH: + case PACKET_READ_DELIM: + version = protocol_v0; + break; + case PACKET_READ_NORMAL: + version = determine_protocol_version_client(reader->line); + break; + } + + switch (version) { + case protocol_v2: + process_capabilities_v2(reader); + break; + case protocol_v1: + /* Read the peeked version line */ + packet_reader_read(reader); + break; + case protocol_v0: + break; + case protocol_unknown_version: + BUG("unknown protocol version"); + } + + return version; +} + static void parse_one_symref_info(struct string_list *symref, const char *val, int len) { char *sym, *target; @@@ -179,7 -86,7 +180,7 @@@ reject static void annotate_refs_with_symref_info(struct ref *ref) { struct string_list symref = STRING_LIST_INIT_DUP; - const char *feature_list = server_capabilities; + const char *feature_list = server_capabilities_v1; while (feature_list) { int len; @@@ -203,21 -110,60 +204,21 @@@ string_list_clear(&symref, 0); } -/* - * Read one line of a server's ref advertisement into packet_buffer. - */ -static int read_remote_ref(int in, char **src_buf, size_t *src_len, - int *responded) -{ - int len = packet_read(in, src_buf, src_len, - packet_buffer, sizeof(packet_buffer), - PACKET_READ_GENTLE_ON_EOF | - PACKET_READ_CHOMP_NEWLINE); - const char *arg; - if (len < 0) - die_initial_contact(*responded); - if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg)) - die("remote error: %s", arg); - - *responded = 1; - - return len; -} - -#define EXPECTING_PROTOCOL_VERSION 0 -#define EXPECTING_FIRST_REF 1 -#define EXPECTING_REF 2 -#define EXPECTING_SHALLOW 3 - -/* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */ -static int process_protocol_version(void) +static void process_capabilities(const char *line, int *len) { - switch (determine_protocol_version_client(packet_buffer)) { - case protocol_v1: - return 1; - case protocol_v0: - return 0; - default: - die("server is speaking an unknown protocol"); - } -} - -static void process_capabilities(int *len) -{ - int nul_location = strlen(packet_buffer); + int nul_location = strlen(line); if (nul_location == *len) return; - server_capabilities = xstrdup(packet_buffer + nul_location + 1); + server_capabilities_v1 = xstrdup(line + nul_location + 1); *len = nul_location; } -static int process_dummy_ref(void) +static int process_dummy_ref(const char *line) { struct object_id oid; const char *name; - if (parse_oid_hex(packet_buffer, &oid, &name)) + if (parse_oid_hex(line, &oid, &name)) return 0; if (*name != ' ') return 0; @@@ -226,20 -172,20 +227,20 @@@ return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}"); } -static void check_no_capabilities(int len) +static void check_no_capabilities(const char *line, int len) { - if (strlen(packet_buffer) != len) + if (strlen(line) != len) warning("Ignoring capabilities after first line '%s'", - packet_buffer + strlen(packet_buffer)); + line + strlen(line)); } -static int process_ref(int len, struct ref ***list, unsigned int flags, - struct oid_array *extra_have) +static int process_ref(const char *line, int len, struct ref ***list, + unsigned int flags, struct oid_array *extra_have) { struct object_id old_oid; const char *name; - if (parse_oid_hex(packet_buffer, &old_oid, &name)) + if (parse_oid_hex(line, &old_oid, &name)) return 0; if (*name != ' ') return 0; @@@ -255,17 -201,16 +256,17 @@@ **list = ref; *list = &ref->next; } - check_no_capabilities(len); + check_no_capabilities(line, len); return 1; } -static int process_shallow(int len, struct oid_array *shallow_points) +static int process_shallow(const char *line, int len, + struct oid_array *shallow_points) { const char *arg; struct object_id old_oid; - if (!skip_prefix(packet_buffer, "shallow ", &arg)) + if (!skip_prefix(line, "shallow ", &arg)) return 0; if (get_oid_hex(arg, &old_oid)) @@@ -273,68 -218,60 +274,68 @@@ if (!shallow_points) die("repository on the other end cannot be shallow"); oid_array_append(shallow_points, &old_oid); - check_no_capabilities(len); + check_no_capabilities(line, len); return 1; } +enum get_remote_heads_state { + EXPECTING_FIRST_REF = 0, + EXPECTING_REF, + EXPECTING_SHALLOW, + EXPECTING_DONE, +}; + /* * Read all the refs from the other end */ -struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, +struct ref **get_remote_heads(struct packet_reader *reader, struct ref **list, unsigned int flags, struct oid_array *extra_have, struct oid_array *shallow_points) { struct ref **orig_list = list; - - /* - * A hang-up after seeing some response from the other end - * means that it is unexpected, as we know the other end is - * willing to talk to us. A hang-up before seeing any - * response does not necessarily mean an ACL problem, though. - */ - int responded = 0; - int len; - int state = EXPECTING_PROTOCOL_VERSION; + int len = 0; + enum get_remote_heads_state state = EXPECTING_FIRST_REF; + const char *arg; *list = NULL; - while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) { + while (state != EXPECTING_DONE) { + switch (packet_reader_read(reader)) { + case PACKET_READ_EOF: + die_initial_contact(1); + case PACKET_READ_NORMAL: + len = reader->pktlen; + if (len > 4 && skip_prefix(reader->line, "ERR ", &arg)) + die("remote error: %s", arg); + break; + case PACKET_READ_FLUSH: + state = EXPECTING_DONE; + break; + case PACKET_READ_DELIM: + die("invalid packet"); + } + switch (state) { - case EXPECTING_PROTOCOL_VERSION: - if (process_protocol_version()) { - state = EXPECTING_FIRST_REF; - break; - } - state = EXPECTING_FIRST_REF; - /* fallthrough */ case EXPECTING_FIRST_REF: - process_capabilities(&len); - if (process_dummy_ref()) { + process_capabilities(reader->line, &len); + if (process_dummy_ref(reader->line)) { state = EXPECTING_SHALLOW; break; } state = EXPECTING_REF; /* fallthrough */ case EXPECTING_REF: - if (process_ref(len, &list, flags, extra_have)) + if (process_ref(reader->line, len, &list, flags, extra_have)) break; state = EXPECTING_SHALLOW; /* fallthrough */ case EXPECTING_SHALLOW: - if (process_shallow(len, shallow_points)) + if (process_shallow(reader->line, len, shallow_points)) break; - die("protocol error: unexpected '%s'", packet_buffer); - default: - die("unexpected state %d", state); + die("protocol error: unexpected '%s'", reader->line); + case EXPECTING_DONE: + break; } } @@@ -343,112 -280,6 +344,112 @@@ return list; } +/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */ +static int process_ref_v2(const char *line, struct ref ***list) +{ + int ret = 1; + int i = 0; + struct object_id old_oid; + struct ref *ref; + struct string_list line_sections = STRING_LIST_INIT_DUP; + const char *end; + + /* + * Ref lines have a number of fields which are space deliminated. The + * first field is the OID of the ref. The second field is the ref + * name. Subsequent fields (symref-target and peeled) are optional and + * don't have a particular order. + */ + if (string_list_split(&line_sections, line, ' ', -1) < 2) { + ret = 0; + goto out; + } + + if (parse_oid_hex(line_sections.items[i++].string, &old_oid, &end) || + *end) { + ret = 0; + goto out; + } + + ref = alloc_ref(line_sections.items[i++].string); + + oidcpy(&ref->old_oid, &old_oid); + **list = ref; + *list = &ref->next; + + for (; i < line_sections.nr; i++) { + const char *arg = line_sections.items[i].string; + if (skip_prefix(arg, "symref-target:", &arg)) + ref->symref = xstrdup(arg); + + if (skip_prefix(arg, "peeled:", &arg)) { + struct object_id peeled_oid; + char *peeled_name; + struct ref *peeled; + if (parse_oid_hex(arg, &peeled_oid, &end) || *end) { + ret = 0; + goto out; + } + + peeled_name = xstrfmt("%s^{}", ref->name); + peeled = alloc_ref(peeled_name); + + oidcpy(&peeled->old_oid, &peeled_oid); + **list = peeled; + *list = &peeled->next; + + free(peeled_name); + } + } + +out: + string_list_clear(&line_sections, 0); + return ret; +} + +struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, + struct ref **list, int for_push, + const struct argv_array *ref_prefixes, + const struct string_list *server_options) +{ + int i; + *list = NULL; + + if (server_supports_v2("ls-refs", 1)) + packet_write_fmt(fd_out, "command=ls-refs\n"); + + if (server_supports_v2("agent", 0)) + packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized()); + + if (server_options && server_options->nr && + server_supports_v2("server-option", 1)) + for (i = 0; i < server_options->nr; i++) + packet_write_fmt(fd_out, "server-option=%s", + server_options->items[i].string); + + packet_delim(fd_out); + /* When pushing we don't want to request the peeled tags */ + if (!for_push) + packet_write_fmt(fd_out, "peel\n"); + packet_write_fmt(fd_out, "symrefs\n"); + for (i = 0; ref_prefixes && i < ref_prefixes->argc; i++) { + packet_write_fmt(fd_out, "ref-prefix %s\n", + ref_prefixes->argv[i]); + } + packet_flush(fd_out); + + /* Process response from server */ + while (packet_reader_read(reader) == PACKET_READ_NORMAL) { + if (!process_ref_v2(reader->line, &list)) + die("invalid ls-refs response: %s", reader->line); + } + + if (reader->status != PACKET_READ_FLUSH) + die("expected flush after ref listing"); + + return list; +} + static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp) { int len; @@@ -493,7 -324,7 +494,7 @@@ int parse_feature_request(const char *f const char *server_feature_value(const char *feature, int *len) { - return parse_feature_value(server_capabilities, feature, len); + return parse_feature_value(server_capabilities_v1, feature, len); } int server_supports(const char *feature) @@@ -1042,7 -873,6 +1043,7 @@@ static enum ssh_variant determine_ssh_v */ static struct child_process *git_connect_git(int fd[2], char *hostandport, const char *path, const char *prog, + enum protocol_version version, int flags) { struct child_process *conn; @@@ -1081,10 -911,10 +1082,10 @@@ target_host, 0); /* If using a new version put that stuff here after a second null byte */ - if (get_protocol_version_config() > 0) { + if (version > 0) { strbuf_addch(&request, '\0'); strbuf_addf(&request, "version=%d%c", - get_protocol_version_config(), '\0'); + version, '\0'); } packet_write(fd[1], request.buf, request.len); @@@ -1100,14 -930,14 +1101,14 @@@ */ static void push_ssh_options(struct argv_array *args, struct argv_array *env, enum ssh_variant variant, const char *port, - int flags) + enum protocol_version version, int flags) { if (variant == VARIANT_SSH && - get_protocol_version_config() > 0) { + version > 0) { argv_array_push(args, "-o"); argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT); argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d", - get_protocol_version_config()); + version); } if (flags & CONNECT_IPV4) { @@@ -1160,8 -990,7 +1161,8 @@@ /* Prepare a child_process for use by Git's SSH-tunneled transport. */ static void fill_ssh_args(struct child_process *conn, const char *ssh_host, - const char *port, int flags) + const char *port, enum protocol_version version, + int flags) { const char *ssh; enum ssh_variant variant; @@@ -1195,14 -1024,14 +1196,14 @@@ argv_array_push(&detect.args, ssh); argv_array_push(&detect.args, "-G"); push_ssh_options(&detect.args, &detect.env_array, - VARIANT_SSH, port, flags); + VARIANT_SSH, port, version, flags); argv_array_push(&detect.args, ssh_host); variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH; } argv_array_push(&conn->args, ssh); - push_ssh_options(&conn->args, &conn->env_array, variant, port, flags); + push_ssh_options(&conn->args, &conn->env_array, variant, port, version, flags); argv_array_push(&conn->args, ssh_host); } @@@ -1223,15 -1052,6 +1224,15 @@@ struct child_process *git_connect(int f char *hostandport, *path; struct child_process *conn; enum protocol protocol; + enum protocol_version version = get_protocol_version_config(); + + /* + * NEEDSWORK: If we are trying to use protocol v2 and we are planning + * to perform a push, then fallback to v0 since the client doesn't know + * how to push yet using v2. + */ + if (version == protocol_v2 && !strcmp("git-receive-pack", prog)) + version = protocol_v0; /* Without this we cannot rely on waitpid() to tell * what happened to our children. @@@ -1246,7 -1066,7 +1247,7 @@@ printf("Diag: path=%s\n", path ? path : "NULL"); conn = NULL; } else if (protocol == PROTO_GIT) { - conn = git_connect_git(fd, hostandport, path, prog, flags); + conn = git_connect_git(fd, hostandport, path, prog, version, flags); } else { struct strbuf cmd = STRBUF_INIT; const char *const *var; @@@ -1289,12 -1109,12 +1290,12 @@@ strbuf_release(&cmd); return NULL; } - fill_ssh_args(conn, ssh_host, port, flags); + fill_ssh_args(conn, ssh_host, port, version, flags); } else { transport_check_allowed("file"); - if (get_protocol_version_config() > 0) { + if (version > 0) { argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d", - get_protocol_version_config()); + version); } } argv_array_push(&conn->args, cmd.buf); diff --combined contrib/completion/git-completion.bash index 1491b7239b,e5b2ccbdd2..12814e9bbf --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@@ -94,70 -94,6 +94,70 @@@ __git ( ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null } +# Removes backslash escaping, single quotes and double quotes from a word, +# stores the result in the variable $dequoted_word. +# 1: The word to dequote. +__git_dequote () +{ + local rest="$1" len ch + + dequoted_word="" + + while test -n "$rest"; do + len=${#dequoted_word} + dequoted_word="$dequoted_word${rest%%[\\\'\"]*}" + rest="${rest:$((${#dequoted_word}-$len))}" + + case "${rest:0:1}" in + \\) + ch="${rest:1:1}" + case "$ch" in + $'\n') + ;; + *) + dequoted_word="$dequoted_word$ch" + ;; + esac + rest="${rest:2}" + ;; + \') + rest="${rest:1}" + len=${#dequoted_word} + dequoted_word="$dequoted_word${rest%%\'*}" + rest="${rest:$((${#dequoted_word}-$len+1))}" + ;; + \") + rest="${rest:1}" + while test -n "$rest" ; do + len=${#dequoted_word} + dequoted_word="$dequoted_word${rest%%[\\\"]*}" + rest="${rest:$((${#dequoted_word}-$len))}" + case "${rest:0:1}" in + \\) + ch="${rest:1:1}" + case "$ch" in + \"|\\|\$|\`) + dequoted_word="$dequoted_word$ch" + ;; + $'\n') + ;; + *) + dequoted_word="$dequoted_word\\$ch" + ;; + esac + rest="${rest:2}" + ;; + \") + rest="${rest:1}" + break + ;; + esac + done + ;; + esac + done +} + # The following function is based on code from: # # bash_completion - programmable completion functions for bash 3.2+ @@@ -348,11 -284,7 +348,11 @@@ __gitcomp ( # Clear the variables caching builtins' options when (re-)sourcing # the completion script. -unset $(set |sed -ne 's/^\(__gitcomp_builtin_[a-zA-Z0-9_][a-zA-Z0-9_]*\)=.*/\1/p') 2>/dev/null +if [[ -n ${ZSH_VERSION-} ]]; then + unset $(set |sed -ne 's/^\(__gitcomp_builtin_[a-zA-Z0-9_][a-zA-Z0-9_]*\)=.*/\1/p') 2>/dev/null +else + unset $(compgen -v __gitcomp_builtin_) +fi # This function is equivalent to # @@@ -410,24 -342,6 +410,24 @@@ __gitcomp_nl ( __gitcomp_nl_append "$@" } +# Fills the COMPREPLY array with prefiltered paths without any additional +# processing. +# Callers must take care of providing only paths that match the current path +# to be completed and adding any prefix path components, if necessary. +# 1: List of newline-separated matching paths, complete with all prefix +# path componens. +__gitcomp_file_direct () +{ + local IFS=$'\n' + + COMPREPLY=($1) + + # use a hack to enable file mode in bash < 4 + compopt -o filenames +o nospace 2>/dev/null || + compgen -f /non-existing-dir/ >/dev/null || + true +} + # Generates completion reply with compgen from newline-separated possible # completion filenames. # It accepts 1 to 3 arguments: @@@ -447,8 -361,7 +447,8 @@@ __gitcomp_file ( # use a hack to enable file mode in bash < 4 compopt -o filenames +o nospace 2>/dev/null || - compgen -f /non-existing-dir/ > /dev/null + compgen -f /non-existing-dir/ >/dev/null || + true } # Execute 'git ls-files', unless the --committable option is specified, in @@@ -458,12 -371,10 +458,12 @@@ __git_ls_files_helper () { if [ "$2" == "--committable" ]; then - __git -C "$1" diff-index --name-only --relative HEAD + __git -C "$1" -c core.quotePath=false diff-index \ + --name-only --relative HEAD -- "${3//\\/\\\\}*" else # NOTE: $2 is not quoted in order to support multiple options - __git -C "$1" ls-files --exclude-standard $2 + __git -C "$1" -c core.quotePath=false ls-files \ + --exclude-standard $2 -- "${3//\\/\\\\}*" fi } @@@ -474,103 -385,17 +474,103 @@@ # If provided, only files within the specified directory are listed. # Sub directories are never recursed. Path must have a trailing # slash. +# 3: List only paths matching this path component (optional). __git_index_files () { - local root="${2-.}" file + local root="$2" match="$3" - __git_ls_files_helper "$root" "$1" | - while read -r file; do - case "$file" in - ?*/*) echo "${file%%/*}" ;; - *) echo "$file" ;; - esac - done | sort | uniq + __git_ls_files_helper "$root" "$1" "$match" | + awk -F / -v pfx="${2//\\/\\\\}" '{ + paths[$1] = 1 + } + END { + for (p in paths) { + if (substr(p, 1, 1) != "\"") { + # No special characters, easy! + print pfx p + continue + } + + # The path is quoted. + p = dequote(p) + if (p == "") + continue + + # Even when a directory name itself does not contain + # any special characters, it will still be quoted if + # any of its (stripped) trailing path components do. + # Because of this we may have seen the same direcory + # both quoted and unquoted. + if (p in paths) + # We have seen the same directory unquoted, + # skip it. + continue + else + print pfx p + } + } + function dequote(p, bs_idx, out, esc, esc_idx, dec) { + # Skip opening double quote. + p = substr(p, 2) + + # Interpret backslash escape sequences. + while ((bs_idx = index(p, "\\")) != 0) { + out = out substr(p, 1, bs_idx - 1) + esc = substr(p, bs_idx + 1, 1) + p = substr(p, bs_idx + 2) + + if ((esc_idx = index("abtvfr\"\\", esc)) != 0) { + # C-style one-character escape sequence. + out = out substr("\a\b\t\v\f\r\"\\", + esc_idx, 1) + } else if (esc == "n") { + # Uh-oh, a newline character. + # We cant reliably put a pathname + # containing a newline into COMPREPLY, + # and the newline would create a mess. + # Skip this path. + return "" + } else { + # Must be a \nnn octal value, then. + dec = esc * 64 + \ + substr(p, 1, 1) * 8 + \ + substr(p, 2, 1) + out = out sprintf("%c", dec) + p = substr(p, 3) + } + } + # Drop closing double quote, if there is one. + # (There isnt any if this is a directory, as it was + # already stripped with the trailing path components.) + if (substr(p, length(p), 1) == "\"") + out = out substr(p, 1, length(p) - 1) + else + out = out p + + return out + }' +} + +# __git_complete_index_file requires 1 argument: +# 1: the options to pass to ls-file +# +# The exception is --committable, which finds the files appropriate commit. +__git_complete_index_file () +{ + local dequoted_word pfx="" cur_ + + __git_dequote "$cur" + + case "$dequoted_word" in + ?*/*) + pfx="${dequoted_word%/*}/" + cur_="${dequoted_word##*/}" + ;; + *) + cur_="$dequoted_word" + esac + + __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")" } # Lists branches from the local repository. @@@ -889,6 -714,26 +889,6 @@@ __git_complete_revlist_file ( esac } - -# __git_complete_index_file requires 1 argument: -# 1: the options to pass to ls-file -# -# The exception is --committable, which finds the files appropriate commit. -__git_complete_index_file () -{ - local pfx="" cur_="$cur" - - case "$cur_" in - ?*/*) - pfx="${cur_%/*}" - cur_="${cur_##*/}" - pfx="${pfx}/" - ;; - esac - - __gitcomp_file "$(__git_index_files "$1" ${pfx:+"$pfx"})" "$pfx" "$cur_" -} - __git_complete_file () { __git_complete_revlist_file @@@ -989,127 -834,11 +989,11 @@@ __git_complete_strategy ( return 1 } - __git_commands () { - if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}" - then - printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}" - else - git help -a|egrep '^ [a-zA-Z0-9]' - fi - } - - __git_list_all_commands () - { - local i IFS=" "$'\n' - for i in $(__git_commands) - do - case $i in - *--*) : helper pattern;; - *) echo $i;; - esac - done - } - __git_all_commands= __git_compute_all_commands () { test -n "$__git_all_commands" || - __git_all_commands=$(__git_list_all_commands) - } - - __git_list_porcelain_commands () - { - local i IFS=" "$'\n' - __git_compute_all_commands - for i in $__git_all_commands - do - case $i in - *--*) : helper pattern;; - applymbox) : ask gittus;; - applypatch) : ask gittus;; - archimport) : import;; - cat-file) : plumbing;; - check-attr) : plumbing;; - check-ignore) : plumbing;; - check-mailmap) : plumbing;; - check-ref-format) : plumbing;; - checkout-index) : plumbing;; - column) : internal helper;; - commit-graph) : plumbing;; - commit-tree) : plumbing;; - count-objects) : infrequent;; - credential) : credentials;; - credential-*) : credentials helper;; - cvsexportcommit) : export;; - cvsimport) : import;; - cvsserver) : daemon;; - daemon) : daemon;; - diff-files) : plumbing;; - diff-index) : plumbing;; - diff-tree) : plumbing;; - fast-import) : import;; - fast-export) : export;; - fsck-objects) : plumbing;; - fetch-pack) : plumbing;; - fmt-merge-msg) : plumbing;; - for-each-ref) : plumbing;; - hash-object) : plumbing;; - http-*) : transport;; - index-pack) : plumbing;; - init-db) : deprecated;; - local-fetch) : plumbing;; - ls-files) : plumbing;; - ls-remote) : plumbing;; - ls-tree) : plumbing;; - mailinfo) : plumbing;; - mailsplit) : plumbing;; - merge-*) : plumbing;; - mktree) : plumbing;; - mktag) : plumbing;; - pack-objects) : plumbing;; - pack-redundant) : plumbing;; - pack-refs) : plumbing;; - parse-remote) : plumbing;; - patch-id) : plumbing;; - prune) : plumbing;; - prune-packed) : plumbing;; - quiltimport) : import;; - read-tree) : plumbing;; - receive-pack) : plumbing;; - remote-*) : transport;; - rerere) : plumbing;; - rev-list) : plumbing;; - rev-parse) : plumbing;; - runstatus) : plumbing;; - sh-setup) : internal;; - shell) : daemon;; - show-ref) : plumbing;; - send-pack) : plumbing;; - show-index) : plumbing;; - ssh-*) : transport;; - stripspace) : plumbing;; - symbolic-ref) : plumbing;; - unpack-file) : plumbing;; - unpack-objects) : plumbing;; - update-index) : plumbing;; - update-ref) : plumbing;; - update-server-info) : daemon;; - upload-archive) : plumbing;; - upload-pack) : plumbing;; - write-tree) : plumbing;; - var) : infrequent;; - verify-pack) : infrequent;; - verify-tag) : plumbing;; - *) echo $i;; - esac - done - } - - __git_porcelain_commands= - __git_compute_porcelain_commands () - { - test -n "$__git_porcelain_commands" || - __git_porcelain_commands=$(__git_list_porcelain_commands) + __git_all_commands=$(git --list-cmds=main,others,alias,nohelpers) } # Lists all set config variables starting with the given section prefix, @@@ -1127,11 -856,6 +1011,6 @@@ __git_pretty_aliases ( __git_get_config_variables "pretty" } - __git_aliases () - { - __git_get_config_variables "alias" - } - # __git_aliased_command requires 1 argument __git_aliased_command () { @@@ -1739,13 -1463,12 +1618,12 @@@ _git_help ( return ;; esac - __git_compute_all_commands - __gitcomp "$__git_all_commands $(__git_aliases) - attributes cli core-tutorial cvs-migration - diffcore everyday gitk glossary hooks ignore modules - namespaces repository-layout revisions tutorial tutorial-2 - workflows - " + if test -n "$GIT_TESTING_ALL_COMMAND_LIST" + then + __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(git --list-cmds=alias,list-guide) gitk" + else + __gitcomp "$(git --list-cmds=main,nohelpers,alias,list-guide) gitk" + fi } _git_init () @@@ -2105,7 -1828,7 +1983,7 @@@ _git_rebase ( --*) __gitcomp " --onto --merge --strategy --interactive - --preserve-merges --stat --no-stat + --rebase-merges --preserve-merges --stat --no-stat --committer-date-is-author-date --ignore-date --ignore-whitespace --whitespace= --autosquash --no-autosquash @@@ -2276,7 -1999,7 +2154,7 @@@ _git_config ( return ;; branch.*.rebase) - __gitcomp "false true preserve interactive" + __gitcomp "false true merges preserve interactive" return ;; remote.pushdefault) @@@ -2333,7 -2056,7 +2211,7 @@@ __gitcomp "$__git_log_date_formats" return ;; - sendemail.aliasesfiletype) + sendemail.aliasfiletype) __gitcomp "mutt mailrc pine elm gnus" return ;; @@@ -2506,7 -2229,6 +2384,7 @@@ core.bigFileThreshold core.checkStat core.commentChar + core.commitGraph core.compression core.createObject core.deltaBaseCacheLimit @@@ -2931,21 -2653,13 +2809,21 @@@ _git_show_branch ( _git_stash () { local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked' - local subcommands='push save list show apply clear drop pop create branch' - local subcommand="$(__git_find_on_cmdline "$subcommands")" + local subcommands='push list show apply clear drop pop create branch' + local subcommand="$(__git_find_on_cmdline "$subcommands save")" + if [ -n "$(__git_find_on_cmdline "-p")" ]; then + subcommand="push" + fi if [ -z "$subcommand" ]; then case "$cur" in --*) __gitcomp "$save_opts" ;; + sa*) + if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then + __gitcomp "save" + fi + ;; *) if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then __gitcomp "$subcommands" @@@ -3214,7 -2928,7 +3092,7 @@@ __git_complete_common () __git_cmds_with_parseopt_helper= __git_support_parseopt_helper () { test -n "$__git_cmds_with_parseopt_helper" || - __git_cmds_with_parseopt_helper="$(__git --list-parseopt-builtins)" + __git_cmds_with_parseopt_helper="$(__git --list-cmds=parseopt)" case " $__git_cmds_with_parseopt_helper " in *" $1 "*) @@@ -3229,17 -2943,10 +3107,17 @@@ __git_complete_command () { local command="$1" local completion_func="_git_${command//-/_}" - if declare -f $completion_func >/dev/null 2>/dev/null; then + if ! declare -f $completion_func >/dev/null 2>/dev/null && + declare -f _completion_loader >/dev/null 2>/dev/null + then + _completion_loader "git-$command" + fi + if declare -f $completion_func >/dev/null 2>/dev/null + then $completion_func return 0 - elif __git_support_parseopt_helper "$command"; then + elif __git_support_parseopt_helper "$command" + then __git_complete_common "$command" return 0 else @@@ -3300,8 -3007,14 +3178,14 @@@ __git_main ( --help " ;; - *) __git_compute_porcelain_commands - __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;; + *) + if test -n "$GIT_TESTING_PORCELAIN_COMMAND_LIST" + then + __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST" + else + __gitcomp "$(git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)" + fi + ;; esac return fi @@@ -3388,15 -3101,6 +3272,15 @@@ if [[ -n ${ZSH_VERSION-} ]]; the compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0 } + __gitcomp_file_direct () + { + emulate -L zsh + + local IFS=$'\n' + compset -P '*[=:]' + compadd -Q -f -- ${=1} && _ret=0 + } + __gitcomp_file () { emulate -L zsh diff --combined git.c index 5771d62a32,447dac0e71..c2f48d53dd --- a/git.c +++ b/git.c @@@ -1,8 -1,9 +1,9 @@@ #include "builtin.h" #include "config.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "help.h" #include "run-command.h" + #include "alias.h" #define RUN_SETUP (1<<0) #define RUN_SETUP_GENTLY (1<<1) @@@ -25,7 -26,7 +26,7 @@@ struct cmd_struct const char git_usage_string[] = N_("git [--version] [--help] [-C ] [-c =]\n" " [--exec-path[=]] [--html-path] [--man-path] [--info-path]\n" - " [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]\n" + " [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\n" " [--git-dir=] [--work-tree=] [--namespace=]\n" " []"); @@@ -36,7 -37,66 +37,66 @@@ const char git_more_info_string[] static int use_pager = -1; - static void list_builtins(unsigned int exclude_option, char sep); + static void list_builtins(struct string_list *list, unsigned int exclude_option); + + static void exclude_helpers_from_list(struct string_list *list) + { + int i = 0; + + while (i < list->nr) { + if (strstr(list->items[i].string, "--")) + unsorted_string_list_delete_item(list, i, 0); + else + i++; + } + } + + static int match_token(const char *spec, int len, const char *token) + { + int token_len = strlen(token); + + return len == token_len && !strncmp(spec, token, token_len); + } + + static int list_cmds(const char *spec) + { + struct string_list list = STRING_LIST_INIT_DUP; + int i; + + while (*spec) { + const char *sep = strchrnul(spec, ','); + int len = sep - spec; + + if (match_token(spec, len, "builtins")) + list_builtins(&list, 0); + else if (match_token(spec, len, "main")) + list_all_main_cmds(&list); + else if (match_token(spec, len, "others")) + list_all_other_cmds(&list); + else if (match_token(spec, len, "nohelpers")) + exclude_helpers_from_list(&list); + else if (match_token(spec, len, "alias")) + list_aliases(&list); + else if (match_token(spec, len, "config")) + list_cmds_by_config(&list); + else if (len > 5 && !strncmp(spec, "list-", 5)) { + struct strbuf sb = STRBUF_INIT; + + strbuf_add(&sb, spec + 5, len - 5); + list_cmds_by_category(&list, sb.buf); + strbuf_release(&sb); + } + else + die(_("unsupported command listing type '%s'"), spec); + spec += len; + if (*spec == ',') + spec++; + } + for (i = 0; i < list.nr; i++) + puts(list.items[i].string); + string_list_clear(&list, 0); + return 0; + } static void commit_pager_choice(void) { switch (use_pager) { @@@ -83,7 -143,7 +143,7 @@@ static int handle_options(const char ** */ if (skip_prefix(cmd, "--exec-path", &cmd)) { if (*cmd == '=') - git_set_argv_exec_path(cmd + 1); + git_set_exec_path(cmd + 1); else { puts(git_exec_path()); exit(0); @@@ -99,7 -159,7 +159,7 @@@ exit(0); } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { use_pager = 1; - } else if (!strcmp(cmd, "--no-pager")) { + } else if (!strcmp(cmd, "-P") || !strcmp(cmd, "--no-pager")) { use_pager = 0; if (envchanged) *envchanged = 1; @@@ -223,12 -283,19 +283,19 @@@ } (*argv)++; (*argc)--; - } else if (!strcmp(cmd, "--list-builtins")) { - list_builtins(0, '\n'); - exit(0); - } else if (!strcmp(cmd, "--list-parseopt-builtins")) { - list_builtins(NO_PARSEOPT, ' '); - exit(0); + } else if (skip_prefix(cmd, "--list-cmds=", &cmd)) { + if (!strcmp(cmd, "parseopt")) { + struct string_list list = STRING_LIST_INIT_DUP; + int i; + + list_builtins(&list, NO_PARSEOPT); + for (i = 0; i < list.nr; i++) + printf("%s ", list.items[i].string); + string_list_clear(&list, 0); + exit(0); + } else { + exit(list_cmds(cmd)); + } } else { fprintf(stderr, _("unknown option: %s\n"), cmd); usage(git_usage_string); @@@ -392,7 -459,6 +459,7 @@@ static struct cmd_struct commands[] = { "clone", cmd_clone }, { "column", cmd_column, RUN_SETUP_GENTLY }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, + { "commit-graph", cmd_commit_graph, RUN_SETUP }, { "commit-tree", cmd_commit_tree, RUN_SETUP | NO_PARSEOPT }, { "config", cmd_config, RUN_SETUP_GENTLY | DELAY_PAGER_CONFIG }, { "count-objects", cmd_count_objects, RUN_SETUP }, @@@ -466,7 -532,6 +533,7 @@@ { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, { "rm", cmd_rm, RUN_SETUP }, { "send-pack", cmd_send_pack, RUN_SETUP }, + { "serve", cmd_serve, RUN_SETUP }, { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER }, { "show", cmd_show, RUN_SETUP }, { "show-branch", cmd_show_branch, RUN_SETUP }, @@@ -484,7 -549,6 +551,7 @@@ { "update-server-info", cmd_update_server_info, RUN_SETUP }, { "upload-archive", cmd_upload_archive, NO_PARSEOPT }, { "upload-archive--writer", cmd_upload_archive_writer, NO_PARSEOPT }, + { "upload-pack", cmd_upload_pack }, { "var", cmd_var, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "verify-commit", cmd_verify_commit, RUN_SETUP }, { "verify-pack", cmd_verify_pack }, @@@ -511,14 -575,14 +578,14 @@@ int is_builtin(const char *s return !!get_builtin(s); } - static void list_builtins(unsigned int exclude_option, char sep) + static void list_builtins(struct string_list *out, unsigned int exclude_option) { int i; for (i = 0; i < ARRAY_SIZE(commands); i++) { if (exclude_option && (commands[i].option & exclude_option)) continue; - printf("%s%c", commands[i].cmd, sep); + string_list_append(out, commands[i].cmd); } } diff --combined help.c index a4feef2ffe,abf87205b2..dd35fcc133 --- a/help.c +++ b/help.c @@@ -1,17 -1,131 +1,131 @@@ #include "cache.h" #include "config.h" #include "builtin.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "run-command.h" #include "levenshtein.h" #include "help.h" - #include "common-cmds.h" + #include "command-list.h" #include "string-list.h" #include "column.h" #include "version.h" #include "refs.h" #include "parse-options.h" + struct category_description { + uint32_t category; + const char *desc; + }; + static uint32_t common_mask = + CAT_init | CAT_worktree | CAT_info | + CAT_history | CAT_remote; + static struct category_description common_categories[] = { + { CAT_init, N_("start a working area (see also: git help tutorial)") }, + { CAT_worktree, N_("work on the current change (see also: git help everyday)") }, + { CAT_info, N_("examine the history and state (see also: git help revisions)") }, + { CAT_history, N_("grow, mark and tweak your common history") }, + { CAT_remote, N_("collaborate (see also: git help workflows)") }, + { 0, NULL } + }; + static struct category_description main_categories[] = { + { CAT_mainporcelain, N_("Main Porcelain Commands") }, + { CAT_ancillarymanipulators, N_("Ancillary Commands / Manipulators") }, + { CAT_ancillaryinterrogators, N_("Ancillary Commands / Interrogators") }, + { CAT_foreignscminterface, N_("Interacting with Others") }, + { CAT_plumbingmanipulators, N_("Low-level Commands / Manipulators") }, + { CAT_plumbinginterrogators, N_("Low-level Commands / Interrogators") }, + { CAT_synchingrepositories, N_("Low-level Commands / Synching Repositories") }, + { CAT_purehelpers, N_("Low-level Commands / Internal Helpers") }, + { 0, NULL } + }; + + static const char *drop_prefix(const char *name, uint32_t category) + { + const char *new_name; + + if (skip_prefix(name, "git-", &new_name)) + return new_name; + if (category == CAT_guide && skip_prefix(name, "git", &new_name)) + return new_name; + return name; + + } + + static void extract_cmds(struct cmdname_help **p_cmds, uint32_t mask) + { + int i, nr = 0; + struct cmdname_help *cmds; + + if (ARRAY_SIZE(command_list) == 0) + BUG("empty command_list[] is a sign of broken generate-cmdlist.sh"); + + ALLOC_ARRAY(cmds, ARRAY_SIZE(command_list) + 1); + + for (i = 0; i < ARRAY_SIZE(command_list); i++) { + const struct cmdname_help *cmd = command_list + i; + + if (!(cmd->category & mask)) + continue; + + cmds[nr] = *cmd; + cmds[nr].name = drop_prefix(cmd->name, cmd->category); + + nr++; + } + cmds[nr].name = NULL; + *p_cmds = cmds; + } + + static void print_command_list(const struct cmdname_help *cmds, + uint32_t mask, int longest) + { + int i; + + for (i = 0; cmds[i].name; i++) { + if (cmds[i].category & mask) { + printf(" %s ", cmds[i].name); + mput_char(' ', longest - strlen(cmds[i].name)); + puts(_(cmds[i].help)); + } + } + } + + static int cmd_name_cmp(const void *elem1, const void *elem2) + { + const struct cmdname_help *e1 = elem1; + const struct cmdname_help *e2 = elem2; + + return strcmp(e1->name, e2->name); + } + + static void print_cmd_by_category(const struct category_description *catdesc) + { + struct cmdname_help *cmds; + int longest = 0; + int i, nr = 0; + uint32_t mask = 0; + + for (i = 0; catdesc[i].desc; i++) + mask |= catdesc[i].category; + + extract_cmds(&cmds, mask); + + for (i = 0; cmds[i].name; i++, nr++) { + if (longest < strlen(cmds[i].name)) + longest = strlen(cmds[i].name); + } + QSORT(cmds, nr, cmd_name_cmp); + + for (i = 0; catdesc[i].desc; i++) { + uint32_t mask = catdesc[i].category; + const char *desc = catdesc[i].desc; + + printf("\n%s\n", _(desc)); + print_command_list(cmds, mask, longest); + } + free(cmds); + } + void add_cmdname(struct cmdnames *cmds, const char *name, int len) { struct cmdname *ent; @@@ -190,44 -304,116 +304,116 @@@ void list_commands(unsigned int colopts } } - static int cmd_group_cmp(const void *elem1, const void *elem2) + void list_common_cmds_help(void) { - const struct cmdname_help *e1 = elem1; - const struct cmdname_help *e2 = elem2; + puts(_("These are common Git commands used in various situations:")); + print_cmd_by_category(common_categories); + } - if (e1->group < e2->group) - return -1; - if (e1->group > e2->group) - return 1; - return strcmp(e1->name, e2->name); + void list_all_main_cmds(struct string_list *list) + { + struct cmdnames main_cmds, other_cmds; + int i; + + memset(&main_cmds, 0, sizeof(main_cmds)); + memset(&other_cmds, 0, sizeof(other_cmds)); + load_command_list("git-", &main_cmds, &other_cmds); + + for (i = 0; i < main_cmds.cnt; i++) + string_list_append(list, main_cmds.names[i]->name); + + clean_cmdnames(&main_cmds); + clean_cmdnames(&other_cmds); } - void list_common_cmds_help(void) + void list_all_other_cmds(struct string_list *list) { - int i, longest = 0; - int current_grp = -1; + struct cmdnames main_cmds, other_cmds; + int i; - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (longest < strlen(common_cmds[i].name)) - longest = strlen(common_cmds[i].name); - } + memset(&main_cmds, 0, sizeof(main_cmds)); + memset(&other_cmds, 0, sizeof(other_cmds)); + load_command_list("git-", &main_cmds, &other_cmds); - QSORT(common_cmds, ARRAY_SIZE(common_cmds), cmd_group_cmp); + for (i = 0; i < other_cmds.cnt; i++) + string_list_append(list, other_cmds.names[i]->name); - puts(_("These are common Git commands used in various situations:")); + clean_cmdnames(&main_cmds); + clean_cmdnames(&other_cmds); + } + + void list_cmds_by_category(struct string_list *list, + const char *cat) + { + int i, n = ARRAY_SIZE(command_list); + uint32_t cat_id = 0; - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (common_cmds[i].group != current_grp) { - printf("\n%s\n", _(common_cmd_groups[common_cmds[i].group])); - current_grp = common_cmds[i].group; + for (i = 0; category_names[i]; i++) { + if (!strcmp(cat, category_names[i])) { + cat_id = 1UL << i; + break; } + } + if (!cat_id) + die(_("unsupported command listing type '%s'"), cat); + + for (i = 0; i < n; i++) { + struct cmdname_help *cmd = command_list + i; + + if (!(cmd->category & cat_id)) + continue; + string_list_append(list, drop_prefix(cmd->name, cmd->category)); + } + } + + void list_cmds_by_config(struct string_list *list) + { + const char *cmd_list; + + /* + * There's no actual repository setup at this point (and even + * if there is, we don't really care; only global config + * matters). If we accidentally set up a repository, it's ok + * too since the caller (git --list-cmds=) should exit shortly + * anyway. + */ + if (git_config_get_string_const("completion.commands", &cmd_list)) + return; + + string_list_sort(list); + string_list_remove_duplicates(list, 0); + + while (*cmd_list) { + struct strbuf sb = STRBUF_INIT; + const char *p = strchrnul(cmd_list, ' '); - printf(" %s ", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name)); - puts(_(common_cmds[i].help)); + strbuf_add(&sb, cmd_list, p - cmd_list); + if (*cmd_list == '-') + string_list_remove(list, cmd_list + 1, 0); + else + string_list_insert(list, sb.buf); + strbuf_release(&sb); + while (*p == ' ') + p++; + cmd_list = p; } } + void list_common_guides_help(void) + { + struct category_description catdesc[] = { + { CAT_guide, N_("The common Git guides are:") }, + { 0, NULL } + }; + print_cmd_by_category(catdesc); + putchar('\n'); + } + + void list_all_cmds_help(void) + { + print_cmd_by_category(main_categories); + } + int is_in_cmdlist(struct cmdnames *c, const char *s) { int i; @@@ -285,6 -471,7 +471,7 @@@ const char *help_unknown_cmd(const cha { int i, n, best_similarity = 0; struct cmdnames main_cmds, other_cmds; + struct cmdname_help *common_cmds; memset(&main_cmds, 0, sizeof(main_cmds)); memset(&other_cmds, 0, sizeof(other_cmds)); @@@ -299,6 -486,8 +486,8 @@@ QSORT(main_cmds.names, main_cmds.cnt, cmdname_compare); uniq(&main_cmds); + extract_cmds(&common_cmds, common_mask); + /* This abuses cmdname->len for levenshtein distance */ for (i = 0, n = 0; i < main_cmds.cnt; i++) { int cmp = 0; /* avoid compiler stupidity */ @@@ -313,10 -502,10 +502,10 @@@ die(_(bad_interpreter_advice), cmd, cmd); /* Does the candidate appear in common_cmds list? */ - while (n < ARRAY_SIZE(common_cmds) && + while (common_cmds[n].name && (cmp = strcmp(common_cmds[n].name, candidate)) < 0) n++; - if ((n < ARRAY_SIZE(common_cmds)) && !cmp) { + if (common_cmds[n].name && !cmp) { /* Yes, this is one of the common commands */ n++; /* use the entry from common_cmds[] */ if (starts_with(candidate, cmd)) { @@@ -329,6 -518,7 +518,7 @@@ main_cmds.names[i]->len = levenshtein(cmd, candidate, 0, 2, 1, 3) + 1; } + FREE_AND_NULL(common_cmds); QSORT(main_cmds.names, main_cmds.cnt, levenshtein_compare); diff --combined pager.c index 226828f178,1f4688fa03..a768797fcf --- a/pager.c +++ b/pager.c @@@ -2,6 -2,7 +2,7 @@@ #include "config.h" #include "run-command.h" #include "sigchain.h" + #include "alias.h" #ifndef DEFAULT_PAGER #define DEFAULT_PAGER "less" @@@ -109,15 -110,10 +110,15 @@@ void setup_pager(void return; /* - * force computing the width of the terminal before we redirect - * the standard output to the pager. + * After we redirect standard output, we won't be able to use an ioctl + * to get the terminal size. Let's grab it now, and then set $COLUMNS + * to communicate it to any sub-processes. */ - (void) term_columns(); + { + char buf[64]; + xsnprintf(buf, sizeof(buf), "%d", term_columns()); + setenv("COLUMNS", buf, 0); + } setenv("GIT_PAGER_IN_USE", "true", 1); diff --combined sequencer.c index 72b4d8ecae,1288a36ebd..560fc9b67d --- a/sequencer.c +++ b/sequencer.c @@@ -7,7 -7,7 +7,7 @@@ #include "sequencer.h" #include "tag.h" #include "run-command.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "utf8.h" #include "cache-tree.h" #include "diff.h" @@@ -23,10 -23,7 +23,11 @@@ #include "hashmap.h" #include "notes-utils.h" #include "sigchain.h" +#include "unpack-trees.h" +#include "worktree.h" +#include "oidmap.h" +#include "oidset.h" + #include "alias.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@@ -78,6 -75,13 +79,6 @@@ static GIT_PATH_FUNC(rebase_path_messag * previous commit and from the first squash/fixup commit are written * to it. The commit message for each subsequent squash/fixup commit * is appended to the file as it is processed. - * - * The first line of the file is of the form - * # This is a combination of $count commits. - * where $count is the number of commits whose messages have been - * written to the file so far (including the initial "pick" commit). - * Each time that a commit message is processed, this line is read and - * updated. It is deleted just before the combined commit is made. */ static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") /* @@@ -88,11 -92,6 +89,11 @@@ * commit without opening the editor.) */ static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") +/* + * This file contains the list fixup/squash commands that have been + * accumulated into message-fixup or message-squash so far. + */ +static GIT_PATH_FUNC(rebase_path_current_fixups, "rebase-merge/current-fixups") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@@ -122,19 -121,6 +123,19 @@@ static GIT_PATH_FUNC(rebase_path_stoppe static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") static GIT_PATH_FUNC(rebase_path_rewritten_pending, "rebase-merge/rewritten-pending") + +/* + * The path of the file containig the OID of the "squash onto" commit, i.e. + * the dummy commit used for `reset [new root]`. + */ +static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto") + +/* + * The path of the file listing refs that need to be deleted after the rebase + * finishes. This is used by the `label` command to record the need for cleanup. + */ +static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete") + /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@@ -142,7 -128,6 +143,7 @@@ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_signoff, "rebase-merge/signoff") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") @@@ -260,35 -245,18 +261,35 @@@ static const char *gpg_sign_opt_quoted( int sequencer_remove_state(struct replay_opts *opts) { - struct strbuf dir = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; int i; + if (is_rebase_i(opts) && + strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) { + char *p = buf.buf; + while (*p) { + char *eol = strchr(p, '\n'); + if (eol) + *eol = '\0'; + if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) + warning(_("could not delete '%s'"), p); + if (!eol) + break; + p = eol + 1; + } + } + free(opts->gpg_sign); free(opts->strategy); for (i = 0; i < opts->xopts_nr; i++) free(opts->xopts[i]); free(opts->xopts); + strbuf_release(&opts->current_fixups); - strbuf_addstr(&dir, get_dir(opts)); - remove_dir_recursively(&dir, 0); - strbuf_release(&dir); + strbuf_reset(&buf); + strbuf_addstr(&buf, get_dir(opts)); + remove_dir_recursively(&buf, 0); + strbuf_release(&buf); return 0; } @@@ -378,14 -346,12 +379,14 @@@ static int write_message(const void *bu if (msg_fd < 0) return error_errno(_("could not lock '%s'"), filename); if (write_in_full(msg_fd, buf, len) < 0) { + error_errno(_("could not write to '%s'"), filename); rollback_lock_file(&msg_file); - return error_errno(_("could not write to '%s'"), filename); + return -1; } if (append_eol && write(msg_fd, "\n", 1) < 0) { + error_errno(_("could not write eol to '%s'"), filename); rollback_lock_file(&msg_file); - return error_errno(_("could not write eol to '%s'"), filename); + return -1; } if (commit_lock_file(&msg_file) < 0) return error(_("failed to finalize '%s'"), filename); @@@ -475,8 -441,7 +476,8 @@@ static int fast_forward_to(const struc transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, "HEAD", - to, unborn ? &null_oid : from, + to, unborn && !is_rebase_i(opts) ? + &null_oid : from, 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); @@@ -535,8 -500,8 +536,8 @@@ static int do_recursive_merge(struct co o.show_rename_progress = 1; head_tree = parse_tree_indirect(head); - next_tree = next ? next->tree : empty_tree(); - base_tree = base ? base->tree : empty_tree(); + next_tree = next ? get_commit_tree(next) : empty_tree(); + base_tree = base ? get_commit_tree(base) : empty_tree(); for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) parse_merge_opt(&o, *xopt); @@@ -568,23 -533,9 +569,23 @@@ return !clean; } +static struct object_id *get_cache_tree_oid(void) +{ + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree)) + if (cache_tree_update(&the_index, 0)) { + error(_("unable to update cache tree")); + return NULL; + } + + return &active_cache_tree->oid; +} + static int is_index_unchanged(void) { - struct object_id head_oid; + struct object_id head_oid, *cache_tree_oid; struct commit *head_commit; if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) @@@ -603,10 -554,15 +604,10 @@@ if (parse_commit(head_commit)) return -1; - if (!active_cache_tree) - active_cache_tree = cache_tree(); - - if (!cache_tree_fully_valid(active_cache_tree)) - if (cache_tree_update(&the_index, 0)) - return error(_("unable to update cache tree")); + if (!(cache_tree_oid = get_cache_tree_oid())) + return -1; - return !oidcmp(&active_cache_tree->oid, - &head_commit->tree->object.oid); + return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit)); } static int write_author_script(const char *message) @@@ -698,52 -654,6 +699,52 @@@ static char *get_author(const char *mes return NULL; } +/* Read author-script and return an ident line (author timestamp) */ +static const char *read_author_ident(struct strbuf *buf) +{ + const char *keys[] = { + "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE=" + }; + char *in, *out, *eol; + int i = 0, len; + + if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0) + return NULL; + + /* dequote values and construct ident line in-place */ + for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) { + if (!skip_prefix(in, keys[i], (const char **)&in)) { + warning("could not parse '%s' (looking for '%s'", + rebase_path_author_script(), keys[i]); + return NULL; + } + + eol = strchrnul(in, '\n'); + *eol = '\0'; + sq_dequote(in); + len = strlen(in); + + if (i > 0) /* separate values by spaces */ + *(out++) = ' '; + if (i == 1) /* email needs to be surrounded by <...> */ + *(out++) = '<'; + memmove(out, in, len); + out += len; + if (i == 1) /* email needs to be surrounded by <...> */ + *(out++) = '>'; + in = eol + 1; + } + + if (i < 3) { + warning("could not parse '%s' (looking for '%s')", + rebase_path_author_script(), keys[i]); + return NULL; + } + + buf->len = out - buf->buf; + return buf->buf; +} + static const char staged_changes_advice[] = N_("you have staged changes in your working tree\n" "If these changes are meant to be squashed into the previous commit, run:\n" @@@ -763,7 -673,6 +764,7 @@@ #define AMEND_MSG (1<<2) #define CLEANUP_MSG (1<<3) #define VERIFY_MSG (1<<4) +#define CREATE_ROOT_COMMIT (1<<5) /* * If we are cherry-pick, and if the merge did not result in @@@ -783,40 -692,6 +784,40 @@@ static int run_git_commit(const char *d struct child_process cmd = CHILD_PROCESS_INIT; const char *value; + if (flags & CREATE_ROOT_COMMIT) { + struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT; + const char *author = is_rebase_i(opts) ? + read_author_ident(&script) : NULL; + struct object_id root_commit, *cache_tree_oid; + int res = 0; + + if (!defmsg) + BUG("root commit without message"); + + if (!(cache_tree_oid = get_cache_tree_oid())) + res = -1; + + if (!res) + res = strbuf_read_file(&msg, defmsg, 0); + + if (res <= 0) + res = error_errno(_("could not read '%s'"), defmsg); + else + res = commit_tree(msg.buf, msg.len, cache_tree_oid, + NULL, &root_commit, author, + opts->gpg_sign); + + strbuf_release(&msg); + strbuf_release(&script); + if (!res) { + update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL, + REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR); + res = update_ref(NULL, "HEAD", &root_commit, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); + } + return res < 0 ? error(_("writing root commit")) : 0; + } + cmd.git_cmd = 1; if (is_rebase_i(opts)) { @@@ -843,8 -718,6 +844,8 @@@ argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); if (defmsg) argv_array_pushl(&cmd.args, "-F", defmsg, NULL); + else if (!(flags & EDIT_MSG)) + argv_array_pushl(&cmd.args, "-C", "HEAD", NULL); if ((flags & CLEANUP_MSG)) argv_array_push(&cmd.args, "--cleanup=strip"); if ((flags & EDIT_MSG)) @@@ -1246,8 -1119,8 +1247,8 @@@ static int try_to_commit(struct strbuf } if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ? - ¤t_head->tree->object.oid : - &empty_tree_oid, &tree)) { + get_commit_tree_oid(current_head) : + the_hash_algo->empty_tree, &tree)) { res = 1; /* run 'git commit' to display error message */ goto out; } @@@ -1276,8 -1149,6 +1277,8 @@@ goto out; } + reset_ident_date(); + if (commit_tree_extended(msg->buf, msg->len, &tree, parents, oid, author, opts->gpg_sign, extra)) { res = error(_("failed to write commit object")); @@@ -1307,8 -1178,7 +1308,8 @@@ static int do_commit(const char *msg_fi { int res = 1; - if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) { + if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) && + !(flags & CREATE_ROOT_COMMIT)) { struct object_id oid; struct strbuf sb = STRBUF_INIT; @@@ -1347,12 -1217,12 +1348,12 @@@ static int is_original_commit_empty(str if (parse_commit(parent)) return error(_("could not parse parent commit %s"), oid_to_hex(&parent->object.oid)); - ptree_oid = &parent->tree->object.oid; + ptree_oid = get_commit_tree_oid(parent); } else { ptree_oid = the_hash_algo->empty_tree; /* commit is root */ } - return !oidcmp(ptree_oid, &commit->tree->object.oid); + return !oidcmp(ptree_oid, get_commit_tree_oid(commit)); } /* @@@ -1408,9 -1278,6 +1409,9 @@@ enum todo_command TODO_SQUASH, /* commands that do something else than handling a single commit */ TODO_EXEC, + TODO_LABEL, + TODO_RESET, + TODO_MERGE, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP, TODO_DROP, @@@ -1429,9 -1296,6 +1430,9 @@@ static struct { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, + { 'l', "label" }, + { 't', "reset" }, + { 'm', "merge" }, { 0, "noop" }, { 'd', "drop" }, { 0, NULL } @@@ -1461,43 -1325,38 +1462,43 @@@ static int is_fixup(enum todo_command c return command == TODO_FIXUP || command == TODO_SQUASH; } +/* Does this command create a (non-merge) commit? */ +static int is_pick_or_similar(enum todo_command command) +{ + switch (command) { + case TODO_PICK: + case TODO_REVERT: + case TODO_EDIT: + case TODO_REWORD: + case TODO_FIXUP: + case TODO_SQUASH: + return 1; + default: + return 0; + } +} + static int update_squash_messages(enum todo_command command, struct commit *commit, struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; - int count, res; + int res; const char *message, *body; - if (file_exists(rebase_path_squash_msg())) { + if (opts->current_fixup_count > 0) { struct strbuf header = STRBUF_INIT; - char *eol, *p; + char *eol; - if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 9) <= 0) return error(_("could not read '%s'"), rebase_path_squash_msg()); - p = buf.buf + 1; - eol = strchrnul(buf.buf, '\n'); - if (buf.buf[0] != comment_line_char || - (p += strcspn(p, "0123456789\n")) == eol) - return error(_("unexpected 1st line of squash message:" - "\n\n\t%.*s"), - (int)(eol - buf.buf), buf.buf); - count = strtol(p, NULL, 10); - - if (count < 1) - return error(_("invalid 1st line of squash message:\n" - "\n\t%.*s"), - (int)(eol - buf.buf), buf.buf); + eol = buf.buf[0] != comment_line_char ? + buf.buf : strchrnul(buf.buf, '\n'); strbuf_addf(&header, "%c ", comment_line_char); - strbuf_addf(&header, - _("This is a combination of %d commits."), ++count); + strbuf_addf(&header, _("This is a combination of %d commits."), + opts->current_fixup_count + 2); strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); strbuf_release(&header); } else { @@@ -1520,8 -1379,10 +1521,8 @@@ rebase_path_fixup_msg()); } - count = 2; strbuf_addf(&buf, "%c ", comment_line_char); - strbuf_addf(&buf, _("This is a combination of %d commits."), - count); + strbuf_addf(&buf, _("This is a combination of %d commits."), 2); strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addstr(&buf, _("This is the 1st commit message:")); strbuf_addstr(&buf, "\n\n"); @@@ -1538,14 -1399,13 +1539,14 @@@ if (command == TODO_SQUASH) { unlink(rebase_path_fixup_msg()); strbuf_addf(&buf, "\n%c ", comment_line_char); - strbuf_addf(&buf, _("This is the commit message #%d:"), count); + strbuf_addf(&buf, _("This is the commit message #%d:"), + ++opts->current_fixup_count); strbuf_addstr(&buf, "\n\n"); strbuf_addstr(&buf, body); } else if (command == TODO_FIXUP) { strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addf(&buf, _("The commit message #%d will be skipped:"), - count); + ++opts->current_fixup_count); strbuf_addstr(&buf, "\n\n"); strbuf_add_commented_lines(&buf, body, strlen(body)); } else @@@ -1554,17 -1414,6 +1555,17 @@@ res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0); strbuf_release(&buf); + + if (!res) { + strbuf_addf(&opts->current_fixups, "%s%s %s", + opts->current_fixups.len ? "\n" : "", + command_to_string(command), + oid_to_hex(&commit->object.oid)); + res = write_message(opts->current_fixups.buf, + opts->current_fixups.len, + rebase_path_current_fixups(), 0); + } + return res; } @@@ -1630,16 -1479,9 +1631,16 @@@ static int do_pick_commit(enum todo_com return error(_("your index file is unmerged.")); } else { unborn = get_oid("HEAD", &head); - if (unborn) + /* Do we want to generate a root commit? */ + if (is_pick_or_similar(command) && opts->have_squash_onto && + !oidcmp(&head, &opts->squash_onto)) { + if (is_fixup(command)) + return error(_("cannot fixup root commit")); + flags |= CREATE_ROOT_COMMIT; + unborn = 1; + } else if (unborn) oidcpy(&head, the_hash_algo->empty_tree); - if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", + if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD", NULL, 0)) return error_dirty_index(opts); } @@@ -1763,7 -1605,7 +1764,7 @@@ } } - if (opts->signoff) + if (opts->signoff && !is_fixup(command)) append_signoff(&msgbuf, 0, 0); if (is_rebase_i(opts) && write_author_script(msg.message) < 0) @@@ -1834,9 -1676,6 +1835,9 @@@ fast_forward_edit if (!res && final_fixup) { unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); + unlink(rebase_path_current_fixups()); + strbuf_reset(&opts->current_fixups); + opts->current_fixup_count = 0; } leave: @@@ -1884,14 -1723,9 +1885,14 @@@ static int read_and_refresh_cache(struc return 0; } +enum todo_item_flags { + TODO_EDIT_MERGE_MSG = 1 +}; + struct todo_item { enum todo_command command; struct commit *commit; + unsigned int flags; const char *arg; int arg_len; size_t offset_in_buf; @@@ -1926,8 -1760,6 +1927,8 @@@ static int parse_insn_line(struct todo_ char *end_of_object_name; int i, saved, status, padding; + item->flags = 0; + /* left-trim */ bol += strspn(bol, " \t"); @@@ -1969,29 -1801,13 +1970,29 @@@ return error(_("missing arguments for %s"), command_to_string(item->command)); - if (item->command == TODO_EXEC) { + if (item->command == TODO_EXEC || item->command == TODO_LABEL || + item->command == TODO_RESET) { item->commit = NULL; item->arg = bol; item->arg_len = (int)(eol - bol); return 0; } + if (item->command == TODO_MERGE) { + if (skip_prefix(bol, "-C", &bol)) + bol += strspn(bol, " \t"); + else if (skip_prefix(bol, "-c", &bol)) { + bol += strspn(bol, " \t"); + item->flags |= TODO_EDIT_MERGE_MSG; + } else { + item->flags |= TODO_EDIT_MERGE_MSG; + item->commit = NULL; + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@@ -2053,23 -1869,6 +2054,23 @@@ static int count_commands(struct todo_l return count; } +static int get_item_line_offset(struct todo_list *todo_list, int index) +{ + return index < todo_list->nr ? + todo_list->items[index].offset_in_buf : todo_list->buf.len; +} + +static const char *get_item_line(struct todo_list *todo_list, int index) +{ + return todo_list->buf.buf + get_item_line_offset(todo_list, index); +} + +static int get_item_line_length(struct todo_list *todo_list, int index) +{ + return get_item_line_offset(todo_list, index + 1) + - get_item_line_offset(todo_list, index); +} + static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path) { int fd; @@@ -2245,30 -2044,9 +2246,30 @@@ static int read_populate_opts(struct re if (file_exists(rebase_path_verbose())) opts->verbose = 1; + if (file_exists(rebase_path_signoff())) { + opts->allow_ff = 0; + opts->signoff = 1; + } + read_strategy_opts(opts, &buf); strbuf_release(&buf); + if (read_oneliner(&opts->current_fixups, + rebase_path_current_fixups(), 1)) { + const char *p = opts->current_fixups.buf; + opts->current_fixup_count = 1; + while ((p = strchr(p, '\n'))) { + opts->current_fixup_count++; + p++; + } + } + + if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) { + if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) + return error(_("unusable squash-onto")); + opts->have_squash_onto = 1; + } + return 0; } @@@ -2342,9 -2120,9 +2343,9 @@@ static int save_head(const char *head written = write_in_full(fd, buf.buf, buf.len); strbuf_release(&buf); if (written < 0) { + error_errno(_("could not write to '%s'"), git_path_head_file()); rollback_lock_file(&head_lock); - return error_errno(_("could not write to '%s'"), - git_path_head_file()); + return -1; } if (commit_lock_file(&head_lock) < 0) return error(_("failed to finalize '%s'"), git_path_head_file()); @@@ -2465,27 -2243,29 +2466,27 @@@ static int save_todo(struct todo_list * fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("could not lock '%s'"), todo_path); - offset = next < todo_list->nr ? - todo_list->items[next].offset_in_buf : todo_list->buf.len; + offset = get_item_line_offset(todo_list, next); if (write_in_full(fd, todo_list->buf.buf + offset, todo_list->buf.len - offset) < 0) return error_errno(_("could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'"), todo_path); - if (is_rebase_i(opts)) { - const char *done_path = rebase_path_done(); - int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); - int prev_offset = !next ? 0 : - todo_list->items[next - 1].offset_in_buf; + if (is_rebase_i(opts) && next > 0) { + const char *done = rebase_path_done(); + int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666); + int ret = 0; - if (fd >= 0 && offset > prev_offset && - write_in_full(fd, todo_list->buf.buf + prev_offset, - offset - prev_offset) < 0) { - close(fd); - return error_errno(_("could not write to '%s'"), - done_path); - } - if (fd >= 0) - close(fd); + if (fd < 0) + return 0; + if (write_in_full(fd, get_item_line(todo_list, next - 1), + get_item_line_length(todo_list, next - 1)) + < 0) + ret = error_errno(_("could not write to '%s'"), done); + if (close(fd) < 0) + ret = error_errno(_("failed to finalize '%s'"), done); + return ret; } return 0; } @@@ -2613,9 -2393,10 +2614,9 @@@ static int error_with_patch(struct comm static int error_failed_squash(struct commit *commit, struct replay_opts *opts, int subject_len, const char *subject) { - if (rename(rebase_path_squash_msg(), rebase_path_message())) - return error(_("could not rename '%s' to '%s'"), + if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666)) + return error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), rebase_path_message()); - unlink(rebase_path_fixup_msg()); unlink(git_path_merge_msg()); if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666)) return error(_("could not copy '%s' to '%s'"), @@@ -2668,377 -2449,6 +2669,377 @@@ static int do_exec(const char *command_ return status; } +static int safe_append(const char *filename, const char *fmt, ...) +{ + va_list ap; + struct lock_file lock = LOCK_INIT; + int fd = hold_lock_file_for_update(&lock, filename, + LOCK_REPORT_ON_ERROR); + struct strbuf buf = STRBUF_INIT; + + if (fd < 0) + return -1; + + if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) { + error_errno(_("could not read '%s'"), filename); + rollback_lock_file(&lock); + return -1; + } + strbuf_complete(&buf, '\n'); + va_start(ap, fmt); + strbuf_vaddf(&buf, fmt, ap); + va_end(ap); + + if (write_in_full(fd, buf.buf, buf.len) < 0) { + error_errno(_("could not write to '%s'"), filename); + strbuf_release(&buf); + rollback_lock_file(&lock); + return -1; + } + if (commit_lock_file(&lock) < 0) { + strbuf_release(&buf); + rollback_lock_file(&lock); + return error(_("failed to finalize '%s'"), filename); + } + + strbuf_release(&buf); + return 0; +} + +static int do_label(const char *name, int len) +{ + struct ref_store *refs = get_main_ref_store(the_repository); + struct ref_transaction *transaction; + struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT; + struct strbuf msg = STRBUF_INIT; + int ret = 0; + struct object_id head_oid; + + if (len == 1 && *name == '#') + return error("Illegal label name: '%.*s'", len, name); + + strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); + strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name); + + transaction = ref_store_transaction_begin(refs, &err); + if (!transaction) { + error("%s", err.buf); + ret = -1; + } else if (get_oid("HEAD", &head_oid)) { + error(_("could not read HEAD")); + ret = -1; + } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid, + NULL, 0, msg.buf, &err) < 0 || + ref_transaction_commit(transaction, &err)) { + error("%s", err.buf); + ret = -1; + } + ref_transaction_free(transaction); + strbuf_release(&err); + strbuf_release(&msg); + + if (!ret) + ret = safe_append(rebase_path_refs_to_delete(), + "%s\n", ref_name.buf); + strbuf_release(&ref_name); + + return ret; +} + +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...); + +static int do_reset(const char *name, int len, struct replay_opts *opts) +{ + struct strbuf ref_name = STRBUF_INIT; + struct object_id oid; + struct lock_file lock = LOCK_INIT; + struct tree_desc desc; + struct tree *tree; + struct unpack_trees_options unpack_tree_opts; + int ret = 0, i; + + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) + return -1; + + if (len == 10 && !strncmp("[new root]", name, len)) { + if (!opts->have_squash_onto) { + const char *hex; + if (commit_tree("", 0, the_hash_algo->empty_tree, + NULL, &opts->squash_onto, + NULL, NULL)) + return error(_("writing fake root commit")); + opts->have_squash_onto = 1; + hex = oid_to_hex(&opts->squash_onto); + if (write_message(hex, strlen(hex), + rebase_path_squash_onto(), 0)) + return error(_("writing squash-onto")); + } + oidcpy(&oid, &opts->squash_onto); + } else { + /* Determine the length of the label */ + for (i = 0; i < len; i++) + if (isspace(name[i])) + len = i; + + strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); + if (get_oid(ref_name.buf, &oid) && + get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) { + error(_("could not read '%s'"), ref_name.buf); + rollback_lock_file(&lock); + strbuf_release(&ref_name); + return -1; + } + } + + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(&unpack_tree_opts, "reset"); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = &the_index; + unpack_tree_opts.dst_index = &the_index; + unpack_tree_opts.fn = oneway_merge; + unpack_tree_opts.merge = 1; + unpack_tree_opts.update = 1; + + if (read_cache_unmerged()) { + rollback_lock_file(&lock); + strbuf_release(&ref_name); + return error_resolve_conflict(_(action_name(opts))); + } + + if (!fill_tree_descriptor(&desc, &oid)) { + error(_("failed to find tree of %s"), oid_to_hex(&oid)); + rollback_lock_file(&lock); + free((void *)desc.buffer); + strbuf_release(&ref_name); + return -1; + } + + if (unpack_trees(1, &desc, &unpack_tree_opts)) { + rollback_lock_file(&lock); + free((void *)desc.buffer); + strbuf_release(&ref_name); + return -1; + } + + tree = parse_tree_indirect(&oid); + prime_cache_tree(&the_index, tree); + + if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0) + ret = error(_("could not write index")); + free((void *)desc.buffer); + + if (!ret) + ret = update_ref(reflog_message(opts, "reset", "'%.*s'", + len, name), "HEAD", &oid, + NULL, 0, UPDATE_REFS_MSG_ON_ERR); + + strbuf_release(&ref_name); + return ret; +} + +static int do_merge(struct commit *commit, const char *arg, int arg_len, + int flags, struct replay_opts *opts) +{ + int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ? + EDIT_MSG | VERIFY_MSG : 0; + struct strbuf ref_name = STRBUF_INIT; + struct commit *head_commit, *merge_commit, *i; + struct commit_list *bases, *j, *reversed = NULL; + struct merge_options o; + int merge_arg_len, oneline_offset, can_fast_forward, ret; + static struct lock_file lock; + const char *p; + + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { + ret = -1; + goto leave_merge; + } + + head_commit = lookup_commit_reference_by_name("HEAD"); + if (!head_commit) { + ret = error(_("cannot merge without a current revision")); + goto leave_merge; + } + + oneline_offset = arg_len; + merge_arg_len = strcspn(arg, " \t\n"); + p = arg + merge_arg_len; + p += strspn(p, " \t\n"); + if (*p == '#' && (!p[1] || isspace(p[1]))) { + p += 1 + strspn(p + 1, " \t\n"); + oneline_offset = p - arg; + } else if (p - arg < arg_len) + BUG("octopus merges are not supported yet: '%s'", p); + + strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg); + merge_commit = lookup_commit_reference_by_name(ref_name.buf); + if (!merge_commit) { + /* fall back to non-rewritten ref or commit */ + strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0); + merge_commit = lookup_commit_reference_by_name(ref_name.buf); + } + + if (!merge_commit) { + ret = error(_("could not resolve '%s'"), ref_name.buf); + goto leave_merge; + } + + if (opts->have_squash_onto && + !oidcmp(&head_commit->object.oid, &opts->squash_onto)) { + /* + * When the user tells us to "merge" something into a + * "[new root]", let's simply fast-forward to the merge head. + */ + rollback_lock_file(&lock); + ret = fast_forward_to(&merge_commit->object.oid, + &head_commit->object.oid, 0, opts); + goto leave_merge; + } + + if (commit) { + const char *message = get_commit_buffer(commit, NULL); + const char *body; + int len; + + if (!message) { + ret = error(_("could not get commit message of '%s'"), + oid_to_hex(&commit->object.oid)); + goto leave_merge; + } + write_author_script(message); + find_commit_subject(message, &body); + len = strlen(body); + ret = write_message(body, len, git_path_merge_msg(), 0); + unuse_commit_buffer(commit, message); + if (ret) { + error_errno(_("could not write '%s'"), + git_path_merge_msg()); + goto leave_merge; + } + } else { + struct strbuf buf = STRBUF_INIT; + int len; + + strbuf_addf(&buf, "author %s", git_author_info(0)); + write_author_script(buf.buf); + strbuf_reset(&buf); + + if (oneline_offset < arg_len) { + p = arg + oneline_offset; + len = arg_len - oneline_offset; + } else { + strbuf_addf(&buf, "Merge branch '%.*s'", + merge_arg_len, arg); + p = buf.buf; + len = buf.len; + } + + ret = write_message(p, len, git_path_merge_msg(), 0); + strbuf_release(&buf); + if (ret) { + error_errno(_("could not write '%s'"), + git_path_merge_msg()); + goto leave_merge; + } + } + + /* + * If HEAD is not identical to the first parent of the original merge + * commit, we cannot fast-forward. + */ + can_fast_forward = opts->allow_ff && commit && commit->parents && + !oidcmp(&commit->parents->item->object.oid, + &head_commit->object.oid); + + /* + * If the merge head is different from the original one, we cannot + * fast-forward. + */ + if (can_fast_forward) { + struct commit_list *second_parent = commit->parents->next; + + if (second_parent && !second_parent->next && + oidcmp(&merge_commit->object.oid, + &second_parent->item->object.oid)) + can_fast_forward = 0; + } + + if (can_fast_forward && commit->parents->next && + !commit->parents->next->next && + !oidcmp(&commit->parents->next->item->object.oid, + &merge_commit->object.oid)) { + rollback_lock_file(&lock); + ret = fast_forward_to(&commit->object.oid, + &head_commit->object.oid, 0, opts); + goto leave_merge; + } + + write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ, + git_path_merge_head(), 0); + write_message("no-ff", 5, git_path_merge_mode(), 0); + + bases = get_merge_bases(head_commit, merge_commit); + if (bases && !oidcmp(&merge_commit->object.oid, + &bases->item->object.oid)) { + ret = 0; + /* skip merging an ancestor of HEAD */ + goto leave_merge; + } + + for (j = bases; j; j = j->next) + commit_list_insert(j->item, &reversed); + free_commit_list(bases); + + read_cache(); + init_merge_options(&o); + o.branch1 = "HEAD"; + o.branch2 = ref_name.buf; + o.buffer_output = 2; + + ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i); + if (ret <= 0) + fputs(o.obuf.buf, stdout); + strbuf_release(&o.obuf); + if (ret < 0) { + error(_("could not even attempt to merge '%.*s'"), + merge_arg_len, arg); + goto leave_merge; + } + /* + * The return value of merge_recursive() is 1 on clean, and 0 on + * unclean merge. + * + * Let's reverse that, so that do_merge() returns 0 upon success and + * 1 upon failed merge (keeping the return value -1 for the cases where + * we will want to reschedule the `merge` command). + */ + ret = !ret; + + if (active_cache_changed && + write_locked_index(&the_index, &lock, COMMIT_LOCK)) { + ret = error(_("merge: Unable to write new index file")); + goto leave_merge; + } + + rollback_lock_file(&lock); + if (ret) + rerere(opts->allow_rerere_auto); + else + /* + * In case of problems, we now want to return a positive + * value (a negative one would indicate that the `merge` + * command needs to be rescheduled). + */ + ret = !!run_git_commit(git_path_merge_msg(), opts, + run_commit_flags); + +leave_merge: + strbuf_release(&ref_name); + rollback_lock_file(&lock); + return ret; +} + static int is_final_fixup(struct todo_list *todo_list) { int i = todo_list->current; @@@ -3129,20 -2539,9 +3130,20 @@@ static const char *reflog_message(struc return buf.buf; } +static const char rescheduled_advice[] = +N_("Could not execute the todo command\n" +"\n" +" %.*s" +"\n" +"It has been rescheduled; To edit the command before continuing, please\n" +"edit the todo list first:\n" +"\n" +" git rebase --edit-todo\n" +" git rebase --continue\n"); + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res = 0; + int res = 0, reschedule = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@@ -3185,11 -2584,6 +3186,11 @@@ opts, is_final_fixup(todo_list)); if (is_rebase_i(opts) && res < 0) { /* Reschedule */ + advise(_(rescheduled_advice), + get_item_line_length(todo_list, + todo_list->current), + get_item_line(todo_list, + todo_list->current)); todo_list->current--; if (save_todo(todo_list, opts)) return -1; @@@ -3213,7 -2607,7 +3214,7 @@@ intend_to_amend(); return error_failed_squash(item->commit, opts, item->arg_len, item->arg); - } else if (res && is_rebase_i(opts)) + } else if (res && is_rebase_i(opts) && item->commit) return res | error_with_patch(item->commit, item->arg, item->arg_len, opts, res, item->command == TODO_REWORD); @@@ -3239,44 -2633,9 +3240,44 @@@ /* `current` will be incremented below */ todo_list->current = -1; } + } else if (item->command == TODO_LABEL) { + if ((res = do_label(item->arg, item->arg_len))) + reschedule = 1; + } else if (item->command == TODO_RESET) { + if ((res = do_reset(item->arg, item->arg_len, opts))) + reschedule = 1; + } else if (item->command == TODO_MERGE) { + if ((res = do_merge(item->commit, + item->arg, item->arg_len, + item->flags, opts)) < 0) + reschedule = 1; + else if (item->commit) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); + if (res > 0) + /* failed with merge conflicts */ + return error_with_patch(item->commit, + item->arg, + item->arg_len, opts, + res, 0); } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); + if (reschedule) { + advise(_(rescheduled_advice), + get_item_line_length(todo_list, + todo_list->current), + get_item_line(todo_list, todo_list->current)); + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + if (item->commit) + return error_with_patch(item->commit, + item->arg, + item->arg_len, opts, + res, 0); + } + todo_list->current++; if (res) return res; @@@ -3403,16 -2762,19 +3404,16 @@@ static int continue_single_pick(void return run_command_v_opt(argv, RUN_GIT_CMD); } -static int commit_staged_changes(struct replay_opts *opts) +static int commit_staged_changes(struct replay_opts *opts, + struct todo_list *todo_list) { unsigned int flags = ALLOW_EMPTY | EDIT_MSG; + unsigned int final_fixup = 0, is_clean; if (has_unstaged_changes(1)) return error(_("cannot rebase: You have unstaged changes.")); - if (!has_uncommitted_changes(0)) { - const char *cherry_pick_head = git_path_cherry_pick_head(); - if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) - return error(_("could not remove CHERRY_PICK_HEAD")); - return 0; - } + is_clean = !has_uncommitted_changes(0); if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; @@@ -3425,107 -2787,19 +3426,107 @@@ if (get_oid_hex(rev.buf, &to_amend)) return error(_("invalid contents: '%s'"), rebase_path_amend()); - if (oidcmp(&head, &to_amend)) + if (!is_clean && oidcmp(&head, &to_amend)) return error(_("\nYou have uncommitted changes in your " "working tree. Please, commit them\n" "first and then run 'git rebase " "--continue' again.")); + /* + * When skipping a failed fixup/squash, we need to edit the + * commit message, the current fixup list and count, and if it + * was the last fixup/squash in the chain, we need to clean up + * the commit message and if there was a squash, let the user + * edit it. + */ + if (is_clean && !oidcmp(&head, &to_amend) && + opts->current_fixup_count > 0 && + file_exists(rebase_path_stopped_sha())) { + const char *p = opts->current_fixups.buf; + int len = opts->current_fixups.len; + + opts->current_fixup_count--; + if (!len) + BUG("Incorrect current_fixups:\n%s", p); + while (len && p[len - 1] != '\n') + len--; + strbuf_setlen(&opts->current_fixups, len); + if (write_message(p, len, rebase_path_current_fixups(), + 0) < 0) + return error(_("could not write file: '%s'"), + rebase_path_current_fixups()); + + /* + * If a fixup/squash in a fixup/squash chain failed, the + * commit message is already correct, no need to commit + * it again. + * + * Only if it is the final command in the fixup/squash + * chain, and only if the chain is longer than a single + * fixup/squash command (which was just skipped), do we + * actually need to re-commit with a cleaned up commit + * message. + */ + if (opts->current_fixup_count > 0 && + !is_fixup(peek_command(todo_list, 0))) { + final_fixup = 1; + /* + * If there was not a single "squash" in the + * chain, we only need to clean up the commit + * message, no need to bother the user with + * opening the commit message in the editor. + */ + if (!starts_with(p, "squash ") && + !strstr(p, "\nsquash ")) + flags = (flags & ~EDIT_MSG) | CLEANUP_MSG; + } else if (is_fixup(peek_command(todo_list, 0))) { + /* + * We need to update the squash message to skip + * the latest commit message. + */ + struct commit *commit; + const char *path = rebase_path_squash_msg(); + + if (parse_head(&commit) || + !(p = get_commit_buffer(commit, NULL)) || + write_message(p, strlen(p), path, 0)) { + unuse_commit_buffer(commit, p); + return error(_("could not write file: " + "'%s'"), path); + } + unuse_commit_buffer(commit, p); + } + } strbuf_release(&rev); flags |= AMEND_MSG; } - if (run_git_commit(rebase_path_message(), opts, flags)) + if (is_clean) { + const char *cherry_pick_head = git_path_cherry_pick_head(); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error(_("could not remove CHERRY_PICK_HEAD")); + if (!final_fixup) + return 0; + } + + if (run_git_commit(final_fixup ? NULL : rebase_path_message(), + opts, flags)) return error(_("could not commit staged changes.")); unlink(rebase_path_amend()); + if (final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } + if (opts->current_fixup_count > 0) { + /* + * Whether final fixup or not, we just cleaned up the commit + * message... + */ + unlink(rebase_path_current_fixups()); + strbuf_reset(&opts->current_fixups); + opts->current_fixup_count = 0; + } return 0; } @@@ -3537,16 -2811,14 +3538,16 @@@ int sequencer_continue(struct replay_op if (read_and_refresh_cache(opts)) return -1; + if (read_populate_opts(opts)) + return -1; if (is_rebase_i(opts)) { - if (commit_staged_changes(opts)) + if ((res = read_populate_todo(&todo_list, opts))) + goto release_todo_list; + if (commit_staged_changes(opts, &todo_list)) return -1; } else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); - if (read_populate_opts(opts)) - return -1; - if ((res = read_populate_todo(&todo_list, opts))) + else if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; if (!is_rebase_i(opts)) { @@@ -3605,8 -2877,7 +3606,8 @@@ int sequencer_pick_revisions(struct rep if (!get_oid(name, &oid)) { if (!lookup_commit_reference_gently(&oid, 1)) { - enum object_type type = oid_object_info(&oid, + enum object_type type = oid_object_info(the_repository, + &oid, NULL); return error(_("%s: can't cherry-pick a %s"), name, type_name(type)); @@@ -3717,348 -2988,6 +3718,348 @@@ void append_signoff(struct strbuf *msgb strbuf_release(&sob); } +struct labels_entry { + struct hashmap_entry entry; + char label[FLEX_ARRAY]; +}; + +static int labels_cmp(const void *fndata, const struct labels_entry *a, + const struct labels_entry *b, const void *key) +{ + return key ? strcmp(a->label, key) : strcmp(a->label, b->label); +} + +struct string_entry { + struct oidmap_entry entry; + char string[FLEX_ARRAY]; +}; + +struct label_state { + struct oidmap commit2label; + struct hashmap labels; + struct strbuf buf; +}; + +static const char *label_oid(struct object_id *oid, const char *label, + struct label_state *state) +{ + struct labels_entry *labels_entry; + struct string_entry *string_entry; + struct object_id dummy; + size_t len; + int i; + + string_entry = oidmap_get(&state->commit2label, oid); + if (string_entry) + return string_entry->string; + + /* + * For "uninteresting" commits, i.e. commits that are not to be + * rebased, and which can therefore not be labeled, we use a unique + * abbreviation of the commit name. This is slightly more complicated + * than calling find_unique_abbrev() because we also need to make + * sure that the abbreviation does not conflict with any other + * label. + * + * We disallow "interesting" commits to be labeled by a string that + * is a valid full-length hash, to ensure that we always can find an + * abbreviation for any uninteresting commit's names that does not + * clash with any other label. + */ + if (!label) { + char *p; + + strbuf_reset(&state->buf); + strbuf_grow(&state->buf, GIT_SHA1_HEXSZ); + label = p = state->buf.buf; + + find_unique_abbrev_r(p, oid, default_abbrev); + + /* + * We may need to extend the abbreviated hash so that there is + * no conflicting label. + */ + if (hashmap_get_from_hash(&state->labels, strihash(p), p)) { + size_t i = strlen(p) + 1; + + oid_to_hex_r(p, oid); + for (; i < GIT_SHA1_HEXSZ; i++) { + char save = p[i]; + p[i] = '\0'; + if (!hashmap_get_from_hash(&state->labels, + strihash(p), p)) + break; + p[i] = save; + } + } + } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ && + !get_oid_hex(label, &dummy)) || + (len == 1 && *label == '#') || + hashmap_get_from_hash(&state->labels, + strihash(label), label)) { + /* + * If the label already exists, or if the label is a valid full + * OID, or the label is a '#' (which we use as a separator + * between merge heads and oneline), we append a dash and a + * number to make it unique. + */ + struct strbuf *buf = &state->buf; + + strbuf_reset(buf); + strbuf_add(buf, label, len); + + for (i = 2; ; i++) { + strbuf_setlen(buf, len); + strbuf_addf(buf, "-%d", i); + if (!hashmap_get_from_hash(&state->labels, + strihash(buf->buf), + buf->buf)) + break; + } + + label = buf->buf; + } + + FLEX_ALLOC_STR(labels_entry, label, label); + hashmap_entry_init(labels_entry, strihash(label)); + hashmap_add(&state->labels, labels_entry); + + FLEX_ALLOC_STR(string_entry, string, label); + oidcpy(&string_entry->entry.oid, oid); + oidmap_put(&state->commit2label, string_entry); + + return string_entry->string; +} + +static int make_script_with_merges(struct pretty_print_context *pp, + struct rev_info *revs, FILE *out, + unsigned flags) +{ + int keep_empty = flags & TODO_LIST_KEEP_EMPTY; + int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS; + struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT; + struct strbuf label = STRBUF_INIT; + struct commit_list *commits = NULL, **tail = &commits, *iter; + struct commit_list *tips = NULL, **tips_tail = &tips; + struct commit *commit; + struct oidmap commit2todo = OIDMAP_INIT; + struct string_entry *entry; + struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT, + shown = OIDSET_INIT; + struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT }; + + int abbr = flags & TODO_LIST_ABBREVIATE_CMDS; + const char *cmd_pick = abbr ? "p" : "pick", + *cmd_label = abbr ? "l" : "label", + *cmd_reset = abbr ? "t" : "reset", + *cmd_merge = abbr ? "m" : "merge"; + + oidmap_init(&commit2todo, 0); + oidmap_init(&state.commit2label, 0); + hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0); + strbuf_init(&state.buf, 32); + + if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) { + struct object_id *oid = &revs->cmdline.rev[0].item->oid; + FLEX_ALLOC_STR(entry, string, "onto"); + oidcpy(&entry->entry.oid, oid); + oidmap_put(&state.commit2label, entry); + } + + /* + * First phase: + * - get onelines for all commits + * - gather all branch tips (i.e. 2nd or later parents of merges) + * - label all branch tips + */ + while ((commit = get_revision(revs))) { + struct commit_list *to_merge; + int is_octopus; + const char *p1, *p2; + struct object_id *oid; + int is_empty; + + tail = &commit_list_insert(commit, tail)->next; + oidset_insert(&interesting, &commit->object.oid); + + is_empty = is_original_commit_empty(commit); + if (!is_empty && (commit->object.flags & PATCHSAME)) + continue; + + strbuf_reset(&oneline); + pretty_print_commit(pp, commit, &oneline); + + to_merge = commit->parents ? commit->parents->next : NULL; + if (!to_merge) { + /* non-merge commit: easy case */ + strbuf_reset(&buf); + if (!keep_empty && is_empty) + strbuf_addf(&buf, "%c ", comment_line_char); + strbuf_addf(&buf, "%s %s %s", cmd_pick, + oid_to_hex(&commit->object.oid), + oneline.buf); + + FLEX_ALLOC_STR(entry, string, buf.buf); + oidcpy(&entry->entry.oid, &commit->object.oid); + oidmap_put(&commit2todo, entry); + + continue; + } + + is_octopus = to_merge && to_merge->next; + + if (is_octopus) + BUG("Octopus merges not yet supported"); + + /* Create a label */ + strbuf_reset(&label); + if (skip_prefix(oneline.buf, "Merge ", &p1) && + (p1 = strchr(p1, '\'')) && + (p2 = strchr(++p1, '\''))) + strbuf_add(&label, p1, p2 - p1); + else if (skip_prefix(oneline.buf, "Merge pull request ", + &p1) && + (p1 = strstr(p1, " from "))) + strbuf_addstr(&label, p1 + strlen(" from ")); + else + strbuf_addbuf(&label, &oneline); + + for (p1 = label.buf; *p1; p1++) + if (isspace(*p1)) + *(char *)p1 = '-'; + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s -C %s", + cmd_merge, oid_to_hex(&commit->object.oid)); + + /* label the tip of merged branch */ + oid = &to_merge->item->object.oid; + strbuf_addch(&buf, ' '); + + if (!oidset_contains(&interesting, oid)) + strbuf_addstr(&buf, label_oid(oid, NULL, &state)); + else { + tips_tail = &commit_list_insert(to_merge->item, + tips_tail)->next; + + strbuf_addstr(&buf, label_oid(oid, label.buf, &state)); + } + strbuf_addf(&buf, " # %s", oneline.buf); + + FLEX_ALLOC_STR(entry, string, buf.buf); + oidcpy(&entry->entry.oid, &commit->object.oid); + oidmap_put(&commit2todo, entry); + } + + /* + * Second phase: + * - label branch points + * - add HEAD to the branch tips + */ + for (iter = commits; iter; iter = iter->next) { + struct commit_list *parent = iter->item->parents; + for (; parent; parent = parent->next) { + struct object_id *oid = &parent->item->object.oid; + if (!oidset_contains(&interesting, oid)) + continue; + if (!oidset_contains(&child_seen, oid)) + oidset_insert(&child_seen, oid); + else + label_oid(oid, "branch-point", &state); + } + + /* Add HEAD as implict "tip of branch" */ + if (!iter->next) + tips_tail = &commit_list_insert(iter->item, + tips_tail)->next; + } + + /* + * Third phase: output the todo list. This is a bit tricky, as we + * want to avoid jumping back and forth between revisions. To + * accomplish that goal, we walk backwards from the branch tips, + * gathering commits not yet shown, reversing the list on the fly, + * then outputting that list (labeling revisions as needed). + */ + fprintf(out, "%s onto\n", cmd_label); + for (iter = tips; iter; iter = iter->next) { + struct commit_list *list = NULL, *iter2; + + commit = iter->item; + if (oidset_contains(&shown, &commit->object.oid)) + continue; + entry = oidmap_get(&state.commit2label, &commit->object.oid); + + if (entry) + fprintf(out, "\n# Branch %s\n", entry->string); + else + fprintf(out, "\n"); + + while (oidset_contains(&interesting, &commit->object.oid) && + !oidset_contains(&shown, &commit->object.oid)) { + commit_list_insert(commit, &list); + if (!commit->parents) { + commit = NULL; + break; + } + commit = commit->parents->item; + } + + if (!commit) + fprintf(out, "%s %s\n", cmd_reset, + rebase_cousins ? "onto" : "[new root]"); + else { + const char *to = NULL; + + entry = oidmap_get(&state.commit2label, + &commit->object.oid); + if (entry) + to = entry->string; + else if (!rebase_cousins) + to = label_oid(&commit->object.oid, NULL, + &state); + + if (!to || !strcmp(to, "onto")) + fprintf(out, "%s onto\n", cmd_reset); + else { + strbuf_reset(&oneline); + pretty_print_commit(pp, commit, &oneline); + fprintf(out, "%s %s # %s\n", + cmd_reset, to, oneline.buf); + } + } + + for (iter2 = list; iter2; iter2 = iter2->next) { + struct object_id *oid = &iter2->item->object.oid; + entry = oidmap_get(&commit2todo, oid); + /* only show if not already upstream */ + if (entry) + fprintf(out, "%s\n", entry->string); + entry = oidmap_get(&state.commit2label, oid); + if (entry) + fprintf(out, "%s %s\n", + cmd_label, entry->string); + oidset_insert(&shown, oid); + } + + free_commit_list(list); + } + + free_commit_list(commits); + free_commit_list(tips); + + strbuf_release(&label); + strbuf_release(&oneline); + strbuf_release(&buf); + + oidmap_free(&commit2todo, 1); + oidmap_free(&state.commit2label, 1); + hashmap_free(&state.labels, 1); + strbuf_release(&state.buf); + + return 0; +} + int sequencer_make_script(FILE *out, int argc, const char **argv, unsigned flags) { @@@ -4069,13 -2998,11 +4070,13 @@@ struct commit *commit; int keep_empty = flags & TODO_LIST_KEEP_EMPTY; const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; + int rebase_merges = flags & TODO_LIST_REBASE_MERGES; init_revisions(&revs, NULL); revs.verbose_header = 1; - revs.max_parents = 1; - revs.cherry_pick = 1; + if (!rebase_merges) + revs.max_parents = 1; + revs.cherry_mark = 1; revs.limited = 1; revs.reverse = 1; revs.right_only = 1; @@@ -4099,16 -3026,9 +4100,16 @@@ if (prepare_revision_walk(&revs) < 0) return error(_("make_script: error preparing revisions")); + if (rebase_merges) + return make_script_with_merges(&pp, &revs, out, flags); + while ((commit = get_revision(&revs))) { + int is_empty = is_original_commit_empty(commit); + + if (!is_empty && (commit->object.flags & PATCHSAME)) + continue; strbuf_reset(&buf); - if (!keep_empty && is_original_commit_empty(commit)) + if (!keep_empty && is_empty) strbuf_addf(&buf, "%c ", comment_line_char); strbuf_addf(&buf, "%s %s ", insn, oid_to_hex(&commit->object.oid)); @@@ -4195,16 -3115,8 +4196,16 @@@ int transform_todos(unsigned flags short_commit_name(item->commit) : oid_to_hex(&item->commit->object.oid); + if (item->command == TODO_MERGE) { + if (item->flags & TODO_EDIT_MERGE_MSG) + strbuf_addstr(&buf, " -c"); + else + strbuf_addstr(&buf, " -C"); + } + strbuf_addf(&buf, " %s", oid); } + /* add all the rest */ if (!item->arg_len) strbuf_addch(&buf, '\n'); @@@ -4384,7 -3296,8 +4385,7 @@@ int skip_unnecessary_picks(void oid = &item->commit->object.oid; } if (i > 0) { - int offset = i < todo_list.nr ? - todo_list.items[i].offset_in_buf : todo_list.buf.len; + int offset = get_item_line_offset(&todo_list, i); const char *done_path = rebase_path_done(); fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); @@@ -4479,7 -3392,7 +4480,7 @@@ int rearrange_squash(void struct subject2item_entry *entry; next[i] = tail[i] = -1; - if (item->command >= TODO_EXEC) { + if (!item->commit || item->command == TODO_DROP) { subjects[i] = NULL; continue; } @@@ -4564,10 -3477,12 +4565,10 @@@ continue; while (cur >= 0) { - int offset = todo_list.items[cur].offset_in_buf; - int end_offset = cur + 1 < todo_list.nr ? - todo_list.items[cur + 1].offset_in_buf : - todo_list.buf.len; - char *bol = todo_list.buf.buf + offset; - char *eol = todo_list.buf.buf + end_offset; + const char *bol = + get_item_line(&todo_list, cur); + const char *eol = + get_item_line(&todo_list, cur + 1); /* replace 'pick', by 'fixup' or 'squash' */ command = todo_list.items[cur].command; diff --combined shell.c index 0200d10796,3ce77b8e34..40084a3013 --- a/shell.c +++ b/shell.c @@@ -1,8 -1,9 +1,9 @@@ #include "cache.h" #include "quote.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "strbuf.h" #include "run-command.h" + #include "alias.h" #define COMMAND_DIR "git-shell-commands" #define HELP_COMMAND COMMAND_DIR "/help" diff --combined t/t9902-completion.sh index 1b6d275254,5863b1acac..36deb0b123 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@@ -13,7 -13,7 +13,7 @@@ complete ( return 0 } - # Be careful when updating this list: + # Be careful when updating these lists: # # (1) The build tree may have build artifact from different branch, or # the user's $PATH may have a random executable that may begin @@@ -30,7 -30,8 +30,8 @@@ # completion for "git ", and a plumbing is excluded. "add", # "filter-branch" and "ls-files" are listed for this. - GIT_TESTING_COMMAND_COMPLETION='add checkout check-attr filter-branch ls-files' + GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr filter-branch ls-files' + GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout filter-branch' . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" @@@ -84,11 -85,10 +85,11 @@@ test_completion ( then printf '%s\n' "$2" >expected else - sed -e 's/Z$//' >expected + sed -e 's/Z$//' |sort >expected fi && run_completion "$1" && - test_cmp expected out + sort out >out_sorted && + test_cmp expected out_sorted } # Test __gitcomp. @@@ -401,46 -401,6 +402,46 @@@ test_expect_success '__gitdir - remote test_cmp expected "$actual" ' + +test_expect_success '__git_dequote - plain unquoted word' ' + __git_dequote unquoted-word && + verbose test unquoted-word = "$dequoted_word" +' + +# input: b\a\c\k\'\\\"s\l\a\s\h\es +# expected: back'\"slashes +test_expect_success '__git_dequote - backslash escaped' ' + __git_dequote "b\a\c\k\\'\''\\\\\\\"s\l\a\s\h\es" && + verbose test "back'\''\\\"slashes" = "$dequoted_word" +' + +# input: sin'gle\' '"quo'ted +# expected: single\ "quoted +test_expect_success '__git_dequote - single quoted' ' + __git_dequote "'"sin'gle\\\\' '\\\"quo'ted"'" && + verbose test '\''single\ "quoted'\'' = "$dequoted_word" +' + +# input: dou"ble\\" "\"\quot"ed +# expected: double\ "\quoted +test_expect_success '__git_dequote - double quoted' ' + __git_dequote '\''dou"ble\\" "\"\quot"ed'\'' && + verbose test '\''double\ "\quoted'\'' = "$dequoted_word" +' + +# input: 'open single quote +test_expect_success '__git_dequote - open single quote' ' + __git_dequote "'\''open single quote" && + verbose test "open single quote" = "$dequoted_word" +' + +# input: "open double quote +test_expect_success '__git_dequote - open double quote' ' + __git_dequote "\"open double quote" && + verbose test "open double quote" = "$dequoted_word" +' + + test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' ' sed -e "s/Z$//g" >expected <<-EOF && with-trailing-space Z @@@ -1209,124 -1169,6 +1210,124 @@@ test_expect_success 'teardown after re git remote remove other ' + +test_path_completion () +{ + test $# = 2 || error "bug in the test script: not 2 parameters to test_path_completion" + + local cur="$1" expected="$2" + echo "$expected" >expected && + ( + # In the following tests calling this function we only + # care about how __git_complete_index_file() deals with + # unusual characters in path names. By requesting only + # untracked files we dont have to bother adding any + # paths to the index in those tests. + __git_complete_index_file --others && + print_comp + ) && + test_cmp expected out +} + +test_expect_success 'setup for path completion tests' ' + mkdir simple-dir \ + "spaces in dir" \ + árvíztűrő && + touch simple-dir/simple-file \ + "spaces in dir/spaces in file" \ + "árvíztűrő/Сайн яваарай" && + if test_have_prereq !MINGW && + mkdir BS\\dir \ + '$'separators\034in\035dir'' && + touch BS\\dir/DQ\"file \ + '$'separators\034in\035dir/sep\036in\037file'' + then + test_set_prereq FUNNYNAMES + else + rm -rf BS\\dir '$'separators\034in\035dir'' + fi +' + +test_expect_success '__git_complete_index_file - simple' ' + test_path_completion simple simple-dir && # Bash is supposed to + # add the trailing /. + test_path_completion simple-dir/simple simple-dir/simple-file +' + +test_expect_success \ + '__git_complete_index_file - escaped characters on cmdline' ' + test_path_completion spac "spaces in dir" && # Bash will turn this + # into "spaces\ in\ dir" + test_path_completion "spaces\\ i" \ + "spaces in dir" && + test_path_completion "spaces\\ in\\ dir/s" \ + "spaces in dir/spaces in file" && + test_path_completion "spaces\\ in\\ dir/spaces\\ i" \ + "spaces in dir/spaces in file" +' + +test_expect_success \ + '__git_complete_index_file - quoted characters on cmdline' ' + # Testing with an opening but without a corresponding closing + # double quote is important. + test_path_completion \"spac "spaces in dir" && + test_path_completion "\"spaces i" \ + "spaces in dir" && + test_path_completion "\"spaces in dir/s" \ + "spaces in dir/spaces in file" && + test_path_completion "\"spaces in dir/spaces i" \ + "spaces in dir/spaces in file" +' + +test_expect_success '__git_complete_index_file - UTF-8 in ls-files output' ' + test_path_completion á árvíztűrő && + test_path_completion árvíztűrő/С "árvíztűrő/Сайн яваарай" +' + +test_expect_success FUNNYNAMES \ + '__git_complete_index_file - C-style escapes in ls-files output' ' + test_path_completion BS \ + BS\\dir && + test_path_completion BS\\\\d \ + BS\\dir && + test_path_completion BS\\\\dir/DQ \ + BS\\dir/DQ\"file && + test_path_completion BS\\\\dir/DQ\\\"f \ + BS\\dir/DQ\"file +' + +test_expect_success FUNNYNAMES \ + '__git_complete_index_file - \nnn-escaped characters in ls-files output' ' + test_path_completion sep '$'separators\034in\035dir'' && + test_path_completion '$'separators\034i'' \ + '$'separators\034in\035dir'' && + test_path_completion '$'separators\034in\035dir/sep'' \ + '$'separators\034in\035dir/sep\036in\037file'' && + test_path_completion '$'separators\034in\035dir/sep\036i'' \ + '$'separators\034in\035dir/sep\036in\037file'' +' + +test_expect_success FUNNYNAMES \ + '__git_complete_index_file - removing repeated quoted path components' ' + test_when_finished rm -r repeated-quoted && + mkdir repeated-quoted && # A directory whose name in itself + # would not be quoted ... + >repeated-quoted/0-file && + >repeated-quoted/1\"file && # ... but here the file makes the + # dirname quoted ... + >repeated-quoted/2-file && + >repeated-quoted/3\"file && # ... and here, too. + + # Still, we shold only list the directory name only once. + test_path_completion repeated repeated-quoted +' + +test_expect_success 'teardown after path completion tests' ' + rm -rf simple-dir "spaces in dir" árvíztűrő \ + BS\\dir '$'separators\034in\035dir'' +' + + test_expect_success '__git_get_config_variables' ' cat >expect <<-EOF && name-1 @@@ -1350,17 -1192,6 +1351,6 @@@ test_expect_success '__git_pretty_alias test_cmp expect actual ' - test_expect_success '__git_aliases' ' - cat >expect <<-EOF && - ci - co - EOF - test_config alias.ci commit && - test_config alias.co checkout && - __git_aliases >actual && - test_cmp expect actual - ' - test_expect_success 'basic' ' run_completion "git " && # built-in @@@ -1524,7 -1355,6 +1514,7 @@@ test_expect_success 'complete files' echo "expected" > .gitignore && echo "out" >> .gitignore && + echo "out_sorted" >> .gitignore && git add .gitignore && test_completion "git commit " ".gitignore" && @@@ -1670,13 -1500,6 +1660,6 @@@ test_expect_success 'sourcing the compl verbose test -z "$__git_all_commands" ' - test_expect_success 'sourcing the completion script clears cached porcelain commands' ' - __git_compute_porcelain_commands && - verbose test -n "$__git_porcelain_commands" && - . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && - verbose test -z "$__git_porcelain_commands" - ' - test_expect_success !GETTEXT_POISON 'sourcing the completion script clears cached merge strategies' ' __git_compute_merge_strategies && verbose test -n "$__git_merge_strategies" &&